/** Initialize the connection class list. * A connection class named "default" is created, with ping frequency, * connection frequency, maximum links and max SendQ values from the * corresponding configuration features. */ void init_class(void) { if (!connClassList) { connClassList = (struct ConnectionClass*) make_class(); connClassList->next = 0; } /* We had better not try and free this... */ ConClass(connClassList) = "default"; PingFreq(connClassList) = feature_int(FEAT_PINGFREQUENCY); ConFreq(connClassList) = feature_int(FEAT_CONNECTFREQUENCY); MaxLinks(connClassList) = feature_int(FEAT_MAXIMUM_LINKS); MaxSendq(connClassList) = feature_uint(FEAT_DEFAULTMAXSENDQLENGTH); connClassList->valid = 1; Links(connClassList) = 1; }
/** Make sure we have a connection class named \a name. * If one does not exist, create it. Then set its ping frequency, * connection frequency, maximum link count, and max SendQ according * to the parameters. * @param[in] name Connection class name. * @param[in] ping Ping frequency for clients in this class. * @param[in] confreq Connection frequency for clients. * @param[in] maxli Maximum link count for class. * @param[in] sendq Max SendQ for clients. */ void add_class(char *name, unsigned int ping, unsigned int confreq, unsigned int maxli, unsigned int sendq) { struct ConnectionClass* p; Debug((DEBUG_DEBUG, "Add Class %s: cf: %u pf: %u ml: %u sq: %d", name, confreq, ping, maxli, sendq)); assert(name != NULL); p = do_find_class(name, 1); if (!p) p = make_class(); else MyFree(ConClass(p)); ConClass(p) = name; ConFreq(p) = confreq; PingFreq(p) = ping; MaxLinks(p) = maxli; MaxSendq(p) = (sendq > 0U) ? sendq : feature_uint(FEAT_DEFAULTMAXSENDQLENGTH); p->valid = 1; }
/** Return maximum SendQ length for a client. * @param[in] cptr Local client to check. * @return Number of bytes allowed in SendQ for \a cptr. */ unsigned int get_sendq(struct Client *cptr) { assert(0 != cptr); assert(0 != cli_local(cptr)); if (cli_max_sendq(cptr)) return cli_max_sendq(cptr); else if (cli_confs(cptr)) { struct SLink* tmp; struct ConnectionClass* cl; for (tmp = cli_confs(cptr); tmp; tmp = tmp->next) { if (!tmp->value.aconf || !(cl = tmp->value.aconf->conn_class)) continue; if (ConClass(cl) != NULL) { cli_max_sendq(cptr) = MaxSendq(cl); return cli_max_sendq(cptr); } } } return feature_uint(FEAT_DEFAULTMAXSENDQLENGTH); }
/** Return a string describing important configuration information. * @return Pointer to a static buffer. */ const char* debug_serveropts(void) { unsigned int bp; int i = 0; #define AddC(c) serveropts[i++] = (c) bp = feature_uint(FEAT_BUFFERPOOL); if (bp < 1000000) { AddC('b'); if (bp > 99999) AddC((char)('0' + (bp / 100000))); if (bp > 9999) AddC((char)('0' + (bp / 10000) % 10)); AddC((char)('0' + (bp / 1000) % 10)); } else { AddC('B'); if (bp > 99999999) AddC((char)('0' + (bp / 100000000))); if (bp > 9999999) AddC((char)('0' + (bp / 10000000) % 10)); AddC((char)('0' + (bp / 1000000) % 10)); } #ifndef NDEBUG AddC('A'); #endif #ifdef DEBUGMODE AddC('D'); #endif if (feature_bool(FEAT_HUB)) AddC('H'); if (feature_bool(FEAT_IDLE_FROM_MSG)) AddC('M'); if (feature_bool(FEAT_RELIABLE_CLOCK)) AddC('R'); #if defined(USE_POLL) && defined(HAVE_POLL_H) AddC('U'); #endif #ifdef IPV6 AddC('6'); #endif #if defined(USE_SSL) AddC('S'); #endif #if defined(USE_ZLIB) AddC('Z'); #endif #if defined(PCRE) AddC('P'); #endif #if defined(UNDERNET) AddC('+'); AddC('U'); AddC('N'); AddC('E'); AddC('T'); #endif #if defined(DDB) AddC('+'); AddC('D'); AddC('D'); AddC('B'); #endif serveropts[i] = '\0'; return serveropts; }
/** Report memory usage statistics to a client. * @param cptr Client to send data to. * @param sd StatDesc that generated the stats request (ignored). * @param param Extra parameter from user (ignored). */ void count_memory(struct Client *cptr, const struct StatDesc *sd, char *param) { struct Client *acptr; struct Invite *inv; struct SLink *link; struct Ban *ban; struct Channel *chptr; struct ConfItem *aconf; const struct ConnectionClass* cltmp; struct Membership* member; int acc = 0, /* accounts */ c = 0, /* clients */ cn = 0, /* connections */ ch = 0, /* channels */ lcc = 0, /* local client conf links */ chb = 0, /* channel bans */ wwu = 0, /* whowas users */ cl = 0, /* classes */ co = 0, /* conf lines */ listeners = 0, /* listeners */ memberships = 0; /* channel memberships */ int usi = 0, /* users invited */ aw = 0, /* aways set */ wwa = 0, /* whowas aways */ gl = 0, /* glines */ ju = 0; /* jupes */ size_t chm = 0, /* memory used by channels */ chbm = 0, /* memory used by channel bans */ cm = 0, /* memory used by clients */ cnm = 0, /* memory used by connections */ us = 0, /* user structs */ usm = 0, /* memory used by user structs */ awm = 0, /* memory used by aways */ wwam = 0, /* whowas away memory used */ wwm = 0, /* whowas array memory used */ wt = 0, /* watch entrys */ wtm = 0, /* memory used by watchs */ #if defined(DDB) dbs = 0, /* keys of database */ dbm = 0, /* memory used by DDB */ #endif glm = 0, /* memory used by glines */ jum = 0, /* memory used by jupes */ com = 0, /* memory used by conf lines */ dbufs_allocated = 0, /* memory used by dbufs */ dbufs_used = 0, /* memory used by dbufs */ msg_allocated = 0, /* memory used by struct Msg */ msgbuf_allocated = 0, /* memory used by struct MsgBuf */ listenersm = 0, /* memory used by listetners */ rm = 0, /* res memory used */ totcl = 0, totch = 0, totww = 0, tot = 0; count_whowas_memory(&wwu, &wwm, &wwa, &wwam); wwm += sizeof(struct Whowas) * feature_uint(FEAT_NICKNAMEHISTORYLENGTH); wwm += sizeof(struct Whowas *) * WW_MAX; for (acptr = GlobalClientList; acptr; acptr = cli_next(acptr)) { c++; if (MyConnect(acptr)) { cn++; for (link = cli_confs(acptr); link; link = link->next) lcc++; } if (cli_user(acptr)) { for (inv = cli_user(acptr)->invited; inv; inv = inv->next_user) usi++; for (member = cli_user(acptr)->channel; member; member = member->next_channel) ++memberships; if (cli_user(acptr)->away) { aw++; awm += (strlen(cli_user(acptr)->away) + 1); } } #if defined(UNDERNET) if (IsAccount(acptr)) acc++; #endif } cm = c * sizeof(struct Client); cnm = cn * sizeof(struct Connection); user_count_memory(&us, &usm); for (chptr = GlobalChannelList; chptr; chptr = chptr->next) { ch++; chm += (strlen(chptr->chname) + sizeof(struct Channel)); for (ban = chptr->banlist; ban; ban = ban->next) { chb++; chbm += strlen(ban->who) + strlen(ban->banstr) + 2 + sizeof(*ban); } } for (aconf = GlobalConfList; aconf; aconf = aconf->next) { co++; com += aconf->host ? strlen(aconf->host) + 1 : 0; com += aconf->passwd ? strlen(aconf->passwd) + 1 : 0; com += aconf->name ? strlen(aconf->name) + 1 : 0; com += sizeof(struct ConfItem); } for (cltmp = get_class_list(); cltmp; cltmp = cltmp->next) cl++; #if defined(USE_SSL) send_reply(cptr, SND_EXPLICIT | RPL_STATSDEBUG, ":Clients %d(%zu) Connections %d(%zu) SSL %d", c, cm, cn, cnm, ssl_count()); #else send_reply(cptr, SND_EXPLICIT | RPL_STATSDEBUG, ":Clients %d(%zu) Connections %d(%zu)", c, cm, cn, cnm); #endif send_reply(cptr, SND_EXPLICIT | RPL_STATSDEBUG, ":Users %zu(%zu) Accounts %d(%zu) Invites %d(%zu)", us, usm, acc, acc * (ACCOUNTLEN + 1), usi, usi * sizeof(struct Invite)); send_reply(cptr, SND_EXPLICIT | RPL_STATSDEBUG, ":User channels %d(%zu) Aways %d(%zu)", memberships, memberships * sizeof(struct Membership), aw, awm); totcl = cm + cnm + us * sizeof(struct User) + memberships * sizeof(struct Membership) + awm; totcl += lcc * sizeof(struct SLink) + usi * sizeof(struct SLink); send_reply(cptr, SND_EXPLICIT | RPL_STATSDEBUG, ":Conflines %d(%zu) Attached %d(%zu) Classes %d(%zu)", co, com, lcc, lcc * sizeof(struct SLink), cl, cl * sizeof(struct ConnectionClass)); send_reply(cptr, SND_EXPLICIT | RPL_STATSDEBUG, ":Channels %d(%zu) Bans %d(%zu)", ch, chm, chb, chbm); send_reply(cptr, SND_EXPLICIT | RPL_STATSDEBUG, ":Channel Members %d(%zu)", memberships, memberships * sizeof(struct Membership)); totch = chm + chbm; send_reply(cptr, SND_EXPLICIT | RPL_STATSDEBUG, ":Whowas Users %d(%zu) Away %d(%zu) Array %u(%zu)", wwu, wwu * sizeof(struct User), wwa, wwam, feature_uint(FEAT_NICKNAMEHISTORYLENGTH), wwm); totww = wwu * sizeof(struct User) + wwam + wwm; watch_count_memory(&wt, &wtm); send_reply(cptr, SND_EXPLICIT | RPL_STATSDEBUG, ":Watchs %d(%zu)", wt, wtm); motd_memory_count(cptr); gl = gline_memory_count(&glm); ju = jupe_memory_count(&jum); send_reply(cptr, SND_EXPLICIT | RPL_STATSDEBUG, ":Glines %d(%zu) Jupes %d(%zu)", gl, glm, ju, jum); send_reply(cptr, SND_EXPLICIT | RPL_STATSDEBUG, ":Hash: client %d(%zu), chan is the same", HASHSIZE, sizeof(void *) * HASHSIZE); count_listener_memory(&listeners, &listenersm); send_reply(cptr, SND_EXPLICIT | RPL_STATSDEBUG, ":Listeners allocated %d(%zu)", listeners, listenersm); #if defined(DDB) ddb_count_memory(&dbs, &dbm); send_reply(cptr, SND_EXPLICIT | RPL_STATSDEBUG, ":DDB keys allocated %d(%zu)", dbs, dbm); #endif /* * NOTE: this count will be accurate only for the exact instant that this * message is being sent, so the count is affected by the dbufs that * are being used to send this message out. If this is not desired, move * the dbuf_count_memory call to a place before we start sending messages * and cache DBufAllocCount and DBufUsedCount in variables until they * are sent. */ dbuf_count_memory(&dbufs_allocated, &dbufs_used); send_reply(cptr, SND_EXPLICIT | RPL_STATSDEBUG, ":DBufs allocated %u(%zu) used %u(%zu)", DBufAllocCount, dbufs_allocated, DBufUsedCount, dbufs_used); /* The DBuf caveats now count for this, but this routine now sends * replies all on its own. */ msgq_count_memory(cptr, &msg_allocated, &msgbuf_allocated); rm = cres_mem(cptr); tot = totww + totch + totcl + com + cl * sizeof(struct ConnectionClass) + dbufs_allocated + msg_allocated + msgbuf_allocated + rm; tot += sizeof(void *) * HASHSIZE * 3; #if defined(MDEBUG) send_reply(cptr, SND_EXPLICIT | RPL_STATSDEBUG, ":Allocations: %zu(%zu)", fda_get_block_count(), fda_get_byte_count()); #endif send_reply(cptr, SND_EXPLICIT | RPL_STATSDEBUG, ":Total: ww %zu ch %zu cl %zu co %zu db %zu ms %zu mb %zu", totww, totch, totcl, com, dbufs_allocated, msg_allocated, msgbuf_allocated); }
/** Read a 'packet' of data from a connection and process it. Read in * 8k chunks to give a better performance rating (for server * connections). Do some tricky stuff for client connections to make * sure they don't do any flooding >:-) -avalon * @param cptr Client from which to read data. * @param socket_ready If non-zero, more data can be read from the client's socket. * @return Positive number on success, zero on connection-fatal failure, negative * if user is killed. */ static int read_packet(struct Client *cptr, int socket_ready) { unsigned int dolen = 0; unsigned int length = 0; if (socket_ready && !(IsUser(cptr) && DBufLength(&(cli_recvQ(cptr))) > feature_uint(FEAT_CLIENT_FLOOD))) { #if defined(USE_SSL) switch (client_recv(cptr, readbuf, sizeof(readbuf), &length)) { #else switch (os_recv_nonb(cli_fd(cptr), readbuf, sizeof(readbuf), &length)) { #endif case IO_SUCCESS: if (length) { cli_lasttime(cptr) = CurrentTime; ClearPingSent(cptr); ClrFlag(cptr, FLAG_NONL); if (cli_lasttime(cptr) > cli_since(cptr)) cli_since(cptr) = cli_lasttime(cptr); } break; case IO_BLOCKED: break; case IO_FAILURE: cli_error(cptr) = errno; /* SetFlag(cptr, FLAG_DEADSOCKET); */ return 0; } } /* * For server connections, we process as many as we can without * worrying about the time of day or anything :) */ if (length > 0 && IsServer(cptr)) return server_dopacket(cptr, readbuf, length); else if (length > 0 && (IsHandshake(cptr) || IsConnecting(cptr))) return connect_dopacket(cptr, readbuf, length); else { /* * Before we even think of parsing what we just read, stick * it on the end of the receive queue and do it when its * turn comes around. */ if (length > 0 && dbuf_put(cptr, &(cli_recvQ(cptr)), readbuf, length) == 0) return exit_client(cptr, cptr, &me, "dbuf_put fail"); if ((DBufLength(&(cli_recvQ(cptr))) > feature_uint(FEAT_CLIENT_FLOOD)) && !IsChannelService(cptr)) return exit_client(cptr, cptr, &me, "Excess Flood"); while (DBufLength(&(cli_recvQ(cptr))) && !NoNewLine(cptr) && (IsTrusted(cptr) || IsChannelService(cptr) || cli_since(cptr) - CurrentTime < 10)) { dolen = dbuf_getmsg(&(cli_recvQ(cptr)), cli_buffer(cptr), BUFSIZE); /* * Devious looking...whats it do ? well..if a client * sends a *long* message without any CR or LF, then * dbuf_getmsg fails and we pull it out using this * loop which just gets the next 512 bytes and then * deletes the rest of the buffer contents. * -avalon */ if (dolen == 0) { if (DBufLength(&(cli_recvQ(cptr))) < 510) SetFlag(cptr, FLAG_NONL); else { /* More than 512 bytes in the line - drop the input and yell * at the client. */ DBufClear(&(cli_recvQ(cptr))); send_reply(cptr, ERR_INPUTTOOLONG); } } else if (client_dopacket(cptr, dolen) == CPTR_KILLED) return CPTR_KILLED; /* * If it has become registered as a Server * then skip the per-message parsing below. */ if (IsHandshake(cptr) || IsServer(cptr)) { while (-1) { dolen = dbuf_get(&(cli_recvQ(cptr)), readbuf, sizeof(readbuf)); if (dolen <= 0) return 1; else if (dolen == 0) { if (DBufLength(&(cli_recvQ(cptr))) < 510) SetFlag(cptr, FLAG_NONL); else { DBufClear(&(cli_recvQ(cptr))); /* send_reply(cptr, ERR_INPUTTOOLONG); */ } } else if ((IsServer(cptr) && server_dopacket(cptr, readbuf, dolen) == CPTR_KILLED) || (!IsServer(cptr) && connect_dopacket(cptr, readbuf, dolen) == CPTR_KILLED)) return CPTR_KILLED; } } } /* If there's still data to process, wait 2 seconds first */ if (DBufLength(&(cli_recvQ(cptr))) && !NoNewLine(cptr) && !t_onqueue(&(cli_proc(cptr)))) { Debug((DEBUG_LIST, "Adding client process timer for %C", cptr)); cli_freeflag(cptr) |= FREEFLAG_TIMER; timer_add(&(cli_proc(cptr)), client_timer_callback, cli_connect(cptr), TT_RELATIVE, 2); } } return 1; } /** Start a connection to another server. * @param aconf Connect block data for target server. * @param by Client who requested the connection (if any). * @return Non-zero on success; zero on failure. */ int connect_server(struct ConfItem* aconf, struct Client* by) { struct Client* cptr = 0; assert(0 != aconf); if (aconf->dns_pending) { sendto_opmask(0, SNO_OLDSNO, "Server %s connect DNS pending", aconf->name); return 0; } Debug((DEBUG_NOTICE, "Connect to %s[@%s]", aconf->name, ircd_ntoa(&aconf->address.addr))); if ((cptr = FindClient(aconf->name))) { if (IsServer(cptr) || IsMe(cptr)) { sendto_opmask(0, SNO_OLDSNO, "Server %s already present from %s", aconf->name, cli_name(cli_from(cptr))); if (by && IsUser(by) && !MyUser(by)) { sendcmdto_one(&me, CMD_NOTICE, by, "%C :Server %s already present " "from %s", by, aconf->name, cli_name(cli_from(cptr))); } return 0; } else if (IsHandshake(cptr) || IsConnecting(cptr)) { if (by && IsUser(by)) { sendcmdto_one(&me, CMD_NOTICE, by, "%C :Connection to %s already in " "progress", by, cli_name(cptr)); } return 0; } } /* * If we don't know the IP# for this host and it is a hostname and * not a ip# string, then try and find the appropriate host record. */ if (!irc_in_addr_valid(&aconf->address.addr) && !ircd_aton(&aconf->address.addr, aconf->host)) { char buf[HOSTLEN + 1]; host_from_uh(buf, aconf->host, HOSTLEN); gethost_byname(buf, connect_dns_callback, aconf); aconf->dns_pending = 1; return 0; } cptr = make_client(NULL, STAT_UNKNOWN_SERVER); /* * Copy these in so we have something for error detection. */ ircd_strncpy(cli_name(cptr), aconf->name, HOSTLEN); ircd_strncpy(cli_sockhost(cptr), aconf->host, HOSTLEN); /* * Attach config entries to client here rather than in * completed_connection. This to avoid null pointer references */ attach_confs_byhost(cptr, aconf->host, CONF_SERVER); if (!find_conf_byhost(cli_confs(cptr), aconf->host, CONF_SERVER)) { sendto_opmask(0, SNO_OLDSNO, "Host %s is not enabled for " "connecting: no Connect block", aconf->name); if (by && IsUser(by) && !MyUser(by)) { sendcmdto_one(&me, CMD_NOTICE, by, "%C :Connect to host %s failed: no " "Connect block", by, aconf->name); } det_confs_butmask(cptr, 0); free_client(cptr); return 0; } /* * attempt to connect to the server in the conf line */ if (!connect_inet(aconf, cptr)) { if (by && IsUser(by) && !MyUser(by)) { sendcmdto_one(&me, CMD_NOTICE, by, "%C :Couldn't connect to %s", by, cli_name(cptr)); } det_confs_butmask(cptr, 0); free_client(cptr); return 0; } /* * NOTE: if we're here we have a valid C:Line and the client should * have started the connection and stored the remote address/port and * ip address name in itself * * The socket has been connected or connect is in progress. */ make_server(cptr); if (by && IsUser(by)) { ircd_snprintf(0, cli_serv(cptr)->by, sizeof(cli_serv(cptr)->by), "%s%s", NumNick(by)); assert(0 == cli_serv(cptr)->user); cli_serv(cptr)->user = cli_user(by); cli_user(by)->refcnt++; } else { *(cli_serv(cptr))->by = '\0'; /* strcpy(cptr->serv->by, "Auto"); */ } cli_serv(cptr)->up = &me; SetConnecting(cptr); if (cli_fd(cptr) > HighestFd) HighestFd = cli_fd(cptr); LocalClientArray[cli_fd(cptr)] = cptr; Count_newunknown(UserStats); /* Actually we lie, the connect hasn't succeeded yet, but we have a valid * cptr, so we register it now. * Maybe these two calls should be merged. */ add_client_to_list(cptr); hAddClient(cptr); /* nextping = CurrentTime; */ return (s_state(&cli_socket(cptr)) == SS_CONNECTED) ? completed_connection(cptr) : 1; } /** Find the real hostname for the host running the server (or one which * matches the server's name) and its primary IP#. Hostname is stored * in the client structure passed as a pointer. */ void init_server_identity(void) { const struct LocalConf* conf = conf_get_local(); assert(0 != conf); ircd_strncpy(cli_name(&me), conf->name, HOSTLEN); SetYXXServerName(&me, conf->numeric); } /** Process events on a client socket. * @param ev Socket event structure that has a struct Connection as * its associated data. */ static void client_sock_callback(struct Event* ev) { struct Client* cptr; struct Connection* con; char *fmt = "%s"; char *fallback = 0; assert(0 != ev_socket(ev)); assert(0 != s_data(ev_socket(ev))); con = (struct Connection*) s_data(ev_socket(ev)); assert(0 != con_client(con) || ev_type(ev) == ET_DESTROY); cptr = con_client(con); assert(0 == cptr || con == cli_connect(cptr)); switch (ev_type(ev)) { case ET_DESTROY: con_freeflag(con) &= ~FREEFLAG_SOCKET; if (!con_freeflag(con) && !cptr) free_connection(con); #if defined(USE_SSL) ssl_free(ev_socket(ev)); #endif break; case ET_CONNECT: /* socket connection completed */ if (!completed_connection(cptr) || IsDead(cptr)) fallback = cli_info(cptr); break; case ET_ERROR: /* an error occurred */ fallback = cli_info(cptr); cli_error(cptr) = ev_data(ev); /* If the OS told us we have a bad file descriptor, we should * record that for future reference. */ if (cli_error(cptr) == EBADF) cli_fd(cptr) = -1; if (s_state(&(con_socket(con))) == SS_CONNECTING) { completed_connection(cptr); /* for some reason, the os_get_sockerr() in completed_connection() * can return 0 even when ev_data(ev) indicates a real error, so * re-assign the client error here. */ cli_error(cptr) = ev_data(ev); break; } /*FALLTHROUGH*/ case ET_EOF: /* end of file on socket */ Debug((DEBUG_ERROR, "READ ERROR: fd = %d %d", cli_fd(cptr), cli_error(cptr))); SetFlag(cptr, FLAG_DEADSOCKET); if ((IsServer(cptr) || IsHandshake(cptr)) && cli_error(cptr) == 0) { exit_client_msg(cptr, cptr, &me, "Server %s closed the connection (%s)", cli_name(cptr), cli_serv(cptr)->last_error_msg); return; } else { fmt = "Read error: %s"; fallback = "EOF from client"; } break; case ET_WRITE: /* socket is writable */ ClrFlag(cptr, FLAG_BLOCKED); if (cli_listing(cptr) && MsgQLength(&(cli_sendQ(cptr))) < 2048) list_next_channels(cptr); Debug((DEBUG_SEND, "Sending queued data to %C", cptr)); send_queued(cptr); break; case ET_READ: /* socket is readable */ if (!IsDead(cptr)) { Debug((DEBUG_DEBUG, "Reading data from %C", cptr)); if (read_packet(cptr, 1) == 0) /* error while reading packet */ fallback = "EOF from client"; } break; default: assert(0 && "Unrecognized socket event in client_sock_callback()"); break; } assert(0 == cptr || 0 == cli_connect(cptr) || con == cli_connect(cptr)); if (fallback) { const char* msg = (cli_error(cptr)) ? strerror(cli_error(cptr)) : fallback; if (!msg) msg = "Unknown error"; exit_client_msg(cptr, cptr, &me, fmt, msg); } } /** Process a timer on client socket. * @param ev Timer event that has a struct Connection as its * associated data. */ static void client_timer_callback(struct Event* ev) { struct Client* cptr; struct Connection* con; assert(0 != ev_timer(ev)); assert(0 != t_data(ev_timer(ev))); assert(ET_DESTROY == ev_type(ev) || ET_EXPIRE == ev_type(ev)); con = (struct Connection*) t_data(ev_timer(ev)); assert(0 != con_client(con) || ev_type(ev) == ET_DESTROY); cptr = con_client(con); assert(0 == cptr || con == cli_connect(cptr)); if (ev_type(ev)== ET_DESTROY) { con_freeflag(con) &= ~FREEFLAG_TIMER; /* timer has expired... */ if (!con_freeflag(con) && !cptr) free_connection(con); /* client is being destroyed */ } else { Debug((DEBUG_LIST, "Client process timer for %C expired; processing", cptr)); read_packet(cptr, 0); /* read_packet will re-add timer if needed */ } assert(0 == cptr || 0 == cli_connect(cptr) || con == cli_connect(cptr)); }