]>
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_BANWHO, 0, 1, 0), | |
406 | F_B(HIS_KILLWHO, 0, 1, 0), | |
407 | /* Asuka - Reimplement HEAD_IN_SAND_GLINE from Lain */ | |
408 | F_B(HIS_GLINE, 0, 1, 0), | |
409 | F_B(HIS_REWRITE, 0, 1, 0), | |
410 | F_I(HIS_REMOTE, 0, 1, 0), | |
411 | F_B(HIS_NETSPLIT, 0, 1, 0), | |
412 | F_S(HIS_SERVERNAME, 0, "*.undernet.org", feature_notify_servername), | |
413 | F_S(HIS_SERVERINFO, 0, "The Undernet Underworld", feature_notify_serverinfo), | |
414 | F_S(HIS_URLSERVERS, 0, "http://www.undernet.org/servers.php", 0), | |
415 | F_B(HIS_USERGLINE, 0, 1, 0), | |
416 | ||
417 | /* Misc. random stuff */ | |
418 | F_S(NETWORK, 0, "UnderNet", 0), | |
419 | F_S(URL_CLIENTS, 0, "ftp://ftp.undernet.org/pub/irc/clients", 0), | |
420 | ||
421 | #undef F_S | |
422 | #undef F_B | |
423 | #undef F_I | |
424 | #undef F_N | |
425 | { FEAT_LAST_F, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } | |
426 | }; | |
427 | ||
428 | /** Given a feature's identifier, look up the feature descriptor. | |
429 | * @param[in] from Client looking up feature, or NULL. | |
430 | * @param[in] feature Feature name to find. | |
431 | * @return Pointer to a FeatureDesc, or NULL if none was found. | |
432 | */ | |
433 | static struct FeatureDesc * | |
434 | feature_desc(struct Client* from, const char *feature) | |
435 | { | |
436 | int i; | |
437 | ||
438 | assert(0 != feature); | |
439 | ||
440 | for (i = 0; features[i].type; i++) /* find appropriate descriptor */ | |
441 | if (!strcmp(feature, features[i].type)) | |
442 | return &features[i]; | |
443 | ||
444 | Debug((DEBUG_ERROR, "Unknown feature \"%s\"", feature)); | |
445 | if (from) /* report an error */ | |
446 | send_reply(from, ERR_NOFEATURE, feature); | |
447 | else | |
448 | log_write(LS_CONFIG, L_ERROR, 0, "Unknown feature \"%s\"", feature); | |
449 | ||
450 | return 0; /* not found */ | |
451 | } | |
452 | ||
453 | /** Given a feature vector string, set the value of a feature. | |
454 | * @param[in] from Client trying to set the feature, or NULL. | |
455 | * @param[in] fields Parameters to set, starting with feature name. | |
456 | * @param[in] count Number of fields in \a fields. | |
457 | * @return Zero (or, theoretically, CPTR_KILLED). | |
458 | */ | |
459 | int | |
460 | feature_set(struct Client* from, const char* const* fields, int count) | |
461 | { | |
462 | int i, change = 0, tmp; | |
463 | const char *t_str; | |
464 | struct FeatureDesc *feat; | |
465 | ||
466 | if (from && !HasPriv(from, PRIV_SET)) | |
467 | return send_reply(from, ERR_NOPRIVILEGES); | |
468 | ||
469 | if (count < 1) { | |
470 | if (from) /* report an error in the number of arguments */ | |
471 | need_more_params(from, "SET"); | |
472 | else | |
473 | log_write(LS_CONFIG, L_ERROR, 0, "Not enough fields in F line"); | |
474 | } else if ((feat = feature_desc(from, fields[0]))) { /* find feature */ | |
475 | if (from && feat->flags & FEAT_READ) | |
476 | return send_reply(from, ERR_NOFEATURE, fields[0]); | |
477 | ||
478 | switch (feat->flags & FEAT_MASK) { | |
479 | case FEAT_NONE: | |
480 | if (feat->set && (i = (*feat->set)(from, fields + 1, count - 1))) { | |
481 | change++; /* feature handler wants a change recorded */ | |
482 | ||
483 | if (i > 0) /* call the set callback and do marking */ | |
484 | feat->flags |= FEAT_MARK; | |
485 | else /* i < 0 */ | |
486 | feat->flags &= ~FEAT_MARK; | |
487 | break; | |
488 | } | |
489 | ||
490 | case FEAT_INT: /* an integer value */ | |
491 | tmp = feat->v_int; /* detect changes... */ | |
492 | ||
493 | if (count < 2) { /* reset value */ | |
494 | feat->v_int = feat->def_int; | |
495 | feat->flags &= ~FEAT_MARK; | |
496 | } else { /* ok, figure out the value and whether to mark it */ | |
497 | feat->v_int = strtoul(fields[1], 0, 0); | |
498 | if (feat->v_int == feat->def_int) | |
499 | feat->flags &= ~FEAT_MARK; | |
500 | else | |
501 | feat->flags |= FEAT_MARK; | |
502 | } | |
503 | ||
504 | if (feat->v_int != tmp) /* check for change */ | |
505 | change++; | |
506 | break; | |
507 | ||
508 | case FEAT_BOOL: /* it's a boolean value--true or false */ | |
509 | tmp = feat->v_int; /* detect changes... */ | |
510 | ||
511 | if (count < 2) { /* reset value */ | |
512 | feat->v_int = feat->def_int; | |
513 | feat->flags &= ~FEAT_MARK; | |
514 | } else { /* figure out the value and whether to mark it */ | |
515 | if (!ircd_strncmp(fields[1], "TRUE", strlen(fields[1])) || | |
516 | !ircd_strncmp(fields[1], "YES", strlen(fields[1])) || | |
517 | (strlen(fields[1]) >= 2 && | |
518 | !ircd_strncmp(fields[1], "ON", strlen(fields[1])))) | |
519 | feat->v_int = 1; | |
520 | else if (!ircd_strncmp(fields[1], "FALSE", strlen(fields[1])) || | |
521 | !ircd_strncmp(fields[1], "NO", strlen(fields[1])) || | |
522 | (strlen(fields[1]) >= 2 && | |
523 | !ircd_strncmp(fields[1], "OFF", strlen(fields[1])))) | |
524 | feat->v_int = 0; | |
525 | else if (from) /* report an error... */ | |
526 | return send_reply(from, ERR_BADFEATVALUE, fields[1], feat->type); | |
527 | else { | |
528 | log_write(LS_CONFIG, L_ERROR, 0, "Bad value \"%s\" for feature %s", | |
529 | fields[1], feat->type); | |
530 | return 0; | |
531 | } | |
532 | ||
533 | if (feat->v_int == feat->def_int) /* figure out whether to mark it */ | |
534 | feat->flags &= ~FEAT_MARK; | |
535 | else | |
536 | feat->flags |= FEAT_MARK; | |
537 | } | |
538 | ||
539 | if (feat->v_int != tmp) /* check for change */ | |
540 | change++; | |
541 | break; | |
542 | ||
543 | case FEAT_STR: /* it's a string value */ | |
544 | if (count < 2) | |
545 | t_str = feat->def_str; /* changing to default */ | |
546 | else | |
547 | t_str = *fields[1] ? fields[1] : 0; | |
548 | ||
549 | if (!t_str && !(feat->flags & FEAT_NULL)) { /* NULL value permitted? */ | |
550 | if (from) | |
551 | return send_reply(from, ERR_BADFEATVALUE, "NULL", feat->type); | |
552 | else { | |
553 | log_write(LS_CONFIG, L_ERROR, 0, "Bad value \"NULL\" for feature %s", | |
554 | feat->type); | |
555 | return 0; | |
556 | } | |
557 | } | |
558 | ||
559 | if (t_str == feat->def_str || | |
560 | (t_str && feat->def_str && | |
561 | !(feat->flags & FEAT_CASE ? strcmp(t_str, feat->def_str) : | |
562 | ircd_strcmp(t_str, feat->def_str)))) { /* resetting to default */ | |
563 | if (feat->v_str != feat->def_str) { | |
564 | change++; /* change from previous value */ | |
565 | ||
566 | if (feat->v_str) | |
567 | MyFree(feat->v_str); /* free old value */ | |
568 | } | |
569 | ||
570 | feat->v_str = feat->def_str; /* very special... */ | |
571 | ||
572 | feat->flags &= ~FEAT_MARK; | |
573 | } else if (!t_str) { | |
574 | if (feat->v_str) { | |
575 | change++; /* change from previous value */ | |
576 | ||
577 | if (feat->v_str != feat->def_str) | |
578 | MyFree(feat->v_str); /* free old value */ | |
579 | } | |
580 | ||
581 | feat->v_str = 0; /* set it to NULL */ | |
582 | ||
583 | feat->flags |= FEAT_MARK; | |
584 | } else if (!feat->v_str || | |
585 | (feat->flags & FEAT_CASE ? strcmp(t_str, feat->v_str) : | |
586 | ircd_strcmp(t_str, feat->v_str))) { /* new value */ | |
587 | change++; /* change from previous value */ | |
588 | ||
589 | if (feat->v_str && feat->v_str != feat->def_str) | |
590 | MyFree(feat->v_str); /* free old value */ | |
591 | DupString(feat->v_str, t_str); /* store new value */ | |
592 | ||
593 | feat->flags |= FEAT_MARK; | |
594 | } else /* they match, but don't match the default */ | |
595 | feat->flags |= FEAT_MARK; | |
596 | break; | |
597 | } | |
598 | ||
599 | if (change && feat->notify) /* call change notify function */ | |
600 | (*feat->notify)(); | |
601 | } | |
602 | ||
603 | return 0; | |
604 | } | |
605 | ||
606 | /** Reset a feature to its default values. | |
607 | * @param[in] from Client trying to reset the feature, or NULL. | |
608 | * @param[in] fields Parameters to set, starting with feature name. | |
609 | * @param[in] count Number of fields in \a fields. | |
610 | * @return Zero (or, theoretically, CPTR_KILLED). | |
611 | */ | |
612 | int | |
613 | feature_reset(struct Client* from, const char* const* fields, int count) | |
614 | { | |
615 | int i, change = 0; | |
616 | struct FeatureDesc *feat; | |
617 | ||
618 | assert(0 != from); | |
619 | ||
620 | if (!HasPriv(from, PRIV_SET)) | |
621 | return send_reply(from, ERR_NOPRIVILEGES); | |
622 | ||
623 | if (count < 1) /* check arguments */ | |
624 | need_more_params(from, "RESET"); | |
625 | else if ((feat = feature_desc(from, fields[0]))) { /* get descriptor */ | |
626 | if (from && feat->flags & FEAT_READ) | |
627 | return send_reply(from, ERR_NOFEATURE, fields[0]); | |
628 | ||
629 | switch (feat->flags & FEAT_MASK) { | |
630 | case FEAT_NONE: /* None... */ | |
631 | if (feat->reset && (i = (*feat->reset)(from, fields + 1, count - 1))) { | |
632 | change++; /* feature handler wants a change recorded */ | |
633 | ||
634 | if (i > 0) /* call reset callback and parse mark return */ | |
635 | feat->flags |= FEAT_MARK; | |
636 | else /* i < 0 */ | |
637 | feat->flags &= ~FEAT_MARK; | |
638 | } | |
639 | break; | |
640 | ||
641 | case FEAT_INT: /* Integer... */ | |
642 | case FEAT_BOOL: /* Boolean... */ | |
643 | if (feat->v_int != feat->def_int) | |
644 | change++; /* change will be made */ | |
645 | ||
646 | feat->v_int = feat->def_int; /* set the default */ | |
647 | feat->flags &= ~FEAT_MARK; /* unmark it */ | |
648 | break; | |
649 | ||
650 | case FEAT_STR: /* string! */ | |
651 | if (feat->v_str != feat->def_str) { | |
652 | change++; /* change has been made */ | |
653 | if (feat->v_str) | |
654 | MyFree(feat->v_str); /* free old value */ | |
655 | } | |
656 | ||
657 | feat->v_str = feat->def_str; /* set it to default */ | |
658 | feat->flags &= ~FEAT_MARK; /* unmark it */ | |
659 | break; | |
660 | } | |
661 | ||
662 | if (change && feat->notify) /* call change notify function */ | |
663 | (*feat->notify)(); | |
664 | } | |
665 | ||
666 | return 0; | |
667 | } | |
668 | ||
669 | /** Gets the value of a specific feature and reports it to the user. | |
670 | * @param[in] from Client trying to get the feature. | |
671 | * @param[in] fields Parameters to set, starting with feature name. | |
672 | * @param[in] count Number of fields in \a fields. | |
673 | * @return Zero (or, theoretically, CPTR_KILLED). | |
674 | */ | |
675 | int | |
676 | feature_get(struct Client* from, const char* const* fields, int count) | |
677 | { | |
678 | struct FeatureDesc *feat; | |
679 | ||
680 | assert(0 != from); | |
681 | ||
682 | if (count < 1) /* check parameters */ | |
683 | need_more_params(from, "GET"); | |
684 | else if ((feat = feature_desc(from, fields[0]))) { | |
685 | if ((feat->flags & FEAT_NODISP) || | |
686 | (feat->flags & FEAT_MYOPER && !MyOper(from)) || | |
687 | (feat->flags & FEAT_OPER && !IsAnOper(from))) /* check privs */ | |
688 | return send_reply(from, ERR_NOPRIVILEGES); | |
689 | ||
690 | switch (feat->flags & FEAT_MASK) { | |
691 | case FEAT_NONE: /* none, call the callback... */ | |
692 | if (feat->get) /* if there's a callback, use it */ | |
693 | (*feat->get)(from, fields + 1, count - 1); | |
694 | break; | |
695 | ||
696 | case FEAT_INT: /* integer, report integer value */ | |
697 | send_reply(from, SND_EXPLICIT | RPL_FEATURE, | |
698 | ":Integer value of %s: %d", feat->type, feat->v_int); | |
699 | break; | |
700 | ||
701 | case FEAT_BOOL: /* boolean, report boolean value */ | |
702 | send_reply(from, SND_EXPLICIT | RPL_FEATURE, | |
703 | ":Boolean value of %s: %s", feat->type, | |
704 | feat->v_int ? "TRUE" : "FALSE"); | |
705 | break; | |
706 | ||
707 | case FEAT_STR: /* string, report string value */ | |
708 | if (feat->v_str) /* deal with null case */ | |
709 | send_reply(from, SND_EXPLICIT | RPL_FEATURE, | |
710 | ":String value of %s: %s", feat->type, feat->v_str); | |
711 | else | |
712 | send_reply(from, SND_EXPLICIT | RPL_FEATURE, | |
713 | ":String value for %s not set", feat->type); | |
714 | break; | |
715 | } | |
716 | } | |
717 | ||
718 | return 0; | |
719 | } | |
720 | ||
721 | /** Called before reading the .conf to clear all dirty marks. */ | |
722 | void | |
723 | feature_unmark(void) | |
724 | { | |
725 | int i; | |
726 | ||
727 | for (i = 0; features[i].type; i++) { | |
728 | features[i].flags &= ~FEAT_MARK; /* clear the marks... */ | |
729 | if (features[i].unmark) /* call the unmark callback if necessary */ | |
730 | (*features[i].unmark)(); | |
731 | } | |
732 | } | |
733 | ||
734 | /** Called after reading the .conf to reset unmodified values to defaults. */ | |
735 | void | |
736 | feature_mark(void) | |
737 | { | |
738 | int i, change; | |
739 | ||
740 | for (i = 0; features[i].type; i++) { | |
741 | change = 0; | |
742 | ||
743 | switch (features[i].flags & FEAT_MASK) { | |
744 | case FEAT_NONE: | |
745 | if (features[i].mark && | |
746 | (*features[i].mark)(features[i].flags & FEAT_MARK ? 1 : 0)) | |
747 | change++; /* feature handler wants a change recorded */ | |
748 | break; | |
749 | ||
750 | case FEAT_INT: /* Integers or Booleans... */ | |
751 | case FEAT_BOOL: | |
752 | if (!(features[i].flags & FEAT_MARK)) { /* not changed? */ | |
753 | if (features[i].v_int != features[i].def_int) | |
754 | change++; /* we're making a change */ | |
755 | features[i].v_int = features[i].def_int; | |
756 | } | |
757 | break; | |
758 | ||
759 | case FEAT_STR: /* strings... */ | |
760 | if (!(features[i].flags & FEAT_MARK)) { /* not changed? */ | |
761 | if (features[i].v_str != features[i].def_str) { | |
762 | change++; /* we're making a change */ | |
763 | if (features[i].v_str) | |
764 | MyFree(features[i].v_str); /* free old value */ | |
765 | } | |
766 | features[i].v_str = features[i].def_str; | |
767 | } | |
768 | break; | |
769 | } | |
770 | ||
771 | if (change && features[i].notify) | |
772 | (*features[i].notify)(); /* call change notify function */ | |
773 | } | |
774 | } | |
775 | ||
776 | /** Initialize the features subsystem. */ | |
777 | void | |
778 | feature_init(void) | |
779 | { | |
780 | int i; | |
781 | ||
782 | for (i = 0; features[i].type; i++) { | |
783 | switch (features[i].flags & FEAT_MASK) { | |
784 | case FEAT_NONE: /* you're on your own */ | |
785 | break; | |
786 | ||
787 | case FEAT_INT: /* Integers or Booleans... */ | |
788 | case FEAT_BOOL: | |
789 | features[i].v_int = features[i].def_int; | |
790 | break; | |
791 | ||
792 | case FEAT_STR: /* Strings */ | |
793 | features[i].v_str = features[i].def_str; | |
794 | assert(features[i].def_str || (features[i].flags & FEAT_NULL)); | |
795 | break; | |
796 | } | |
797 | } | |
798 | ||
799 | cli_magic(&his) = CLIENT_MAGIC; | |
800 | cli_status(&his) = STAT_SERVER; | |
801 | feature_notify_servername(); | |
802 | feature_notify_serverinfo(); | |
803 | } | |
804 | ||
805 | /** Report all F-lines to a user. | |
806 | * @param[in] to Client requesting statistics. | |
807 | * @param[in] sd Stats descriptor for request (ignored). | |
808 | * @param[in] param Extra parameter from user (ignored). | |
809 | */ | |
810 | void | |
811 | feature_report(struct Client* to, const struct StatDesc* sd, char* param) | |
812 | { | |
813 | int i; | |
814 | ||
815 | for (i = 0; features[i].type; i++) { | |
816 | if ((features[i].flags & FEAT_NODISP) || | |
817 | (features[i].flags & FEAT_MYOPER && !MyOper(to)) || | |
818 | (features[i].flags & FEAT_OPER && !IsAnOper(to))) | |
819 | continue; /* skip this one */ | |
820 | ||
821 | switch (features[i].flags & FEAT_MASK) { | |
822 | case FEAT_NONE: | |
823 | if (features[i].report) /* let the callback handle this */ | |
824 | (*features[i].report)(to, features[i].flags & FEAT_MARK ? 1 : 0); | |
825 | break; | |
826 | ||
827 | ||
828 | case FEAT_INT: /* Report an F-line with integer values */ | |
829 | if (features[i].flags & FEAT_MARK) /* it's been changed */ | |
830 | send_reply(to, SND_EXPLICIT | RPL_STATSFLINE, "F %s %d", | |
831 | features[i].type, features[i].v_int); | |
832 | break; | |
833 | ||
834 | case FEAT_BOOL: /* Report an F-line with boolean values */ | |
835 | if (features[i].flags & FEAT_MARK) /* it's been changed */ | |
836 | send_reply(to, SND_EXPLICIT | RPL_STATSFLINE, "F %s %s", | |
837 | features[i].type, features[i].v_int ? "TRUE" : "FALSE"); | |
838 | break; | |
839 | ||
840 | case FEAT_STR: /* Report an F-line with string values */ | |
841 | if (features[i].flags & FEAT_MARK) { /* it's been changed */ | |
842 | if (features[i].v_str) | |
843 | send_reply(to, SND_EXPLICIT | RPL_STATSFLINE, "F %s %s", | |
844 | features[i].type, features[i].v_str); | |
845 | else /* Actually, F:<type> would reset it; you want F:<type>: */ | |
846 | send_reply(to, SND_EXPLICIT | RPL_STATSFLINE, "F %s", | |
847 | features[i].type); | |
848 | } | |
849 | break; | |
850 | } | |
851 | } | |
852 | } | |
853 | ||
854 | /** Return a feature's integer value. | |
855 | * @param[in] feat &Feature identifier. | |
856 | * @return Integer value of feature. | |
857 | */ | |
858 | int | |
859 | feature_int(enum Feature feat) | |
860 | { | |
861 | assert(features[feat].feat == feat); | |
862 | assert((features[feat].flags & FEAT_MASK) == FEAT_INT); | |
863 | ||
864 | return features[feat].v_int; | |
865 | } | |
866 | ||
867 | /** Return a feature's boolean value. | |
868 | * @param[in] feat &Feature identifier. | |
869 | * @return Boolean value of feature. | |
870 | */ | |
871 | int | |
872 | feature_bool(enum Feature feat) | |
873 | { | |
874 | assert(feat <= FEAT_LAST_F); | |
875 | if (FEAT_LAST_F < feat) | |
876 | return 0; | |
877 | assert(features[feat].feat == feat); | |
878 | assert((features[feat].flags & FEAT_MASK) == FEAT_BOOL); | |
879 | ||
880 | return features[feat].v_int; | |
881 | } | |
882 | ||
883 | /** Return a feature's string value. | |
884 | * @param[in] feat &Feature identifier. | |
885 | * @return String value of feature. | |
886 | */ | |
887 | const char * | |
888 | feature_str(enum Feature feat) | |
889 | { | |
890 | assert(features[feat].feat == feat); | |
891 | assert((features[feat].flags & FEAT_MASK) == FEAT_STR); | |
892 | ||
893 | return features[feat].v_str; | |
894 | } |