/************************************************************************ * IRC - Internet Relay Chat, ircd/s_auth.c * Copyright (C) 1992 Darren Reed * * 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. */ #ifndef lint static char rcsid[] = "@(#)$Id: s_auth.c,v 1.43.2.5 2001/05/04 19:34:52 chopin Exp $"; #endif #include "os.h" #include "s_defines.h" #define S_AUTH_C #include "s_externs.h" #undef S_AUTH_C /* * set_clean_username * * As non OTHER type of usernames retrieved via ident lookup are forced as * usernames for the user, one has to be careful not to allow potentially * damaging characters in the username. * This procedure fills up cptr->username based on cptr->auth * Characters disallowed: * leading : for obvious reasons * spaces for obvious reasons * @ because of how get_sockhost() is implemented, * and because it's used from attached_Iline() * [ /trace parsing is impossible */ static void set_clean_username(cptr) aClient *cptr; { int i = 0, dirty = 0; char *s; if (cptr->auth == NULL) return; s = cptr->auth; if (index(cptr->auth, '[') || index(cptr->auth, '@') || strlen(cptr->auth) > USERLEN) dirty = 1; else if (cptr->auth[0] == ':') { dirty = 1; s += 1; } else { char *r = cptr->auth; while (*r) if (isspace(*(r++))) break; if (*r) dirty = 1; } if (dirty) cptr->username[i++] = '-'; while (i < USERLEN && *s) { if (*s != '@' && *s != '[' && !isspace(*s)) cptr->username[i++] = *s; s += 1; } cptr->username[i] = '\0'; if (!strcmp(cptr->username, cptr->auth)) { MyFree(cptr->auth); cptr->auth = cptr->username; } else { istat.is_authmem += strlen(cptr->auth) + 1; istat.is_auth += 1; } } #if defined(USE_IAUTH) u_char iauth_options = 0; u_int iauth_spawn = 0; char *iauth_version = NULL; static aExtCf *iauth_conf = NULL; static aExtData *iauth_stats = NULL; /* * sendto_iauth * * Send the buffer to the authentication slave process. * Return 0 if everything went well, -1 otherwise. */ #if ! USE_STDARG int sendto_iauth(pattern, p1, p2, p3, p4, p5, p6, p7, p8, p9, p10) char *pattern, *p1, *p2, *p3, *p4, *p5, *p6, *p7, *p8, *p9, *p10; #else int vsendto_iauth(char *pattern, va_list va) #endif { static char abuf[BUFSIZ], *p; int i, len; if (adfd < 0) return -1; #if ! USE_STDARG sprintf(abuf, pattern, p1, p2, p3, p4, p5, p6, p7, p8, p9, p10); #else vsprintf(abuf, pattern, va); #endif strcat(abuf, "\n"); p = abuf; len = strlen(p); do { i = write(adfd, abuf, len); if (i == -1) { if (errno != EAGAIN && errno != EWOULDBLOCK) { sendto_flag(SCH_AUTH, "Aiiie! lost slave " "authentication process"); close(adfd); adfd = -1; start_iauth(0); return -1; } i = 0; } p += i; len -= i; } while (len > 0); return 0; } # if USE_STDARG int sendto_iauth(char *pattern, ...) { int i; va_list va; va_start(va, pattern); i = vsendto_iauth(pattern, va); va_end(va); return i; } # endif /* * read_iauth * * read and process data from the authentication slave process. */ void read_iauth() { static char obuf[READBUF_SIZE+1], last = '?'; static int olen = 0, ia_dbg = 0; char buf[READBUF_SIZE+1], *start, *end, tbuf[BUFSIZ]; aClient *cptr; int i; if (adfd == -1) { olen = 0; return; } while (1) { if (olen) bcopy(obuf, buf, olen); if ((i = recv(adfd, buf+olen, READBUF_SIZE-olen, 0)) <= 0) { if (errno != EAGAIN && errno != EWOULDBLOCK) { sendto_flag(SCH_AUTH, "Aiiie! lost slave authentication process (errno = %d)", errno); close(adfd); adfd = -1; olen = 0; start_iauth(0); } break; } olen += i; buf[olen] = '\0'; start = buf; while (end = index(start, '\n')) { *end++ = '\0'; last = *start; if (*start == '>') { sendto_flag(SCH_AUTH, "%s", start+1); start = end; continue; } if (*start == 'G') { ia_dbg = atoi(start+2); if (ia_dbg) sendto_flag(SCH_AUTH,"ia_dbg = %d",ia_dbg); start = end; continue; } if (*start == 'O') /* options */ { iauth_options = 0; if (strchr(start+2, 'A')) iauth_options |= XOPT_EARLYPARSE; if (strchr(start+2, 'R')) iauth_options |= XOPT_REQUIRED; if (strchr(start+2, 'T')) iauth_options |= XOPT_NOTIMEOUT; if (strchr(start+2, 'W')) iauth_options |= XOPT_EXTWAIT; if (iauth_options) sendto_flag(SCH_AUTH, "iauth options: %x", iauth_options); start = end; continue; } if (*start == 'V') /* version */ { if (iauth_version) MyFree(iauth_version); iauth_version = mystrdup(start+2); sendto_flag(SCH_AUTH, "iauth version %s running.", iauth_version); start = end; continue; } if (*start == 'a') { aExtCf *ectmp; while (ectmp = iauth_conf) { iauth_conf = iauth_conf->next; MyFree(ectmp->line); MyFree(ectmp); } /* little lie.. ;) */ sendto_flag(SCH_AUTH, "New iauth configuration."); start = end; continue; } if (*start == 'A') { aExtCf **ectmp = &iauth_conf; while (*ectmp) ectmp = &((*ectmp)->next); *ectmp = (aExtCf *) MyMalloc(sizeof(aExtCf)); (*ectmp)->line = mystrdup(start+2); (*ectmp)->next = NULL; start = end; continue; } if (*start == 's') { aExtData *ectmp; while (ectmp = iauth_stats) { iauth_stats = iauth_stats->next; MyFree(ectmp->line); MyFree(ectmp); } iauth_stats = (aExtData *) MyMalloc(sizeof(aExtData)); iauth_stats->line = MyMalloc(60); sprintf(iauth_stats->line, "iauth modules statistics (%s)", myctime(timeofday)); iauth_stats->next = (aExtData *) MyMalloc(sizeof(aExtData)); iauth_stats->next->line = MyMalloc(60); sprintf(iauth_stats->next->line, "spawned: %d, current options: %X (%.11s)", iauth_spawn, iauth_options, (iauth_version) ? iauth_version : "???"); iauth_stats->next->next = NULL; start = end; continue; } if (*start == 'S') { aExtData **ectmp = &iauth_stats; while (*ectmp) ectmp = &((*ectmp)->next); *ectmp = (aExtData *) MyMalloc(sizeof(aExtData)); (*ectmp)->line = mystrdup(start+2); (*ectmp)->next = NULL; start = end; continue; } if (*start != 'U' && *start != 'u' && *start != 'o' && *start != 'K' && *start != 'k' && *start != 'D') { sendto_flag(SCH_AUTH, "Garbage from iauth [%s]", start); sendto_iauth("-1 E Garbage [%s]", start); /* ** The above should never happen, but i've seen it ** occasionnally, so let's try to get more info ** about it! -kalt */ sendto_flag(SCH_AUTH, "last=%u start=%x end=%x buf=%x olen=%d i=%d", last, start, end, buf, olen, i); sendto_iauth( "-1 E last=%u start=%x end=%x buf=%x olen=%d i=%d", last, start, end, buf, olen, i); start = end; continue; } if ((cptr = local[i = atoi(start+2)]) == NULL) { /* this is fairly common and can be ignored */ if (ia_dbg) { sendto_flag(SCH_AUTH, "Client %d is gone.", i); sendto_iauth("%d E Gone [%s]", i, start); } start = end; continue; } #ifndef INET6 sprintf(tbuf, "%c %d %s %u ", start[0], i, inetntoa((char *)&cptr->ip), cptr->port); #else sprintf(tbuf, "%c %d %s %u ", start[0], i, inetntop(AF_INET6, (char *)&cptr->ip, mydummy, MYDUMMY_SIZE), cptr->port); #endif if (strncmp(tbuf, start, strlen(tbuf))) { /* this is fairly common and can be ignored */ if (ia_dbg) { sendto_flag(SCH_AUTH, "Client mismatch: %d [%s] != [%s]", i, start, tbuf); sendto_iauth("%d E Mismatch [%s] != [%s]", i, start, tbuf); } start = end; continue; } if (start[0] == 'U') { if (*(start+strlen(tbuf)) == '\0') { sendto_flag(SCH_AUTH, "Null U message! %d [%s]", i, start); sendto_iauth("%d E Null U [%s]", i, start); start = end; continue; } if (cptr->auth != cptr->username) { istat.is_authmem -= strlen(cptr->auth) + 1; istat.is_auth -= 1; MyFree(cptr->auth); } cptr->auth = mystrdup(start+strlen(tbuf)); set_clean_username(cptr); cptr->flags |= FLAGS_GOTID; } else if (start[0] == 'u') { if (*(start+strlen(tbuf)) == '\0') { sendto_flag(SCH_AUTH, "Null u message! %d [%s]", i, start); sendto_iauth("%d E Null u [%s]", i, start); start = end; continue; } if (cptr->auth != cptr->username) { istat.is_authmem -= strlen(cptr->auth) + 1; istat.is_auth -= 1; MyFree(cptr->auth); } cptr->auth = MyMalloc(strlen(start+strlen(tbuf)) + 2); *cptr->auth = '-'; strcpy(cptr->auth+1, start+strlen(tbuf)); set_clean_username(cptr); cptr->flags |= FLAGS_GOTID; } else if (start[0] == 'o') { if (!WaitingXAuth(cptr)) { sendto_flag(SCH_AUTH, "Early o message discarded!"); sendto_iauth("%d E Early o [%s]", i,start); start = end; continue; } if (cptr->user == NULL) { /* just to be safe */ sendto_flag(SCH_AUTH, "Ack! cptr->user is NULL"); start = end; continue; } strncpyzt(cptr->user->username, tbuf, USERLEN+1); } else if (start[0] == 'D') { /*authentication finished*/ ClearXAuth(cptr); SetDoneXAuth(cptr); if (WaitingXAuth(cptr)) { ClearWXAuth(cptr); register_user(cptr, cptr, cptr->name, cptr->user->username); } else ClearWXAuth(cptr); } else { /* ** 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') { char *msg = strchr(start, ':'); cptr->exitc = EXITC_AREF; if (msg) strncpyzt(cptr->name, ++msg, HOSTLEN); else cptr->name[0] = '\0'; } else cptr->exitc = EXITC_AREFQ; /* should also check to make sure it's still an unregistered client.. */ /* should be extended to work after registration */ } start = end; } olen -= start - buf; if (olen) memcpy(obuf, start, olen); } } /* * report_iauth_conf * * called from m_stats(), this is the reply to /stats a */ void report_iauth_conf(sptr, to) aClient *sptr; char *to; { aExtCf *ectmp = iauth_conf; if (adfd < 0) return; while (ectmp) { sendto_one(sptr, ":%s %d %s :%s", ME, RPL_STATSIAUTH, to, ectmp->line); ectmp = ectmp->next; } } /* * report_iauth_stats * * called from m_stats(), this is part of the reply to /stats t */ void report_iauth_stats(sptr, to) aClient *sptr; char *to; { aExtData *ectmp = iauth_stats; while (ectmp) { sendto_one(sptr, ":%s %d %s :%s", ME, RPL_STATSDEBUG, to, ectmp->line); ectmp = ectmp->next; } } #endif /* * start_auth * * 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". */ void start_auth(cptr) Reg aClient *cptr; { #ifndef NO_IDENT struct SOCKADDR_IN us, them; SOCK_LEN_TYPE ulen, tlen; # if defined(USE_IAUTH) if ((iauth_options & XOPT_REQUIRED) && adfd < 0) return; # endif Debug((DEBUG_NOTICE,"start_auth(%x) fd %d status %d", cptr, cptr->fd, cptr->status)); if ((cptr->authfd = socket(AFINET, SOCK_STREAM, 0)) == -1) { # ifdef USE_SYSLOG syslog(LOG_ERR, "Unable to create auth socket for %s:%m", get_client_name(cptr,TRUE)); # endif Debug((DEBUG_ERROR, "Unable to create auth socket for %s:%s", get_client_name(cptr, TRUE), strerror(get_sockerr(cptr)))); ircstp->is_abad++; return; } if (cptr->authfd >= (MAXCONNECTIONS - 2)) { sendto_flag(SCH_ERROR, "Can't allocate fd for auth on %s", get_client_name(cptr, TRUE)); (void)close(cptr->authfd); return; } set_non_blocking(cptr->authfd, cptr); /* get remote host peer - so that we get right interface -- jrg */ tlen = ulen = sizeof(us); if (getpeername(cptr->fd, (struct sockaddr *)&them, &tlen) < 0) { /* we probably don't need this error message -kalt */ report_error("getpeername for auth request %s:%s", cptr); close(cptr->authfd); cptr->authfd = -1; return; } them.SIN_FAMILY = AFINET; /* We must bind the local end to the interface that they connected to: The local system might have more than one network address, and RFC931 check only sends port numbers: server takes IP addresses from query socket -- jrg */ (void)getsockname(cptr->fd, (struct sockaddr *)&us, &ulen); us.SIN_FAMILY = AFINET; # if defined(USE_IAUTH) if (adfd >= 0) { char abuf[BUFSIZ]; # ifdef INET6 sprintf(abuf, "%d C %s %u ", cptr->fd, inetntop(AF_INET6, (char *)&them.sin6_addr, mydummy, MYDUMMY_SIZE), ntohs(them.SIN_PORT)); sprintf(abuf+strlen(abuf), "%s %u", inetntop(AF_INET6, (char *)&us.sin6_addr, mydummy, MYDUMMY_SIZE), ntohs(us.SIN_PORT)); # else sprintf(abuf, "%d C %s %u ", cptr->fd, inetntoa((char *)&them.sin_addr),ntohs(them.SIN_PORT)); sprintf(abuf+strlen(abuf), "%s %u", inetntoa((char *)&us.sin_addr), ntohs(us.SIN_PORT)); # endif if (sendto_iauth(abuf) == 0) { close(cptr->authfd); cptr->authfd = -1; cptr->flags |= FLAGS_XAUTH; return; } } # endif # ifdef INET6 Debug((DEBUG_NOTICE,"auth(%x) from %s %x %x", cptr, inet_ntop(AF_INET6, (char *)&us.sin6_addr, mydummy, MYDUMMY_SIZE), us.sin6_addr.s6_addr[14], us.sin6_addr.s6_addr[15])); # else Debug((DEBUG_NOTICE,"auth(%x) from %s", cptr, inetntoa((char *)&us.sin_addr))); # endif them.SIN_PORT = htons(113); us.SIN_PORT = htons(0); /* bind assigns us a port */ if (bind(cptr->authfd, (struct SOCKADDR *)&us, ulen) >= 0) { (void)getsockname(cptr->fd, (struct SOCKADDR *)&us, &ulen); # ifdef INET6 Debug((DEBUG_NOTICE,"auth(%x) to %s", cptr, inet_ntop(AF_INET6, (char *)&them.sin6_addr, mydummy, MYDUMMY_SIZE))); # else Debug((DEBUG_NOTICE,"auth(%x) to %s", cptr, inetntoa((char *)&them.sin_addr))); # endif (void)alarm((unsigned)4); if (connect(cptr->authfd, (struct SOCKADDR *)&them, tlen) == -1 && errno != EINPROGRESS) { # ifdef INET6 Debug((DEBUG_ERROR, "auth(%x) connect failed to %s - %d", cptr, inet_ntop(AF_INET6, (char *)&them.sin6_addr, mydummy, MYDUMMY_SIZE), errno)); # else Debug((DEBUG_ERROR, "auth(%x) connect failed to %s - %d", cptr, inetntoa((char *)&them.sin_addr), errno)); # endif ircstp->is_abad++; /* * No error report from this... */ (void)alarm((unsigned)0); (void)close(cptr->authfd); cptr->authfd = -1; return; } (void)alarm((unsigned)0); } else { report_error("binding stream socket for auth request %s:%s", cptr); # ifdef INET6 Debug((DEBUG_ERROR,"auth(%x) bind failed on %s port %d - %d", cptr, inet_ntop(AF_INET6, (char *)&us.sin6_addr, mydummy, MYDUMMY_SIZE), ntohs(us.SIN_PORT), errno)); # else Debug((DEBUG_ERROR,"auth(%x) bind failed on %s port %d - %d", cptr, inetntoa((char *)&us.sin_addr), ntohs(us.SIN_PORT), errno)); # endif } cptr->flags |= (FLAGS_WRAUTH|FLAGS_AUTH); if (cptr->authfd > highest_fd) highest_fd = cptr->authfd; #endif return; } /* * send_authports * * 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 */ void send_authports(cptr) aClient *cptr; { struct SOCKADDR_IN us, them; char authbuf[32]; SOCK_LEN_TYPE ulen, tlen; Debug((DEBUG_NOTICE,"write_authports(%x) fd %d authfd %d stat %d", cptr, cptr->fd, cptr->authfd, cptr->status)); tlen = ulen = sizeof(us); if (getsockname(cptr->fd, (struct SOCKADDR *)&us, &ulen) || getpeername(cptr->fd, (struct SOCKADDR *)&them, &tlen)) { #ifdef USE_SYSLOG syslog(LOG_ERR, "auth get{sock,peer}name error for %s:%m", get_client_name(cptr, TRUE)); #endif goto authsenderr; } SPRINTF(authbuf, "%u , %u\r\n", (unsigned int)ntohs(them.SIN_PORT), (unsigned int)ntohs(us.SIN_PORT)); #ifdef INET6 Debug((DEBUG_SEND, "sending [%s] to auth port %s.113", authbuf, inet_ntop,(AF_INET6, (char *)&them.sin6_addr, mydummy, MYDUMMY_SIZE))); #else Debug((DEBUG_SEND, "sending [%s] to auth port %s.113", authbuf, inetntoa((char *)&them.sin_addr))); #endif if (write(cptr->authfd, authbuf, strlen(authbuf)) != strlen(authbuf)) { authsenderr: ircstp->is_abad++; (void)close(cptr->authfd); if (cptr->authfd == highest_fd) while (!local[highest_fd]) highest_fd--; cptr->authfd = -1; cptr->flags &= ~(FLAGS_AUTH|FLAGS_WRAUTH); return; } cptr->flags &= ~FLAGS_WRAUTH; return; } /* * read_authports * * read the reply (if any) from the ident server we connected to. * The actual read processijng here is pretty weak - no handling of the reply * if it is fragmented by IP. */ void read_authports(cptr) Reg aClient *cptr; { Reg char *s, *t; Reg int len; char ruser[513], system[8]; u_short remp = 0, locp = 0; *system = *ruser = '\0'; Debug((DEBUG_NOTICE,"read_authports(%x) fd %d authfd %d stat %d", cptr, cptr->fd, cptr->authfd, cptr->status)); /* * Nasty. Can't allow any other reads from client fd while we're * waiting on the authfd to return a full valid string. Use the * client's input buffer to buffer the authd reply. * Oh. this is needed because an authd reply may come back in more * than 1 read! -avalon */ if ((len = read(cptr->authfd, cptr->buffer + cptr->count, sizeof(cptr->buffer) - 1 - cptr->count)) >= 0) { cptr->count += len; cptr->buffer[cptr->count] = '\0'; } if ((len > 0) && (cptr->count != (sizeof(cptr->buffer) - 1)) && (sscanf(cptr->buffer, "%hd , %hd : USERID : %*[^:]: %512s", &remp, &locp, ruser) == 3)) { s = rindex(cptr->buffer, ':'); *s++ = '\0'; for (t = (rindex(cptr->buffer, ':') + 1); *t; t++) if (!isspace(*t)) break; strncpyzt(system, t, sizeof(system)); for (t = ruser; *s && (t < ruser + sizeof(ruser)); s++) if (!isspace(*s) && *s != ':') *t++ = *s; *t = '\0'; Debug((DEBUG_INFO,"auth reply ok [%s] [%s]", system, ruser)); } else if (len != 0) { if (!index(cptr->buffer, '\n') && !index(cptr->buffer, '\r')) return; Debug((DEBUG_ERROR,"local %d remote %d s %x", locp, remp, ruser)); Debug((DEBUG_ERROR,"bad auth reply in [%s]", cptr->buffer)); *ruser = '\0'; } (void)close(cptr->authfd); if (cptr->authfd == highest_fd) while (!local[highest_fd]) highest_fd--; cptr->count = 0; cptr->authfd = -1; ClearAuth(cptr); if (len > 0) Debug((DEBUG_INFO,"ident reply: [%s]", cptr->buffer)); if (!locp || !remp || !*ruser) { ircstp->is_abad++; return; } ircstp->is_asuc++; if (cptr->auth != cptr->username)/*impossible, but...*/ { istat.is_authmem -= strlen(cptr->auth) + 1; istat.is_auth -= 1; MyFree(cptr->auth); } if (!strncmp(system, "OTHER", 5)) { /* OTHER type of identifier */ cptr->auth = MyMalloc(strlen(ruser) + 2); *cptr->auth = '-'; strcpy(cptr->auth+1, ruser); } else cptr->auth = mystrdup(ruser); set_clean_username(cptr); cptr->flags |= FLAGS_GOTID; Debug((DEBUG_INFO, "got username [%s]", ruser)); return; }