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