Author: Kev <klmitch@mit.edu>
[ircu2.10.12-pk.git] / ircd / gline.c
1 /*
2  * IRC - Internet Relay Chat, ircd/gline.c
3  * Copyright (C) 1990 Jarkko Oikarinen and
4  *                    University of Oulu, Finland
5  *
6  * This program is free software; you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation; either version 1, or (at your option)
9  * any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with this program; if not, write to the Free Software
18  * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
19  *
20  * $Id$
21  */
22 #include "config.h"
23
24 #include "gline.h"
25 #include "client.h"
26 #include "ircd.h"
27 #include "ircd_alloc.h"
28 #include "ircd_log.h"
29 #include "ircd_policy.h"
30 #include "ircd_reply.h"
31 #include "ircd_string.h"
32 #include "match.h"
33 #include "numeric.h"
34 #include "s_bsd.h"
35 #include "s_debug.h"
36 #include "s_misc.h"
37 #include "send.h"
38 #include "struct.h"
39 #include "support.h"
40 #include "msg.h"
41 #include "numnicks.h"
42 #include "numeric.h"
43 #include "sys.h"    /* FALSE bleah */
44
45 #include <assert.h>
46 #include <string.h>
47 #include <stdio.h>
48 #include <arpa/inet.h> /* for inet_ntoa */
49
50 struct Gline* GlobalGlineList  = 0;
51 struct Gline* BadChanGlineList = 0;
52
53 static void
54 canon_userhost(char *userhost, char **user_p, char **host_p, char *def_user)
55 {
56   char *tmp;
57
58   if (!(tmp = strchr(userhost, '@'))) {
59     *user_p = def_user;
60     *host_p = userhost;
61   } else {
62     *user_p = userhost;
63     *(tmp++) = '\0';
64     *host_p = tmp;
65   }
66 }
67
68 static struct Gline *
69 make_gline(char *userhost, char *reason, time_t expire, time_t lastmod,
70            unsigned int flags)
71 {
72   struct Gline *gline, *sgline, *after = 0;
73   char *user, *host;
74
75   if (!(flags & GLINE_BADCHAN)) { /* search for overlapping glines first */
76     canon_userhost(userhost, &user, &host, "*"); /* find user and host */
77
78     for (gline = GlobalGlineList; gline; gline = sgline) {
79       sgline = gline->gl_next;
80
81       if (gline->gl_expire <= CurrentTime)
82         gline_free(gline);
83       else if ((gline->gl_flags & GLINE_LOCAL) != (flags & GLINE_LOCAL))
84         continue;
85       else if (!mmatch(gline->gl_user, user) && /* gline contains new mask */
86                !mmatch(gline->gl_host, host)) {
87         if (expire <= gline->gl_expire) /* will expire before wider gline */
88           return 0;
89         else
90           after = gline; /* stick new gline after this one */
91       } else if (!mmatch(user, gline->gl_user) && /* new mask contains gline */
92                  !mmatch(host, gline->gl_host) &&
93                  gline->gl_expire <= expire) /* gline expires before new one */
94         gline_free(gline); /* save some memory */
95     }
96   }
97
98   gline = (struct Gline *)MyMalloc(sizeof(struct Gline)); /* alloc memory */
99   assert(0 != gline);
100
101   DupString(gline->gl_reason, reason); /* initialize gline... */
102   gline->gl_expire = expire;
103   gline->gl_lastmod = lastmod;
104   gline->gl_flags = flags & GLINE_MASK;
105
106   if (flags & GLINE_BADCHAN) { /* set a BADCHAN gline */
107     DupString(gline->gl_user, userhost); /* first, remember channel */
108     gline->gl_host = 0;
109
110     gline->gl_next = BadChanGlineList; /* then link it into list */
111     gline->gl_prev_p = &BadChanGlineList;
112     if (BadChanGlineList)
113       BadChanGlineList->gl_prev_p = &gline->gl_next;
114     BadChanGlineList = gline;
115   } else {
116     DupString(gline->gl_user, user); /* remember them... */
117     DupString(gline->gl_host, host);
118
119     if (check_if_ipmask(host)) { /* mark if it's an IP mask */
120       int class;
121       char ipname[16];
122       int ad[4] = { 0 };
123       int bits2 = 0;
124        
125       class = sscanf(host,"%d.%d.%d.%d/%d",
126                      &ad[0],&ad[1],&ad[2],&ad[3], &bits2);
127       if (class!=5) {
128         gline->bits=class*8;
129       }
130       else {
131         gline->bits=bits2;
132       }
133       sprintf_irc(ipname,"%d.%d.%d.%d",ad[0],ad[1],ad[2],ad[3]);
134       gline->ipnum.s_addr = inet_addr(ipname);
135       Debug((DEBUG_DEBUG,"IP gline: %08x/%i",gline->ipnum.s_addr,gline->bits));
136       gline->gl_flags |= GLINE_IPMASK;
137     }
138
139     if (after) {
140       gline->gl_next = after->gl_next;
141       gline->gl_prev_p = &after->gl_next;
142       if (after->gl_next)
143         after->gl_next->gl_prev_p = &gline->gl_next;
144       after->gl_next = gline;
145     } else {
146       gline->gl_next = GlobalGlineList; /* then link it into list */
147       gline->gl_prev_p = &GlobalGlineList;
148       if (GlobalGlineList)
149         GlobalGlineList->gl_prev_p = &gline->gl_next;
150       GlobalGlineList = gline;
151     }
152   }
153
154   return gline;
155 }
156
157 static int
158 do_gline(struct Client *cptr, struct Client *sptr, struct Gline *gline)
159 {
160   struct Client *acptr;
161   int fd, retval = 0, tval;
162
163   if (!GlineIsActive(gline)) /* no action taken on inactive glines */
164     return 0;
165
166   for (fd = HighestFd; fd >= 0; --fd) {
167     /*
168      * get the users!
169      */
170     if ((acptr = LocalClientArray[fd])) {
171       if (!cli_user(acptr))
172         continue;
173         
174       if (cli_user(acptr)->username && 
175           match (gline->gl_user, (cli_user(acptr))->username) != 0)
176                continue;
177           
178       if (GlineIsIpMask(gline)) {
179         Debug((DEBUG_DEBUG,"IP gline: %08x %08x/%i",(cli_ip(cptr)).s_addr,gline->ipnum.s_addr,gline->bits));
180         if (((cli_ip(acptr)).s_addr & NETMASK(gline->bits)) != gline->ipnum.s_addr)
181           continue;
182       }
183       else {
184         if (match(gline->gl_host, cli_sockhost(acptr)) != 0)
185           continue;
186       }
187
188       /* ok, here's one that got G-lined */
189       send_reply(acptr, SND_EXPLICIT | ERR_YOUREBANNEDCREEP, ":%s",
190            gline->gl_reason);
191
192       /* let the ops know about it */
193       sendto_opmask_butone(0, SNO_GLINE, "G-line active for %s",
194                      get_client_name(acptr, TRUE));
195
196       /* and get rid of him */
197       if ((tval = exit_client_msg(cptr, acptr, &me, "G-lined (%s)",
198           gline->gl_reason)))
199         retval = tval; /* retain killed status */
200     }
201   }
202   return retval;
203 }
204
205 int
206 gline_propagate(struct Client *cptr, struct Client *sptr, struct Gline *gline)
207 {
208   if (GlineIsLocal(gline) || (IsUser(sptr) && !gline->gl_lastmod))
209     return 0;
210
211   if (gline->gl_lastmod)
212     sendcmdto_serv_butone(sptr, CMD_GLINE, cptr, "* %c%s%s%s %Tu %Tu :%s",
213                           GlineIsRemActive(gline) ? '+' : '-', gline->gl_user,
214                           GlineIsBadChan(gline) ? "" : "@",
215                           GlineIsBadChan(gline) ? "" : gline->gl_host,
216                           gline->gl_expire - CurrentTime, gline->gl_lastmod,
217                           gline->gl_reason);
218   else
219     sendcmdto_serv_butone(sptr, CMD_GLINE, cptr,
220                           (GlineIsRemActive(gline) ?
221                            "* +%s%s%s %Tu :%s" : "* -%s%s%s"),
222                           gline->gl_user, GlineIsBadChan(gline) ? "" : "@",
223                           GlineIsBadChan(gline) ? "" : gline->gl_host,
224                           gline->gl_expire - CurrentTime, gline->gl_reason);
225
226   return 0;
227 }
228
229 int 
230 gline_add(struct Client *cptr, struct Client *sptr, char *userhost,
231           char *reason, time_t expire, time_t lastmod, unsigned int flags)
232 {
233   struct Gline *agline;
234
235   assert(0 != userhost);
236   assert(0 != reason);
237
238   /*
239    * You cannot set a negative (or zero) expire time, nor can you set an
240    * expiration time for greater than GLINE_MAX_EXPIRE.
241    */
242   if (!(flags & GLINE_FORCE) && (expire <= 0 || expire > GLINE_MAX_EXPIRE)) {
243     if (!IsServer(sptr) && MyConnect(sptr))
244       send_reply(sptr, ERR_BADEXPIRE, expire);
245     return 0;
246   }
247
248   expire += CurrentTime; /* convert from lifetime to timestamp */
249
250   /* NO_OLD_GLINE allows *@#channel to work correctly */
251   if (*userhost == '#' || *userhost == '&' || *userhost == '+'
252 # ifndef NO_OLD_GLINE
253       || userhost[2] == '#' || userhost[2] == '&' || userhost[2] == '+'
254 # endif /* OLD_GLINE */
255       ) {
256     if ((flags & GLINE_LOCAL) && !HasPriv(sptr, PRIV_LOCAL_BADCHAN))
257       return send_reply(sptr, ERR_NOPRIVILEGES);
258
259     flags |= GLINE_BADCHAN;
260   }
261
262   /* Inform ops... */
263   sendto_opmask_butone(0, SNO_GLINE, "%s adding %s %s for %s, expiring at "
264                        "%Tu: %s",
265 #ifdef HEAD_IN_SAND_SNOTICES
266                        cli_name(sptr),
267 #else
268                        IsServer(sptr) ? cli_name(sptr) :
269                        cli_name((cli_user(sptr))->server),
270 #endif
271                        flags & GLINE_LOCAL ? "local" : "global",
272                        flags & GLINE_BADCHAN ? "BADCHAN" : "GLINE", userhost,
273                        expire + TSoffset, reason);
274
275   /* and log it */
276   log_write(LS_GLINE, L_INFO, LOG_NOSNOTICE,
277             "%#C adding %s %s for %s, expiring at %Tu: %s", sptr,
278             flags & GLINE_LOCAL ? "local" : "global",
279             flags & GLINE_BADCHAN ? "BADCHAN" : "GLINE", userhost,
280             expire + TSoffset, reason);
281
282   /* make the gline */
283   agline = make_gline(userhost, reason, expire, lastmod, flags);
284
285   if (!agline) /* if it overlapped, silently return */
286     return 0;
287
288   gline_propagate(cptr, sptr, agline);
289
290   if (GlineIsBadChan(agline))
291     return 0;
292
293   return do_gline(cptr, sptr, agline); /* knock off users if necessary */
294 }
295
296 int
297 gline_activate(struct Client *cptr, struct Client *sptr, struct Gline *gline,
298                time_t lastmod, unsigned int flags)
299 {
300   unsigned int saveflags = 0;
301
302   assert(0 != gline);
303
304   saveflags = gline->gl_flags;
305
306   if (flags & GLINE_LOCAL)
307     gline->gl_flags &= ~GLINE_LDEACT;
308   else {
309     gline->gl_flags |= GLINE_ACTIVE;
310
311     if (gline->gl_lastmod) {
312       if (gline->gl_lastmod >= lastmod) /* force lastmod to increase */
313         gline->gl_lastmod++;
314       else
315         gline->gl_lastmod = lastmod;
316     }
317   }
318
319   if ((saveflags & GLINE_ACTMASK) == GLINE_ACTIVE)
320     return 0; /* was active to begin with */
321
322   /* Inform ops and log it */
323   sendto_opmask_butone(0, SNO_GLINE, "%s activating global %s for %s%s%s, "
324                        "expiring at %Tu: %s",
325 #ifdef HEAD_IN_SAND_SNOTICES
326                        cli_name(sptr),
327 #else
328                        IsServer(sptr) ? cli_name(sptr) :
329                        cli_name((cli_user(sptr))->server),
330 #endif
331                        GlineIsBadChan(gline) ? "BADCHAN" : "GLINE",
332                        gline->gl_user, GlineIsBadChan(gline) ? "" : "@",
333                        GlineIsBadChan(gline) ? "" : gline->gl_host,
334                        gline->gl_expire + TSoffset, gline->gl_reason);
335
336   log_write(LS_GLINE, L_INFO, LOG_NOSNOTICE,
337             "%#C activating global %s for %s%s%s, expiring at %Tu: %s", sptr,
338             GlineIsBadChan(gline) ? "BADCHAN" : "GLINE", gline->gl_user,
339             GlineIsBadChan(gline) ? "" : "@",
340             GlineIsBadChan(gline) ? "" : gline->gl_host,
341             gline->gl_expire + TSoffset, gline->gl_reason);
342
343   if (!(flags & GLINE_LOCAL)) /* don't propagate local changes */
344     gline_propagate(cptr, sptr, gline);
345
346   return GlineIsBadChan(gline) ? 0 : do_gline(cptr, sptr, gline);
347 }
348
349 int
350 gline_deactivate(struct Client *cptr, struct Client *sptr, struct Gline *gline,
351                  time_t lastmod, unsigned int flags)
352 {
353   unsigned int saveflags = 0;
354   char *msg;
355
356   assert(0 != gline);
357
358   saveflags = gline->gl_flags;
359
360   if (GlineIsLocal(gline))
361     msg = "removing local";
362   else if (!gline->gl_lastmod && !(flags & GLINE_LOCAL)) {
363     msg = "removing global";
364     gline->gl_flags &= ~GLINE_ACTIVE; /* propagate a -<mask> */
365   } else {
366     msg = "deactivating global";
367
368     if (flags & GLINE_LOCAL)
369       gline->gl_flags |= GLINE_LDEACT;
370     else {
371       gline->gl_flags &= ~GLINE_ACTIVE;
372
373       if (gline->gl_lastmod) {
374         if (gline->gl_lastmod >= lastmod)
375           gline->gl_lastmod++;
376         else
377           gline->gl_lastmod = lastmod;
378       }
379     }
380
381     if ((saveflags & GLINE_ACTMASK) != GLINE_ACTIVE)
382       return 0; /* was inactive to begin with */
383   }
384
385   /* Inform ops and log it */
386   sendto_opmask_butone(0, SNO_GLINE, "%s %s %s for %s%s%s, expiring at %Tu: "
387                        "%s",
388 #ifdef HEAD_IN_SAND_SNOTICES
389                        cli_name(sptr),
390 #else
391                        IsServer(sptr) ? cli_name(sptr) :
392                        cli_name((cli_user(sptr))->server),
393 #endif
394                        msg, GlineIsBadChan(gline) ? "BADCHAN" : "GLINE",
395                        gline->gl_user, GlineIsBadChan(gline) ? "" : "@",
396                        GlineIsBadChan(gline) ? "" : gline->gl_host,
397                        gline->gl_expire + TSoffset, gline->gl_reason);
398
399   log_write(LS_GLINE, L_INFO, LOG_NOSNOTICE,
400             "%#C %s %s for %s%s%s, expiring at %Tu: %s", sptr, msg,
401             GlineIsBadChan(gline) ? "BADCHAN" : "GLINE", gline->gl_user,
402             GlineIsBadChan(gline) ? "" : "@",
403             GlineIsBadChan(gline) ? "" : gline->gl_host,
404             gline->gl_expire + TSoffset, gline->gl_reason);
405
406   if (!(flags & GLINE_LOCAL)) /* don't propagate local changes */
407     gline_propagate(cptr, sptr, gline);
408
409   /* if it's a local gline or a Uworld gline (and not locally deactivated).. */
410   if (GlineIsLocal(gline) || (!gline->gl_lastmod && !(flags & GLINE_LOCAL)))
411     gline_free(gline); /* get rid of it */
412
413   return 0;
414 }
415
416 struct Gline *
417 gline_find(char *userhost, unsigned int flags)
418 {
419   struct Gline *gline;
420   struct Gline *sgline;
421   char *user, *host, *t_uh;
422
423   if (flags & (GLINE_BADCHAN | GLINE_ANY)) {
424     for (gline = BadChanGlineList; gline; gline = sgline) {
425       sgline = gline->gl_next;
426
427       if (gline->gl_expire <= CurrentTime)
428         gline_free(gline);
429       else if ((flags & GLINE_GLOBAL && gline->gl_flags & GLINE_LOCAL) ||
430                (flags & GLINE_LASTMOD && !gline->gl_lastmod))
431         continue;
432       else if ((flags & GLINE_EXACT ? ircd_strcmp(gline->gl_user, userhost) :
433                 match(gline->gl_user, userhost)) == 0)
434         return gline;
435     }
436   }
437
438   if ((flags & (GLINE_BADCHAN | GLINE_ANY)) == GLINE_BADCHAN ||
439       *userhost == '#' || *userhost == '&' || *userhost == '+'
440 #ifndef NO_OLD_GLINE
441       || userhost[2] == '#' || userhost[2] == '&' || userhost[2] == '+'
442 #endif /* NO_OLD_GLINE */
443       )
444     return 0;
445
446   DupString(t_uh, userhost);
447   canon_userhost(t_uh, &user, &host, 0);
448
449   for (gline = GlobalGlineList; gline; gline = sgline) {
450     sgline = gline->gl_next;
451
452     if (gline->gl_expire <= CurrentTime)
453       gline_free(gline);
454     else if ((flags & GLINE_GLOBAL && gline->gl_flags & GLINE_LOCAL) ||
455              (flags & GLINE_LASTMOD && !gline->gl_lastmod))
456       continue;
457     else if (flags & GLINE_EXACT) {
458       if (ircd_strcmp(gline->gl_host, host) == 0 &&
459           ((!user && ircd_strcmp(gline->gl_user, "*") == 0) ||
460            ircd_strcmp(gline->gl_user, user) == 0))
461         break;
462     } else {
463       if (match(gline->gl_host, host) == 0 &&
464           ((!user && ircd_strcmp(gline->gl_user, "*") == 0) ||
465            match(gline->gl_user, user) == 0))
466       break;
467     }
468   }
469
470   MyFree(t_uh);
471
472   return gline;
473 }
474
475 struct Gline *
476 gline_lookup(struct Client *cptr, unsigned int flags)
477 {
478   struct Gline *gline;
479   struct Gline *sgline;
480
481   for (gline = GlobalGlineList; gline; gline = sgline) {
482     sgline = gline->gl_next;
483
484     if (gline->gl_expire <= CurrentTime) {
485       gline_free(gline);
486       continue;
487     }
488     
489     if ((flags & GLINE_GLOBAL && gline->gl_flags & GLINE_LOCAL) ||
490              (flags & GLINE_LASTMOD && !gline->gl_lastmod))
491       continue;
492      
493     if (match(gline->gl_user, (cli_user(cptr))->username) != 0)
494       continue;
495          
496     if (GlineIsIpMask(gline)) {
497       Debug((DEBUG_DEBUG,"IP gline: %08x %08x/%i",(cli_ip(cptr)).s_addr,gline->ipnum.s_addr,gline->bits));
498       if (((cli_ip(cptr)).s_addr & NETMASK(gline->bits)) != gline->ipnum.s_addr)
499         continue;
500     }
501     else {
502       if (match(gline->gl_host, (cli_user(cptr))->host) != 0) 
503         continue;
504     }
505     return gline;
506   }
507   /*
508    * No Glines matched
509    */
510   return 0;
511 }
512
513 void
514 gline_free(struct Gline *gline)
515 {
516   assert(0 != gline);
517
518   *gline->gl_prev_p = gline->gl_next; /* squeeze this gline out */
519   if (gline->gl_next)
520     gline->gl_next->gl_prev_p = gline->gl_prev_p;
521
522   MyFree(gline->gl_user); /* free up the memory */
523   if (gline->gl_host)
524     MyFree(gline->gl_host);
525   MyFree(gline->gl_reason);
526   MyFree(gline);
527 }
528
529 void
530 gline_burst(struct Client *cptr)
531 {
532   struct Gline *gline;
533   struct Gline *sgline;
534
535   for (gline = GlobalGlineList; gline; gline = sgline) { /* all glines */
536     sgline = gline->gl_next;
537
538     if (gline->gl_expire <= CurrentTime) /* expire any that need expiring */
539       gline_free(gline);
540     else if (!GlineIsLocal(gline) && gline->gl_lastmod)
541       sendcmdto_one(&me, CMD_GLINE, cptr, "* %c%s@%s %Tu %Tu :%s",
542                     GlineIsRemActive(gline) ? '+' : '-', gline->gl_user,
543                     gline->gl_host, gline->gl_expire - CurrentTime,
544                     gline->gl_lastmod, gline->gl_reason);
545   }
546
547   for (gline = BadChanGlineList; gline; gline = sgline) { /* all glines */
548     sgline = gline->gl_next;
549
550     if (gline->gl_expire <= CurrentTime) /* expire any that need expiring */
551       gline_free(gline);
552     else if (!GlineIsLocal(gline) && gline->gl_lastmod)
553       sendcmdto_one(&me, CMD_GLINE, cptr, "* %c%s %Tu %Tu :%s",
554                     GlineIsRemActive(gline) ? '+' : '-', gline->gl_user,
555                     gline->gl_expire - CurrentTime, gline->gl_lastmod,
556                     gline->gl_reason);
557   }
558 }
559
560 int
561 gline_resend(struct Client *cptr, struct Gline *gline)
562 {
563   if (GlineIsLocal(gline) || !gline->gl_lastmod)
564     return 0;
565
566   sendcmdto_one(&me, CMD_GLINE, cptr, "* %c%s%s%s %Tu %Tu :%s",
567                 GlineIsRemActive(gline) ? '+' : '-', gline->gl_user,
568                 GlineIsBadChan(gline) ? "" : "@",
569                 GlineIsBadChan(gline) ? "" : gline->gl_host,
570                 gline->gl_expire - CurrentTime, gline->gl_lastmod,
571                 gline->gl_reason);
572
573   return 0;
574 }
575
576 int
577 gline_list(struct Client *sptr, char *userhost)
578 {
579   struct Gline *gline;
580   struct Gline *sgline;
581
582   if (userhost) {
583     if (!(gline = gline_find(userhost, GLINE_ANY))) /* no such gline */
584       return send_reply(sptr, ERR_NOSUCHGLINE, userhost);
585
586     /* send gline information along */
587     send_reply(sptr, RPL_GLIST, gline->gl_user,
588                GlineIsBadChan(gline) ? "" : "@",
589                GlineIsBadChan(gline) ? "" : gline->gl_host,
590                gline->gl_expire + TSoffset,
591                GlineIsLocal(gline) ? cli_name(&me) : "*",
592                GlineIsActive(gline) ? '+' : '-', gline->gl_reason);
593   } else {
594     for (gline = GlobalGlineList; gline; gline = sgline) {
595       sgline = gline->gl_next;
596
597       if (gline->gl_expire <= CurrentTime)
598         gline_free(gline);
599       else
600         send_reply(sptr, RPL_GLIST, gline->gl_user, "@", gline->gl_host,
601                    gline->gl_expire + TSoffset,
602                    GlineIsLocal(gline) ? cli_name(&me) : "*",
603                    GlineIsActive(gline) ? '+' : '-', gline->gl_reason);
604     }
605
606     for (gline = BadChanGlineList; gline; gline = sgline) {
607       sgline = gline->gl_next;
608
609       if (gline->gl_expire <= CurrentTime)
610         gline_free(gline);
611       else
612         send_reply(sptr, RPL_GLIST, gline->gl_user, "", "",
613                    gline->gl_expire + TSoffset,
614                    GlineIsLocal(gline) ? cli_name(&me) : "*",
615                    GlineIsActive(gline) ? '+' : '-', gline->gl_reason);
616     }
617   }
618
619   /* end of gline information */
620   return send_reply(sptr, RPL_ENDOFGLIST);
621 }
622
623 void
624 gline_stats(struct Client *sptr)
625 {
626   struct Gline *gline;
627   struct Gline *sgline;
628
629   for (gline = GlobalGlineList; gline; gline = sgline) {
630     sgline = gline->gl_next;
631
632     if (gline->gl_expire <= CurrentTime)
633       gline_free(gline);
634     else
635       send_reply(sptr, RPL_STATSGLINE, 'G', gline->gl_user, gline->gl_host,
636                  gline->gl_expire + TSoffset, gline->gl_reason);
637   }
638 }