/src/s_auth.c
C | 685 lines | 440 code | 93 blank | 152 comment | 77 complexity | b649965ba0219e97a5920c5768ed7955 MD5 | raw file
Possible License(s): GPL-2.0, BSD-3-Clause
- /*
- * ircd-hybrid: an advanced Internet Relay Chat Daemon(ircd).
- * s_auth.c: Functions for querying a users ident.
- *
- * Copyright (C) 2002 by the past and present ircd coders, and others.
- *
- * 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.
- *
- * 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: s_auth.c 409 2006-06-22 07:09:39Z jon $
- */
- /*
- * Changes:
- * July 6, 1999 - Rewrote most of the code here. When a client connects
- * to the server and passes initial socket validation checks, it
- * is owned by this module (auth) which returns it to the rest of the
- * server when dns and auth queries are finished. Until the client is
- * released, the server does not know it exists and does not process
- * any messages from it.
- * --Bleep Thomas Helvey <tomh@inxpress.net>
- */
- #include "stdinc.h"
- #include "tools.h"
- #include "list.h"
- #include "s_auth.h"
- #include "s_conf.h"
- #include "client.h"
- #include "common.h"
- #include "event.h"
- #include "fdlist.h" /* fdlist_add */
- #include "hook.h"
- #include "irc_string.h"
- #include "sprintf_irc.h"
- #include "ircd.h"
- #include "numeric.h"
- #include "packet.h"
- #include "irc_res.h"
- #include "s_bsd.h"
- #include "s_log.h"
- #include "s_stats.h"
- #include "send.h"
- #include "memory.h"
- #include "dnsbl.h"
- static const char *HeaderMessages[] = {
- ":%s NOTICE AUTH :*** Looking up your hostname...",
- ":%s NOTICE AUTH :*** Found your hostname",
- ":%s NOTICE AUTH :*** Couldn't look up your hostname",
- ":%s NOTICE AUTH :*** Checking Ident",
- ":%s NOTICE AUTH :*** Got Ident response",
- ":%s NOTICE AUTH :*** No Ident response",
- ":%s NOTICE AUTH :*** Your forward and reverse DNS do not match, ignoring hostname.",
- ":%s NOTICE AUTH :*** Your hostname is too long, ignoring hostname"
- };
- enum
- {
- REPORT_DO_DNS,
- REPORT_FIN_DNS,
- REPORT_FAIL_DNS,
- REPORT_DO_ID,
- REPORT_FIN_ID,
- REPORT_FAIL_ID,
- REPORT_IP_MISMATCH,
- REPORT_HOST_TOOLONG
- };
- #define sendheader(c, i) sendto_one((c), HeaderMessages[(i)], me.name)
- /*
- * Ok, the original was confusing.
- * Now there are two lists, an auth request can be on both at the same time
- * or only on one or the other.
- * - Dianora
- */
- static dlink_list auth_doing_dns_list = { NULL, NULL, 0 };
- static dlink_list auth_doing_ident_list = { NULL, NULL, 0 };
- /* We can't free AuthRequest in read_auth_reply as the underlying socket engine relies on auth->fd */
- static dlink_list dead_auth_list = { NULL, NULL, 0 };
- static EVH timeout_auth_queries_event;
- static PF read_auth_reply;
- static CNCB auth_connect_callback;
- static CBFUNC start_auth;
- struct Callback *auth_cb = NULL;
- /* init_auth()
- *
- * Initialise the auth code
- */
- void
- init_auth(void)
- {
- auth_cb = register_callback("start_auth", start_auth);
- eventAddIsh("timeout_auth_queries_event", timeout_auth_queries_event, NULL, 1);
- }
- /*
- * make_auth_request - allocate a new auth request
- */
- static struct AuthRequest *
- make_auth_request(struct Client *client)
- {
- struct AuthRequest *request = MyMalloc(sizeof(struct AuthRequest));
- request->client = client;
- request->timeout = CurrentTime + CONNECTTIMEOUT;
- return request;
- }
- /*
- * release_auth - release auth from auth system
- * this adds the client into the local client lists so it can be read by
- * the main io processing loop
- */
- static void
- release_auth(struct AuthRequest *auth)
- {
- /*
- * When a client has auth'ed, we want to start reading what it sends
- * us. This is what read_packet() does.
- * -- adrian
- */
- struct Client *client = auth->client;
- client->localClient->allow_read = MAX_FLOOD;
- comm_setflush(&client->localClient->fd, 1000, flood_recalc, client);
- if((client->node.prev != NULL) || (client->node.next != NULL))
- {
- sendto_realops_flags(UMODE_ALL, L_OPER,
- "already linked %s at %s:%d", client->name,
- __FILE__, __LINE__);
- ilog(L_ERROR, "already linked %s at %s:%d", client->name, __FILE__, __LINE__);
- assert(0 == 5);
- }
- else
- dlinkAdd(client, &client->node, &global_client_list);
- client->since = client->lasttime = client->firsttime = CurrentTime;
- client->flags |= FLAGS_FINISHED_AUTH;
- read_packet(&client->localClient->fd, client);
- dlinkAdd(auth, &auth->dead_node, &dead_auth_list);
- }
- /*
- * auth_dns_callback - called when resolver query finishes
- * if the query resulted in a successful search, hp will contain
- * a non-null pointer, otherwise hp will be null.
- * set the client on it's way to a connection completion, regardless
- * of success of failure
- */
- static void
- auth_dns_callback(void *vptr, struct DNSReply *reply)
- {
- struct AuthRequest *auth = (struct AuthRequest *) vptr;
- dlinkDelete(&auth->dns_node, &auth_doing_dns_list);
- ClearDNSPending(auth);
- if(reply != NULL)
- {
- struct sockaddr_in *v4, *v4dns;
- #ifdef IPV6
- struct sockaddr_in6 *v6, *v6dns;
- #endif
- int good = 1;
- #ifdef IPV6
- if(auth->client->localClient->ip.ss.ss_family == AF_INET6)
- {
- v6 = (struct sockaddr_in6 *) &auth->client->localClient->ip;
- v6dns = (struct sockaddr_in6 *) &reply->addr;
- if(memcmp(&v6->sin6_addr, &v6dns->sin6_addr, sizeof(struct in6_addr)) != 0)
- {
- sendheader(auth->client, REPORT_IP_MISMATCH);
- good = 0;
- }
- }
- else
- #endif
- {
- v4 = (struct sockaddr_in *) &auth->client->localClient->ip;
- v4dns = (struct sockaddr_in *) &reply->addr;
- if(v4->sin_addr.s_addr != v4dns->sin_addr.s_addr)
- {
- sendheader(auth->client, REPORT_IP_MISMATCH);
- good = 0;
- }
- }
- if(good && strlen(reply->h_name) <= HOSTLEN)
- {
- strlcpy(auth->client->host, reply->h_name, sizeof(auth->client->host));
- strlcpy(auth->client->realhost, reply->h_name,
- sizeof(auth->client->realhost));
- sendheader(auth->client, REPORT_FIN_DNS);
- }
- else if(strlen(reply->h_name) > HOSTLEN)
- sendheader(auth->client, REPORT_HOST_TOOLONG);
- }
- else
- sendheader(auth->client, REPORT_FAIL_DNS);
- MyFree(auth->client->localClient->dns_query);
- auth->client->localClient->dns_query = NULL;
- if(!IsDoingAuth(auth))
- release_auth(auth);
- }
- /*
- * authsenderr - handle auth send errors
- */
- static void
- auth_error(struct AuthRequest *auth)
- {
- ++ServerStats->is_abad;
- fd_close(&auth->fd);
- dlinkDelete(&auth->ident_node, &auth_doing_ident_list);
- ClearAuth(auth);
- sendheader(auth->client, REPORT_FAIL_ID);
- if(!IsDNSPending(auth) && !IsCrit(auth))
- release_auth(auth);
- }
- /*
- * start_auth_query - Flag the client to show that an attempt to
- * contact the ident server on
- * the client's host. The connect and subsequently the socket are all put
- * into 'non-blocking' mode. Should the connect or any later phase of the
- * identifing process fail, it is aborted and the user is given a username
- * of "unknown".
- */
- static int
- start_auth_query(struct AuthRequest *auth)
- {
- struct irc_ssaddr localaddr;
- socklen_t locallen = sizeof(struct irc_ssaddr);
- #ifdef IPV6
- struct sockaddr_in6 *v6;
- #else
- struct sockaddr_in *v4;
- #endif
- /* open a socket of the same type as the client socket */
- if(comm_open(&auth->fd, auth->client->localClient->ip.ss.ss_family,
- SOCK_STREAM, 0, "ident") == -1)
- {
- report_error(L_ALL, "creating auth stream socket %s:%s",
- get_client_name(auth->client, SHOW_IP), errno);
- ilog(L_ERROR, "Unable to create auth socket for %s",
- get_client_name(auth->client, SHOW_IP));
- ++ServerStats->is_abad;
- return 0;
- }
- sendheader(auth->client, REPORT_DO_ID);
- /*
- * get the local address of the client and bind to that to
- * make the auth request. This used to be done only for
- * ifdef VIRTUAL_HOST, but needs to be done for all clients
- * since the ident request must originate from that same address--
- * and machines with multiple IP addresses are common now
- */
- memset(&localaddr, 0, locallen);
- getsockname(auth->client->localClient->fd.fd, (struct sockaddr *) &localaddr, &locallen);
- #ifdef IPV6
- remove_ipv6_mapping(&localaddr);
- v6 = (struct sockaddr_in6 *) &localaddr;
- v6->sin6_port = htons(0);
- #else
- localaddr.ss_len = locallen;
- v4 = (struct sockaddr_in *) &localaddr;
- v4->sin_port = htons(0);
- #endif
- localaddr.ss_port = htons(0);
- SetDoingAuth(auth);
- dlinkAdd(auth, &auth->ident_node, &auth_doing_ident_list);
- comm_connect_tcp(&auth->fd, auth->client->sockhost, 113,
- (struct sockaddr *) &localaddr, localaddr.ss_len, auth_connect_callback,
- auth, auth->client->localClient->ip.ss.ss_family,
- GlobalSetOptions.ident_timeout);
- return 1; /* We suceed here for now */
- }
- /*
- * GetValidIdent - parse ident query reply from identd server
- *
- * Inputs - pointer to ident buf
- * Output - NULL if no valid ident found, otherwise pointer to name
- * Side effects -
- */
- /*
- * A few questions have been asked about this mess, obviously
- * it should have been commented better the first time.
- * The original idea was to remove all references to libc from ircd-hybrid.
- * Instead of having to write a replacement for sscanf(), I did a
- * rather gruseome parser here so we could remove this function call.
- * Note, that I had also removed a few floating point printfs as well (though
- * now we are still stuck with a few...)
- * Remember, we have a replacement ircd sprintf, we have bleeps fputs lib
- * it would have been nice to remove some unneeded code.
- * Oh well. If we don't remove libc stuff totally, then it would be
- * far cleaner to use sscanf()
- *
- * - Dianora
- */
- static char *
- GetValidIdent(char *buf)
- {
- int remp = 0;
- int locp = 0;
- char *colon1Ptr;
- char *colon2Ptr;
- char *colon3Ptr;
- char *commaPtr;
- char *remotePortString;
- /* All this to get rid of a sscanf() fun. */
- remotePortString = buf;
- if((colon1Ptr = strchr(remotePortString, ':')) == NULL)
- return 0;
- *colon1Ptr = '\0';
- colon1Ptr++;
- if((colon2Ptr = strchr(colon1Ptr, ':')) == NULL)
- return 0;
- *colon2Ptr = '\0';
- colon2Ptr++;
- if((commaPtr = strchr(remotePortString, ',')) == NULL)
- return 0;
- *commaPtr = '\0';
- commaPtr++;
- if((remp = atoi(remotePortString)) == 0)
- return 0;
- if((locp = atoi(commaPtr)) == 0)
- return 0;
- /* look for USERID bordered by first pair of colons */
- if(strstr(colon1Ptr, "USERID") == NULL)
- return 0;
- if((colon3Ptr = strchr(colon2Ptr, ':')) == NULL)
- return 0;
- *colon3Ptr = '\0';
- colon3Ptr++;
- return (colon3Ptr);
- }
- /*
- * start_auth
- *
- * inputs - pointer to client to auth
- * output - NONE
- * side effects - starts auth (identd) and dns queries for a client
- */
- static void *
- start_auth(va_list args)
- {
- struct Client *client = va_arg(args, struct Client *);
- struct AuthRequest *auth = NULL;
- assert(client != NULL);
- if(ConfigFileEntry.anti_spam_connect_numeric)
- sendto_one(client, form_str(ERR_TARGETTOOFAST), me.name, "*");
- auth = make_auth_request(client);
- SetCrit(auth);
- client->localClient->dns_query = MyMalloc(sizeof(struct DNSQuery));
- client->localClient->dns_query->ptr = auth;
- client->localClient->dns_query->callback = auth_dns_callback;
- start_dnsbl_lookup(client);
- sendheader(client, REPORT_DO_DNS);
- if(ConfigFileEntry.disable_auth == 0)
- start_auth_query(auth);
- /* auth order changed, before gethost_byaddr can immediately call
- * dns callback under win32 when the lookup cannot be started.
- * And that would do MyFree(auth) etc -adx */
- SetDNSPending(auth);
- dlinkAdd(auth, &auth->dns_node, &auth_doing_dns_list);
- ClearCrit(auth);
- gethost_byaddr(&client->localClient->ip, client->localClient->dns_query);
- return NULL;
- }
- /*
- * timeout_auth_queries - timeout resolver and identd requests
- * allow clients through if requests failed
- */
- static void
- timeout_auth_queries_event(void *notused)
- {
- dlink_node *ptr;
- dlink_node *next_ptr;
- struct AuthRequest *auth;
- DLINK_FOREACH_SAFE(ptr, next_ptr, auth_doing_ident_list.head)
- {
- auth = ptr->data;
- if(auth->timeout <= CurrentTime)
- {
- fd_close(&auth->fd);
- ++ServerStats->is_abad;
- sendheader(auth->client, REPORT_FAIL_ID);
- if(IsDNSPending(auth))
- {
- struct Client *client_p = auth->client;
- dlinkDelete(&auth->dns_node, &auth_doing_dns_list);
- if(client_p->localClient->dns_query != NULL)
- {
- delete_resolver_queries(client_p->localClient->dns_query);
- MyFree(client_p->localClient->dns_query);
- }
- auth->client->localClient->dns_query = NULL;
- sendheader(client_p, REPORT_FAIL_DNS);
- }
- ilog(L_INFO, "DNS/AUTH timeout %s", get_client_name(auth->client, SHOW_IP));
- dlinkDelete(&auth->ident_node, &auth_doing_ident_list);
- release_auth(auth);
- }
- }
- DLINK_FOREACH_SAFE(ptr, next_ptr, dead_auth_list.head)
- {
- auth = ptr->data;
- dlinkDelete(&auth->dead_node, &dead_auth_list);
- MyFree(auth);
- }
- }
- /*
- * auth_connect_callback() - deal with the result of comm_connect_tcp()
- *
- * If the connection failed, we simply close the auth fd and report
- * a failure. If the connection suceeded send the ident server a query
- * giving "theirport , ourport". The write is only attempted *once* so
- * it is deemed to be a fail if the entire write doesn't write all the
- * data given. This shouldnt be a problem since the socket should have
- * a write buffer far greater than this message to store it in should
- * problems arise. -avalon
- */
- static void
- auth_connect_callback(fde_t * fd, int error, void *data)
- {
- struct AuthRequest *auth = data;
- struct irc_ssaddr us;
- struct irc_ssaddr them;
- char authbuf[32];
- socklen_t ulen = sizeof(struct irc_ssaddr);
- socklen_t tlen = sizeof(struct irc_ssaddr);
- u_int16_t uport, tport;
- #ifdef IPV6
- struct sockaddr_in6 *v6;
- #else
- struct sockaddr_in *v4;
- #endif
- if(error != COMM_OK)
- {
- auth_error(auth);
- return;
- }
- if(getsockname(auth->client->localClient->fd.fd, (struct sockaddr *) &us,
- (socklen_t *) & ulen) ||
- getpeername(auth->client->localClient->fd.fd, (struct sockaddr *) &them,
- (socklen_t *) & tlen))
- {
- ilog(L_INFO, "auth get{sock,peer}name error for %s",
- get_client_name(auth->client, SHOW_IP));
- auth_error(auth);
- return;
- }
- #ifdef IPV6
- v6 = (struct sockaddr_in6 *) &us;
- uport = ntohs(v6->sin6_port);
- v6 = (struct sockaddr_in6 *) &them;
- tport = ntohs(v6->sin6_port);
- remove_ipv6_mapping(&us);
- remove_ipv6_mapping(&them);
- #else
- v4 = (struct sockaddr_in *) &us;
- uport = ntohs(v4->sin_port);
- v4 = (struct sockaddr_in *) &them;
- tport = ntohs(v4->sin_port);
- us.ss_len = ulen;
- them.ss_len = tlen;
- #endif
- ircsprintf(authbuf, "%u , %u\r\n", tport, uport);
- if(send(fd->fd, authbuf, strlen(authbuf), 0) == -1)
- {
- auth_error(auth);
- return;
- }
- read_auth_reply(&auth->fd, auth);
- }
- /*
- * read_auth_reply - read the reply (if any) from the ident server
- * we connected to.
- * We only give it one shot, if the reply isn't good the first time
- * fail the authentication entirely. --Bleep
- */
- #define AUTH_BUFSIZ 128
- static void
- read_auth_reply(fde_t * fd, void *data)
- {
- struct AuthRequest *auth = data;
- char *s = NULL;
- char *t = NULL;
- int len;
- int count;
- char buf[AUTH_BUFSIZ + 1]; /* buffer to read auth reply into */
- /* Why?
- * Well, recv() on many POSIX systems is a per-packet operation,
- * and we do not necessarily want this, because on lowspec machines,
- * the ident response may come back fragmented, thus resulting in an
- * invalid ident response, even if the ident response was really OK.
- *
- * So PLEASE do not change this code to recv without being aware of the
- * consequences.
- *
- * --nenolod
- */
- #ifndef _WIN32
- len = read(fd->fd, buf, AUTH_BUFSIZ);
- #else
- len = recv(fd->fd, buf, AUTH_BUFSIZ, 0);
- #endif
- if(len < 0)
- {
- #ifdef _WIN32
- errno = WSAGetLastError();
- #endif
- if(ignoreErrno(errno))
- comm_setselect(fd, COMM_SELECT_READ, read_auth_reply, auth, 0);
- else
- auth_error(auth);
- return;
- }
- if(len > 0)
- {
- buf[len] = '\0';
- if((s = GetValidIdent(buf)))
- {
- t = auth->client->username;
- while(*s == '~' || *s == '^')
- s++;
- for(count = USERLEN; *s && count; s++)
- {
- if(*s == '@')
- break;
- if(!IsSpace(*s) && *s != ':' && *s != '[')
- {
- *t++ = *s;
- count--;
- }
- }
- *t = '\0';
- }
- }
- fd_close(fd);
- dlinkDelete(&auth->ident_node, &auth_doing_ident_list);
- ClearAuth(auth);
- if(s == NULL)
- {
- sendheader(auth->client, REPORT_FAIL_ID);
- ++ServerStats->is_abad;
- }
- else
- {
- sendheader(auth->client, REPORT_FIN_ID);
- ++ServerStats->is_asuc;
- SetGotId(auth->client);
- }
- if(!IsDNSPending(auth) && !IsCrit(auth))
- release_auth(auth);
- }
- /*
- * delete_auth()
- */
- void
- delete_auth(struct Client *target_p)
- {
- dlink_node *ptr;
- dlink_node *next_ptr;
- struct AuthRequest *auth;
- if(!IsUnknown(target_p))
- return;
- if(target_p->localClient->dns_query != NULL)
- DLINK_FOREACH_SAFE(ptr, next_ptr, auth_doing_dns_list.head)
- {
- auth = ptr->data;
- if(auth->client == target_p)
- {
- delete_resolver_queries(target_p->localClient->dns_query);
- MyFree(target_p->localClient->dns_query);
- target_p->localClient->dns_query = NULL;
- dlinkDelete(&auth->dns_node, &auth_doing_dns_list);
- if(!IsDoingAuth(auth))
- {
- MyFree(auth);
- return;
- }
- }
- }
- DLINK_FOREACH_SAFE(ptr, next_ptr, auth_doing_ident_list.head)
- {
- auth = ptr->data;
- if(auth->client == target_p)
- {
- fd_close(&auth->fd);
- dlinkDelete(&auth->ident_node, &auth_doing_ident_list);
- MyFree(auth);
- }
- }
- }