Author: Kev <klmitch@mit.edu>
[ircu2.10.12-pk.git] / ircd / m_trace.c
1 /*
2  * IRC - Internet Relay Chat, ircd/m_trace.c
3  * Copyright (C) 1990 Jarkko Oikarinen and
4  *                    University of Oulu, Computing Center
5  *
6  * See file AUTHORS in IRC package for additional names of
7  * the programmers.
8  *
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)
12  * any later version.
13  *
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.
18  *
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.
22  *
23  * $Id$
24  */
25
26 /*
27  * m_functions execute protocol messages on this server:
28  *
29  *    cptr    is always NON-NULL, pointing to a *LOCAL* client
30  *            structure (with an open socket connected!). This
31  *            identifies the physical socket where the message
32  *            originated (or which caused the m_function to be
33  *            executed--some m_functions may call others...).
34  *
35  *    sptr    is the source of the message, defined by the
36  *            prefix part of the message if present. If not
37  *            or prefix not found, then sptr==cptr.
38  *
39  *            (!IsServer(cptr)) => (cptr == sptr), because
40  *            prefixes are taken *only* from servers...
41  *
42  *            (IsServer(cptr))
43  *                    (sptr == cptr) => the message didn't
44  *                    have the prefix.
45  *
46  *                    (sptr != cptr && IsServer(sptr) means
47  *                    the prefix specified servername. (?)
48  *
49  *                    (sptr != cptr && !IsServer(sptr) means
50  *                    that message originated from a remote
51  *                    user (not local).
52  *
53  *            combining
54  *
55  *            (!IsServer(sptr)) means that, sptr can safely
56  *            taken as defining the target structure of the
57  *            message in this server.
58  *
59  *    *Always* true (if 'parse' and others are working correct):
60  *
61  *    1)      sptr->from == cptr  (note: cptr->from == cptr)
62  *
63  *    2)      MyConnect(sptr) <=> sptr == cptr (e.g. sptr
64  *            *cannot* be a local connection, unless it's
65  *            actually cptr!). [MyConnect(x) should probably
66  *            be defined as (x == x->from) --msa ]
67  *
68  *    parc    number of variable parameter strings (if zero,
69  *            parv is allowed to be NULL)
70  *
71  *    parv    a NULL terminated list of parameter pointers,
72  *
73  *                    parv[0], sender (prefix string), if not present
74  *                            this points to an empty string.
75  *                    parv[1]...parv[parc-1]
76  *                            pointers to additional parameters
77  *                    parv[parc] == NULL, *always*
78  *
79  *            note:   it is guaranteed that parv[0]..parv[parc-1] are all
80  *                    non-NULL pointers.
81  */
82 #if 0
83 /*
84  * No need to include handlers.h here the signatures must match
85  * and we don't need to force a rebuild of all the handlers everytime
86  * we add a new one to the list. --Bleep
87  */
88 #include "handlers.h"
89 #endif /* 0 */
90 #include "class.h"
91 #include "client.h"
92 #include "hash.h"
93 #include "ircd.h"
94 #include "ircd_reply.h"
95 #include "ircd_string.h"
96 #include "match.h"
97 #include "msg.h"
98 #include "numeric.h"
99 #include "numnicks.h"
100 #include "s_bsd.h"
101 #include "s_conf.h"
102 #include "s_user.h"
103 #include "send.h"
104 #include "version.h"
105
106 #include <assert.h>
107 #include <string.h>
108
109 /*
110  * m_trace - generic message handler
111  *
112  * parv[0] = sender prefix
113  * parv[1] = nick or servername
114  * parv[2] = 'target' servername
115  */
116 int m_trace(struct Client* cptr, struct Client* sptr, int parc, char* parv[])
117 {
118   int i;
119   struct Client *acptr;
120   const struct ConnectionClass* cl;
121   char* tname;
122   int doall;
123   int link_s[MAXCONNECTIONS];
124   int link_u[MAXCONNECTIONS];
125   int cnt = 0;
126   int wilds;
127   int dow;
128
129   if (parc < 2 || BadPtr(parv[1])) {
130     /* just "TRACE" without parameters. Must be from local client */
131     parc = 1;
132     acptr = &me;
133     tname = cli_name(&me);
134     i = HUNTED_ISME;
135   } else if (parc < 3 || BadPtr(parv[2])) {
136     /* No target specified. Make one before propagating. */
137     parc = 2;
138     tname = parv[1];
139     if ((acptr = find_match_server(parv[1])) ||
140         ((acptr = FindClient(parv[1])) && !MyUser(acptr))) {
141       if (IsUser(acptr))
142         parv[2] = cli_name(cli_user(acptr)->server);
143       else
144         parv[2] = cli_name(acptr);
145       parc = 3;
146       parv[3] = 0;
147       if ((i = hunt_server_cmd(sptr, CMD_TRACE, cptr, IsServer(acptr),
148                                "%s :%C", 2, parc, parv)) == HUNTED_NOSUCH)
149         return 0;
150     } else
151       i = HUNTED_ISME;
152   } else {
153     /* Got "TRACE <tname> :<target>" */
154     parc = 3;
155     if (MyUser(sptr) || Protocol(cptr) < 10)
156       acptr = find_match_server(parv[2]);
157     else
158       acptr = FindNServer(parv[2]);
159     if ((i = hunt_server_cmd(sptr, CMD_TRACE, cptr, 0, "%s :%C", 2, parc,
160                              parv)) == HUNTED_NOSUCH)
161       return 0;
162     tname = parv[1];
163   }
164
165   if (i == HUNTED_PASS) {
166     if (!acptr)
167       acptr = next_client(GlobalClientList, tname);
168     else
169       acptr = cli_from(acptr);
170     send_reply(sptr, RPL_TRACELINK,
171                version, debugmode, tname,
172                acptr ? cli_name(cli_from(acptr)) : "<No_match>");
173     return 0;
174   }
175
176   doall = (parv[1] && (parc > 1)) ? !match(tname, cli_name(&me)) : 1;
177   wilds = !parv[1] || strchr(tname, '*') || strchr(tname, '?');
178   dow = wilds || doall;
179
180   /* Don't give (long) remote listings to lusers */
181   if (dow && !MyConnect(sptr) && !IsAnOper(sptr))
182     return 0;
183
184   for (i = 0; i < MAXCONNECTIONS; i++)
185     link_s[i] = 0, link_u[i] = 0;
186
187   if (doall) {
188     for (acptr = GlobalClientList; acptr; acptr = cli_next(acptr)) {
189       if (IsUser(acptr))
190         link_u[cli_fd(cli_from(acptr))]++;
191       else if (IsServer(acptr))
192         link_s[cli_fd(cli_from(acptr))]++;
193     }
194   }
195
196   /* report all direct connections */
197
198   for (i = 0; i <= HighestFd; i++) {
199     unsigned int conClass;
200
201     if (!(acptr = LocalClientArray[i])) /* Local Connection? */
202       continue;
203     if (IsInvisible(acptr) && dow && !(MyConnect(sptr) && IsOper(sptr)) &&
204         !IsAnOper(acptr) && (acptr != sptr))
205       continue;
206     if (!doall && wilds && match(tname, cli_name(acptr)))
207       continue;
208     if (!dow && 0 != ircd_strcmp(tname, cli_name(acptr)))
209       continue;
210
211     conClass = get_client_class(acptr);
212
213     switch (cli_status(acptr)) {
214       case STAT_CONNECTING:
215         send_reply(sptr, RPL_TRACECONNECTING, conClass, cli_name(acptr));
216         cnt++;
217         break;
218       case STAT_HANDSHAKE:
219         send_reply(sptr, RPL_TRACEHANDSHAKE, conClass, cli_name(acptr));
220         cnt++;
221         break;
222       case STAT_ME:
223         break;
224       case STAT_UNKNOWN:
225       case STAT_UNKNOWN_USER:
226         send_reply(sptr, RPL_TRACEUNKNOWN, conClass,
227                    get_client_name(acptr, HIDE_IP));
228         cnt++;
229         break;
230       case STAT_UNKNOWN_SERVER:
231         send_reply(sptr, RPL_TRACEUNKNOWN, conClass, "Unknown Server");
232         cnt++;
233         break;
234       case STAT_USER:
235         /* Only opers see users if there is a wildcard
236            but anyone can see all the opers. */
237         if ((IsAnOper(sptr) && (MyUser(sptr) ||
238             !(dow && IsInvisible(acptr)))) || !dow || IsAnOper(acptr)) {
239           if (IsAnOper(acptr))
240             send_reply(sptr, RPL_TRACEOPERATOR, conClass,
241                        get_client_name(acptr, SHOW_IP),
242                        CurrentTime - cli_lasttime(acptr));
243           else
244             send_reply(sptr, RPL_TRACEUSER, conClass,
245                        get_client_name(acptr, SHOW_IP),
246                        CurrentTime - cli_lasttime(acptr));
247           cnt++;
248         }
249         break;
250         /*
251          * Connection is a server
252          *
253          * Serv <class> <nS> <nC> <name> <ConnBy> <last> <age>
254          *
255          * class        Class the server is in
256          * nS           Number of servers reached via this link
257          * nC           Number of clients reached via this link
258          * name         Name of the server linked
259          * ConnBy       Who established this link
260          * last         Seconds since we got something from this link
261          * age          Seconds this link has been alive
262          *
263          * Additional comments etc......        -Cym-<cym@acrux.net>
264          */
265
266       case STAT_SERVER:
267         if (cli_serv(acptr)->user)
268           send_reply(sptr, RPL_TRACESERVER, conClass, link_s[i],
269                      link_u[i], cli_name(acptr),
270                      (*(cli_serv(acptr))->by) ? cli_serv(acptr)->by : "*",
271                      cli_serv(acptr)->user->username, cli_serv(acptr)->user->host,
272                      CurrentTime - cli_lasttime(acptr),
273                      CurrentTime - cli_serv(acptr)->timestamp);
274         else
275           send_reply(sptr, RPL_TRACESERVER, conClass, link_s[i],
276                      link_u[i], cli_name(acptr),
277                      (*(cli_serv(acptr))->by) ?  cli_serv(acptr)->by : "*", "*",
278                      cli_name(&me), CurrentTime - cli_lasttime(acptr),
279                      CurrentTime - cli_serv(acptr)->timestamp);
280         cnt++;
281         break;
282       default:                  /* We actually shouldn't come here, -msa */
283         send_reply(sptr, RPL_TRACENEWTYPE, get_client_name(acptr, HIDE_IP));
284         cnt++;
285         break;
286     }
287   }
288   /*
289    * Add these lines to summarize the above which can get rather long
290    * and messy when done remotely - Avalon
291    */
292   if (!IsAnOper(sptr) || !cnt) {
293     if (!cnt)
294       /* let the user have some idea that its at the end of the trace */
295       send_reply(sptr, RPL_TRACESERVER, 0, link_s[cli_fd(&me)],
296                  link_u[cli_fd(&me)], "<No_match>", *(cli_serv(&me)->by) ?
297                  cli_serv(&me)->by : "*", "*", cli_name(&me), 0, 0);
298     return 0;
299   }
300   if (doall) {
301     for (cl = get_class_list(); cl; cl = cl->next) {
302       if (Links(cl) > 0)
303        send_reply(sptr, RPL_TRACECLASS, ConClass(cl), Links(cl));
304     }
305   }
306   return 0;
307 }
308
309 /*
310  * ms_trace - server message handler
311  *
312  * parv[0] = sender prefix
313  * parv[1] = nick or servername
314  * parv[2] = 'target' servername
315  */
316 int ms_trace(struct Client* cptr, struct Client* sptr, int parc, char* parv[])
317 {
318   int i;
319   struct Client *acptr;
320   const struct ConnectionClass* cl;
321   char *tname;
322   int doall;
323   int link_s[MAXCONNECTIONS];
324   int link_u[MAXCONNECTIONS];
325   int cnt = 0;
326   int wilds;
327   int dow;
328
329   if (parc < 2 || BadPtr(parv[1])) {
330     /* just "TRACE" without parameters. Must be from local client */
331     parc = 1;
332     acptr = &me;
333     tname = cli_name(&me);
334     i = HUNTED_ISME;
335   } else if (parc < 3 || BadPtr(parv[2])) {
336     /* No target specified. Make one before propagating. */
337     parc = 2;
338     tname = parv[1];
339     if ((acptr = find_match_server(parv[1])) ||
340         ((acptr = FindClient(parv[1])) && !MyUser(acptr))) {
341       if (IsUser(acptr))
342         parv[2] = cli_name(cli_user(acptr)->server);
343       else
344         parv[2] = cli_name(acptr);
345       parc = 3;
346       parv[3] = 0;
347
348       if ((i = hunt_server_cmd(sptr, CMD_TRACE, cptr, IsServer(acptr),
349                                "%s :%C", 2, parc, parv)) == HUNTED_NOSUCH)
350         return 0;
351     } else
352       i = HUNTED_ISME;
353   } else {
354     /* Got "TRACE <tname> :<target>" */
355     parc = 3;
356     if (MyUser(sptr) || Protocol(cptr) < 10)
357       acptr = find_match_server(parv[2]);
358     else
359       acptr = FindNServer(parv[2]);
360     if ((i = hunt_server_cmd(sptr, CMD_TRACE, cptr, 0, "%s :%C", 2, parc,
361                              parv)) == HUNTED_NOSUCH)
362       return 0;
363     tname = parv[1];
364   }
365
366   if (i == HUNTED_PASS) {
367     if (!acptr)
368       acptr = next_client(GlobalClientList, tname);
369     else
370       acptr = cli_from(acptr);
371     send_reply(sptr, RPL_TRACELINK,
372                version, debugmode, tname,
373                acptr ? cli_name(cli_from(acptr)) : "<No_match>");
374     return 0;
375   }
376
377   doall = (parv[1] && (parc > 1)) ? !match(tname, cli_name(&me)) : 1;
378   wilds = !parv[1] || strchr(tname, '*') || strchr(tname, '?');
379   dow = wilds || doall;
380
381   /* Don't give (long) remote listings to lusers */
382   if (dow && !MyConnect(sptr) && !IsAnOper(sptr))
383     return 0;
384
385   for (i = 0; i < MAXCONNECTIONS; i++)
386     link_s[i] = 0, link_u[i] = 0;
387
388   if (doall) {
389     for (acptr = GlobalClientList; acptr; acptr = cli_next(acptr)) {
390       if (IsUser(acptr))
391         link_u[cli_fd(cli_from(acptr))]++;
392       else if (IsServer(acptr))
393         link_s[cli_fd(cli_from(acptr))]++;
394     }
395   }
396
397   /* report all direct connections */
398
399   for (i = 0; i <= HighestFd; i++) {
400     unsigned int conClass;
401
402     if (!(acptr = LocalClientArray[i])) /* Local Connection? */
403       continue;
404     if (IsInvisible(acptr) && dow && !(MyConnect(sptr) && IsOper(sptr)) &&
405         !IsAnOper(acptr) && (acptr != sptr))
406       continue;
407     if (!doall && wilds && match(tname, cli_name(acptr)))
408       continue;
409     if (!dow && 0 != ircd_strcmp(tname, cli_name(acptr)))
410       continue;
411     conClass = get_client_class(acptr);
412
413     switch (cli_status(acptr)) {
414       case STAT_CONNECTING:
415         send_reply(sptr, RPL_TRACECONNECTING, conClass, cli_name(acptr));
416         cnt++;
417         break;
418       case STAT_HANDSHAKE:
419         send_reply(sptr, RPL_TRACEHANDSHAKE, conClass, cli_name(acptr));
420         cnt++;
421         break;
422       case STAT_ME:
423         break;
424       case STAT_UNKNOWN:
425       case STAT_UNKNOWN_USER:
426         send_reply(sptr, RPL_TRACEUNKNOWN, conClass,
427                    get_client_name(acptr, HIDE_IP));
428         cnt++;
429         break;
430       case STAT_UNKNOWN_SERVER:
431         send_reply(sptr, RPL_TRACEUNKNOWN, conClass, "Unknown Server");
432         cnt++;
433         break;
434       case STAT_USER:
435         /* Only opers see users if there is a wildcard
436            but anyone can see all the opers. */
437         if ((IsAnOper(sptr) && (MyUser(sptr) ||
438             !(dow && IsInvisible(acptr)))) || !dow || IsAnOper(acptr)) {
439           if (IsAnOper(acptr))
440             send_reply(sptr, RPL_TRACEOPERATOR, conClass,
441                        get_client_name(acptr, SHOW_IP),
442                        CurrentTime - cli_lasttime(acptr));
443           else
444             send_reply(sptr, RPL_TRACEUSER, conClass,
445                        get_client_name(acptr, SHOW_IP),
446                        CurrentTime - cli_lasttime(acptr));
447           cnt++;
448         }
449         break;
450         /*
451          * Connection is a server
452          *
453          * Serv <class> <nS> <nC> <name> <ConnBy> <last> <age>
454          *
455          * class        Class the server is in
456          * nS           Number of servers reached via this link
457          * nC           Number of clients reached via this link
458          * name         Name of the server linked
459          * ConnBy       Who established this link
460          * last         Seconds since we got something from this link
461          * age          Seconds this link has been alive
462          *
463          * Additional comments etc......        -Cym-<cym@acrux.net>
464          */
465
466       case STAT_SERVER:
467         if (cli_serv(acptr)->user)
468           send_reply(sptr, RPL_TRACESERVER, conClass, link_s[i],
469                      link_u[i], cli_name(acptr),
470                      (*(cli_serv(acptr))->by) ? cli_serv(acptr)->by : "*",
471                      cli_serv(acptr)->user->username, cli_serv(acptr)->user->host,
472                      CurrentTime - cli_lasttime(acptr),
473                      CurrentTime - cli_serv(acptr)->timestamp);
474         else
475           send_reply(sptr, RPL_TRACESERVER, conClass, link_s[i],
476                      link_u[i], cli_name(acptr),
477                      (*(cli_serv(acptr))->by) ? cli_serv(acptr)->by : "*", "*",
478                      cli_name(&me), CurrentTime - cli_lasttime(acptr),
479                      CurrentTime - cli_serv(acptr)->timestamp);
480         cnt++;
481         break;
482       default:                  /* We actually shouldn't come here, -msa */
483         send_reply(sptr, RPL_TRACENEWTYPE, get_client_name(acptr, HIDE_IP));
484         cnt++;
485         break;
486     }
487   }
488   /*
489    * Add these lines to summarize the above which can get rather long
490    * and messy when done remotely - Avalon
491    */
492   if (!IsAnOper(sptr) || !cnt) {
493     if (!cnt)
494       /* let the user have some idea that its at the end of the trace */
495       send_reply(sptr, RPL_TRACESERVER, 0, link_s[cli_fd(&me)],
496           link_u[cli_fd(&me)], "<No_match>", *(cli_serv(&me)->by) ?
497           cli_serv(&me)->by : "*", "*", cli_name(&me), 0, 0);
498     return 0;
499   }
500   if (doall) {
501     for (cl = get_class_list(); cl; cl = cl->next) {
502       if (Links(cl) > 0)
503         send_reply(sptr, RPL_TRACECLASS, ConClass(cl), Links(cl));
504     }
505   }
506   return 0;
507 }
508
509 /*
510  * mo_trace - oper message handler
511  *
512  * parv[0] = sender prefix
513  * parv[1] = nick or servername
514  * parv[2] = 'target' servername
515  */
516 int mo_trace(struct Client* cptr, struct Client* sptr, int parc, char* parv[])
517 {
518   int               i;
519   struct Client*    acptr;
520   const struct ConnectionClass* cl;
521   char*             tname;
522   int doall;
523   int link_s[MAXCONNECTIONS];
524   int link_u[MAXCONNECTIONS];
525   int cnt = 0;
526   int wilds;
527   int dow;
528
529   if (parc < 2 || BadPtr(parv[1])) {
530     /* just "TRACE" without parameters. Must be from local client */
531     parc = 1;
532     acptr = &me;
533     tname = cli_name(&me);
534     i = HUNTED_ISME;
535   } else if (parc < 3 || BadPtr(parv[2])) {
536     /* No target specified. Make one before propagating. */
537     parc = 2;
538     tname = parv[1];
539     if ((acptr = find_match_server(parv[1])) ||
540         ((acptr = FindClient(parv[1])) && !MyUser(acptr))) {
541       if (IsUser(acptr))
542         parv[2] = cli_name(cli_user(acptr)->server);
543       else
544         parv[2] = cli_name(acptr);
545       parc = 3;
546       parv[3] = 0;
547       if ((i = hunt_server_cmd(sptr, CMD_TRACE, cptr, IsServer(acptr),
548                                "%s :%C", 2, parc, parv)) == HUNTED_NOSUCH)
549         return 0;
550     } else
551       i = HUNTED_ISME;
552   } else {
553     /* Got "TRACE <tname> :<target>" */
554     parc = 3;
555     if (MyUser(sptr) || Protocol(cptr) < 10)
556       acptr = find_match_server(parv[2]);
557     else
558       acptr = FindNServer(parv[2]);
559     if ((i = hunt_server_cmd(sptr, CMD_TRACE, cptr, 0, "%s :%C", 2, parc,
560                              parv)) == HUNTED_NOSUCH)
561       return 0;
562     tname = parv[1];
563   }
564
565   if (i == HUNTED_PASS) {
566     if (!acptr)
567       acptr = next_client(GlobalClientList, tname);
568     else
569       acptr = cli_from(acptr);
570     send_reply(sptr, RPL_TRACELINK,
571                version, debugmode, tname,
572                acptr ? cli_name(cli_from(acptr)) : "<No_match>");
573     return 0;
574   }
575
576   doall = (parv[1] && (parc > 1)) ? !match(tname, cli_name(&me)) : 1;
577   wilds = !parv[1] || strchr(tname, '*') || strchr(tname, '?');
578   dow = wilds || doall;
579
580   /* Don't give (long) remote listings to lusers */
581   if (dow && !MyConnect(sptr) && !IsAnOper(sptr))
582     return 0;
583
584   for (i = 0; i < MAXCONNECTIONS; i++)
585     link_s[i] = 0, link_u[i] = 0;
586
587   if (doall) {
588     for (acptr = GlobalClientList; acptr; acptr = cli_next(acptr)) {
589       if (IsUser(acptr))
590         link_u[cli_fd(cli_from(acptr))]++;
591       else if (IsServer(acptr))
592         link_s[cli_fd(cli_from(acptr))]++;
593     }
594   }
595
596   /* report all direct connections */
597
598   for (i = 0; i <= HighestFd; i++) {
599     unsigned int conClass;
600
601     if (!(acptr = LocalClientArray[i])) /* Local Connection? */
602       continue;
603     if (IsInvisible(acptr) && dow && !(MyConnect(sptr) && IsOper(sptr)) &&
604         !IsAnOper(acptr) && (acptr != sptr))
605       continue;
606     if (!doall && wilds && match(tname, cli_name(acptr)))
607       continue;
608     if (!dow && 0 != ircd_strcmp(tname, cli_name(acptr)))
609       continue;
610     conClass = get_client_class(acptr);
611
612     switch (cli_status(acptr)) {
613       case STAT_CONNECTING:
614         send_reply(sptr, RPL_TRACECONNECTING, conClass, cli_name(acptr));
615         cnt++;
616         break;
617       case STAT_HANDSHAKE:
618         send_reply(sptr, RPL_TRACEHANDSHAKE, conClass, cli_name(acptr));
619         cnt++;
620         break;
621       case STAT_ME:
622         break;
623       case STAT_UNKNOWN:
624       case STAT_UNKNOWN_USER:
625         send_reply(sptr, RPL_TRACEUNKNOWN, conClass,
626                    get_client_name(acptr, HIDE_IP));
627         cnt++;
628         break;
629       case STAT_UNKNOWN_SERVER:
630         send_reply(sptr, RPL_TRACEUNKNOWN, conClass, "Unknown Server");
631         cnt++;
632         break;
633       case STAT_USER:
634         /* Only opers see users if there is a wildcard
635            but anyone can see all the opers. */
636         if ((IsAnOper(sptr) && (MyUser(sptr) ||
637             !(dow && IsInvisible(acptr)))) || !dow || IsAnOper(acptr)) {
638           if (IsAnOper(acptr))
639             send_reply(sptr, RPL_TRACEOPERATOR, conClass,
640                        get_client_name(acptr, SHOW_IP),
641                        CurrentTime - cli_lasttime(acptr));
642           else
643             send_reply(sptr, RPL_TRACEUSER, conClass,
644                        get_client_name(acptr, SHOW_IP),
645                        CurrentTime - cli_lasttime(acptr));
646           cnt++;
647         }
648         break;
649         /*
650          * Connection is a server
651          *
652          * Serv <class> <nS> <nC> <name> <ConnBy> <last> <age>
653          *
654          * class        Class the server is in
655          * nS           Number of servers reached via this link
656          * nC           Number of clients reached via this link
657          * name         Name of the server linked
658          * ConnBy       Who established this link
659          * last         Seconds since we got something from this link
660          * age          Seconds this link has been alive
661          *
662          * Additional comments etc......        -Cym-<cym@acrux.net>
663          */
664
665       case STAT_SERVER:
666         if (cli_serv(acptr)->user)
667           send_reply(sptr, RPL_TRACESERVER, conClass, link_s[i],
668                      link_u[i], cli_name(acptr),
669                      (*(cli_serv(acptr))->by) ? cli_serv(acptr)->by : "*",
670                      cli_serv(acptr)->user->username, cli_serv(acptr)->user->host,
671                      CurrentTime - cli_lasttime(acptr),
672                      CurrentTime - cli_serv(acptr)->timestamp);
673         else
674           send_reply(sptr, RPL_TRACESERVER, conClass, link_s[i],
675                      link_u[i], cli_name(acptr),
676                      (*(cli_serv(acptr))->by) ? cli_serv(acptr)->by : "*", "*",
677                      cli_name(&me), CurrentTime - cli_lasttime(acptr),
678                      CurrentTime - cli_serv(acptr)->timestamp);
679         cnt++;
680         break;
681       default:                  /* We actually shouldn't come here, -msa */
682         send_reply(sptr, RPL_TRACENEWTYPE, get_client_name(acptr, HIDE_IP));
683         cnt++;
684         break;
685     }
686   }
687   /*
688    * Add these lines to summarize the above which can get rather long
689    * and messy when done remotely - Avalon
690    */
691   if (!IsAnOper(sptr) || !cnt) {
692     if (!cnt)
693       /* let the user have some idea that its at the end of the trace */
694       send_reply(sptr, RPL_TRACESERVER, 0, link_s[cli_fd(&me)],
695                  link_u[cli_fd(&me)], "<No_match>", *(cli_serv(&me)->by) ?
696                  cli_serv(&me)->by : "*", "*", cli_name(&me), 0, 0);
697     return 0;
698   }
699   if (doall) {
700     for (cl = get_class_list(); cl; cl = cl->next) {
701       if (Links(cl) > 0)
702         send_reply(sptr, RPL_TRACECLASS, ConClass(cl), Links(cl));
703     }
704   }
705   return 0;
706 }
707
708