Author: Entrope <mdpoole@troilus.org>
[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 2, 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 /** @file
21  * @brief Implementation of Gline manipulation functions.
22  * @version $Id$
23  */
24 #include "config.h"
25
26 #include "gline.h"
27 #include "client.h"
28 #include "ircd.h"
29 #include "ircd_alloc.h"
30 #include "ircd_features.h"
31 #include "ircd_log.h"
32 #include "ircd_reply.h"
33 #include "ircd_snprintf.h"
34 #include "ircd_string.h"
35 #include "match.h"
36 #include "numeric.h"
37 #include "s_bsd.h"
38 #include "s_debug.h"
39 #include "s_misc.h"
40 #include "s_stats.h"
41 #include "send.h"
42 #include "struct.h"
43 #include "sys.h"
44 #include "msg.h"
45 #include "numnicks.h"
46 #include "numeric.h"
47
48 /* #include <assert.h> -- Now using assert in ircd_log.h */
49 #include <string.h>
50 #include <stdio.h>
51 #include <stdlib.h>
52
53 #define CHECK_APPROVED     0    /**< Mask is acceptable */
54 #define CHECK_OVERRIDABLE  1    /**< Mask is acceptable, but not by default */
55 #define CHECK_REJECTED     2    /**< Mask is totally unacceptable */
56
57 #define MASK_WILD_0     0x01    /**< Wildcards in the last position */
58 #define MASK_WILD_1     0x02    /**< Wildcards in the next-to-last position */
59
60 #define MASK_WILD_MASK  0x03    /**< Mask out the positional wildcards */
61
62 #define MASK_WILDS      0x10    /**< Mask contains wildcards */
63 #define MASK_IP         0x20    /**< Mask is an IP address */
64 #define MASK_HALT       0x40    /**< Finished processing mask */
65
66 /** List of user G-lines. */
67 struct Gline* GlobalGlineList  = 0;
68 /** List of BadChan G-lines. */
69 struct Gline* BadChanGlineList = 0;
70
71 /** Iterate through \a list of G-lines.  Use this like a for loop,
72  * i.e., follow it with braces and use whatever you passed as \a gl
73  * as a single G-line to be acted upon.
74  *
75  * @param[in] list List of G-lines to iterate over.
76  * @param[in] gl Name of a struct Gline pointer variable that will be made to point to the G-lines in sequence.
77  * @param[in] next Name of a scratch struct Gline pointer variable.
78  */
79 /* There is some subtlety here with the boolean operators:
80  * (x || 1) is used to continue in a logical-and series even when !x.
81  * (x && 0) is used to continue in a logical-or series even when x.
82  */
83 #define gliter(list, gl, next)                          \
84   /* Iterate through the G-lines in the list */         \
85   for ((gl) = (list); (gl); (gl) = (next))              \
86     /* Figure out the next pointer in list... */        \
87     if ((((next) = (gl)->gl_next) || 1) &&              \
88         /* Then see if it's expired */                  \
89         (gl)->gl_lifetime <= CurrentTime)               \
90       /* Record has expired, so free the G-line */      \
91       gline_free((gl));                                 \
92     /* See if we need to expire the G-line */           \
93     else if ((((gl)->gl_expire > CurrentTime) ||        \
94               (((gl)->gl_flags &= ~GLINE_ACTIVE) && 0) ||       \
95               ((gl)->gl_state = GLOCAL_GLOBAL)) && 0)   \
96       ; /* empty statement */                           \
97     else
98
99 /** Find canonical user and host for a string.
100  * If \a userhost starts with '$', assign \a userhost to *user_p and NULL to *host_p.
101  * Otherwise, if \a userhost contains '@', assign the earlier part of it to *user_p and the rest to *host_p.
102  * Otherwise, assign \a def_user to *user_p and \a userhost to *host_p.
103  *
104  * @param[in] userhost Input string from user.
105  * @param[out] user_p Gets pointer to user (or channel/realname) part of hostmask.
106  * @param[out] host_p Gets point to host part of hostmask (may be assigned NULL).
107  * @param[in] def_user Default value for user part.
108  */
109 static void
110 canon_userhost(char *userhost, char **user_p, char **host_p, char *def_user)
111 {
112   char *tmp;
113
114   if (*userhost == '$') {
115     *user_p = userhost;
116     *host_p = NULL;
117     return;
118   }
119
120   if (!(tmp = strchr(userhost, '@'))) {
121     *user_p = def_user;
122     *host_p = userhost;
123   } else {
124     *user_p = userhost;
125     *(tmp++) = '\0';
126     *host_p = tmp;
127   }
128 }
129
130 /** Create a Gline structure.
131  * @param[in] user User part of mask.
132  * @param[in] host Host part of mask (NULL if not applicable).
133  * @param[in] reason Reason for G-line.
134  * @param[in] expire Expiration timestamp.
135  * @param[in] lastmod Last modification timestamp.
136  * @param[in] flags Bitwise combination of GLINE_* bits.
137  * @return Newly allocated G-line.
138  */
139 static struct Gline *
140 make_gline(char *user, char *host, char *reason, time_t expire, time_t lastmod,
141            time_t lifetime, unsigned int flags)
142 {
143   struct Gline *gline;
144
145   assert(0 != expire);
146
147   gline = (struct Gline *)MyMalloc(sizeof(struct Gline)); /* alloc memory */
148   assert(0 != gline);
149
150   DupString(gline->gl_reason, reason); /* initialize gline... */
151   gline->gl_expire = expire;
152   gline->gl_lifetime = lifetime;
153   gline->gl_lastmod = lastmod;
154   gline->gl_flags = flags & GLINE_MASK;
155   gline->gl_state = GLOCAL_GLOBAL; /* not locally modified */
156
157   if (flags & GLINE_BADCHAN) { /* set a BADCHAN gline */
158     DupString(gline->gl_user, user); /* first, remember channel */
159     gline->gl_host = NULL;
160
161     gline->gl_next = BadChanGlineList; /* then link it into list */
162     gline->gl_prev_p = &BadChanGlineList;
163     if (BadChanGlineList)
164       BadChanGlineList->gl_prev_p = &gline->gl_next;
165     BadChanGlineList = gline;
166   } else {
167     DupString(gline->gl_user, user); /* remember them... */
168     if (*user != '$')
169       DupString(gline->gl_host, host);
170     else
171       gline->gl_host = NULL;
172
173     if (*user != '$' && ipmask_parse(host, &gline->gl_addr, &gline->gl_bits))
174       gline->gl_flags |= GLINE_IPMASK;
175
176     gline->gl_next = GlobalGlineList; /* then link it into list */
177     gline->gl_prev_p = &GlobalGlineList;
178     if (GlobalGlineList)
179       GlobalGlineList->gl_prev_p = &gline->gl_next;
180     GlobalGlineList = gline;
181   }
182
183   return gline;
184 }
185
186 /** Check local clients against a new G-line.
187  * If the G-line is inactive or a badchan, return immediately.
188  * Otherwise, if any users match it, disconnect them.
189  * @param[in] cptr Peer connect that sent the G-line.
190  * @param[in] sptr Client that originated the G-line.
191  * @param[in] gline New G-line to check.
192  * @return Zero, unless \a sptr G-lined himself, in which case CPTR_KILLED.
193  */
194 static int
195 do_gline(struct Client *cptr, struct Client *sptr, struct Gline *gline)
196 {
197   struct Client *acptr;
198   int fd, retval = 0, tval;
199
200   if (feature_bool(FEAT_DISABLE_GLINES))
201     return 0; /* G-lines are disabled */
202
203   if (GlineIsBadChan(gline)) /* no action taken on badchan glines */
204     return 0;
205   if (!GlineIsActive(gline)) /* no action taken on inactive glines */
206     return 0;
207
208   for (fd = HighestFd; fd >= 0; --fd) {
209     /*
210      * get the users!
211      */
212     if ((acptr = LocalClientArray[fd])) {
213       if (!cli_user(acptr))
214         continue;
215
216       if (GlineIsRealName(gline)) { /* Realname Gline */
217         Debug((DEBUG_DEBUG,"Realname Gline: %s %s",(cli_info(acptr)),
218                                         gline->gl_user+2));
219         if (match(gline->gl_user+2, cli_info(acptr)) != 0)
220             continue;
221         Debug((DEBUG_DEBUG,"Matched!"));
222       } else { /* Host/IP gline */
223         if (cli_user(acptr)->username &&
224             match(gline->gl_user, (cli_user(acptr))->username) != 0)
225           continue;
226
227         if (GlineIsIpMask(gline)) {
228           if (!ipmask_check(&cli_ip(acptr), &gline->gl_addr, gline->gl_bits))
229             continue;
230         }
231         else {
232           if (match(gline->gl_host, cli_sockhost(acptr)) != 0)
233             continue;
234         }
235       }
236
237       /* ok, here's one that got G-lined */
238       send_reply(acptr, SND_EXPLICIT | ERR_YOUREBANNEDCREEP, ":%s",
239            gline->gl_reason);
240
241       /* let the ops know about it */
242       sendto_opmask_butone(0, SNO_GLINE, "G-line active for %s",
243                            get_client_name(acptr, SHOW_IP));
244
245       /* and get rid of him */
246       if ((tval = exit_client_msg(cptr, acptr, &me, "G-lined (%s)",
247           gline->gl_reason)))
248         retval = tval; /* retain killed status */
249     }
250   }
251   return retval;
252 }
253
254 /**
255  * Implements the mask checking applied to local G-lines.
256  * Basically, host masks must have a minimum of two non-wild domain
257  * fields, and IP masks must have a minimum of 16 bits.  If the mask
258  * has even one wild-card, OVERRIDABLE is returned, assuming the other
259  * check doesn't fail.
260  * @param[in] mask G-line mask to check.
261  * @return One of CHECK_REJECTED, CHECK_OVERRIDABLE, or CHECK_APPROVED.
262  */
263 static int
264 gline_checkmask(char *mask)
265 {
266   unsigned int flags = MASK_IP;
267   unsigned int dots = 0;
268   unsigned int ipmask = 0;
269
270   for (; *mask; mask++) { /* go through given mask */
271     if (*mask == '.') { /* it's a separator; advance positional wilds */
272       flags = (flags & ~MASK_WILD_MASK) | ((flags << 1) & MASK_WILD_MASK);
273       dots++;
274
275       if ((flags & (MASK_IP | MASK_WILDS)) == MASK_IP)
276         ipmask += 8; /* It's an IP with no wilds, count bits */
277     } else if (*mask == '*' || *mask == '?')
278       flags |= MASK_WILD_0 | MASK_WILDS; /* found a wildcard */
279     else if (*mask == '/') { /* n.n.n.n/n notation; parse bit specifier */
280       ++mask;
281       ipmask = strtoul(mask, &mask, 10);
282
283       /* sanity-check to date */
284       if (*mask || (flags & (MASK_WILDS | MASK_IP)) != MASK_IP)
285         return CHECK_REJECTED;
286       if (!dots) {
287         if (ipmask > 128)
288           return CHECK_REJECTED;
289         if (ipmask < 128)
290           flags |= MASK_WILDS;
291       } else {
292         if (dots != 3 || ipmask > 32)
293           return CHECK_REJECTED;
294         if (ipmask < 32)
295           flags |= MASK_WILDS;
296       }
297
298       flags |= MASK_HALT; /* Halt the ipmask calculation */
299       break; /* get out of the loop */
300     } else if (!IsIP6Char(*mask)) {
301       flags &= ~MASK_IP; /* not an IP anymore! */
302       ipmask = 0;
303     }
304   }
305
306   /* Sanity-check quads */
307   if (dots > 3 || (!(flags & MASK_WILDS) && dots < 3)) {
308     flags &= ~MASK_IP;
309     ipmask = 0;
310   }
311
312   /* update bit count if necessary */
313   if ((flags & (MASK_IP | MASK_WILDS | MASK_HALT)) == MASK_IP)
314     ipmask += 8;
315
316   /* Check to see that it's not too wide of a mask */
317   if (flags & MASK_WILDS &&
318       ((!(flags & MASK_IP) && (dots < 2 || flags & MASK_WILD_MASK)) ||
319        (flags & MASK_IP && ipmask < 16)))
320     return CHECK_REJECTED; /* to wide, reject */
321
322   /* Ok, it's approved; require override if it has wildcards, though */
323   return flags & MASK_WILDS ? CHECK_OVERRIDABLE : CHECK_APPROVED;
324 }
325
326 /** Forward a G-line to other servers.
327  * @param[in] cptr Client that sent us the G-line.
328  * @param[in] sptr Client that originated the G-line.
329  * @param[in] gline G-line to forward.
330  * @return Zero.
331  */
332 static int
333 gline_propagate(struct Client *cptr, struct Client *sptr, struct Gline *gline)
334 {
335   if (GlineIsLocal(gline))
336     return 0;
337
338   assert(gline->gl_lastmod);
339
340   sendcmdto_serv_butone(sptr, CMD_GLINE, cptr, "* %c%s%s%s %Tu %Tu %Tu :%s",
341                         GlineIsRemActive(gline) ? '+' : '-', gline->gl_user,
342                         gline->gl_host ? "@" : "",
343                         gline->gl_host ? gline->gl_host : "",
344                         gline->gl_expire - CurrentTime, gline->gl_lastmod,
345                         gline->gl_lifetime, gline->gl_reason);
346
347   return 0;
348 }
349
350 /** Count number of users who match \a mask.
351  * @param[in] mask user\@host or user\@ip mask to check.
352  * @return Count of matching users.
353  */
354 static int
355 count_users(char *mask)
356 {
357   struct irc_in_addr ipmask;
358   struct Client *acptr;
359   int count = 0;
360   int ipmask_valid;
361   char namebuf[USERLEN + HOSTLEN + 2];
362   char ipbuf[USERLEN + SOCKIPLEN + 2];
363   unsigned char ipmask_len;
364
365   ipmask_valid = ipmask_parse(mask, &ipmask, &ipmask_len);
366   for (acptr = GlobalClientList; acptr; acptr = cli_next(acptr)) {
367     if (!IsUser(acptr))
368       continue;
369
370     ircd_snprintf(0, namebuf, sizeof(namebuf), "%s@%s",
371                   cli_user(acptr)->username, cli_user(acptr)->host);
372     ircd_snprintf(0, ipbuf, sizeof(ipbuf), "%s@%s", cli_user(acptr)->username,
373                   ircd_ntoa(&cli_ip(acptr)));
374
375     if (!match(mask, namebuf)
376         || !match(mask, ipbuf)
377         || (ipmask_valid && ipmask_check(&cli_ip(acptr), &ipmask, ipmask_len)))
378       count++;
379   }
380
381   return count;
382 }
383
384 /** Count number of users with a realname matching \a mask.
385  * @param[in] mask Wildcard mask to match against realnames.
386  * @return Count of matching users.
387  */
388 static int
389 count_realnames(const char *mask)
390 {
391   struct Client *acptr;
392   int minlen;
393   int count;
394   char cmask[BUFSIZE];
395
396   count = 0;
397   matchcomp(cmask, &minlen, NULL, mask);
398   for (acptr = GlobalClientList; acptr; acptr = cli_next(acptr)) {
399     if (!IsUser(acptr))
400       continue;
401     if (strlen(cli_info(acptr)) < minlen)
402       continue;
403     if (!matchexec(cli_info(acptr), cmask, minlen))
404       count++;
405   }
406   return count;
407 }
408
409 /** Create a new G-line and add it to global lists.
410  * \a userhost may be in one of four forms:
411  * \li A channel name, to add a BadChan.
412  * \li A string starting with $R and followed by a mask to match against their realname.
413  * \li A user\@IP mask (user\@ part optional) to create an IP-based ban.
414  * \li A user\@host mask (user\@ part optional) to create a hostname ban.
415  *
416  * @param[in] cptr Client that sent us the G-line.
417  * @param[in] sptr Client that originated the G-line.
418  * @param[in] userhost Text mask for the G-line.
419  * @param[in] reason Reason for G-line.
420  * @param[in] expire Expiration time of G-line.
421  * @param[in] lastmod Last modification time of G-line.
422  * @param[in] lifetime Lifetime of G-line.
423  * @param[in] flags Bitwise combination of GLINE_* flags.
424  * @return Zero or CPTR_KILLED, depending on whether \a sptr is suicidal.
425  */
426 int
427 gline_add(struct Client *cptr, struct Client *sptr, char *userhost,
428           char *reason, time_t expire, time_t lastmod, time_t lifetime,
429           unsigned int flags)
430 {
431   struct Gline *agline;
432   char uhmask[USERLEN + HOSTLEN + 2];
433   char *user, *host;
434   int tmp;
435
436   assert(0 != userhost);
437   assert(0 != reason);
438   assert(((flags & (GLINE_GLOBAL | GLINE_LOCAL)) == GLINE_GLOBAL) ||
439          ((flags & (GLINE_GLOBAL | GLINE_LOCAL)) == GLINE_LOCAL));
440
441   Debug((DEBUG_DEBUG, "gline_add(\"%s\", \"%s\", \"%s\", \"%s\", %Tu, %Tu "
442          "%Tu, 0x%04x)", cli_name(cptr), cli_name(sptr), userhost, reason,
443          expire, lastmod, lifetime, flags));
444
445   if (*userhost == '#' || *userhost == '&') {
446     if ((flags & GLINE_LOCAL) && !HasPriv(sptr, PRIV_LOCAL_BADCHAN))
447       return send_reply(sptr, ERR_NOPRIVILEGES);
448
449     flags |= GLINE_BADCHAN;
450     user = userhost;
451     host = NULL;
452   } else if (*userhost == '$') {
453     switch (userhost[1]) {
454       case 'R': flags |= GLINE_REALNAME; break;
455       default:
456         /* uh, what to do here? */
457         /* The answer, my dear Watson, is we throw a protocol_violation()
458            -- hikari */
459         if (IsServer(cptr))
460           return protocol_violation(sptr,"%s has been smoking the sweet leaf and sent me a whacky gline",cli_name(sptr));
461         sendto_opmask_butone(NULL, SNO_GLINE, "%s has been smoking the sweet leaf and sent me a whacky gline", cli_name(sptr));
462         return 0;
463     }
464     user = userhost;
465     host = NULL;
466     if (MyUser(sptr) || (IsUser(sptr) && flags & GLINE_LOCAL)) {
467       tmp = count_realnames(userhost + 2);
468       if ((tmp >= feature_int(FEAT_GLINEMAXUSERCOUNT))
469           && !(flags & GLINE_OPERFORCE))
470         return send_reply(sptr, ERR_TOOMANYUSERS, tmp);
471     }
472   } else {
473     canon_userhost(userhost, &user, &host, "*");
474     if (sizeof(uhmask) <
475         ircd_snprintf(0, uhmask, sizeof(uhmask), "%s@%s", user, host))
476       return send_reply(sptr, ERR_LONGMASK);
477     else if (MyUser(sptr) || (IsUser(sptr) && flags & GLINE_LOCAL)) {
478       switch (gline_checkmask(host)) {
479       case CHECK_OVERRIDABLE: /* oper overrided restriction */
480         if (flags & GLINE_OPERFORCE)
481           break;
482         /*FALLTHROUGH*/
483       case CHECK_REJECTED:
484         return send_reply(sptr, ERR_MASKTOOWIDE, uhmask);
485         break;
486       }
487
488       if ((tmp = count_users(uhmask)) >=
489           feature_int(FEAT_GLINEMAXUSERCOUNT) && !(flags & GLINE_OPERFORCE))
490         return send_reply(sptr, ERR_TOOMANYUSERS, tmp);
491     }
492   }
493
494   /*
495    * You cannot set a negative (or zero) expire time, nor can you set an
496    * expiration time for greater than GLINE_MAX_EXPIRE.
497    */
498   if (!(flags & GLINE_FORCE) &&
499       (expire <= CurrentTime || expire > CurrentTime + GLINE_MAX_EXPIRE)) {
500     if (!IsServer(sptr) && MyConnect(sptr))
501       send_reply(sptr, ERR_BADEXPIRE, expire);
502     return 0;
503   }
504
505   if (!lifetime) /* no lifetime set, use expiration time */
506     lifetime = expire;
507
508   /* lifetime is already an absolute timestamp */
509
510   /* Inform ops... */
511   sendto_opmask_butone(0, ircd_strncmp(reason, "AUTO", 4) ? SNO_GLINE :
512                        SNO_AUTO, "%s adding %s %s for %s%s%s, expiring at "
513                        "%Tu: %s",
514                        (feature_bool(FEAT_HIS_SNOTICES) || IsServer(sptr)) ?
515                          cli_name(sptr) :
516                          cli_name((cli_user(sptr))->server),
517                        (flags & GLINE_LOCAL) ? "local" : "global",
518                        (flags & GLINE_BADCHAN) ? "BADCHAN" : "GLINE", user,
519                        (flags & (GLINE_BADCHAN|GLINE_REALNAME)) ? "" : "@",
520                        (flags & (GLINE_BADCHAN|GLINE_REALNAME)) ? "" : host,
521                        expire + TSoffset, reason);
522
523   /* and log it */
524   log_write(LS_GLINE, L_INFO, LOG_NOSNOTICE,
525             "%#C adding %s %s for %s%s%s, expiring at %Tu: %s", sptr,
526             flags & GLINE_LOCAL ? "local" : "global",
527             flags & GLINE_BADCHAN ? "BADCHAN" : "GLINE", user,
528             flags & (GLINE_BADCHAN|GLINE_REALNAME) ? "" : "@",
529             flags & (GLINE_BADCHAN|GLINE_REALNAME) ? "" : host,
530             expire + TSoffset, reason);
531
532   /* make the gline */
533   agline = make_gline(user, host, reason, expire, lastmod, lifetime, flags);
534
535   /* since we've disabled overlapped G-line checking, agline should
536    * never be NULL...
537    */
538   assert(agline);
539
540   gline_propagate(cptr, sptr, agline);
541
542   return do_gline(cptr, sptr, agline); /* knock off users if necessary */
543 }
544
545 /** Activate a currently inactive G-line.
546  * @param[in] cptr Peer that told us to activate the G-line.
547  * @param[in] sptr Client that originally thought it was a good idea.
548  * @param[in] gline G-line to activate.
549  * @param[in] lastmod New value for last modification timestamp.
550  * @param[in] flags 0 if the activation should be propagated, GLINE_LOCAL if not.
551  * @return Zero, unless \a sptr had a death wish (in which case CPTR_KILLED).
552  */
553 int
554 gline_activate(struct Client *cptr, struct Client *sptr, struct Gline *gline,
555                time_t lastmod, unsigned int flags)
556 {
557   unsigned int saveflags = 0;
558
559   assert(0 != gline);
560
561   saveflags = gline->gl_flags;
562
563   if (flags & GLINE_LOCAL)
564     gline->gl_flags &= ~GLINE_LDEACT;
565   else {
566     gline->gl_flags |= GLINE_ACTIVE;
567
568     if (gline->gl_lastmod) {
569       if (gline->gl_lastmod >= lastmod) /* force lastmod to increase */
570         gline->gl_lastmod++;
571       else
572         gline->gl_lastmod = lastmod;
573     }
574   }
575
576   if ((saveflags & GLINE_ACTMASK) == GLINE_ACTIVE)
577     return 0; /* was active to begin with */
578
579   /* Inform ops and log it */
580   sendto_opmask_butone(0, SNO_GLINE, "%s activating global %s for %s%s%s, "
581                        "expiring at %Tu: %s",
582                        (feature_bool(FEAT_HIS_SNOTICES) || IsServer(sptr)) ?
583                          cli_name(sptr) :
584                          cli_name((cli_user(sptr))->server),
585                        GlineIsBadChan(gline) ? "BADCHAN" : "GLINE",
586                        gline->gl_user, gline->gl_host ? "@" : "",
587                        gline->gl_host ? gline->gl_host : "",
588                        gline->gl_expire + TSoffset, gline->gl_reason);
589   
590   log_write(LS_GLINE, L_INFO, LOG_NOSNOTICE,
591             "%#C activating global %s for %s%s%s, expiring at %Tu: %s", sptr,
592             GlineIsBadChan(gline) ? "BADCHAN" : "GLINE", gline->gl_user,
593             gline->gl_host ? "@" : "",
594             gline->gl_host ? gline->gl_host : "",
595             gline->gl_expire + TSoffset, gline->gl_reason);
596
597   if (!(flags & GLINE_LOCAL)) /* don't propagate local changes */
598     gline_propagate(cptr, sptr, gline);
599
600   return do_gline(cptr, sptr, gline);
601 }
602
603 /** Deactivate a G-line.
604  * @param[in] cptr Peer that gave us the message.
605  * @param[in] sptr Client that initiated the deactivation.
606  * @param[in] gline G-line to deactivate.
607  * @param[in] lastmod New value for G-line last modification timestamp.
608  * @param[in] flags GLINE_LOCAL to only deactivate locally, 0 to propagate.
609  * @return Zero.
610  */
611 int
612 gline_deactivate(struct Client *cptr, struct Client *sptr, struct Gline *gline,
613                  time_t lastmod, unsigned int flags)
614 {
615   unsigned int saveflags = 0;
616   char *msg;
617
618   assert(0 != gline);
619
620   saveflags = gline->gl_flags;
621
622   if (GlineIsLocal(gline))
623     msg = "removing local";
624   else if (!gline->gl_lastmod && !(flags & GLINE_LOCAL)) {
625     msg = "removing global";
626     gline->gl_flags &= ~GLINE_ACTIVE; /* propagate a -<mask> */
627   } else {
628     msg = "deactivating global";
629
630     if (flags & GLINE_LOCAL)
631       gline->gl_flags |= GLINE_LDEACT;
632     else {
633       gline->gl_flags &= ~GLINE_ACTIVE;
634
635       if (gline->gl_lastmod) {
636         if (gline->gl_lastmod >= lastmod)
637           gline->gl_lastmod++;
638         else
639           gline->gl_lastmod = lastmod;
640       }
641     }
642
643     if ((saveflags & GLINE_ACTMASK) != GLINE_ACTIVE)
644       return 0; /* was inactive to begin with */
645   }
646
647   /* Inform ops and log it */
648   sendto_opmask_butone(0, SNO_GLINE, "%s %s %s for %s%s%s, expiring at %Tu: "
649                        "%s",
650                        (feature_bool(FEAT_HIS_SNOTICES) || IsServer(sptr)) ?
651                          cli_name(sptr) :
652                          cli_name((cli_user(sptr))->server),
653                        msg, GlineIsBadChan(gline) ? "BADCHAN" : "GLINE",
654                        gline->gl_user, gline->gl_host ? "@" : "",
655                        gline->gl_host ? gline->gl_host : "",
656                        gline->gl_expire + TSoffset, gline->gl_reason);
657
658   log_write(LS_GLINE, L_INFO, LOG_NOSNOTICE,
659             "%#C %s %s for %s%s%s, expiring at %Tu: %s", sptr, msg,
660             GlineIsBadChan(gline) ? "BADCHAN" : "GLINE", gline->gl_user,
661             gline->gl_host ? "@" : "",
662             gline->gl_host ? gline->gl_host : "",
663             gline->gl_expire + TSoffset, gline->gl_reason);
664
665   if (!(flags & GLINE_LOCAL)) /* don't propagate local changes */
666     gline_propagate(cptr, sptr, gline);
667
668   /* if it's a local gline or a Uworld gline (and not locally deactivated).. */
669   if (GlineIsLocal(gline) || (!gline->gl_lastmod && !(flags & GLINE_LOCAL)))
670     gline_free(gline); /* get rid of it */
671
672   return 0;
673 }
674
675 /** Send a deactivation request for a locally unknown G-line.
676  * @param[in] cptr Client that sent us the G-line modification.
677  * @param[in] sptr Client that originated the G-line modification.
678  * @param[in] userhost Text representation of G-line target.
679  * @param[in] expire Expiration time of G-line.
680  * @param[in] lastmod Last modification time of G-line.
681  * @param[in] lifetime Lifetime of G-line.
682  * @param[in] flags Bitwise combination of GLINE_* flags.
683  * @return Zero.
684  */
685 int
686 gline_forward_deactivation(struct Client *cptr, struct Client *sptr,
687                            char *userhost, time_t expire, time_t lastmod,
688                            time_t lifetime, unsigned int flags)
689 {
690   char *msg = "deactivating unknown global";
691
692   if (!lifetime)
693     lifetime = expire;
694
695   /* Inform ops and log it */
696   sendto_opmask_butone(0, SNO_GLINE, "%s %s GLINE for %s, expiring at %Tu",
697                        (feature_bool(FEAT_HIS_SNOTICES) || IsServer(sptr)) ?
698                          cli_name(sptr) : cli_name((cli_user(sptr))->server),
699                        msg, userhost, expire + TSoffset);
700
701   log_write(LS_GLINE, L_INFO, LOG_NOSNOTICE,
702             "%#C %s GLINE for %s, expiring at %Tu", sptr, msg, userhost,
703             expire);
704
705   sendcmdto_serv_butone(sptr, CMD_GLINE, cptr, "* -%s %Tu %Tu %Tu",
706                         userhost, expire, lastmod, lifetime);
707
708   return 0;
709 }
710
711 /** Modify a global G-line.
712  * @param[in] cptr Client that sent us the G-line modification.
713  * @param[in] sptr Client that originated the G-line modification.
714  * @param[in] gline G-line being modified.
715  * @param[in] action Resultant status of the G-line.
716  * @param[in] reason Reason for G-line.
717  * @param[in] expire Expiration time of G-line.
718  * @param[in] lastmod Last modification time of G-line.
719  * @param[in] lifetime Lifetime of G-line.
720  * @param[in] flags Bitwise combination of GLINE_* flags.
721  * @return Zero or CPTR_KILLED, depending on whether \a sptr is suicidal.
722  */
723 int
724 gline_modify(struct Client *cptr, struct Client *sptr, struct Gline *gline,
725              enum GlineAction action, char *reason, time_t expire,
726              time_t lastmod, time_t lifetime, unsigned int flags)
727 {
728   char buf[BUFSIZE], *op = "";
729   int pos = 0;
730
731   assert(gline);
732   assert(!GlineIsLocal(gline));
733
734   Debug((DEBUG_DEBUG,  "gline_modify(\"%s\", \"%s\", \"%s%s%s\", %s, \"%s\", "
735          "%Tu, %Tu, %Tu, 0x%04x)", cli_name(cptr), cli_name(sptr),
736          gline->gl_user, gline->gl_host ? "@" : "",
737          gline->gl_host ? gline->gl_host : "",
738          action == GLINE_ACTIVATE ? "GLINE_ACTIVATE" :
739          (action == GLINE_DEACTIVATE ? "GLINE_DEACTIVATE" :
740           (action == GLINE_LOCAL_ACTIVATE ? "GLINE_LOCAL_ACTIVATE" :
741            (action == GLINE_LOCAL_DEACTIVATE ? "GLINE_LOCAL_DEACTIVATE" :
742             (action == GLINE_MODIFY ? "GLINE_MODIFY" : "<UNKNOWN>")))),
743          reason, expire, lastmod, lifetime, flags));
744
745   /* First, let's check lastmod... */
746   if (action != GLINE_LOCAL_ACTIVATE && action != GLINE_LOCAL_DEACTIVATE) {
747     if (GlineLastMod(gline) > lastmod) { /* we have a more recent version */
748       if (IsBurstOrBurstAck(cptr))
749         return 0; /* middle of a burst, it'll resync on its own */
750       return gline_resend(cptr, gline); /* resync the server */
751     } else if (GlineLastMod(gline) == lastmod)
752       return 0; /* we have that version of the G-line... */
753   }
754
755   /* All right, we know that there's a change of some sort.  What is it? */
756   /* first, check out the expiration time... */
757   if ((flags & GLINE_EXPIRE) && expire) {
758     if (!(flags & GLINE_FORCE) &&
759         (expire <= CurrentTime || expire > CurrentTime + GLINE_MAX_EXPIRE)) {
760       if (!IsServer(sptr) && MyConnect(sptr)) /* bad expiration time */
761         send_reply(sptr, ERR_BADEXPIRE, expire);
762       return 0;
763     }
764   } else
765     flags &= ~GLINE_EXPIRE;
766
767   /* Now check to see if there's any change... */
768   if ((flags & GLINE_EXPIRE) && expire == gline->gl_expire) {
769     flags &= ~GLINE_EXPIRE; /* no change to expiration time... */
770     expire = 0;
771   }
772
773   /* Next, check out lifetime--this one's a bit trickier... */
774   if (!(flags & GLINE_LIFETIME) || !lifetime)
775     lifetime = gline->gl_lifetime; /* use G-line lifetime */
776
777   lifetime = IRCD_MAX(lifetime, expire); /* set lifetime to the max */
778
779   /* OK, let's see which is greater... */
780   if (lifetime > gline->gl_lifetime)
781     flags |= GLINE_LIFETIME; /* have to update lifetime */
782   else {
783     flags &= ~GLINE_LIFETIME; /* no change to lifetime */
784     lifetime = 0;
785   }
786
787   /* Finally, let's see if the reason needs to be updated */
788   if ((flags & GLINE_REASON) && reason &&
789       !ircd_strcmp(gline->gl_reason, reason))
790     flags &= ~GLINE_REASON; /* no changes to the reason */
791
792   /* OK, now let's take a look at the action... */
793   if ((action == GLINE_ACTIVATE && (gline->gl_flags & GLINE_ACTIVE)) ||
794       (action == GLINE_DEACTIVATE && !(gline->gl_flags & GLINE_ACTIVE)) ||
795       (action == GLINE_LOCAL_ACTIVATE &&
796        (gline->gl_state == GLOCAL_ACTIVATED)) ||
797       (action == GLINE_LOCAL_DEACTIVATE &&
798        (gline->gl_state == GLOCAL_DEACTIVATED)) ||
799       /* can't activate an expired G-line */
800       IRCD_MAX(gline->gl_expire, expire) <= CurrentTime)
801     action = GLINE_MODIFY; /* no activity state modifications */
802
803   Debug((DEBUG_DEBUG,  "About to perform changes; flags 0x%04x, action %s",
804          flags, action == GLINE_ACTIVATE ? "GLINE_ACTIVATE" :
805          (action == GLINE_DEACTIVATE ? "GLINE_DEACTIVATE" :
806           (action == GLINE_LOCAL_ACTIVATE ? "GLINE_LOCAL_ACTIVATE" :
807            (action == GLINE_LOCAL_DEACTIVATE ? "GLINE_LOCAL_DEACTIVATE" :
808             (action == GLINE_MODIFY ? "GLINE_MODIFY" : "<UNKNOWN>"))))));
809
810   /* If there are no changes to perform, do no changes */
811   if (!(flags & GLINE_UPDATE) && action == GLINE_MODIFY)
812     return 0;
813
814   /* Now we know what needs to be changed, so let's process the changes... */
815
816   /* Start by updating lastmod, if indicated... */
817   if (action != GLINE_LOCAL_ACTIVATE && action != GLINE_LOCAL_DEACTIVATE)
818     gline->gl_lastmod = lastmod;
819
820   /* Then move on to activity status changes... */
821   switch (action) {
822   case GLINE_ACTIVATE: /* Globally activating G-line */
823     gline->gl_flags |= GLINE_ACTIVE; /* make it active... */
824     gline->gl_state = GLOCAL_GLOBAL; /* reset local activity state */
825     pos += ircd_snprintf(0, buf, sizeof(buf), " globally activating G-line");
826     op = "+"; /* operation for G-line propagation */
827     break;
828
829   case GLINE_DEACTIVATE: /* Globally deactivating G-line */
830     gline->gl_flags &= ~GLINE_ACTIVE; /* make it inactive... */
831     gline->gl_state = GLOCAL_GLOBAL; /* reset local activity state */
832     pos += ircd_snprintf(0, buf, sizeof(buf), " globally deactivating G-line");
833     op = "-"; /* operation for G-line propagation */
834     break;
835
836   case GLINE_LOCAL_ACTIVATE: /* Locally activating G-line */
837     gline->gl_state = GLOCAL_ACTIVATED; /* make it locally active */
838     pos += ircd_snprintf(0, buf, sizeof(buf), " locally activating G-line");
839     break;
840
841   case GLINE_LOCAL_DEACTIVATE: /* Locally deactivating G-line */
842     gline->gl_state = GLOCAL_DEACTIVATED; /* make it locally inactive */
843     pos += ircd_snprintf(0, buf, sizeof(buf), " locally deactivating G-line");
844     break;
845
846   case GLINE_MODIFY: /* no change to activity status */
847     break;
848   }
849
850   /* Handle expiration changes... */
851   if (flags & GLINE_EXPIRE) {
852     gline->gl_expire = expire; /* save new expiration time */
853     if (pos < BUFSIZE)
854       pos += ircd_snprintf(0, buf + pos, sizeof(buf) - pos,
855                            "%s%s changing expiration time to %Tu",
856                            pos ? ";" : "",
857                            pos && !(flags & (GLINE_LIFETIME | GLINE_REASON)) ?
858                            " and" : "", expire);
859   }
860
861   /* Next, handle lifetime changes... */
862   if (flags & GLINE_LIFETIME) {
863     gline->gl_lifetime = lifetime; /* save new lifetime */
864     if (pos < BUFSIZE)
865       pos += ircd_snprintf(0, buf + pos, sizeof(buf) - pos,
866                            "%s%s extending record lifetime to %Tu",
867                            pos ? ";" : "", pos && !(flags & GLINE_REASON) ?
868                            " and" : "", lifetime);
869   }
870
871   /* Now, handle reason changes... */
872   if (flags & GLINE_REASON) {
873     MyFree(gline->gl_reason); /* release old reason */
874     DupString(gline->gl_reason, reason); /* store new reason */
875     if (pos < BUFSIZE)
876       pos += ircd_snprintf(0, buf + pos, sizeof(buf) - pos,
877                            "%s%s changing reason to \"%s\"",
878                            pos ? ";" : "", pos ? " and" : "", reason);
879   }
880
881   /* All right, inform ops... */
882   sendto_opmask_butone(0, SNO_GLINE, "%s modifying global %s for %s%s%s:%s",
883                        (feature_bool(FEAT_HIS_SNOTICES) || IsServer(sptr)) ?
884                        cli_name(sptr) : cli_name((cli_user(sptr))->server),
885                        GlineIsBadChan(gline) ? "BADCHAN" : "GLINE",
886                        gline->gl_user, gline->gl_host ? "@" : "",
887                        gline->gl_host ? gline->gl_host : "", buf);
888
889   /* and log the change */
890   log_write(LS_GLINE, L_INFO, LOG_NOSNOTICE,
891             "%#C modifying global %s for %s%s%s:%s", sptr,
892             GlineIsBadChan(gline) ? "BADCHAN" : "GLINE", gline->gl_user,
893             gline->gl_host ? "@" : "", gline->gl_host ? gline->gl_host : "",
894             buf);
895
896   /* We'll be simple for this release, but we can update this to change
897    * the propagation syntax on future updates
898    */
899   if (action != GLINE_LOCAL_ACTIVATE && action != GLINE_LOCAL_DEACTIVATE)
900     sendcmdto_serv_butone(sptr, CMD_GLINE, cptr,
901                           "* %s%s%s%s%s %Tu %Tu %Tu :%s",
902                           flags & GLINE_OPERFORCE ? "!" : "", op,
903                           gline->gl_user, gline->gl_host ? "@" : "",
904                           gline->gl_host ? gline->gl_host : "",
905                           gline->gl_expire - CurrentTime, gline->gl_lastmod,
906                           gline->gl_lifetime, gline->gl_reason);
907
908   /* OK, let's do the G-line... */
909   return do_gline(cptr, sptr, gline);
910 }
911
912 /** Destroy a local G-line.
913  * @param[in] cptr Peer that gave us the message.
914  * @param[in] sptr Client that initiated the destruction.
915  * @param[in] gline G-line to destroy.
916  * @return Zero.
917  */
918 int
919 gline_destroy(struct Client *cptr, struct Client *sptr, struct Gline *gline)
920 {
921   assert(gline);
922   assert(GlineIsLocal(gline));
923
924   /* Inform ops and log it */
925   sendto_opmask_butone(0, SNO_GLINE, "%s removing local %s for %s%s%s",
926                        (feature_bool(FEAT_HIS_SNOTICES) || IsServer(sptr)) ?
927                        cli_name(sptr) : cli_name((cli_user(sptr))->server),
928                        GlineIsBadChan(gline) ? "BADCHAN" : "GLINE",
929                        gline->gl_user, gline->gl_host ? "@" : "",
930                        gline->gl_host ? gline->gl_host : "");
931   log_write(LS_GLINE, L_INFO, LOG_NOSNOTICE,
932             "%#C removing local %s for %s%s%s", sptr,
933             GlineIsBadChan(gline) ? "BADCHAN" : "GLINE", gline->gl_user,
934             gline->gl_host ? "@" : "", gline->gl_host ? gline->gl_host : "");
935
936   gline_free(gline); /* get rid of the G-line */
937
938   return 0; /* convenience return */
939 }
940
941 /** Find a G-line for a particular mask, guided by certain flags.
942  * Certain bits in \a flags are interpreted specially:
943  * <dl>
944  * <dt>GLINE_ANY</dt><dd>Search both BadChans and user G-lines.</dd>
945  * <dt>GLINE_BADCHAN</dt><dd>Search BadChans.</dd>
946  * <dt>GLINE_GLOBAL</dt><dd>Only match global G-lines.</dd>
947  * <dt>GLINE_LOCAL</dt><dd>Only match local G-lines.</dd>
948  * <dt>GLINE_LASTMOD</dt><dd>Only match G-lines with a last modification time.</dd>
949  * <dt>GLINE_EXACT</dt><dd>Require an exact match of G-line mask.</dd>
950  * <dt>anything else</dt><dd>Search user G-lines.</dd>
951  * </dl>
952  * @param[in] userhost Mask to search for.
953  * @param[in] flags Bitwise combination of GLINE_* flags.
954  * @return First matching G-line, or NULL if none are found.
955  */
956 struct Gline *
957 gline_find(char *userhost, unsigned int flags)
958 {
959   struct Gline *gline = 0;
960   struct Gline *sgline;
961   char *user, *host, *t_uh;
962
963   if (flags & (GLINE_BADCHAN | GLINE_ANY)) {
964     gliter(BadChanGlineList, gline, sgline) {
965         if ((flags & (GlineIsLocal(gline) ? GLINE_GLOBAL : GLINE_LOCAL)) ||
966           (flags & GLINE_LASTMOD && !gline->gl_lastmod))
967         continue;
968       else if ((flags & GLINE_EXACT ? ircd_strcmp(gline->gl_user, userhost) :
969                 match(gline->gl_user, userhost)) == 0)
970         return gline;
971     }
972   }
973
974   if ((flags & (GLINE_BADCHAN | GLINE_ANY)) == GLINE_BADCHAN ||
975       *userhost == '#' || *userhost == '&')
976     return 0;
977
978   DupString(t_uh, userhost);
979   canon_userhost(t_uh, &user, &host, "*");
980
981   gliter(GlobalGlineList, gline, sgline) {
982     if ((flags & (GlineIsLocal(gline) ? GLINE_GLOBAL : GLINE_LOCAL)) ||
983         (flags & GLINE_LASTMOD && !gline->gl_lastmod))
984       continue;
985     else if (flags & GLINE_EXACT) {
986       if (((gline->gl_host && host && ircd_strcmp(gline->gl_host, host) == 0)
987            || (!gline->gl_host && !host)) &&
988           (ircd_strcmp(gline->gl_user, user) == 0))
989         break;
990     } else {
991       if (((gline->gl_host && host && match(gline->gl_host, host) == 0)
992            || (!gline->gl_host && !host)) &&
993           (match(gline->gl_user, user) == 0))
994         break;
995     }
996   }
997
998   MyFree(t_uh);
999
1000   return gline;
1001 }
1002
1003 /** Find a matching G-line for a user.
1004  * @param[in] cptr Client to compare against.
1005  * @param[in] flags Bitwise combination of GLINE_GLOBAL and/or
1006  * GLINE_LASTMOD to limit matches.
1007  * @return Matching G-line, or NULL if none are found.
1008  */
1009 struct Gline *
1010 gline_lookup(struct Client *cptr, unsigned int flags)
1011 {
1012   struct Gline *gline;
1013   struct Gline *sgline;
1014
1015   gliter(GlobalGlineList, gline, sgline) {
1016     if ((flags & GLINE_GLOBAL && gline->gl_flags & GLINE_LOCAL) ||
1017         (flags & GLINE_LASTMOD && !gline->gl_lastmod))
1018       continue;
1019
1020     if (GlineIsRealName(gline)) {
1021       Debug((DEBUG_DEBUG,"realname gline: '%s' '%s'",gline->gl_user,cli_info(cptr)));
1022       if (match(gline->gl_user+2, cli_info(cptr)) != 0)
1023         continue;
1024     }
1025     else {
1026       if (match(gline->gl_user, (cli_user(cptr))->username) != 0)
1027         continue;
1028
1029       if (GlineIsIpMask(gline)) {
1030         if (!ipmask_check(&cli_ip(cptr), &gline->gl_addr, gline->gl_bits))
1031           continue;
1032       }
1033       else {
1034         if (match(gline->gl_host, (cli_user(cptr))->realhost) != 0)
1035           continue;
1036       }
1037     }
1038     if (GlineIsActive(gline))
1039       return gline;
1040   }
1041   /*
1042    * No Glines matched
1043    */
1044   return 0;
1045 }
1046
1047 /** Delink and free a G-line.
1048  * @param[in] gline G-line to free.
1049  */
1050 void
1051 gline_free(struct Gline *gline)
1052 {
1053   assert(0 != gline);
1054
1055   *gline->gl_prev_p = gline->gl_next; /* squeeze this gline out */
1056   if (gline->gl_next)
1057     gline->gl_next->gl_prev_p = gline->gl_prev_p;
1058
1059   MyFree(gline->gl_user); /* free up the memory */
1060   if (gline->gl_host)
1061     MyFree(gline->gl_host);
1062   MyFree(gline->gl_reason);
1063   MyFree(gline);
1064 }
1065
1066 /** Burst all known global G-lines to another server.
1067  * @param[in] cptr Destination of burst.
1068  */
1069 void
1070 gline_burst(struct Client *cptr)
1071 {
1072   struct Gline *gline;
1073   struct Gline *sgline;
1074
1075   gliter(GlobalGlineList, gline, sgline) {
1076     if (!GlineIsLocal(gline) && gline->gl_lastmod)
1077       sendcmdto_one(&me, CMD_GLINE, cptr, "* %c%s%s%s %Tu %Tu %Tu :%s",
1078                     GlineIsRemActive(gline) ? '+' : '-', gline->gl_user,
1079                     gline->gl_host ? "@" : "",
1080                     gline->gl_host ? gline->gl_host : "",
1081                     gline->gl_expire - CurrentTime, gline->gl_lastmod,
1082                     gline->gl_lifetime, gline->gl_reason);
1083   }
1084
1085   gliter(BadChanGlineList, gline, sgline) {
1086     if (!GlineIsLocal(gline) && gline->gl_lastmod)
1087       sendcmdto_one(&me, CMD_GLINE, cptr, "* %c%s %Tu %Tu %Tu :%s",
1088                     GlineIsRemActive(gline) ? '+' : '-', gline->gl_user,
1089                     gline->gl_expire - CurrentTime, gline->gl_lastmod,
1090                     gline->gl_lifetime, gline->gl_reason);
1091   }
1092 }
1093
1094 /** Send a G-line to another server.
1095  * @param[in] cptr Who to inform of the G-line.
1096  * @param[in] gline G-line to send.
1097  * @return Zero.
1098  */
1099 int
1100 gline_resend(struct Client *cptr, struct Gline *gline)
1101 {
1102   if (GlineIsLocal(gline) || !gline->gl_lastmod)
1103     return 0;
1104
1105   sendcmdto_one(&me, CMD_GLINE, cptr, "* %c%s%s%s %Tu %Tu %Tu :%s",
1106                 GlineIsRemActive(gline) ? '+' : '-', gline->gl_user,
1107                 gline->gl_host ? "@" : "",
1108                 gline->gl_host ? gline->gl_host : "",
1109                 gline->gl_expire - CurrentTime, gline->gl_lastmod,
1110                 gline->gl_lifetime, gline->gl_reason);
1111
1112   return 0;
1113 }
1114
1115 /** Display one or all G-lines to a user.
1116  * If \a userhost is not NULL, only send the first matching G-line.
1117  * Otherwise send the whole list.
1118  * @param[in] sptr User asking for G-line list.
1119  * @param[in] userhost G-line mask to search for (or NULL).
1120  * @return Zero.
1121  */
1122 int
1123 gline_list(struct Client *sptr, char *userhost)
1124 {
1125   struct Gline *gline;
1126   struct Gline *sgline;
1127
1128   if (userhost) {
1129     if (!(gline = gline_find(userhost, GLINE_ANY))) /* no such gline */
1130       return send_reply(sptr, ERR_NOSUCHGLINE, userhost);
1131
1132     /* send gline information along */
1133     send_reply(sptr, RPL_GLIST, gline->gl_user,
1134                gline->gl_host ? "@" : "",
1135                gline->gl_host ? gline->gl_host : "",
1136                gline->gl_expire + TSoffset, gline->gl_lastmod,
1137                gline->gl_lifetime + TSoffset,
1138                GlineIsLocal(gline) ? cli_name(&me) : "*",
1139                gline->gl_state == GLOCAL_ACTIVATED ? ">" :
1140                (gline->gl_state == GLOCAL_DEACTIVATED ? "<" : ""),
1141                GlineIsRemActive(gline) ? '+' : '-', gline->gl_reason);
1142   } else {
1143     gliter(GlobalGlineList, gline, sgline) {
1144       send_reply(sptr, RPL_GLIST, gline->gl_user,
1145                  gline->gl_host ? "@" : "",
1146                  gline->gl_host ? gline->gl_host : "",
1147                  gline->gl_expire + TSoffset, gline->gl_lastmod,
1148                  gline->gl_lifetime + TSoffset,
1149                  GlineIsLocal(gline) ? cli_name(&me) : "*",
1150                  gline->gl_state == GLOCAL_ACTIVATED ? ">" :
1151                  (gline->gl_state == GLOCAL_DEACTIVATED ? "<" : ""),
1152                  GlineIsRemActive(gline) ? '+' : '-', gline->gl_reason);
1153     }
1154
1155     gliter(BadChanGlineList, gline, sgline) {
1156       send_reply(sptr, RPL_GLIST, gline->gl_user, "", "",
1157                  gline->gl_expire + TSoffset, gline->gl_lastmod,
1158                  gline->gl_lifetime + TSoffset,
1159                  GlineIsLocal(gline) ? cli_name(&me) : "*",
1160                  gline->gl_state == GLOCAL_ACTIVATED ? ">" :
1161                  (gline->gl_state == GLOCAL_DEACTIVATED ? "<" : ""),
1162                  GlineIsRemActive(gline) ? '+' : '-', gline->gl_reason);
1163     }
1164   }
1165
1166   /* end of gline information */
1167   return send_reply(sptr, RPL_ENDOFGLIST);
1168 }
1169
1170 /** Statistics callback to list G-lines.
1171  * @param[in] sptr Client requesting statistics.
1172  * @param[in] sd Stats descriptor for request (ignored).
1173  * @param[in] param Extra parameter from user (ignored).
1174  */
1175 void
1176 gline_stats(struct Client *sptr, const struct StatDesc *sd,
1177             char *param)
1178 {
1179   struct Gline *gline;
1180   struct Gline *sgline;
1181
1182   gliter(GlobalGlineList, gline, sgline) {
1183     send_reply(sptr, RPL_STATSGLINE, 'G', gline->gl_user,
1184                gline->gl_host ? "@" : "",
1185                gline->gl_host ? gline->gl_host : "",
1186                gline->gl_expire + TSoffset, gline->gl_lastmod,
1187                gline->gl_lifetime + TSoffset,
1188                gline->gl_state == GLOCAL_ACTIVATED ? ">" :
1189                (gline->gl_state == GLOCAL_DEACTIVATED ? "<" : ""),
1190                GlineIsRemActive(gline) ? '+' : '-',
1191                gline->gl_reason);
1192   }
1193 }
1194
1195 /** Calculate memory used by G-lines.
1196  * @param[out] gl_size Number of bytes used by G-lines.
1197  * @return Number of G-lines in use.
1198  */
1199 int
1200 gline_memory_count(size_t *gl_size)
1201 {
1202   struct Gline *gline;
1203   unsigned int gl = 0;
1204
1205   for (gline = GlobalGlineList; gline; gline = gline->gl_next) {
1206     gl++;
1207     *gl_size += sizeof(struct Gline);
1208     *gl_size += gline->gl_user ? (strlen(gline->gl_user) + 1) : 0;
1209     *gl_size += gline->gl_host ? (strlen(gline->gl_host) + 1) : 0;
1210     *gl_size += gline->gl_reason ? (strlen(gline->gl_reason) + 1) : 0;
1211   }
1212
1213   for (gline = BadChanGlineList; gline; gline = gline->gl_next) {
1214     gl++;
1215     *gl_size += sizeof(struct Gline);
1216     *gl_size += gline->gl_user ? (strlen(gline->gl_user) + 1) : 0;
1217     *gl_size += gline->gl_host ? (strlen(gline->gl_host) + 1) : 0;
1218     *gl_size += gline->gl_reason ? (strlen(gline->gl_reason) + 1) : 0;
1219   }
1220
1221   return gl;
1222 }