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