diff --git a/.clang-format b/.clang-format index 1384c682..d87cfa50 100644 --- a/.clang-format +++ b/.clang-format @@ -7,10 +7,10 @@ AlignOperands: Align AllowAllArgumentsOnNextLine: false AllowAllConstructorInitializersOnNextLine: false AllowAllParametersOfDeclarationOnNextLine: false -AllowShortBlocksOnASingleLine: Always +AllowShortBlocksOnASingleLine: false AllowShortCaseLabelsOnASingleLine: false -AllowShortFunctionsOnASingleLine: All -AllowShortIfStatementsOnASingleLine: Always +AllowShortFunctionsOnASingleLine: None +AllowShortIfStatementsOnASingleLine: false AllowShortLambdasOnASingleLine: All AllowShortLoopsOnASingleLine: true AlwaysBreakAfterReturnType: None @@ -33,7 +33,7 @@ BreakBeforeBinaryOperators: None BreakBeforeTernaryOperators: true BreakConstructorInitializers: BeforeColon BreakInheritanceList: BeforeColon -ColumnLimit: 0 +ColumnLimit: 80 CompactNamespaces: false ContinuationIndentWidth: 8 IndentCaseLabels: true diff --git a/common/struct_def.h b/common/struct_def.h index 41d7c41e..d2694278 100644 --- a/common/struct_def.h +++ b/common/struct_def.h @@ -167,6 +167,7 @@ typedef enum Status { #define FLAGS_WXAUTH 0x0004000 /* same as above, but also prevent parsing */ #define FLAGS_NONL 0x0008000 /* No \n in buffer */ #define FLAGS_CBURST 0x0010000 /* set to mark connection burst being sent */ +#define DEFER_USER_REG 0x0020000 /* iauth wants ircd to defer register_user() */ #define FLAGS_QUIT 0x0040000 /* QUIT :comment shows it's not a split */ #define FLAGS_SPLIT 0x0080000 /* client QUITting because of a netsplit */ #define FLAGS_HIDDEN 0x0100000 /* netsplit is behind a hostmask, @@ -194,9 +195,7 @@ typedef enum Status { #define FLAGS_AWAY 0x0020 /* user is away */ #define FLAGS_EXEMPT 0x0040 /* user is exempted from k-lines */ #define FLAGS_CLOAKED 0x0080 /* user's hostname is cloaked */ -#ifdef XLINE #define FLAGS_XLINED 0x0100 /* X-lined client */ -#endif #define FLAGS_TLS 0x0200 /* user is on a secure connection port (SSL/TLS) -- mh 2020-04-27 */ #define SEND_UMODES (FLAGS_INVISIBLE|FLAGS_OPER|FLAGS_WALLOP|FLAGS_AWAY|FLAGS_RESTRICT) #define ALL_UMODES (SEND_UMODES|FLAGS_LOCOP) @@ -261,11 +260,9 @@ typedef enum Status { #define ClearXAuth(x) ((x)->flags &= ~FLAGS_XAUTH) #define ClearWXAuth(x) ((x)->flags &= ~FLAGS_WXAUTH) #define ClearListenerInactive(x) ((x)->flags &= ~FLAGS_LISTENINACTIVE) -#ifdef XLINE #define IsXlined(x) ((x)->user && (x)->user->flags & FLAGS_XLINED) #define SetXlined(x) ((x)->user->flags |= FLAGS_XLINED) #define ClearXlined(x) ((x)->user->flags &= ~FLAGS_XLINED) -#endif #define IsCloaked(x) ((x)->user && (x)->user->flags & FLAGS_CLOAKED) #define SetCloaked(x) ((x)->user->flags |= FLAGS_CLOAKED) #define HAS_CLOAK_IP(x) (!IN6_IS_ADDR_UNSPECIFIED(&((x)->cloak_ip))) @@ -317,9 +314,7 @@ struct ConfItem { char *passwd; char *name; char *name2; -#ifdef XLINE char *name3; -#endif int port; long flags; /* I-line flags */ int pref; /* preference value */ @@ -365,9 +360,7 @@ struct ListItem { #define CONF_TKILL 0x200000 #define CONF_TOTHERKILL 0x400000 #endif -#ifdef XLINE #define CONF_XLINE 0x800000 -#endif #define CONF_OPS CONF_OPERATOR #define CONF_SERVER_MASK (CONF_CONNECT_SERVER | CONF_NOCONNECT_SERVER |\ CONF_ZCONNECT_SERVER) @@ -381,9 +374,7 @@ struct ListItem { #define CFLAG_NORESOLVE 0x00010 #define CFLAG_FALL 0x00020 #define CFLAG_NORESOLVEMATCH 0x00040 -#ifdef XLINE #define CFLAG_XEXEMPT 0x00080 -#endif #define CFLAG_REQUIRE_SASL 0x00100 /* K-Line flags */ @@ -397,9 +388,7 @@ struct ListItem { #define IsConfNoResolve(x) ((x)->flags & CFLAG_NORESOLVE) #define IsConfNoResolveMatch(x) ((x)->flags & CFLAG_NORESOLVEMATCH) #define IsConfFallThrough(x) ((x)->flags & CFLAG_FALL) -#ifdef XLINE #define IsConfXlineExempt(x) ((x)->flags & CFLAG_XEXEMPT) -#endif #define IsConfRequireSASL(x) ((x)->flags & CFLAG_REQUIRE_SASL) #define PFLAG_DELAYED 0x00001 @@ -577,13 +566,12 @@ struct Client { char passwd[PASSWDLEN+1]; char exitc; char *reason; /* additional exit message */ -#ifdef XLINE /* Those logically should be in anUser struct, but would be null for - ** all remote users... so better waste two pointers for all local - ** non-users than two pointers for all remote users. --B. */ + ** all remote users... so better waste three pointers for all local + ** non-users than three pointers for all remote users. --B. */ + char *user1; /* 1st param of USER */ char *user2; /* 2nd param of USER */ char *user3; /* 3rd param of USER */ -#endif int caps; /* Enabled capabilities */ int cap_negotation; /* CAP negotiation is in progress. Registration must wait for "CAP END" */ aClient *sasl_service; /* The SASL service that is responsible for this user. */ @@ -978,9 +966,7 @@ typedef enum ServerChannels { #define EXITC_AREF 'U' /* Unauthorized by iauth */ #define EXITC_AREFQ 'u' /* Unauthorized by iauth, be quiet */ #define EXITC_VIRUS 'v' /* joined a channel used by PrettyPark virus */ -#ifdef XLINE #define EXITC_XLINE 'X' /* Forbidden GECOS */ -#endif #define EXITC_YLINEMAX 'Y' /* Y:line max clients limit */ /* eXternal authentication slave OPTions */ diff --git a/iauth/a_conf.c b/iauth/a_conf.c index 6290f2c3..16501f6a 100644 --- a/iauth/a_conf.c +++ b/iauth/a_conf.c @@ -1,6 +1,7 @@ /************************************************************************ * IRC - Internet Relay Chat, iauth/a_conf.c * Copyright (C) 1998 Christophe Kalt + * Copyright (C) 2025 IRCnet.com team * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -241,6 +242,10 @@ char *conf_read(char *cfile) (*last)->address = NULL; (*last)->timeout = timeout; (*last)->reason = NULL; + (*last)->wait_for_reg = 0; + (*last)->skip_if_sasl = 0; + (*last)->wait_for_ident = 0; + (*last)->skip_if_ident = 0; (*last)->delayed = o_del; (*last)->port = 0; if (Mlist[i] == &Module_rfc931) @@ -278,16 +283,110 @@ char *conf_read(char *cfile) } *ch = '\0'; if (!strncasecmp(buffer+1, "option = ", 9)) - { + { if ((*last)->opt) - conf_err(lnnb, - "Duplicate option keyword: ignored.", - cfile); + conf_err(lnnb, "Duplicate option keyword: ignored.", + cfile); else - (*last)->opt = - mystrdup(buffer + 10); + { + /* Parse comma-separated option tokens */ + const char *p = buffer + 10, *q; + char tmpbuf[512]; + int j = 0; + + (*last)->opt = mystrdup(buffer + 10); + + /* trim leading spaces/tabs */ + while (*p == ' ' || *p == '\t') + { + p++; + } + /* copy line to tmpbuf in lowercase */ + while (*p && *p != '\n' && j < (int) sizeof(tmpbuf) - 1) + { + char ch2 = *p++; + if (ch2 >= 'A' && ch2 <= 'Z') + { + ch2 = (char) (ch2 - 'A' + 'a'); + } + tmpbuf[j++] = ch2; + } + tmpbuf[j] = '\0'; + + /* iterate tokens split by commas; token may be key + * or key=value */ + q = tmpbuf; + while (*q) + { + char *eq, token[256]; + const char *start, *end; + size_t token_len; + + /* skip separators and whitespace */ + while (*q == ' ' || *q == '\t' || *q == ',') + { + q++; + } + if (!*q) + { + break; + } + start = q; + while (*q && *q != ',') + { + q++; + } + end = q; + + /* trim trailing spaces */ + while (end > start && + (end[-1] == ' ' || end[-1] == '\t')) + { + end--; + } + if (end <= start) + { + continue; + } + + /* extract token */ + token_len = (size_t) (end - start); + if (token_len >= sizeof(token)) + { + token_len = sizeof(token) - 1; + } + memcpy(token, start, token_len); + token[token_len] = '\0'; + + /* split name[=value] */ + eq = strchr(token, '='); + if (eq) + { + /* we only care about the name */ + *eq = '\0'; + } + if (!strcmp(token, "wait_for_reg")) + { + (*last)->wait_for_reg = 1; + } + else if (!strcmp(token, "skip_if_sasl")) + { + (*last)->wait_for_reg = 1; + (*last)->skip_if_sasl = 1; + } + else if (!strcmp(token, "wait_for_ident")) + { + (*last)->wait_for_ident = 1; + } + else if (!strcmp(token, "skip_if_ident")) + { + (*last)->wait_for_ident = 1; + (*last)->skip_if_ident = 1; + } + } + } continue; - } + } if (!strncasecmp(buffer+1, "reason = ", 9)) { if ((*last)->reason) @@ -487,11 +586,18 @@ char *conf_read(char *cfile) printf("\t\tport: %u\n", itmp->port); if (itmp->mod->init) - { + { err = itmp->mod->init(itmp); printf("\t\tInitialization: %s\n", - (err) ? err : "Successful"); - } + (err) ? err : "Successful"); + + if (err == NULL && itmp->mod->ginit) + { + int grc = itmp->mod->ginit(itmp); + printf("\t\tGlobal initialization: %s\n", + (grc < 0) ? "Failed" : "Successful"); + } + } itmp = itmp->nexti; } } @@ -502,6 +608,11 @@ char *conf_read(char *cfile) if (itmp->mod->init) { itmp->mod->init(itmp); + + if (itmp->mod->ginit) + { + itmp->mod->ginit(itmp); + } } itmp = itmp->nexti; } @@ -524,6 +635,23 @@ int conf_match(u_int cl, AnInstance *inst) { aTarget *ttmp; + /* + * If the module is configured with option wait_for_ident, + * do not start it until the ident lookup has finished: + * - A_GOTIDENT: ident succeeded (USERID available) + * - A_NOIDENT: ident is definitively unavailable (timeout/refused/fail) + * + * If neither flag is set yet, ask the scheduler to try again later. + */ + if (inst->wait_for_ident || inst->skip_if_ident) + { + if ((cldata[cl].state & (A_GOTIDENT | A_NOIDENT)) == 0) + { + /* try again later */ + return 1; + } + } + /* general case, always matches */ if (inst->address == NULL && inst->hostname == NULL) return 0; diff --git a/iauth/a_conf_def.h b/iauth/a_conf_def.h index 1a3791db..ad41392d 100644 --- a/iauth/a_conf_def.h +++ b/iauth/a_conf_def.h @@ -32,6 +32,12 @@ struct Module * done (incoming data, timeout..) */ int (*timeout)(u_int); /* called when timeout is reached */ void (*clean)(u_int); /* finish/abort: cleanup*/ + + /* Optional global (module-wide) lifecycle hooks */ + int (*ginit)(AnInstance *); /* initialize persistent resources */ + void (*gtick)(AnInstance *); /* invoked periodically */ + int (*gwork)(AnInstance *); /* handle global events (if any) */ + void (*grelease)(AnInstance *); /* cleanup persistent resources */ }; struct Instance @@ -46,6 +52,11 @@ struct Instance aTarget *hostname; u_int timeout; u_int port; + u_char wait_for_reg; /* wait until client sent NICK/USER + (and possibly CAP/AUTHENTICATE) */ + u_char skip_if_sasl; /* skip module if SASL authentication succeeded. */ + u_char wait_for_ident; /* wait until ident lookup completes */ + u_char skip_if_ident; /* skip module if we got an ident reply */ char *reason; /* reject reason */ u_char delayed; /* delayed execution mode */ }; diff --git a/iauth/a_io.c b/iauth/a_io.c index 28542b2a..bd61bbcc 100644 --- a/iauth/a_io.c +++ b/iauth/a_io.c @@ -38,11 +38,158 @@ static char iobuf[IOBUFSIZE+1]; static char rbuf[IOBUFSIZE+1]; /* incoming ircd stream */ static int iob_len = 0, rb_len = 0; +typedef struct { + int fd; + int want_write; + AnInstance *inst; +} GFD; + +#ifndef MAXGFD +#define MAXGFD MAXI +#endif +static GFD gfdv[MAXGFD]; /* fd == 0 indicates a free (unused) entry */ + +static void gfd_clear_slot(int i) +{ + gfdv[i].fd = 0; + gfdv[i].want_write = 0; + gfdv[i].inst = NULL; +} + +static int gfd_used(int i) +{ + return gfdv[i].fd > 0 && gfdv[i].inst != NULL; +} + +/* Registers a global file descriptor for the given instance */ +int io_register_gfd(AnInstance *inst, int fd, int want_write) +{ + int i; + if (!inst || fd <= 0) + { + return -1; + } + for (i = 0; i < MAXGFD; i++) + { + if (!gfd_used(i)) + { + gfdv[i].fd = fd; + gfdv[i].want_write = want_write ? 1 : 0; + gfdv[i].inst = inst; + return 0; + } + } + return -1; +} + +/* Updates write-interest flag for a previously registered global + * file descriptor. + * Returns 0 on success, -1 if the (inst, fd) pair was not found. + */ +int io_update_gfd(AnInstance *inst, int fd, int want_write) +{ + int i; + + if (!inst || fd <= 0) + { + return -1; + } + + for (i = 0; i < MAXGFD; i++) + { + if (gfd_used(i) && gfdv[i].inst == inst && gfdv[i].fd == fd) + { + gfdv[i].want_write = want_write ? 1 : 0; + return 0; + } + } + return -1; +} + +/* Unregisters all global file descriptors belonging to an instance */ +void io_unregister_gfd(AnInstance *inst) +{ + int i; + if (!inst) + { + return; + } + for (i = 0; i < MAXGFD; i++) + { + if (gfdv[i].inst == inst) + { + gfd_clear_slot(i); + } + } +} + +/* Dispatches I/O event for a ready global file descriptor */ +int io_handle_global_fd(int ready_fd) +{ + int gi, handled = 0; + + for (gi = 0; gi < MAXGFD; gi++) + { + if (gfd_used(gi) && gfdv[gi].fd == ready_fd) + { + if (gfdv[gi].inst && gfdv[gi].inst->mod && + gfdv[gi].inst->mod->gwork) + { + gfdv[gi].inst->mod->gwork(gfdv[gi].inst); + } + handled = 1; + break; + } + } + return handled ? 0 : -1; +} + void init_io(void) { bzero((char *) cldata, sizeof(cldata)); } +/* + * Count iauth instances that should run after the client has sent NICK/USER + * (and possibly CAP/AUTHENTICATE). + * + * Criteria: + * - instance has wait_for_reg set (directly or implicitly) + * - and is not currently skipped: + * - skip_if_sasl: skip when A_SASL is set + * - skip_if_ident: skip when A_GOTIDENT is set + */ +static int count_wait_for_reg_instances(int cl) +{ + int n = 0; + AnInstance *it = instances; + + while (it) + { + int cm = conf_match((u_int) cl, it); + if (cm == 0) + { + if (it->wait_for_reg) + { + if (it->skip_if_sasl && (cldata[cl].state & A_SASL)) + { + /* module would be skipped later due to completed SASL */ + } + else if (it->skip_if_ident && (cldata[cl].state & A_GOTIDENT)) + { + /* module would be skipped later due to ident reply */ + } + else + { + n++; + } + } + } + it = it->nexti; + } + return n; +} + /* sendto_ircd() functions */ void vsendto_ircd(char *pattern, va_list va) { @@ -67,6 +214,40 @@ void sendto_ircd(char *pattern, ...) va_end(va); } +static char *iauth_skip_ws(char *p) +{ + while (*p == ' ' || *p == '\t') + p++; + return p; +} + +/* Reads a token up to the next whitespace and stores it in dst (bounded). + * Returns a pointer to the character right after the token. + */ +static char *iauth_read_token(char *p, char *dst, size_t dstlen) +{ + size_t i = 0; + + p = iauth_skip_ws(p); + if (!*p) + { + if (dstlen > 0) + dst[0] = '\0'; + return p; + } + + while (*p && *p != ' ' && *p != '\t') + { + if (i + 1 < dstlen) + dst[i++] = *p; + p++; + } + if (dstlen > 0) + dst[i] = '\0'; + + return p; +} + /* * next_io * @@ -74,49 +255,106 @@ void sendto_ircd(char *pattern, ...) */ static void next_io(int cl, AnInstance *last) { - DebugLog((ALOG_DIO, 0, "next_io(#%d, %x): last=%s state=0x%X", cl, last, - (last) ? last->mod->name : "", cldata[cl].state)); + /* If any module set A_DENY, stop scheduling immediately */ + if (cldata[cl].state & A_DENY) + { + DebugLog((ALOG_DSPY, 0, + "skipping further modules for client %d (A_DENY set)", cl)); + sendto_ircd("D %d %s %u ", cl, cldata[cl].itsip, cldata[cl].itsport, + cldata[cl].authuser); + return; + } - /* first, bail out immediately if the entry is flagged A_DONE */ - if (cldata[cl].state & A_DONE) - return; + DebugLog((ALOG_DIO, 0, "next_io(#%d, %x): last=%s state=0x%X", cl, last, + (last) ? last->mod->name : "", cldata[cl].state)); + + /* + * iauth distinguishes between: + * (1) modules which run immediately after the TCP + * connection is established; + * (2) wait_for_reg modules which run after the client has sent + * NICK/USER (and possibly CAP/AUTHENTICATE), but + * before registration completes (before numeric 001). + * + * This block handles (2). On the first detection that any + * wait_for_reg modules are required for this client, we notify + * ircd with: "P " + * where is the number of wait_for_reg modules. The 'P' tells + * ircd to hold registration until we later signal completion with 'H'. + */ + if (!(cldata[cl].state & A_WAIT_FOR_REG)) + { + int n = count_wait_for_reg_instances(cl); + if (n > 0) + { + sendto_log(ALOG_DSPY, LOG_DEBUG, + "sending wait_for_reg request to ircd for %d", cl); + sendto_ircd("P %d %d", cl, n); + cldata[cl].state |= A_WAIT_FOR_REG; + /* + * If called from inside a module (last != NULL), clear the current + * instance pointer. Otherwise, iauth would still think a module is + * running and could block the later "H" completion signal. + */ + if (last) + { + cldata[cl].instance = NULL; + } + return; + } + } + /* Stop here if this entry is already completed (A_DONE) */ + if (cldata[cl].state & A_DONE) + { + return; + } - /* second, make sure the last instance which ran cleaned up */ - if (cldata[cl].rfd > 0 || cldata[cl].wfd > 0) + /* Next, ensure that the previously running instance has cleaned up its FDs. + * This applies only when advancing from 'last' (i.e., switching modules). + * If last == NULL (external trigger such as 'H'), do not discard + * the currently running module. + */ + if (last && (cldata[cl].rfd > 0 || cldata[cl].wfd > 0)) + { + /* last is defined here */ + sendto_log(ALOG_IRCD | ALOG_DMISC, LOG_ERR, + "module \"%s\" didn't clean up fd's! (%d %d)", + last->mod->name, cldata[cl].rfd, cldata[cl].wfd); + if (cldata[cl].rfd > 0) + close(cldata[cl].rfd); + if (cldata[cl].wfd > 0 && cldata[cl].rfd != cldata[cl].wfd) + close(cldata[cl].wfd); + cldata[cl].rfd = cldata[cl].wfd = 0; + } + + cldata[cl].buflen = 0; + cldata[cl].mod_status = 0; + + if (last) { - /* last is defined here */ - sendto_log(ALOG_IRCD|ALOG_DMISC, LOG_ERR, - "module \"%s\" didn't clean up fd's! (%d %d)", - last->mod->name, cldata[cl].rfd, cldata[cl].wfd); - if (cldata[cl].rfd > 0) - close(cldata[cl].rfd); - if (cldata[cl].wfd > 0 && cldata[cl].rfd != cldata[cl].wfd) - close(cldata[cl].wfd); - cldata[cl].rfd = cldata[cl].wfd = 0; + cldata[cl].instance = NULL; } - - cldata[cl].buflen = 0; - cldata[cl].mod_status = 0; - cldata[cl].instance = NULL; - cldata[cl].timeout = 0; - /* third, if A_START is set, a new pass has to be started */ + cldata[cl].timeout = 0; + + /* If A_START is set, a new pass has to be started */ if (cldata[cl].state & A_START) { cldata[cl].state ^= A_START; DebugLog((ALOG_DIO, 0, "next_io(#%d, %x): Starting again", cl, last)); - last = NULL; /* start from beginning */ + /* start from beginning */ + last = NULL; } - /* fourth, find next instance to be ran */ - if (last == NULL) + /* Find next instance to be run */ + if (last == NULL) { - cldata[cl].instance = instances; - cldata[cl].ileft = 0; + cldata[cl].instance = instances; + cldata[cl].ileft = 0; } - else - cldata[cl].instance = last->nexti; + else + cldata[cl].instance = last->nexti; while (cldata[cl].instance) { @@ -133,6 +371,49 @@ static void next_io(int cl, AnInstance *last) cldata[cl].instance = cldata[cl].instance->nexti; continue; } + /* Implicit skip or defer based on option flags: + * skip_if_sasl / skip_if_ident */ + if (cldata[cl].instance) + { + AnInstance *inst = cldata[cl].instance; + const char *modname = (inst->mod && inst->mod->name) + ? inst->mod->name + : ""; + + /* Skip if SASL already succeeded */ + if (inst->skip_if_sasl && (cldata[cl].state & A_SASL)) + { + SetBit(cldata[cl].idone, inst->in); + cldata[cl].instance = inst->nexti; + DebugLog((ALOG_DIO, 0, + "skipping module \"%s\" (in=%d) due to SASL", modname, + inst->in)); + continue; + } + + /* Skip if an ident reply was already received */ + if (inst->skip_if_ident && (cldata[cl].state & A_GOTIDENT)) + { + SetBit(cldata[cl].idone, inst->in); + cldata[cl].instance = inst->nexti; + DebugLog((ALOG_DIO, 0, + "skipping module \"%s\" (in=%d) due to ident", + modname, inst->in)); + continue; + } + + /* Defer wait_for_reg modules until client registration */ + if (inst->wait_for_reg && !(cldata[cl].state & A_REG_PENDING)) + { + cldata[cl].instance = inst->nexti; + DebugLog((ALOG_DIO, 0, + "deferring module \"%s\" (in=%d) " + "waiting for client registration", + modname, inst->in)); + continue; + } + } + cm = conf_match(cl, cldata[cl].instance); DebugLog((ALOG_DIO, 0, "conf_match(#%d, %x, goth=%d, noh=%d) said \"%s\" for %x (%s)", @@ -157,7 +438,22 @@ static void next_io(int cl, AnInstance *last) cl, last, cldata[cl].ileft)); if (cldata[cl].ileft == 0) { - /* we are done */ + /* we are done */ + if (cldata[cl].state & A_DENY) + { + sendto_log(ALOG_DSPY, LOG_DEBUG, + "suppressing D for %d (A_DENY set)", cl); + return; + } + if ((cldata[cl].state & A_WAIT_FOR_REG) && + !(cldata[cl].state & A_REG_PENDING)) + { + sendto_log(ALOG_DSPY, LOG_DEBUG, + "deferring D for %d (P sent; waiting for " + "client registration)", + cl); + return; + } sendto_ircd("D %d %s %u ", cl, cldata[cl].itsip, cldata[cl].itsport); cldata[cl].state |= A_DONE; @@ -180,7 +476,14 @@ static void next_io(int cl, AnInstance *last) cldata[cl].state |= A_DELAYEDSENT; } - cldata[cl].timeout = time(NULL) + cldata[cl].instance->timeout; + DebugLog((ALOG_DIO, 0, + "next_io(#%d): calling %s->start() with instance=%p", cl, + cldata[cl].instance && cldata[cl].instance->mod + ? cldata[cl].instance->mod->name + : "", + cldata[cl].instance)); + + cldata[cl].timeout = time(NULL) + cldata[cl].instance->timeout; r = cldata[cl].instance->mod->start(cl); DebugLog((ALOG_DIO, 0, "next_io(#%d, %x): %s->start() returned %d", @@ -203,7 +506,7 @@ static void next_io(int cl, AnInstance *last) */ static void parse_ircd(void) { - char *ch, *chp, *buf = iobuf; + char *ch, *chp, *buf = iobuf, *p; int cl = -1, ncl; iobuf[iob_len] = '\0'; @@ -258,7 +561,11 @@ static void parse_ircd(void) free(cldata[cl].inbuffer); cldata[cl].inbuffer = NULL; } - cldata[cl].user[0] = '\0'; + cldata[cl].nick[0] = '\0'; + cldata[cl].user1[0] = '\0'; + cldata[cl].user2[0] = '\0'; + cldata[cl].user3[0] = '\0'; + cldata[cl].realname[0] = '\0'; cldata[cl].passwd[0] = '\0'; cldata[cl].host[0] = '\0'; bzero(cldata[cl].idone, BDSIZE); @@ -420,7 +727,7 @@ static void parse_ircd(void) } if (cldata[cl].state & A_IGNORE) break; - strcpy(cldata[cl].user, chp+2); + strcpy(cldata[cl].user1, chp + 2); cldata[cl].state |= A_GOTU|A_START; if (cldata[cl].instance == NULL) next_io(cl, NULL); @@ -467,6 +774,85 @@ static void parse_ircd(void) case 'M': /* RPL_HELLO to be exact, but who cares. */ strConnLen = sprintf(strConn, ":%s 020 * :", chp+2); + break; + case 'S': /* SASL status: 1 = success */ + if (chp[2] == '1') + { + cldata[cl].state |= A_SASL; + } + break; + case 'H': /* ircd received NICK/USER and possibly CAP/AUTHENTICATE + and is waiting for iauth before registering the user */ + /* If already denied, ignore 'H' to avoid deferring teardown */ + sendto_log(ALOG_DSPY, LOG_DEBUG, + "received registration pending trigger (H) for client " + "%d: [%s]", + cl, chp); + + /* Entry must be active */ + if (!(cldata[cl].state & A_ACTIVE)) + { + sendto_log(ALOG_IRCD, LOG_WARNING, + "Warning: Entry %d [H] is not active.", cl); + break; + } + + /* Parse and store values of: + * H : */ + p = chp + 1; + p = iauth_skip_ws(p); + + p = iauth_read_token(p, cldata[cl].nick, sizeof(cldata[cl].nick)); + p = iauth_read_token(p, cldata[cl].user1, sizeof(cldata[cl].user1)); + p = iauth_read_token(p, cldata[cl].user2, sizeof(cldata[cl].user2)); + p = iauth_read_token(p, cldata[cl].user3, sizeof(cldata[cl].user3)); + + p = iauth_skip_ws(p); + if (*p == ':') + p++; + p = iauth_skip_ws(p); + + if (*p) + { + strncpy(cldata[cl].realname, p, + sizeof(cldata[cl].realname) - 1); + cldata[cl].realname[sizeof(cldata[cl].realname) - 1] = '\0'; + } + else + { + cldata[cl].realname[0] = '\0'; + } + + /* Mark registration pending */ + cldata[cl].state |= A_REG_PENDING; + sendto_log(ALOG_DSPY, LOG_DEBUG, + "marking registration pending for %d (state=0x%lx)", cl, + (unsigned long) cldata[cl].state); + + /* If a module is still running, wait until it completes */ + if (cldata[cl].rfd > 0 || cldata[cl].wfd > 0) + { + const char *modname = ""; + /* reschedule once current module completes */ + cldata[cl].state |= A_START; + if (cldata[cl].instance && cldata[cl].instance->mod && + cldata[cl].instance->mod->name) + { + modname = cldata[cl].instance->mod->name; + } + sendto_log(ALOG_DSPY, LOG_DEBUG, + "deferring until current module " + "completes (cl=%d, running=%s)", + cl, modname); + break; + } + + sendto_log(ALOG_DSPY, LOG_DEBUG, + "no active module; starting wait_for_reg modules now " + "(cl=%d)", + cl); + next_io(cl, NULL); + break; default: sendto_log(ALOG_IRCD, LOG_ERR, "Unexpected data [%s]", @@ -528,9 +914,10 @@ void loop_io(void) int nbr_pfds = 0; #endif - int i, nfds = 0; + int gi, i, nfds = 0; struct timeval wait; time_t now = time(NULL); + struct pollfd *pf; #if !defined(USE_POLL) FD_ZERO(&read_set); @@ -543,10 +930,43 @@ void loop_io(void) pfd->fd = -1; #endif /* USE_POLL */ - SET_READ_EVENT(0); nfds = 1; /* ircd stream */ -#if defined(USE_POLL) && defined(IAUTH_DEBUG) + /* always register fd 0 (ircd stream) first */ + SET_READ_EVENT(0); + nfds = 1; + + /* now register global file descriptors */ +#if !defined(USE_POLL) + for (gi = 0; gi < MAXGFD; gi++) + { + if (gfd_used(gi)) + { + FD_SET(gfdv[gi].fd, &read_set); + if (gfdv[gi].want_write) + { + FD_SET(gfdv[gi].fd, &write_set); + } + if (gfdv[gi].fd > highfd) + { + highfd = gfdv[gi].fd; + } + } + } +#else + for (gi = 0; gi < MAXGFD; gi++) + { + if (gfd_used(gi)) + { + CHECK_PFD(gfdv[gi].fd); + pfd->events |= POLLSETREADFLAGS; + if (gfdv[gi].want_write) + { + pfd->events |= POLLSETWRITEFLAGS; + } + } + } + /* reset fd->client mapping: global FDs remain -1 */ for (i = 0; i < MAXCONNECTIONS; i++) - fd2cl[i] = -1; /* sanity */ + fd2cl[i] = -1; #endif for (i = 0; i <= cl_highest; i++) { @@ -583,18 +1003,19 @@ void loop_io(void) } } - DebugLog((ALOG_DIO, 0, "io_loop(): checking for %d fd's", nfds)); wait.tv_sec = 5; wait.tv_usec = 0; #if !defined(USE_POLL) nfds = select(highfd + 1, (SELECT_FDSET_TYPE *)&read_set, (SELECT_FDSET_TYPE *)&write_set, 0, &wait); - DebugLog((ALOG_DIO, 0, "io_loop(): select() returned %d, errno = %d", - nfds, errno)); + if (nfds < 0) + DebugLog((ALOG_DIO, 0, "io_loop(): select() returned %d, errno = %d", + nfds, errno)); #else nfds = poll(poll_fdarray, nbr_pfds, wait.tv_sec * 1000 + wait.tv_usec/1000 ); - DebugLog((ALOG_DIO, 0, "io_loop(): poll() returned %d, errno = %d", - nfds, errno)); + if (nfds < 0) + DebugLog((ALOG_DIO, 0, "io_loop(): poll() returned %d, errno = %d", + nfds, errno)); pfd = poll_fdarray; #endif if (nfds == -1) @@ -614,6 +1035,58 @@ void loop_io(void) if (nfds == 0) /* end of timeout */ return; +#if !defined(USE_POLL) + for (gi = 0; gi < MAXGFD; gi++) + { + if (gfd_used(gi)) + { + int ready = 0; + if (FD_ISSET(gfdv[gi].fd, &read_set)) + { + ready = 1; + } + if (!ready && gfdv[gi].want_write && + FD_ISSET(gfdv[gi].fd, &write_set)) + { + ready = 1; + } + if (ready) + { + if (gfdv[gi].inst && gfdv[gi].inst->mod && + gfdv[gi].inst->mod->gwork) + { + gfdv[gi].inst->mod->gwork(gfdv[gi].inst); + } + nfds--; + } + } + } +#else + for (pf = poll_fdarray; pf != poll_fdarray + nbr_pfds; ++pf) + { + int fdj; + if (pf->revents == 0) + { + continue; + } + fdj = pf->fd; + for (gi = 0; gi < MAXGFD; gi++) + { + if (gfd_used(gi) && gfdv[gi].fd == fdj) + { + if (gfdv[gi].inst && gfdv[gi].inst->mod && + gfdv[gi].inst->mod->gwork) + { + gfdv[gi].inst->mod->gwork(gfdv[gi].inst); + } + pf->revents = 0; + nfds--; + break; + } + } + } +#endif + /* no matter select() or poll() this is also fd # 0 */ if (TST_READ_EVENT(0)) nfds--; @@ -625,16 +1098,31 @@ void loop_io(void) #endif { #if defined(USE_POLL) - i = fd2cl[pfd->fd]; -# if defined(IAUTH_DEBUG) - if (i == -1) - { - sendto_log(ALOG_DALL, LOG_CRIT,"io_loop(): fatal bug"); - exit(1); - } -# endif + /* bounds check against fd2cl array and skip global FDs */ + int fdj = pfd->fd; + /* fd outside mapping range -> not a client FD */ + if (fdj < 0 || fdj >= MAXCONNECTIONS) + { + continue; + } + i = fd2cl[fdj]; + /* global or unmapped FD -> already handled in global block */ + if (i == -1) + { + continue; + } +#if defined(IAUTH_DEBUG) + /* sanity check: ensure client index is within valid range */ + if (i < 0 || i > cl_highest) + { + sendto_log(ALOG_DALL, LOG_CRIT, + "io_loop(): fatal bug (invalid cl=%d fd=%d)", i, + fdj); + exit(1); + } +#endif #endif - if (cldata[i].rfd <= 0 && cldata[i].wfd <= 0) + if (cldata[i].rfd <= 0 && cldata[i].wfd <= 0) { #if defined(USE_POLL) sendto_log(ALOG_IRCD, LOG_CRIT, @@ -777,16 +1265,23 @@ int tcp_connect(char *ourIP, char *theirIP, u_short port, char **error) */ bzero((char *)&sk, sizeof(sk)); sk.SIN_FAMILY = AFINET; - if(!inetpton(AF_INET6, ourIP, sk.sin6_addr.s6_addr)) - bcopy(minus_one, sk.sin6_addr.s6_addr, IN6ADDRSZ); - sk.SIN_PORT = htons(0); - if (bind(fd, (SAP)&sk, sizeof(sk)) < 0) - { - sprintf(errbuf, "bind() failed: %s", strerror(errno)); - *error = errbuf; - close(fd); - return -1; - } + + if (ourIP) + { + if (!inetpton(AF_INET6, ourIP, sk.sin6_addr.s6_addr)) + { + bcopy(minus_one, sk.sin6_addr.s6_addr, IN6ADDRSZ); + } + sk.SIN_PORT = htons(0); + + if (bind(fd, (SAP) &sk, sizeof(sk)) < 0) + { + sprintf(errbuf, "bind() failed: %s", strerror(errno)); + *error = errbuf; + close(fd); + return -1; + } + } set_non_blocking(fd, theirIP, port); if(!inetpton(AF_INET6, theirIP, sk.sin6_addr.s6_addr)) bcopy(minus_one, sk.sin6_addr.s6_addr, IN6ADDRSZ); @@ -803,3 +1298,38 @@ int tcp_connect(char *ourIP, char *theirIP, u_short port, char **error) return fd; } +/* + * Ident state helpers (called by modules, e.g. rfc931) + * - OK: set A_GOTIDENT + * - FAIL: set A_NOIDENT (timeout/refused/unavailable) + * Both trigger the scheduler via next_io() so wait_for_ident + * modules can proceed. + */ +void iauth_mark_ident_ok(u_int cl) +{ + if (cl >= MAXCONNECTIONS) + return; + if (!(cldata[cl].state & A_ACTIVE)) + return; + + if (!(cldata[cl].state & A_GOTIDENT)) + { + cldata[cl].state |= A_GOTIDENT; + sendto_log(ALOG_DSPY, LOG_DEBUG, "ident: ok for #%u (state=0x%lX)", cl, + (unsigned long) cldata[cl].state); + } + /* if nothing is running, try to advance */ + if (cldata[cl].instance == NULL) + next_io((int) cl, NULL); +} + +void iauth_mark_noident(u_int cl) +{ + if (cl >= MAXCONNECTIONS) + return; + + cldata[cl].state |= A_NOIDENT; + sendto_log(ALOG_DSPY, LOG_DEBUG, "ident: unavailable for #%u", cl); + if (cldata[cl].instance == NULL) + next_io((int) cl, NULL); +} diff --git a/iauth/a_io_ext.h b/iauth/a_io_ext.h index 9f8e084f..6ce736c1 100644 --- a/iauth/a_io_ext.h +++ b/iauth/a_io_ext.h @@ -45,5 +45,9 @@ EXTERN int tcp_connect (char *, char *, u_short, char **); EXTERN char strConn[256]; EXTERN int strConnLen; -/* () */ +int io_register_gfd(AnInstance *inst, int fd, int want_write); +void io_unregister_gfd(AnInstance *inst); +int io_update_gfd(AnInstance *inst, int fd, int want_write); +void iauth_mark_ident_ok(u_int cl); +void iauth_mark_noident(u_int cl); #undef EXTERN diff --git a/iauth/a_struct_def.h b/iauth/a_struct_def.h index 6ca56601..82e77028 100644 --- a/iauth/a_struct_def.h +++ b/iauth/a_struct_def.h @@ -27,7 +27,6 @@ typedef struct AuthData anAuthData; struct AuthData { /* the following are set by a_io.c and may be read by modules */ - char user[USERLEN+1]; /* username */ char passwd[PASSWDLEN+1]; /* password */ char host[HOSTLEN+1]; /* hostname */ char itsip[HOSTLEN+1]; /* client ip */ @@ -35,6 +34,11 @@ struct AuthData char ourip[HOSTLEN+1]; /* our ip */ u_short ourport; /* our port */ u_int state; /* state (general) */ + char nick[NICKLEN+1]; /* nick */ + char user1[USERLEN+1]; /* username */ + char user2[USERLEN+1]; /* umodes */ + char user3[HOSTLEN+1]; /* servername */ + char realname[REALLEN+1]; /* realname */ /* the following are set by modules */ char *authuser; /* authenticated username */ @@ -67,6 +71,25 @@ struct AuthData #define A_UNIX 0x1000 /* authuser is suitable for use by ircd */ #define A_DELAYEDSENT 0x2000 /* client already has been let in to ircd */ #define A_DENY 0x8000 /* connection should be denied access */ +#define A_SASL 0x10000 /* SASL auth succeeded */ + +/* iauth has detected that one or more wait_for_reg modules + * apply to this client and has sent "P " to ircd. + * ircd will now hold registration until the client has completed + * its introduction (NICK/USER and possibly CAP/AUTHENTICATE), + * after which ircd will signal readiness with "H". + */ +#define A_WAIT_FOR_REG 0x20000 + +/* Client has sent NICK/USER and possibly CAP/AUTHENTICATE. + * wait_for_reg modules may run. + */ +#define A_REG_PENDING 0x40000 + +/* Ident (rfc931) completion flags (used for wait_for_ident option) */ +#define A_GOTIDENT 0x80000 /* ident finished with a username */ +#define A_NOIDENT 0x100000 /* ident lookup failed or unavailable + * (timeout/refused/fail) */ #define SetBit(v,n) v[n/8] |= (1 << (n % 8)) #define UnsetBit(v,n) v[n/8] &= ~(1 << (n % 8)) diff --git a/iauth/iauth.c b/iauth/iauth.c index 01b7271e..02ff9627 100644 --- a/iauth/iauth.c +++ b/iauth/iauth.c @@ -219,9 +219,19 @@ int main(int argc, char *argv[]) write_pidfile(); while (1) - { + { + AnInstance *it = instances; loop_io(); + while (it) + { + if (it->mod->gtick) + { + it->mod->gtick(it); + } + it = it->nexti; + } + if (do_log) { sendto_log(ALOG_IRCD|ALOG_DMISC, LOG_INFO, @@ -243,6 +253,6 @@ int main(int argc, char *argv[]) } nextst = time(NULL) + 60; } - } + } } diff --git a/iauth/mod_rfc931.c b/iauth/mod_rfc931.c index 0c36b71d..7d34f5c5 100644 --- a/iauth/mod_rfc931.c +++ b/iauth/mod_rfc931.c @@ -139,6 +139,7 @@ static int rfc931_start(u_int cl) DebugLog((ALOG_D931, 0, "rfc931_start(%d): tcp_connect() reported %s", cl, error)); + iauth_mark_noident(cl); return -1; } @@ -177,9 +178,10 @@ static int rfc931_work(u_int cl) DebugLog((ALOG_D931, 0, "rfc931_work(%d): write() failed: %s", cl, strerror(errno))); + iauth_mark_noident(cl); close(cldata[cl].wfd); cldata[cl].rfd = cldata[cl].wfd = 0; - return 1; + return -1; } else st->connected += 1; @@ -262,6 +264,7 @@ static int rfc931_work(u_int cl) cldata[cl].itsip, cldata[cl].itsport, cldata[cl].authuser); + iauth_mark_ident_ok(cl); } else bad = 1; @@ -295,11 +298,15 @@ static int rfc931_work(u_int cl) cldata[cl].inbuffer); } } - /* - ** In any case, our job is done, let's cleanup. - */ - close(cldata[cl].rfd); - cldata[cl].rfd = 0; + /* + * In any case, our job is done, cleanup. + */ + if (bad) + { + iauth_mark_noident(cl); + } + close(cldata[cl].rfd); + cldata[cl].rfd = 0; return -1; } else @@ -321,6 +328,17 @@ static void rfc931_clean(u_int cl) st->clean += 1; DebugLog((ALOG_D931, 0, "rfc931_clean(%d): cleaning up", cl)); + + /* + * if the connection closed but no ident status + * (A_GOTIDENT or A_NOIDENT) has been set yet, assume ident + * is unavailable. + */ + if (!(cldata[cl].state & (A_GOTIDENT | A_NOIDENT))) + { + iauth_mark_noident(cl); + } + /* ** only one of rfd and wfd may be set at the same time, ** in any case, they would be the same fd, so only close() once @@ -347,6 +365,7 @@ static int rfc931_timeout(u_int cl) st->timeout += 1; DebugLog((ALOG_D931, 0, "rfc931_timeout(%d): calling rfc931_clean ", cl)); + iauth_mark_noident(cl); rfc931_clean(cl); return -1; } diff --git a/ircd/chkconf.c b/ircd/chkconf.c index ba83e647..aa1a3cd3 100644 --- a/ircd/chkconf.c +++ b/ircd/chkconf.c @@ -499,11 +499,9 @@ static aConfItem *initconf(void) case 'y': aconf->status = CONF_CLASS; break; -#ifdef XLINE case 'X': aconf->status = CONF_XLINE; break; -#endif default: config_error(CF_WARN, CK_FILE, CK_LINE, "unknown conf line letter (%c)\n", *tmp); @@ -547,9 +545,7 @@ static aConfItem *initconf(void) { switch (*s) { -#ifdef XLINE case 'e': -#endif case 'S': case 'R': case 'D': diff --git a/ircd/list.c b/ircd/list.c index 4ea63e2b..68b520a3 100644 --- a/ircd/list.c +++ b/ircd/list.c @@ -139,10 +139,9 @@ aClient *make_client(aClient *from) #ifdef ZIP_LINKS cptr->zip = NULL; #endif -#ifdef XLINE + cptr->user1 = NULL; cptr->user2 = NULL; cptr->user3 = NULL; -#endif cptr->cap_negotation = 0; cptr->caps = 0; cptr->sasl_service = NULL; @@ -176,12 +175,12 @@ void free_client(aClient *cptr) { MyFree(cptr->cloak_tmp); } -#ifdef XLINE + if (cptr->user1) + MyFree(cptr->user1); if (cptr->user2) MyFree(cptr->user2); if (cptr->user3) MyFree(cptr->user3); -#endif if(cptr->sasl_user) { MyFree(cptr->sasl_user); @@ -612,9 +611,7 @@ aConfItem *make_conf(void) aconf->clients = aconf->port = 0; aconf->next = NULL; aconf->host = aconf->passwd = aconf->name = aconf->name2 = NULL; -#ifdef XLINE aconf->name3 = NULL; -#endif aconf->ping = NULL; aconf->status = CONF_ILLEGAL; aconf->pref = -1; @@ -649,9 +646,7 @@ void free_conf(aConfItem *aconf) istat.is_confmem -= aconf->passwd ? strlen(aconf->passwd)+1 : 0; istat.is_confmem -= aconf->name ? strlen(aconf->name)+1 : 0; istat.is_confmem -= aconf->name2 ? strlen(aconf->name2)+1 : 0; -#ifdef XLINE istat.is_confmem -= aconf->name3 ? strlen(aconf->name3)+1 : 0; -#endif istat.is_confmem -= aconf->ping ? sizeof(*aconf->ping) : 0; istat.is_confmem -= aconf->source_ip ? strlen(aconf->source_ip)+1 : 0; istat.is_confmem -= sizeof(aConfItem); @@ -666,10 +661,8 @@ void free_conf(aConfItem *aconf) MyFree(aconf->passwd); MyFree(aconf->name); MyFree(aconf->name2); -#ifdef XLINE if (aconf->name3) MyFree(aconf->name3); -#endif MyFree(aconf); #ifdef DEBUGMODE aconfs.inuse--; diff --git a/ircd/s_auth.c b/ircd/s_auth.c index c433c080..ee65a18f 100644 --- a/ircd/s_auth.c +++ b/ircd/s_auth.c @@ -1,6 +1,7 @@ /************************************************************************ * IRC - Internet Relay Chat, ircd/s_auth.c * Copyright (C) 1992 Darren Reed + * Copyright (C) 2025 IRCnet.com team * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -114,6 +115,7 @@ int vsendto_iauth(char *pattern, va_list va) } vsprintf(abuf, pattern, va); + Debug((DEBUG_INFO, "Sending to iauth [%s]", abuf)); strcat(abuf, "\n"); p = abuf; len = strlen(p); @@ -301,6 +303,32 @@ void read_iauth(void) start = end; continue; } + if (*start == 'P') /* iauth has wait_for_reg modules */ + { + int fd = atoi(start + 2); + cptr = local[fd]; + if (cptr && MyConnect(cptr)) + { + cptr->flags |= DEFER_USER_REG; + Debug((DEBUG_INFO, + "set DEFER_USER_REG on fd=%d (flags now=0x%08x)", fd, + cptr->flags)); + + /* Did we get 'P' after NICK/USER? */ + if (MyConnect(cptr) && !IsServer(cptr) && + cptr->name[0] != '\0' && cptr->user != NULL && + !IsRegistered(cptr)) + { + Debug((DEBUG_INFO, + "got delayed P [%s] - executing register_user()", + start)); + ClearWXAuth(cptr); + register_user(cptr, cptr, cptr->name, cptr->user1); + } + } + start = end; + continue; + } if (*start != 'U' && *start != 'u' && *start != 'o' && *start != 'K' && *start != 'k' && *start != 'D') @@ -335,7 +363,7 @@ void read_iauth(void) continue; } sprintf(tbuf, "%c %d %s %u ", start[0], i, - inetntop(AF_INET6, (char *)&cptr->ip, + inetntop(AF_INET6, (char *)&cptr->ip, ipv6string, sizeof(ipv6string)), cptr->port); if (strncmp(tbuf, start, strlen(tbuf))) { @@ -363,7 +391,7 @@ void read_iauth(void) continue; } if (cptr->auth != cptr->username) - { + { istat.is_authmem -= strlen(cptr->auth) + 1; istat.is_auth -= 1; MyFree(cptr->auth); @@ -416,66 +444,88 @@ void read_iauth(void) } strncpyzt(cptr->user->username, tbuf, USERLEN+1); } - else if (start[0] == 'D') - { - /*authentication finished*/ - ClearXAuth(cptr); - SetDoneXAuth(cptr); - if (WaitingXAuth(cptr)) + else if (start[0] == 'D') + { + /*authentication finished*/ + ClearXAuth(cptr); + SetDoneXAuth(cptr); + + if ((WaitingXAuth(cptr) || (cptr->flags & DEFER_USER_REG)) && + MyConnect(cptr) && !IsServer(cptr) && + cptr->name[0] != '\0' && cptr->user != NULL && + !IsRegistered(cptr)) + { + Debug((DEBUG_INFO, "got D [%s] - executing register_user()", + start)); + ClearWXAuth(cptr); + cptr->flags &= ~DEFER_USER_REG; + register_user(cptr, cptr, cptr->name, cptr->user1); + } + else { - ClearWXAuth(cptr); - register_user(cptr, cptr, cptr->name, - cptr->user->username); + Debug((DEBUG_INFO, "got D [%s]", start)); + ClearWXAuth(cptr); + cptr->flags &= ~DEFER_USER_REG; } - else - ClearWXAuth(cptr); - } - else + } + else { - char *reason; - - /* Copy kill reason received from iauth */ - reason = strstr(start, " :"); - if (reason && *(reason + 2) != '\0') - { - if (cptr->reason) - { - MyFree(cptr->reason); - } - cptr->reason = mystrdup(reason + 2); - } - /* + char *reason; + + Debug((DEBUG_INFO, "got K [%s]", start)); + + /* Copy kill reason received from iauth */ + reason = strstr(start, " :"); + if (reason && *(reason + 2) != '\0') + { + if (cptr->reason) + { + MyFree(cptr->reason); + } + cptr->reason = mystrdup(reason + 2); + } + /* ** mark for kill, because it cannot be killed ** yet: we don't even know if this is a server ** or a user connection! */ - if (start[0] == 'K') - cptr->exitc = EXITC_AREF; - else - cptr->exitc = EXITC_AREFQ; - /* should also check to make sure it's still - an unregistered client.. */ - - /* Finally, working after registration. --B. */ - if (IsRegisteredUser(cptr)) - { - if (cptr->exitc == EXITC_AREF) + if (start[0] == 'K') + cptr->exitc = EXITC_AREF; + else + cptr->exitc = EXITC_AREFQ; + + /* If we got NICK and USER we can assume it is a client + and can kill it directly */ + if (MyConnect(cptr) && cptr->name[0] != '\0' && + cptr->user != NULL) + { + if (cptr->exitc == EXITC_AREF) + { + sendto_flag(SCH_LOCAL, "Denied connection from %s (%s)", + get_client_host(cptr), + cptr->reason ? cptr->reason + : "Denied access"); + } + (void) exit_client(cptr, cptr, &me, + cptr->reason ? cptr->reason + : "Denied access"); + } + else { - sendto_flag(SCH_LOCAL, - "Denied after connection " - "from %s.", - get_client_host(cptr)); + Debug((DEBUG_NOTICE, + "Defer kill: missing nick/user (name='%s', " + "user=%p, uname_set=%d)\n", + cptr->name, (void *) cptr->user, + (cptr->user && cptr->user->username[0] != '\0') + ? 1 + : 0)); } - (void) exit_client(cptr, cptr, &me, - cptr->reason ? cptr->reason : - "Denied access"); - } } - start = end; + start = end; } - olen -= start - buf; - if (olen) - memcpy(obuf, start, olen); + olen -= start - buf; + if (olen) + memcpy(obuf, start, olen); } } diff --git a/ircd/s_bsd.c b/ircd/s_bsd.c index d1f40544..74bcf337 100644 --- a/ircd/s_bsd.c +++ b/ircd/s_bsd.c @@ -2101,10 +2101,9 @@ int read_message(time_t delay, FdAry *fdp, int ro) ** so no need to check for anything! */ #if defined(USE_IAUTH) - if (DoingDNS(cptr) || DoingAuth(cptr) || - WaitingXAuth(cptr) || - (DoingXAuth(cptr) && - !(iauth_options & XOPT_EARLYPARSE))) + if (DoingDNS(cptr) || DoingAuth(cptr) || WaitingXAuth(cptr) || + (DoingXAuth(cptr) && !((iauth_options & XOPT_EARLYPARSE) || + (cptr->flags & DEFER_USER_REG)))) #else if (DoingDNS(cptr) || DoingAuth(cptr)) #endif diff --git a/ircd/s_cap.c b/ircd/s_cap.c index 95fb8983..57a9c89f 100644 --- a/ircd/s_cap.c +++ b/ircd/s_cap.c @@ -205,7 +205,7 @@ int cap_end(aClient *cptr, aClient *sptr, char *arg) // complete registration if we received NICK and USER already if (sptr->name[0] && sptr->user) { - return register_user(cptr, sptr, sptr->name, sptr->user->username); + return register_user(cptr, sptr, sptr->name, sptr->user1); } return 0; diff --git a/ircd/s_conf.c b/ircd/s_conf.c index a6cc33c4..a1cfa78e 100644 --- a/ircd/s_conf.c +++ b/ircd/s_conf.c @@ -109,12 +109,10 @@ long iline_flags_parse(char *string) { tmp |= CFLAG_KEXEMPT; } -#ifdef XLINE if (index(string,'e')) { tmp |= CFLAG_XEXEMPT; } -#endif if (index(string,'N')) { tmp |= CFLAG_NORESOLVE; @@ -157,12 +155,10 @@ char *iline_flags_to_string(long flags) { *s++ = 'E'; } -#ifdef XLINE if (flags & CFLAG_XEXEMPT) { *s++ = 'e'; } -#endif if (flags & CFLAG_NORESOLVE) { *s++ = 'N'; @@ -702,12 +698,10 @@ int attach_Iline(aClient *cptr, struct hostent *hp, char *sockhost) { SetKlineExempt(cptr); } -#ifdef XLINE if (IsConfXlineExempt(aconf)) { ClearXlined(cptr); } -#endif /* Copy uhost (hostname) over sockhost, if conf flag permits. */ if (!IsCloaked(cptr) && *uhost && !IsConfNoResolve(aconf)) @@ -1787,11 +1781,9 @@ int initconf(int opt) case 'y': aconf->status = CONF_CLASS; break; -#ifdef XLINE case 'X': aconf->status = CONF_XLINE; break; -#endif default: Debug((DEBUG_ERROR, "Error in config file: %s", line)); break; @@ -1821,7 +1813,6 @@ int initconf(int opt) DupString(aconf->name, tmp); if ((tmp = getfield(NULL)) == NULL) break; -#ifdef XLINE if (aconf->status == CONF_XLINE) { DupString(aconf->name2, tmp); @@ -1833,7 +1824,6 @@ int initconf(int opt) DupString(aconf->source_ip, tmp); break; } -#endif aconf->port = 0; if (sscanf(tmp, "0x%x", &aconf->port) != 1 || aconf->port == 0) @@ -1859,9 +1849,7 @@ int initconf(int opt) istat.is_confmem += aconf->passwd ? strlen(aconf->passwd)+1 :0; istat.is_confmem += aconf->name ? strlen(aconf->name)+1 : 0; istat.is_confmem += aconf->name2 ? strlen(aconf->name2)+1 : 0; -#ifdef XLINE istat.is_confmem += aconf->name3 ? strlen(aconf->name3)+1 : 0; -#endif istat.is_confmem += aconf->source_ip ? strlen(aconf->source_ip)+1 : 0; /* diff --git a/ircd/s_sasl.c b/ircd/s_sasl.c index c4665fa1..dc7a1269 100644 --- a/ircd/s_sasl.c +++ b/ircd/s_sasl.c @@ -184,6 +184,11 @@ void m_sasl_service(aClient *cptr, aClient *sptr, int parc, char *parv[]) { // Authentication successful acptr->flags |= FLAGS_SASL; + /* Notify iauth of successful SASL authentication */ + if (MyConnect(acptr)) + { + sendto_iauth("%d S 1", acptr->fd); + } sendto_one(acptr, replies[RPL_SASLSUCCESS], me.name, BadTo(acptr->name)); acptr->sasl_service = NULL; } diff --git a/ircd/s_serv.c b/ircd/s_serv.c index e1ddb885..47a49b9e 100644 --- a/ircd/s_serv.c +++ b/ircd/s_serv.c @@ -1712,7 +1712,6 @@ static int report_array[18][3] = { { 0, 0, 0} }; -#ifdef XLINE static void report_x_lines(aClient *sptr, char *to) { aConfItem *tmp; @@ -1729,7 +1728,6 @@ static void report_x_lines(aClient *sptr, char *to) BadTo(tmp->name3), BadTo(tmp->source_ip)); } } -#endif static void report_configured_links(aClient *sptr, char *to, int mask) { @@ -2101,12 +2099,10 @@ int m_stats(aClient *cptr, aClient *sptr, int parc, char *parv[]) report_configured_links(cptr, parv[0], CONF_VER); break; case 'X' : -#ifdef XLINE if (IsAnOper(sptr)) { report_x_lines(sptr, parv[0]); } -#endif break; case 'x' : /* lists */ #ifdef DEBUGMODE @@ -2851,11 +2847,7 @@ int m_etrace(aClient *cptr, aClient *sptr, int parc, char *parv[]) get_client_class(acptr), acptr->name, acptr->user->username, acptr->user->host, get_client_ip(acptr), -#ifdef XLINE - acptr->user2, acptr->user3, -#else - "-", "-", -#endif + acptr->user2, acptr->user3, acptr->info); } else @@ -2874,11 +2866,7 @@ int m_etrace(aClient *cptr, aClient *sptr, int parc, char *parv[]) get_client_class(acptr), acptr->name, acptr->user->username, acptr->user->host, get_client_ip(acptr), -#ifdef XLINE - acptr->user2, acptr->user3, -#else - "-", "-", -#endif + acptr->user2, acptr->user3, acptr->info); } } @@ -2910,12 +2898,8 @@ int m_sidtrace(aClient *cptr, aClient *sptr, int parc, char *parv[]) MyClient(acptr) ? get_client_class(acptr) : -1, acptr->name, acptr->user->username, acptr->user->host, get_client_ip(acptr), -#ifdef XLINE MyClient(acptr) ? acptr->user2 : "-", MyClient(acptr) ? acptr->user3 : "-", -#else - "-", "-", -#endif acptr->info); } diff --git a/ircd/s_user.c b/ircd/s_user.c index e1864dc2..801fdd81 100644 --- a/ircd/s_user.c +++ b/ircd/s_user.c @@ -324,6 +324,32 @@ char *canonize(char *buffer) int register_user(aClient *cptr, aClient *sptr, char *nick, char *username) { +#if defined(USE_IAUTH) + if (MyConnect(sptr) && (sptr->flags & DEFER_USER_REG)) + { + /* iauth previously sent 'P' to inform the ircd that it + * requires NICK/USER (and possibly CAP/AUTHENTICATE). + */ + if (sptr->exitc == EXITC_AREF || sptr->exitc == EXITC_AREFQ) + { + Debug((DEBUG_INFO, + "DEFER_USER_REG: fallthrough due to exitc=%d (fd=%d)", + sptr->exitc, sptr->fd)); + sptr->flags &= ~DEFER_USER_REG; + } + else + { + /* 1. Send 'H' message to tell iauth to run wait_for_reg modules + * 2. Defer registration until iauth sends 'D' + */ + sendto_iauth("%d H %s %s %s %s :%s", sptr->fd, sptr->name, + sptr->user1, sptr->user2, sptr->user3, sptr->info); + strncpyzt(sptr->user->username, username, USERLEN + 1); + return 1; + } + } +#endif + Reg aConfItem *aconf; aClient *acptr; anUser *user = sptr->user; @@ -332,11 +358,6 @@ int register_user(aClient *cptr, aClient *sptr, char *nick, char *username) char prefix; #endif int i; -#ifdef XLINE - static char savedusername[USERLEN+1]; - - strncpyzt(savedusername, username, USERLEN+1); -#endif user->last = timeofday; parv[0] = sptr->name; @@ -352,7 +373,6 @@ int register_user(aClient *cptr, aClient *sptr, char *nick, char *username) static time_t last = 0; static u_int count = 0; #endif -#ifdef XLINE aConfItem *xtmp; /* Just for clarification, so there's less confusion: @@ -373,7 +393,7 @@ int register_user(aClient *cptr, aClient *sptr, char *nick, char *username) if (xtmp->status != CONF_XLINE) continue; if (!BadPtr(xtmp->host) && - match(xtmp->host, username)) + match(xtmp->host, sptr->user1)) continue; if (!BadPtr(xtmp->passwd) && match(xtmp->passwd, sptr->user2)) @@ -404,38 +424,51 @@ int register_user(aClient *cptr, aClient *sptr, char *nick, char *username) return exit_client(cptr, sptr, &me, XLINE_EXIT_REASON); } -#endif #if defined(USE_IAUTH) if (iauth_options & XOPT_EARLYPARSE && DoingXAuth(cptr)) { - cptr->flags |= FLAGS_WXAUTH; - /* fool check_pings() and give iauth more time! */ - cptr->firsttime = timeofday; - cptr->lasttime = timeofday; - strncpyzt(sptr->user->username, username, USERLEN+1); - if (sptr->passwd[0]) - sendto_iauth("%d P %s", sptr->fd, sptr->passwd); - sendto_iauth("%d U %s", sptr->fd, sptr->user->username); - return 1; + /* If iauth already set a 'K', do not defer here again. */ + if (sptr->exitc == EXITC_AREF || sptr->exitc == EXITC_AREFQ) + { + Debug((DEBUG_INFO, "EARLYPARSE bypass due to exitc=%d (fd=%d)", + sptr->exitc, sptr->fd)); + /* Fall through to the kill check below. */ + } + else if (!(cptr->flags & DEFER_USER_REG)) + { + cptr->flags |= FLAGS_WXAUTH; + /* fool check_pings() and give iauth more time! */ + cptr->firsttime = timeofday; + cptr->lasttime = timeofday; + strncpyzt(sptr->user->username, username, USERLEN+1); + if (sptr->passwd[0]) + sendto_iauth("%d P %s", sptr->fd,sptr->passwd); + sendto_iauth("%d U %s", sptr->fd, username); + return 1; + } } if (!DoneXAuth(sptr) && (iauth_options & XOPT_REQUIRED)) { + if (sptr->flags & DEFER_USER_REG) + { + return 1; /* defer */ + } if (iauth_options & XOPT_NOTIMEOUT) { count += 1; if (timeofday - last > 300) - { - sendto_flag(SCH_AUTH, - "iauth may be not running! (refusing new user connections)"); + { + sendto_flag(SCH_AUTH, "iauth may be not running! (refusing " + "new user connections)"); last = timeofday; - } + } sptr->exitc = EXITC_AUTHFAIL; } else sptr->exitc = EXITC_AUTHTOUT; return exit_client(cptr, cptr, &me, - "Authentication failure! - no iauth?"); + "Authentication failure! - no iauth?"); } if (timeofday - last > 300 && count) { @@ -529,7 +562,7 @@ int register_user(aClient *cptr, aClient *sptr, char *nick, char *username) strncpy(&user->username[1], buf2, USERLEN); } else - strncpy(user->username, buf2, USERLEN+1); + strncpy(user->username, buf2, USERLEN + 1); user->username[USERLEN] = '\0'; /* eos */ #else @@ -731,17 +764,13 @@ int register_user(aClient *cptr, aClient *sptr, char *nick, char *username) # if defined(CLIENTS_CHANNEL) && (CLIENTS_CHANNEL_LEVEL & CCL_CONN) sendto_flag(SCH_CLIENT, "%s %s %s %s CONN %s" # if (CLIENTS_CHANNEL_LEVEL & CCL_CONNINFO) -# ifdef XLINE - " %s %s %s" -# endif + " %s %s %s" " :%s" # endif , sptr->uid, nick, user->username, user->host, user->sip # if (CLIENTS_CHANNEL_LEVEL & CCL_CONNINFO) -# ifdef XLINE - , savedusername, sptr->user2, sptr->user3 -# endif + , sptr->user1, sptr->user2, sptr->user3 , sptr->info # endif ); @@ -1214,7 +1243,7 @@ int m_nick(aClient *cptr, aClient *sptr, int parc, char *parv[]) ** --must test this and exit m_nick too!!! */ if (register_user(cptr, sptr, nick, - sptr->user->username) + sptr->user1) == FLUSH_BUFFER) return FLUSH_BUFFER; } @@ -2483,18 +2512,16 @@ int m_user(aClient *cptr, aClient *sptr, int parc, char *parv[]) if (strlen(realname) > REALLEN) realname[REALLEN] = '\0'; sptr->info = mystrdup(realname); -#ifdef XLINE + sptr->user1 = mystrdup(username); sptr->user2 = mystrdup(umodes); sptr->user3 = mystrdup(server); -#endif - if (sptr->name[0] && !IsCAPNegotiation(sptr)) /* NICK already received, now we have USER... */ - { - return register_user(cptr, sptr, sptr->name, username); - } - else + + if (sptr->name[0] && !IsCAPNegotiation(sptr)) { - strncpyzt(sptr->user->username, username, USERLEN+1); + /* NICK already received, now we have USER... */ + return register_user(cptr, sptr, sptr->name, sptr->user1); } + return 2; }