From 141e9484fbf4e609601b69f4c0e48d1b1595d361 Mon Sep 17 00:00:00 2001 From: Michael Poole Date: Wed, 30 Jun 2004 22:59:10 +0000 Subject: [PATCH] implement IAUTH client code to query a separate server before allowing a user onto IRC. git-svn-id: file:///home/klmitch/undernet-ircu/undernet-ircu-svn/ircu2/trunk@1083 c9e4aea6-c8fd-4c43-8297-357d70d61c8c --- ChangeLog | 21 ++ doc/example.conf | 9 + doc/readme.iauth | 82 ++++++ include/client.h | 6 + include/ircd_auth.h | 40 +++ ircd/Makefile.in | 1 + ircd/iauth.c | 69 ----- ircd/ircd_auth.c | 696 ++++++++++++++++++++++++++++++++++++++++++++ ircd/ircd_lexer.l | 2 + ircd/ircd_parser.y | 48 ++- ircd/s_conf.c | 3 + ircd/s_misc.c | 3 + ircd/s_user.c | 7 + 13 files changed, 917 insertions(+), 70 deletions(-) create mode 100644 doc/readme.iauth create mode 100644 include/ircd_auth.h delete mode 100644 ircd/iauth.c create mode 100644 ircd/ircd_auth.c diff --git a/ChangeLog b/ChangeLog index 1e217b9..dbdb077 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,24 @@ +2004-06-30 Michael Poole + + * doc/readme.iauth, include/ircd_auth.h, ircd/ircd_auth.c: New + files. + + * doc/example.conf: Illustrate IAUTH configuration. + + * include/client.h: Add fields to record IAUTH status. + + * ircd/Makefile.in: Add ircd_auth.c to Makefile. + + * ircd/ircd_lexer.l, ircd/ircd_parser.y: Add new IAUTH section. + + * ircd/s_conf.c: Notify IAUTH code when reloading a configuration + so that an obsolete connection can be abandoned. + + * ircd/s_misc.c: Report client exits via IAUTH. + + * ircd/s_user.c: If IAUTH is active and a connecting user has not + been checked against it, interrogate the IAUTH server. + 2004-06-25 Michael Poole * configure.in: Check for crypt.h as well. diff --git a/doc/example.conf b/doc/example.conf index c55148c..d6132af 100644 --- a/doc/example.conf +++ b/doc/example.conf @@ -738,6 +738,15 @@ Pseudo "LOGIN" { nick = "X@channels.undernet.org"; }; +# You can ask a separate server whether to allow users to connect. +IAuth { + pass = "ircd-iauth"; + host = "127.0.0.1"; + port = 7700; + connectfreq = 30; + timeout = 60; +}; + # [features] # IRC servers have a large number of options and features. Most of these # are set at compile time through the use of #define's--see "make config" diff --git a/doc/readme.iauth b/doc/readme.iauth new file mode 100644 index 0000000..455d9da --- /dev/null +++ b/doc/readme.iauth @@ -0,0 +1,82 @@ +OVERVIEW +======== + +The IAUTH protocol used here is based on the one in ircd-hybrid 7.0, +with minor changes to support login-on-connect and true IAUTH-side +connection classes. (Several networks use central authorities to vary +per-netblock connection limits; for example, users from one ISP may +only be allowed one connection per IP, or one shell provider's +netblock may be limited to 50 total connections.) IAUTH-side +connection classes are controlled by a configuration option; if that +is enabled, this document will say ICLASS is enabled. + +As in IRC, lines sent between the IRC and IAUTH servers are limited to +512 characters, including the terminating sequence. As in +IRC, the final argument on a line may be prefixed with :, and must be +prefixed with : if it contains a space (decimal 32) character. Tokens +are separated by single space characters, and each line is a separate +command. The first token on a line is a case-insensitive command +name; unrecognized commands must be ignored. + +GREETING +======== + +The IRC server connects and sends the Server greeting: + Server [password] +If ICLASS is enabled, it sends a list of currently connected users: + MyUsers :@: ... +The IRC server may send several MyUsers lines. When it has sent all +MyUsers lines, it sends an EndUsers line: + EndUsers +If ICLASS is disabled, EndUsers is sent immediately after Server. + +LOGIN REQUESTS +============== + +When users connect, the IRC server sends a DoAuth request: + FullAuth + is a text string up to 20 characters long that identifies the +client, and is unique a BadAuth response is received or until an +ExitUser command is sent with the same uid (see below for details on +those messages). may not contain a colon character. +is the client's initially requested nickname. is the +username returned by the ident server (RFC 1413), or a tilde-prefixed +username supplied by the user. is a text hostname, +possibly in the form of a dotted quad or IPv6 address, or the +character '?'. is a dotted quad IPv4 address or an IPv6 hex +address. and are optional, and are used when the +client attempts login-on-connect. is the realname +specified by the client's USER message, and may contain spaces. + +If the client is accepted, the IAUTH server responds: + DoneAuth [account] + is a replacement username, and is a replacement +hostname. If the from DoAuth was ?, is the +result of a DNS lookup for the client. is the name of a +connection class for the client. is optional and is +provided if the user's login was successful. + +If the client is rejected, the IAUTH server responds: + BadAuth : + may include spaces, and should have a leading ':' sentinel. + +DISCONNECTS +=========== + +If ICLASS is enabled, the IRC server sends ExitUser when a client +disconnects: + ExitUser + +DIFFERENCES FROM IRCD-HYBRID +============================ + +The ircd-hybrid IAUTH code is slightly bitrotted and disabled in 7.0 +(through at least 7.0.1). This code added the following items: + MyUsers, EndUsers and ExitUser commands + Server passwords may contain whitespace and be prefixed by : + DoneAuth may include an account name + FullAuth command replaces DoAuth command and adds account, password, + realname parameters +The Class command is present in ircd-hybrid's code but not used here. +IP addresses in ircd-hybrid are "in unsigned int format," which is +limited to IPv4, and so it is not used here. diff --git a/include/client.h b/include/client.h index cb67796..478f9bc 100644 --- a/include/client.h +++ b/include/client.h @@ -139,6 +139,7 @@ enum Flag FLAG_BURST, /* Server is receiving a net.burst */ FLAG_BURST_ACK, /* Server is waiting for eob ack */ FLAG_IPCHECK, /* Added or updated IPregistry data */ + FLAG_IAUTHED, /* Got IAUTH response for user */ FLAG_LOCOP, /* Local operator -- SRB */ FLAG_SERVNOTICE, /* server notices such as kill */ FLAG_OPER, /* Operator */ @@ -211,6 +212,7 @@ struct Connection struct Socket con_socket; /* socket descriptor for client */ struct Timer con_proc; /* process latent messages from client */ struct AuthRequest* con_auth; /* auth request for client */ + struct IAuthRequest* con_iauth; /* iauth request for client */ }; #define CONNECTION_MAGIC 0x12f955f3 @@ -308,6 +310,7 @@ struct Client { #define cli_socket(cli) ((cli)->cli_connect->con_socket) #define cli_proc(cli) ((cli)->cli_connect->con_proc) #define cli_auth(cli) ((cli)->cli_connect->con_auth) +#define cli_iauth(cli) ((cli)->cli_connect->con_iauth) #define con_verify(con) ((con)->con_magic == CONNECTION_MAGIC) #define con_magic(con) ((con)->con_magic) @@ -347,6 +350,7 @@ struct Client { #define con_socket(con) ((con)->con_socket) #define con_proc(con) ((con)->con_proc) #define con_auth(con) ((con)->con_auth) +#define con_iauth(con) ((con)->con_iauth) #define STAT_CONNECTING 0x001 /* connecting to another server */ #define STAT_HANDSHAKE 0x002 /* pass - server sent */ @@ -414,6 +418,7 @@ struct Client { #define IsDead(x) HasFlag(x, FLAG_DEADSOCKET) #define IsDeaf(x) HasFlag(x, FLAG_DEAF) #define IsIPChecked(x) HasFlag(x, FLAG_IPCHECK) +#define IsIAuthed(x) HasFlag(x, FLAG_IAUTHED) #define IsIdented(x) HasFlag(x, FLAG_GOTID) #define IsInvisible(x) HasFlag(x, FLAG_INVISIBLE) #define IsJunction(x) HasFlag(x, FLAG_JUNCTION) @@ -442,6 +447,7 @@ struct Client { #define SetDebug(x) SetFlag(x, FLAG_DEBUG) #define SetGotId(x) SetFlag(x, FLAG_GOTID) #define SetIPChecked(x) SetFlag(x, FLAG_IPCHECK) +#define SetIAuthed(x) SetFlag(x, FLAG_IAUTHED) #define SetInvisible(x) SetFlag(x, FLAG_INVISIBLE) #define SetJunction(x) SetFlag(x, FLAG_JUNCTION) #define SetLocOp(x) SetFlag(x, FLAG_LOCOP) diff --git a/include/ircd_auth.h b/include/ircd_auth.h new file mode 100644 index 0000000..61e27f1 --- /dev/null +++ b/include/ircd_auth.h @@ -0,0 +1,40 @@ +#ifndef INCLUDED_ircd_auth_h +#define INCLUDED_ircd_auth_h + +/* + * IRC - Internet Relay Chat, ircd/ircd_auth.h + * Copyright 2004 Michael Poole + * + * 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 + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA + * 02111-1307, USA. + * + * $Id$ + */ + +#ifndef INCLUDED_config_h +#include "config.h" +#endif + +struct IAuth; +extern struct IAuth *iauth_active; + +struct IAuth *iauth_connect(char *host, unsigned short port, char *passwd, time_t reconnect, time_t timeout); +int iauth_start_client(struct IAuth *iauth, struct Client *cptr); +void iauth_exit_client(struct Client *cptr); + +void iauth_mark_closing(void); +void iauth_close_unused(void); + +#endif diff --git a/ircd/Makefile.in b/ircd/Makefile.in index 03d23d4..0f02c97 100644 --- a/ircd/Makefile.in +++ b/ircd/Makefile.in @@ -107,6 +107,7 @@ IRCD_SRC = \ hash.c \ ircd.c \ ircd_alloc.c \ + ircd_auth.c \ ircd_crypt.c \ ircd_events.c \ ircd_features.c \ diff --git a/ircd/iauth.c b/ircd/iauth.c deleted file mode 100644 index 51cd131..0000000 --- a/ircd/iauth.c +++ /dev/null @@ -1,69 +0,0 @@ -/************************************************************************ - * IRC - Internet Relay Chat, src/listener.c - * Copyright (C) 2001 Perry Lorier - * - * 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 - * the Free Software Foundation; either version 1, or (at your option) - * any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. - * - * $Id$ - */ -#include "config.h" -#include "iauth.h" -#include -#include - -struct Iauth_Outstanding { - struct iauth_outstanding *next; - int client_id; -} - -static struct Iauth_Outstanding *iauth_freelist=NULL; -struct Iauth IauthPollList = NULL; - -void iauth_new(char *service) -{ - /* TODO: Check to see if the service is a hostname, and if so - * connect to it like hybrid does. Hybrid uses a blocking - * gethostbyname here (bletch!!). On the other hand a - * nonblocking solutions likely to be worse.... - */ - struct Iauth* tmp = (struct Iauth *)malloc(sizeof(struct Iauth)); - int fd[2]; - tmp->next = IauthPollList; - tmp->fd = 0; - tmp->service=strdup(service); - tmp->ref_count=0; - tmp->active=0; - if (socketpair(domain,type,protocol,fd)<0) { - free(tmp->service); - free(tmp); - return; - } - tmp->fd=fd[0]; - if (0 == fork()) { - (void)close(pi[0]); - (void)dup2(pi[1],0); - (void)dup2(pi[1],1); - for (i = 2; i < MAXCONNECTIONS; i++) - (void)close(i); - if (pi[1] != 0 && pi[1] != 1) - (void)close(pi[1]); - (void)execlp(tmp->service, tmp->service, 0); - exit(-1); - } else { - /* TODO: Put the real servername in there */ - (void)write(tmp->fd,"Server undernet.org 1.1"); - } - IauthPollList=tmp; -} diff --git a/ircd/ircd_auth.c b/ircd/ircd_auth.c new file mode 100644 index 0000000..e3629d0 --- /dev/null +++ b/ircd/ircd_auth.c @@ -0,0 +1,696 @@ +/* + * IRC - Internet Relay Chat, ircd/ircd_auth.c + * Copyright 2004 Michael Poole + * + * 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 + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA + * 02111-1307, USA. + * + * $Id$ + */ + +#include "config.h" +#include "client.h" +#include "ircd_alloc.h" +#include "ircd_auth.h" +#include "ircd_events.h" +#include "ircd_features.h" +#include "ircd_log.h" +#include "ircd_osdep.h" +#include "ircd_snprintf.h" +#include "ircd_string.h" +#include "ircd.h" +#include "msg.h" +#include "msgq.h" +#include "res.h" +#include "s_bsd.h" +#include "s_misc.h" +#include "s_user.h" +#include "send.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +struct IAuthRequest { + struct IAuthRequest *iar_prev; /* previous request struct */ + struct IAuthRequest *iar_next; /* next request struct */ + struct Client *iar_client; /* client being authenticated */ + char iar_timed; /* if non-zero, using parent i_request_timer */ +}; + +enum IAuthFlag +{ + IAUTH_BLOCKED, /* socket buffer full */ + IAUTH_CONNECTED, /* server greeting/handshake done */ + IAUTH_ABORT, /* abort connection asap */ + IAUTH_ICLASS, /* tell iauth about all local users */ + IAUTH_CLOSING, /* candidate to be disposed */ + IAUTH_LAST_FLAG +}; +DECLARE_FLAGSET(IAuthFlags, IAUTH_LAST_FLAG); + +struct IAuth { + struct IAuthRequest i_list_head; /* doubly linked list of requests */ + struct MsgQ i_sendQ; /* messages queued to send */ + struct Socket i_socket; /* connection to server */ + struct Timer i_reconn_timer; /* when to reconnect the connection */ + struct Timer i_request_timer; /* when the current request times out */ + struct IAuthFlags i_flags; /* connection state/status/flags */ + struct DNSQuery i_query; /* DNS lookup for iauth server */ + unsigned int i_recvM; /* messages received */ + unsigned int i_sendM; /* messages sent */ + unsigned int i_recvK; /* kilobytes received */ + unsigned int i_sendK; /* kilobytes sent */ + unsigned short i_recvB; /* bytes received modulo 1024 */ + unsigned short i_sendB; /* bytes sent modulo 1024 */ + time_t i_reconnect; /* seconds to wait before reconnecting */ + time_t i_timeout; /* seconds to wait for a request */ + unsigned int i_count; /* characters used in i_buffer */ + char i_buffer[BUFSIZE+1]; /* partial unprocessed line from server */ + char i_passwd[PASSWDLEN+1]; /* password for connection */ + char i_host[HOSTLEN+1]; /* iauth server hostname */ + in_addr_t i_addr; /* iauth server ip address */ + unsigned short i_port; /* iauth server port */ + struct IAuth *i_next; /* next connection in list */ +}; + +#define i_flags(iauth) ((iauth)->i_flags) +#define IAuthGet(iauth, flag) FlagHas(&i_flags(iauth), flag) +#define IAuthSet(iauth, flag) FlagSet(&i_flags(iauth), flag) +#define IAuthClr(iauth, flag) FlagClr(&i_flags(iauth), flag) +#define i_GetBlocked(iauth) IAuthGet(iauth, IAUTH_BLOCKED) +#define i_SetBlocked(iauth) IAuthSet(iauth, IAUTH_BLOCKED) +#define i_ClrBlocked(iauth) IAuthClr(iauth, IAUTH_BLOCKED) +#define i_GetConnected(iauth) IAuthGet(iauth, IAUTH_CONNECTED) +#define i_SetConnected(iauth) IAuthSet(iauth, IAUTH_CONNECTED) +#define i_ClrConnected(iauth) IAuthClr(iauth, IAUTH_CONNECTED) +#define i_GetAbort(iauth) IAuthGet(iauth, IAUTH_ABORT) +#define i_SetAbort(iauth) IAuthSet(iauth, IAUTH_ABORT) +#define i_ClrAbort(iauth) IAuthClr(iauth, IAUTH_ABORT) +#define i_GetIClass(iauth) IAuthGet(iauth, IAUTH_ICLASS) +#define i_SetIClass(iauth) IAuthSet(iauth, IAUTH_ICLASS) +#define i_ClrIClass(iauth) IAuthClr(iauth, IAUTH_ICLASS) +#define i_GetClosing(iauth) IAuthGet(iauth, IAUTH_CLOSING) +#define i_SetClosing(iauth) IAuthSet(iauth, IAUTH_CLOSING) +#define i_ClrClosing(iauth) IAuthClr(iauth, IAUTH_CLOSING) + +#define i_list_head(iauth) ((iauth)->i_list_head) +#define i_socket(iauth) ((iauth)->i_socket) +#define i_reconn_timer(iauth) ((iauth)->i_reconn_timer) +#define i_request_timer(iauth) ((iauth)->i_request_timer) +#define i_query(iauth) ((iauth)->i_query) +#define i_recvB(iauth) ((iauth)->i_recvB) +#define i_recvK(iauth) ((iauth)->i_recvK) +#define i_recvM(iauth) ((iauth)->i_recvM) +#define i_sendB(iauth) ((iauth)->i_sendB) +#define i_sendK(iauth) ((iauth)->i_sendK) +#define i_sendM(iauth) ((iauth)->i_sendM) +#define i_sendQ(iauth) ((iauth)->i_sendQ) +#define i_reconnect(iauth) ((iauth)->i_reconnect) +#define i_timeout(iauth) ((iauth)->i_timeout) +#define i_count(iauth) ((iauth)->i_count) +#define i_buffer(iauth) ((iauth)->i_buffer) +#define i_passwd(iauth) ((iauth)->i_passwd) +#define i_host(iauth) ((iauth)->i_host) +#define i_addr(iauth) ((iauth)->i_addr) +#define i_port(iauth) ((iauth)->i_port) +#define i_next(iauth) ((iauth)->i_next) + +struct IAuthCmd { + const char *iac_name; + void (*iac_func)(struct IAuth *iauth, int, char *[]); +}; + +struct IAuth *iauth_active; + +static const struct IAuthCmd iauth_cmdtab[]; + +static void iauth_write(struct IAuth *iauth); +static void iauth_reconnect(struct IAuth *iauth); +static void iauth_disconnect(struct IAuth *iauth); +static void iauth_sock_callback(struct Event *ev); +static void iauth_send_request(struct IAuth *iauth, struct IAuthRequest *iar); +static void iauth_dispose_request(struct IAuth *iauth, struct IAuthRequest *iar); + +struct IAuth *iauth_connect(char *host, unsigned short port, char *passwd, time_t reconnect, time_t timeout) +{ + struct IAuth *iauth; + + for (iauth = iauth_active; iauth; iauth = i_next(iauth)) { + if (!ircd_strncmp(i_host(iauth), host, HOSTLEN) + && (i_port(iauth) == port)) { + i_ClrClosing(iauth); + i_reconnect(iauth) = reconnect; + if (t_active(&i_reconn_timer(iauth)) && (t_expire(&i_reconn_timer(iauth)) > CurrentTime + i_reconnect(iauth))) + timer_chg(&i_reconn_timer(iauth), TT_RELATIVE, i_reconnect(iauth)); + break; + } + } + if (NULL == iauth) { + if (iauth_active && !i_GetClosing(iauth_active)) { + log_write(LS_CONFIG, L_WARNING, 0, "Creating extra active IAuth connection to %s:%d.", host, port); + } + iauth = MyCalloc(1, sizeof(*iauth)); + i_list_head(iauth).iar_prev = &i_list_head(iauth); + i_list_head(iauth).iar_next = &i_list_head(iauth); + msgq_init(&i_sendQ(iauth)); + ircd_strncpy(i_host(iauth), host, HOSTLEN); + i_port(iauth) = port; + i_addr(iauth) = INADDR_NONE; + i_next(iauth) = iauth_active; + iauth_active = iauth; + i_reconnect(iauth) = reconnect; + iauth_reconnect(iauth); + } + if (passwd) + ircd_strncpy(i_passwd(iauth), passwd, PASSWDLEN); + else + i_passwd(iauth)[0] = '\0'; + i_timeout(iauth) = timeout; + return iauth; +} + +void iauth_mark_closing(void) +{ + struct IAuth *iauth; + for (iauth = iauth_active; iauth; iauth = i_next(iauth)) + i_SetClosing(iauth); +} + +void iauth_close(struct IAuth *iauth) +{ + /* Figure out what to do with the closing connection's requests. */ + if (i_list_head(iauth).iar_next != &i_list_head(iauth)) { + struct IAuthRequest *iar; + if (iauth_active || i_next(iauth)) { + /* If iauth_active != NULL, send requests to it; otherwise if + * i_next(iauth) != NULL, we can hope it or some later + * connection will be active. + */ + struct IAuth *target = iauth_active ? iauth_active : i_next(iauth); + + /* Append iauth->i_list_head to end of target->i_list_head. */ + iar = i_list_head(iauth).iar_next; + iar->iar_prev = i_list_head(target).iar_prev; + i_list_head(target).iar_prev->iar_next = iar; + iar = i_list_head(iauth).iar_prev; + iar->iar_next = &i_list_head(target); + i_list_head(target).iar_prev = iar; + + /* If the target is not closing, send the requests. */ + for (iar = i_list_head(iauth).iar_next; + iar != &i_list_head(target); + iar = iar->iar_next) { + if (!i_GetClosing(target)) + iauth_send_request(target, iar); + } + } else { + /* No active connections - approve the requests and drop them. */ + while ((iar = i_list_head(iauth).iar_next) != &i_list_head(iauth)) { + struct Client *client = iar->iar_client; + iauth_dispose_request(iauth, iar); + register_user(client, client, cli_name(client), cli_username(client)); + } + } + } + /* Make sure the connection closes with an empty request list. */ + i_list_head(iauth).iar_prev = &i_list_head(iauth); + i_list_head(iauth).iar_next = &i_list_head(iauth); + /* Cancel the timer, if it is active. */ + if (t_active(&i_reconn_timer(iauth))) + timer_del(&i_reconn_timer(iauth)); + if (t_active(&i_request_timer(iauth))) + timer_del(&i_request_timer(iauth)); + /* Disconnect from the server. */ + if (s_fd(&i_socket(iauth)) != -1) + iauth_disconnect(iauth); + /* Free memory. */ + MyFree(iauth); +} + +void iauth_close_unused(void) +{ + struct IAuth *prev, *iauth, *next; + + for (prev = NULL, iauth = iauth_active; iauth; iauth = next) { + next = i_next(iauth); + if (i_GetClosing(iauth)) { + /* Update iauth_active linked list. */ + if (prev) + i_next(prev) = next; + else + iauth_active = next; + /* Close and destroy the connection. */ + iauth_close(iauth); + } else { + prev = iauth; + } + } +} + +static void iauth_send(struct IAuth *iauth, const char *format, ...) +{ + va_list vl; + struct MsgBuf *mb; + + va_start(vl, format); + mb = msgq_vmake(0, format, vl); + va_end(vl); + msgq_add(&i_sendQ(iauth), mb, 0); + msgq_clean(mb); +} + +static void iauth_protocol_violation(struct IAuth *iauth, const char *format, ...) +{ + struct VarData vd; + assert(iauth != 0); + assert(format != 0); + vd.vd_format = format; + va_start(vd.vd_args, format); + sendwallto_group_butone(&me, WALL_DESYNCH, NULL, "IAuth protocol violation: %v", &vd); + va_end(vd.vd_args); +} + +static void iauth_on_connect(struct IAuth *iauth) +{ + struct IAuthRequest *iar; + if (EmptyString(i_passwd(iauth))) + iauth_send(iauth, "Server %s", cli_name(&me)); + else + iauth_send(iauth, "Server %s %s", cli_name(&me), i_passwd(iauth)); + if (i_GetIClass(iauth)) { + /* TODO: report local users to iauth */ + iauth_send(iauth, "EndUsers"); + } + i_SetConnected(iauth); + for (iar = i_list_head(iauth).iar_next; + iar != &i_list_head(iauth); + iar = iar->iar_next) + iauth_send_request(iauth, iar); + iauth_write(iauth); +} + +static void iauth_disconnect(struct IAuth *iauth) +{ + close(s_fd(&i_socket(iauth))); + socket_del(&i_socket(iauth)); + s_fd(&i_socket(iauth)) = -1; +} + +static void iauth_dns_callback(void *vptr, struct hostent *he) +{ + struct IAuth *iauth = vptr; + if (!he) { + sendto_opmask_butone(0, SNO_OLDSNO, "IAuth connection to %s failed: host lookup failed", i_host(iauth)); + } else if (he->h_addrtype != AF_INET) { + sendto_opmask_butone(0, SNO_OLDSNO, "IAuth connection to %s failed: bad host type %d", i_host(iauth), he->h_addrtype); + } else { + assert(he->h_addrtype == sizeof(i_addr(iauth))); + memcpy(&i_addr(iauth), he->h_addr_list[0], sizeof(i_addr(iauth))); + if (INADDR_NONE == i_addr(iauth)) { + sendto_opmask_butone(0, SNO_OLDSNO, "IAuth connection to %s failed: host came back as INADDR_NONE", i_host(iauth)); + return; + } + iauth_reconnect(iauth); + } +} + +static void iauth_reconnect_ev(struct Event *ev) +{ + if (ev_type(ev) == ET_EXPIRE) + iauth_reconnect(t_data(ev_timer(ev))); +} + +static void iauth_schedule_reconnect(struct IAuth *iauth) +{ + struct Timer *timer; + assert(!t_active(&i_reconn_timer(iauth))); + timer = timer_init(&i_reconn_timer(iauth)); + timer_add(timer, iauth_reconnect_ev, iauth, TT_RELATIVE, i_reconnect(iauth)); +} + +static void iauth_reconnect(struct IAuth *iauth) +{ + extern struct sockaddr_in VirtualHost; + struct sockaddr_in sin; + IOResult result; + int fd; + + if (INADDR_NONE == i_addr(iauth)) { + i_addr(iauth) = inet_addr(i_host(iauth)); + if (INADDR_NONE == i_addr(iauth)) { + i_query(iauth).vptr = iauth; + i_query(iauth).callback = iauth_dns_callback; + gethost_byname(i_host(iauth), &i_query(iauth)); + return; + } + } + fd = socket(AF_INET, SOCK_STREAM, 0); + if (0 > fd) { + sendto_opmask_butone(0, SNO_OLDSNO, "IAuth reconnect unable to allocate socket: %s", strerror(errno)); + return; + } + if (feature_bool(FEAT_VIRTUAL_HOST) + && bind(fd, (struct sockaddr*)&VirtualHost, sizeof(VirtualHost)) != 0) { + close(fd); + sendto_opmask_butone(0, SNO_OLDSNO, "IAuth reconnect unable to bind vhost: %s", strerror(errno)); + return; + } + if (!os_set_sockbufs(fd, SERVER_TCP_WINDOW, SERVER_TCP_WINDOW)) { + close(fd); + sendto_opmask_butone(0, SNO_OLDSNO, "IAuth reconnect unable to set socket buffers: %s", strerror(errno)); + return; + } + if (!os_set_nonblocking(fd)) { + close(fd); + sendto_opmask_butone(0, SNO_OLDSNO, "IAuth reconnect unable to make socket non-blocking: %s", strerror(errno)); + return; + } + memset(&sin, 0, sizeof(sin)); + sin.sin_family = AF_INET; + sin.sin_addr.s_addr = i_addr(iauth); + sin.sin_port = htons(i_port(iauth)); + result = os_connect_nonb(fd, &sin); + if (result == IO_FAILURE) { + close(fd); + sendto_opmask_butone(0, SNO_OLDSNO, "IAuth reconnect unable to initiate connection: %s", strerror(errno)); + return; + } + if (!socket_add(&i_socket(iauth), iauth_sock_callback, iauth, + (result == IO_SUCCESS) ? SS_CONNECTED : SS_CONNECTING, + SOCK_EVENT_READABLE | SOCK_EVENT_WRITABLE, fd)) { + close(fd); + sendto_opmask_butone(0, SNO_OLDSNO, "IAuth reconnect unable to add socket: %s", strerror(errno)); + return; + } +} + +static void iauth_read(struct IAuth *iauth) +{ + char *src, *endp, *old_buffer, *argv[MAXPARA + 1]; + unsigned int length, argc, ii; + char readbuf[SERVER_TCP_WINDOW]; + + length = 0; + if (IO_FAILURE == os_recv_nonb(s_fd(&i_socket(iauth)), readbuf, sizeof(readbuf), &length)) + return; + i_recvB(iauth) += length; + if (i_recvB(iauth) > 1023) { + i_recvK(iauth) += i_recvB(iauth) >> 10; + i_recvB(iauth) &= 1023; + } + old_buffer = i_buffer(iauth); + endp = old_buffer + i_count(iauth); + for (src = readbuf; length > 0; --length) { + *endp = *src++; + if (IsEol(*endp)) { + /* Skip blank lines. */ + if (endp == old_buffer) + continue; + /* NUL-terminate line and split parameters. */ + *endp = '\0'; + for (argc = 0, endp = old_buffer; *endp && (argc < MAXPARA); ) { + while (*endp == ' ') + *endp++ = '\0'; + if (*endp == '\0') + break; + if (*endp == ':') + { + argv[argc++] = endp + 1; + break; + } + argv[argc++] = endp; + for (; *endp && *endp != ' '; ++endp) ; + } + argv[argc] = NULL; + /* Count line and reset endp to start of buffer. */ + i_recvM(iauth)++; + endp = old_buffer; + /* Look up command and try to dispatch. */ + if (argc > 0) { + for (ii = 0; iauth_cmdtab[ii].iac_name; ++ii) { + if (!ircd_strcmp(iauth_cmdtab[ii].iac_name, argv[0])) { + iauth_cmdtab[ii].iac_func(iauth, argc, argv); + if (i_GetAbort(iauth)) + iauth_disconnect(iauth); + break; + } + } + } + } + else if (endp < old_buffer + BUFSIZE) + endp++; + } + i_count(iauth) = endp - old_buffer; +} + +static void iauth_write(struct IAuth *iauth) +{ + unsigned int bytes_tried, bytes_sent; + IOResult iores; + + if (i_GetBlocked(iauth)) + return; + while (MsgQLength(&i_sendQ(iauth)) > 0) { + iores = os_sendv_nonb(s_fd(&i_socket(iauth)), &i_sendQ(iauth), &bytes_tried, &bytes_sent); + switch (iores) { + case IO_SUCCESS: + msgq_delete(&i_sendQ(iauth), bytes_sent); + i_sendB(iauth) += bytes_sent; + if (i_sendB(iauth) > 1023) { + i_sendK(iauth) += i_sendB(iauth) >> 10; + i_sendB(iauth) &= 1023; + } + if (bytes_tried == bytes_sent) + break; + /* If bytes_sent < bytes_tried, fall through to IO_BLOCKED. */ + case IO_BLOCKED: + i_SetBlocked(iauth); + socket_events(&i_socket(iauth), SOCK_ACTION_ADD | SOCK_EVENT_WRITABLE); + return; + case IO_FAILURE: + iauth_disconnect(iauth); + return; + } + } + /* We were able to flush all events, so remove notification. */ + socket_events(&i_socket(iauth), SOCK_ACTION_DEL | SOCK_EVENT_WRITABLE); +} + +static void iauth_sock_callback(struct Event *ev) +{ + struct IAuth *iauth; + + assert(0 != ev_socket(ev)); + iauth = (struct IAuth*) s_data(ev_socket(ev)); + assert(0 != iauth); + + switch (ev_type(ev)) { + case ET_CONNECT: + socket_state(ev_socket(ev), SS_CONNECTED); + iauth_on_connect(iauth); + break; + case ET_DESTROY: + if (!i_GetClosing(iauth)) + iauth_schedule_reconnect(iauth); + break; + case ET_READ: + iauth_read(iauth); + break; + case ET_WRITE: + i_ClrBlocked(iauth); + iauth_write(iauth); + break; + case ET_EOF: + iauth_disconnect(iauth); + break; + case ET_ERROR: + sendto_opmask_butone(0, SNO_OLDSNO, "IAuth socket error: %s", strerror(ev_data(ev))); + log_write(LS_SOCKET, L_ERROR, 0, "IAuth socket error: %s", strerror(ev_data(ev))); + iauth_disconnect(iauth); + break; + default: + assert(0 && "Unrecognized event type"); + break; + } +} + +/* Functions related to IAuthRequest structs */ + +static void iauth_request_ev(struct Event *ev) +{ + /* TODO: this could probably be more intelligent */ + if (ev_type(ev) == ET_EXPIRE) { + sendto_opmask_butone(0, SNO_OLDSNO, "IAuth request timed out; reconnecting"); + iauth_reconnect(t_data(ev_timer(ev))); + } +} + +static void iauth_send_request(struct IAuth *iauth, struct IAuthRequest *iar) +{ + struct Client *client; + + /* If iauth is not connected, we must defer the request. */ + if (!i_GetConnected(iauth)) + return; + + /* If no timed request, set up expiration timer. */ + if (!t_active(&i_request_timer(iauth))) { + struct Timer *timer = timer_init(&i_request_timer(iauth)); + timer_add(timer, iauth_request_ev, iauth, TT_RELATIVE, i_timeout(iauth)); + iar->iar_timed = 1; + } else + iar->iar_timed = 0; + + /* Send the FullAuth request. */ + client = iar->iar_client; + assert(iar->iar_client != NULL); + iauth_send(iauth, "FullAuth %x %s %s %s %s %s :%s", + client, cli_name(client), cli_username(client), + cli_user(client)->host, cli_sock_ip(client), + cli_passwd(client), cli_info(client)); + + /* Write to the socket if we can. */ + iauth_write(iauth); +} + +int iauth_start_client(struct IAuth *iauth, struct Client *cptr) +{ + struct IAuthRequest *iar; + + /* Allocate and initialize IAuthRequest struct. */ + if (!(iar = MyCalloc(1, sizeof(*iar)))) + return exit_client(cptr, cptr, &me, "IAuth memory allocation failed"); + iar->iar_next = &i_list_head(iauth); + iar->iar_prev = i_list_head(iauth).iar_prev; + iar->iar_client = cptr; + iar->iar_prev->iar_next = iar; + iar->iar_next->iar_prev = iar; + + /* Send request. */ + iauth_send_request(iauth, iar); + + return 0; +} + +void iauth_exit_client(struct Client *cptr) +{ + if (cli_iauth(cptr)) { + iauth_dispose_request(iauth_active, cli_iauth(cptr)); + cli_iauth(cptr) = NULL; + } else if (IsIAuthed(cptr) && i_GetIClass(iauth_active)) { + /* TODO: report quit to iauth */ + } +} + +static struct IAuthRequest *iauth_find_request(struct IAuth *iauth, char *id) +{ + struct IAuthRequest *curr; + struct Client *target; + target = (struct Client*)strtoul(id, NULL, 16); + for (curr = i_list_head(iauth).iar_next; + curr != &i_list_head(iauth); + curr = curr->iar_next) { + assert(curr->iar_client != NULL); + if (target == curr->iar_client) + return curr; + } + return NULL; +} + +static void iauth_dispose_request(struct IAuth *iauth, struct IAuthRequest *iar) +{ + assert(iar->iar_client != NULL); + if (iar->iar_timed) + timer_del(&i_request_timer(iauth)); + cli_iauth(iar->iar_client) = NULL; + iar->iar_prev->iar_next = iar->iar_next; + iar->iar_next->iar_prev = iar->iar_prev; + MyFree(iar); +} + +static void iauth_cmd_doneauth(struct IAuth *iauth, int argc, char *argv[]) +{ + struct IAuthRequest *iar; + struct Client *client; + char *id; + char *username; + char *hostname; + char *c_class; + char *account; + + if (argc < 5) { + iauth_protocol_violation(iauth, "Only %d parameters for DoneAuth (expected >=5)", argc); + return; + } + id = argv[1]; + username = argv[2]; + hostname = argv[3]; + c_class = argv[4]; + account = (argc > 5) ? argv[5] : 0; + iar = iauth_find_request(iauth, id); + if (!iar) { + iauth_protocol_violation(iauth, "Got unexpected DoneAuth for id %s", id); + return; + } + client = iar->iar_client; + ircd_strncpy(cli_username(client), username, USERLEN); + ircd_strncpy(cli_user(client)->host, hostname, HOSTLEN); + if (account) { + ircd_strncpy(cli_user(client)->account, account, ACCOUNTLEN); + SetAccount(client); + } + SetIAuthed(client); + iauth_dispose_request(iauth, iar); + register_user(client, client, cli_name(client), username); +} + +static void iauth_cmd_badauth(struct IAuth *iauth, int argc, char *argv[]) +{ + struct IAuthRequest *iar; + struct Client *client; + char *id; + char *reason; + + if (argc < 3) { + iauth_protocol_violation(iauth, "Only %d parameters for BadAuth (expected >=3)", argc); + return; + } + id = argv[1]; + reason = argv[2]; + if (EmptyString(reason)) { + iauth_protocol_violation(iauth, "Empty BadAuth reason for id %s", id); + return; + } + iar = iauth_find_request(iauth, id); + if (!iar) { + iauth_protocol_violation(iauth, "Got unexpected BadAuth for id %s", id); + return; + } + client = iar->iar_client; + iauth_dispose_request(iauth, iar); + exit_client(client, client, &me, reason); +} + +static const struct IAuthCmd iauth_cmdtab[] = { + { "DoneAuth", iauth_cmd_doneauth }, + { "BadAuth", iauth_cmd_badauth }, + { NULL, NULL } +}; diff --git a/ircd/ircd_lexer.l b/ircd/ircd_lexer.l index 91f022f..7abff8f 100644 --- a/ircd/ircd_lexer.l +++ b/ircd/ircd_lexer.l @@ -121,6 +121,8 @@ ip return IP; crule return CRULE; kill return KILL; quarantine return QUARANTINE; +iauth return IAUTH; +timeout return TIMEOUT; features return FEATURES; channel return CHANNEL; chan_limit return TPRIV_CHAN_LIMIT; diff --git a/ircd/ircd_parser.y b/ircd/ircd_parser.y index cb22a29..7def3f0 100644 --- a/ircd/ircd_parser.y +++ b/ircd/ircd_parser.y @@ -32,6 +32,7 @@ #include "hash.h" #include "ircd.h" #include "ircd_alloc.h" +#include "ircd_auth.h" #include "ircd_chattr.h" #include "ircd_log.h" #include "ircd_reply.h" @@ -157,6 +158,8 @@ static void parse_error(char *pattern,...) { %token PSEUDO %token PREPEND %token USERMODE +%token IAUTH +%token TIMEOUT /* and now a lot of priviledges... */ %token TPRIV_CHAN_LIMIT TPRIV_MODE_LCHAN TPRIV_DEOP_LCHAN TPRIV_WALK_LCHAN %token TPRIV_LOCAL_KILL TPRIV_REHASH TPRIV_RESTART TPRIV_DIE @@ -183,7 +186,7 @@ blocks: blocks block | block; block: adminblock | generalblock | classblock | connectblock | serverblock | operblock | portblock | jupeblock | clientblock | killblock | cruleblock | motdblock | featuresblock | quarantineblock | - pseudoblock | error; + pseudoblock | iauthblock | error; /* The timespec, sizespec and expr was ripped straight from * ircd-hybrid-7. */ @@ -997,3 +1000,46 @@ pseudonick: NICK '=' QSTRING ';' smap->services = nh; } }; + +iauthblock: IAUTH '{' +{ + pass = host = NULL; + port = 0; + tconn = 60; + tping = 60; +} iauthitems '}' ';' +{ + if (!name || !host || !port) { + log_write(LS_CONFIG, L_ERROR, 0, "IAuth block needs a server name and port."); + return 0; + } + iauth_connect(host, port, pass, tconn, tping); + MyFree(pass); + MyFree(host); + pass = host = NULL; +} + +iauthitems: iauthitem iauthitems | iauthitem; +iauthitem: iauthpass | iauthhost | iauthport | iauthconnfreq | iauthtimeout | error; +iauthpass: PASS '=' QSTRING ';' +{ + MyFree(pass); + DupString(pass, yylval.text); +}; +iauthhost: HOST '=' QSTRING ';' +{ + MyFree(host); + DupString(host, yylval.text); +}; +iauthport: PORT '=' NUMBER ';' +{ + port = yylval.num; +}; +iauthconnfreq: CONNECTFREQ '=' timespec ';' +{ + tconn = yylval.num; +}; +iauthtimeout: TIMEOUT '=' timespec ';' +{ + tping = yylval.num; +}; diff --git a/ircd/s_conf.c b/ircd/s_conf.c index 63e5bfe..f386b98 100644 --- a/ircd/s_conf.c +++ b/ircd/s_conf.c @@ -32,6 +32,7 @@ #include "hash.h" #include "ircd.h" #include "ircd_alloc.h" +#include "ircd_auth.h" #include "ircd_chattr.h" #include "ircd_log.h" #include "ircd_reply.h" @@ -1060,11 +1061,13 @@ int rehash(struct Client *cptr, int sig) class_mark_delete(); mark_listeners_closing(); + iauth_mark_closing(); read_configuration_file(); log_reopen(); /* reopen log files */ + iauth_close_unused(); close_listeners(); class_delete_marked(); /* unless it fails */ diff --git a/ircd/s_misc.c b/ircd/s_misc.c index bf37eb9..aa9030b 100644 --- a/ircd/s_misc.c +++ b/ircd/s_misc.c @@ -31,6 +31,7 @@ #include "hash.h" #include "ircd.h" #include "ircd_alloc.h" +#include "ircd_auth.h" #include "ircd_features.h" #include "ircd_log.h" #include "ircd_reply.h" @@ -296,6 +297,8 @@ static void exit_one_client(struct Client* bcptr, const char* comment) assert(!IsServer(bcptr)); /* bcptr->user->server->serv->client_list[IndexYXX(bcptr)] = NULL; */ RemoveYXXClient(cli_user(bcptr)->server, cli_yxx(bcptr)); + if (IsIAuthed(bcptr) || cli_iauth(bcptr)) + iauth_exit_client(bcptr); } /* Remove bcptr from the client list */ diff --git a/ircd/s_user.c b/ircd/s_user.c index 4082ea3..2aefc68 100644 --- a/ircd/s_user.c +++ b/ircd/s_user.c @@ -32,6 +32,7 @@ #include "hash.h" #include "ircd.h" #include "ircd_alloc.h" +#include "ircd_auth.h" #include "ircd_chattr.h" #include "ircd_features.h" #include "ircd_log.h" @@ -375,6 +376,12 @@ int register_user(struct Client *cptr, struct Client *sptr, static time_t last_too_many2; assert(cptr == sptr); + if (!IsIAuthed(sptr)) { + if (iauth_active) + return iauth_start_client(iauth_active, sptr); + else + SetIAuthed(sptr); + } switch (conf_check_client(sptr)) { case ACR_OK: -- 2.20.1