2 * IRC - Internet Relay Chat, ircd/s_misc.c (formerly ircd/date.c)
3 * Copyright (C) 1990 Jarkko Oikarinen and
4 * University of Oulu, Computing Center
6 * See file AUTHORS in IRC package for additional names of
9 * This program is free software; you can redistribute it and/or modify
10 * it under the terms of the GNU General Public License as published by
11 * the Free Software Foundation; either version 1, or (at your option)
14 * This program is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 * GNU General Public License for more details.
19 * You should have received a copy of the GNU General Public License
20 * along with this program; if not, write to the Free Software
21 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
57 #include "sprintf_irc.h"
58 #include "querycmds.h"
65 static void exit_one_client(aClient *, char *);
67 static char *months[] = {
68 "January", "February", "March", "April",
69 "May", "June", "July", "August",
70 "September", "October", "November", "December"
73 static char *weekdays[] = {
74 "Sunday", "Monday", "Tuesday", "Wednesday",
75 "Thursday", "Friday", "Saturday"
81 struct stats ircst, *ircstp = &ircst;
83 char *date(time_t clock)
85 static char buf[80], plus;
86 Reg1 struct tm *lt, *gm;
93 memcpy(&gmbuf, gm, sizeof(gmbuf));
95 lt = localtime(&clock);
97 minswest = (gm->tm_hour - lt->tm_hour) * 60 + (gm->tm_min - lt->tm_min);
98 if (lt->tm_yday != gm->tm_yday)
100 if ((lt->tm_yday > gm->tm_yday && lt->tm_year == gm->tm_year) ||
101 (lt->tm_yday < gm->tm_yday && lt->tm_year != gm->tm_year))
107 plus = (minswest > 0) ? '-' : '+';
109 minswest = -minswest;
111 sprintf(buf, "%s %s %d %d -- %02d:%02d %c%02d:%02d",
112 weekdays[lt->tm_wday], months[lt->tm_mon], lt->tm_mday,
113 1900 + lt->tm_year, lt->tm_hour, lt->tm_min,
114 plus, minswest / 60, minswest % 60);
122 * This is like standard ctime()-function, but it zaps away
123 * the newline from the end of that string. Also, it takes
124 * the time value as parameter, instead of pointer to it.
125 * Note that it is necessary to copy the string to alternate
126 * buffer (who knows how ctime() implements it, maybe it statically
127 * has newline there and never 'refreshes' it -- zapping that
128 * might break things in other places...)
130 char *myctime(time_t value)
135 strcpy(buf, ctime(&value));
136 if ((p = strchr(buf, '\n')) != NULL)
144 * Return the name of the client for various tracking and
145 * admin purposes. The main purpose of this function is to
146 * return the "socket host" name of the client, if that
147 * differs from the advertised name (other than case).
148 * But, this can be used to any client structure.
151 * "name[user@ip#.port]" if 'showip' is true;
152 * "name[sockethost]", if name and sockhost are different and
153 * showip is false; else
157 * Watch out the allocation of "nbuf", if either sptr->name
158 * or sptr->sockhost gets changed into pointers instead of
159 * directly allocated within the structure...
162 * Function return either a pointer to the structure (sptr) or
163 * to internal buffer (nbuf). *NEVER* use the returned pointer
164 * to modify what it points!!!
166 char *get_client_name(aClient *sptr, int showip)
168 static char nbuf[HOSTLEN * 2 + USERLEN + 5];
172 if (IsUnixSocket(sptr))
175 sprintf_irc(nbuf, "%s[%s]", sptr->name, sptr->sockhost);
177 sprintf_irc(nbuf, "%s[%s]", sptr->name, me.sockhost);
182 sprintf_irc(nbuf, "%s[%s@%s]", sptr->name,
183 (!(sptr->flags & FLAGS_GOTID)) ? "" :
184 sptr->username, inetntoa(sptr->ip));
187 if (strCasediff(sptr->name, sptr->sockhost))
188 sprintf_irc(nbuf, "%s[%s]", sptr->name, sptr->sockhost);
198 char *get_client_host(aClient *cptr)
200 static char nbuf[HOSTLEN * 2 + USERLEN + 5];
202 if (!MyConnect(cptr))
205 return get_client_name(cptr, FALSE);
206 if (IsUnixSocket(cptr))
207 sprintf_irc(nbuf, "%s[%s]", cptr->name, me.name);
209 sprintf(nbuf, "%s[%-.*s@%-.*s]", cptr->name, USERLEN,
210 (!(cptr->flags & FLAGS_GOTID)) ? "" : cptr->username,
211 HOSTLEN, cptr->hostp->h_name);
216 * Form sockhost such that if the host is of form user@host, only the host
219 void get_sockhost(aClient *cptr, char *host)
222 if ((s = strchr(host, '@')))
226 strncpy(cptr->sockhost, s, sizeof(cptr->sockhost) - 1);
230 * Return wildcard name of my server name according to given config entry
233 char *my_name_for_link(char *name, aConfItem *aconf)
235 static char namebuf[HOSTLEN];
236 register int count = aconf->port;
237 register char *start = name;
239 if (count <= 0 || count > 5)
242 while (count-- && name)
245 name = strchr(name, '.');
251 strncpy(&namebuf[1], name, HOSTLEN - 1);
252 namebuf[HOSTLEN - 1] = '\0';
258 * exit_downlinks - added by Run 25-9-94
260 * Removes all clients and downlinks (+clients) of any server
261 * QUITs are generated and sent to local users.
263 * cptr : server that must have all dependents removed
264 * sptr : source who thought that this was a good idea
265 * comment : comment sent as sign off message to local clients
267 static void exit_downlinks(aClient *cptr, aClient *sptr, char *comment)
275 /* Run over all its downlinks */
276 for (lp = cptr->serv->down; lp; lp = next)
279 acptr = lp->value.cptr;
280 /* Remove the downlinks and client of the downlink */
281 exit_downlinks(acptr, sptr, comment);
282 /* Remove the downlink itself */
283 exit_one_client(acptr, me.name);
285 /* Remove all clients of this server */
286 acptrp = cptr->serv->client_list;
287 for (i = 0; i <= cptr->serv->nn_mask; ++acptrp, ++i)
289 exit_one_client(*acptrp, comment);
293 * exit_client, rewritten 25-9-94 by Run
295 * This function exits a client of *any* type (user, server, etc)
296 * from this server. Also, this generates all necessary prototol
297 * messages that this exit may cause.
299 * This function implicitly exits all other clients depending on
302 * For convenience, this function returns a suitable value for
303 * m_funtion return value:
305 * CPTR_KILLED if (cptr == bcptr)
306 * 0 if (cptr != bcptr)
308 * This function can be called in two ways:
309 * 1) From before or in parse(), exitting the 'cptr', in which case it was
310 * invoked as exit_client(cptr, cptr, &me,...), causing it to always
311 * return CPTR_KILLED.
312 * 2) Via parse from a m_function call, in which case it was invoked as
313 * exit_client(cptr, acptr, sptr, ...). Here 'sptr' is known; the client
314 * that generated the message in a way that we can assume he already
315 * did remove acptr from memory himself (or in other cases we don't mind
316 * because he will be delinked.) Or invoked as:
317 * exit_client(cptr, acptr/sptr, &me, ...) when WE decide this one should
319 * In general: No generated SQUIT or QUIT should be sent to source link
320 * sptr->from. And CPTR_KILLED should be returned if cptr got removed (too).
324 int exit_client(aClient *cptr, /* Connection being handled by
325 read_message right now */
326 aClient *bcptr, /* Client being killed */
327 aClient *sptr, /* The client that made the decision
328 to remove this one, never NULL */
329 char *comment) /* Reason for the exit */
336 char comment1[HOSTLEN + HOSTLEN + 2];
338 if (MyConnect(bcptr))
340 bcptr->flags |= FLAGS_CLOSING;
341 #ifdef ALLOW_SNO_CONNEXIT
342 #ifdef SNO_CONNEXIT_IP
346 ":%s NOTICE * :*** Notice -- Client exiting: %s (%s@%s) [%s] [%s]",
347 me.name, bcptr->name, bcptr->user->username, bcptr->user->host,
348 comment, inetntoa(bcptr->ip));
349 sendbufto_op_mask(SNO_CONNEXIT);
351 #else /* SNO_CONNEXIT_IP */
355 ":%s NOTICE * :*** Notice -- Client exiting: %s (%s@%s) [%s]",
356 me.name, bcptr->name, bcptr->user->username, bcptr->user->host,
358 sendbufto_op_mask(SNO_CONNEXIT);
360 #endif /* SNO_CONNEXIT_IP */
361 #endif /* ALLOW_SNO_CONNEXIT */
364 on_for = now - bcptr->firsttime;
365 #if defined(USE_SYSLOG) && defined(SYSLOG_USERS)
367 syslog(LOG_NOTICE, "%s (%3d:%02d:%02d): %s@%s (%s)\n",
368 myctime(bcptr->firsttime), on_for / 3600, (on_for % 3600) / 60,
369 on_for % 60, bcptr->user->username, bcptr->sockhost, bcptr->name);
372 write_log(FNAME_USERLOG,
373 "%s (%3d:%02d:%02d): %s@%s [%s]\n",
374 myctime(bcptr->firsttime),
375 on_for / 3600, (on_for % 3600) / 60,
377 bcptr->user->username, bcptr->user->host, bcptr->username);
380 if (bcptr != sptr->from /* The source knows already */
381 && IsClient(bcptr)) /* Not a Ping struct or Log file */
383 if (IsServer(bcptr) || IsHandshake(bcptr))
384 sendto_one(bcptr, ":%s SQUIT %s 0 :%s", sptr->name, me.name, comment);
385 else if (!IsConnecting(bcptr))
386 sendto_one(bcptr, "ERROR :Closing Link: %s by %s (%s)",
387 get_client_name(bcptr, FALSE), sptr->name, comment);
388 if ((IsServer(bcptr) || IsHandshake(bcptr) || IsConnecting(bcptr)) &&
389 (sptr == &me || (IsServer(sptr) &&
390 (strncmp(comment, "Leaf-only link", 14) ||
391 strncmp(comment, "Non-Hub link", 12)))))
393 if (bcptr->serv->user && *bcptr->serv->by &&
394 (acptr = findNUser(bcptr->serv->by)) &&
395 acptr->user == bcptr->serv->user)
397 if (MyUser(acptr) || Protocol(acptr->from) < 10)
399 ":%s NOTICE %s :Link with %s cancelled: %s",
400 me.name, acptr->name, bcptr->name, comment);
403 "%s NOTICE %s%s :Link with %s cancelled: %s",
404 NumServ(&me), NumNick(acptr), bcptr->name, comment);
409 sendto_lops_butone(acptr, "Link with %s cancelled: %s",
410 bcptr->name, comment);
414 * Close the Client connection first.
416 close_connection(bcptr);
421 strcpy(comment1, bcptr->serv->up->name);
422 strcat(comment1, " ");
423 strcat(comment1, bcptr->name);
425 sendto_lops_butone(sptr, "%s SQUIT by %s [%s]:",
426 (sptr->user->server == bcptr ||
427 sptr->user->server == bcptr->serv->up) ? "Local" : "Remote",
428 get_client_name(sptr, TRUE), sptr->user->server->name);
429 else if (sptr != &me && bcptr->serv->up != sptr)
430 sendto_ops("Received SQUIT %s from %s :", bcptr->name,
431 IsServer(sptr) ? sptr->name : get_client_name(sptr, TRUE));
432 sendto_op_mask(SNO_NETWORK, "Net break: %s (%s)", comment1, comment);
436 * First generate the needed protocol for the other server links
439 for (dlp = me.serv->down; dlp; dlp = dlp->next)
440 if (dlp->value.cptr != sptr->from && dlp->value.cptr != bcptr)
443 sendto_one(dlp->value.cptr, ":%s SQUIT %s " TIME_T_FMT " :%s",
444 sptr->name, bcptr->name, bcptr->serv->timestamp, comment);
445 else if (IsUser(bcptr) && (bcptr->flags & FLAGS_KILLED) == 0)
446 sendto_one(dlp->value.cptr, ":%s QUIT :%s", bcptr->name, comment);
449 /* Then remove the client structures */
451 exit_downlinks(bcptr, sptr, comment1);
452 exit_one_client(bcptr, comment);
455 * cptr can only have been killed if it was cptr itself that got killed here,
456 * because cptr can never have been a dependant of bcptr --Run
458 return (cptr == bcptr) ? CPTR_KILLED : 0;
462 * Exit client with formatted message, added 25-9-94 by Run
464 int vexit_client_msg(aClient *cptr, aClient *bcptr, aClient *sptr,
465 char *pattern, va_list vl)
468 vsprintf_irc(msgbuf, pattern, vl);
469 return exit_client(cptr, bcptr, sptr, msgbuf);
472 int exit_client_msg(aClient *cptr, aClient *bcptr,
473 aClient *sptr, char *pattern, ...)
478 va_start(vl, pattern);
479 vsprintf_irc(msgbuf, pattern, vl);
482 return exit_client(cptr, bcptr, sptr, msgbuf);
486 * Exit one client, local or remote. Assuming for local client that
487 * all dependants already have been removed, and socket is closed.
489 * Rewritten by Run - 24 sept 94
491 * bcptr : client being (s)quitted
492 * sptr : The source (prefix) of the QUIT or SQUIT
496 static void exit_one_client(aClient *bcptr, char *comment)
500 if (bcptr->serv && bcptr->serv->client_list) /* Was SetServerYXX called ? */
501 ClearServerYXX(bcptr); /* Removes server from server_list[] */
504 /* Stop a running /LIST clean */
505 if (MyUser(bcptr) && bcptr->listing)
507 bcptr->listing->chptr->mode.mode &= ~MODE_LISTED;
508 RunFree(bcptr->listing);
509 bcptr->listing = NULL;
512 if (AskedPing(bcptr))
513 cancel_ping(bcptr, NULL);
515 * If a person is on a channel, send a QUIT notice
516 * to every client (person) on the same channel (so
517 * that the client can show the "**signoff" message).
518 * (Note: The notice is to the local clients *only*)
520 sendto_common_channels(bcptr, ":%s QUIT :%s", bcptr->name, comment);
522 while ((lp = bcptr->user->channel))
523 remove_user_from_channel(bcptr, lp->value.chptr);
525 /* Clean up invitefield */
526 while ((lp = bcptr->user->invited))
527 del_invite(bcptr, lp->value.chptr);
529 /* Clean up silencefield */
530 while ((lp = bcptr->user->silence))
531 del_silence(bcptr, lp->value.cp);
533 if (IsInvisible(bcptr))
537 if (MyConnect(bcptr))
538 Count_clientdisconnects(bcptr, nrof);
540 Count_remoteclientquits(nrof);
542 else if (IsServer(bcptr))
544 /* Remove downlink list node of uplink */
545 remove_dlink(&bcptr->serv->up->serv->down, bcptr->serv->updown);
547 if (MyConnect(bcptr))
548 Count_serverdisconnects(nrof);
550 Count_remoteserverquits(nrof);
552 else if (IsPing(bcptr)) /* Apperently, we are closing ALL links */
554 del_queries((char *)bcptr);
558 else if (IsMe(bcptr))
560 sendto_ops("ERROR: tried to exit me! : %s", comment);
561 return; /* ...must *never* exit self! */
563 else if (IsUnknown(bcptr) || IsConnecting(bcptr) || IsHandshake(bcptr))
564 Count_unknowndisconnects(nrof);
566 /* Update IPregistry */
567 if (IsIPChecked(bcptr))
568 IPcheck_disconnect(bcptr);
571 * Remove from serv->client_list
572 * NOTE: user is *always* NULL if this is a server
576 assert(!IsServer(bcptr));
577 /* bcptr->user->server->serv->client_list[IndexYXX(bcptr)] = NULL; */
578 RemoveYXXClient(bcptr->user->server, bcptr->yxx);
581 /* Remove bcptr from the client list */
583 if (hRemClient(bcptr) != 0)
584 Debug((DEBUG_ERROR, "%p !in tab %s[%s] %p %p %p %d %d %p",
585 bcptr, bcptr->name, bcptr->from ? bcptr->from->sockhost : "??host",
586 bcptr->from, bcptr->next, bcptr->prev, bcptr->fd,
587 bcptr->status, bcptr->user));
591 remove_client_from_list(bcptr);
600 if (!(bootopt & BOOT_AUTODIE))
602 for (j = i = 0; i <= highest_fd; i++)
603 if (!(acptr = loc_clients[i]))
605 else if (IsUser(acptr))
610 syslog(LOG_WARNING, "ircd exiting: autodie");
619 memset(&ircst, 0, sizeof(ircst));
622 void tstats(aClient *cptr, char *name)
626 Reg3 struct stats *sp;
630 memcpy(sp, ircstp, sizeof(*sp));
631 for (i = 0; i < MAXCONNECTIONS; i++)
633 if (!(acptr = loc_clients[i]))
637 sp->is_sbs += acptr->sendB;
638 sp->is_sbr += acptr->receiveB;
639 sp->is_sks += acptr->sendK;
640 sp->is_skr += acptr->receiveK;
641 sp->is_sti += now - acptr->firsttime;
643 if (sp->is_sbs > 1023)
645 sp->is_sks += (sp->is_sbs >> 10);
648 if (sp->is_sbr > 1023)
650 sp->is_skr += (sp->is_sbr >> 10);
654 else if (IsUser(acptr))
656 sp->is_cbs += acptr->sendB;
657 sp->is_cbr += acptr->receiveB;
658 sp->is_cks += acptr->sendK;
659 sp->is_ckr += acptr->receiveK;
660 sp->is_cti += now - acptr->firsttime;
662 if (sp->is_cbs > 1023)
664 sp->is_cks += (sp->is_cbs >> 10);
667 if (sp->is_cbr > 1023)
669 sp->is_ckr += (sp->is_cbr >> 10);
673 else if (IsUnknown(acptr))
677 sendto_one(cptr, ":%s %d %s :accepts %u refused %u",
678 me.name, RPL_STATSDEBUG, name, sp->is_ac, sp->is_ref);
679 sendto_one(cptr, ":%s %d %s :unknown commands %u prefixes %u",
680 me.name, RPL_STATSDEBUG, name, sp->is_unco, sp->is_unpf);
681 sendto_one(cptr, ":%s %d %s :nick collisions %u unknown closes %u",
682 me.name, RPL_STATSDEBUG, name, sp->is_kill, sp->is_ni);
683 sendto_one(cptr, ":%s %d %s :wrong direction %u empty %u",
684 me.name, RPL_STATSDEBUG, name, sp->is_wrdi, sp->is_empt);
685 sendto_one(cptr, ":%s %d %s :numerics seen %u mode fakes %u",
686 me.name, RPL_STATSDEBUG, name, sp->is_num, sp->is_fake);
687 sendto_one(cptr, ":%s %d %s :auth successes %u fails %u",
688 me.name, RPL_STATSDEBUG, name, sp->is_asuc, sp->is_abad);
689 sendto_one(cptr, ":%s %d %s :local connections %u udp packets %u",
690 me.name, RPL_STATSDEBUG, name, sp->is_loc, sp->is_udp);
691 sendto_one(cptr, ":%s %d %s :Client Server", me.name, RPL_STATSDEBUG, name);
692 sendto_one(cptr, ":%s %d %s :connected %u %u",
693 me.name, RPL_STATSDEBUG, name, sp->is_cl, sp->is_sv);
694 sendto_one(cptr, ":%s %d %s :bytes sent %u.%uK %u.%uK",
695 me.name, RPL_STATSDEBUG, name,
696 sp->is_cks, sp->is_cbs, sp->is_sks, sp->is_sbs);
697 sendto_one(cptr, ":%s %d %s :bytes recv %u.%uK %u.%uK",
698 me.name, RPL_STATSDEBUG, name,
699 sp->is_ckr, sp->is_cbr, sp->is_skr, sp->is_sbr);
700 sendto_one(cptr, ":%s %d %s :time connected " TIME_T_FMT " " TIME_T_FMT,
701 me.name, RPL_STATSDEBUG, name, sp->is_cti, sp->is_sti);