Author: Bleep <helveytw@home.com>
[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 = me.name;
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] = acptr->user->server->name;
143       else
144         parv[2] = acptr->name;
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 = acptr->from;
170     send_reply(sptr, RPL_TRACELINK,
171 #ifndef GODMODE
172         version, debugmode, tname, acptr ? acptr->from->name : "<No_match>"
173 #else /* GODMODE */
174         version, debugmode, tname, acptr ? acptr->from->name : "<No_match>",
175         (acptr && acptr->from->serv) ? acptr->from->serv->timestamp : 0
176 #endif /* GODMODE */
177                ); /* I really could do without GODMODE */
178     return 0;
179   }
180
181   doall = (parv[1] && (parc > 1)) ? !match(tname, me.name) : 1;
182   wilds = !parv[1] || strchr(tname, '*') || strchr(tname, '?');
183   dow = wilds || doall;
184
185   /* Don't give (long) remote listings to lusers */
186   if (dow && !MyConnect(sptr) && !IsAnOper(sptr))
187     return 0;
188
189   for (i = 0; i < MAXCONNECTIONS; i++)
190     link_s[i] = 0, link_u[i] = 0;
191
192   if (doall) {
193     for (acptr = GlobalClientList; acptr; acptr = acptr->next) {
194       if (IsUser(acptr))
195         link_u[acptr->from->fd]++;
196       else if (IsServer(acptr))
197         link_s[acptr->from->fd]++;
198     }
199   }
200
201   /* report all direct connections */
202
203   for (i = 0; i <= HighestFd; i++) {
204     unsigned int conClass;
205
206     if (!(acptr = LocalClientArray[i])) /* Local Connection? */
207       continue;
208     if (IsInvisible(acptr) && dow && !(MyConnect(sptr) && IsOper(sptr)) &&
209         !IsAnOper(acptr) && (acptr != sptr))
210       continue;
211     if (!doall && wilds && match(tname, acptr->name))
212       continue;
213     if (!dow && 0 != ircd_strcmp(tname, acptr->name))
214       continue;
215
216     conClass = get_client_class(acptr);
217
218     switch (acptr->status) {
219       case STAT_CONNECTING:
220         send_reply(sptr, RPL_TRACECONNECTING, conClass, acptr->name);
221         cnt++;
222         break;
223       case STAT_HANDSHAKE:
224         send_reply(sptr, RPL_TRACEHANDSHAKE, conClass, acptr->name);
225         cnt++;
226         break;
227       case STAT_ME:
228         break;
229       case STAT_UNKNOWN:
230       case STAT_UNKNOWN_USER:
231         send_reply(sptr, RPL_TRACEUNKNOWN, conClass,
232                    get_client_name(acptr, HIDE_IP));
233         cnt++;
234         break;
235       case STAT_UNKNOWN_SERVER:
236         send_reply(sptr, RPL_TRACEUNKNOWN, conClass, "Unknown Server");
237         cnt++;
238         break;
239       case STAT_USER:
240         /* Only opers see users if there is a wildcard
241            but anyone can see all the opers. */
242         if ((IsAnOper(sptr) && (MyUser(sptr) ||
243             !(dow && IsInvisible(acptr)))) || !dow || IsAnOper(acptr)) {
244           if (IsAnOper(acptr))
245             send_reply(sptr, RPL_TRACEOPERATOR, conClass,
246                        get_client_name(acptr, HIDE_IP),
247                        CurrentTime - acptr->lasttime);
248           else
249             send_reply(sptr, RPL_TRACEUSER, conClass,
250                        get_client_name(acptr, HIDE_IP),
251                        CurrentTime - acptr->lasttime);
252           cnt++;
253         }
254         break;
255         /*
256          * Connection is a server
257          *
258          * Serv <class> <nS> <nC> <name> <ConnBy> <last> <age>
259          *
260          * class        Class the server is in
261          * nS           Number of servers reached via this link
262          * nC           Number of clients reached via this link
263          * name         Name of the server linked
264          * ConnBy       Who established this link
265          * last         Seconds since we got something from this link
266          * age          Seconds this link has been alive
267          *
268          * Additional comments etc......        -Cym-<cym@acrux.net>
269          */
270
271       case STAT_SERVER:
272         if (acptr->serv->user)
273           send_reply(sptr, RPL_TRACESERVER, conClass, link_s[i],
274                      link_u[i], acptr->name,
275                      (*acptr->serv->by) ? acptr->serv->by : "*",
276                      acptr->serv->user->username, acptr->serv->user->host,
277                      CurrentTime - acptr->lasttime,
278                      CurrentTime - acptr->serv->timestamp);
279         else
280           send_reply(sptr, RPL_TRACESERVER, conClass, link_s[i],
281                      link_u[i], acptr->name,
282                      (*acptr->serv->by) ?  acptr->serv->by : "*", "*",
283                      me.name, CurrentTime - acptr->lasttime,
284                      CurrentTime - acptr->serv->timestamp);
285         cnt++;
286         break;
287       default:                  /* We actually shouldn't come here, -msa */
288         send_reply(sptr, RPL_TRACENEWTYPE, get_client_name(acptr, HIDE_IP));
289         cnt++;
290         break;
291     }
292   }
293   /*
294    * Add these lines to summarize the above which can get rather long
295    * and messy when done remotely - Avalon
296    */
297   if (!IsAnOper(sptr) || !cnt) {
298     if (!cnt)
299       /* let the user have some idea that its at the end of the trace */
300       send_reply(sptr, RPL_TRACESERVER, 0, link_s[me.fd],
301                  link_u[me.fd], "<No_match>", *(me.serv->by) ?
302                  me.serv->by : "*", "*", me.name, 0, 0);
303     return 0;
304   }
305   if (doall) {
306     for (cl = get_class_list(); cl; cl = cl->next) {
307       if (Links(cl) > 0)
308        send_reply(sptr, RPL_TRACECLASS, ConClass(cl), Links(cl));
309     }
310   }
311   return 0;
312 }
313
314 /*
315  * ms_trace - server message handler
316  *
317  * parv[0] = sender prefix
318  * parv[1] = nick or servername
319  * parv[2] = 'target' servername
320  */
321 int ms_trace(struct Client* cptr, struct Client* sptr, int parc, char* parv[])
322 {
323   int i;
324   struct Client *acptr;
325   const struct ConnectionClass* cl;
326   char *tname;
327   int doall;
328   int link_s[MAXCONNECTIONS];
329   int link_u[MAXCONNECTIONS];
330   int cnt = 0;
331   int wilds;
332   int dow;
333
334   if (parc < 2 || BadPtr(parv[1])) {
335     /* just "TRACE" without parameters. Must be from local client */
336     parc = 1;
337     acptr = &me;
338     tname = me.name;
339     i = HUNTED_ISME;
340   } else if (parc < 3 || BadPtr(parv[2])) {
341     /* No target specified. Make one before propagating. */
342     parc = 2;
343     tname = parv[1];
344     if ((acptr = find_match_server(parv[1])) ||
345         ((acptr = FindClient(parv[1])) && !MyUser(acptr))) {
346       if (IsUser(acptr))
347         parv[2] = acptr->user->server->name;
348       else
349         parv[2] = acptr->name;
350       parc = 3;
351       parv[3] = 0;
352
353       if ((i = hunt_server_cmd(sptr, CMD_TRACE, cptr, IsServer(acptr),
354                                "%s :%C", 2, parc, parv)) == HUNTED_NOSUCH)
355         return 0;
356     } else
357       i = HUNTED_ISME;
358   } else {
359     /* Got "TRACE <tname> :<target>" */
360     parc = 3;
361     if (MyUser(sptr) || Protocol(cptr) < 10)
362       acptr = find_match_server(parv[2]);
363     else
364       acptr = FindNServer(parv[2]);
365     if ((i = hunt_server_cmd(sptr, CMD_TRACE, cptr, 0, "%s :%C", 2, parc,
366                              parv)) == HUNTED_NOSUCH)
367       return 0;
368     tname = parv[1];
369   }
370
371   if (i == HUNTED_PASS) {
372     if (!acptr)
373       acptr = next_client(GlobalClientList, tname);
374     else
375       acptr = acptr->from;
376     send_reply(sptr, RPL_TRACELINK,
377 #ifndef GODMODE
378         version, debugmode, tname, acptr ? acptr->from->name : "<No_match>"
379 #else /* GODMODE */
380         version, debugmode, tname, acptr ? acptr->from->name : "<No_match>",
381         (acptr && acptr->from->serv) ? acptr->from->serv->timestamp : 0
382 #endif /* GODMODE */
383                ); /* I really could do without GODMODE */
384     return 0;
385   }
386
387   doall = (parv[1] && (parc > 1)) ? !match(tname, me.name) : 1;
388   wilds = !parv[1] || strchr(tname, '*') || strchr(tname, '?');
389   dow = wilds || doall;
390
391   /* Don't give (long) remote listings to lusers */
392   if (dow && !MyConnect(sptr) && !IsAnOper(sptr))
393     return 0;
394
395   for (i = 0; i < MAXCONNECTIONS; i++)
396     link_s[i] = 0, link_u[i] = 0;
397
398   if (doall) {
399     for (acptr = GlobalClientList; acptr; acptr = acptr->next) {
400       if (IsUser(acptr))
401         link_u[acptr->from->fd]++;
402       else if (IsServer(acptr))
403         link_s[acptr->from->fd]++;
404     }
405   }
406
407   /* report all direct connections */
408
409   for (i = 0; i <= HighestFd; i++) {
410     unsigned int conClass;
411
412     if (!(acptr = LocalClientArray[i])) /* Local Connection? */
413       continue;
414     if (IsInvisible(acptr) && dow && !(MyConnect(sptr) && IsOper(sptr)) &&
415         !IsAnOper(acptr) && (acptr != sptr))
416       continue;
417     if (!doall && wilds && match(tname, acptr->name))
418       continue;
419     if (!dow && 0 != ircd_strcmp(tname, acptr->name))
420       continue;
421     conClass = get_client_class(acptr);
422
423     switch (acptr->status) {
424       case STAT_CONNECTING:
425         send_reply(sptr, RPL_TRACECONNECTING, conClass, acptr->name);
426         cnt++;
427         break;
428       case STAT_HANDSHAKE:
429         send_reply(sptr, RPL_TRACEHANDSHAKE, conClass, acptr->name);
430         cnt++;
431         break;
432       case STAT_ME:
433         break;
434       case STAT_UNKNOWN:
435       case STAT_UNKNOWN_USER:
436         send_reply(sptr, RPL_TRACEUNKNOWN, conClass,
437                    get_client_name(acptr, HIDE_IP));
438         cnt++;
439         break;
440       case STAT_UNKNOWN_SERVER:
441         send_reply(sptr, RPL_TRACEUNKNOWN, conClass, "Unknown Server");
442         cnt++;
443         break;
444       case STAT_USER:
445         /* Only opers see users if there is a wildcard
446            but anyone can see all the opers. */
447         if ((IsAnOper(sptr) && (MyUser(sptr) ||
448             !(dow && IsInvisible(acptr)))) || !dow || IsAnOper(acptr)) {
449           if (IsAnOper(acptr))
450             send_reply(sptr, RPL_TRACEOPERATOR, conClass,
451                        get_client_name(acptr, HIDE_IP),
452                        CurrentTime - acptr->lasttime);
453           else
454             send_reply(sptr, RPL_TRACEUSER, conClass,
455                        get_client_name(acptr, HIDE_IP),
456                        CurrentTime - acptr->lasttime);
457           cnt++;
458         }
459         break;
460         /*
461          * Connection is a server
462          *
463          * Serv <class> <nS> <nC> <name> <ConnBy> <last> <age>
464          *
465          * class        Class the server is in
466          * nS           Number of servers reached via this link
467          * nC           Number of clients reached via this link
468          * name         Name of the server linked
469          * ConnBy       Who established this link
470          * last         Seconds since we got something from this link
471          * age          Seconds this link has been alive
472          *
473          * Additional comments etc......        -Cym-<cym@acrux.net>
474          */
475
476       case STAT_SERVER:
477         if (acptr->serv->user)
478           send_reply(sptr, RPL_TRACESERVER, conClass, link_s[i],
479                      link_u[i], acptr->name,
480                      (*acptr->serv->by) ? acptr->serv->by : "*",
481                      acptr->serv->user->username, acptr->serv->user->host,
482                      CurrentTime - acptr->lasttime,
483                      CurrentTime - acptr->serv->timestamp);
484         else
485           send_reply(sptr, RPL_TRACESERVER, conClass, link_s[i],
486                      link_u[i], acptr->name,
487                      (*acptr->serv->by) ?  acptr->serv->by : "*", "*",
488                      me.name, CurrentTime - acptr->lasttime,
489                      CurrentTime - acptr->serv->timestamp);
490         cnt++;
491         break;
492       default:                  /* We actually shouldn't come here, -msa */
493         send_reply(sptr, RPL_TRACENEWTYPE, get_client_name(acptr, HIDE_IP));
494         cnt++;
495         break;
496     }
497   }
498   /*
499    * Add these lines to summarize the above which can get rather long
500    * and messy when done remotely - Avalon
501    */
502   if (!IsAnOper(sptr) || !cnt) {
503     if (!cnt)
504       /* let the user have some idea that its at the end of the trace */
505       send_reply(sptr, RPL_TRACESERVER, 0, link_s[me.fd],
506           link_u[me.fd], "<No_match>", *(me.serv->by) ?
507           me.serv->by : "*", "*", me.name, 0, 0);
508     return 0;
509   }
510   if (doall) {
511     for (cl = get_class_list(); cl; cl = cl->next) {
512       if (Links(cl) > 0)
513         send_reply(sptr, RPL_TRACECLASS, ConClass(cl), Links(cl));
514     }
515   }
516   return 0;
517 }
518
519 /*
520  * mo_trace - oper message handler
521  *
522  * parv[0] = sender prefix
523  * parv[1] = nick or servername
524  * parv[2] = 'target' servername
525  */
526 int mo_trace(struct Client* cptr, struct Client* sptr, int parc, char* parv[])
527 {
528   int               i;
529   struct Client*    acptr;
530   const struct ConnectionClass* cl;
531   char*             tname;
532   int doall;
533   int link_s[MAXCONNECTIONS];
534   int link_u[MAXCONNECTIONS];
535   int cnt = 0;
536   int wilds;
537   int dow;
538
539   if (parc < 2 || BadPtr(parv[1])) {
540     /* just "TRACE" without parameters. Must be from local client */
541     parc = 1;
542     acptr = &me;
543     tname = me.name;
544     i = HUNTED_ISME;
545   } else if (parc < 3 || BadPtr(parv[2])) {
546     /* No target specified. Make one before propagating. */
547     parc = 2;
548     tname = parv[1];
549     if ((acptr = find_match_server(parv[1])) ||
550         ((acptr = FindClient(parv[1])) && !MyUser(acptr))) {
551       if (IsUser(acptr))
552         parv[2] = acptr->user->server->name;
553       else
554         parv[2] = acptr->name;
555       parc = 3;
556       parv[3] = 0;
557       if ((i = hunt_server_cmd(sptr, CMD_TRACE, cptr, IsServer(acptr),
558                                "%s :%C", 2, parc, parv)) == HUNTED_NOSUCH)
559         return 0;
560     } else
561       i = HUNTED_ISME;
562   } else {
563     /* Got "TRACE <tname> :<target>" */
564     parc = 3;
565     if (MyUser(sptr) || Protocol(cptr) < 10)
566       acptr = find_match_server(parv[2]);
567     else
568       acptr = FindNServer(parv[2]);
569     if ((i = hunt_server_cmd(sptr, CMD_TRACE, cptr, 0, "%s :%C", 2, parc,
570                              parv)) == HUNTED_NOSUCH)
571       return 0;
572     tname = parv[1];
573   }
574
575   if (i == HUNTED_PASS) {
576     if (!acptr)
577       acptr = next_client(GlobalClientList, tname);
578     else
579       acptr = acptr->from;
580     send_reply(sptr, RPL_TRACELINK,
581 #ifndef GODMODE
582         version, debugmode, tname, acptr ? acptr->from->name : "<No_match>"
583 #else /* GODMODE */
584         version, debugmode, tname, acptr ? acptr->from->name : "<No_match>",
585         (acptr && acptr->from->serv) ? acptr->from->serv->timestamp : 0
586 #endif /* GODMODE */
587                ); /* I really could do without GODMODE */
588     return 0;
589   }
590
591   doall = (parv[1] && (parc > 1)) ? !match(tname, me.name) : 1;
592   wilds = !parv[1] || strchr(tname, '*') || strchr(tname, '?');
593   dow = wilds || doall;
594
595   /* Don't give (long) remote listings to lusers */
596   if (dow && !MyConnect(sptr) && !IsAnOper(sptr))
597     return 0;
598
599   for (i = 0; i < MAXCONNECTIONS; i++)
600     link_s[i] = 0, link_u[i] = 0;
601
602   if (doall) {
603     for (acptr = GlobalClientList; acptr; acptr = acptr->next) {
604       if (IsUser(acptr))
605         link_u[acptr->from->fd]++;
606       else if (IsServer(acptr))
607         link_s[acptr->from->fd]++;
608     }
609   }
610
611   /* report all direct connections */
612
613   for (i = 0; i <= HighestFd; i++) {
614     unsigned int conClass;
615
616     if (!(acptr = LocalClientArray[i])) /* Local Connection? */
617       continue;
618     if (IsInvisible(acptr) && dow && !(MyConnect(sptr) && IsOper(sptr)) &&
619         !IsAnOper(acptr) && (acptr != sptr))
620       continue;
621     if (!doall && wilds && match(tname, acptr->name))
622       continue;
623     if (!dow && 0 != ircd_strcmp(tname, acptr->name))
624       continue;
625     conClass = get_client_class(acptr);
626
627     switch (acptr->status) {
628       case STAT_CONNECTING:
629         send_reply(sptr, RPL_TRACECONNECTING, conClass, acptr->name);
630         cnt++;
631         break;
632       case STAT_HANDSHAKE:
633         send_reply(sptr, RPL_TRACEHANDSHAKE, conClass, acptr->name);
634         cnt++;
635         break;
636       case STAT_ME:
637         break;
638       case STAT_UNKNOWN:
639       case STAT_UNKNOWN_USER:
640         send_reply(sptr, RPL_TRACEUNKNOWN, conClass,
641                    get_client_name(acptr, HIDE_IP));
642         cnt++;
643         break;
644       case STAT_UNKNOWN_SERVER:
645         send_reply(sptr, RPL_TRACEUNKNOWN, conClass, "Unknown Server");
646         cnt++;
647         break;
648       case STAT_USER:
649         /* Only opers see users if there is a wildcard
650            but anyone can see all the opers. */
651         if ((IsAnOper(sptr) && (MyUser(sptr) ||
652             !(dow && IsInvisible(acptr)))) || !dow || IsAnOper(acptr)) {
653           if (IsAnOper(acptr))
654             send_reply(sptr, RPL_TRACEOPERATOR, conClass,
655                        get_client_name(acptr, HIDE_IP),
656                        CurrentTime - acptr->lasttime);
657           else
658             send_reply(sptr, RPL_TRACEUSER, conClass,
659                        get_client_name(acptr, HIDE_IP),
660                        CurrentTime - acptr->lasttime);
661           cnt++;
662         }
663         break;
664         /*
665          * Connection is a server
666          *
667          * Serv <class> <nS> <nC> <name> <ConnBy> <last> <age>
668          *
669          * class        Class the server is in
670          * nS           Number of servers reached via this link
671          * nC           Number of clients reached via this link
672          * name         Name of the server linked
673          * ConnBy       Who established this link
674          * last         Seconds since we got something from this link
675          * age          Seconds this link has been alive
676          *
677          * Additional comments etc......        -Cym-<cym@acrux.net>
678          */
679
680       case STAT_SERVER:
681         if (acptr->serv->user)
682           send_reply(sptr, RPL_TRACESERVER, conClass, link_s[i],
683                      link_u[i], acptr->name,
684                      (*acptr->serv->by) ? acptr->serv->by : "*",
685                      acptr->serv->user->username, acptr->serv->user->host,
686                      CurrentTime - acptr->lasttime,
687                      CurrentTime - acptr->serv->timestamp);
688         else
689           send_reply(sptr, RPL_TRACESERVER, conClass, link_s[i],
690                      link_u[i], acptr->name,
691                      (*acptr->serv->by) ?  acptr->serv->by : "*", "*",
692                      me.name, CurrentTime - acptr->lasttime,
693                      CurrentTime - acptr->serv->timestamp);
694         cnt++;
695         break;
696       default:                  /* We actually shouldn't come here, -msa */
697         send_reply(sptr, RPL_TRACENEWTYPE, get_client_name(acptr, HIDE_IP));
698         cnt++;
699         break;
700     }
701   }
702   /*
703    * Add these lines to summarize the above which can get rather long
704    * and messy when done remotely - Avalon
705    */
706   if (!IsAnOper(sptr) || !cnt) {
707     if (!cnt)
708       /* let the user have some idea that its at the end of the trace */
709       send_reply(sptr, RPL_TRACESERVER, 0, link_s[me.fd],
710                  link_u[me.fd], "<No_match>", *(me.serv->by) ?
711                  me.serv->by : "*", "*", me.name, 0, 0);
712     return 0;
713   }
714   if (doall) {
715     for (cl = get_class_list(); cl; cl = cl->next) {
716       if (Links(cl) > 0)
717         send_reply(sptr, RPL_TRACECLASS, ConClass(cl), Links(cl));
718     }
719   }
720   return 0;
721 }
722
723