void start_auth(struct Client* client) { struct AuthRequest* auth = 0; assert(0 != client); if (conf_check_slines(client)) { sendheader(client, REPORT_USING_SLINE); SetSLined(client); release_auth_client(client); return; } auth = make_auth_request(client); assert(0 != auth); Debug((DEBUG_INFO, "Beginning auth request on client %p", client)); if (!feature_bool(FEAT_NODNS)) { if (LOOPBACK == inet_netof(cli_ip(client))) strcpy(cli_sockhost(client), cli_name(&me)); else { struct DNSQuery query; query.vptr = auth; query.callback = auth_dns_callback; if (IsUserPort(auth->client)) sendheader(client, REPORT_DO_DNS); cli_dns_reply(client) = gethost_byaddr((const char*) &(cli_ip(client)), &query); if (cli_dns_reply(client)) { ++(cli_dns_reply(client))->ref_count; ircd_strncpy(cli_sockhost(client), cli_dns_reply(client)->hp->h_name, HOSTLEN); if (IsUserPort(auth->client)) sendheader(client, REPORT_FIN_DNSC); Debug((DEBUG_LIST, "DNS entry for %p was cached", auth->client)); } else SetDNSPending(auth); } } if (start_auth_query(auth)) { Debug((DEBUG_LIST, "identd query for %p initiated successfully", auth->client)); link_auth_request(auth, &AuthPollList); } else if (IsDNSPending(auth)) { Debug((DEBUG_LIST, "identd query for %p not initiated successfully; " "waiting on DNS", auth->client)); link_auth_request(auth, &AuthIncompleteList); } else { Debug((DEBUG_LIST, "identd query for %p not initiated successfully; " "no DNS pending; releasing immediately", auth->client)); free_auth_request(auth); release_auth_client(client); } }
/* * destroy_auth_request - stop an auth request completely */ void destroy_auth_request(struct AuthRequest* auth, int send_reports) { struct AuthRequest** authList; if (IsDoingAuth(auth)) { authList = &AuthPollList; if (-1 < auth->fd) { close(auth->fd); auth->fd = -1; socket_del(&auth->socket); } if (send_reports && IsUserPort(auth->client)) sendheader(auth->client, REPORT_FAIL_ID); } else authList = &AuthIncompleteList; if (IsDNSPending(auth)) { delete_resolver_queries(auth); if (send_reports && IsUserPort(auth->client)) sendheader(auth->client, REPORT_FAIL_DNS); } if (send_reports) { log_write(LS_RESOLVER, L_INFO, 0, "DNS/AUTH timeout %s", get_client_name(auth->client, HIDE_IP)); release_auth_client(auth->client); } unlink_auth_request(auth, authList); free_auth_request(auth); }
/* * authsenderr - handle auth send errors */ static void auth_error(struct AuthRequest* auth, int kill) { ++ServerStats->is_abad; assert(0 != auth); close(auth->fd); auth->fd = -1; socket_del(&auth->socket); if (IsUserPort(auth->client)) sendheader(auth->client, REPORT_FAIL_ID); if (kill) { /* * we can't read the client info from the client socket, * close the client connection and free the client * Need to do this before we ClearAuth(auth) so we know * which list to remove the query from. --Bleep */ auth_kill_client(auth); return; } ClearAuth(auth); unlink_auth_request(auth, &AuthPollList); if (IsDNSPending(auth)) link_auth_request(auth, &AuthIncompleteList); else { release_auth_client(auth->client); free_auth_request(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 */ void read_auth_reply(struct AuthRequest* auth) { char* username = 0; unsigned int len; /* * rfc1453 sez we MUST accept 512 bytes */ char buf[BUFSIZE + 1]; assert(0 != auth); assert(0 != auth->client); assert(auth == cli_auth(auth->client)); if (IO_SUCCESS == os_recv_nonb(auth->fd, buf, BUFSIZE, &len)) { buf[len] = '\0'; Debug((DEBUG_LIST, "Auth %p [%p] reply: %s", auth, &auth->socket, buf)); username = check_ident_reply(buf); Debug((DEBUG_LIST, "Username: %s", username)); } close(auth->fd); auth->fd = -1; Debug((DEBUG_LIST, "Deleting auth [%p] socket %p", auth, &auth->socket)); socket_del(&auth->socket); ClearAuth(auth); if (!EmptyString(username)) { ircd_strncpy(cli_username(auth->client), username, USERLEN); /* * Not needed, struct is zeroed by memset * auth->client->username[USERLEN] = '\0'; */ SetGotId(auth->client); ++ServerStats->is_asuc; if (IsUserPort(auth->client)) sendheader(auth->client, REPORT_FIN_ID); } else { ++ServerStats->is_abad; } unlink_auth_request(auth, &AuthPollList); if (IsDNSPending(auth)) link_auth_request(auth, &AuthIncompleteList); else { release_auth_client(auth->client); free_auth_request(auth); } }
/** Handle a PASS message from an unregistered connection. * * \a parv[1..\a parc-1] is a possibly broken-up password. Since some * clients do not prefix the password with ':', this functions stitches * the elements back together before using it. * * See @ref m_functions for discussion of the arguments. * @param[in] cptr Client that sent us the message. * @param[in] sptr Original source of message. * @param[in] parc Number of arguments. * @param[in] parv Argument vector. */ int mr_pass(struct Client* cptr, struct Client* sptr, int parc, char* parv[]) { char password[BUFSIZE]; int arg, len; assert(0 != cptr); assert(cptr == sptr); assert(!IsRegistered(sptr)); /* Some clients (brokenly) send "PASS x y" rather than "PASS :x y" * when the user enters "x y" as the password. Unsplit arguments to * work around this. */ for (arg = 1, len = 0; arg < parc; ++arg) { ircd_strncpy(password + len, parv[arg], sizeof(password) - len); len += strlen(parv[arg]); password[len++] = ' '; } if (len > 0) --len; password[len] = '\0'; if (EmptyString(password)) return need_more_params(cptr, "PASS"); #if defined(DDB) if (IsUserPort(cptr)) { /* * PASS :server_pass[:ddb_pass] * PASS :ddb_pass */ char *ddb_pwd; ddb_pwd = strchr(password, ':'); if (ddb_pwd) *ddb_pwd++ = '\0'; else ddb_pwd = password; ircd_strncpy(cli_ddb_passwd(cptr), ddb_pwd, DDBPWDLEN); } #endif ircd_strncpy(cli_passwd(cptr), password, PASSWDLEN); return cli_auth(cptr) ? auth_set_password(cli_auth(cptr), password) : 0; }
/** Handle a SERVER message from an unregistered connection. * * \a parv has the following elements: * \li \a parv[1] is the server name * \li \a parv[2] is the hop count to the server * \li \a parv[3] is the start timestamp for the server * \li \a parv[4] is the link timestamp * \li \a parv[5] is the protocol version (P10 or J10) * \li \a parv[6] is the numnick mask for the server * \li \a parv[7] is a string of flags like +hs to mark hubs and services * \li \a parv[\a parc - 1] is the server description * * See @ref m_functions for discussion of the arguments. * @param[in] cptr Client that sent us the message. * @param[in] sptr Original source of message. * @param[in] parc Number of arguments. * @param[in] parv Argument vector. */ int mr_server(struct Client* cptr, struct Client* sptr, int parc, char* parv[]) { char* host; struct ConfItem* aconf; struct Jupe* ajupe; unsigned int hop; int ret; unsigned short prot; time_t start_timestamp; time_t timestamp; time_t recv_time; time_t ghost; if (IsUserPort(cptr)) return exit_client_msg(cptr, cptr, &me, "Cannot connect a server to a user port"); if (parc < 8) { need_more_params(sptr, "SERVER"); return exit_client(cptr, cptr, &me, "Need more parameters"); } host = clean_servername(parv[1]); if (!host) { sendto_opmask(0, SNO_OLDSNO, "Bogus server name (%s) from %s", host, cli_name(cptr)); return exit_client_msg(cptr, cptr, &me, "Bogus server name (%s)", host); } if ((ajupe = jupe_find(host)) && JupeIsActive(ajupe)) return exit_client_msg(cptr, sptr, &me, "Juped: %s", JupeReason(ajupe)); /* check connection rules */ if (0 != conf_eval_crule(host, CRULE_ALL)) { ServerStats->is_ref++; sendto_opmask(0, SNO_OLDSNO, "Refused connection from %s.", cli_name(cptr)); return exit_client(cptr, cptr, &me, "Disallowed by connection rule"); } log_write(LS_NETWORK, L_NOTICE, LOG_NOSNOTICE, "SERVER: %s %s[%s]", host, cli_sockhost(cptr), cli_sock_ip(cptr)); /* * Detect protocol */ hop = atoi(parv[2]); start_timestamp = atoi(parv[3]); timestamp = atoi(parv[4]); prot = parse_protocol(parv[5]); if (!prot) return exit_client_msg(cptr, sptr, &me, "Bogus protocol (%s)", parv[5]); else if (prot < atoi(MINOR_PROTOCOL)) return exit_new_server(cptr, sptr, host, timestamp, "Incompatible protocol: %s", parv[5]); Debug((DEBUG_INFO, "Got SERVER %s with timestamp [%s] age %Tu (%Tu)", host, parv[4], start_timestamp, cli_serv(&me)->timestamp)); if (timestamp < OLDEST_TS || start_timestamp < OLDEST_TS) return exit_client_msg(cptr, sptr, &me, "Bogus timestamps (%s %s)", parv[3], parv[4]); /* If the server had a different name before, change it. */ if (!EmptyString(cli_name(cptr)) && (IsUnknown(cptr) || IsHandshake(cptr)) && 0 != ircd_strcmp(cli_name(cptr), host)) hChangeClient(cptr, host); ircd_strncpy(cli_name(cptr), host, HOSTLEN); ircd_strncpy(cli_info(cptr), parv[parc-1][0] ? parv[parc-1] : cli_name(&me), REALLEN); cli_hopcount(cptr) = hop; if (conf_check_server(cptr)) { ++ServerStats->is_ref; sendto_opmask(0, SNO_OLDSNO, "Received unauthorized connection from %s", cli_name(cptr)); log_write(LS_NETWORK, L_NOTICE, LOG_NOSNOTICE, "Received unauthorized " "connection from %C [%s]", cptr, ircd_ntoa(&cli_ip(cptr))); return exit_client(cptr, cptr, &me, "No Connect block"); } host = cli_name(cptr); update_load(); if (!(aconf = find_conf_byname(cli_confs(cptr), host, CONF_SERVER))) { ++ServerStats->is_ref; sendto_opmask(0, SNO_OLDSNO, "Access denied. No conf line for server %s", cli_name(cptr)); return exit_client_msg(cptr, cptr, &me, "Access denied. No conf line for server %s", cli_name(cptr)); } #if defined(USE_SSL) if (!verify_sslclifp(cptr, aconf)) { ++ServerStats->is_ref; sendto_opmask(0, SNO_OLDSNO, "Access denied (SSL fingerprint mismatch) %s", cli_name(cptr)); return exit_client_msg(cptr, cptr, &me, "No Access (SSL fingerprint mismatch) %s", cli_name(cptr)); } #endif if (*aconf->passwd && !!strcmp(aconf->passwd, cli_passwd(cptr))) { ++ServerStats->is_ref; sendto_opmask(0, SNO_OLDSNO, "Access denied (passwd mismatch) %s", cli_name(cptr)); return exit_client_msg(cptr, cptr, &me, "No Access (passwd mismatch) %s", cli_name(cptr)); } memset(cli_passwd(cptr), 0, sizeof(cli_passwd(cptr))); ret = check_loop_and_lh(cptr, sptr, &ghost, host, (parc > 7 ? parv[6] : NULL), timestamp, hop, 1); if (ret != 1) return ret; make_server(cptr); cli_serv(cptr)->timestamp = timestamp; cli_serv(cptr)->prot = prot; cli_serv(cptr)->ghost = ghost; memset(cli_privs(cptr), 255, sizeof(struct Privs)); ClrPriv(cptr, PRIV_SET); SetServerYXX(cptr, cptr, parv[6]); update_uworld_flags(cptr); if (*parv[7] == '+') set_server_flags(cptr, parv[7] + 1); recv_time = TStime(); check_start_timestamp(cptr, timestamp, start_timestamp, recv_time); ret = server_estab(cptr, aconf); if (feature_bool(FEAT_RELIABLE_CLOCK) && abs(cli_serv(cptr)->timestamp - recv_time) > 30) { sendto_opmask(0, SNO_OLDSNO, "Connected to a net with a " "timestamp-clock difference of %Td seconds! " "Used SETTIME to correct this.", timestamp - recv_time); sendcmdto_prio_one(&me, CMD_SETTIME, cptr, "%Tu :%s", TStime(), cli_name(&me)); } return ret; }
/** * Exits a client of *any* type (user, server, etc) * from this server. Also, this generates all necessary prototol * messages that this exit may cause. * * This function implicitly exits all other clients depending on * this connection. * * For convenience, this function returns a suitable value for * m_function return value: * * CPTR_KILLED if (cptr == bcptr) * 0 if (cptr != bcptr) * * This function can be called in two ways: * 1) From before or in parse(), exiting the 'cptr', in which case it was * invoked as exit_client(cptr, cptr, &me,...), causing it to always * return CPTR_KILLED. * 2) Via parse from a m_function call, in which case it was invoked as * exit_client(cptr, acptr, sptr, ...). Here 'sptr' is known; the client * that generated the message in a way that we can assume he already * did remove acptr from memory himself (or in other cases we don't mind * because he will be delinked.) Or invoked as: * exit_client(cptr, acptr/sptr, &me, ...) when WE decide this one should * be removed. * In general: No generated SQUIT or QUIT should be sent to source link * sptr->from. And CPTR_KILLED should be returned if cptr got removed (too). * * --Run * @param cptr Connection currently being handled by read_message. * @param victim Client being killed. * @param killer Client that made the decision to remove \a victim. * @param comment Reason for the exit. * @return CPTR_KILLED if cptr == bcptr, else 0. */ int exit_client(struct Client *cptr, struct Client* victim, struct Client* killer, const char* comment) { struct Client* acptr = 0; struct DLink *dlp; time_t on_for; char comment1[HOSTLEN + HOSTLEN + 2]; assert(killer); if (MyConnect(victim)) { SetFlag(victim, FLAG_CLOSING); if (feature_bool(FEAT_CONNEXIT_NOTICES) && IsUser(victim)) sendto_opmask_butone(0, SNO_CONNEXIT, "Client exiting: %s (%s@%s) [%s] [%s] <%s%s>", cli_name(victim), cli_user(victim)->username, cli_user(victim)->host, comment, ircd_ntoa(&cli_ip(victim)), NumNick(victim) /* two %s's */); update_load(); on_for = CurrentTime - cli_firsttime(victim); if (IsUser(victim) || IsUserPort(victim)) auth_send_exit(victim); if (IsUser(victim)) log_write(LS_USER, L_TRACE, 0, "%Tu %i %s@%s %s %s %s%s %s :%s", cli_firsttime(victim), on_for, cli_user(victim)->username, cli_sockhost(victim), ircd_ntoa(&cli_ip(victim)), IsAccount(victim) ? cli_username(victim) : "0", NumNick(victim), /* two %s's */ cli_name(victim), cli_info(victim)); if (victim != cli_from(killer) /* The source knows already */ && IsClient(victim)) /* Not a Ping struct or Log file */ { if (IsServer(victim) || IsHandshake(victim)) sendcmdto_one(killer, CMD_SQUIT, victim, "%s 0 :%s", cli_name(&me), comment); else if (!IsConnecting(victim)) { if (!IsDead(victim)) { if (IsServer(victim)) sendcmdto_one(killer, CMD_ERROR, victim, ":Closing Link: %s by %s (%s)", cli_name(victim), cli_name(killer), comment); else sendrawto_one(victim, MSG_ERROR " :Closing Link: %s by %s (%s)", cli_name(victim), cli_name(IsServer(killer) ? &his : killer), comment); } } if ((IsServer(victim) || IsHandshake(victim) || IsConnecting(victim)) && (killer == &me || (IsServer(killer) && (strncmp(comment, "Leaf-only link", 14) || strncmp(comment, "Non-Hub link", 12))))) { /* * Note: check user == user needed to make sure we have the same * client */ if (cli_serv(victim)->user && *(cli_serv(victim))->by && (acptr = findNUser(cli_serv(victim)->by))) { if (cli_user(acptr) == cli_serv(victim)->user) { sendcmdto_one(&me, CMD_NOTICE, acptr, "%C :Link with %s canceled: %s", acptr, cli_name(victim), comment); } else { /* * not right client, set by to empty string */ acptr = 0; *(cli_serv(victim))->by = '\0'; } } if (killer == &me) sendto_opmask_butone(acptr, SNO_OLDSNO, "Link with %s canceled: %s", cli_name(victim), comment); } } /* * Close the Client connection first. */ close_connection(victim); } if (IsServer(victim)) { if (feature_bool(FEAT_HIS_NETSPLIT)) strcpy(comment1, "*.net *.split"); else { strcpy(comment1, cli_name(cli_serv(victim)->up)); strcat(comment1, " "); strcat(comment1, cli_name(victim)); } if (IsUser(killer)) sendto_opmask_butone(killer, SNO_OLDSNO, "%s SQUIT by %s [%s]:", (cli_user(killer)->server == victim || cli_user(killer)->server == cli_serv(victim)->up) ? "Local" : "Remote", get_client_name(killer, HIDE_IP), cli_name(cli_user(killer)->server)); else if (killer != &me && cli_serv(victim)->up != killer) sendto_opmask_butone(0, SNO_OLDSNO, "Received SQUIT %s from %s :", cli_name(victim), IsServer(killer) ? cli_name(killer) : get_client_name(killer, HIDE_IP)); sendto_opmask_butone(0, SNO_NETWORK, "Net break: %C %C (%s)", cli_serv(victim)->up, victim, comment); } /* * First generate the needed protocol for the other server links * except the source: */ for (dlp = cli_serv(&me)->down; dlp; dlp = dlp->next) { if (dlp->value.cptr != cli_from(killer) && dlp->value.cptr != victim) { if (IsServer(victim)) sendcmdto_one(killer, CMD_SQUIT, dlp->value.cptr, "%s %Tu :%s", cli_name(victim), cli_serv(victim)->timestamp, comment); else if (IsUser(victim) && !HasFlag(victim, FLAG_KILLED)) sendcmdto_one(victim, CMD_QUIT, dlp->value.cptr, ":%s", comment); } } /* Then remove the client structures */ if (IsServer(victim)) exit_downlinks(victim, killer, comment1); exit_one_client(victim, comment); /* * cptr can only have been killed if it was cptr itself that got killed here, * because cptr can never have been a dependent of victim --Run */ return (cptr == victim) ? CPTR_KILLED : 0; }
/* * 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 sockaddr_in remote_addr; struct sockaddr_in local_addr; int fd; IOResult result; assert(0 != auth); assert(0 != auth->client); if ((fd = socket(AF_INET, SOCK_STREAM, 0)) == -1) { ++ServerStats->is_abad; return 0; } if ((MAXCONNECTIONS - 10) < fd) { close(fd); return 0; } if (!os_set_nonblocking(fd)) { close(fd); return 0; } if (IsUserPort(auth->client)) 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 VIRTTUAL_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(&local_addr, 0, sizeof(struct sockaddr_in)); os_get_sockname(cli_fd(auth->client), &local_addr); local_addr.sin_port = htons(0); if (bind(fd, (struct sockaddr*) &local_addr, sizeof(struct sockaddr_in))) { close(fd); return 0; } remote_addr.sin_addr.s_addr = (cli_ip(auth->client)).s_addr; remote_addr.sin_port = htons(113); remote_addr.sin_family = AF_INET; if ((result = os_connect_nonb(fd, &remote_addr)) == IO_FAILURE || !socket_add(&auth->socket, auth_sock_callback, (void*) auth, result == IO_SUCCESS ? SS_CONNECTED : SS_CONNECTING, SOCK_EVENT_READABLE, fd)) { ServerStats->is_abad++; /* * No error report from this... */ close(fd); if (IsUserPort(auth->client)) sendheader(auth->client, REPORT_FAIL_ID); return 0; } auth->flags |= AM_SOCKET; auth->fd = fd; SetAuthConnect(auth); if (result == IO_SUCCESS) send_auth_query(auth); /* this does a SetAuthPending(auth) for us */ return 1; }
/* * 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; assert(0 != auth); /* * need to do this here so auth_kill_client doesn't * try have the resolver delete the query it's about * to delete anyways. --Bleep */ ClearDNSPending(auth); if (reply) { const struct hostent* hp = reply->hp; int i; assert(0 != hp); /* * Verify that the host to ip mapping is correct both ways and that * the ip#(s) for the socket is listed for the host. */ for (i = 0; hp->h_addr_list[i]; ++i) { if (0 == memcmp(hp->h_addr_list[i], &(cli_ip(auth->client)), sizeof(struct in_addr))) break; } if (!hp->h_addr_list[i]) { if (IsUserPort(auth->client)) sendheader(auth->client, REPORT_IP_MISMATCH); sendto_opmask_butone(0, SNO_IPMISMATCH, "IP# Mismatch: %s != %s[%s]", cli_sock_ip(auth->client), hp->h_name, ircd_ntoa(hp->h_addr_list[0])); if (feature_bool(FEAT_KILL_IPMISMATCH)) { auth_kill_client(auth); return; } } else if (!auth_verify_hostname(hp->h_name, HOSTLEN)) { if (IsUserPort(auth->client)) sendheader(auth->client, REPORT_INVAL_DNS); } else { ++reply->ref_count; cli_dns_reply(auth->client) = reply; ircd_strncpy(cli_sockhost(auth->client), hp->h_name, HOSTLEN); if (IsUserPort(auth->client)) sendheader(auth->client, REPORT_FIN_DNS); } } else { /* * this should have already been done by s_bsd.c in add_connection * * strcpy(auth->client->sockhost, auth->client->sock_ip); */ if (IsUserPort(auth->client)) sendheader(auth->client, REPORT_FAIL_DNS); } if (!IsDoingAuth(auth)) { release_auth_client(auth->client); unlink_auth_request(auth, &AuthIncompleteList); free_auth_request(auth); } }