]>
Commit | Line | Data |
---|---|---|
189935b1 | 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. | |
052b069e | 21 | * @version $Id: ircd_features.c,v 1.50.2.4 2006/01/10 01:23:28 entrope Exp $ |
189935b1 | 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(CONNEXIT_NOTICES, 0, 0, 0), | |
311 | F_B(OPLEVELS, 0, 1, 0), | |
052b069e | 312 | F_B(ZANNELS, 0, 1, 0), |
189935b1 | 313 | F_B(LOCAL_CHANNELS, 0, 1, 0), |
314 | F_B(TOPIC_BURST, 0, 0, 0), | |
315 | ||
316 | /* features that probably should not be touched */ | |
317 | F_I(KILLCHASETIMELIMIT, 0, 30, 0), | |
318 | F_I(MAXCHANNELSPERUSER, 0, 10, 0), | |
319 | F_I(NICKLEN, 0, 12, 0), | |
320 | F_I(AVBANLEN, 0, 40, 0), | |
321 | F_I(MAXBANS, 0, 45, 0), | |
322 | F_I(MAXSILES, 0, 15, 0), | |
323 | F_I(HANGONGOODLINK, 0, 300, 0), | |
324 | F_I(HANGONRETRYDELAY, 0, 10, 0), | |
325 | F_I(CONNECTTIMEOUT, 0, 90, 0), | |
326 | F_I(MAXIMUM_LINKS, 0, 1, init_class), /* reinit class 0 as needed */ | |
327 | F_I(PINGFREQUENCY, 0, 120, init_class), | |
328 | F_I(CONNECTFREQUENCY, 0, 600, init_class), | |
329 | F_I(DEFAULTMAXSENDQLENGTH, 0, 40000, init_class), | |
330 | F_I(GLINEMAXUSERCOUNT, 0, 20, 0), | |
331 | F_I(SOCKSENDBUF, 0, SERVER_TCP_WINDOW, 0), | |
332 | F_I(SOCKRECVBUF, 0, SERVER_TCP_WINDOW, 0), | |
333 | F_I(IPCHECK_CLONE_LIMIT, 0, 4, 0), | |
334 | F_I(IPCHECK_CLONE_PERIOD, 0, 40, 0), | |
335 | F_I(IPCHECK_CLONE_DELAY, 0, 600, 0), | |
336 | F_I(CHANNELLEN, 0, 200, 0), | |
337 | ||
338 | /* Some misc. default paths */ | |
339 | F_S(MPATH, FEAT_CASE | FEAT_MYOPER, "ircd.motd", motd_init), | |
340 | F_S(RPATH, FEAT_CASE | FEAT_MYOPER, "remote.motd", motd_init), | |
341 | F_S(PPATH, FEAT_CASE | FEAT_MYOPER | FEAT_READ, "ircd.pid", 0), | |
342 | ||
343 | /* Networking features */ | |
344 | F_I(TOS_SERVER, 0, 0x08, 0), | |
345 | F_I(TOS_CLIENT, 0, 0x08, 0), | |
346 | F_I(POLLS_PER_LOOP, 0, 200, 0), | |
347 | F_I(IRCD_RES_RETRIES, 0, 2, 0), | |
348 | F_I(IRCD_RES_TIMEOUT, 0, 4, 0), | |
349 | F_I(AUTH_TIMEOUT, 0, 9, 0), | |
350 | F_B(ANNOUNCE_INVITES, 0, 0, 0), | |
351 | ||
352 | /* features that affect all operators */ | |
353 | F_B(CONFIG_OPERCMDS, 0, 0, 0), | |
354 | ||
355 | /* HEAD_IN_SAND Features */ | |
356 | F_B(HIS_SNOTICES, 0, 1, 0), | |
357 | F_B(HIS_SNOTICES_OPER_ONLY, 0, 1, 0), | |
358 | F_B(HIS_DEBUG_OPER_ONLY, 0, 1, 0), | |
359 | F_B(HIS_WALLOPS, 0, 1, 0), | |
360 | F_B(HIS_MAP, 0, 1, 0), | |
361 | F_B(HIS_LINKS, 0, 1, 0), | |
362 | F_B(HIS_TRACE, 0, 1, 0), | |
363 | F_B(HIS_STATS_a, 0, 1, 0), | |
364 | F_B(HIS_STATS_c, 0, 1, 0), | |
365 | F_B(HIS_STATS_d, 0, 1, 0), | |
366 | F_B(HIS_STATS_e, 0, 1, 0), | |
367 | F_B(HIS_STATS_f, 0, 1, 0), | |
368 | F_B(HIS_STATS_g, 0, 1, 0), | |
369 | F_B(HIS_STATS_i, 0, 1, 0), | |
370 | F_B(HIS_STATS_j, 0, 1, 0), | |
371 | F_B(HIS_STATS_J, 0, 1, 0), | |
372 | F_B(HIS_STATS_k, 0, 1, 0), | |
373 | F_B(HIS_STATS_l, 0, 1, 0), | |
374 | F_B(HIS_STATS_L, 0, 1, 0), | |
375 | F_B(HIS_STATS_M, 0, 1, 0), | |
376 | F_B(HIS_STATS_m, 0, 1, 0), | |
377 | F_B(HIS_STATS_o, 0, 1, 0), | |
378 | F_B(HIS_STATS_p, 0, 1, 0), | |
379 | F_B(HIS_STATS_q, 0, 1, 0), | |
380 | F_B(HIS_STATS_R, 0, 1, 0), | |
381 | F_B(HIS_STATS_r, 0, 1, 0), | |
382 | F_B(HIS_STATS_t, 0, 1, 0), | |
383 | F_B(HIS_STATS_T, 0, 1, 0), | |
384 | F_B(HIS_STATS_u, 0, 0, 0), | |
385 | F_B(HIS_STATS_U, 0, 1, 0), | |
386 | F_B(HIS_STATS_v, 0, 1, 0), | |
387 | F_B(HIS_STATS_w, 0, 0, 0), | |
388 | F_B(HIS_STATS_x, 0, 1, 0), | |
389 | F_B(HIS_STATS_y, 0, 1, 0), | |
390 | F_B(HIS_STATS_z, 0, 1, 0), | |
391 | F_B(HIS_WHOIS_SERVERNAME, 0, 1, 0), | |
392 | F_B(HIS_WHOIS_IDLETIME, 0, 1, 0), | |
393 | F_B(HIS_WHOIS_LOCALCHAN, 0, 1, 0), | |
394 | F_B(HIS_WHO_SERVERNAME, 0, 1, 0), | |
395 | F_B(HIS_WHO_HOPCOUNT, 0, 1, 0), | |
052b069e | 396 | F_B(HIS_MODEWHO, 0, 1, 0), |
189935b1 | 397 | F_B(HIS_BANWHO, 0, 1, 0), |
398 | F_B(HIS_KILLWHO, 0, 1, 0), | |
399 | F_B(HIS_REWRITE, 0, 1, 0), | |
400 | F_I(HIS_REMOTE, 0, 1, 0), | |
401 | F_B(HIS_NETSPLIT, 0, 1, 0), | |
402 | F_S(HIS_SERVERNAME, 0, "*.undernet.org", feature_notify_servername), | |
403 | F_S(HIS_SERVERINFO, 0, "The Undernet Underworld", feature_notify_serverinfo), | |
404 | F_S(HIS_URLSERVERS, 0, "http://www.undernet.org/servers.php", 0), | |
405 | ||
406 | /* Misc. random stuff */ | |
407 | F_S(NETWORK, 0, "UnderNet", 0), | |
408 | F_S(URL_CLIENTS, 0, "ftp://ftp.undernet.org/pub/irc/clients", 0), | |
409 | ||
410 | #undef F_S | |
411 | #undef F_B | |
412 | #undef F_I | |
413 | #undef F_N | |
414 | { FEAT_LAST_F, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } | |
415 | }; | |
416 | ||
417 | /** Given a feature's identifier, look up the feature descriptor. | |
418 | * @param[in] from Client looking up feature, or NULL. | |
419 | * @param[in] feature Feature name to find. | |
420 | * @return Pointer to a FeatureDesc, or NULL if none was found. | |
421 | */ | |
422 | static struct FeatureDesc * | |
423 | feature_desc(struct Client* from, const char *feature) | |
424 | { | |
425 | int i; | |
426 | ||
427 | assert(0 != feature); | |
428 | ||
429 | for (i = 0; features[i].type; i++) /* find appropriate descriptor */ | |
430 | if (!strcmp(feature, features[i].type)) | |
431 | return &features[i]; | |
432 | ||
433 | Debug((DEBUG_ERROR, "Unknown feature \"%s\"", feature)); | |
434 | if (from) /* report an error */ | |
435 | send_reply(from, ERR_NOFEATURE, feature); | |
436 | else | |
437 | log_write(LS_CONFIG, L_ERROR, 0, "Unknown feature \"%s\"", feature); | |
438 | ||
439 | return 0; /* not found */ | |
440 | } | |
441 | ||
442 | /** Given a feature vector string, set the value of a feature. | |
443 | * @param[in] from Client trying to set the feature, or NULL. | |
444 | * @param[in] fields Parameters to set, starting with feature name. | |
445 | * @param[in] count Number of fields in \a fields. | |
446 | * @return Zero (or, theoretically, CPTR_KILLED). | |
447 | */ | |
448 | int | |
449 | feature_set(struct Client* from, const char* const* fields, int count) | |
450 | { | |
451 | int i, change = 0, tmp; | |
452 | const char *t_str; | |
453 | struct FeatureDesc *feat; | |
454 | ||
455 | if (from && !HasPriv(from, PRIV_SET)) | |
456 | return send_reply(from, ERR_NOPRIVILEGES); | |
457 | ||
458 | if (count < 1) { | |
459 | if (from) /* report an error in the number of arguments */ | |
460 | need_more_params(from, "SET"); | |
461 | else | |
462 | log_write(LS_CONFIG, L_ERROR, 0, "Not enough fields in F line"); | |
463 | } else if ((feat = feature_desc(from, fields[0]))) { /* find feature */ | |
464 | if (from && feat->flags & FEAT_READ) | |
465 | return send_reply(from, ERR_NOFEATURE, fields[0]); | |
466 | ||
467 | switch (feat->flags & FEAT_MASK) { | |
468 | case FEAT_NONE: | |
469 | if (feat->set && (i = (*feat->set)(from, fields + 1, count - 1))) { | |
470 | change++; /* feature handler wants a change recorded */ | |
471 | ||
472 | if (i > 0) /* call the set callback and do marking */ | |
473 | feat->flags |= FEAT_MARK; | |
474 | else /* i < 0 */ | |
475 | feat->flags &= ~FEAT_MARK; | |
476 | break; | |
477 | } | |
478 | ||
479 | case FEAT_INT: /* an integer value */ | |
480 | tmp = feat->v_int; /* detect changes... */ | |
481 | ||
482 | if (count < 2) { /* reset value */ | |
483 | feat->v_int = feat->def_int; | |
484 | feat->flags &= ~FEAT_MARK; | |
485 | } else { /* ok, figure out the value and whether to mark it */ | |
486 | feat->v_int = strtoul(fields[1], 0, 0); | |
487 | if (feat->v_int == feat->def_int) | |
488 | feat->flags &= ~FEAT_MARK; | |
489 | else | |
490 | feat->flags |= FEAT_MARK; | |
491 | } | |
492 | ||
493 | if (feat->v_int != tmp) /* check for change */ | |
494 | change++; | |
495 | break; | |
496 | ||
497 | case FEAT_BOOL: /* it's a boolean value--true or false */ | |
498 | tmp = feat->v_int; /* detect changes... */ | |
499 | ||
500 | if (count < 2) { /* reset value */ | |
501 | feat->v_int = feat->def_int; | |
502 | feat->flags &= ~FEAT_MARK; | |
503 | } else { /* figure out the value and whether to mark it */ | |
504 | if (!ircd_strncmp(fields[1], "TRUE", strlen(fields[1])) || | |
505 | !ircd_strncmp(fields[1], "YES", strlen(fields[1])) || | |
506 | (strlen(fields[1]) >= 2 && | |
507 | !ircd_strncmp(fields[1], "ON", strlen(fields[1])))) | |
508 | feat->v_int = 1; | |
509 | else if (!ircd_strncmp(fields[1], "FALSE", strlen(fields[1])) || | |
510 | !ircd_strncmp(fields[1], "NO", strlen(fields[1])) || | |
511 | (strlen(fields[1]) >= 2 && | |
512 | !ircd_strncmp(fields[1], "OFF", strlen(fields[1])))) | |
513 | feat->v_int = 0; | |
514 | else if (from) /* report an error... */ | |
515 | return send_reply(from, ERR_BADFEATVALUE, fields[1], feat->type); | |
516 | else { | |
517 | log_write(LS_CONFIG, L_ERROR, 0, "Bad value \"%s\" for feature %s", | |
518 | fields[1], feat->type); | |
519 | return 0; | |
520 | } | |
521 | ||
522 | if (feat->v_int == feat->def_int) /* figure out whether to mark it */ | |
523 | feat->flags &= ~FEAT_MARK; | |
524 | else | |
525 | feat->flags |= FEAT_MARK; | |
526 | } | |
527 | ||
528 | if (feat->v_int != tmp) /* check for change */ | |
529 | change++; | |
530 | break; | |
531 | ||
532 | case FEAT_STR: /* it's a string value */ | |
533 | if (count < 2) | |
534 | t_str = feat->def_str; /* changing to default */ | |
535 | else | |
536 | t_str = *fields[1] ? fields[1] : 0; | |
537 | ||
538 | if (!t_str && !(feat->flags & FEAT_NULL)) { /* NULL value permitted? */ | |
539 | if (from) | |
540 | return send_reply(from, ERR_BADFEATVALUE, "NULL", feat->type); | |
541 | else { | |
542 | log_write(LS_CONFIG, L_ERROR, 0, "Bad value \"NULL\" for feature %s", | |
543 | feat->type); | |
544 | return 0; | |
545 | } | |
546 | } | |
547 | ||
548 | if (t_str == feat->def_str || | |
549 | (t_str && feat->def_str && | |
550 | !(feat->flags & FEAT_CASE ? strcmp(t_str, feat->def_str) : | |
551 | ircd_strcmp(t_str, feat->def_str)))) { /* resetting to default */ | |
552 | if (feat->v_str != feat->def_str) { | |
553 | change++; /* change from previous value */ | |
554 | ||
555 | if (feat->v_str) | |
556 | MyFree(feat->v_str); /* free old value */ | |
557 | } | |
558 | ||
559 | feat->v_str = feat->def_str; /* very special... */ | |
560 | ||
561 | feat->flags &= ~FEAT_MARK; | |
562 | } else if (!t_str) { | |
563 | if (feat->v_str) { | |
564 | change++; /* change from previous value */ | |
565 | ||
566 | if (feat->v_str != feat->def_str) | |
567 | MyFree(feat->v_str); /* free old value */ | |
568 | } | |
569 | ||
570 | feat->v_str = 0; /* set it to NULL */ | |
571 | ||
572 | feat->flags |= FEAT_MARK; | |
573 | } else if (!feat->v_str || | |
574 | (feat->flags & FEAT_CASE ? strcmp(t_str, feat->v_str) : | |
575 | ircd_strcmp(t_str, feat->v_str))) { /* new value */ | |
576 | change++; /* change from previous value */ | |
577 | ||
578 | if (feat->v_str && feat->v_str != feat->def_str) | |
579 | MyFree(feat->v_str); /* free old value */ | |
580 | DupString(feat->v_str, t_str); /* store new value */ | |
581 | ||
582 | feat->flags |= FEAT_MARK; | |
583 | } else /* they match, but don't match the default */ | |
584 | feat->flags |= FEAT_MARK; | |
585 | break; | |
586 | } | |
587 | ||
588 | if (change && feat->notify) /* call change notify function */ | |
589 | (*feat->notify)(); | |
590 | } | |
591 | ||
592 | return 0; | |
593 | } | |
594 | ||
595 | /** Reset a feature to its default values. | |
596 | * @param[in] from Client trying to reset the feature, or NULL. | |
597 | * @param[in] fields Parameters to set, starting with feature name. | |
598 | * @param[in] count Number of fields in \a fields. | |
599 | * @return Zero (or, theoretically, CPTR_KILLED). | |
600 | */ | |
601 | int | |
602 | feature_reset(struct Client* from, const char* const* fields, int count) | |
603 | { | |
604 | int i, change = 0; | |
605 | struct FeatureDesc *feat; | |
606 | ||
607 | assert(0 != from); | |
608 | ||
609 | if (!HasPriv(from, PRIV_SET)) | |
610 | return send_reply(from, ERR_NOPRIVILEGES); | |
611 | ||
612 | if (count < 1) /* check arguments */ | |
613 | need_more_params(from, "RESET"); | |
614 | else if ((feat = feature_desc(from, fields[0]))) { /* get descriptor */ | |
615 | if (from && feat->flags & FEAT_READ) | |
616 | return send_reply(from, ERR_NOFEATURE, fields[0]); | |
617 | ||
618 | switch (feat->flags & FEAT_MASK) { | |
619 | case FEAT_NONE: /* None... */ | |
620 | if (feat->reset && (i = (*feat->reset)(from, fields + 1, count - 1))) { | |
621 | change++; /* feature handler wants a change recorded */ | |
622 | ||
623 | if (i > 0) /* call reset callback and parse mark return */ | |
624 | feat->flags |= FEAT_MARK; | |
625 | else /* i < 0 */ | |
626 | feat->flags &= ~FEAT_MARK; | |
627 | } | |
628 | break; | |
629 | ||
630 | case FEAT_INT: /* Integer... */ | |
631 | case FEAT_BOOL: /* Boolean... */ | |
632 | if (feat->v_int != feat->def_int) | |
633 | change++; /* change will be made */ | |
634 | ||
635 | feat->v_int = feat->def_int; /* set the default */ | |
636 | feat->flags &= ~FEAT_MARK; /* unmark it */ | |
637 | break; | |
638 | ||
639 | case FEAT_STR: /* string! */ | |
640 | if (feat->v_str != feat->def_str) { | |
641 | change++; /* change has been made */ | |
642 | if (feat->v_str) | |
643 | MyFree(feat->v_str); /* free old value */ | |
644 | } | |
645 | ||
646 | feat->v_str = feat->def_str; /* set it to default */ | |
647 | feat->flags &= ~FEAT_MARK; /* unmark it */ | |
648 | break; | |
649 | } | |
650 | ||
651 | if (change && feat->notify) /* call change notify function */ | |
652 | (*feat->notify)(); | |
653 | } | |
654 | ||
655 | return 0; | |
656 | } | |
657 | ||
658 | /** Gets the value of a specific feature and reports it to the user. | |
659 | * @param[in] from Client trying to get the feature. | |
660 | * @param[in] fields Parameters to set, starting with feature name. | |
661 | * @param[in] count Number of fields in \a fields. | |
662 | * @return Zero (or, theoretically, CPTR_KILLED). | |
663 | */ | |
664 | int | |
665 | feature_get(struct Client* from, const char* const* fields, int count) | |
666 | { | |
667 | struct FeatureDesc *feat; | |
668 | ||
669 | assert(0 != from); | |
670 | ||
671 | if (count < 1) /* check parameters */ | |
672 | need_more_params(from, "GET"); | |
673 | else if ((feat = feature_desc(from, fields[0]))) { | |
674 | if ((feat->flags & FEAT_NODISP) || | |
675 | (feat->flags & FEAT_MYOPER && !MyOper(from)) || | |
676 | (feat->flags & FEAT_OPER && !IsAnOper(from))) /* check privs */ | |
677 | return send_reply(from, ERR_NOPRIVILEGES); | |
678 | ||
679 | switch (feat->flags & FEAT_MASK) { | |
680 | case FEAT_NONE: /* none, call the callback... */ | |
681 | if (feat->get) /* if there's a callback, use it */ | |
682 | (*feat->get)(from, fields + 1, count - 1); | |
683 | break; | |
684 | ||
685 | case FEAT_INT: /* integer, report integer value */ | |
686 | send_reply(from, SND_EXPLICIT | RPL_FEATURE, | |
687 | ":Integer value of %s: %d", feat->type, feat->v_int); | |
688 | break; | |
689 | ||
690 | case FEAT_BOOL: /* boolean, report boolean value */ | |
691 | send_reply(from, SND_EXPLICIT | RPL_FEATURE, | |
692 | ":Boolean value of %s: %s", feat->type, | |
693 | feat->v_int ? "TRUE" : "FALSE"); | |
694 | break; | |
695 | ||
696 | case FEAT_STR: /* string, report string value */ | |
697 | if (feat->v_str) /* deal with null case */ | |
698 | send_reply(from, SND_EXPLICIT | RPL_FEATURE, | |
699 | ":String value of %s: %s", feat->type, feat->v_str); | |
700 | else | |
701 | send_reply(from, SND_EXPLICIT | RPL_FEATURE, | |
702 | ":String value for %s not set", feat->type); | |
703 | break; | |
704 | } | |
705 | } | |
706 | ||
707 | return 0; | |
708 | } | |
709 | ||
710 | /** Called before reading the .conf to clear all dirty marks. */ | |
711 | void | |
712 | feature_unmark(void) | |
713 | { | |
714 | int i; | |
715 | ||
716 | for (i = 0; features[i].type; i++) { | |
717 | features[i].flags &= ~FEAT_MARK; /* clear the marks... */ | |
718 | if (features[i].unmark) /* call the unmark callback if necessary */ | |
719 | (*features[i].unmark)(); | |
720 | } | |
721 | } | |
722 | ||
723 | /** Called after reading the .conf to reset unmodified values to defaults. */ | |
724 | void | |
725 | feature_mark(void) | |
726 | { | |
727 | int i, change; | |
728 | ||
729 | for (i = 0; features[i].type; i++) { | |
730 | change = 0; | |
731 | ||
732 | switch (features[i].flags & FEAT_MASK) { | |
733 | case FEAT_NONE: | |
734 | if (features[i].mark && | |
735 | (*features[i].mark)(features[i].flags & FEAT_MARK ? 1 : 0)) | |
736 | change++; /* feature handler wants a change recorded */ | |
737 | break; | |
738 | ||
739 | case FEAT_INT: /* Integers or Booleans... */ | |
740 | case FEAT_BOOL: | |
741 | if (!(features[i].flags & FEAT_MARK)) { /* not changed? */ | |
742 | if (features[i].v_int != features[i].def_int) | |
743 | change++; /* we're making a change */ | |
744 | features[i].v_int = features[i].def_int; | |
745 | } | |
746 | break; | |
747 | ||
748 | case FEAT_STR: /* strings... */ | |
749 | if (!(features[i].flags & FEAT_MARK)) { /* not changed? */ | |
750 | if (features[i].v_str != features[i].def_str) { | |
751 | change++; /* we're making a change */ | |
752 | if (features[i].v_str) | |
753 | MyFree(features[i].v_str); /* free old value */ | |
754 | } | |
755 | features[i].v_str = features[i].def_str; | |
756 | } | |
757 | break; | |
758 | } | |
759 | ||
760 | if (change && features[i].notify) | |
761 | (*features[i].notify)(); /* call change notify function */ | |
762 | } | |
763 | } | |
764 | ||
765 | /** Initialize the features subsystem. */ | |
766 | void | |
767 | feature_init(void) | |
768 | { | |
769 | int i; | |
770 | ||
771 | for (i = 0; features[i].type; i++) { | |
772 | switch (features[i].flags & FEAT_MASK) { | |
773 | case FEAT_NONE: /* you're on your own */ | |
774 | break; | |
775 | ||
776 | case FEAT_INT: /* Integers or Booleans... */ | |
777 | case FEAT_BOOL: | |
778 | features[i].v_int = features[i].def_int; | |
779 | break; | |
780 | ||
781 | case FEAT_STR: /* Strings */ | |
782 | features[i].v_str = features[i].def_str; | |
783 | assert(features[i].def_str || (features[i].flags & FEAT_NULL)); | |
784 | break; | |
785 | } | |
786 | } | |
787 | ||
788 | cli_magic(&his) = CLIENT_MAGIC; | |
789 | cli_status(&his) = STAT_SERVER; | |
790 | feature_notify_servername(); | |
791 | feature_notify_serverinfo(); | |
792 | } | |
793 | ||
794 | /** Report all F-lines to a user. | |
795 | * @param[in] to Client requesting statistics. | |
796 | * @param[in] sd Stats descriptor for request (ignored). | |
797 | * @param[in] param Extra parameter from user (ignored). | |
798 | */ | |
799 | void | |
800 | feature_report(struct Client* to, const struct StatDesc* sd, char* param) | |
801 | { | |
802 | int i; | |
803 | ||
804 | for (i = 0; features[i].type; i++) { | |
805 | if ((features[i].flags & FEAT_NODISP) || | |
806 | (features[i].flags & FEAT_MYOPER && !MyOper(to)) || | |
807 | (features[i].flags & FEAT_OPER && !IsAnOper(to))) | |
808 | continue; /* skip this one */ | |
809 | ||
810 | switch (features[i].flags & FEAT_MASK) { | |
811 | case FEAT_NONE: | |
812 | if (features[i].report) /* let the callback handle this */ | |
813 | (*features[i].report)(to, features[i].flags & FEAT_MARK ? 1 : 0); | |
814 | break; | |
815 | ||
816 | ||
817 | case FEAT_INT: /* Report an F-line with integer values */ | |
818 | if (features[i].flags & FEAT_MARK) /* it's been changed */ | |
819 | send_reply(to, SND_EXPLICIT | RPL_STATSFLINE, "F %s %d", | |
820 | features[i].type, features[i].v_int); | |
821 | break; | |
822 | ||
823 | case FEAT_BOOL: /* Report an F-line with boolean values */ | |
824 | if (features[i].flags & FEAT_MARK) /* it's been changed */ | |
825 | send_reply(to, SND_EXPLICIT | RPL_STATSFLINE, "F %s %s", | |
826 | features[i].type, features[i].v_int ? "TRUE" : "FALSE"); | |
827 | break; | |
828 | ||
829 | case FEAT_STR: /* Report an F-line with string values */ | |
830 | if (features[i].flags & FEAT_MARK) { /* it's been changed */ | |
831 | if (features[i].v_str) | |
832 | send_reply(to, SND_EXPLICIT | RPL_STATSFLINE, "F %s %s", | |
833 | features[i].type, features[i].v_str); | |
834 | else /* Actually, F:<type> would reset it; you want F:<type>: */ | |
835 | send_reply(to, SND_EXPLICIT | RPL_STATSFLINE, "F %s", | |
836 | features[i].type); | |
837 | } | |
838 | break; | |
839 | } | |
840 | } | |
841 | } | |
842 | ||
843 | /** Return a feature's integer value. | |
844 | * @param[in] feat &Feature identifier. | |
845 | * @return Integer value of feature. | |
846 | */ | |
847 | int | |
848 | feature_int(enum Feature feat) | |
849 | { | |
850 | assert(features[feat].feat == feat); | |
851 | assert((features[feat].flags & FEAT_MASK) == FEAT_INT); | |
852 | ||
853 | return features[feat].v_int; | |
854 | } | |
855 | ||
856 | /** Return a feature's boolean value. | |
857 | * @param[in] feat &Feature identifier. | |
858 | * @return Boolean value of feature. | |
859 | */ | |
860 | int | |
861 | feature_bool(enum Feature feat) | |
862 | { | |
863 | assert(feat <= FEAT_LAST_F); | |
864 | if (FEAT_LAST_F < feat) | |
865 | return 0; | |
866 | assert(features[feat].feat == feat); | |
867 | assert((features[feat].flags & FEAT_MASK) == FEAT_BOOL); | |
868 | ||
869 | return features[feat].v_int; | |
870 | } | |
871 | ||
872 | /** Return a feature's string value. | |
873 | * @param[in] feat &Feature identifier. | |
874 | * @return String value of feature. | |
875 | */ | |
876 | const char * | |
877 | feature_str(enum Feature feat) | |
878 | { | |
879 | assert(features[feat].feat == feat); | |
880 | assert((features[feat].flags & FEAT_MASK) == FEAT_STR); | |
881 | ||
882 | return features[feat].v_str; | |
883 | } |