]>
Commit | Line | Data |
---|---|---|
1 | /* | |
2 | * IRC - Internet Relay Chat, ircd/features.c | |
3 | * Copyright (C) 2000 Kevin L. Mitchell <klmitch@mit.edu> | |
4 | * | |
5 | * This program is free software; you can redistribute it and/or modify | |
6 | * it under the terms of the GNU General Public License as published by | |
7 | * the Free Software Foundation; either version 1, or (at your option) | |
8 | * any later version. | |
9 | * | |
10 | * This program is distributed in the hope that it will be useful, | |
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
13 | * GNU General Public License for more details. | |
14 | * | |
15 | * You should have received a copy of the GNU General Public License | |
16 | * along with this program; if not, write to the Free Software | |
17 | * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. | |
18 | */ | |
19 | /** @file | |
20 | * @brief Implementation of configurable feature support. | |
21 | * @version $Id: ircd_features.c,v 1.50.2.5 2006/02/16 03:49:54 entrope Exp $ | |
22 | */ | |
23 | #include "config.h" | |
24 | ||
25 | #include "ircd_features.h" | |
26 | #include "channel.h" /* list_set_default */ | |
27 | #include "class.h" | |
28 | #include "client.h" | |
29 | #include "hash.h" | |
30 | #include "ircd.h" | |
31 | #include "ircd_alloc.h" | |
32 | #include "ircd_log.h" | |
33 | #include "ircd_reply.h" | |
34 | #include "ircd_string.h" | |
35 | #include "match.h" | |
36 | #include "motd.h" | |
37 | #include "msg.h" | |
38 | #include "numeric.h" | |
39 | #include "numnicks.h" | |
40 | #include "random.h" /* random_seed_set */ | |
41 | #include "s_bsd.h" | |
42 | #include "s_debug.h" | |
43 | #include "s_misc.h" | |
44 | #include "send.h" | |
45 | #include "struct.h" | |
46 | #include "sys.h" /* FALSE bleah */ | |
47 | #include "whowas.h" /* whowas_realloc */ | |
48 | ||
49 | /* #include <assert.h> -- Now using assert in ircd_log.h */ | |
50 | #include <stdlib.h> | |
51 | #include <string.h> | |
52 | ||
53 | struct Client his; | |
54 | ||
55 | /** List of log output types that can be set */ | |
56 | static struct LogTypes { | |
57 | char *type; /**< Settable name. */ | |
58 | int (*set)(const char *, const char *); /**< Function to set the value. */ | |
59 | char *(*get)(const char *); /**< Function to get the value. */ | |
60 | } logTypes[] = { | |
61 | { "FILE", log_set_file, log_get_file }, | |
62 | { "FACILITY", log_set_facility, log_get_facility }, | |
63 | { "SNOMASK", log_set_snomask, log_get_snomask }, | |
64 | { "LEVEL", log_set_level, log_get_level }, | |
65 | { 0, 0, 0 } | |
66 | }; | |
67 | ||
68 | /** Look up a struct LogType given the type string. | |
69 | * @param[in] from &Client requesting type, or NULL. | |
70 | * @param[in] type Name of log type to find. | |
71 | * @return Pointer to the found LogType, or NULL if none was found. | |
72 | */ | |
73 | static struct LogTypes * | |
74 | feature_log_desc(struct Client* from, const char *type) | |
75 | { | |
76 | int i; | |
77 | ||
78 | assert(0 != type); | |
79 | ||
80 | for (i = 0; logTypes[i].type; i++) /* find appropriate descriptor */ | |
81 | if (!ircd_strcmp(type, logTypes[i].type)) | |
82 | return &logTypes[i]; | |
83 | ||
84 | Debug((DEBUG_ERROR, "Unknown log feature type \"%s\"", type)); | |
85 | if (from) /* send an error; if from is NULL, called from conf parser */ | |
86 | send_reply(from, ERR_BADLOGTYPE, type); | |
87 | else | |
88 | log_write(LS_CONFIG, L_ERROR, 0, "Unknown log feature type \"%s\"", type); | |
89 | ||
90 | return 0; /* not found */ | |
91 | } | |
92 | ||
93 | /** Set the value of a log output type for a log subsystem. | |
94 | * @param[in] from &Client trying to set the log type, or NULL. | |
95 | * @param[in] fields Array of parameters to set. | |
96 | * @param[in] count Number of parameters in \a fields. | |
97 | * @return -1 to clear the mark, 0 to leave the mask alone, 1 to set the mask. | |
98 | */ | |
99 | static int | |
100 | feature_log_set(struct Client* from, const char* const* fields, int count) | |
101 | { | |
102 | struct LogTypes *desc; | |
103 | char *subsys; | |
104 | ||
105 | if (count < 2) { /* set default facility */ | |
106 | if (log_set_default(count < 1 ? 0 : fields[0])) { | |
107 | assert(count >= 1); /* should always accept default */ | |
108 | ||
109 | if (from) /* send an error */ | |
110 | send_reply(from, ERR_BADLOGVALUE, fields[0]); | |
111 | else | |
112 | log_write(LS_CONFIG, L_ERROR, 0, | |
113 | "Bad value \"%s\" for default facility", fields[0]); | |
114 | } else | |
115 | return count < 1 ? -1 : 1; /* tell feature to set or clear mark */ | |
116 | } else if (!(subsys = log_canon(fields[0]))) { /* no such subsystem */ | |
117 | if (from) /* send an error */ | |
118 | send_reply(from, ERR_BADLOGSYS, fields[0]); | |
119 | else | |
120 | log_write(LS_CONFIG, L_ERROR, 0, | |
121 | "No such logging subsystem \"%s\"", fields[0]); | |
122 | } else if ((desc = feature_log_desc(from, fields[1]))) { /* set value */ | |
123 | if ((*desc->set)(fields[0], count < 3 ? 0 : fields[2])) { | |
124 | assert(count >= 3); /* should always accept default */ | |
125 | ||
126 | if (from) /* send an error */ | |
127 | send_reply(from, ERR_BADLOGVALUE, fields[2]); | |
128 | else | |
129 | log_write(LS_CONFIG, L_ERROR, 0, | |
130 | "Bad value \"%s\" for log type %s (subsystem %s)", | |
131 | fields[2], desc->type, subsys); | |
132 | } | |
133 | } | |
134 | ||
135 | return 0; | |
136 | } | |
137 | ||
138 | /** Reset a log type for a subsystem to its default value. | |
139 | * @param[in] from &Client trying to reset the subsystem. | |
140 | * @param[in] fields Array of parameters to reset. | |
141 | * @param[in] count Number of fields in \a fields. | |
142 | * @return -1 to unmark the entry, or zero to leave it alone. | |
143 | */ | |
144 | static int | |
145 | feature_log_reset(struct Client* from, const char* const* fields, int count) | |
146 | { | |
147 | struct LogTypes *desc; | |
148 | char *subsys; | |
149 | ||
150 | assert(0 != from); /* Never called by the .conf parser */ | |
151 | ||
152 | if (count < 1) { /* reset default facility */ | |
153 | log_set_default(0); | |
154 | return -1; /* unmark this entry */ | |
155 | } else if (count < 2) | |
156 | need_more_params(from, "RESET"); | |
157 | else if (!(subsys = log_canon(fields[0]))) /* no such subsystem */ | |
158 | send_reply(from, ERR_BADLOGSYS, fields[0]); | |
159 | else if ((desc = feature_log_desc(from, fields[1]))) /* reset value */ | |
160 | (*desc->set)(fields[0], 0); /* default should always be accepted */ | |
161 | ||
162 | return 0; | |
163 | } | |
164 | ||
165 | /** Handle an update to FEAT_HIS_SERVERNAME. */ | |
166 | static void | |
167 | feature_notify_servername(void) | |
168 | { | |
169 | ircd_strncpy(cli_name(&his), feature_str(FEAT_HIS_SERVERNAME), HOSTLEN); | |
170 | } | |
171 | ||
172 | /** Handle an update to FEAT_HIS_SERVERINFO. */ | |
173 | static void | |
174 | feature_notify_serverinfo(void) | |
175 | { | |
176 | ircd_strncpy(cli_info(&his), feature_str(FEAT_HIS_SERVERINFO), REALLEN); | |
177 | } | |
178 | ||
179 | /** Report the value of a log setting. | |
180 | * @param[in] from &Client asking for details. | |
181 | * @param[in] fields Array of parameters to get. | |
182 | * @param[in] count Number of fields in \a fields. | |
183 | */ | |
184 | static void | |
185 | feature_log_get(struct Client* from, const char* const* fields, int count) | |
186 | { | |
187 | struct LogTypes *desc; | |
188 | char *value, *subsys; | |
189 | ||
190 | assert(0 != from); /* never called by .conf parser */ | |
191 | ||
192 | if (count < 1) /* return default facility */ | |
193 | send_reply(from, SND_EXPLICIT | RPL_FEATURE, ":Log facility: %s", | |
194 | log_get_default()); | |
195 | else if (count < 2) | |
196 | need_more_params(from, "GET"); | |
197 | else if (!(subsys = log_canon(fields[0]))) { /* no such subsystem */ | |
198 | send_reply(from, ERR_BADLOGSYS, fields[0]); | |
199 | } else if ((desc = feature_log_desc(from, fields[1]))) { | |
200 | if ((value = (*desc->get)(fields[0]))) /* send along value */ | |
201 | send_reply(from, SND_EXPLICIT | RPL_FEATURE, | |
202 | ":Log %s for subsystem %s: %s", desc->type, subsys, | |
203 | (*desc->get)(subsys)); | |
204 | else | |
205 | send_reply(from, SND_EXPLICIT | RPL_FEATURE, | |
206 | ":No log %s is set for subsystem %s", desc->type, subsys); | |
207 | } | |
208 | } | |
209 | ||
210 | /** Sets a feature to the given value. | |
211 | * @param[in] from Client trying to set parameters. | |
212 | * @param[in] fields Array of parameters to set. | |
213 | * @param[in] count Number of fields in \a count. | |
214 | * @return <0 to clear the feature mark, 0 to leave it, >0 to set the feature mark. | |
215 | */ | |
216 | typedef int (*feat_set_call)(struct Client* from, const char* const* fields, int count); | |
217 | /** Gets the value of a feature. | |
218 | * @param[in] from Client trying to get parameters. | |
219 | * @param[in] fields Array of parameters to set. | |
220 | * @param[in] count Number of fields in \a count. | |
221 | */ | |
222 | typedef void (*feat_get_call)(struct Client* from, const char* const* fields, int count); | |
223 | /** Callback to notify of a feature's change. */ | |
224 | typedef void (*feat_notify_call)(void); | |
225 | /** Unmarks all sub-feature values prior to reading .conf. */ | |
226 | typedef void (*feat_unmark_call)(void); | |
227 | /** Resets to defaults all currently unmarked values. | |
228 | * @param[in] marked Non-zero if the feature is marked. | |
229 | */ | |
230 | typedef int (*feat_mark_call)(int marked); | |
231 | /* Reports features as a /stats f list. | |
232 | * @param[in] sptr Client asking for feature list. | |
233 | * @param[in] marked Non-zero if the feature is marked. | |
234 | */ | |
235 | typedef void (*feat_report_call)(struct Client* sptr, int marked); | |
236 | ||
237 | #define FEAT_NONE 0x0000 /**< no value */ | |
238 | #define FEAT_INT 0x0001 /**< set if entry contains an integer value */ | |
239 | #define FEAT_BOOL 0x0002 /**< set if entry contains a boolean value */ | |
240 | #define FEAT_STR 0x0003 /**< set if entry contains a string value */ | |
241 | #define FEAT_MASK 0x000f /**< possible value types */ | |
242 | ||
243 | #define FEAT_MARK 0x0010 /**< set if entry has been changed */ | |
244 | #define FEAT_NULL 0x0020 /**< NULL string is permitted */ | |
245 | #define FEAT_CASE 0x0040 /**< string is case-sensitive */ | |
246 | ||
247 | #define FEAT_OPER 0x0100 /**< set to display only to opers */ | |
248 | #define FEAT_MYOPER 0x0200 /**< set to display only to local opers */ | |
249 | #define FEAT_NODISP 0x0400 /**< feature must never be displayed */ | |
250 | ||
251 | #define FEAT_READ 0x1000 /**< feature is read-only (for now, perhaps?) */ | |
252 | ||
253 | /** Declare a feature with custom behavior. */ | |
254 | #define F_N(type, flags, set, reset, get, notify, unmark, mark, report) \ | |
255 | { FEAT_ ## type, #type, FEAT_NONE | (flags), 0, 0, 0, 0, \ | |
256 | (set), (reset), (get), (notify), (unmark), (mark), (report) } | |
257 | /** Declare a feature that takes integer values. */ | |
258 | #define F_I(type, flags, v_int, notify) \ | |
259 | { FEAT_ ## type, #type, FEAT_INT | (flags), 0, (v_int), 0, 0, \ | |
260 | 0, 0, 0, (notify), 0, 0, 0 } | |
261 | /** Declare a feature that takes boolean values. */ | |
262 | #define F_B(type, flags, v_int, notify) \ | |
263 | { FEAT_ ## type, #type, FEAT_BOOL | (flags), 0, (v_int), 0, 0, \ | |
264 | 0, 0, 0, (notify), 0, 0, 0 } | |
265 | /** Declare a feature that takes string values. */ | |
266 | #define F_S(type, flags, v_str, notify) \ | |
267 | { FEAT_ ## type, #type, FEAT_STR | (flags), 0, 0, 0, (v_str), \ | |
268 | 0, 0, 0, (notify), 0, 0, 0 } | |
269 | ||
270 | /** Table of feature descriptions. */ | |
271 | static struct FeatureDesc { | |
272 | enum Feature feat; /**< feature identifier */ | |
273 | char* type; /**< string describing type */ | |
274 | unsigned int flags; /**< flags for feature */ | |
275 | int v_int; /**< integer value */ | |
276 | int def_int; /**< default value */ | |
277 | char* v_str; /**< string value */ | |
278 | char* def_str; /**< default value */ | |
279 | feat_set_call set; /**< set feature values */ | |
280 | feat_set_call reset; /**< reset feature values to defaults */ | |
281 | feat_get_call get; /**< get feature values */ | |
282 | feat_notify_call notify; /**< notify of value change */ | |
283 | feat_unmark_call unmark; /**< unmark all feature change values */ | |
284 | feat_mark_call mark; /**< reset to defaults all unchanged features */ | |
285 | feat_report_call report; /**< report feature values */ | |
286 | } features[] = { | |
287 | /* Misc. features */ | |
288 | F_N(LOG, FEAT_MYOPER, feature_log_set, feature_log_reset, feature_log_get, | |
289 | 0, log_feature_unmark, log_feature_mark, log_feature_report), | |
290 | F_S(DOMAINNAME, 0, DOMAINNAME, 0), | |
291 | F_B(RELIABLE_CLOCK, 0, 0, 0), | |
292 | F_I(BUFFERPOOL, 0, 27000000, 0), | |
293 | F_B(HAS_FERGUSON_FLUSHER, 0, 0, 0), | |
294 | F_I(CLIENT_FLOOD, 0, 1024, 0), | |
295 | F_I(SERVER_PORT, FEAT_OPER, 4400, 0), | |
296 | F_B(NODEFAULTMOTD, 0, 1, 0), | |
297 | F_S(MOTD_BANNER, FEAT_NULL, 0, 0), | |
298 | F_S(PROVIDER, FEAT_NULL, 0, 0), | |
299 | F_B(KILL_IPMISMATCH, FEAT_OPER, 0, 0), | |
300 | F_B(IDLE_FROM_MSG, 0, 1, 0), | |
301 | F_B(HUB, 0, 0, 0), | |
302 | F_B(WALLOPS_OPER_ONLY, 0, 0, 0), | |
303 | F_B(NODNS, 0, 0, 0), | |
304 | F_N(RANDOM_SEED, FEAT_NODISP, random_seed_set, 0, 0, 0, 0, 0, 0), | |
305 | F_S(DEFAULT_LIST_PARAM, FEAT_NULL, 0, list_set_default), | |
306 | F_I(NICKNAMEHISTORYLENGTH, 0, 800, whowas_realloc), | |
307 | F_B(HOST_HIDING, 0, 1, 0), | |
308 | F_S(HIDDEN_HOST, FEAT_CASE, "users.undernet.org", 0), | |
309 | F_S(HIDDEN_IP, 0, "127.0.0.1", 0), | |
310 | F_B(AUTOINVISIBLE, 0, 1, 0), | |
311 | F_B(CONNEXIT_NOTICES, 0, 0, 0), | |
312 | F_B(USER_HIDECHANS, 0, 0, 0), | |
313 | F_B(USER_HIDEIDLETIME, 0, 0, 0), | |
314 | F_B(OPLEVELS, 0, 1, 0), | |
315 | F_B(ZANNELS, 0, 1, 0), | |
316 | F_B(LOCAL_CHANNELS, 0, 1, 0), | |
317 | F_B(TOPIC_BURST, 0, 0, 0), | |
318 | F_B(AUTOCHANMODES, 0, 1, 0), | |
319 | F_S(AUTOCHANMODES_LIST, FEAT_CASE | FEAT_NULL, "ntCN", 0), | |
320 | ||
321 | /* features that probably should not be touched */ | |
322 | F_I(KILLCHASETIMELIMIT, 0, 30, 0), | |
323 | F_I(MAXCHANNELSPERUSER, 0, 10, 0), | |
324 | F_I(NICKLEN, 0, 15, 0), | |
325 | F_I(AVBANLEN, 0, 40, 0), | |
326 | F_I(MAXBANS, 0, 45, 0), | |
327 | F_I(MAXSILES, 0, 15, 0), | |
328 | F_I(HANGONGOODLINK, 0, 300, 0), | |
329 | F_I(HANGONRETRYDELAY, 0, 10, 0), | |
330 | F_I(CONNECTTIMEOUT, 0, 90, 0), | |
331 | F_I(MAXIMUM_LINKS, 0, 1, init_class), /* reinit class 0 as needed */ | |
332 | F_I(PINGFREQUENCY, 0, 120, init_class), | |
333 | F_I(CONNECTFREQUENCY, 0, 600, init_class), | |
334 | F_I(DEFAULTMAXSENDQLENGTH, 0, 40000, init_class), | |
335 | F_I(GLINEMAXUSERCOUNT, 0, 20, 0), | |
336 | F_I(SOCKSENDBUF, 0, SERVER_TCP_WINDOW, 0), | |
337 | F_I(SOCKRECVBUF, 0, SERVER_TCP_WINDOW, 0), | |
338 | F_I(IPCHECK_CLONE_LIMIT, 0, 4, 0), | |
339 | F_I(IPCHECK_CLONE_PERIOD, 0, 40, 0), | |
340 | F_I(IPCHECK_CLONE_DELAY, 0, 600, 0), | |
341 | F_I(CHANNELLEN, 0, 200, 0), | |
342 | ||
343 | /* Some misc. default paths */ | |
344 | F_S(MPATH, FEAT_CASE | FEAT_MYOPER, "ircd.motd", motd_init), | |
345 | F_S(RPATH, FEAT_CASE | FEAT_MYOPER, "remote.motd", motd_init), | |
346 | F_S(PPATH, FEAT_CASE | FEAT_MYOPER | FEAT_READ, "ircd.pid", 0), | |
347 | ||
348 | /* Networking features */ | |
349 | F_I(TOS_SERVER, 0, 0x08, 0), | |
350 | F_I(TOS_CLIENT, 0, 0x08, 0), | |
351 | F_I(POLLS_PER_LOOP, 0, 200, 0), | |
352 | F_I(IRCD_RES_RETRIES, 0, 2, 0), | |
353 | F_I(IRCD_RES_TIMEOUT, 0, 4, 0), | |
354 | F_I(AUTH_TIMEOUT, 0, 9, 0), | |
355 | F_B(ANNOUNCE_INVITES, 0, 0, 0), | |
356 | ||
357 | /* features that affect all operators */ | |
358 | F_B(EXTENDED_CHECKCMD, 0, 0, 0), | |
359 | F_B(CONFIG_OPERCMDS, 0, 0, 0), | |
360 | F_B(SETHOST, 0, 0, 0), | |
361 | F_B(SETHOST_FREEFORM, 0, 0, 0), | |
362 | F_B(SETHOST_USER, 0, 0, 0), | |
363 | F_B(SETHOST_AUTO, 0, 0, 0), | |
364 | ||
365 | /* HEAD_IN_SAND Features */ | |
366 | F_B(HIS_SNOTICES, 0, 1, 0), | |
367 | F_B(HIS_SNOTICES_OPER_ONLY, 0, 1, 0), | |
368 | F_B(HIS_DEBUG_OPER_ONLY, 0, 1, 0), | |
369 | F_B(HIS_WALLOPS, 0, 1, 0), | |
370 | F_B(HIS_MAP, 0, 1, 0), | |
371 | F_B(HIS_LINKS, 0, 1, 0), | |
372 | F_B(HIS_TRACE, 0, 1, 0), | |
373 | F_B(HIS_STATS_a, 0, 1, 0), | |
374 | F_B(HIS_STATS_c, 0, 1, 0), | |
375 | F_B(HIS_STATS_d, 0, 1, 0), | |
376 | F_B(HIS_STATS_e, 0, 1, 0), | |
377 | F_B(HIS_STATS_f, 0, 1, 0), | |
378 | F_B(HIS_STATS_g, 0, 1, 0), | |
379 | F_B(HIS_STATS_i, 0, 1, 0), | |
380 | F_B(HIS_STATS_j, 0, 1, 0), | |
381 | F_B(HIS_STATS_J, 0, 1, 0), | |
382 | F_B(HIS_STATS_k, 0, 1, 0), | |
383 | F_B(HIS_STATS_l, 0, 1, 0), | |
384 | F_B(HIS_STATS_L, 0, 1, 0), | |
385 | F_B(HIS_STATS_M, 0, 1, 0), | |
386 | F_B(HIS_STATS_m, 0, 1, 0), | |
387 | F_B(HIS_STATS_o, 0, 1, 0), | |
388 | F_B(HIS_STATS_p, 0, 1, 0), | |
389 | F_B(HIS_STATS_q, 0, 1, 0), | |
390 | F_B(HIS_STATS_R, 0, 1, 0), | |
391 | F_B(HIS_STATS_r, 0, 1, 0), | |
392 | F_B(HIS_STATS_s, 0, 1, 0), | |
393 | F_B(HIS_STATS_t, 0, 1, 0), | |
394 | F_B(HIS_STATS_T, 0, 1, 0), | |
395 | F_B(HIS_STATS_u, 0, 0, 0), | |
396 | F_B(HIS_STATS_U, 0, 1, 0), | |
397 | F_B(HIS_STATS_v, 0, 1, 0), | |
398 | F_B(HIS_STATS_w, 0, 0, 0), | |
399 | F_B(HIS_STATS_x, 0, 1, 0), | |
400 | F_B(HIS_STATS_y, 0, 1, 0), | |
401 | F_B(HIS_STATS_z, 0, 1, 0), | |
402 | F_B(HIS_STATS_IAUTH, 0, 1, 0), | |
403 | F_B(HIS_WHOIS_SERVERNAME, 0, 1, 0), | |
404 | F_B(HIS_WHOIS_IDLETIME, 0, 1, 0), | |
405 | F_B(HIS_WHOIS_LOCALCHAN, 0, 1, 0), | |
406 | F_B(HIS_WHO_SERVERNAME, 0, 1, 0), | |
407 | F_B(HIS_WHO_HOPCOUNT, 0, 1, 0), | |
408 | F_B(HIS_WHO_FILTERIP, 0, 1, 0), | |
409 | F_B(HIS_MODEWHO, 0, 1, 0), | |
410 | F_B(HIS_BANWHO, 0, 1, 0), | |
411 | F_B(HIS_KILLWHO, 0, 1, 0), | |
412 | /* Asuka - Reimplement HEAD_IN_SAND_GLINE from Lain */ | |
413 | F_B(HIS_GLINE, 0, 1, 0), | |
414 | F_B(HIS_REWRITE, 0, 1, 0), | |
415 | F_I(HIS_REMOTE, 0, 1, 0), | |
416 | F_B(HIS_NETSPLIT, 0, 1, 0), | |
417 | F_S(HIS_SERVERNAME, 0, "*.undernet.org", feature_notify_servername), | |
418 | F_S(HIS_SERVERINFO, 0, "The Undernet Underworld", feature_notify_serverinfo), | |
419 | F_S(HIS_URLSERVERS, 0, "http://www.undernet.org/servers.php", 0), | |
420 | F_B(HIS_USERGLINE, 0, 1, 0), | |
421 | ||
422 | /* Misc. random stuff */ | |
423 | F_S(NETWORK, 0, "UnderNet", 0), | |
424 | F_S(URL_CLIENTS, 0, "ftp://ftp.undernet.org/pub/irc/clients", 0), | |
425 | ||
426 | #undef F_S | |
427 | #undef F_B | |
428 | #undef F_I | |
429 | #undef F_N | |
430 | { FEAT_LAST_F, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } | |
431 | }; | |
432 | ||
433 | /** Given a feature's identifier, look up the feature descriptor. | |
434 | * @param[in] from Client looking up feature, or NULL. | |
435 | * @param[in] feature Feature name to find. | |
436 | * @return Pointer to a FeatureDesc, or NULL if none was found. | |
437 | */ | |
438 | static struct FeatureDesc * | |
439 | feature_desc(struct Client* from, const char *feature) | |
440 | { | |
441 | int i; | |
442 | ||
443 | assert(0 != feature); | |
444 | ||
445 | for (i = 0; features[i].type; i++) /* find appropriate descriptor */ | |
446 | if (!strcmp(feature, features[i].type)) | |
447 | return &features[i]; | |
448 | ||
449 | Debug((DEBUG_ERROR, "Unknown feature \"%s\"", feature)); | |
450 | if (from) /* report an error */ | |
451 | send_reply(from, ERR_NOFEATURE, feature); | |
452 | else | |
453 | log_write(LS_CONFIG, L_ERROR, 0, "Unknown feature \"%s\"", feature); | |
454 | ||
455 | return 0; /* not found */ | |
456 | } | |
457 | ||
458 | /** Given a feature vector string, set the value of a feature. | |
459 | * @param[in] from Client trying to set the feature, or NULL. | |
460 | * @param[in] fields Parameters to set, starting with feature name. | |
461 | * @param[in] count Number of fields in \a fields. | |
462 | * @return Zero (or, theoretically, CPTR_KILLED). | |
463 | */ | |
464 | int | |
465 | feature_set(struct Client* from, const char* const* fields, int count) | |
466 | { | |
467 | int i, change = 0, tmp; | |
468 | const char *t_str; | |
469 | struct FeatureDesc *feat; | |
470 | ||
471 | if (from && !HasPriv(from, PRIV_SET)) | |
472 | return send_reply(from, ERR_NOPRIVILEGES); | |
473 | ||
474 | if (count < 1) { | |
475 | if (from) /* report an error in the number of arguments */ | |
476 | need_more_params(from, "SET"); | |
477 | else | |
478 | log_write(LS_CONFIG, L_ERROR, 0, "Not enough fields in F line"); | |
479 | } else if ((feat = feature_desc(from, fields[0]))) { /* find feature */ | |
480 | if (from && feat->flags & FEAT_READ) | |
481 | return send_reply(from, ERR_NOFEATURE, fields[0]); | |
482 | ||
483 | switch (feat->flags & FEAT_MASK) { | |
484 | case FEAT_NONE: | |
485 | if (feat->set && (i = (*feat->set)(from, fields + 1, count - 1))) { | |
486 | change++; /* feature handler wants a change recorded */ | |
487 | ||
488 | if (i > 0) /* call the set callback and do marking */ | |
489 | feat->flags |= FEAT_MARK; | |
490 | else /* i < 0 */ | |
491 | feat->flags &= ~FEAT_MARK; | |
492 | break; | |
493 | } | |
494 | ||
495 | case FEAT_INT: /* an integer value */ | |
496 | tmp = feat->v_int; /* detect changes... */ | |
497 | ||
498 | if (count < 2) { /* reset value */ | |
499 | feat->v_int = feat->def_int; | |
500 | feat->flags &= ~FEAT_MARK; | |
501 | } else { /* ok, figure out the value and whether to mark it */ | |
502 | feat->v_int = strtoul(fields[1], 0, 0); | |
503 | if (feat->v_int == feat->def_int) | |
504 | feat->flags &= ~FEAT_MARK; | |
505 | else | |
506 | feat->flags |= FEAT_MARK; | |
507 | } | |
508 | ||
509 | if (feat->v_int != tmp) /* check for change */ | |
510 | change++; | |
511 | break; | |
512 | ||
513 | case FEAT_BOOL: /* it's a boolean value--true or false */ | |
514 | tmp = feat->v_int; /* detect changes... */ | |
515 | ||
516 | if (count < 2) { /* reset value */ | |
517 | feat->v_int = feat->def_int; | |
518 | feat->flags &= ~FEAT_MARK; | |
519 | } else { /* figure out the value and whether to mark it */ | |
520 | if (!ircd_strncmp(fields[1], "TRUE", strlen(fields[1])) || | |
521 | !ircd_strncmp(fields[1], "YES", strlen(fields[1])) || | |
522 | (strlen(fields[1]) >= 2 && | |
523 | !ircd_strncmp(fields[1], "ON", strlen(fields[1])))) | |
524 | feat->v_int = 1; | |
525 | else if (!ircd_strncmp(fields[1], "FALSE", strlen(fields[1])) || | |
526 | !ircd_strncmp(fields[1], "NO", strlen(fields[1])) || | |
527 | (strlen(fields[1]) >= 2 && | |
528 | !ircd_strncmp(fields[1], "OFF", strlen(fields[1])))) | |
529 | feat->v_int = 0; | |
530 | else if (from) /* report an error... */ | |
531 | return send_reply(from, ERR_BADFEATVALUE, fields[1], feat->type); | |
532 | else { | |
533 | log_write(LS_CONFIG, L_ERROR, 0, "Bad value \"%s\" for feature %s", | |
534 | fields[1], feat->type); | |
535 | return 0; | |
536 | } | |
537 | ||
538 | if (feat->v_int == feat->def_int) /* figure out whether to mark it */ | |
539 | feat->flags &= ~FEAT_MARK; | |
540 | else | |
541 | feat->flags |= FEAT_MARK; | |
542 | } | |
543 | ||
544 | if (feat->v_int != tmp) /* check for change */ | |
545 | change++; | |
546 | break; | |
547 | ||
548 | case FEAT_STR: /* it's a string value */ | |
549 | if (count < 2) | |
550 | t_str = feat->def_str; /* changing to default */ | |
551 | else | |
552 | t_str = *fields[1] ? fields[1] : 0; | |
553 | ||
554 | if (!t_str && !(feat->flags & FEAT_NULL)) { /* NULL value permitted? */ | |
555 | if (from) | |
556 | return send_reply(from, ERR_BADFEATVALUE, "NULL", feat->type); | |
557 | else { | |
558 | log_write(LS_CONFIG, L_ERROR, 0, "Bad value \"NULL\" for feature %s", | |
559 | feat->type); | |
560 | return 0; | |
561 | } | |
562 | } | |
563 | ||
564 | if (t_str == feat->def_str || | |
565 | (t_str && feat->def_str && | |
566 | !(feat->flags & FEAT_CASE ? strcmp(t_str, feat->def_str) : | |
567 | ircd_strcmp(t_str, feat->def_str)))) { /* resetting to default */ | |
568 | if (feat->v_str != feat->def_str) { | |
569 | change++; /* change from previous value */ | |
570 | ||
571 | if (feat->v_str) | |
572 | MyFree(feat->v_str); /* free old value */ | |
573 | } | |
574 | ||
575 | feat->v_str = feat->def_str; /* very special... */ | |
576 | ||
577 | feat->flags &= ~FEAT_MARK; | |
578 | } else if (!t_str) { | |
579 | if (feat->v_str) { | |
580 | change++; /* change from previous value */ | |
581 | ||
582 | if (feat->v_str != feat->def_str) | |
583 | MyFree(feat->v_str); /* free old value */ | |
584 | } | |
585 | ||
586 | feat->v_str = 0; /* set it to NULL */ | |
587 | ||
588 | feat->flags |= FEAT_MARK; | |
589 | } else if (!feat->v_str || | |
590 | (feat->flags & FEAT_CASE ? strcmp(t_str, feat->v_str) : | |
591 | ircd_strcmp(t_str, feat->v_str))) { /* new value */ | |
592 | change++; /* change from previous value */ | |
593 | ||
594 | if (feat->v_str && feat->v_str != feat->def_str) | |
595 | MyFree(feat->v_str); /* free old value */ | |
596 | DupString(feat->v_str, t_str); /* store new value */ | |
597 | ||
598 | feat->flags |= FEAT_MARK; | |
599 | } else /* they match, but don't match the default */ | |
600 | feat->flags |= FEAT_MARK; | |
601 | break; | |
602 | } | |
603 | ||
604 | if (change && feat->notify) /* call change notify function */ | |
605 | (*feat->notify)(); | |
606 | } | |
607 | ||
608 | return 0; | |
609 | } | |
610 | ||
611 | /** Reset a feature to its default values. | |
612 | * @param[in] from Client trying to reset the feature, or NULL. | |
613 | * @param[in] fields Parameters to set, starting with feature name. | |
614 | * @param[in] count Number of fields in \a fields. | |
615 | * @return Zero (or, theoretically, CPTR_KILLED). | |
616 | */ | |
617 | int | |
618 | feature_reset(struct Client* from, const char* const* fields, int count) | |
619 | { | |
620 | int i, change = 0; | |
621 | struct FeatureDesc *feat; | |
622 | ||
623 | assert(0 != from); | |
624 | ||
625 | if (!HasPriv(from, PRIV_SET)) | |
626 | return send_reply(from, ERR_NOPRIVILEGES); | |
627 | ||
628 | if (count < 1) /* check arguments */ | |
629 | need_more_params(from, "RESET"); | |
630 | else if ((feat = feature_desc(from, fields[0]))) { /* get descriptor */ | |
631 | if (from && feat->flags & FEAT_READ) | |
632 | return send_reply(from, ERR_NOFEATURE, fields[0]); | |
633 | ||
634 | switch (feat->flags & FEAT_MASK) { | |
635 | case FEAT_NONE: /* None... */ | |
636 | if (feat->reset && (i = (*feat->reset)(from, fields + 1, count - 1))) { | |
637 | change++; /* feature handler wants a change recorded */ | |
638 | ||
639 | if (i > 0) /* call reset callback and parse mark return */ | |
640 | feat->flags |= FEAT_MARK; | |
641 | else /* i < 0 */ | |
642 | feat->flags &= ~FEAT_MARK; | |
643 | } | |
644 | break; | |
645 | ||
646 | case FEAT_INT: /* Integer... */ | |
647 | case FEAT_BOOL: /* Boolean... */ | |
648 | if (feat->v_int != feat->def_int) | |
649 | change++; /* change will be made */ | |
650 | ||
651 | feat->v_int = feat->def_int; /* set the default */ | |
652 | feat->flags &= ~FEAT_MARK; /* unmark it */ | |
653 | break; | |
654 | ||
655 | case FEAT_STR: /* string! */ | |
656 | if (feat->v_str != feat->def_str) { | |
657 | change++; /* change has been made */ | |
658 | if (feat->v_str) | |
659 | MyFree(feat->v_str); /* free old value */ | |
660 | } | |
661 | ||
662 | feat->v_str = feat->def_str; /* set it to default */ | |
663 | feat->flags &= ~FEAT_MARK; /* unmark it */ | |
664 | break; | |
665 | } | |
666 | ||
667 | if (change && feat->notify) /* call change notify function */ | |
668 | (*feat->notify)(); | |
669 | } | |
670 | ||
671 | return 0; | |
672 | } | |
673 | ||
674 | /** Gets the value of a specific feature and reports it to the user. | |
675 | * @param[in] from Client trying to get the feature. | |
676 | * @param[in] fields Parameters to set, starting with feature name. | |
677 | * @param[in] count Number of fields in \a fields. | |
678 | * @return Zero (or, theoretically, CPTR_KILLED). | |
679 | */ | |
680 | int | |
681 | feature_get(struct Client* from, const char* const* fields, int count) | |
682 | { | |
683 | struct FeatureDesc *feat; | |
684 | ||
685 | assert(0 != from); | |
686 | ||
687 | if (count < 1) /* check parameters */ | |
688 | need_more_params(from, "GET"); | |
689 | else if ((feat = feature_desc(from, fields[0]))) { | |
690 | if ((feat->flags & FEAT_NODISP) || | |
691 | (feat->flags & FEAT_MYOPER && !MyOper(from)) || | |
692 | (feat->flags & FEAT_OPER && !IsAnOper(from))) /* check privs */ | |
693 | return send_reply(from, ERR_NOPRIVILEGES); | |
694 | ||
695 | switch (feat->flags & FEAT_MASK) { | |
696 | case FEAT_NONE: /* none, call the callback... */ | |
697 | if (feat->get) /* if there's a callback, use it */ | |
698 | (*feat->get)(from, fields + 1, count - 1); | |
699 | break; | |
700 | ||
701 | case FEAT_INT: /* integer, report integer value */ | |
702 | send_reply(from, SND_EXPLICIT | RPL_FEATURE, | |
703 | ":Integer value of %s: %d", feat->type, feat->v_int); | |
704 | break; | |
705 | ||
706 | case FEAT_BOOL: /* boolean, report boolean value */ | |
707 | send_reply(from, SND_EXPLICIT | RPL_FEATURE, | |
708 | ":Boolean value of %s: %s", feat->type, | |
709 | feat->v_int ? "TRUE" : "FALSE"); | |
710 | break; | |
711 | ||
712 | case FEAT_STR: /* string, report string value */ | |
713 | if (feat->v_str) /* deal with null case */ | |
714 | send_reply(from, SND_EXPLICIT | RPL_FEATURE, | |
715 | ":String value of %s: %s", feat->type, feat->v_str); | |
716 | else | |
717 | send_reply(from, SND_EXPLICIT | RPL_FEATURE, | |
718 | ":String value for %s not set", feat->type); | |
719 | break; | |
720 | } | |
721 | } | |
722 | ||
723 | return 0; | |
724 | } | |
725 | ||
726 | /** Called before reading the .conf to clear all dirty marks. */ | |
727 | void | |
728 | feature_unmark(void) | |
729 | { | |
730 | int i; | |
731 | ||
732 | for (i = 0; features[i].type; i++) { | |
733 | features[i].flags &= ~FEAT_MARK; /* clear the marks... */ | |
734 | if (features[i].unmark) /* call the unmark callback if necessary */ | |
735 | (*features[i].unmark)(); | |
736 | } | |
737 | } | |
738 | ||
739 | /** Called after reading the .conf to reset unmodified values to defaults. */ | |
740 | void | |
741 | feature_mark(void) | |
742 | { | |
743 | int i, change; | |
744 | ||
745 | for (i = 0; features[i].type; i++) { | |
746 | change = 0; | |
747 | ||
748 | switch (features[i].flags & FEAT_MASK) { | |
749 | case FEAT_NONE: | |
750 | if (features[i].mark && | |
751 | (*features[i].mark)(features[i].flags & FEAT_MARK ? 1 : 0)) | |
752 | change++; /* feature handler wants a change recorded */ | |
753 | break; | |
754 | ||
755 | case FEAT_INT: /* Integers or Booleans... */ | |
756 | case FEAT_BOOL: | |
757 | if (!(features[i].flags & FEAT_MARK)) { /* not changed? */ | |
758 | if (features[i].v_int != features[i].def_int) | |
759 | change++; /* we're making a change */ | |
760 | features[i].v_int = features[i].def_int; | |
761 | } | |
762 | break; | |
763 | ||
764 | case FEAT_STR: /* strings... */ | |
765 | if (!(features[i].flags & FEAT_MARK)) { /* not changed? */ | |
766 | if (features[i].v_str != features[i].def_str) { | |
767 | change++; /* we're making a change */ | |
768 | if (features[i].v_str) | |
769 | MyFree(features[i].v_str); /* free old value */ | |
770 | } | |
771 | features[i].v_str = features[i].def_str; | |
772 | } | |
773 | break; | |
774 | } | |
775 | ||
776 | if (change && features[i].notify) | |
777 | (*features[i].notify)(); /* call change notify function */ | |
778 | } | |
779 | } | |
780 | ||
781 | /** Initialize the features subsystem. */ | |
782 | void | |
783 | feature_init(void) | |
784 | { | |
785 | int i; | |
786 | ||
787 | for (i = 0; features[i].type; i++) { | |
788 | switch (features[i].flags & FEAT_MASK) { | |
789 | case FEAT_NONE: /* you're on your own */ | |
790 | break; | |
791 | ||
792 | case FEAT_INT: /* Integers or Booleans... */ | |
793 | case FEAT_BOOL: | |
794 | features[i].v_int = features[i].def_int; | |
795 | break; | |
796 | ||
797 | case FEAT_STR: /* Strings */ | |
798 | features[i].v_str = features[i].def_str; | |
799 | assert(features[i].def_str || (features[i].flags & FEAT_NULL)); | |
800 | break; | |
801 | } | |
802 | } | |
803 | ||
804 | cli_magic(&his) = CLIENT_MAGIC; | |
805 | cli_status(&his) = STAT_SERVER; | |
806 | feature_notify_servername(); | |
807 | feature_notify_serverinfo(); | |
808 | } | |
809 | ||
810 | /** Report all F-lines to a user. | |
811 | * @param[in] to Client requesting statistics. | |
812 | * @param[in] sd Stats descriptor for request (ignored). | |
813 | * @param[in] param Extra parameter from user (ignored). | |
814 | */ | |
815 | void | |
816 | feature_report(struct Client* to, const struct StatDesc* sd, char* param) | |
817 | { | |
818 | int i; | |
819 | ||
820 | for (i = 0; features[i].type; i++) { | |
821 | if ((features[i].flags & FEAT_NODISP) || | |
822 | (features[i].flags & FEAT_MYOPER && !MyOper(to)) || | |
823 | (features[i].flags & FEAT_OPER && !IsAnOper(to))) | |
824 | continue; /* skip this one */ | |
825 | ||
826 | switch (features[i].flags & FEAT_MASK) { | |
827 | case FEAT_NONE: | |
828 | if (features[i].report) /* let the callback handle this */ | |
829 | (*features[i].report)(to, features[i].flags & FEAT_MARK ? 1 : 0); | |
830 | break; | |
831 | ||
832 | ||
833 | case FEAT_INT: /* Report an F-line with integer values */ | |
834 | if (features[i].flags & FEAT_MARK) /* it's been changed */ | |
835 | send_reply(to, SND_EXPLICIT | RPL_STATSFLINE, "F %s %d", | |
836 | features[i].type, features[i].v_int); | |
837 | break; | |
838 | ||
839 | case FEAT_BOOL: /* Report an F-line with boolean values */ | |
840 | if (features[i].flags & FEAT_MARK) /* it's been changed */ | |
841 | send_reply(to, SND_EXPLICIT | RPL_STATSFLINE, "F %s %s", | |
842 | features[i].type, features[i].v_int ? "TRUE" : "FALSE"); | |
843 | break; | |
844 | ||
845 | case FEAT_STR: /* Report an F-line with string values */ | |
846 | if (features[i].flags & FEAT_MARK) { /* it's been changed */ | |
847 | if (features[i].v_str) | |
848 | send_reply(to, SND_EXPLICIT | RPL_STATSFLINE, "F %s %s", | |
849 | features[i].type, features[i].v_str); | |
850 | else /* Actually, F:<type> would reset it; you want F:<type>: */ | |
851 | send_reply(to, SND_EXPLICIT | RPL_STATSFLINE, "F %s", | |
852 | features[i].type); | |
853 | } | |
854 | break; | |
855 | } | |
856 | } | |
857 | } | |
858 | ||
859 | /** Return a feature's integer value. | |
860 | * @param[in] feat &Feature identifier. | |
861 | * @return Integer value of feature. | |
862 | */ | |
863 | int | |
864 | feature_int(enum Feature feat) | |
865 | { | |
866 | assert(features[feat].feat == feat); | |
867 | assert((features[feat].flags & FEAT_MASK) == FEAT_INT); | |
868 | ||
869 | return features[feat].v_int; | |
870 | } | |
871 | ||
872 | /** Return a feature's boolean value. | |
873 | * @param[in] feat &Feature identifier. | |
874 | * @return Boolean value of feature. | |
875 | */ | |
876 | int | |
877 | feature_bool(enum Feature feat) | |
878 | { | |
879 | assert(feat <= FEAT_LAST_F); | |
880 | if (FEAT_LAST_F < feat) | |
881 | return 0; | |
882 | assert(features[feat].feat == feat); | |
883 | assert((features[feat].flags & FEAT_MASK) == FEAT_BOOL); | |
884 | ||
885 | return features[feat].v_int; | |
886 | } | |
887 | ||
888 | /** Return a feature's string value. | |
889 | * @param[in] feat &Feature identifier. | |
890 | * @return String value of feature. | |
891 | */ | |
892 | const char * | |
893 | feature_str(enum Feature feat) | |
894 | { | |
895 | assert(features[feat].feat == feat); | |
896 | assert((features[feat].flags & FEAT_MASK) == FEAT_STR); | |
897 | ||
898 | return features[feat].v_str; | |
899 | } |