ngircd-tor/src/ngircd/login.c

266 lines
6.8 KiB
C

/*
* ngIRCd -- The Next Generation IRC Daemon
* Copyright (c)2001-2014 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.
*/
#include "portab.h"
/**
* @file
* Functions to deal with client logins
*/
#include <assert.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include "conn.h"
#include "class.h"
#include "client-cap.h"
#include "channel.h"
#include "conf.h"
#include "parse.h"
#include "log.h"
#include "messages.h"
#include "ngircd.h"
#include "irc-info.h"
#include "irc-mode.h"
#include "irc-write.h"
#include "login.h"
#ifdef PAM
#include "io.h"
#include "pam.h"
static void cb_Read_Auth_Result PARAMS((int r_fd, UNUSED short events));
#endif
/**
* Initiate client login.
*
* 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.
*/
GLOBAL bool
Login_User(CLIENT * Client)
{
#ifdef PAM
int pipefd[2], result;
pid_t pid;
#endif
CONN_ID conn;
assert(Client != NULL);
conn = Client_Conn(Client);
#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
/* Still waiting for "CAP END" command? */
if (Client_Cap(Client) & CLIENT_CAP_PENDING) {
Client_SetType(Client, CLIENT_WAITCAPEND);
LogDebug("Connection %d: Waiting for CAP END ...", conn);
return CONNECTED;
}
#ifdef PAM
if (!Conf_PAM) {
/* Don't do any PAM authentication at all if PAM is not
* enabled, instead emulate the behavior of the daemon
* compiled without PAM support. */
if (strcmp(Conn_Password(conn), Conf_ServerPwd) == 0)
return Login_User_PostAuth(Client);
Client_Reject(Client, "Bad server password", false);
return DISCONNECTED;
}
if (Conf_PAMIsOptional &&
strcmp(Conn_Password(conn), "") == 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 Login_User_PostAuth(Client);
}
if (Conf_PAM) {
/* 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 return CONNECTED;
#else
/* Check global server password ... */
if (strcmp(Conn_Password(conn), Conf_ServerPwd) != 0) {
/* Bad password! */
Client_Reject(Client, "Bad server password", false);
return DISCONNECTED;
}
return Login_User_PostAuth(Client);
#endif
}
/**
* 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.
* @return CONNECTED or DISCONNECTED.
*/
GLOBAL bool
Login_User_PostAuth(CLIENT *Client)
{
REQUEST Req;
char modes[CLIENT_MODE_LEN + 1];
assert(Client != NULL);
if (Class_HandleServerBans(Client) != CONNECTED)
return DISCONNECTED;
Client_Introduce(NULL, Client, CLIENT_USER);
if (!IRC_WriteStrClient
(Client, RPL_WELCOME_MSG, Client_ID(Client), Client_Mask(Client)))
return false;
if (!IRC_WriteStrClient
(Client, RPL_YOURHOST_MSG, Client_ID(Client),
Client_ID(Client_ThisServer()), PACKAGE_VERSION, HOST_CPU,
HOST_VENDOR, HOST_OS))
return false;
if (!IRC_WriteStrClient
(Client, RPL_CREATED_MSG, Client_ID(Client), NGIRCd_StartStr))
return false;
if (!IRC_WriteStrClient
(Client, RPL_MYINFO_MSG, Client_ID(Client),
Client_ID(Client_ThisServer()), PACKAGE_VERSION, USERMODES,
CHANMODES))
return false;
/* Features supported by this server (005 numeric, ISUPPORT),
* see <http://www.irc.org/tech_docs/005.html> for details. */
if (!IRC_Send_ISUPPORT(Client))
return DISCONNECTED;
if (!IRC_Send_LUSERS(Client))
return DISCONNECTED;
if (!IRC_Show_MOTD(Client))
return DISCONNECTED;
/* Set default user modes */
if (Conf_DefaultUserModes[0]) {
snprintf(modes, sizeof(modes), "+%s", Conf_DefaultUserModes);
Req.prefix = Client_ThisServer();
Req.command = "MODE";
Req.argc = 2;
Req.argv[0] = Client_ID(Client);
Req.argv[1] = modes;
IRC_MODE(Client, &Req);
} else
IRC_SetPenalty(Client, 1);
return CONNECTED;
}
#ifdef PAM
/**
* Read result of the authenticator 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)
{
char user[CLIENT_USER_LEN], *ptr;
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!");
Client_Reject(client, "Internal error", false);
return;
}
if (result == true) {
/* Authentication succeeded, now set the correct user name
* supplied by the client (without prepended '~' for exmaple),
* but cut it at the first '@' character: */
strlcpy(user, Client_OrigUser(client), sizeof(user));
ptr = strchr(user, '@');
if (ptr)
*ptr = '\0';
Client_SetUser(client, user, true);
(void)Login_User_PostAuth(client);
} else
Client_Reject(client, "Bad password", false);
}
#endif
/* -eof- */