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