X-Git-Url: https://arthur.barton.de/cgi-bin/gitweb.cgi?p=ngircd-alex.git;a=blobdiff_plain;f=src%2Fngircd%2Firc-login.c;h=bbb2f0d5e430c88a904514ff77bbfa3d3ed49b92;hp=8e77e7fe34ddcefde11788fd00a5e15c0c7e77f9;hb=56b7e67307c1be110eaa4e84681bca03df21bd69;hpb=f76e0a1db689dadfe32f211002248d03416b3982 diff --git a/src/ngircd/irc-login.c b/src/ngircd/irc-login.c index 8e77e7fe..bbb2f0d5 100644 --- a/src/ngircd/irc-login.c +++ b/src/ngircd/irc-login.c @@ -1,34 +1,39 @@ /* * ngIRCd -- The Next Generation IRC Daemon - * Copyright (c)2001-2010 Alexander Barton (alex@barton.de) + * Copyright (c)2001-2011 Alexander Barton (alex@barton.de) and Contributors. * * 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 of the License, or * (at your option) any later version. * Please read the file COPYING, README and AUTHORS for more information. - * - * Login and logout */ - #include "portab.h" +/** + * @file + * Login and logout + */ + #include "imp.h" #include #include #include #include #include +#include +#include #include "ngircd.h" -#include "resolve.h" #include "conn-func.h" +#include "class.h" #include "conf.h" -#include "client.h" #include "channel.h" +#include "io.h" #include "log.h" #include "messages.h" +#include "pam.h" #include "parse.h" #include "irc.h" #include "irc-info.h" @@ -39,15 +44,26 @@ static bool Hello_User PARAMS(( CLIENT *Client )); +static bool Hello_User_PostAuth PARAMS(( CLIENT *Client )); static void Kill_Nick PARAMS(( char *Nick, char *Reason )); static void Introduce_Client PARAMS((CLIENT *To, CLIENT *Client, int Type)); +static void Reject_Client PARAMS((CLIENT *Client, const char *InternalReason)); + static void cb_introduceClient PARAMS((CLIENT *Client, CLIENT *Prefix, void *i)); +#ifdef PAM +static void cb_Read_Auth_Result PARAMS((int r_fd, UNUSED short events)); +#endif /** - * Handler for the IRC command "PASS". + * Handler for the IRC "PASS" command. + * * See RFC 2813 section 4.1.1, and RFC 2812 section 3.1.1. + * + * @param Client The client from which this command has been received. + * @param Req Request structure with prefix and all parameters. + * @returns CONNECTED or DISCONNECTED. */ GLOBAL bool IRC_PASS( CLIENT *Client, REQUEST *Req ) @@ -135,16 +151,17 @@ IRC_PASS( CLIENT *Client, REQUEST *Req ) if (type && strcmp(type, PROTOIRCPLUS) == 0) { /* The peer seems to be a server which supports the * IRC+ protocol (see doc/Protocol.txt). */ - serverver = ptr + 1; - flags = strchr(serverver, ':'); + serverver = ptr ? ptr + 1 : "?"; + flags = strchr(ptr ? serverver : impl, ':'); if (flags) { *flags = '\0'; flags++; } else flags = ""; Log(LOG_INFO, - "Peer announces itself as %s-%s using protocol %d.%d/IRC+ (flags: \"%s\").", - impl, serverver, protohigh, protolow, flags); + "Peer on conenction %d announces itself as %s-%s using protocol %d.%d/IRC+ (flags: \"%s\").", + Client_Conn(Client), impl, serverver, + protohigh, protolow, flags); } else { /* The peer seems to be a server supporting the * "original" IRC protocol (RFC 2813). */ @@ -153,8 +170,9 @@ IRC_PASS( CLIENT *Client, REQUEST *Req ) else flags = ""; Log(LOG_INFO, - "Peer announces itself as \"%s\" using protocol %d.%d (flags: \"%s\").", - impl, protohigh, protolow, flags); + "Peer on connection %d announces itself as \"%s\" using protocol %d.%d (flags: \"%s\").", + Client_Conn(Client), impl, + protohigh, protolow, flags); } Client_SetFlags(Client, flags); } @@ -164,10 +182,17 @@ IRC_PASS( CLIENT *Client, REQUEST *Req ) /** - * IRC "NICK" command. + * Handler for the IRC "NICK" command. + * + * See RFC 2812, 3.1.2 "Nick message", and RFC 2813, 4.1.3 "Nick". + * * This function implements the IRC command "NICK" which is used to register * with the server, to change already registered nicknames and to introduce * new users which are connected to other servers. + * + * @param Client The client from which this command has been received. + * @param Req Request structure with prefix and all parameters. + * @returns CONNECTED or DISCONNECTED. */ GLOBAL bool IRC_NICK( CLIENT *Client, REQUEST *Req ) @@ -247,6 +272,17 @@ IRC_NICK( CLIENT *Client, REQUEST *Req ) /* Register new nickname of this client */ Client_SetID( target, Req->argv[0] ); +#ifndef STRICT_RFC + if (Conf_AuthPing) { + Conn_SetAuthPing(Client_Conn(Client), rand()); + IRC_WriteStrClient(Client, "PING :%ld", + Conn_GetAuthPing(Client_Conn(Client))); + LogDebug("Connection %d: sent AUTH PING %ld ...", + Client_Conn(Client), + Conn_GetAuthPing(Client_Conn(Client))); + } +#endif + /* If we received a valid USER command already then * register the new client! */ if( Client_Type( Client ) == CLIENT_GOTUSER ) @@ -369,7 +405,13 @@ IRC_NICK( CLIENT *Client, REQUEST *Req ) /** - * Handler for the IRC command "USER". + * Handler for the IRC "USER" command. + * + * See RFC 2812, 3.1.3 "User message". + * + * @param Client The client from which this command has been received. + * @param Req Request structure with prefix and all parameters. + * @returns CONNECTED or DISCONNECTED. */ GLOBAL bool IRC_USER(CLIENT * Client, REQUEST * Req) @@ -403,6 +445,7 @@ IRC_USER(CLIENT * Client, REQUEST * Req) #else Client_SetUser(Client, Req->argv[0], false); #endif + Client_SetOrigUser(Client, Req->argv[0]); /* "Real name" or user info text: Don't set it to the empty * string, the original ircd can't deal with such "real names" @@ -435,6 +478,7 @@ IRC_USER(CLIENT * Client, REQUEST * Req) Req->prefix); Client_SetUser(c, Req->argv[0], true); + Client_SetOrigUser(c, Req->argv[0]); Client_SetHostname(c, Req->argv[1]); Client_SetInfo(c, Req->argv[3]); @@ -460,12 +504,18 @@ IRC_USER(CLIENT * Client, REQUEST * Req) /** - * Handler for the IRC command "SERVICE". + * Handler for the IRC "SERVICE" command. + * * This function implements IRC Services registration using the SERVICE command * defined in RFC 2812 3.1.6 and RFC 2813 4.1.4. + * * At the moment ngIRCd doesn't support directly linked services, so this * function returns ERR_ERRONEUSNICKNAME when the SERVICE command has not been * received from a peer server. + * + * @param Client The client from which this command has been received. + * @param Req Request structure with prefix and all parameters. + * @returns CONNECTED or DISCONNECTED.. */ GLOBAL bool IRC_SERVICE(CLIENT *Client, REQUEST *Req) @@ -558,8 +608,14 @@ IRC_SERVICE(CLIENT *Client, REQUEST *Req) /** - * Handler for the IRC command "WEBIRC". - * Syntax: WEBIRC + * Handler for the IRC "WEBIRC" command. + * + * See doc/Protocol.txt, section II.4: + * "Update webchat/proxy client information". + * + * @param Client The client from which this command has been received. + * @param Req Request structure with prefix and all parameters. + * @returns CONNECTED or DISCONNECTED. */ GLOBAL bool IRC_WEBIRC(CLIENT *Client, REQUEST *Req) @@ -577,43 +633,58 @@ IRC_WEBIRC(CLIENT *Client, REQUEST *Req) Client_Conn(Client), Req->argv[1], Req->argv[2], Req->argv[3]); Client_SetUser(Client, Req->argv[1], true); + Client_SetOrigUser(Client, Req->argv[1]); Client_SetHostname(Client, Req->argv[2]); return CONNECTED; } /* IRC_WEBIRC */ +/** + * Handler for the IRC "QUIT" command. + * + * See RFC 2812, 3.1.7 "Quit", and RFC 2813, 4.1.5 "Quit". + * + * @param Client The client from which this command has been received. + * @param Req Request structure with prefix and all parameters. + * @returns CONNECTED or DISCONNECTED. + */ GLOBAL bool IRC_QUIT( CLIENT *Client, REQUEST *Req ) { CLIENT *target; char quitmsg[LINE_LEN]; - assert( Client != NULL ); - assert( Req != NULL ); + assert(Client != NULL); + assert(Req != NULL); /* Wrong number of arguments? */ - if( Req->argc > 1 ) - return IRC_WriteStrClient( Client, ERR_NEEDMOREPARAMS_MSG, Client_ID( Client ), Req->command ); + if (Req->argc > 1) + return IRC_WriteStrClient(Client, ERR_NEEDMOREPARAMS_MSG, + Client_ID(Client), Req->command); if (Req->argc == 1) strlcpy(quitmsg, Req->argv[0], sizeof quitmsg); - if ( Client_Type( Client ) == CLIENT_SERVER ) - { + if (Client_Type(Client) == CLIENT_SERVER) { /* Server */ - target = Client_Search( Req->prefix ); - if( ! target ) - { - Log( LOG_WARNING, "Got QUIT from %s for unknown client!?", Client_ID( Client )); + target = Client_Search(Req->prefix); + if (!target) { + Log(LOG_WARNING, + "Got QUIT from %s for unknown client!?", + Client_ID(Client)); return CONNECTED; } - Client_Destroy( target, "Got QUIT command.", Req->argc == 1 ? quitmsg : NULL, true); - - return CONNECTED; - } - else - { + if (target != Client) { + Client_Destroy(target, "Got QUIT command.", + Req->argc == 1 ? quitmsg : NULL, true); + return CONNECTED; + } else { + Conn_Close(Client_Conn(Client), "Got QUIT command.", + Req->argc == 1 ? quitmsg : NULL, true); + return DISCONNECTED; + } + } else { if (Req->argc == 1 && quitmsg[0] != '\"') { /* " " to avoid confusion */ strlcpy(quitmsg, "\"", sizeof quitmsg); @@ -622,13 +693,46 @@ IRC_QUIT( CLIENT *Client, REQUEST *Req ) } /* User, Service, or not yet registered */ - Conn_Close( Client_Conn( Client ), "Got QUIT command.", Req->argc == 1 ? quitmsg : NULL, true); + Conn_Close(Client_Conn(Client), "Got QUIT command.", + Req->argc == 1 ? quitmsg : NULL, true); return DISCONNECTED; } } /* IRC_QUIT */ +#ifndef STRICT_RFC + +/** + * Handler for HTTP command, e.g. GET and POST + * + * We handle these commands here to avoid the quite long timeout when + * some user tries to access this IRC daemon using an web browser ... + * + * @param Client The client from which this command has been received. + * @param Req Request structure with prefix and all parameters. + * @returns CONNECTED or DISCONNECTED. + */ +GLOBAL bool +IRC_QUIT_HTTP( CLIENT *Client, REQUEST *Req ) +{ + Req->argc = 1; + Req->argv[0] = "Oops, HTTP request received? This is IRC!"; + return IRC_QUIT(Client, Req); +} /* IRC_QUIT_HTTP */ + +#endif + + +/** + * Handler for the IRC "PING" command. + * + * See RFC 2812, 3.7.2 "Ping message". + * + * @param Client The client from which this command has been received. + * @param Req Request structure with prefix and all parameters. + * @returns CONNECTED or DISCONNECTED. + */ GLOBAL bool IRC_PING(CLIENT *Client, REQUEST *Req) { @@ -698,22 +802,45 @@ IRC_PING(CLIENT *Client, REQUEST *Req) } /* IRC_PING */ +/** + * Handler for the IRC "PONG" command. + * + * See RFC 2812, 3.7.3 "Pong message". + * + * @param Client The client from which this command has been received. + * @param Req Request structure with prefix and all parameters. + * @returns CONNECTED or DISCONNECTED. + */ GLOBAL bool IRC_PONG(CLIENT *Client, REQUEST *Req) { CLIENT *target, *from; + CONN_ID conn; +#ifndef STRICT_RFC + long auth_ping; +#endif char *s; assert(Client != NULL); assert(Req != NULL); /* Wrong number of arguments? */ - if (Req->argc < 1) - return IRC_WriteStrClient(Client, ERR_NOORIGIN_MSG, - Client_ID(Client)); - if (Req->argc > 2) - return IRC_WriteStrClient(Client, ERR_NEEDMOREPARAMS_MSG, - Client_ID(Client), Req->command); + if (Req->argc < 1) { + if (Client_Type(Client) == CLIENT_USER) + return IRC_WriteStrClient(Client, ERR_NOORIGIN_MSG, + Client_ID(Client)); + else + return CONNECTED; + } + if (Req->argc > 2) { + if (Client_Type(Client) == CLIENT_USER) + return IRC_WriteStrClient(Client, + ERR_NEEDMOREPARAMS_MSG, + Client_ID(Client), + Req->command); + else + return CONNECTED; + } /* Forward? */ if (Req->argc == 2 && Client_Type(Client) == CLIENT_SERVER) { @@ -742,32 +869,214 @@ IRC_PONG(CLIENT *Client, REQUEST *Req) /* The connection timestamp has already been updated when the data has * been read from so socket, so we don't need to update it here. */ + + conn = Client_Conn(Client); + +#ifndef STRICT_RFC + /* Check authentication PING-PONG ... */ + auth_ping = Conn_GetAuthPing(conn); + if (auth_ping) { + LogDebug("AUTH PONG: waiting for token \"%ld\", got \"%s\" ...", + auth_ping, Req->argv[0]); + if (auth_ping == atoi(Req->argv[0])) { + Conn_SetAuthPing(conn, 0); + if (Client_Type(Client) == CLIENT_WAITAUTHPING) + Hello_User(Client); + } else + if (!IRC_WriteStrClient(Client, + "To connect, type /QUOTE PONG %ld", + auth_ping)) + return DISCONNECTED; + } +#endif + #ifdef DEBUG - if (Client_Conn(Client) > NONE) + if (conn > NONE) Log(LOG_DEBUG, - "Connection %d: received PONG. Lag: %ld seconds.", - Client_Conn(Client), + "Connection %d: received PONG. Lag: %ld seconds.", conn, time(NULL) - Conn_LastPing(Client_Conn(Client))); else Log(LOG_DEBUG, - "Connection %d: received PONG.", Client_Conn(Client)); + "Connection %d: received PONG.", conn); #endif return CONNECTED; } /* IRC_PONG */ +/** + * Initiate client registration. + * + * This function is called after the daemon received the required NICK and + * USER commands of a new client. If the daemon is compiled with support for + * PAM, the authentication sub-processs is forked; otherwise the global server + * password is checked. + * + * @param Client The client logging in. + * @returns CONNECTED or DISCONNECTED. + */ static bool Hello_User(CLIENT * Client) { +#ifdef PAM + int pipefd[2], result; + pid_t pid; +#endif + CONN_ID conn; + assert(Client != NULL); + conn = Client_Conn(Client); - /* Check password ... */ +#ifndef STRICT_RFC + if (Conf_AuthPing) { + /* Did we receive the "auth PONG" already? */ + if (Conn_GetAuthPing(conn)) { + Client_SetType(Client, CLIENT_WAITAUTHPING); + LogDebug("Connection %d: Waiting for AUTH PONG ...", conn); + return CONNECTED; + } + } +#endif + +#ifdef PAM + if (!Conf_PAM) { + /* Don't do any PAM authentication at all, instead emulate + * the beahiour of the daemon compiled without PAM support: + * because there can't be any "server password", all + * passwords supplied are classified as "wrong". */ + if(Client_Password(Client)[0] == '\0') + return Hello_User_PostAuth(Client); + Reject_Client(Client, "non-empty password"); + return DISCONNECTED; + } + + if (Conf_PAMIsOptional && strcmp(Client_Password(Client), "") == 0) { + /* Clients are not required to send a password and to be PAM- + * authenticated at all. If not, they won't become "identified" + * and keep the "~" in their supplied user name. + * Therefore it is sensible to either set Conf_PAMisOptional or + * to enable IDENT lookups -- not both. */ + return Hello_User_PostAuth(Client); + } + + /* Fork child process for PAM authentication; and make sure that the + * process timeout is set higher than the login timeout! */ + pid = Proc_Fork(Conn_GetProcStat(conn), pipefd, + cb_Read_Auth_Result, Conf_PongTimeout + 1); + if (pid > 0) { + LogDebug("Authenticator for connection %d created (PID %d).", + conn, pid); + return CONNECTED; + } else { + /* Sub process */ + Log_Init_Subprocess("Auth"); + Conn_CloseAllSockets(NONE); + result = PAM_Authenticate(Client); + if (write(pipefd[1], &result, sizeof(result)) != sizeof(result)) + Log_Subprocess(LOG_ERR, + "Failed to pipe result to parent!"); + Log_Exit_Subprocess("Auth"); + exit(0); + } +#else + /* Check global server password ... */ if (strcmp(Client_Password(Client), Conf_ServerPwd) != 0) { /* Bad password! */ - Log(LOG_ERR, - "Client \"%s\" rejected (connection %d): Bad password!", - Client_Mask(Client), Client_Conn(Client)); - Conn_Close(Client_Conn(Client), NULL, "Bad password", true); + Reject_Client(Client, "bad server password"); + return DISCONNECTED; + } + return Hello_User_PostAuth(Client); +#endif +} + + +#ifdef PAM + +/** + * Read result of the authenticatior sub-process from pipe + * + * @param r_fd File descriptor of the pipe. + * @param events (ignored IO specification) + */ +static void +cb_Read_Auth_Result(int r_fd, UNUSED short events) +{ + CONN_ID conn; + CLIENT *client; + int result; + size_t len; + PROC_STAT *proc; + + LogDebug("Auth: Got callback on fd %d, events %d", r_fd, events); + conn = Conn_GetFromProc(r_fd); + if (conn == NONE) { + /* Ops, none found? Probably the connection has already + * been closed!? We'll ignore that ... */ + io_close(r_fd); + LogDebug("Auth: Got callback for unknown connection!?"); + return; + } + proc = Conn_GetProcStat(conn); + client = Conn_GetClient(conn); + + /* Read result from pipe */ + len = Proc_Read(proc, &result, sizeof(result)); + Proc_Close(proc); + if (len == 0) + return; + + if (len != sizeof(result)) { + Log(LOG_CRIT, "Auth: Got malformed result!"); + Reject_Client(client, "internal error"); + return; + } + + if (result == true) { + Client_SetUser(client, Client_OrigUser(client), true); + (void)Hello_User_PostAuth(client); + } else + Reject_Client(client, "bad password"); +} + +#endif + + +/** + * Reject a client because of wrong password. + * + * This function is called either when the global server password or a password + * checked using PAM has been wrong. + * + * @param Client The client to reject. + */ +static void +Reject_Client(CLIENT *Client, const char *InternalReason) +{ + Log(LOG_ERR, + "User \"%s\" rejected (connection %d): %s!", + Client_Mask(Client), Client_Conn(Client), InternalReason); + Conn_Close(Client_Conn(Client), InternalReason, + "Access denied! Bad password?", true); +} + + +/** + * Finish client registration. + * + * Introduce the new client to the network and send all "hello messages" + * to it after authentication has been succeeded. + * + * @param Client The client logging in. + * @returns CONNECTED or DISCONNECTED. + */ +static bool +Hello_User_PostAuth(CLIENT *Client) +{ + if (Class_IsMember(CLASS_GLINE, Client)) { + Reject_Client(Client, "G-Line'd"); + return DISCONNECTED; + } + if (Class_IsMember(CLASS_KLINE, Client)) { + Reject_Client(Client, "K-Line'd"); return DISCONNECTED; } @@ -804,27 +1113,42 @@ Hello_User(CLIENT * Client) IRC_SetPenalty(Client, 1); return CONNECTED; -} /* Hello_User */ +} +/** + * Kill all users with a specific nick name in the network. + * + * @param Nick Nick name. + * @param Reason Reason for the KILL. + */ static void -Kill_Nick( char *Nick, char *Reason ) +Kill_Nick(char *Nick, char *Reason) { REQUEST r; - assert( Nick != NULL ); - assert( Reason != NULL ); + assert (Nick != NULL); + assert (Reason != NULL); - r.prefix = (char *)Client_ThisServer( ); + r.prefix = NULL; r.argv[0] = Nick; r.argv[1] = Reason; r.argc = 2; - Log( LOG_ERR, "User(s) with nick \"%s\" will be disconnected: %s", Nick, Reason ); - IRC_KILL( Client_ThisServer( ), &r ); + Log(LOG_ERR, "User(s) with nick \"%s\" will be disconnected: %s", + Nick, Reason); + + IRC_KILL(Client_ThisServer(), &r); } /* Kill_Nick */ +/** + * Introduce a new user or service client in the network. + * + * @param From Remote server introducing the client or NULL (local). + * @param Client New client. + * @param Type Type of the client (CLIENT_USER or CLIENT_SERVICE). + */ static void Introduce_Client(CLIENT *From, CLIENT *Client, int Type) { @@ -858,6 +1182,16 @@ Introduce_Client(CLIENT *From, CLIENT *Client, int Type) } /* Introduce_Client */ +/** + * Introduce a new user or service client to a remote server. + * + * This function differentiates between RFC1459 and RFC2813 server links and + * generates the appropriate commands to register the new user or service. + * + * @param To The remote server to inform. + * @param Prefix Prefix for the generated commands. + * @param data CLIENT structure of the new client. + */ static void cb_introduceClient(CLIENT *To, CLIENT *Prefix, void *data) {