added full half-op support
[NeonServV5.git] / src / cmd_neonserv_set.c
1 /* cmd_neonserv_set.c - NeonServ v5.3
2  * Copyright (C) 2011-2012  Philipp Kreil (pk910)
3  * 
4  * This program is free software: you can redistribute it and/or modify
5  * it under the terms of the GNU General Public License as published by
6  * the Free Software Foundation, either version 3 of the License, or
7  * (at your option) any later version.
8  * 
9  * This program is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  * GNU General Public License for more details.
13  * 
14  * You should have received a copy of the GNU General Public License 
15  * along with this program. If not, see <http://www.gnu.org/licenses/>. 
16  */
17
18 #include "cmd_neonserv.h"
19
20 typedef char* neonserv_cmd_set_function(struct ClientSocket *client, struct UserNode *user, struct ChanNode *chan, struct Event *event, const char *setting, char *argument);
21 static void neonserv_cmd_set_setting(struct ClientSocket *client, struct UserNode *user, struct ChanNode *chan, struct Event *event, int setting, char *argument);
22 static char* neonserv_cmd_set_trigger(struct ClientSocket *client, struct UserNode *user, struct ChanNode *chan, struct Event *event, const char *setting, char *argument);
23 static char* neonserv_cmd_set_modes(struct ClientSocket *client, struct UserNode *user, struct ChanNode *chan, struct Event *event, const char *setting, char *argument);
24 static char* neonserv_cmd_set_dynlimit(struct ClientSocket *client, struct UserNode *user, struct ChanNode *chan, struct Event *event, const char *setting, char *argument);
25 static char* neonserv_cmd_set_nodelete(struct ClientSocket *client, struct UserNode *user, struct ChanNode *chan, struct Event *event, const char *setting, char *argument);
26
27 #define NS_VALID_FUNCTION 0x01
28 #define NS_VALID_STRING   0x02
29 #define NS_VALID_ACCESS   0x04
30 #define NS_VALID_NO501    0x08
31 #define NS_VALID_OPTIONS  0x10
32 #define NS_VALID_NUMERIC  0x20
33 #define NS_VALID_BOOLEAN  0x40
34 #define NS_VALID_IF_HALFOP 0x80
35
36 #define NS_HAS_OPT  0x100 /* options (SET_OPTION_{NAME}_{VALUE}) */
37 #define NS_HAS_HELP 0x200 /* help    (SET_HELP_{NAME}) - only shown if help is requested */
38
39 static const struct {
40     const char *setting;
41     const char *chanfield;
42     unsigned int valid;
43     void *parameter;
44 } channel_settings[] = {
45     {"TRIGGER",         NULL,                   NS_VALID_FUNCTION,                  neonserv_cmd_set_trigger},
46     {"DEFAULTTOPIC",    "channel_defaulttopic", NS_VALID_STRING,                    NULL},
47     {"TOPICMASK",       "channel_topicmask",    NS_VALID_STRING,                    NULL},
48     {"ADVANCEDTOPIC",   "channel_exttopic",     NS_VALID_BOOLEAN | NS_HAS_OPT,      NULL},
49     {"GREETING",        "channel_greeting",     NS_VALID_STRING,                    NULL},
50     {"USERGREETING",    "channel_usergreeting", NS_VALID_STRING,                    NULL},
51     {"USERINFO",        "channel_userinfo",     NS_VALID_ACCESS | NS_HAS_HELP,      NULL},
52     {"WIPEINFO",        "channel_wipeinfo",     NS_VALID_ACCESS | NS_HAS_HELP,      NULL},
53     {"MODES",           "channel_modes",        NS_VALID_FUNCTION,                  neonserv_cmd_set_modes},
54     {"INVITEME",        "channel_getinvite",    NS_VALID_ACCESS,                    NULL},
55     {"GIVEOPS",         "channel_getop",        NS_VALID_ACCESS | NS_HAS_HELP,      NULL},
56     {"GIVEHALFOPS",     "channel_gethalfop",    NS_VALID_ACCESS | NS_HAS_HELP | NS_VALID_IF_HALFOP, NULL},
57     {"GIVEVOICE",       "channel_getvoice",     NS_VALID_ACCESS | NS_HAS_HELP,      NULL},
58     {"ENFOPS",          "channel_canop",        NS_VALID_ACCESS | NS_HAS_HELP,      NULL},
59     {"ENFHALFOPS",      "channel_canhalfop",    NS_VALID_ACCESS | NS_HAS_HELP | NS_VALID_IF_HALFOP, NULL},
60     {"ENFVOICE",        "channel_canvoice",     NS_VALID_ACCESS | NS_HAS_HELP,      NULL},
61     {"KICK",            "channel_cankick",      NS_VALID_ACCESS | NS_HAS_HELP,      NULL},
62     {"BAN",             "channel_canban",       NS_VALID_ACCESS | NS_HAS_HELP,      NULL},
63     {"STATICBAN",       "channel_staticban",    NS_VALID_ACCESS | NS_HAS_HELP,      NULL},
64     {"PUBCMD",          "channel_pubcmd",       NS_VALID_ACCESS,                    NULL},
65     {"ENFMODES",        "channel_enfmodes",     NS_VALID_ACCESS,                    NULL},
66     {"ENFTOPIC",        "channel_enftopic",     NS_VALID_ACCESS,                    NULL},
67     {"TOPICSNARF",      "channel_topicsnarf",   NS_VALID_ACCESS | NS_HAS_HELP,      NULL},
68     {"CHANGETOPIC",     "channel_changetopic",  NS_VALID_ACCESS | NS_HAS_HELP,      NULL},
69     {"SETTERS",         "channel_setters",      NS_VALID_ACCESS | NS_VALID_NO501 | NS_HAS_HELP, NULL},
70     {"ADDUSER",         "channel_canadd",       NS_VALID_ACCESS | NS_HAS_HELP,      NULL},
71     {"DELUSER",         "channel_candel",       NS_VALID_ACCESS | NS_HAS_HELP,      NULL},
72     {"CLVL",            "channel_canclvl",      NS_VALID_ACCESS | NS_HAS_HELP,      NULL},
73     {"RESYNC",          "channel_canresync",    NS_VALID_ACCESS | NS_HAS_HELP,      NULL},
74     {"SUSPEND",         "channel_cansuspend",   NS_VALID_ACCESS | NS_HAS_HELP,      NULL},
75     {"NOTICEUSERS",     "channel_notice",       NS_VALID_ACCESS,                    NULL},
76     {"NOTICEREACTION",  "channel_noticereaction", NS_VALID_OPTIONS | NS_HAS_OPT,    "4"},
77     {"CTCPUSERS",       "channel_ctcp",         NS_VALID_ACCESS,                    NULL},
78     {"CTCPREACTION",    "channel_ctcpreaction", NS_VALID_OPTIONS | NS_HAS_OPT,      "4"},
79     {"PROTECT",         "channel_protect",      NS_VALID_OPTIONS | NS_HAS_OPT,      "4"},
80     {"TOYS",            "channel_toys",         NS_VALID_OPTIONS | NS_HAS_OPT,      "3"},
81     {"DYNLIMIT",        "channel_dynlimit",     NS_VALID_NUMERIC | NS_VALID_FUNCTION | NS_HAS_OPT, neonserv_cmd_set_dynlimit},
82     {"NODELETE",        "channel_nodelete",     NS_VALID_BOOLEAN | NS_VALID_FUNCTION, neonserv_cmd_set_nodelete},
83     {NULL, NULL, 0, NULL}
84 };
85
86 #define MAX_QUERY_LEN 1024
87 CMD_BIND(neonserv_cmd_set) {
88     int i, j;
89     if(argc && !strcmp(argv[0], "defaults")) {
90         //reset channel settings
91         int uaccess = getChannelAccess(user, chan);
92         if(uaccess < 500) {
93             if(isGodMode(user)) {
94                 event->flags |= CMDFLAG_OPLOG;
95             } else {
96                 reply(getTextBot(), user, "NS_SET_DEFAULTS_OWNER", chan->name);
97                 return;
98             }
99         }
100         int seed = 0;
101         char *tmp;
102         static char defaultskey[16];
103         for(tmp = user->auth; *tmp; tmp++)
104             seed = (seed * 0xEECE66DL ^ ((*tmp << 24) | (*tmp << 16) | (*tmp << 8) | *tmp));
105         for(tmp = chan->name; *tmp; tmp++)
106             seed = (seed * 0xEECE66DL ^ ((*tmp << 24) | (*tmp << 16) | (*tmp << 8) | *tmp));
107         sprintf(defaultskey, "%08x", seed);
108         if(argc > 1 && !strcmp(argv[1], defaultskey)) {
109             char query[MAX_QUERY_LEN];
110             int querypos = 0;
111             i = 0;
112             while(channel_settings[i].setting) {
113                 if(channel_settings[i].chanfield)
114                     querypos += sprintf(query + querypos, "`%s` = NULL, ", channel_settings[i].chanfield);
115                 i++;
116             }
117             if(querypos) {
118                 query[querypos-2] = '\0';
119             }
120             printf_mysql_query("UPDATE `channels` SET %s WHERE `channel_id` = '%d'", query, chan->channel_id);
121             reply(getTextBot(), user, "NS_SET_DEFAULTS_DONE", chan->name);
122             logEvent(event);
123         } else {
124             reply(getTextBot(), user, "NS_SET_DEFAULTS_CODE", chan->name, defaultskey);
125         }
126     } else if(argc && strcmp(argv[0], "help")) {
127         //find the correct command
128         i = 0;
129         j = 0;
130         char *args = (argc > 1 ? merge_argv(argv, 1, argc) : NULL);
131         int with_halfops = get_int_field("General.have_halfop");
132         while(channel_settings[i].setting) {
133             if(!stricmp(channel_settings[i].setting, argv[0]) && (!(channel_settings[i].valid & NS_VALID_IF_HALFOP) || with_halfops)) {
134                 //setting found
135                 if(channel_settings[i].valid & NS_VALID_FUNCTION) {
136                     neonserv_cmd_set_function *func = channel_settings[i].parameter;
137                     func(client, user, chan, event, channel_settings[i].setting, args);
138                 } else {
139                     neonserv_cmd_set_setting(client, user, chan, event, i, args);
140                 }
141                 j = 1;
142                 break;
143             }
144             i++;
145         }
146         if(j == 0) {
147             //unknown setting
148             reply(getTextBot(), user, "NS_SET_UNKNOWN_SETTING", argv[0]);
149         }
150     } else {
151         char query[MAX_QUERY_LEN], *value, *org_value, *tmp, nameBuf[64];
152         int querypos = 0;
153         MYSQL_RES *res, *defaults_res;
154         MYSQL_ROW row, defaults;
155         struct Table *table;
156         char *content[2];
157         int with_halfops = get_int_field("General.have_halfop");
158         i = 0;
159         j = 0;
160         while(channel_settings[i].setting) {
161             if((channel_settings[i].valid & NS_VALID_IF_HALFOP) && !with_halfops) {
162                 j++;
163                 continue;
164             }
165             if(channel_settings[i].chanfield)
166                 querypos += sprintf(query + querypos, ", `%s`", channel_settings[i].chanfield);
167             i++;
168         }
169         table = table_init(2, i-j, 0);
170         table_set_bold(table, 0, 1);
171         printf_mysql_query("SELECT `channel_id` %s FROM `channels` WHERE `channel_name` = 'defaults'", query);
172         defaults_res = mysql_use();
173         defaults = mysql_fetch_row(defaults_res);
174         printf_mysql_query("SELECT `channel_name` %s FROM `channels` WHERE `channel_id` = '%d'", query, chan->channel_id);
175         res = mysql_use();
176         row = mysql_fetch_row(res);
177         i = 0;
178         j = 0;
179         reply(getTextBot(), user, "NS_SET_HEADER", chan->name);
180         while(channel_settings[i].setting) {
181             if((channel_settings[i].valid & NS_VALID_IF_HALFOP) && !with_halfops) {
182                 i++;
183                 continue;
184             }
185             if(channel_settings[i].chanfield) {
186                 j++;
187                 org_value = (row[j] ? row[j] : defaults[j]);
188             } else if(channel_settings[i].valid & NS_VALID_FUNCTION) {
189                 neonserv_cmd_set_function *func = channel_settings[i].parameter;
190                 org_value = func(client, user, chan, event, NULL, NULL);
191             } else
192                 org_value = "0";
193             value = org_value;
194             if(channel_settings[i].valid & NS_VALID_BOOLEAN) {
195                 if(!strcmp(value, "0"))
196                     value = get_language_string(user, "NS_SET_OFF");
197                 else
198                     value = get_language_string(user, "NS_SET_ON");
199             }
200             strcpy(query, value);
201             querypos = strlen(query);
202             if(channel_settings[i].valid & NS_HAS_OPT) {
203                 sprintf(nameBuf, "NS_SET_OPTION_%s_%s", channel_settings[i].setting, org_value);
204                 tmp = get_language_string(user, nameBuf);
205                 if(tmp) {
206                     querypos += sprintf(query+querypos, " - %s", tmp);
207                 }
208             }
209             if(argc && channel_settings[i].valid & NS_HAS_HELP) {
210                 sprintf(nameBuf, "NS_SET_HELP_%s", channel_settings[i].setting);
211                 tmp = get_language_string(user, nameBuf);
212                 if(tmp) {
213                     querypos += sprintf(query+querypos, " - %s", tmp);
214                 }
215             }
216             content[0] = (char*)channel_settings[i].setting;
217             content[1] = query;
218             table_add(table, content);
219             i++;
220         }
221         char **table_lines = table_end(table);
222         for(i = 0; i < table->entrys; i++) {
223             reply(getTextBot(), user, table_lines[i]);
224         }
225         table_free(table);
226     }
227 }
228
229 static void neonserv_cmd_set_setting(struct ClientSocket *client, struct UserNode *user, struct ChanNode *chan, struct Event *event, int setting, char *args) {
230     char *value;
231     char nameBuf[64];
232     //get current value
233     MYSQL_RES *res;
234     MYSQL_ROW row;
235     printf_mysql_query("SELECT `%s` FROM `channels` WHERE `channel_id` = '%d'", channel_settings[setting].chanfield, chan->channel_id);
236     res = mysql_use();
237     row = mysql_fetch_row(res);
238     if(row[0] == NULL) {
239         printf_mysql_query("SELECT `%s` FROM `channels` WHERE `channel_name` = 'defaults'", channel_settings[setting].chanfield);
240         res = mysql_use();
241         row = mysql_fetch_row(res);
242     }
243     value = row[0];
244     if(args) {
245         //change the channel setting
246         //check the new argument
247         int valid = channel_settings[setting].valid;
248         if(valid & NS_VALID_STRING) {
249             if(!strcmp(args, "*")) {
250                 args = "";
251             }
252         }
253         if(valid & NS_VALID_ACCESS) {
254             int caccess = atoi(args);
255             int max = ((valid & NS_VALID_NO501) ? 500 : 501);
256             if(caccess < 0 || caccess > max) {
257                 reply(getTextBot(), user, "NS_INVALID_ACCESS", caccess);
258                 return;
259             }
260             int uaccess = getChannelAccess(user, chan);
261             if(uaccess == 500) uaccess++;
262             if(atoi(value) > uaccess) {
263                 if(isGodMode(user)) {
264                     event->flags |= CMDFLAG_OPLOG;
265                 } else {
266                     reply(getTextBot(), user, "NS_SET_CANNOT_SET");
267                     return;
268                 }
269             }
270             if(caccess > uaccess) {
271                 if(isGodMode(user)) {
272                     event->flags |= CMDFLAG_OPLOG;
273                 } else {
274                     reply(getTextBot(), user, "NS_SET_BADLEVEL");
275                     return;
276                 }
277             }
278             sprintf(nameBuf, "%d", caccess);
279             args = nameBuf;
280         }
281         if(valid & NS_VALID_OPTIONS) {
282             int options = atoi((char *) channel_settings[setting].parameter);
283             int coption = atoi(args);
284             if(coption < 0 || coption >= options) {
285                 reply(getTextBot(), user, "NS_SET_INVALID_OPTION", coption);
286                 int i;
287                 int nameBufPos = 0;
288                 if(valid & NS_HAS_OPT) {
289                     for(i = 0; i < options; i++) {
290                         sprintf(nameBuf, "NS_SET_OPTION_%s_%d", channel_settings[setting].setting, i);
291                         reply(getTextBot(), user, "\002%d\002 - %s", i, get_language_string(user, nameBuf));
292                     }
293                 } else {
294                     for(i = 0; i < options; i++) {
295                         nameBufPos += sprintf(nameBuf + nameBufPos, "\002%d\002, ", i);
296                     }
297                     if(nameBufPos) {
298                         nameBuf[nameBufPos-2] = '\0';
299                         reply(getTextBot(), user, nameBuf);
300                     }
301                 }
302                 return;
303             }
304         }
305         if(valid & NS_VALID_NUMERIC) {
306             sprintf(nameBuf, "%d", atoi(args));
307             args = nameBuf;
308         }
309         if(valid & NS_VALID_BOOLEAN) {
310             if(!strcmp(args, "0") || !stricmp(args, "off") || !stricmp(args, get_language_string(user, "NS_SET_OFF"))) {
311                 args = "0";
312             } else if(!strcmp(args, "1") || !stricmp(args, "on") || !stricmp(args, get_language_string(user, "NS_SET_ON"))) {
313                 args = "1";
314             } else {
315                 reply(getTextBot(), user, "NS_SET_INVALID_BOOLEAN", args);
316                 return;
317             }
318         }
319         //valid - set it
320         value = args;
321         printf_mysql_query("UPDATE `channels` SET `%s` = '%s' WHERE `channel_id` = '%d'", channel_settings[setting].chanfield, escape_string(value), chan->channel_id);
322         logEvent(event);
323     }
324     if(channel_settings[setting].valid & NS_HAS_OPT) {
325         sprintf(nameBuf, "NS_SET_OPTION_%s_%s", channel_settings[setting].setting, value);
326         char *tmp = get_language_string(user, nameBuf);
327         if(tmp)
328             reply(getTextBot(), user, "\002%s\002 %s - %s", channel_settings[setting].setting, value, tmp);
329         else
330             reply(getTextBot(), user, "\002%s\002 %s", channel_settings[setting].setting, value);
331     } else
332         reply(getTextBot(), user, "\002%s\002 %s", channel_settings[setting].setting, value);
333     if(channel_settings[setting].valid & NS_HAS_HELP) {
334          sprintf(nameBuf, "NS_SET_HELP_%s", channel_settings[setting].setting);
335          reply(getTextBot(), user, "  %s", get_language_string(user, nameBuf));
336     }
337 }
338
339 static char* neonserv_cmd_set_trigger(struct ClientSocket *client, struct UserNode *user, struct ChanNode *chan, struct Event *event, const char *setting, char *argument) {
340     char *trigger;
341     //get current trigger
342     MYSQL_RES *res;
343     MYSQL_ROW row;
344     printf_mysql_query("SELECT `trigger`, `defaulttrigger` FROM `bot_channels` LEFT JOIN `bots` ON `botid` = `bots`.`id` WHERE `chanid` = '%d' AND `botid` = '%d'", chan->channel_id, client->clientid);
345     res = mysql_use();
346     row = mysql_fetch_row(res);
347     trigger = (row[0] ? row[0] : row[1]);
348     if(argument) {
349         int uaccess = getChannelAccess(user, chan);
350         if(uaccess < 500) {
351             if(isGodMode(user)) {
352                 event->flags |= CMDFLAG_OPLOG;
353             } else {
354                 reply(getTextBot(), user, "NS_SET_TRIGGER_OWNER", chan->name);
355                 return NULL;
356             }
357         }
358         if(strlen(argument) > 15)
359             argument[15] = '\0';
360         printf_mysql_query("UPDATE `bot_channels` SET `trigger` = '%s' WHERE `chanid` = '%d' AND `botid` = '%d'", escape_string(argument), chan->channel_id, client->clientid);
361         trigger = argument;
362         changeChannelTrigger(client->botid, chan, trigger);
363         logEvent(event);
364     }
365     if(setting) {
366         reply(getTextBot(), user, "\002%s\002 %s", setting, trigger);
367     }
368     return trigger;
369 }
370
371 static char* neonserv_cmd_set_modes(struct ClientSocket *client, struct UserNode *user, struct ChanNode *chan, struct Event *event, const char *setting, char *argument) {
372     char *value;
373     char valueBuf[MAXLEN];
374     //get current value
375     MYSQL_RES *res;
376     MYSQL_ROW row;
377     printf_mysql_query("SELECT `channel_modes` FROM `channels` WHERE `channel_id` = '%d'", chan->channel_id);
378     res = mysql_use();
379     row = mysql_fetch_row(res);
380     if(row[0] == NULL) {
381         printf_mysql_query("SELECT `channel_modes` FROM `channels` WHERE `channel_name` = 'defaults'");
382         res = mysql_use();
383         row = mysql_fetch_row(res);
384     }
385     value = row[0];
386     if(argument) {
387         //change the channel setting
388         struct ModeNode *modenode = createModeNode(NULL);
389         parseModeString(modenode, argument);
390         getFullModeString(modenode, valueBuf);
391         value = valueBuf;
392         printf_mysql_query("UPDATE `channels` SET `channel_modes` = '%s' WHERE `channel_id` = '%d'", escape_string(value), chan->channel_id);
393         //TODO: set modelock
394         freeModeNode(modenode);
395     }
396     if(setting) {
397         reply(getTextBot(), user, "\002%s\002 %s", setting, value);
398     }
399     return value;
400 }
401
402 static char* neonserv_cmd_set_dynlimit(struct ClientSocket *client, struct UserNode *user, struct ChanNode *chan, struct Event *event, const char *setting, char *argument) {
403     char *value;
404     char tmp[64];
405     //get current value
406     MYSQL_RES *res;
407     MYSQL_ROW row;
408     printf_mysql_query("SELECT `channel_dynlimit` FROM `channels` WHERE `channel_id` = '%d'", chan->channel_id);
409     res = mysql_use();
410     row = mysql_fetch_row(res);
411     if(row[0] == NULL) {
412         printf_mysql_query("SELECT `channel_dynlimit` FROM `channels` WHERE `channel_name` = 'defaults'");
413         res = mysql_use();
414         row = mysql_fetch_row(res);
415     }
416     value = row[0];
417     if(argument) {
418         //change the channel setting
419         sprintf(tmp, "%d", atoi(argument));
420         argument = tmp;
421         printf_mysql_query("UPDATE `channels` SET `channel_dynlimit` = '%s' WHERE `channel_id` = '%d'", escape_string(argument), chan->channel_id);
422         if(strcmp(argument, "0"))
423             putsock(client, "MODE %s +l %d", chan->name, (chan->usercount + atoi(argument)));
424         else if(isModeSet(chan->modes, 'l'))
425             putsock(client, "MODE %s -l", chan->name);
426         value = argument;
427         logEvent(event);
428     }
429     if(setting) {
430         reply(getTextBot(), user, "\002%s\002 %s", setting, value);
431     }
432     return value;
433 }
434
435 static char* neonserv_cmd_set_nodelete(struct ClientSocket *client, struct UserNode *user, struct ChanNode *chan, struct Event *event, const char *setting, char *argument) {
436     char *value;
437     //get current value
438     MYSQL_RES *res;
439     MYSQL_ROW row;
440     printf_mysql_query("SELECT `channel_nodelete` FROM `channels` WHERE `channel_id` = '%d'", chan->channel_id);
441     res = mysql_use();
442     row = mysql_fetch_row(res);
443     if(row[0] == NULL) {
444         printf_mysql_query("SELECT `channel_nodelete` FROM `channels` WHERE `channel_name` = 'defaults'");
445         res = mysql_use();
446         row = mysql_fetch_row(res);
447     }
448     value = row[0];
449     if(argument && isGodMode(user)) {
450         //change the channel setting
451         if(!strcmp(argument, "0") || !strcmp(argument, "off") || !strcmp(argument, get_language_string(user, "NS_SET_OFF"))) {
452             argument = "0";
453         } else if(!strcmp(argument, "0") || !strcmp(argument, "off") || !strcmp(argument, get_language_string(user, "NS_SET_OFF"))) {
454             argument = "1";
455         } else {
456             reply(getTextBot(), user, "NS_SET_INVALID_BOOLEAN", argument);
457             return NULL;
458         }
459         printf_mysql_query("UPDATE `channels` SET `channel_nodelete` = '%s' WHERE `channel_id` = '%d'", escape_string(argument), chan->channel_id);
460         event->flags |= CMDFLAG_OPLOG;
461         value = argument;
462         logEvent(event);
463     }
464     if(setting) {
465         reply(getTextBot(), user, "\002%s\002 %s", setting, value);
466     }
467     return value;
468 }
469
470 #undef MAX_QUERY_LEN