]>
Commit | Line | Data |
---|---|---|
189935b1 | 1 | /* |
2 | * IRC - Internet Relay Chat, ircd/motd.c | |
3 | * Copyright (C) 1990 Jarkko Oikarinen and | |
4 | * University of Oulu, Computing Center | |
5 | * Copyright (C) 2000 Kevin L. Mitchell <klmitch@mit.edu> | |
6 | * | |
7 | * See file AUTHORS in IRC package for additional names of | |
8 | * the programmers. | |
9 | * | |
10 | * This program is free software; you can redistribute it and/or modify | |
11 | * it under the terms of the GNU General Public License as published by | |
12 | * the Free Software Foundation; either version 1, or (at your option) | |
13 | * any later version. | |
14 | * | |
15 | * This program is distributed in the hope that it will be useful, | |
16 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
17 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
18 | * GNU General Public License for more details. | |
19 | * | |
20 | * You should have received a copy of the GNU General Public License | |
21 | * along with this program; if not, write to the Free Software | |
22 | * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. | |
23 | */ | |
24 | /** @file | |
25 | * @brief Message-of-the-day manipulation implementation. | |
26 | * @version $Id: motd.c,v 1.22 2005/05/10 03:43:08 entrope Exp $ | |
27 | */ | |
28 | #include "config.h" | |
29 | ||
30 | #include "motd.h" | |
31 | #include "class.h" | |
32 | #include "client.h" | |
33 | #include "fileio.h" | |
34 | #include "ircd.h" | |
35 | #include "ircd_alloc.h" | |
36 | #include "ircd_features.h" | |
37 | #include "ircd_log.h" | |
38 | #include "ircd_reply.h" | |
39 | #include "ircd_string.h" | |
40 | #include "match.h" | |
41 | #include "msg.h" | |
42 | #include "numeric.h" | |
43 | #include "numnicks.h" | |
44 | #include "s_conf.h" | |
45 | #include "s_debug.h" | |
46 | #include "s_user.h" | |
47 | #include "s_stats.h" | |
48 | #include "send.h" | |
49 | ||
50 | /* #include <assert.h> -- Now using assert in ircd_log.h */ | |
51 | #include <errno.h> | |
52 | #include <stdlib.h> | |
53 | #include <string.h> | |
54 | #include <sys/stat.h> | |
55 | ||
56 | /** Global list of messages of the day. */ | |
57 | static struct { | |
58 | struct Motd* local; /**< Local MOTD. */ | |
59 | struct Motd* remote; /**< Remote MOTD. */ | |
60 | struct Motd* other; /**< MOTDs specified in configuration file. */ | |
61 | struct Motd* freelist; /**< Currently unused Motd structs. */ | |
62 | struct MotdCache* cachelist; /**< List of MotdCache entries. */ | |
63 | } MotdList = { 0, 0, 0, 0, 0 }; | |
64 | ||
65 | /** Create a struct Motd and initialize it. | |
66 | * @param[in] hostmask Hostmask (or connection class name) to filter on. | |
67 | * @param[in] path Path to MOTD file. | |
68 | * @param[in] maxcount Maximum number of lines permitted for MOTD. | |
69 | */ | |
70 | static struct Motd * | |
71 | motd_create(const char *hostmask, const char *path, int maxcount) | |
72 | { | |
73 | struct Motd* tmp; | |
74 | ||
75 | assert(0 != path); | |
76 | ||
77 | /* allocate memory and initialize the structure */ | |
78 | if (MotdList.freelist) | |
79 | { | |
80 | tmp = MotdList.freelist; | |
81 | MotdList.freelist = tmp->next; | |
82 | } else | |
83 | tmp = (struct Motd *)MyMalloc(sizeof(struct Motd)); | |
84 | tmp->next = 0; | |
85 | ||
86 | if (hostmask == NULL) | |
87 | tmp->type = MOTD_UNIVERSAL; | |
88 | else if (find_class(hostmask)) | |
89 | tmp->type = MOTD_CLASS; | |
90 | else if (ipmask_parse(hostmask, &tmp->address, &tmp->addrbits)) | |
91 | tmp->type = MOTD_IPMASK; | |
92 | else | |
93 | tmp->type = MOTD_HOSTMASK; | |
94 | ||
95 | if (hostmask != NULL) | |
96 | DupString(tmp->hostmask, hostmask); | |
97 | else | |
98 | tmp->hostmask = NULL; | |
99 | ||
100 | DupString(tmp->path, path); | |
101 | tmp->maxcount = maxcount; | |
102 | tmp->cache = 0; | |
103 | ||
104 | return tmp; | |
105 | } | |
106 | ||
107 | /** This function reads a motd out of a file (if needed) and caches it. | |
108 | * If a matching cache entry already exists, reuse it. Otherwise, | |
109 | * allocate and populate a new MotdCache for it. | |
110 | * @param[in] motd Specification for MOTD file. | |
111 | * @return Matching MotdCache entry. | |
112 | */ | |
113 | static struct MotdCache * | |
114 | motd_cache(struct Motd *motd) | |
115 | { | |
116 | FBFILE* file; | |
117 | struct MotdCache* cache; | |
118 | struct stat sb; | |
119 | char line[MOTD_LINESIZE + 2]; /* \r\n */ | |
120 | char* tmp; | |
121 | int i; | |
122 | ||
123 | assert(0 != motd); | |
124 | assert(0 != motd->path); | |
125 | ||
126 | if (motd->cache) | |
127 | return motd->cache; | |
128 | ||
129 | /* try to find it in the list of cached files... */ | |
130 | for (cache = MotdList.cachelist; cache; cache = cache->next) { | |
131 | if (!strcmp(cache->path, motd->path) && | |
132 | cache->maxcount == motd->maxcount) { /* found one... */ | |
133 | cache->ref++; /* increase reference count... */ | |
134 | motd->cache = cache; /* remember cache... */ | |
135 | return motd->cache; /* return it */ | |
136 | } | |
137 | } | |
138 | ||
139 | /* gotta read in the file, now */ | |
140 | if (!(file = fbopen(motd->path, "r"))) { | |
141 | Debug((DEBUG_ERROR, "Couldn't open \"%s\": %s", motd->path, | |
142 | strerror(errno))); | |
143 | return 0; | |
144 | } | |
145 | ||
146 | /* need the file's modification time */ | |
147 | if (-1 == fbstat(&sb, file)) { | |
148 | fbclose(file); | |
149 | return 0; | |
150 | } | |
151 | ||
152 | /* Ok, allocate a structure; we'll realloc later to trim memory */ | |
153 | cache = (struct MotdCache *)MyMalloc(sizeof(struct MotdCache) + | |
154 | (MOTD_LINESIZE * (MOTD_MAXLINES - 1))); | |
155 | ||
156 | cache->ref = 1; | |
157 | DupString(cache->path, motd->path); | |
158 | cache->maxcount = motd->maxcount; | |
159 | ||
160 | cache->modtime = *localtime((time_t *) &sb.st_mtime); /* store modtime */ | |
161 | ||
162 | cache->count = 0; | |
163 | while (cache->count < cache->maxcount && fbgets(line, sizeof(line), file)) { | |
164 | /* copy over line, stopping when we overflow or hit line end */ | |
165 | for (tmp = line, i = 0; | |
166 | i < (MOTD_LINESIZE - 1) && *tmp && *tmp != '\r' && *tmp != '\n'; | |
167 | tmp++, i++) | |
168 | cache->motd[cache->count][i] = *tmp; | |
169 | cache->motd[cache->count][i] = '\0'; | |
170 | ||
171 | cache->count++; | |
172 | } | |
173 | ||
174 | fbclose(file); /* close the file */ | |
175 | ||
176 | /* trim memory usage a little */ | |
177 | motd->cache = (struct MotdCache*)MyMalloc(sizeof(struct MotdCache) + | |
178 | (MOTD_LINESIZE * (cache->count - 1))); | |
179 | memcpy(motd->cache, cache, sizeof(struct MotdCache) + | |
180 | (MOTD_LINESIZE * (cache->count - 1))); | |
181 | MyFree(cache); | |
182 | ||
183 | /* now link it in... */ | |
184 | motd->cache->next = MotdList.cachelist; | |
185 | motd->cache->prev_p = &MotdList.cachelist; | |
186 | if (MotdList.cachelist) | |
187 | MotdList.cachelist->prev_p = &motd->cache->next; | |
188 | MotdList.cachelist = motd->cache; | |
189 | ||
190 | return motd->cache; | |
191 | } | |
192 | ||
193 | /** Clear and dereference the Motd::cache element of \a motd. | |
194 | * If the MotdCache::ref count goes to zero, free it. | |
195 | * @param[in] motd MOTD to uncache. | |
196 | */ | |
197 | static void | |
198 | motd_decache(struct Motd *motd) | |
199 | { | |
200 | struct MotdCache* cache; | |
201 | ||
202 | assert(0 != motd); | |
203 | ||
204 | if (!(cache = motd->cache)) /* we can be called for records with no cache */ | |
205 | return; | |
206 | ||
207 | motd->cache = 0; /* zero the cache */ | |
208 | ||
209 | if (!--cache->ref) { /* reduce reference count... */ | |
210 | if (cache->next) /* ref is 0, delink from list and free */ | |
211 | cache->next->prev_p = cache->prev_p; | |
212 | *cache->prev_p = cache->next; | |
213 | ||
214 | MyFree(cache->path); /* free path info... */ | |
215 | ||
216 | MyFree(cache); /* very simple for a reason... */ | |
217 | } | |
218 | } | |
219 | ||
220 | /** Deallocate a MOTD structure. | |
221 | * If it has cached content, uncache it. | |
222 | * @param[in] motd MOTD to destroy. | |
223 | */ | |
224 | static void | |
225 | motd_destroy(struct Motd *motd) | |
226 | { | |
227 | assert(0 != motd); | |
228 | ||
229 | MyFree(motd->path); /* we always must have a path */ | |
230 | MyFree(motd->hostmask); | |
231 | if (motd->cache) /* drop the cache */ | |
232 | motd_decache(motd); | |
233 | ||
234 | motd->next = MotdList.freelist; | |
235 | MotdList.freelist = motd; | |
236 | } | |
237 | ||
238 | /** Find the first matching MOTD block for a user. | |
239 | * If the user is remote, always use remote MOTD. | |
240 | * Otherwise, if there is a hostmask- or class-based MOTD that matches | |
241 | * the user, use it. | |
242 | * Otherwise, use the local MOTD. | |
243 | * @param[in] cptr Client to find MOTD for. | |
244 | * @return Pointer to first matching MOTD for the client. | |
245 | */ | |
246 | static struct Motd * | |
247 | motd_lookup(struct Client *cptr) | |
248 | { | |
249 | struct Motd *ptr; | |
250 | char *c_class = NULL; | |
251 | ||
252 | assert(0 != cptr); | |
253 | ||
254 | if (!MyUser(cptr)) /* not my user, always return remote motd */ | |
255 | return MotdList.remote; | |
256 | ||
257 | c_class = get_client_class(cptr); | |
258 | assert(c_class != NULL); | |
259 | ||
260 | /* check the motd blocks first */ | |
261 | for (ptr = MotdList.other; ptr; ptr = ptr->next) | |
262 | { | |
263 | if (ptr->type == MOTD_CLASS | |
264 | && !match(ptr->hostmask, c_class)) | |
265 | return ptr; | |
266 | else if (ptr->type == MOTD_HOSTMASK | |
267 | && !match(ptr->hostmask, cli_sockhost(cptr))) | |
268 | return ptr; | |
269 | else if (ptr->type == MOTD_IPMASK | |
270 | && ipmask_check(&cli_ip(cptr), &ptr->address, ptr->addrbits)) | |
271 | return ptr; | |
272 | } | |
273 | ||
274 | return MotdList.local; /* Ok, return the default motd */ | |
275 | } | |
276 | ||
277 | /** Send the content of a MotdCache to a user. | |
278 | * If \a cache is NULL, simply send ERR_NOMOTD to the client. | |
279 | * @param[in] cptr Client to send MOTD to. | |
280 | * @param[in] cache MOTD body to send to client. | |
281 | */ | |
282 | static int | |
283 | motd_forward(struct Client *cptr, struct MotdCache *cache) | |
284 | { | |
285 | int i; | |
286 | ||
287 | assert(0 != cptr); | |
288 | ||
289 | if (!cache) /* no motd to send */ | |
290 | return send_reply(cptr, ERR_NOMOTD); | |
291 | ||
292 | /* send the motd */ | |
293 | send_reply(cptr, RPL_MOTDSTART, cli_name(&me)); | |
294 | send_reply(cptr, SND_EXPLICIT | RPL_MOTD, ":- %d-%d-%d %d:%02d", | |
295 | cache->modtime.tm_year + 1900, cache->modtime.tm_mon + 1, | |
296 | cache->modtime.tm_mday, cache->modtime.tm_hour, | |
297 | cache->modtime.tm_min); | |
298 | ||
299 | for (i = 0; i < cache->count; i++) | |
300 | send_reply(cptr, RPL_MOTD, cache->motd[i]); | |
301 | ||
302 | return send_reply(cptr, RPL_ENDOFMOTD); /* end */ | |
303 | } | |
304 | ||
305 | /** Find the MOTD for a client and send it. | |
306 | * @param[in] cptr Client being greeted. | |
307 | */ | |
308 | int | |
309 | motd_send(struct Client* cptr) | |
310 | { | |
311 | assert(0 != cptr); | |
312 | ||
313 | return motd_forward(cptr, motd_cache(motd_lookup(cptr))); | |
314 | } | |
315 | ||
316 | /** Send the signon MOTD to a user. | |
317 | * If FEAT_NODEFAULTMOTD is true and a matching MOTD exists for the | |
318 | * user, direct the client to type /MOTD to read it. Otherwise, call | |
319 | * motd_forward() for the user. | |
320 | * @param[in] cptr Client that has just connected. | |
321 | */ | |
322 | void | |
323 | motd_signon(struct Client* cptr) | |
324 | { | |
325 | struct MotdCache *cache; | |
326 | const char *banner = NULL; | |
327 | ||
328 | cache = motd_cache(motd_lookup(cptr)); | |
329 | ||
330 | if (!feature_bool(FEAT_NODEFAULTMOTD) || !cache) | |
331 | motd_forward(cptr, cache); | |
332 | else { | |
333 | send_reply(cptr, RPL_MOTDSTART, cli_name(&me)); | |
334 | if ((banner = feature_str(FEAT_MOTD_BANNER))) | |
335 | send_reply(cptr, SND_EXPLICIT | RPL_MOTD, ":%s", banner); | |
336 | send_reply(cptr, SND_EXPLICIT | RPL_MOTD, ":\002Type /MOTD to read the " | |
337 | "AUP before continuing using this service.\002"); | |
338 | send_reply(cptr, SND_EXPLICIT | RPL_MOTD, ":The message of the day was " | |
339 | "last changed: %d-%d-%d %d:%d", cache->modtime.tm_year + 1900, | |
340 | cache->modtime.tm_mon + 1, cache->modtime.tm_mday, | |
341 | cache->modtime.tm_hour, cache->modtime.tm_min); | |
342 | send_reply(cptr, RPL_ENDOFMOTD); | |
343 | } | |
344 | } | |
345 | ||
346 | /** Clear all cached MOTD bodies. | |
347 | * The local and remote MOTDs are re-cached immediately. | |
348 | */ | |
349 | void | |
350 | motd_recache(void) | |
351 | { | |
352 | struct Motd* tmp; | |
353 | ||
354 | motd_decache(MotdList.local); /* decache local and remote MOTDs */ | |
355 | motd_decache(MotdList.remote); | |
356 | ||
357 | for (tmp = MotdList.other; tmp; tmp = tmp->next) /* now all the others */ | |
358 | motd_decache(tmp); | |
359 | ||
360 | /* now recache local and remote MOTDs */ | |
361 | motd_cache(MotdList.local); | |
362 | motd_cache(MotdList.remote); | |
363 | } | |
364 | ||
365 | /** Re-cache the local and remote MOTDs. | |
366 | * If they already exist, they are deallocated first. | |
367 | */ | |
368 | void | |
369 | motd_init(void) | |
370 | { | |
371 | if (MotdList.local) /* destroy old local... */ | |
372 | motd_destroy(MotdList.local); | |
373 | ||
374 | MotdList.local = motd_create(0, feature_str(FEAT_MPATH), MOTD_MAXLINES); | |
375 | motd_cache(MotdList.local); /* init local and cache it */ | |
376 | ||
377 | if (MotdList.remote) /* destroy old remote... */ | |
378 | motd_destroy(MotdList.remote); | |
379 | ||
380 | MotdList.remote = motd_create(0, feature_str(FEAT_RPATH), MOTD_MAXREMOTE); | |
381 | motd_cache(MotdList.remote); /* init remote and cache it */ | |
382 | } | |
383 | ||
384 | /** Add a new MOTD. | |
385 | * @param[in] hostmask Hostmask (or connection class name) to send this to. | |
386 | * @param[in] path Pathname of file to send. | |
387 | */ | |
388 | void | |
389 | motd_add(const char *hostmask, const char *path) | |
390 | { | |
391 | struct Motd *tmp; | |
392 | ||
393 | tmp = motd_create(hostmask, path, MOTD_MAXLINES); /* create the motd */ | |
394 | ||
395 | tmp->next = MotdList.other; /* link it into the list */ | |
396 | MotdList.other = tmp; | |
397 | } | |
398 | ||
399 | /** Clear out all MOTDs. | |
400 | * Compared to motd_recache(), this destroys all hostmask- or | |
401 | * class-based MOTDs rather than simply uncaching them. | |
402 | * Re-cache the local and remote MOTDs. | |
403 | */ | |
404 | void | |
405 | motd_clear(void) | |
406 | { | |
407 | struct Motd *ptr, *next; | |
408 | ||
409 | motd_decache(MotdList.local); /* decache local and remote MOTDs */ | |
410 | motd_decache(MotdList.remote); | |
411 | ||
412 | if (MotdList.other) /* destroy other MOTDs */ | |
413 | for (ptr = MotdList.other; ptr; ptr = next) | |
414 | { | |
415 | next = ptr->next; | |
416 | motd_destroy(ptr); | |
417 | } | |
418 | ||
419 | MotdList.other = 0; | |
420 | ||
421 | /* now recache local and remote MOTDs */ | |
422 | motd_cache(MotdList.local); | |
423 | motd_cache(MotdList.remote); | |
424 | } | |
425 | ||
426 | /** Report list of non-default MOTDs. | |
427 | * @param[in] to Client requesting statistics. | |
428 | * @param[in] sd Stats descriptor for request (ignored). | |
429 | * @param[in] param Extra parameter from user (ignored). | |
430 | */ | |
431 | void | |
432 | motd_report(struct Client *to, const struct StatDesc *sd, char *param) | |
433 | { | |
434 | struct Motd *ptr; | |
435 | ||
436 | for (ptr = MotdList.other; ptr; ptr = ptr->next) | |
437 | send_reply(to, SND_EXPLICIT | RPL_STATSTLINE, "T %s %s", | |
438 | ptr->hostmask, ptr->path); | |
439 | } | |
440 | ||
441 | /** Report MOTD memory usage to a client. | |
442 | * @param[in] cptr Client requesting memory usage. | |
443 | */ | |
444 | void | |
445 | motd_memory_count(struct Client *cptr) | |
446 | { | |
447 | struct Motd *ptr; | |
448 | struct MotdCache *cache; | |
449 | unsigned int mt = 0, /* motd count */ | |
450 | mtc = 0, /* motd cache count */ | |
451 | mtf = 0; /* motd free list count */ | |
452 | size_t mtm = 0, /* memory consumed by motd */ | |
453 | mtcm = 0; /* memory consumed by motd cache */ | |
454 | if (MotdList.local) | |
455 | { | |
456 | mt++; | |
457 | mtm += sizeof(struct Motd); | |
458 | mtm += MotdList.local->path ? (strlen(MotdList.local->path) + 1) : 0; | |
459 | } | |
460 | ||
461 | if (MotdList.remote) | |
462 | { | |
463 | mt++; | |
464 | mtm += sizeof(struct Motd); | |
465 | mtm += MotdList.remote->path ? (strlen(MotdList.remote->path) + 1) : 0; | |
466 | } | |
467 | ||
468 | for (ptr = MotdList.other; ptr; ptr = ptr->next) | |
469 | { | |
470 | mt++; | |
471 | mtm += sizeof(struct Motd); | |
472 | mtm += ptr->path ? (strlen(ptr->path) + 1) : 0; | |
473 | } | |
474 | ||
475 | for (cache = MotdList.cachelist; cache; cache = cache->next) | |
476 | { | |
477 | mtc++; | |
478 | mtcm += sizeof(struct MotdCache) + (MOTD_LINESIZE * (cache->count - 1)); | |
479 | } | |
480 | ||
481 | if (MotdList.freelist) | |
482 | for (ptr = MotdList.freelist; ptr; ptr = ptr->next) | |
483 | mtf++; | |
484 | ||
485 | send_reply(cptr, SND_EXPLICIT | RPL_STATSDEBUG, | |
486 | ":Motds %d(%zu) Cache %d(%zu) Free %d(%zu)", | |
487 | mt, mtm, mtc, mtcm, mtf, (mtf * sizeof(struct Motd))); | |
488 | } |