]> jfr.im git - irc/evilnet/x3.git/blame - src/opserv.c
$! helpfile syntax for trigger, some cleanup
[irc/evilnet/x3.git] / src / opserv.c
CommitLineData
d76ed9a9
AS
1/* opserv.c - IRC Operator assistance service
2 * Copyright 2000-2004 srvx Development Team
3 *
83ff05c3 4 * This file is part of x3.
d76ed9a9 5 *
d0f04f71 6 * x3 is free software; you can redistribute it and/or modify
d76ed9a9
AS
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 2 of the License, or
9 * (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with srvx; if not, write to the Free Software Foundation,
18 * Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
19 */
20
21#include "conf.h"
47956fc5 22#include "common.h"
d76ed9a9
AS
23#include "gline.h"
24#include "global.h"
25#include "nickserv.h"
26#include "modcmd.h"
ec311f39 27#include "modules.h"
89d871d8 28#include "proto.h"
d76ed9a9
AS
29#include "opserv.h"
30#include "timeq.h"
31#include "saxdb.h"
d914d1cb 32#include "shun.h"
d76ed9a9 33
35305a49 34#include <tre/regex.h>
5b1166fd 35
d76ed9a9
AS
36#ifdef HAVE_SYS_TIMES_H
37#include <sys/times.h>
38#endif
39#ifdef HAVE_NETINET_IN_H
40#include <netinet/in.h>
41#endif
42#ifdef HAVE_ARPA_INET_H
43#include <arpa/inet.h>
44#endif
5b1166fd 45
d76ed9a9
AS
46
47#define OPSERV_CONF_NAME "services/opserv"
48
49#define KEY_ALERT_CHANNEL "alert_channel"
50#define KEY_ALERT_CHANNEL_MODES "alert_channel_modes"
51#define KEY_DEBUG_CHANNEL "debug_channel"
52#define KEY_DEBUG_CHANNEL_MODES "debug_channel_modes"
53#define KEY_UNTRUSTED_MAX "untrusted_max"
54#define KEY_PURGE_LOCK_DELAY "purge_lock_delay"
55#define KEY_JOIN_FLOOD_MODERATE "join_flood_moderate"
56#define KEY_JOIN_FLOOD_MODERATE_THRESH "join_flood_moderate_threshold"
57#define KEY_NICK "nick"
58#define KEY_JOIN_POLICER "join_policer"
59#define KEY_NEW_USER_POLICER "new_user_policer"
7637f48f 60#define KEY_AUTOJOIN_CHANNELS "autojoin_channels"
d76ed9a9
AS
61#define KEY_REASON "reason"
62#define KEY_RESERVES "reserves"
63#define KEY_IDENT "username" /* for compatibility with 1.0 DBs */
64#define KEY_HOSTNAME "hostname"
65#define KEY_DESC "description"
66#define KEY_BAD_WORDS "bad"
67#define KEY_EXEMPT_CHANNELS "exempt"
68#define KEY_SECRET_WORDS "secret"
69#define KEY_TRUSTED_HOSTS "trusted"
70#define KEY_OWNER "owner"
71#define KEY_GAGS "gags"
72#define KEY_ALERTS "alerts"
73#define KEY_REACTION "reaction"
74#define KEY_DISCRIM "discrim"
75#define KEY_WARN "chanwarn"
76#define KEY_MAX "max"
77#define KEY_TIME "time"
78#define KEY_MAX_CLIENTS "max_clients"
79#define KEY_LIMIT "limit"
80#define KEY_EXPIRES "expires"
81#define KEY_STAFF_AUTH_CHANNEL "staff_auth_channel"
82#define KEY_STAFF_AUTH_CHANNEL_MODES "staff_auth_channel_modes"
83#define KEY_CLONE_GLINE_DURATION "clone_gline_duration"
84#define KEY_BLOCK_GLINE_DURATION "block_gline_duration"
d914d1cb 85#define KEY_BLOCK_SHUN_DURATION "block_shun_duration"
d76ed9a9
AS
86#define KEY_ISSUER "issuer"
87#define KEY_ISSUED "issued"
5a1daaab 88#define KEY_ADMIN_LEVEL "admin_level"
1c5f6697 89#define KEY_SILENT_LEVEL "silent_level"
47956fc5
AS
90#define KEY_UPLINK "uplink"
91#define KEY_SECOND "secondaryuplink"
92#define KEY_PORT "port"
93#define KEY_KARMA "karma"
94#define KEY_OFFLINE "offline"
95#define KEY_ROUTINGPLAN "routingplan"
96#define KEY_ROUTINGPLAN_OPTIONS "routingplan_options"
08895577 97#define KEY_DEFCON1 "DefCon1"
98#define KEY_DEFCON2 "DefCon2"
99#define KEY_DEFCON3 "DefCon3"
100#define KEY_DEFCON4 "DefCon4"
101#define KEY_DEFCON_LEVEL "DefConLevel"
102#define KEY_DEFCON_CHANMODES "DefConChanModes"
103#define KEY_DEFCON_SESSION_LIMIT "DefConSessionLimit"
104#define KEY_DEFCON_TIMEOUT "DefConTimeOut"
08895577 105#define KEY_DEFCON_GLOBAL "GlobalOnDefcon"
106#define KEY_DEFCON_GLOBAL_MORE "GlobalOnDefconMore"
107#define KEY_DEFCON_MESSAGE "DefconMessage"
108#define KEY_DEFCON_OFF_MESSAGE "DefConOffMessage"
109#define KEY_DEFCON_GLINE_DURATION "DefConGlineExpire"
110#define KEY_DEFCON_GLINE_REASON "DefConGlineReason"
47956fc5
AS
111
112/* Routing karma values: */
113/* What value we start out with when new servers are added: */
114#define KARMA_DEFAULT 10
115 /* max, min */
116#define KARMA_MAX 10
117#define KARMA_MIN -10
118/* ping out, reduce karma by this much: */
119#define KARMA_PINGOUT -8
120/* read err, reduce karma by this much: */
121#define KARMA_READERROR -5
122/* every 24 hours everyone gets this much added (so we eventually re-try bad servers) */
123#define KARMA_ENTROPE 1
124/* every 24 hours servers linked for 24 hours get an additional ammount: */
125#define KARMA_RELIABLE 1
126/* How often to run entrope and reliable checks */
127#define KARMA_TIMER 86400 /* 1 day */
128
129#define ROUTING_CONNECT_TIMEOUT 30 /* 30 seconds */
d76ed9a9 130
258d1427 131#define IDENT_FORMAT "%s [%s@%s/%s]"
2f61d1d7 132#define IDENT_DATA(user) user->nick, user->ident, user->hostname, irc_ntoa(&user->ip)
258d1427 133#define MAX_CHANNELS_WHOIS 50
d76ed9a9
AS
134#define OSMSG_PART_REASON "%s has no reason."
135#define OSMSG_KICK_REQUESTED "Kick requested by %s."
136#define OSMSG_KILL_REQUESTED "Kill requested by %s."
137#define OSMSG_GAG_REQUESTED "Gag requested by %s."
138
139static const struct message_entry msgtab[] = {
de9510bc 140 { "OSMSG_BAR", "----------------------------------------" },
d76ed9a9
AS
141 { "OSMSG_USER_ACCESS_IS", "$b%s$b (account $b%s$b) has %d access." },
142 { "OSMSG_LEVEL_TOO_LOW", "You lack sufficient access to use this command." },
143 { "OSMSG_NEED_CHANNEL", "You must specify a channel for $b%s$b." },
144 { "OSMSG_INVALID_IRCMASK", "$b%s$b is an invalid IRC hostmask." },
145 { "OSMSG_ADDED_BAN", "I have banned $b%s$b from $b%s$b." },
d914d1cb 146 { "OSMSG_SHUN_ISSUED", "Shun issued for $b%s$b." },
147 { "OSMSG_SHUN_REMOVED", "Shun removed for $b%s$b." },
148 { "OSMSG_SHUN_FORCE_REMOVED", "Unknown/expired Shun removed for $b%s$b." },
149 { "OSMSG_SHUN_ONE_REFRESHED", "All Shuns resent to $b%s$b." },
150 { "OSMSG_SHUN_REFRESHED", "All Shuns refreshed." },
d76ed9a9
AS
151 { "OSMSG_GLINE_ISSUED", "G-line issued for $b%s$b." },
152 { "OSMSG_GLINE_REMOVED", "G-line removed for $b%s$b." },
153 { "OSMSG_GLINE_FORCE_REMOVED", "Unknown/expired G-line removed for $b%s$b." },
154 { "OSMSG_GLINES_ONE_REFRESHED", "All G-lines resent to $b%s$b." },
155 { "OSMSG_GLINES_REFRESHED", "All G-lines refreshed." },
156 { "OSMSG_CLEARBANS_DONE", "Cleared all bans from channel $b%s$b." },
157 { "OSMSG_CLEARMODES_DONE", "Cleared all modes from channel $b%s$b." },
158 { "OSMSG_NO_CHANNEL_MODES", "Channel $b%s$b had no modes to clear." },
159 { "OSMSG_DEOP_DONE", "Deopped the requested lusers." },
160 { "OSMSG_DEOPALL_DONE", "Deopped everyone on $b%s$b." },
55342ce8 161 { "OSMSG_DEHOP_DONE", "Dehalfopped the requested lusers." },
162 { "OSMSG_DEHOPALL_DONE", "Dehalfopped everyone on $b%s$b." },
d76ed9a9
AS
163 { "OSMSG_NO_DEBUG_CHANNEL", "No debug channel has been configured." },
164 { "OSMSG_INVITE_DONE", "Invited $b%s$b to $b%s$b." },
165 { "OSMSG_ALREADY_THERE", "You are already in $b%s$b." },
39c1a4ef 166 { "OSMSG_NOT_THERE", "You not in $b%s$b." },
d76ed9a9 167 { "OSMSG_JOIN_DONE", "I have joined $b%s$b." },
c408f18a 168 { "OSMSG_SVSJOIN_SENT", "Sent the SVSJOIN." },
39c1a4ef 169 { "OSMSG_SVSPART_SENT", "Sent the SVSPART." },
d76ed9a9
AS
170 { "OSMSG_ALREADY_JOINED", "I am already in $b%s$b." },
171 { "OSMSG_NOT_ON_CHANNEL", "$b%s$b does not seem to be on $b%s$b." },
172 { "OSMSG_KICKALL_DONE", "I have cleared out %s." },
173 { "OSMSG_LEAVING", "Leaving $b%s$b." },
174 { "OSMSG_MODE_SET", "I have set the modes for $b%s$b." },
175 { "OSMSG_OP_DONE", "Opped the requested lusers." },
176 { "OSMSG_OPALL_DONE", "Opped everyone on $b%s$b." },
55342ce8 177 { "OSMSG_HOP_DONE", "Halfopped the requested lusers." },
178 { "OSMSG_HOPALL_DONE", "Halfopped everyone on $b%s$b." },
37ef8ee3 179 { "OSMSG_WHOIS_IDENT", "%s (%s@%s) from %d.%d.%d.%d" },
21f6caee 180 { "OSMSG_WHOIS_NICK", "Nick : %s" },
181 { "OSMSG_WHOIS_HOST", "Host : %s@%s" },
182 { "OSMSG_WHOIS_FAKEHOST", "Fakehost : %s" },
183 { "OSMSG_WHOIS_CRYPT_HOST", "Crypt Host : %s" },
184 { "OSMSG_WHOIS_CRYPT_IP", "Crypt IP : %s" },
185 { "OSMSG_WHOIS_IP", "Real IP : %s" },
186 { "OSMSG_WHOIS_COUNTRY", "Country : %s" },
187 { "OSMSG_WHOIS_COUNTRY_CODE","Country Code : %s" },
188 { "OSMSG_WHOIS_CITY", "City : %s" },
189 { "OSMSG_WHOIS_REGION", "Region/State : %s" },
190 { "OSMSG_WHOIS_POSTAL_CODE","Postal Code : %s" },
191 { "OSMSG_WHOIS_LATITUDE", "Latitude : %f" },
192 { "OSMSG_WHOIS_LONGITUDE", "Longitude : %f" },
1ad7ac15 193 { "OSMSG_WHOIS_MAP", "Map : %s" },
21f6caee 194 { "OSMSG_WHOIS_DMA_CODE", "DMA Code : %d" },
195 { "OSMSG_WHOIS_AREA_CODE", "Area Code : %d" },
196 { "OSMSG_WHOIS_MODES", "Modes : +%s " },
197 { "OSMSG_WHOIS_INFO", "Info : %s" },
198 { "OSMSG_WHOIS_NUMERIC", "Numnick : %s" },
199 { "OSMSG_WHOIS_SERVER", "Server : %s" },
200 { "OSMSG_WHOIS_NICK_AGE", "Nick Age : %s" },
201 { "OSMSG_WHOIS_ACCOUNT", "Account : %s" },
89d871d8 202 { "OSMSG_WHOIS_PRIVS", "IRCd Privs : %s" },
21f6caee 203 { "OSMSG_WHOIS_CHANNELS", "Channels : %s" },
37ef8ee3 204 { "OSMSG_WHOIS_HIDECHANS", "Channel list omitted for your sanity." },
0e08a8e0 205 { "OSMSG_WHOIS_VERSION", "Version : %s" },
d76ed9a9
AS
206 { "OSMSG_UNBAN_DONE", "Ban(s) removed from channel %s." },
207 { "OSMSG_CHANNEL_VOICED", "All users on %s voiced." },
208 { "OSMSG_CHANNEL_DEVOICED", "All voiced users on %s de-voiced." },
209 { "OSMSG_BAD_MODIFIER", "Unknown bad-word modifier $b%s$b." },
210 { "OSMSG_BAD_REDUNDANT", "$b%s$b is already covered by a bad word ($b%s$b)." },
211 { "OSMSG_BAD_GROWING", "Replacing bad word $b%s$b with shorter bad word $b%s$b." },
212 { "OSMSG_BAD_NUKING", " .. and removing redundant bad word $b%s$b." },
213 { "OSMSG_ADDED_BAD", "Added $b%s$b to the bad-word list." },
214 { "OSMSG_REMOVED_BAD", "Removed $b%s$b from the bad-word list." },
215 { "OSMSG_NOT_BAD_WORD", "$b%s$b is not a bad word." },
216 { "OSMSG_ADDED_EXEMPTION", "Added $b%s$b to the bad-word exemption list." },
217 { "OSMSG_ADDED_EXEMPTIONS", "Added %d exception(s) to the bad word list." },
218 { "OSMSG_REMOVED_EXEMPTION", "Removed $b%s$b from the exemption list." },
219 { "OSMSG_NOT_EXEMPT", "$b%s$b is not on the exempt list." },
220 { "OSMSG_ALREADY_TRUSTED", "Host $b%s$b is already trusted (use $bdeltrust$b and then $baddtrust$b to adjust)." },
221 { "OSMSG_NOT_TRUSTED", "Host $b%s$b is not trusted." },
222 { "OSMSG_BAD_IP", "$b%s$b is not a valid IP address" },
223 { "OSMSG_BAD_NUMBER", "$b%s$b is not a number" },
224 { "OSMSG_ADDED_TRUSTED", "Added trusted hosts to the trusted-hosts list." },
225 { "OSMSG_UPDATED_TRUSTED", "Updated trusted host $b%s$b." },
226 { "OSMSG_REMOVED_TRUSTED", "Removed trusted hosts from the trusted-hosts list." },
227 { "OSMSG_CLONE_EXISTS", "Nick $b%s$b is already in use." },
228 { "OSMSG_NOT_A_HOSTMASK", "The hostmask must be in user@host form." },
229 { "OSMSG_BADWORD_LIST", "Bad words: %s" },
230 { "OSMSG_EXEMPTED_LIST", "Exempted channels: %s" },
231 { "OSMSG_GLINE_COUNT", "There are %d glines active on the network." },
d914d1cb 232 { "OSMSG_SHUN_COUNT", "There are %d shuns active on the network." },
d76ed9a9
AS
233 { "OSMSG_LINKS_SERVER", "%s%s (%u clients; %s)" },
234 { "OSMSG_MAX_CLIENTS", "Max clients: %d at %s" },
235 { "OSMSG_NETWORK_INFO", "Total users: %d (%d invisible, %d opers)" },
236 { "OSMSG_RESERVED_LIST", "List of reserved nicks:" },
de9510bc
AS
237 { "OSMSG_TRUSTED_LIST", "$bTrusted Hosts$b" },
238 { "OSMSG_TRUSTED_LIST_HEADER", "IP Address Limit By Time" },
239 { "OSMSG_HOST_IS_TRUSTED", "%-15s %-5s %-10s set %s ago, expires %s" },
240 { "OSMSG_HOST_IS_TRUSTED_DESC", " Reason: %s" },
241 { "OSMSG_TRUSTED_LIST_BAR", "----------------------------------------" },
242 { "OSMSG_TRUSTED_LIST_END", "----------End of Trusted Hosts----------" },
d76ed9a9
AS
243 { "OSMSG_HOST_NOT_TRUSTED", "%s does not have a special trust." },
244 { "OSMSG_UPTIME_STATS", "Uptime: %s (%u lines processed, CPU time %.2fu/%.2fs)" },
245 { "OSMSG_LINE_DUMPED", "Raw line sent." },
246 { "OSMSG_RAW_PARSE_ERROR", "Error parsing raw line (not dumping to uplink)." },
247 { "OSMSG_COLLIDED_NICK", "Now temporarily holding nick $b%s$b." },
248 { "OSMSG_RESERVED_NICK", "Now reserving nick $b%s$b." },
249 { "OSMSG_NICK_UNRESERVED", "Nick $b%s$b is no longer reserved." },
250 { "OSMSG_NOT_RESERVED", "Nick $b%s$b is not reserved." },
251 { "OSMSG_ILLEGAL_REASON", "This channel is illegal." },
252 { "OSMSG_ILLEGAL_KILL_REASON", "Joined an illegal modeless channel - do not repeat." },
253 { "OSMSG_ILLEGAL_CHANNEL", "$b%s$b is an ILLEGAL channel. Do not re-join it." },
254 { "OSMSG_FLOOD_MODERATE", "This channel has been temporarily moderated due to a possible join flood attack detected in this channel; network staff have been notified and will investigate." },
255 { "OSMSG_CLONE_WARNING", "WARNING: You have connected the maximum permitted number of clients from one IP address (clones). If you connect any more, your host will be temporarily banned from the network." },
256 { "OSMSG_CLONE_ADDED", "Added clone $b%s$b." },
257 { "OSMSG_CLONE_FAILED", "Unable to add user $b%s$b." },
258 { "OSMSG_NOT_A_CLONE", "Har har. $b%s$b isn't a clone." },
259 { "OSMSG_CLONE_REMOVED", "Removed clone $b%s$b." },
260 { "OSMSG_CLONE_JOINED", "$b%s$b has joined $b%s$b." },
261 { "OSMSG_CLONE_PARTED", "$b%s$b has left $b%s$b." },
262 { "OSMSG_OPS_GIVEN", "I have given ops in $b%s$b to $b%s$b." },
55342ce8 263 { "OSMSG_HOPS_GIVEN", "I have given halfops in $b%s$b to $b%s$b." },
d76ed9a9
AS
264 { "OSMSG_CLONE_SAID", "$b%s$b has spoken to $b%s$b." },
265 { "OSMSG_UNKNOWN_SUBCOMMAND", "$b%s$b is not a valid subcommand of $b%s$b." },
266 { "OSMSG_UNKNOWN_OPTION", "$b%s$b has not been set." },
267 { "OSMSG_OPTION_IS", "$b%s$b is set to $b%s$b." },
268 { "OSMSG_OPTION_ROOT", "The following keys can be queried:" },
269 { "OSMSG_OPTION_LIST", "$b%s$b contains the following values:" },
270 { "OSMSG_OPTION_KEYS", "$b%s$b contains the following keys:" },
271 { "OSMSG_OPTION_LIST_EMPTY", "Empty list." },
272 { "OSMSG_SET_NOT_SET", "$b%s$b does not exist, and cannot be set." },
273 { "OSMSG_SET_BAD_TYPE", "$b%s$b is not a string, and cannot be set." },
274 { "OSMSG_SET_SUCCESS", "$b%s$b has been set to $b%s$b." },
275 { "OSMSG_SETTIME_SUCCESS", "Set time for servers named like $b%s$b." },
276 { "OSMSG_BAD_ACTION", "Unrecognized trace action $b%s$b." },
277 { "OSMSG_USER_SEARCH_RESULTS", "The following users were found:" },
de9510bc
AS
278 { "OSMSG_USER_SEARCH_HEADER", "Nick User@Host (Account)" },
279 { "OSMSG_USER_SEARCH_BAR", "-------------------------------------------" },
8e11460f
AS
280 { "OSMSG_USER_SEARCH_COUNT", "There were %4u matches" },
281 { "OSMSG_USER_SEARCH_COUNT_BAR", "------------ Found %4u matches -----------" },
a62ba70c 282 { "OSMSG_SVSJOIN_NO_TARGET", "SVSJOIN action requires chantarget criteria (where should they join?)" },
39c1a4ef 283 { "OSMSG_SVSPART_NO_TARGET", "SVSPART action requires chantarget criteria (where should they join?)" },
d76ed9a9
AS
284 { "OSMSG_CHANNEL_SEARCH_RESULTS", "The following channels were found:" },
285 { "OSMSG_GLINE_SEARCH_RESULTS", "The following glines were found:" },
d914d1cb 286 { "OSMSG_SHUN_SEARCH_RESULTS", "The following shun were found:" },
d76ed9a9
AS
287 { "OSMSG_LOG_SEARCH_RESULTS", "The following log entries were found:" },
288 { "OSMSG_GSYNC_RUNNING", "Synchronizing glines from %s." },
d914d1cb 289 { "OSMSG_SSYNC_RUNNING", "Synchronizing shuns from %s." },
d76ed9a9 290 { "OSMSG_GTRACE_FORMAT", "%s (issued %s by %s, expires %s): %s" },
d914d1cb 291 { "OSMSG_STRACE_FORMAT", "%s (issued %s by %s, expires %s): %s" },
d76ed9a9
AS
292 { "OSMSG_GAG_APPLIED", "Gagged $b%s$b, affecting %d users." },
293 { "OSMSG_GAG_ADDED", "Gagged $b%s$b." },
294 { "OSMSG_REDUNDANT_GAG", "Gag $b%s$b is redundant." },
295 { "OSMSG_GAG_NOT_FOUND", "Could not find gag $b%s$b." },
296 { "OSMSG_NO_GAGS", "No gags have been set." },
297 { "OSMSG_UNGAG_APPLIED", "Ungagged $b%s$b, affecting %d users." },
298 { "OSMSG_UNGAG_ADDED", "Ungagged $b%s$b." },
299 { "OSMSG_TIMEQ_INFO", "%u events in timeq; next in %lu seconds." },
300 { "OSMSG_ALERT_EXISTS", "An alert named $b%s$b already exists." },
301 { "OSMSG_UNKNOWN_REACTION", "Unknown alert reaction $b%s$b." },
302 { "OSMSG_ADDED_ALERT", "Added alert named $b%s$b." },
a62ba70c 303 { "OSMSG_ALERT_ADD_FAILED", "Unable to add alert. Check syntax, required parts, and access" },
d76ed9a9
AS
304 { "OSMSG_REMOVED_ALERT", "Removed alert named $b%s$b." },
305 { "OSMSG_NO_SUCH_ALERT", "No alert named $b%s$b could be found." },
de9510bc
AS
306 { "OSMSG_ALERTS_LIST", "$bCurrent $O alerts$b" },
307 { "OSMSG_ALERTS_BAR", "----------------------------------------------" },
308 { "OSMSG_ALERTS_HEADER", "Name Action (by Oper)" },
309 { "OSMSG_ALERTS_DESC", " Criteria: %s" },
310 { "OSMSG_ALERT_IS", "$b%-20s$b %-6s (by %s)" },
311 { "OSMSG_ALERT_END", "----------------End of Alerts-----------------" },
47956fc5
AS
312 /* routing messages */
313 { "OSMSG_ROUTINGPLAN_LIST", "$bRouting Plans$b" },
314 { "OSMSG_ROUTINGPLAN_BAR", "----------------------------------------------" },
315 { "OSMSG_ROUTINGPLAN_END", "-------------End of Routing Plans-------------" },
316 { "OSMSG_ROUTINGPLAN_OPTION", "%s is set to %s" },
317 { "OSMSG_ROUTINGPLAN_ACTIVE", "Auto routing is active, using plan '%s'." },
318 { "OSMSG_ROUTING_ACTIVATION_ERROR", "There was an error activating the routing plan. Check for loops, and make sure the map includes my own uplink." },
319 { "OSMSG_ROUTINGPLAN_OPTION_NOT_FOUND", "There is no routing plan option '%s'." },
320 { "OSMSG_ROUTINGPLAN_OPTION_NOT_SET", "Option '%s' is not currently set." },
321 { "OSMSG_ROUTINGPLAN_NAME", "$b%s:$b" },
322 { "OSMSG_ROUTINGPLAN_SERVER"," %s:%d <-- %s[%d/%s] (%s)" },
323 { "OSMSG_ADDPLAN_SUCCESS", "Added new routing plan '%s'." },
ce9266cf 324 { "OSMSG_ADDPLAN_FAILED", "Could not add new plan '%s' (does it already exist?)." },
47956fc5
AS
325 { "OSMSG_INVALID_PLAN", "That routing plan name is not valid." },
326 { "OSMSG_PLAN_DELETED", "The routing plan was sucessfully deleted." },
327 { "OSMSG_PLAN_NOT_FOUND", "There is no routing plan called '%s'." },
328 { "OSMSG_PLAN_SERVER_ADDED", "Added %s to the routing plan." },
329 { "OSMSG_PLAN_SERVER_DELETED", "The server has been deleted." },
330 { "OSMSG_PLAN_SERVER_NOT_FOUND", "The server '%s' was not found in that routing plan." },
331 { "OSMSG_ROUTING_DISABLED", "Routing is now disabled." },
332 { "OSMSG_DOWNLINKS_FORMAT_A", "%s%s-$b%s$b [%s]" },
333 { "OSMSG_DOWNLINKS_FORMAT_B", "$b%s$b (me)" },
334 { "OSMSG_ROUTELIST_EMPTY", "No servers in route list" },
335 { "OSMSG_ROUTELIST_AS_PLANNED", "Routing plan: Servers as they SHOULD be linked" },
336 { "OSMSG_MAP_CENTERED", "map %s centered, Maxdepth:%d" },
337 { "OSMSG_NO_SERVERS_MISSING", "No servers are missing." },
338 { "OSMSG_CONNECTING_MISSING", "Attempted to connect %d missing servers." },
339 { "OSMSG_CONNECT", "->connect %s %d %s" },
340 { "OSMSG_SQUIT", "->squit %s" },
341 { "OSMSG_COULDNT_FIND_SERVER", "Couldnt find %s, so using %s to link %s" },
342 { "OSMSG_INSPECTING_SERVER", "Inspecting server [%s]" },
343 { "OSMSG_REROUTING_ACC_MAP", "Rerouting network according to loaded map.." },
964abe6b 344 { "OSMSG_REROUTING_NOTCONFIGURED", "You have not configured routing. See $/msg $O help routing$b." },
47956fc5
AS
345 { "OSMSG_CONNECTING_MISSING_ONLY", "Connecting missing servers only.." },
346 { "OSMSG_NO_ROUTING_NECESSARY", "No rerouting appears necessary." },
347 { "OSMSG_TESTING_REROUTE", "Testing Reroute(): Commands not sent to socket.." },
348 { "OSMSG_INVALID_DIRECTIVE", "Reroute(): Invalid directive %s", },
349 { "OSMSG_UPLINKS_MISSING", "%d servers' uplinks were missing, and were not connected." },
350 { "OSMSG_REROUTE_COMPLETE", "Reroute complete: Moved %d, connected %d, total %d changes." },
351 /* end of routing */
d76ed9a9
AS
352 { "OSMSG_REHASH_COMPLETE", "Completed rehash of configuration database." },
353 { "OSMSG_REHASH_FAILED", "Rehash of configuration database failed, previous configuration is intact." },
354 { "OSMSG_REOPEN_COMPLETE", "Closed and reopened all log files." },
355 { "OSMSG_RECONNECTING", "Reconnecting to my uplink." },
356 { "OSMSG_NUMERIC_COLLIDE", "Numeric %d (%s) is already in use." },
357 { "OSMSG_NAME_COLLIDE", "That name is already in use." },
358 { "OSMSG_SRV_CREATE_FAILED", "Server creation failed -- check log files." },
359 { "OSMSG_SERVER_JUPED", "Added new jupe server %s." },
df5f6070
AS
360 { "OSMSG_INVALID_NUMERIC", "Invalid numeric" },
361 { "OSMSG_INVALID_SERVERNAME", "Server name must contain a '.'." },
d76ed9a9
AS
362 { "OSMSG_SERVER_NOT_JUPE", "That server is not a juped server." },
363 { "OSMSG_SERVER_UNJUPED", "Server jupe removed." },
c52666c6 364 /*
d76ed9a9
AS
365 { "OSMSG_WARN_ADDED", "Added channel activity warning for $b%s$b (%s)" },
366 { "OSMSG_WARN_EXISTS", "Channel activity warning for $b%s$b already exists." },
367 { "OSMSG_WARN_DELETED", "Removed channel activity warning for $b%s$b" },
368 { "OSMSG_WARN_NOEXIST", "Channel activity warning for $b%s$b does not exist." },
369 { "OSMSG_WARN_LISTSTART", "Channel activity warnings:" },
370 { "OSMSG_WARN_LISTENTRY", "%s (%s)" },
371 { "OSMSG_WARN_LISTEND", "End of activity warning list." },
c52666c6 372 */
d76ed9a9
AS
373 { "OSMSG_UPLINK_CONNECTING", "Establishing connection with %s (%s:%d)." },
374 { "OSMSG_CURRENT_UPLINK", "$b%s$b is already the current uplink." },
375 { "OSMSG_INVALID_UPLINK", "$b%s$b is not a valid uplink name." },
376 { "OSMSG_UPLINK_DISABLED", "$b%s$b is a disabled or unavailable uplink." },
377 { "OSMSG_UPLINK_START", "Uplink $b%s$b:" },
378 { "OSMSG_UPLINK_ADDRESS", "Address: %s:%d" },
379 { "OSMSG_STUPID_GLINE", "Gline %s? Now $bthat$b would be smooth." },
d914d1cb 380 { "OSMSG_STUPID_SHUN", "Shun %s? Now $bthat$b would be smooth." },
d76ed9a9
AS
381 { "OSMSG_ACCOUNTMASK_AUTHED", "Invalid criteria: it is impossible to match an account mask but not be authed" },
382 { "OSMSG_CHANINFO_HEADER", "%s Information" },
383 { "OSMSG_CHANINFO_TIMESTAMP", "Created on: %a %b %d %H:%M:%S %Y (%s)" },
384 { "OSMSG_CHANINFO_MODES", "Modes: %s" },
385 { "OSMSG_CHANINFO_MODES_BADWORD", "Modes: %s; bad-word channel" },
386 { "OSMSG_CHANINFO_TOPIC", "Topic (set by %%s, %a %b %d %H:%M:%S %Y): %%s" },
387 { "OSMSG_CHANINFO_TOPIC_UNKNOWN", "Topic: (none / not gathered)" },
388 { "OSMSG_CHANINFO_BAN_COUNT", "Bans (%d):" },
389 { "OSMSG_CHANINFO_BAN", "%%s by %%s (%a %b %d %H:%M:%S %Y)" },
2aef5f4b 390 { "OSMSG_CHANINFO_EXEMPT_COUNT", "Exempts (%d):" },
391 { "OSMSG_CHANINFO_EXEMPT", "%%s by %%s (%a %b %d %H:%M:%S %Y)" },
d76ed9a9
AS
392 { "OSMSG_CHANINFO_MANY_USERS", "%d users (\"/msg $S %s %s users\" for the list)" },
393 { "OSMSG_CHANINFO_USER_COUNT", "Users (%d):" },
394 { "OSMSG_CSEARCH_CHANNEL_INFO", "%s [%d users] %s %s" },
27eaa617 395 { "OSMSG_INVALID_REGEX", "Invalid regex: %s: %s (%d)" },
ec311f39 396 { "OSMSG_TRACK_DISABLED", "Tracking is not currently compiled into X3" },
7637f48f 397 { "OSMSG_MAXUSERS_RESET", "Max clients has been reset to $b%d$b" },
08895577 398
399 { "OSMSG_DEFCON_INVALID", "DefCon level %d is invalid, please choose a value between 1 and 5" },
400 { "OSMSG_DEFCON_ALLOWING_ALL", "DefCon is at level 5 and allowing everything" },
401 { "OSMSG_DEFCON_DISALLOWING", "DefCon is at level %d and enforcing:" },
402 { "OSMSG_DEFCON_NO_NEW_CHANNELS", "No Channel Registrations" },
403 { "OSMSG_DEFCON_NO_NEW_NICKS", "No Nickname/Account Registrations" },
404 { "OSMSG_DEFCON_NO_MODE_CHANGE", "No Channel Mode Changes" },
405 { "OSMSG_DEFCON_NO_NEW_CLIENTS", "No New Clients" },
406 { "OSMSG_DEFCON_FORCE_CHANMODES", "Forcing Channel Mode(s): %s" },
407 { "OSMSG_DEFCON_REDUCE_SESSION", "Forcing Reduced Session: %d" },
408 { "OSMSG_DEFCON_OPER_ONLY", "Allowing Services Communication With Opers Only" },
409 { "OSMSG_DEFCON_SILENT_OPER_ONLY", "Allowing Services Communication With Opers Only AND Silently Ignoring Regular Users" },
410 { "OSMSG_DEFCON_GLINE_NEW_CLIENTS", "Glining New Clients" },
0272358e 411 { "OSMSG_DEFCON_SHUN_NEW_CLIENTS", "Shunning New Clients" },
08895577 412 { "OSMSG_DEFCON_NO_NEW_MEMOS", "Disallowing New Memos" },
413
6c34bb5a 414 { "OSMSG_PRIV_UNKNOWN", "Unknown privilege flag %s, see /msg $O HELP PRIVFLAGS for a flag list" },
415 { "OSMSG_PRIV_SET", "Privilege flag %s has been %sset" },
416
d76ed9a9
AS
417 { NULL, NULL }
418};
419
b1bf690d 420#define OPSERV_SYNTAX() svccmd_send_help_brief(user, opserv, cmd)
d76ed9a9 421
08895577 422int DefConLevel = 5;
423int DefCon[6];
424int DefConTimeOut;
425int GlobalOnDefcon = 0;
426int GlobalOnDefconMore = 0;
427int DefConGlineExpire;
428int DefConModesSet = 0;
08895577 429unsigned int DefConSessionLimit;
430char *DefConChanModes;
431char *DefConGlineReason;
432char *DefConMessage;
433char *DefConOffMessage;
434
ec311f39 435extern void add_track_user(struct userNode *user);
d76ed9a9
AS
436typedef int (*discrim_search_func)(struct userNode *match, void *extra);
437
438struct userNode *opserv;
258d1427 439static struct service *opserv_service;
d76ed9a9 440
de9510bc 441/*static dict_t opserv_chan_warn; */ /* data is char* */
d76ed9a9
AS
442static dict_t opserv_reserved_nick_dict; /* data is struct userNode* */
443static struct string_list *opserv_bad_words;
444static dict_t opserv_exempt_channels; /* data is not used */
445static dict_t opserv_trusted_hosts; /* data is struct trusted_host* */
47956fc5
AS
446static dict_t opserv_routing_plans; /* data is struct routingPlan */
447static dict_t opserv_routing_plan_options; /* data is a dict_t key->val list*/
448static dict_t opserv_waiting_connections; /* data is struct waitingConnection */
d76ed9a9
AS
449static dict_t opserv_hostinfo_dict; /* data is struct opserv_hostinfo* */
450static dict_t opserv_user_alerts; /* data is struct opserv_user_alert* */
451static dict_t opserv_nick_based_alerts; /* data is struct opserv_user_alert* */
452static dict_t opserv_channel_alerts; /* data is struct opserv_user_alert* */
453static struct module *opserv_module;
454static struct log_type *OS_LOG;
455static unsigned int new_user_flood;
456static char *level_strings[1001];
7637f48f 457struct string_list *autojoin_channels;
47956fc5 458struct route *opserv_route = NULL; /* Main active routing table from activate_routing()*/
d76ed9a9
AS
459
460static struct {
461 struct chanNode *debug_channel;
462 struct chanNode *alert_channel;
463 struct chanNode *staff_auth_channel;
464 struct policer_params *join_policer_params;
465 struct policer new_user_policer;
466 unsigned long untrusted_max;
467 unsigned long clone_gline_duration;
468 unsigned long block_gline_duration;
d914d1cb 469 unsigned long block_shun_duration;
d76ed9a9
AS
470 unsigned long purge_lock_delay;
471 unsigned long join_flood_moderate;
472 unsigned long join_flood_moderate_threshold;
5a1daaab 473 unsigned long admin_level;
1c5f6697 474 unsigned long silent_level;
d76ed9a9
AS
475} opserv_conf;
476
477struct trusted_host {
478 char *ipaddr;
479 char *issuer;
480 char *reason;
481 unsigned long limit;
482 time_t issued;
483 time_t expires;
484};
485
486struct gag_entry {
487 char *mask;
488 char *owner;
489 char *reason;
490 time_t expires;
491 struct gag_entry *next;
492};
493
494static struct gag_entry *gagList;
495
496struct opserv_hostinfo {
497 struct userList clients;
498 struct trusted_host *trusted;
499};
500
501static void
502opserv_free_hostinfo(void *data)
503{
504 struct opserv_hostinfo *ohi = data;
505 userList_clean(&ohi->clients);
506 free(ohi);
507}
508
47956fc5
AS
509static void
510opserv_free_waiting_connection(void *data)
511{
512 struct waitingConnection *wc = data;
513 free(wc->server);
514 free(wc->target);
515 free(wc);
516}
517
d76ed9a9
AS
518typedef struct opservDiscrim {
519 struct chanNode *channel;
0e08a8e0 520 char *mask_nick, *mask_ident, *mask_host, *mask_info, *mask_version, *server, *reason, *accountmask, *chantarget;
2f61d1d7 521 irc_in_addr_t ip_mask;
522 unsigned long limit;
523 time_t min_ts, max_ts;
0e08a8e0
AS
524 regex_t regex_nick, regex_ident, regex_host, regex_info, regex_version;
525 unsigned int has_regex_nick : 1, has_regex_ident : 1, has_regex_host : 1, has_regex_info : 1, has_regex_version : 1;
d76ed9a9 526 unsigned int min_level, max_level, domain_depth, duration, min_clones, min_channels, max_channels;
2f61d1d7 527 unsigned char ip_mask_bits;
d76ed9a9
AS
528 unsigned int match_opers : 1, option_log : 1;
529 unsigned int chan_req_modes : 2, chan_no_modes : 2;
530 int authed : 2, info_space : 2;
63665495 531 unsigned int intra_scmp : 2, intra_dcmp : 2;
27eaa617 532 unsigned int use_regex : 1;
1c5f6697 533 unsigned int silent : 1;
d76ed9a9
AS
534} *discrim_t;
535
536struct discrim_and_source {
537 discrim_t discrim;
538 struct userNode *source;
258d1427 539 struct userNode *destination;
d76ed9a9
AS
540 dict_t dict;
541 unsigned int disp_limit;
542};
543
258d1427 544static discrim_t opserv_discrim_create(struct userNode *user, struct userNode *bot, unsigned int argc, char *argv[], int allow_channel);
d76ed9a9
AS
545static unsigned int opserv_discrim_search(discrim_t discrim, discrim_search_func dsf, void *data);
546static int gag_helper_func(struct userNode *match, void *extra);
547static int ungag_helper_func(struct userNode *match, void *extra);
548
549typedef enum {
550 REACT_NOTICE,
551 REACT_KILL,
d914d1cb 552 REACT_GLINE,
ec311f39 553 REACT_TRACK,
c408f18a 554 REACT_SHUN,
0e08a8e0 555 REACT_SVSJOIN,
39c1a4ef 556 REACT_SVSPART,
0e08a8e0 557 REACT_VERSION
d76ed9a9
AS
558} opserv_alert_reaction;
559
560struct opserv_user_alert {
561 char *owner;
562 char *text_discrim, *split_discrim;
563 discrim_t discrim;
564 opserv_alert_reaction reaction;
565};
566
567/* funny type to make it acceptible to dict_set_free_data, far below */
568static void
569opserv_free_user_alert(void *data)
570{
571 struct opserv_user_alert *alert = data;
572 if (alert->discrim->channel)
573 UnlockChannel(alert->discrim->channel);
574 free(alert->owner);
575 free(alert->text_discrim);
576 free(alert->split_discrim);
27eaa617 577 if(alert->discrim->has_regex_nick)
578 regfree(&alert->discrim->regex_nick);
579 if(alert->discrim->has_regex_ident)
580 regfree(&alert->discrim->regex_ident);
581 if(alert->discrim->has_regex_host)
582 regfree(&alert->discrim->regex_host);
583 if(alert->discrim->has_regex_info)
584 regfree(&alert->discrim->regex_info);
0e08a8e0
AS
585 if(alert->discrim->has_regex_version)
586 regfree(&alert->discrim->regex_version);
d76ed9a9
AS
587 free(alert->discrim->reason);
588 free(alert->discrim);
589 free(alert);
590}
591
592#define opserv_debug(format...) do { if (opserv_conf.debug_channel) send_channel_notice(opserv_conf.debug_channel , opserv , ## format); } while (0)
593#define opserv_alert(format...) do { if (opserv_conf.alert_channel) send_channel_notice(opserv_conf.alert_channel , opserv , ## format); } while (0)
594
7637f48f 595
08895577 596char *defconReverseModes(const char *modes)
597{
598 char *newmodes = NULL;
599 unsigned int i = 0;
600 if (!modes) {
601 return NULL;
602 }
603 if (!(newmodes = malloc(sizeof(char) * strlen(modes) + 1))) {
604 return NULL;
605 }
606 for (i = 0; i < strlen(modes); i++) {
607 if (modes[i] == '+')
608 newmodes[i] = '-';
609 else if (modes[i] == '-')
610 newmodes[i] = '+';
611 else
612 newmodes[i] = modes[i];
613 }
614 newmodes[i] = '\0';
615 return newmodes;
616}
617
618int checkDefCon(int level)
619{
620 return DefCon[DefConLevel] & level;
621}
622
623void showDefConSettings(struct userNode *user, struct svccmd *cmd)
624{
625 if (DefConLevel == 5) {
626 reply("OSMSG_DEFCON_ALLOWING_ALL");
627 return;
628 } else
629 reply("OSMSG_DEFCON_DISALLOWING", DefConLevel);
630
631 if (checkDefCon(DEFCON_NO_NEW_CHANNELS))
632 reply("OSMSG_DEFCON_NO_NEW_CHANNELS");
633
634 if (checkDefCon(DEFCON_NO_NEW_NICKS))
635 reply("OSMSG_DEFCON_NO_NEW_NICKS");
636
637 if (checkDefCon(DEFCON_NO_MODE_CHANGE))
638 reply("OSMSG_DEFCON_NO_MODE_CHANGE");
639
640 if (checkDefCon(DEFCON_FORCE_CHAN_MODES) && (DefConChanModes))
641 reply("OSMSG_DEFCON_FORCE_CHANMODES", DefConChanModes);
642
643 if (checkDefCon(DEFCON_REDUCE_SESSION))
644 reply("OSMSG_DEFCON_REDUCE_SESSION", DefConSessionLimit);
645
646 if (checkDefCon(DEFCON_NO_NEW_CLIENTS))
647 reply("OSMSG_DEFCON_NO_NEW_CLIENTS");
648
649 if (checkDefCon(DEFCON_OPER_ONLY))
650 reply("OSMSG_DEFCON_OPER_ONLY");
651
652 if (checkDefCon(DEFCON_SILENT_OPER_ONLY))
653 reply("OSMSG_DEFCON_SILENT_OPER_ONLY");
654
655 if (checkDefCon(DEFCON_GLINE_NEW_CLIENTS))
656 reply("OSMSG_DEFCON_GLINE_NEW_CLIENTS");
657
0272358e 658 if (checkDefCon(DEFCON_SHUN_NEW_CLIENTS))
659 reply("OSMSG_DEFCON_SHUN_NEW_CLIENTS");
660
08895577 661 if (checkDefCon(DEFCON_NO_NEW_MEMOS))
662 reply("OSMSG_DEFCON_NO_NEW_MEMOS");
663
664 return;
665}
666
667void do_mass_mode(char *modes)
668{
669 dict_iterator_t it;
670
671 if (!modes)
672 return;
673
674 for (it = dict_first(channels); it; it = iter_next(it)) {
675 struct chanNode *chan = iter_data(it);
676
677 irc_mode(opserv, chan, modes);
678 }
679
680}
681
682void DefConProcess(struct userNode *user)
683{
684 char *newmodes;
08895577 685
985d4109 686 if (GlobalOnDefcon)
687 global_message_args(MESSAGE_RECIPIENT_LUSERS, "DEFCON_NETWORK_CHANGED", DefConLevel);
08895577 688
985d4109 689 if (GlobalOnDefconMore && GlobalOnDefcon)
690 global_message(MESSAGE_RECIPIENT_LUSERS, DefConMessage);
08895577 691
692 if ((DefConLevel == 5) && !GlobalOnDefconMore && !GlobalOnDefcon)
985d4109 693 global_message(MESSAGE_RECIPIENT_LUSERS, DefConOffMessage);
08895577 694
985d4109 695 if (user)
696 global_message_args(MESSAGE_RECIPIENT_OPERS, "DEFCON_OPER_LEVEL_CHANGE", user->nick, DefConLevel);
697 else
698 global_message_args(MESSAGE_RECIPIENT_OPERS, "DEFCON_TIMEOUT_LEVEL_CHANGE", DefConLevel);
08895577 699
700 if (checkDefCon(DEFCON_FORCE_CHAN_MODES)) {
701 if (DefConChanModes && !DefConModesSet) {
702 if (DefConChanModes[0] == '+' || DefConChanModes[0] == '-') {
703 do_mass_mode(DefConChanModes);
704 DefConModesSet = 1;
705 }
706 }
707 } else {
708 if (DefConChanModes && (DefConModesSet != 0)) {
709 if (DefConChanModes[0] == '+' || DefConChanModes[0] == '-') {
710 if ((newmodes = defconReverseModes(DefConChanModes))) {
711 do_mass_mode(newmodes);
712 free(newmodes);
713 }
714 DefConModesSet = 0;
715 }
716 }
717 }
718
719 return;
720}
721
722void
723defcon_timeout(UNUSED_ARG(void *data))
724{
725 DefConLevel = 5;
726 DefConProcess(NULL);
727}
728
729static MODCMD_FUNC(cmd_defcon)
730{
731 if ((argc < 2) || (atoi(argv[1]) == DefConLevel)) {
732 showDefConSettings(user, cmd);
733 return 1;
734 }
735
736 if ((atoi(argv[1]) < 1) || (atoi(argv[1]) > 5)) {
737 reply("OSMSG_DEFCON_INVALID", atoi(argv[1]));
738 return 0;
739 }
740
741 DefConLevel = atoi(argv[1]);
742 showDefConSettings(user, cmd);
743
744 if (DefConTimeOut > 0) {
745 timeq_del(0, defcon_timeout, NULL, TIMEQ_IGNORE_DATA & TIMEQ_IGNORE_WHEN);
746 timeq_add(now + DefConTimeOut, defcon_timeout, NULL);
747 }
748
749 DefConProcess(user);
750 return 1;
751}
752
6c34bb5a 753/* TODO
754static MODCMD_FUNC(cmd_privallow)
755{
756//privallow servername/username +/-flag (global is set in conf)
757}
758
759static MODCMD_FUNC(cmd_privdissallow)
760{
761//privdisallow servername/username +/-flag (global is set in conf)
762}
763
764static MODCMD_FUNC(cmd_privlist)
765{
766//privlist servername/user (global with none)
767}
768*/
769
770static MODCMD_FUNC(cmd_privset)
771{
772 struct userNode *target;
773 char *flag;
774 int add = PRIV_ADD;
775
776 flag = argv[2];
777 if (*flag == '-') {
778 add = PRIV_DEL;
779 flag++;
780 } else if (*flag == '+') {
781 add = PRIV_ADD;
782 flag++;
783 }
784
785 target = GetUserH(argv[1]);
786 if (!target) {
787 reply("MSG_NICK_UNKNOWN", argv[1]);
788 return 0;
789 }
790
791 if (check_priv(flag)) {
792 irc_privs(target, flag, add);
793 reply("OSMSG_PRIV_SET", argv[2], (add == 1) ? "" : "un");
794 } else {
795 reply("OSMSG_PRIV_UNKNOWN", argv[2]);
796 return 0;
797 }
798
799 return 1;
800}
801
d76ed9a9
AS
802/* A lot of these commands are very similar to what ChanServ can do,
803 * but OpServ can do them even on channels that aren't registered.
804 */
805
806static MODCMD_FUNC(cmd_access)
807{
808 struct handle_info *hi;
809 const char *target;
810 unsigned int res;
811
812 target = (argc > 1) ? (const char*)argv[1] : user->nick;
813 if (!irccasecmp(target, "*")) {
814 nickserv_show_oper_accounts(user, cmd);
815 return 1;
816 }
817 if (!(hi = modcmd_get_handle_info(user, target)))
818 return 0;
258d1427 819 res = (argc > 2) ? oper_try_set_access(user, opserv_service->bot, hi, strtoul(argv[2], NULL, 0)) : 0;
d76ed9a9
AS
820 reply("OSMSG_USER_ACCESS_IS", target, hi->handle, hi->opserv_level);
821 return res;
822}
823
824static MODCMD_FUNC(cmd_ban)
825{
826 struct mod_chanmode change;
827 struct userNode *victim;
828
829 mod_chanmode_init(&change);
830 change.argc = 1;
831 change.args[0].mode = MODE_BAN;
832 if (is_ircmask(argv[1]))
a32da4c7 833 change.args[0].u.hostmask = strdup(argv[1]);
d76ed9a9 834 else if ((victim = GetUserH(argv[1])))
a32da4c7 835 change.args[0].u.hostmask = generate_hostmask(victim, 0);
d76ed9a9 836 else {
258d1427
AS
837 reply("OSMSG_INVALID_IRCMASK", argv[1]);
838 return 0;
d76ed9a9
AS
839 }
840 modcmd_chanmode_announce(&change);
a32da4c7 841 reply("OSMSG_ADDED_BAN", change.args[0].u.hostmask, channel->name);
842 free((char*)change.args[0].u.hostmask);
d76ed9a9
AS
843 return 1;
844}
845
846static MODCMD_FUNC(cmd_chaninfo)
847{
848 char buffer[MAXLEN];
849 const char *fmt;
850 struct banNode *ban;
2aef5f4b 851 struct exemptNode *exempt;
d76ed9a9
AS
852 struct modeNode *moden;
853 unsigned int n;
854
855 reply("OSMSG_CHANINFO_HEADER", channel->name);
856 fmt = user_find_message(user, "OSMSG_CHANINFO_TIMESTAMP");
857 strftime(buffer, sizeof(buffer), fmt, gmtime(&channel->timestamp));
858 send_message_type(4, user, cmd->parent->bot, "%s", buffer);
859 irc_make_chanmode(channel, buffer);
860 if (channel->bad_channel)
861 reply("OSMSG_CHANINFO_MODES_BADWORD", buffer);
862 else
863 reply("OSMSG_CHANINFO_MODES", buffer);
864 if (channel->topic_time) {
865 fmt = user_find_message(user, "OSMSG_CHANINFO_TOPIC");
866 strftime(buffer, sizeof(buffer), fmt, gmtime(&channel->topic_time));
867 send_message_type(4, user, cmd->parent->bot, buffer, channel->topic_nick, channel->topic);
868 } else {
258d1427
AS
869 irc_fetchtopic(cmd->parent->bot, channel->name);
870 reply("OSMSG_CHANINFO_TOPIC_UNKNOWN");
d76ed9a9
AS
871 }
872 if (channel->banlist.used) {
258d1427 873 reply("OSMSG_CHANINFO_BAN_COUNT", channel->banlist.used);
d76ed9a9 874 fmt = user_find_message(user, "OSMSG_CHANINFO_BAN");
258d1427
AS
875 for (n = 0; n < channel->banlist.used; n++) {
876 ban = channel->banlist.list[n];
877 strftime(buffer, sizeof(buffer), fmt, localtime(&ban->set));
878 send_message_type(4, user, cmd->parent->bot, buffer, ban->ban, ban->who);
879 }
d76ed9a9 880 }
2aef5f4b 881 if (channel->exemptlist.used) {
882 reply("OSMSG_CHANINFO_EXEMPT_COUNT", channel->exemptlist.used);
883 fmt = user_find_message(user, "OSMSG_CHANINFO_EXEMPT");
884 for (n = 0; n < channel->exemptlist.used; n++) {
885 exempt = channel->exemptlist.list[n];
886 strftime(buffer, sizeof(buffer), fmt, localtime(&exempt->set));
887 send_message_type(4, user, cmd->parent->bot, buffer, exempt->exempt, exempt->who);
888 }
889 }
d76ed9a9
AS
890 if ((argc < 2) && (channel->members.used >= 50)) {
891 /* early out unless they ask for users */
892 reply("OSMSG_CHANINFO_MANY_USERS", channel->members.used, argv[0], channel->name);
893 return 1;
894 }
895 reply("OSMSG_CHANINFO_USER_COUNT", channel->members.used);
896 for (n=0; n<channel->members.used; n++) {
258d1427 897 moden = channel->members.list[n];
2f61d1d7 898 if (moden->modes & MODE_CHANOP) {
899 if (moden->oplevel >= 0)
900 send_message_type(4, user, cmd->parent->bot, " @%s:%d (%s@%s)", moden->user->nick, moden->oplevel, moden->user->ident, moden->user->hostname);
901 else
902 send_message_type(4, user, cmd->parent->bot, " @%s (%s@%s)", moden->user->nick, moden->user->ident, moden->user->hostname);
903 }
d76ed9a9 904 }
55342ce8 905 for (n=0; n<channel->members.used; n++) {
906 moden = channel->members.list[n];
907 if ((moden->modes & (MODE_CHANOP|MODE_HALFOP|MODE_VOICE)) == MODE_HALFOP)
908 send_message_type(4, user, cmd->parent->bot, " %s%s (%s@%s)", "%", moden->user->nick, moden->user->ident, moden->user->hostname);
909 }
d76ed9a9 910 for (n=0; n<channel->members.used; n++) {
258d1427
AS
911 moden = channel->members.list[n];
912 if ((moden->modes & (MODE_CHANOP|MODE_HALFOP|MODE_VOICE)) == MODE_VOICE)
d76ed9a9
AS
913 send_message_type(4, user, cmd->parent->bot, " +%s (%s@%s)", moden->user->nick, moden->user->ident, moden->user->hostname);
914 }
915 for (n=0; n<channel->members.used; n++) {
258d1427
AS
916 moden = channel->members.list[n];
917 if ((moden->modes & (MODE_CHANOP|MODE_HALFOP|MODE_VOICE)) == 0)
d76ed9a9
AS
918 send_message_type(4, user, cmd->parent->bot, " %s (%s@%s)", moden->user->nick, moden->user->ident, moden->user->hostname);
919 }
920 return 1;
921}
922
de9510bc
AS
923/* This command has been replaced by 'alert notice channel #foo' */
924/*
2f61d1d7 925static MODCMD_FUNC(cmd_warn)
d76ed9a9
AS
926{
927 char *reason, *message;
928
929 if (!IsChannelName(argv[1])) {
258d1427
AS
930 reply("OSMSG_NEED_CHANNEL", argv[0]);
931 return 0;
d76ed9a9
AS
932 }
933 reason = dict_find(opserv_chan_warn, argv[1], NULL);
934 if (reason) {
935 reply("OSMSG_WARN_EXISTS", argv[1]);
936 return 0;
937 }
938 if (argv[2])
939 reason = strdup(unsplit_string(argv+2, argc-2, NULL));
940 else
941 reason = strdup("No reason");
942 dict_insert(opserv_chan_warn, strdup(argv[1]), reason);
943 reply("OSMSG_WARN_ADDED", argv[1], reason);
944 if (dict_find(channels, argv[1], NULL)) {
57692f5e 945 global_message_args(MESSAGE_RECIPIENT_OPERS, "OSMSG_CHANNEL_ACTIVITY_WARN" argv[1], reason);
d76ed9a9
AS
946 }
947 return 1;
948}
949
950static MODCMD_FUNC(cmd_unwarn)
951{
952 if ((argc < 2) || !IsChannelName(argv[1])) {
953 reply("OSMSG_NEED_CHANNEL", argv[0]);
258d1427 954 return 0;
d76ed9a9
AS
955 }
956 if (!dict_remove(opserv_chan_warn, argv[1])) {
957 reply("OSMSG_WARN_NOEXIST", argv[1]);
958 return 0;
959 }
960 reply("OSMSG_WARN_DELETED", argv[1]);
961 return 1;
962}
de9510bc 963*/
d76ed9a9
AS
964
965static MODCMD_FUNC(cmd_clearbans)
966{
967 struct mod_chanmode *change;
968 unsigned int ii;
969
970 change = mod_chanmode_alloc(channel->banlist.used);
971 for (ii=0; ii<channel->banlist.used; ii++) {
972 change->args[ii].mode = MODE_REMOVE | MODE_BAN;
ec1a68c8 973 change->args[ii].u.hostmask = strdup(channel->banlist.list[ii]->ban);
d76ed9a9
AS
974 }
975 modcmd_chanmode_announce(change);
ec1a68c8 976 for (ii=0; ii<change->argc; ++ii)
977 free((char*)change->args[ii].u.hostmask);
d76ed9a9
AS
978 mod_chanmode_free(change);
979 reply("OSMSG_CLEARBANS_DONE", channel->name);
980 return 1;
981}
982
983static MODCMD_FUNC(cmd_clearmodes)
984{
985 struct mod_chanmode change;
986
987 if (!channel->modes) {
258d1427 988 reply("OSMSG_NO_CHANNEL_MODES", channel->name);
d76ed9a9
AS
989 return 0;
990 }
991 mod_chanmode_init(&change);
992 change.modes_clear = channel->modes;
993 modcmd_chanmode_announce(&change);
994 reply("OSMSG_CLEARMODES_DONE", channel->name);
995 return 1;
996}
997
998static MODCMD_FUNC(cmd_deop)
999{
1000 struct mod_chanmode *change;
1001 unsigned int arg, count;
1002
1003 change = mod_chanmode_alloc(argc-1);
1004 for (arg = 1, count = 0; arg < argc; ++arg) {
1005 struct userNode *victim = GetUserH(argv[arg]);
1006 struct modeNode *mn;
258d1427 1007 if (!victim || IsService(victim)
d76ed9a9
AS
1008 || !(mn = GetUserMode(channel, victim))
1009 || !(mn->modes & MODE_CHANOP))
1010 continue;
1011 change->args[count].mode = MODE_REMOVE | MODE_CHANOP;
a32da4c7 1012 change->args[count++].u.member = mn;
d76ed9a9
AS
1013 }
1014 if (count) {
1015 change->argc = count;
1016 modcmd_chanmode_announce(change);
1017 }
1018 mod_chanmode_free(change);
1019 reply("OSMSG_DEOP_DONE");
1020 return 1;
1021}
1022
55342ce8 1023static MODCMD_FUNC(cmd_dehop)
1024{
1025 struct mod_chanmode *change;
1026 unsigned int arg, count;
1027
1028 change = mod_chanmode_alloc(argc-1);
1029 for (arg = 1, count = 0; arg < argc; ++arg) {
1030 struct userNode *victim = GetUserH(argv[arg]);
1031 struct modeNode *mn;
1032 if (!victim || IsService(victim)
1033 || !(mn = GetUserMode(channel, victim))
1034 || !(mn->modes & MODE_HALFOP))
1035 continue;
1036 change->args[count].mode = MODE_REMOVE | MODE_HALFOP;
11408ce4 1037 change->args[count++].u.member = mn;
55342ce8 1038 }
1039 if (count) {
1040 change->argc = count;
1041 modcmd_chanmode_announce(change);
1042 }
1043 mod_chanmode_free(change);
1044 reply("OSMSG_DEHOP_DONE");
1045 return 1;
1046}
1047
d76ed9a9
AS
1048static MODCMD_FUNC(cmd_deopall)
1049{
1050 struct mod_chanmode *change;
1051 unsigned int ii, count;
1052
1053 change = mod_chanmode_alloc(channel->members.used);
1054 for (ii = count = 0; ii < channel->members.used; ++ii) {
258d1427
AS
1055 struct modeNode *mn = channel->members.list[ii];
1056 if (IsService(mn->user) || !(mn->modes & MODE_CHANOP))
d76ed9a9
AS
1057 continue;
1058 change->args[count].mode = MODE_REMOVE | MODE_CHANOP;
a32da4c7 1059 change->args[count++].u.member = mn;
d76ed9a9
AS
1060 }
1061 if (count) {
1062 change->argc = count;
1063 modcmd_chanmode_announce(change);
1064 }
1065 mod_chanmode_free(change);
1066 reply("OSMSG_DEOPALL_DONE", channel->name);
1067 return 1;
1068}
1069
55342ce8 1070static MODCMD_FUNC(cmd_dehopall)
1071{
1072 struct mod_chanmode *change;
1073 unsigned int ii, count;
1074
1075 change = mod_chanmode_alloc(channel->members.used);
1076 for (ii = count = 0; ii < channel->members.used; ++ii) {
1077 struct modeNode *mn = channel->members.list[ii];
1078 if (IsService(mn->user) || !(mn->modes & MODE_HALFOP))
1079 continue;
1080 change->args[count].mode = MODE_REMOVE | MODE_HALFOP;
11408ce4 1081 change->args[count++].u.member = mn;
55342ce8 1082 }
1083 if (count) {
1084 change->argc = count;
1085 modcmd_chanmode_announce(change);
1086 }
1087 mod_chanmode_free(change);
1088 reply("OSMSG_DEHOPALL_DONE", channel->name);
1089 return 1;
1090}
1091
7637f48f 1092static MODCMD_FUNC(cmd_resetmax)
1093{
1094 max_clients = dict_size(clients);
1095 max_clients_time = now;
1096 reply("OSMSG_MAXUSERS_RESET", max_clients);
1097 return 1;
1098}
1099
d76ed9a9
AS
1100static MODCMD_FUNC(cmd_rehash)
1101{
1102 extern char *services_config;
1103
1104 if (conf_read(services_config))
258d1427 1105 reply("OSMSG_REHASH_COMPLETE");
d76ed9a9 1106 else
258d1427 1107 reply("OSMSG_REHASH_FAILED");
d76ed9a9
AS
1108 return 1;
1109}
1110
1111static MODCMD_FUNC(cmd_reopen)
1112{
1113 log_reopen();
1114 reply("OSMSG_REOPEN_COMPLETE");
1115 return 1;
1116}
1117
1118static MODCMD_FUNC(cmd_reconnect)
1119{
1120 reply("OSMSG_RECONNECTING");
1121 irc_squit(self, "Reconnecting.", NULL);
1122 return 1;
1123}
1124
1125static MODCMD_FUNC(cmd_jupe)
1126{
1127 extern int force_n2k;
1128 struct server *newsrv;
1129 unsigned int num;
1130 char numeric[COMBO_NUMERIC_LEN+1], srvdesc[SERVERDESCRIPTMAX+1];
1131
1132 num = atoi(argv[2]);
df5f6070
AS
1133 if(num == 0) {
1134 reply("OSMSG_INVALID_NUMERIC");
1135 return 0;
1136 }
d76ed9a9
AS
1137 if ((num < 64) && !force_n2k) {
1138 inttobase64(numeric, num, 1);
1139 inttobase64(numeric+1, 64*64-1, 2);
1140 } else {
1141 inttobase64(numeric, num, 2);
1142 inttobase64(numeric+2, 64*64*64-1, 3);
1143 }
1144#ifdef WITH_PROTOCOL_P10
1145 if (GetServerN(numeric)) {
1146 reply("OSMSG_NUMERIC_COLLIDE", num, numeric);
1147 return 0;
1148 }
1149#endif
1150 if (GetServerH(argv[1])) {
1151 reply("OSMSG_NAME_COLLIDE");
1152 return 0;
1153 }
1154 snprintf(srvdesc, sizeof(srvdesc), "JUPE %s", unsplit_string(argv+3, argc-3, NULL));
df5f6070
AS
1155 if(!strchr(argv[1], '.')) {
1156 reply("OSMSG_INVALID_SERVERNAME");
1157 return 0;
1158 }
d76ed9a9
AS
1159 newsrv = AddServer(self, argv[1], 1, now, now, numeric, srvdesc);
1160 if (!newsrv) {
1161 reply("OSMSG_SRV_CREATE_FAILED");
1162 return 0;
1163 }
1164 irc_server(newsrv);
1165 reply("OSMSG_SERVER_JUPED", argv[1]);
1166 return 1;
1167}
1168
1169static MODCMD_FUNC(cmd_unjupe)
1170{
1171 struct server *srv;
1172 char *reason;
1173
1174 srv = GetServerH(argv[1]);
1175 if (!srv) {
1176 reply("MSG_SERVER_UNKNOWN", argv[1]);
1177 return 0;
1178 }
1179 if (strncmp(srv->description, "JUPE", 4)) {
1180 reply("OSMSG_SERVER_NOT_JUPE");
1181 return 0;
1182 }
1183 reason = (argc > 2) ? unsplit_string(argv+2, argc-2, NULL) : "Unjuping server";
1184 DelServer(srv, 1, reason);
1185 reply("OSMSG_SERVER_UNJUPED");
1186 return 1;
1187}
1188
1189static MODCMD_FUNC(cmd_jump)
1190{
1191 extern struct cManagerNode cManager;
1192 void uplink_select(char *name);
1193 struct uplinkNode *uplink_find(char *name);
1194 struct uplinkNode *uplink;
1195 char *target;
1196
1197 target = unsplit_string(argv+1, argc-1, NULL);
1198
1199 if (!strcmp(cManager.uplink->name, target)) {
258d1427
AS
1200 reply("OSMSG_CURRENT_UPLINK", cManager.uplink->name);
1201 return 0;
d76ed9a9
AS
1202 }
1203
1204 uplink = uplink_find(target);
1205 if (!uplink) {
258d1427
AS
1206 reply("OSMSG_INVALID_UPLINK", target);
1207 return 0;
d76ed9a9
AS
1208 }
1209 if (uplink->flags & UPLINK_UNAVAILABLE) {
1210 reply("OSMSG_UPLINK_DISABLED", uplink->name);
1211 return 0;
1212 }
1213
1214 reply("OSMSG_UPLINK_CONNECTING", uplink->name, uplink->host, uplink->port);
1215 uplink_select(target);
1216 irc_squit(self, "Reconnecting.", NULL);
1217 return 1;
1218}
1219
1220static MODCMD_FUNC(cmd_die)
1221{
1222 char *reason, *text;
1223
1224 text = unsplit_string(argv+1, argc-1, NULL);
1225 reason = alloca(strlen(text) + strlen(user->nick) + 20);
1226 sprintf(reason, "Disconnected by %s [%s]", user->nick, text);
1227 irc_squit(self, reason, text);
1228 quit_services = 1;
1229 return 1;
1230}
1231
1232static MODCMD_FUNC(cmd_restart)
1233{
1234 extern int services_argc;
1235 extern char **services_argv;
1236 char **restart_argv, *reason, *text;
1237
1238 text = unsplit_string(argv+1, argc-1, NULL);
1239 reason = alloca(strlen(text) + strlen(user->nick) + 17);
1240 sprintf(reason, "Restarted by %s [%s]", user->nick, text);
1241 irc_squit(self, reason, text);
1242
1243 /* Append a NULL to the end of argv[]. */
1244 restart_argv = (char **)alloca((services_argc + 1) * sizeof(char *));
1245 memcpy(restart_argv, services_argv, services_argc * sizeof(char *));
1246 restart_argv[services_argc] = NULL;
1247
1248 call_exit_funcs();
1249
1250 /* Don't blink. */
1251 execv(services_argv[0], restart_argv);
1252
1253 /* If we're still here, that means something went wrong. Reconnect. */
1254 return 1;
1255}
1256
1257static struct gline *
9a75756e 1258opserv_block(struct userNode *target, char *src_handle, char *reason, unsigned long duration, int silent)
d76ed9a9 1259{
2f61d1d7 1260 char mask[IRC_NTOP_MAX_SIZE+3] = { '*', '@', '\0' };
1261 irc_ntop(mask + 2, sizeof(mask) - 2, &target->ip);
1262 if (!reason)
1263 snprintf(reason = alloca(MAXLEN), MAXLEN,
1264 "G-line requested by %s.", src_handle);
1265 if (!duration)
1266 duration = opserv_conf.block_gline_duration;
9a75756e 1267 return gline_add(src_handle, mask, duration, reason, now, 1, silent ? 1 : 0);
d76ed9a9
AS
1268}
1269
1270static MODCMD_FUNC(cmd_block)
1271{
1272 struct userNode *target;
1273 struct gline *gline;
1274 char *reason;
1275
1276 target = GetUserH(argv[1]);
1277 if (!target) {
258d1427
AS
1278 reply("MSG_NICK_UNKNOWN", argv[1]);
1279 return 0;
d76ed9a9
AS
1280 }
1281 if (IsService(target)) {
258d1427
AS
1282 reply("MSG_SERVICE_IMMUNE", target->nick);
1283 return 0;
d76ed9a9
AS
1284 }
1285 reason = (argc > 2) ? unsplit_string(argv+2, argc-2, NULL) : NULL;
9a75756e 1286 gline = opserv_block(target, user->handle_info->handle, reason, 0, 0);
d76ed9a9
AS
1287 reply("OSMSG_GLINE_ISSUED", gline->target);
1288 return 1;
1289}
1290
1291static MODCMD_FUNC(cmd_gline)
1292{
1293 unsigned long duration;
1294 char *reason;
1295 struct gline *gline;
1296
1297 reason = unsplit_string(argv+3, argc-3, NULL);
1298 if (!is_gline(argv[1]) && !IsChannelName(argv[1]) && (argv[1][0] != '&')) {
258d1427
AS
1299 reply("MSG_INVALID_GLINE", argv[1]);
1300 return 0;
d76ed9a9
AS
1301 }
1302 if (!argv[1][strspn(argv[1], "#&*?@.")] && (strlen(argv[1]) < 10)) {
1303 reply("OSMSG_STUPID_GLINE", argv[1]);
1304 return 0;
1305 }
1306 duration = ParseInterval(argv[2]);
1307 if (!duration) {
1308 reply("MSG_INVALID_DURATION", argv[2]);
1309 return 0;
1310 }
9a75756e 1311 gline = gline_add(user->handle_info->handle, argv[1], duration, reason, now, 1, 0);
d76ed9a9
AS
1312 reply("OSMSG_GLINE_ISSUED", gline->target);
1313 return 1;
1314}
1315
1316static MODCMD_FUNC(cmd_ungline)
1317{
1318 if (gline_remove(argv[1], 1))
1319 reply("OSMSG_GLINE_REMOVED", argv[1]);
1320 else
1321 reply("OSMSG_GLINE_FORCE_REMOVED", argv[1]);
1322 return 1;
1323}
1324
1325static MODCMD_FUNC(cmd_refreshg)
1326{
1327 if (argc > 1) {
1328 unsigned int count;
1329 dict_iterator_t it;
1330 struct server *srv;
1331
1332 for (it=dict_first(servers), count=0; it; it=iter_next(it)) {
1333 srv = iter_data(it);
1334 if ((srv == self) || !match_ircglob(srv->name, argv[1]))
1335 continue;
1336 gline_refresh_server(srv);
1337 reply("OSMSG_GLINES_ONE_REFRESHED", srv->name);
1338 count++;
1339 }
1340 if (!count) {
1341 reply("MSG_SERVER_UNKNOWN", argv[1]);
1342 return 0;
1343 }
1344 } else {
1345 gline_refresh_all();
1346 reply("OSMSG_GLINES_REFRESHED");
1347 }
1348 return 1;
1349}
1350
c408f18a 1351static void
0e08a8e0
AS
1352opserv_version(struct userNode *target)
1353{
1354 irc_version_user(opserv, target);
1355}
1356
1357static void
1358opserv_svsjoin(struct userNode *target, UNUSED_ARG(char *src_handle), UNUSED_ARG(char *reason), char *channame)
c408f18a
AS
1359{
1360 struct chanNode *channel;
1361
1362 if(!channame || !IsChannelName(channame)) {
1363 /* Not a valid channel name. We shouldnt ever get this if we check properly in addalert */
1364 return;
1365 }
1366
1367 if (!(channel = GetChannel(channame))) {
1368 channel = AddChannel(channame, now, NULL, NULL, NULL);
1369 }
1370 if (GetUserMode(channel, target)) {
1371 /* already in it */
1372 return;
1373 }
1374
1375 irc_svsjoin(opserv, target, channel);
1376 /* Should we tell the user they got joined? -Rubin*/
1377}
1378
39c1a4ef 1379static void
1380opserv_svspart(struct userNode *target, UNUSED_ARG(char *src_handle), UNUSED_ARG(char *reason), char *channame)
1381{
1382 struct chanNode *channel;
1383
1384 if(!channame || !IsChannelName(channame)) {
1385 /* Not a valid channel name. We shouldnt ever get this if we check properly in addalert */
1386 return;
1387 }
1388
1389 if (!(channel = GetChannel(channame))) {
1390 /* channel doesnt exist */
1391 return;
1392 }
1393
1394 if (!GetUserMode(channel, target)) {
1395 /* not in it */
1396 return;
1397 }
1398
1399 irc_svspart(opserv, target, channel);
1400}
1401
d914d1cb 1402static struct shun *
1403opserv_shun(struct userNode *target, char *src_handle, char *reason, unsigned long duration)
1404{
1405 char *mask;
1406 mask = alloca(MAXLEN);
1407 snprintf(mask, MAXLEN, "*@%s", target->hostname);
1408 if (!reason) {
1409 reason = alloca(MAXLEN);
1410 snprintf(reason, MAXLEN, "Shun requested by %s.", src_handle);
1411 }
1412 if (!duration) duration = opserv_conf.block_shun_duration;
1413 return shun_add(src_handle, mask, duration, reason, now, 1);
1414}
1415
1416static MODCMD_FUNC(cmd_sblock)
1417{
1418 struct userNode *target;
1419 struct shun *shun;
1420 char *reason;
1421
1422 target = GetUserH(argv[1]);
1423 if (!target) {
1424 reply("MSG_NICK_UNKNOWN", argv[1]);
1425 return 0;
1426 }
1427 if (IsService(target)) {
1428 reply("MSG_SERVICE_IMMUNE", target->nick);
1429 return 0;
1430 }
1431 reason = (argc > 2) ? unsplit_string(argv+2, argc-2, NULL) : NULL;
1432 shun = opserv_shun(target, user->handle_info->handle, reason, 0);
1433 reply("OSMSG_SHUN_ISSUED", shun->target);
1434 return 1;
1435}
1436
1437static MODCMD_FUNC(cmd_shun)
1438{
1439 unsigned long duration;
1440 char *reason;
1441 struct shun *shun;
1442
1443 reason = unsplit_string(argv+3, argc-3, NULL);
1444 if (!is_shun(argv[1]) && !IsChannelName(argv[1]) && (argv[1][0] != '&')) {
1445 reply("MSG_INVALID_SHUN", argv[1]);
1446 return 0;
1447 }
1448 if (!argv[1][strspn(argv[1], "#&*?@.")] && (strlen(argv[1]) < 10)) {
1449 reply("OSMSG_STUPID_SHUN", argv[1]);
1450 return 0;
1451 }
1452 duration = ParseInterval(argv[2]);
1453 if (!duration) {
1454 reply("MSG_INVALID_DURATION", argv[2]);
1455 return 0;
1456 }
1457 shun = shun_add(user->handle_info->handle, argv[1], duration, reason, now, 1);
1458 reply("OSMSG_SHUN_ISSUED", shun->target);
1459 return 1;
1460}
1461
1462static MODCMD_FUNC(cmd_unshun)
1463{
1464 if (shun_remove(argv[1], 1))
1465 reply("OSMSG_SHUN_REMOVED", argv[1]);
1466 else
1467 reply("OSMSG_SHUN_FORCE_REMOVED", argv[1]);
1468 return 1;
1469}
1470
1471static MODCMD_FUNC(cmd_refreshs)
1472{
1473 if (argc > 1) {
1474 unsigned int count;
1475 dict_iterator_t it;
1476 struct server *srv;
1477
1478 for (it=dict_first(servers), count=0; it; it=iter_next(it)) {
1479 srv = iter_data(it);
1480 if ((srv == self) || !match_ircglob(srv->name, argv[1]))
1481 continue;
1482 shun_refresh_server(srv);
1483 reply("OSMSG_SHUNS_ONE_REFRESHED", srv->name);
1484 count++;
1485 }
1486 if (!count) {
1487 reply("MSG_SERVER_UNKNOWN", argv[1]);
1488 return 0;
1489 }
1490 } else {
1491 shun_refresh_all();
1492 reply("OSMSG_SHUNS_REFRESHED");
1493 }
1494 return 1;
1495}
1496
d76ed9a9 1497static void
4b44eb0f 1498opserv_ison(struct userNode *bot, struct userNode *tell, struct userNode *target, const char *message)
d76ed9a9
AS
1499{
1500 struct modeNode *mn;
1501 unsigned int count, here_len, n, maxlen;
1502 char buff[MAXLEN];
1503
1504 maxlen = tell->handle_info ? tell->handle_info->screen_width : 0;
1505 if (!maxlen)
1506 maxlen = MAX_LINE_SIZE;
1507 for (n=count=0; n<target->channels.used; n++) {
258d1427
AS
1508 mn = target->channels.list[n];
1509 here_len = strlen(mn->channel->name);
1510 if ((count + here_len + 4) > maxlen) {
1511 buff[count] = 0;
4b44eb0f 1512 send_message(tell, bot, message, buff);
258d1427
AS
1513 count = 0;
1514 }
1515 if (mn->modes & MODE_CHANOP)
d76ed9a9 1516 buff[count++] = '@';
55342ce8 1517 if (mn->modes & MODE_HALFOP)
1518 buff[count++] = '%';
258d1427 1519 if (mn->modes & MODE_VOICE)
d76ed9a9 1520 buff[count++] = '+';
258d1427
AS
1521 memcpy(buff+count, mn->channel->name, here_len);
1522 count += here_len;
1523 buff[count++] = ' ';
d76ed9a9
AS
1524 }
1525 if (count) {
258d1427 1526 buff[count] = 0;
4b44eb0f 1527 send_message(tell, bot, message, buff);
d76ed9a9
AS
1528 }
1529}
1530
1531static MODCMD_FUNC(cmd_inviteme)
1532{
1533 struct userNode *target;
1534
1535 if (argc < 2) {
258d1427 1536 target = user;
d76ed9a9 1537 } else {
258d1427
AS
1538 target = GetUserH(argv[1]);
1539 if (!target) {
1540 reply("MSG_NICK_UNKNOWN", argv[1]);
1541 return 0;
1542 }
d76ed9a9
AS
1543 }
1544 if (opserv_conf.debug_channel == NULL) {
258d1427
AS
1545 reply("OSMSG_NO_DEBUG_CHANNEL");
1546 return 0;
d76ed9a9
AS
1547 }
1548 if (GetUserMode(opserv_conf.debug_channel, user)) {
a32da4c7 1549 reply("OSMSG_ALREADY_THERE", opserv_conf.debug_channel->name);
d76ed9a9
AS
1550 return 0;
1551 }
1552 irc_invite(cmd->parent->bot, target, opserv_conf.debug_channel);
1553 if (target != user)
258d1427 1554 reply("OSMSG_INVITE_DONE", target->nick, opserv_conf.debug_channel->name);
d76ed9a9
AS
1555 return 1;
1556}
1557
1558static MODCMD_FUNC(cmd_invite)
1559{
1560 if (GetUserMode(channel, user)) {
1561 reply("OSMSG_ALREADY_THERE", channel->name);
1562 return 0;
1563 }
1564 irc_invite(cmd->parent->bot, user, channel);
1565 return 1;
1566}
1567
c408f18a
AS
1568static MODCMD_FUNC(cmd_svsjoin)
1569{
1570 struct userNode *target;
1571
1572
1573 if(!IsChannelName(argv[2])) {
1574 reply("MSG_NOT_CHANNEL_NAME");
1575 return 0;
1576 }
1577 target = GetUserH(argv[1]);
1578 if (!target) {
1579 reply("MSG_NICK_UNKNOWN", argv[1]);
1580 return 0;
1581 }
1582
1583 if (!(channel = GetChannel(argv[2]))) {
1584 channel = AddChannel(argv[2], now, NULL, NULL, NULL);
1585 }
1586 if (GetUserMode(channel, target)) {
1587 reply("OSMSG_ALREADY_THERE", channel->name);
1588 return 0;
1589 }
1590 irc_svsjoin(opserv, target, channel);
1591 reply("OSMSG_SVSJOIN_SENT");
1592 return 1;
1593}
1594
d76ed9a9
AS
1595static MODCMD_FUNC(cmd_join)
1596{
1597 struct userNode *bot = cmd->parent->bot;
1598
1599 if (!IsChannelName(argv[1])) {
1600 reply("MSG_NOT_CHANNEL_NAME");
1601 return 0;
1602 } else if (!(channel = GetChannel(argv[1]))) {
2aef5f4b 1603 channel = AddChannel(argv[1], now, NULL, NULL, NULL);
d76ed9a9
AS
1604 AddChannelUser(bot, channel)->modes |= MODE_CHANOP;
1605 } else if (GetUserMode(channel, bot)) {
1606 reply("OSMSG_ALREADY_JOINED", channel->name);
1607 return 0;
1608 } else {
1609 struct mod_chanmode change;
1610 mod_chanmode_init(&change);
1611 change.argc = 1;
1612 change.args[0].mode = MODE_CHANOP;
a32da4c7 1613 change.args[0].u.member = AddChannelUser(bot, channel);
d76ed9a9
AS
1614 modcmd_chanmode_announce(&change);
1615 }
1616 irc_fetchtopic(bot, channel->name);
1617 reply("OSMSG_JOIN_DONE", channel->name);
1618 return 1;
1619}
1620
1621static MODCMD_FUNC(cmd_kick)
1622{
1623 struct userNode *target;
1624 char *reason;
1625
1626 if (argc < 3) {
258d1427
AS
1627 reason = alloca(strlen(OSMSG_KICK_REQUESTED)+strlen(user->nick)+1);
1628 sprintf(reason, OSMSG_KICK_REQUESTED, user->nick);
d76ed9a9 1629 } else {
258d1427 1630 reason = unsplit_string(argv+2, argc-2, NULL);
d76ed9a9
AS
1631 }
1632 target = GetUserH(argv[1]);
1633 if (!target) {
258d1427
AS
1634 reply("MSG_NICK_UNKNOWN", argv[1]);
1635 return 0;
d76ed9a9
AS
1636 }
1637 if (!GetUserMode(channel, target)) {
258d1427
AS
1638 reply("OSMSG_NOT_ON_CHANNEL", target->nick, channel->name);
1639 return 0;
d76ed9a9
AS
1640 }
1641 KickChannelUser(target, channel, cmd->parent->bot, reason);
1642 return 1;
1643}
1644
1645static MODCMD_FUNC(cmd_kickall)
1646{
1647 unsigned int limit, n, inchan;
1648 struct modeNode *mn;
1649 char *reason;
1650 struct userNode *bot = cmd->parent->bot;
1651
1652 /* ircu doesn't let servers KICK users, so if OpServ's not in the
1653 * channel, we have to join it in temporarily. */
1654 if (!(inchan = GetUserMode(channel, bot) ? 1 : 0)) {
1655 struct mod_chanmode change;
1656 mod_chanmode_init(&change);
1657 change.args[0].mode = MODE_CHANOP;
a32da4c7 1658 change.args[0].u.member = AddChannelUser(bot, channel);
d76ed9a9
AS
1659 modcmd_chanmode_announce(&change);
1660 }
1661 if (argc < 2) {
258d1427
AS
1662 reason = alloca(strlen(OSMSG_KICK_REQUESTED)+strlen(user->nick)+1);
1663 sprintf(reason, OSMSG_KICK_REQUESTED, user->nick);
d76ed9a9 1664 } else {
258d1427 1665 reason = unsplit_string(argv+1, argc-1, NULL);
d76ed9a9
AS
1666 }
1667 limit = user->handle_info->opserv_level;
1668 for (n=channel->members.used; n>0;) {
258d1427
AS
1669 mn = channel->members.list[--n];
1670 if (IsService(mn->user)
1671 || (mn->user->handle_info
1672 && (mn->user->handle_info->opserv_level >= limit))) {
1673 continue;
1674 }
1675 KickChannelUser(mn->user, channel, bot, reason);
d76ed9a9
AS
1676 }
1677 if (!inchan)
1678 DelChannelUser(bot, channel, "My work here is done", 0);
1679 reply("OSMSG_KICKALL_DONE", channel->name);
1680 return 1;
1681}
1682
1683static MODCMD_FUNC(cmd_kickban)
1684{
1685 struct mod_chanmode change;
1686 struct userNode *target;
1687 char *reason;
1688 char *mask;
1689
1690 if (argc == 2) {
258d1427
AS
1691 reason = alloca(strlen(OSMSG_KICK_REQUESTED)+strlen(user->nick)+1);
1692 sprintf(reason, OSMSG_KICK_REQUESTED, user->nick);
d76ed9a9 1693 } else {
258d1427 1694 reason = unsplit_string(argv+2, argc-2, NULL);
d76ed9a9
AS
1695 }
1696 target = GetUserH(argv[1]);
1697 if (!target) {
258d1427
AS
1698 reply("MSG_NICK_UNKNOWN", argv[1]);
1699 return 0;
d76ed9a9
AS
1700 }
1701 if (!GetUserMode(channel, target)) {
258d1427
AS
1702 reply("OSMSG_NOT_ON_CHANNEL", target->nick, channel->name);
1703 return 0;
d76ed9a9
AS
1704 }
1705 mod_chanmode_init(&change);
1706 change.argc = 1;
1707 change.args[0].mode = MODE_BAN;
a32da4c7 1708 change.args[0].u.hostmask = mask = generate_hostmask(target, 0);
d76ed9a9
AS
1709 modcmd_chanmode_announce(&change);
1710 KickChannelUser(target, channel, cmd->parent->bot, reason);
1711 free(mask);
1712 return 1;
1713}
1714
1715static MODCMD_FUNC(cmd_kickbanall)
1716{
1717 struct modeNode *mn;
1718 struct userNode *bot = cmd->parent->bot;
1719 struct mod_chanmode *change;
1720 char *reason;
1721 unsigned int limit, n, inchan;
1722
1723 /* ircu doesn't let servers KICK users, so if OpServ's not in the
1724 * channel, we have to join it in temporarily. */
1725 if (!(inchan = GetUserMode(channel, bot) ? 1 : 0)) {
1726 change = mod_chanmode_alloc(2);
1727 change->args[0].mode = MODE_CHANOP;
a32da4c7 1728 change->args[0].u.member = AddChannelUser(bot, channel);
d76ed9a9 1729 change->args[1].mode = MODE_BAN;
a32da4c7 1730 change->args[1].u.hostmask = "*!*@*";
d76ed9a9
AS
1731 } else {
1732 change = mod_chanmode_alloc(1);
1733 change->args[0].mode = MODE_BAN;
a32da4c7 1734 change->args[0].u.hostmask = "*!*@*";
d76ed9a9
AS
1735 }
1736 modcmd_chanmode_announce(change);
a32da4c7 1737 mod_chanmode_free(change);
d76ed9a9 1738 if (argc < 2) {
258d1427
AS
1739 reason = alloca(strlen(OSMSG_KICK_REQUESTED)+strlen(user->nick)+1);
1740 sprintf(reason, OSMSG_KICK_REQUESTED, user->nick);
d76ed9a9 1741 } else {
258d1427 1742 reason = unsplit_string(argv+1, argc-1, NULL);
d76ed9a9
AS
1743 }
1744 /* now kick them */
1745 limit = user->handle_info->opserv_level;
1746 for (n=channel->members.used; n>0; ) {
258d1427
AS
1747 mn = channel->members.list[--n];
1748 if (IsService(mn->user)
1749 || (mn->user->handle_info
1750 && (mn->user->handle_info->opserv_level >= limit))) {
1751 continue;
1752 }
1753 KickChannelUser(mn->user, channel, bot, reason);
d76ed9a9
AS
1754 }
1755 if (!inchan)
1756 DelChannelUser(bot, channel, "My work here is done", 0);
1757 reply("OSMSG_KICKALL_DONE", channel->name);
1758 return 1;
1759}
1760
39c1a4ef 1761static MODCMD_FUNC(cmd_svspart)
1762{
1763 struct userNode *target;
1764
1765 if(!IsChannelName(argv[2])) {
1766 reply("MSG_NOT_CHANNEL_NAME");
1767 return 0;
1768 }
1769 target = GetUserH(argv[1]);
1770 if (!target) {
1771 reply("MSG_NICK_UNKNOWN", argv[1]);
1772 return 0;
1773 }
1774
1775 if (!(channel = GetChannel(argv[2]))) {
1776 reply("OSMSG_NOT_ON_CHANNEL", cmd->parent->bot->nick, channel->name);
1777 return 0;
1778 }
1779
1780 if (!GetUserMode(channel, target)) {
1781 reply("OSMSG_NOT_ON_CHANNEL", cmd->parent->bot->nick, channel->name);
1782 return 0;
1783 }
1784
1785 irc_svspart(opserv, target, channel);
1786 reply("OSMSG_SVSPART_SENT");
1787 return 1;
1788}
1789
d76ed9a9
AS
1790static MODCMD_FUNC(cmd_part)
1791{
1792 char *reason;
1793
1794 if (!IsChannelName(argv[1])) {
1795 reply("MSG_NOT_CHANNEL_NAME");
1796 return 0;
1797 }
1798 if ((channel = GetChannel(argv[1]))) {
1799 if (!GetUserMode(channel, cmd->parent->bot)) {
1800 reply("OSMSG_NOT_ON_CHANNEL", cmd->parent->bot->nick, channel->name);
1801 return 0;
1802 }
1803 reason = (argc < 3) ? "Leaving." : unsplit_string(argv+2, argc-2, NULL);
1804 reply("OSMSG_LEAVING", channel->name);
1805 DelChannelUser(cmd->parent->bot, channel, reason, 0);
1806 }
1807 return 1;
1808}
1809
1810static MODCMD_FUNC(cmd_mode)
1811{
1812 if (!modcmd_chanmode(argv+1, argc-1, MCP_ALLOW_OVB|MCP_KEY_FREE|MC_ANNOUNCE)) {
1813 reply("MSG_INVALID_MODES", unsplit_string(argv+1, argc-1, NULL));
1814 return 0;
1815 }
1816 reply("OSMSG_MODE_SET", channel->name);
1817 return 1;
1818}
1819
1820static MODCMD_FUNC(cmd_op)
1821{
1822 struct mod_chanmode *change;
1823 unsigned int arg, count;
1824
1825 change = mod_chanmode_alloc(argc-1);
1826 for (arg = 1, count = 0; arg < argc; ++arg) {
1827 struct userNode *victim;
1828 struct modeNode *mn;
1829 if (!(victim = GetUserH(argv[arg])))
1830 continue;
1831 if (!(mn = GetUserMode(channel, victim)))
1832 continue;
1833 if (mn->modes & MODE_CHANOP)
1834 continue;
1835 change->args[count].mode = MODE_CHANOP;
a32da4c7 1836 change->args[count++].u.member = mn;
d76ed9a9
AS
1837 }
1838 if (count) {
1839 change->argc = count;
1840 modcmd_chanmode_announce(change);
1841 }
1842 mod_chanmode_free(change);
1843 reply("OSMSG_OP_DONE");
1844 return 1;
1845}
1846
55342ce8 1847static MODCMD_FUNC(cmd_hop)
1848{
1849 struct mod_chanmode *change;
1850 unsigned int arg, count;
1851
1852 change = mod_chanmode_alloc(argc-1);
1853 for (arg = 1, count = 0; arg < argc; ++arg) {
1854 struct userNode *victim;
1855 struct modeNode *mn;
1856 if (!(victim = GetUserH(argv[arg])))
1857 continue;
1858 if (!(mn = GetUserMode(channel, victim)))
1859 continue;
1860 if (mn->modes & MODE_HALFOP)
1861 continue;
1862 change->args[count].mode = MODE_HALFOP;
11408ce4 1863 change->args[count++].u.member = mn;
55342ce8 1864 }
1865 if (count) {
1866 change->argc = count;
1867 modcmd_chanmode_announce(change);
1868 }
1869 mod_chanmode_free(change);
1870 reply("OSMSG_HOP_DONE");
1871 return 1;
1872}
1873
d76ed9a9
AS
1874static MODCMD_FUNC(cmd_opall)
1875{
1876 struct mod_chanmode *change;
1877 unsigned int ii, count;
1878
1879 change = mod_chanmode_alloc(channel->members.used);
1880 for (ii = count = 0; ii < channel->members.used; ++ii) {
258d1427
AS
1881 struct modeNode *mn = channel->members.list[ii];
1882 if (mn->modes & MODE_CHANOP)
d76ed9a9
AS
1883 continue;
1884 change->args[count].mode = MODE_CHANOP;
a32da4c7 1885 change->args[count++].u.member = mn;
d76ed9a9
AS
1886 }
1887 if (count) {
1888 change->argc = count;
258d1427 1889 modcmd_chanmode_announce(change);
d76ed9a9
AS
1890 }
1891 mod_chanmode_free(change);
1892 reply("OSMSG_OPALL_DONE", channel->name);
1893 return 1;
1894}
1895
55342ce8 1896static MODCMD_FUNC(cmd_hopall)
1897{
1898 struct mod_chanmode *change;
1899 unsigned int ii, count;
1900
1901 change = mod_chanmode_alloc(channel->members.used);
1902 for (ii = count = 0; ii < channel->members.used; ++ii) {
1903 struct modeNode *mn = channel->members.list[ii];
1904 if (mn->modes & MODE_HALFOP)
1905 continue;
1906 change->args[count].mode = MODE_HALFOP;
11408ce4 1907 change->args[count++].u.member = mn;
55342ce8 1908 }
1909 if (count) {
1910 change->argc = count;
1911 modcmd_chanmode_announce(change);
1912 }
1913 mod_chanmode_free(change);
1914 reply("OSMSG_HOPALL_DONE", channel->name);
1915 return 1;
1916}
1917
d76ed9a9
AS
1918static MODCMD_FUNC(cmd_whois)
1919{
1920 struct userNode *target;
1921 char buffer[128];
1922 int bpos, herelen;
1923
1924#ifdef WITH_PROTOCOL_P10
1925 if (argv[1][0] == '*')
1926 target = GetUserN(argv[1]+1);
1927 else
d76ed9a9 1928#endif
1117fc5a 1929 target = GetUserH(argv[1]);
d76ed9a9
AS
1930 if (!target) {
1931 reply("MSG_NICK_UNKNOWN", argv[1]);
1932 return 0;
1933 }
1934 reply("OSMSG_WHOIS_NICK", target->nick);
1935 reply("OSMSG_WHOIS_HOST", target->ident, target->hostname);
1936 if (IsFakeHost(target))
1937 reply("OSMSG_WHOIS_FAKEHOST", target->fakehost);
37ef8ee3 1938 reply("OSMSG_WHOIS_CRYPT_HOST", target->crypthost);
1939 reply("OSMSG_WHOIS_CRYPT_IP", target->cryptip);
2f61d1d7 1940 reply("OSMSG_WHOIS_IP", irc_ntoa(&target->ip));
21f6caee 1941
1942 if (target->city) {
1943 reply("OSMSG_WHOIS_COUNTRY", target->country_name);
1944 reply("OSMSG_WHOIS_COUNTRY_CODE", target->country_code);
1945 reply("OSMSG_WHOIS_CITY", target->city);
1946 reply("OSMSG_WHOIS_REGION", target->region);
1947
1948 reply("OSMSG_WHOIS_POSTAL_CODE", target->postal_code);
1949 reply("OSMSG_WHOIS_LATITUDE", target->latitude);
1950 reply("OSMSG_WHOIS_LONGITUDE", target->longitude);
1ad7ac15 1951 /* Only show a map url if we have a city, latitude and longitude.
1952 * Theres not much point of latitude and longitude coordinates are
1953 * returned but no city, the coordinates are useless.
1954 */
1955 if (target->latitude && target->longitude && target->city) {
1956 char map_url[MAXLEN];
1957 snprintf(map_url, sizeof(map_url), "http://www.mapquest.com/maps/map.adp?searchtype=address&formtype=address&latlongtype=decimal&latitude=%f&longitude=%f",
1958 target->latitude, target->longitude);
1959 reply("OSMSG_WHOIS_MAP", map_url);
1960 }
21f6caee 1961 reply("OSMSG_WHOIS_DMA_CODE", target->dma_code);
1962 reply("OSMSG_WHOIS_AREA_CODE", target->area_code);
1963 } else if (target->country_name) {
1964 reply("OSMSG_WHOIS_COUNTRY", target->country_name);
1965 }
0e08a8e0
AS
1966 if(target->version_reply) {
1967 reply("OSMSG_WHOIS_VERSION", target->version_reply);
1968 }
21f6caee 1969
d76ed9a9 1970 if (target->modes) {
258d1427 1971 bpos = 0;
d76ed9a9 1972#define buffer_cat(str) (herelen = strlen(str), memcpy(buffer+bpos, str, herelen), bpos += herelen)
258d1427
AS
1973 if (IsInvisible(target)) buffer[bpos++] = 'i';
1974 if (IsWallOp(target)) buffer[bpos++] = 'w';
1975 if (IsOper(target)) buffer[bpos++] = 'o';
1976 if (IsGlobal(target)) buffer[bpos++] = 'g';
1977 if (IsServNotice(target)) buffer[bpos++] = 's';
1978
1979 // sethost - reed/apples
1980 // if (IsHelperIrcu(target)) buffer[bpos++] = 'h';
1981 if (IsSetHost(target)) buffer[bpos++] = 'h';
1982
1983 if (IsService(target)) buffer[bpos++] = 'k';
1984 if (IsDeaf(target)) buffer[bpos++] = 'd';
b2cf3d66 1985 if (target->handle_info) buffer[bpos++] = 'r';
d76ed9a9
AS
1986 if (IsHiddenHost(target)) buffer[bpos++] = 'x';
1987 if (IsGagged(target)) buffer_cat(" (gagged)");
258d1427
AS
1988 if (IsRegistering(target)) buffer_cat(" (registered account)");
1989 buffer[bpos] = 0;
1990 if (bpos > 0)
d76ed9a9
AS
1991 reply("OSMSG_WHOIS_MODES", buffer);
1992 }
1993 reply("OSMSG_WHOIS_INFO", target->info);
1994#ifdef WITH_PROTOCOL_P10
1995 reply("OSMSG_WHOIS_NUMERIC", target->numeric);
1996#endif
1997 reply("OSMSG_WHOIS_SERVER", target->uplink->name);
1998 reply("OSMSG_WHOIS_ACCOUNT", (target->handle_info ? target->handle_info->handle : "Not authenticated"));
89d871d8 1999
2000 reply("OSMSG_WHOIS_PRIVS", client_report_privs(target));
2001
d76ed9a9
AS
2002 intervalString(buffer, now - target->timestamp, user->handle_info);
2003 reply("OSMSG_WHOIS_NICK_AGE", buffer);
2004 if (target->channels.used <= MAX_CHANNELS_WHOIS)
4b44eb0f 2005 opserv_ison(cmd->parent->bot, user, target, "OSMSG_WHOIS_CHANNELS");
d76ed9a9 2006 else
258d1427 2007 reply("OSMSG_WHOIS_HIDECHANS");
d76ed9a9
AS
2008 return 1;
2009}
2010
2011static MODCMD_FUNC(cmd_unban)
2012{
2013 struct mod_chanmode change;
2014 mod_chanmode_init(&change);
2015 change.argc = 1;
2016 change.args[0].mode = MODE_REMOVE | MODE_BAN;
a32da4c7 2017 change.args[0].u.hostmask = argv[1];
d76ed9a9
AS
2018 modcmd_chanmode_announce(&change);
2019 reply("OSMSG_UNBAN_DONE", channel->name);
2020 return 1;
2021}
2022
2023static MODCMD_FUNC(cmd_voiceall)
2024{
2025 struct mod_chanmode *change;
2026 unsigned int ii, count;
2027
2028 change = mod_chanmode_alloc(channel->members.used);
2029 for (ii = count = 0; ii < channel->members.used; ++ii) {
258d1427
AS
2030 struct modeNode *mn = channel->members.list[ii];
2031 if (mn->modes & (MODE_CHANOP|MODE_HALFOP|MODE_VOICE))
d76ed9a9
AS
2032 continue;
2033 change->args[count].mode = MODE_VOICE;
a32da4c7 2034 change->args[count++].u.member = mn;
d76ed9a9
AS
2035 }
2036 if (count) {
2037 change->argc = count;
258d1427 2038 modcmd_chanmode_announce(change);
d76ed9a9
AS
2039 }
2040 mod_chanmode_free(change);
2041 reply("OSMSG_CHANNEL_VOICED", channel->name);
2042 return 1;
2043}
2044
2045static MODCMD_FUNC(cmd_devoiceall)
2046{
2047 struct mod_chanmode *change;
2048 unsigned int ii, count;
2049
2050 change = mod_chanmode_alloc(channel->members.used);
2051 for (ii = count = 0; ii < channel->members.used; ++ii) {
258d1427
AS
2052 struct modeNode *mn = channel->members.list[ii];
2053 if (!(mn->modes & MODE_VOICE))
d76ed9a9
AS
2054 continue;
2055 change->args[count].mode = MODE_REMOVE | MODE_VOICE;
a32da4c7 2056 change->args[count++].u.member = mn;
d76ed9a9
AS
2057 }
2058 if (count) {
2059 change->argc = count;
258d1427 2060 modcmd_chanmode_announce(change);
d76ed9a9
AS
2061 }
2062 mod_chanmode_free(change);
2063 reply("OSMSG_CHANNEL_DEVOICED", channel->name);
2064 return 1;
2065}
2066
2067static MODCMD_FUNC(cmd_stats_bad) {
2068 dict_iterator_t it;
2069 unsigned int ii, end, here_len;
2070 char buffer[400];
2071
2072 /* Show the bad word list.. */
de9510bc 2073 /* TODO: convert nonprinting chars like bold to $b etc in a usable way */
d76ed9a9
AS
2074 for (ii=end=0; ii<opserv_bad_words->used; ii++) {
2075 here_len = strlen(opserv_bad_words->list[ii]);
de9510bc 2076 /* If the line is full output it & start again */
d76ed9a9
AS
2077 if ((end + here_len + 2) > sizeof(buffer)) {
2078 buffer[end] = 0;
2079 reply("OSMSG_BADWORD_LIST", buffer);
2080 end = 0;
2081 }
2082 memcpy(buffer+end, opserv_bad_words->list[ii], here_len);
2083 end += here_len;
2084 buffer[end++] = ' ';
2085 }
2086 buffer[end] = 0;
2087 reply("OSMSG_BADWORD_LIST", buffer);
2088
2089 /* Show the exemption list.. */
2090 for (it=dict_first(opserv_exempt_channels), end=0; it; it=iter_next(it)) {
2091 here_len = strlen(iter_key(it));
2092 if ((end + here_len + 2) > sizeof(buffer)) {
2093 buffer[end] = 0;
2094 reply("OSMSG_EXEMPTED_LIST", buffer);
2095 end = 0;
2096 }
2097 memcpy(buffer+end, iter_key(it), here_len);
2098 end += here_len;
2099 buffer[end++] = ' ';
2100 }
2101 buffer[end] = 0;
2102 reply("OSMSG_EXEMPTED_LIST", buffer);
2103 return 1;
2104}
2105
2106static MODCMD_FUNC(cmd_stats_glines) {
2107 reply("OSMSG_GLINE_COUNT", gline_count());
2108 return 1;
2109}
2110
d914d1cb 2111static MODCMD_FUNC(cmd_stats_shuns) {
2112 reply("OSMSG_SHUN_COUNT", shun_count());
2113 return 1;
2114}
2115
d76ed9a9
AS
2116static void
2117trace_links(struct userNode *bot, struct userNode *user, struct server *server, unsigned int depth) {
2118 unsigned int nn, pos;
2119 char buffer[400];
2120
2121 for (nn=1; nn<=depth; nn<<=1) ;
2122 for (pos=0, nn>>=1; nn>1; ) {
2123 nn >>= 1;
2124 buffer[pos++] = (depth & nn) ? ((nn == 1) ? '`' : ' ') : '|';
2125 buffer[pos++] = (nn == 1) ? '-': ' ';
2126 }
2127 buffer[pos] = 0;
2128 send_message(user, bot, "OSMSG_LINKS_SERVER", buffer, server->name, server->clients, server->description);
2129 if (!server->children.used)
2130 return;
2131 for (nn=0; nn<server->children.used-1; nn++) {
2132 trace_links(bot, user, server->children.list[nn], depth<<1);
2133 }
2134 trace_links(bot, user, server->children.list[nn], (depth<<1)|1);
2135}
2136
2137static MODCMD_FUNC(cmd_stats_links) {
2138 trace_links(cmd->parent->bot, user, self, 1);
2139 return 1;
2140}
2141
2142
2143static MODCMD_FUNC(cmd_stats_max) {
2144 reply("OSMSG_MAX_CLIENTS", max_clients, asctime(localtime(&max_clients_time)));
2145 return 1;
2146}
2147
2148static MODCMD_FUNC(cmd_stats_network) {
2149 struct helpfile_table tbl;
2150 unsigned int nn, tot_clients;
2151 dict_iterator_t it;
2152
2153 tot_clients = dict_size(clients);
2154 reply("OSMSG_NETWORK_INFO", tot_clients, invis_clients, curr_opers.used);
2155 tbl.length = dict_size(servers)+1;
2156 tbl.width = 3;
2157 tbl.flags = TABLE_NO_FREE;
2158 tbl.contents = calloc(tbl.length, sizeof(*tbl.contents));
2159 tbl.contents[0] = calloc(tbl.width, sizeof(**tbl.contents));
2160 tbl.contents[0][0] = "Server Name";
2161 tbl.contents[0][1] = "Clients";
2162 tbl.contents[0][2] = "Load";
2163 for (it=dict_first(servers), nn=1; it; it=iter_next(it)) {
2164 struct server *server = iter_data(it);
2165 char *buffer = malloc(32);
2166 tbl.contents[nn] = calloc(tbl.width, sizeof(**tbl.contents));
2167 tbl.contents[nn][0] = server->name;
2168 tbl.contents[nn][1] = buffer;
2169 sprintf(buffer, "%u", server->clients);
2170 tbl.contents[nn][2] = buffer + 16;
2171 sprintf(buffer+16, "%3.3g%%", ((double)server->clients/tot_clients)*100);
2172 nn++;
2173 }
2174 table_send(cmd->parent->bot, user->nick, 0, 0, tbl);
2175 for (nn=1; nn<tbl.length; nn++) {
2176 free((char*)tbl.contents[nn][1]);
2177 free(tbl.contents[nn]);
2178 }
2179 free(tbl.contents[0]);
2180 free(tbl.contents);
2181 return 1;
2182}
2183
2184static MODCMD_FUNC(cmd_stats_network2) {
2185 struct helpfile_table tbl;
2186 unsigned int nn;
2187 dict_iterator_t it;
2188
2189 tbl.length = dict_size(servers)+1;
2190 tbl.width = 3;
2191 tbl.flags = TABLE_NO_FREE;
2192 tbl.contents = calloc(tbl.length, sizeof(*tbl.contents));
2193 tbl.contents[0] = calloc(tbl.width, sizeof(**tbl.contents));
2194 tbl.contents[0][0] = "Server Name";
2195 tbl.contents[0][1] = "Numeric";
2196 tbl.contents[0][2] = "Link Time";
2197 for (it=dict_first(servers), nn=1; it; it=iter_next(it)) {
2198 struct server *server = iter_data(it);
2199 char *buffer = malloc(64);
2200 int ofs;
2201
2202 tbl.contents[nn] = calloc(tbl.width, sizeof(**tbl.contents));
2203 tbl.contents[nn][0] = server->name;
2204#ifdef WITH_PROTOCOL_P10
2205 sprintf(buffer, "%s (%ld)", server->numeric, base64toint(server->numeric, strlen(server->numeric)));
2206#else
2207 buffer[0] = 0;
2208#endif
2209 tbl.contents[nn][1] = buffer;
2210 ofs = strlen(buffer) + 1;
2211 intervalString(buffer + ofs, now - server->link, user->handle_info);
2212 if (server->self_burst)
2213 strcat(buffer + ofs, " Bursting");
2214 tbl.contents[nn][2] = buffer + ofs;
2215 nn++;
2216 }
2217 table_send(cmd->parent->bot, user->nick, 0, 0, tbl);
2218 for (nn=1; nn<tbl.length; nn++) {
2219 free((char*)tbl.contents[nn][1]);
2220 free(tbl.contents[nn]);
2221 }
2222 free(tbl.contents[0]);
2223 free(tbl.contents);
2224 return 1;
2225}
2226
2227static MODCMD_FUNC(cmd_stats_reserved) {
2228 dict_iterator_t it;
2229
2230 reply("OSMSG_RESERVED_LIST");
2231 for (it = dict_first(opserv_reserved_nick_dict); it; it = iter_next(it))
2232 send_message_type(4, user, cmd->parent->bot, "%s", iter_key(it));
2233 return 1;
2234}
2235
2236static MODCMD_FUNC(cmd_stats_trusted) {
2237 dict_iterator_t it;
2238 struct trusted_host *th;
2239 char length[INTERVALLEN], issued[INTERVALLEN], limit[32];
2240
de9510bc
AS
2241 reply("OSMSG_TRUSTED_LIST");
2242 reply("OSMSG_TRUSTED_LIST_BAR");
2243 reply("OSMSG_TRUSTED_LIST_HEADER");
2244 reply("OSMSG_TRUSTED_LIST_BAR");
d76ed9a9
AS
2245 if (argc > 1) {
2246 th = dict_find(opserv_trusted_hosts, argv[1], NULL);
2247 if (th) {
2248 if (th->issued)
2249 intervalString(issued, now - th->issued, user->handle_info);
2250 if (th->expires)
2251 intervalString(length, th->expires - now, user->handle_info);
2252 if (th->limit)
de9510bc 2253 sprintf(limit, "%lu", th->limit);
d76ed9a9
AS
2254 reply("OSMSG_HOST_IS_TRUSTED",
2255 th->ipaddr,
de9510bc 2256 (th->limit ? limit : "none"),
d76ed9a9 2257 (th->issuer ? th->issuer : "<unknown>"),
de9510bc
AS
2258 (th->issued ? issued : "some time"),
2259 (th->expires ? length : "never"));
2260 reply("OSMSG_HOST_IS_TRUSTED_DESC", (th->reason ? th->reason : "<unknown>"));
d76ed9a9
AS
2261 } else {
2262 reply("OSMSG_HOST_NOT_TRUSTED", argv[1]);
2263 }
2264 } else {
d76ed9a9
AS
2265 for (it = dict_first(opserv_trusted_hosts); it; it = iter_next(it)) {
2266 th = iter_data(it);
2267 if (th->issued)
2268 intervalString(issued, now - th->issued, user->handle_info);
2269 if (th->expires)
2270 intervalString(length, th->expires - now, user->handle_info);
2271 if (th->limit)
de9510bc 2272 sprintf(limit, "%lu", th->limit);
d76ed9a9 2273 reply("OSMSG_HOST_IS_TRUSTED", iter_key(it),
de9510bc 2274 (th->limit ? limit : "none"),
d76ed9a9 2275 (th->issuer ? th->issuer : "<unknown>"),
de9510bc
AS
2276 (th->issued ? issued : "some time"),
2277 (th->expires ? length : "never"));
2278 reply("OSMSG_HOST_IS_TRUSTED_DESC", (th->reason ? th->reason : "<unknown>"));
d76ed9a9
AS
2279 }
2280 }
de9510bc 2281 reply("OSMSG_TRUSTED_LIST_END");
d76ed9a9
AS
2282 return 1;
2283}
2284
2285static MODCMD_FUNC(cmd_stats_uplink) {
2286 extern struct cManagerNode cManager;
2287 struct uplinkNode *uplink;
2288
2289 uplink = cManager.uplink;
2290 reply("OSMSG_UPLINK_START", uplink->name);
2291 reply("OSMSG_UPLINK_ADDRESS", uplink->host, uplink->port);
2292 return 1;
2293}
2294
2295static MODCMD_FUNC(cmd_stats_uptime) {
2296 char uptime[INTERVALLEN];
2297 struct tms buf;
2298 extern time_t boot_time;
2299 extern int lines_processed;
2300 static long clocks_per_sec;
2301
2302 if (!clocks_per_sec) {
2303#if defined(HAVE_SYSCONF) && defined(_SC_CLK_TCK)
2304 clocks_per_sec = sysconf(_SC_CLK_TCK);
2305 if (clocks_per_sec <= 0)
2306#endif
2307 {
2308 log_module(OS_LOG, LOG_ERROR, "Unable to query sysconf(_SC_CLK_TCK), output of 'stats uptime' will be wrong");
2309 clocks_per_sec = CLOCKS_PER_SEC;
2310 }
2311 }
2312 intervalString(uptime, time(NULL)-boot_time, user->handle_info);
2313 times(&buf);
2314 reply("OSMSG_UPTIME_STATS",
2315 uptime, lines_processed,
2316 buf.tms_utime/(double)clocks_per_sec,
2317 buf.tms_stime/(double)clocks_per_sec);
2318 return 1;
2319}
2320
2321static MODCMD_FUNC(cmd_stats_alerts) {
2322 dict_iterator_t it;
2323 struct opserv_user_alert *alert;
2324 const char *reaction;
2325
2326 reply("OSMSG_ALERTS_LIST");
de9510bc
AS
2327 reply("OSMSG_ALERTS_BAR");
2328 reply("OSMSG_ALERTS_HEADER");
2329 reply("OSMSG_ALERTS_BAR");
d76ed9a9
AS
2330 for (it = dict_first(opserv_user_alerts); it; it = iter_next(it)) {
2331 alert = iter_data(it);
2332 switch (alert->reaction) {
2333 case REACT_NOTICE: reaction = "notice"; break;
2334 case REACT_KILL: reaction = "kill"; break;
1c5f6697 2335// case REACT_SILENT: reaction = "silent"; break;
d76ed9a9 2336 case REACT_GLINE: reaction = "gline"; break;
ec311f39 2337 case REACT_TRACK: reaction = "track"; break;
d914d1cb 2338 case REACT_SHUN: reaction = "shun"; break;
c408f18a 2339 case REACT_SVSJOIN: reaction = "svsjoin"; break;
39c1a4ef 2340 case REACT_SVSPART: reaction = "svspart"; break;
0e08a8e0 2341 case REACT_VERSION: reaction = "version"; break;
d76ed9a9
AS
2342 default: reaction = "<unknown>"; break;
2343 }
de9510bc
AS
2344 reply("OSMSG_ALERT_IS", iter_key(it), reaction, alert->owner);
2345 reply("OSMSG_ALERTS_DESC", alert->text_discrim);
d76ed9a9 2346 }
de9510bc 2347 reply("OSMSG_ALERT_END");
d76ed9a9
AS
2348 return 1;
2349}
2350
2351static MODCMD_FUNC(cmd_stats_gags) {
2352 struct gag_entry *gag;
2353 struct helpfile_table table;
2354 unsigned int nn;
2355
2356 if (!gagList) {
258d1427 2357 reply("OSMSG_NO_GAGS");
d76ed9a9
AS
2358 return 1;
2359 }
2360 for (nn=0, gag=gagList; gag; nn++, gag=gag->next) ;
2361 table.length = nn+1;
2362 table.width = 4;
2363 table.flags = TABLE_NO_FREE;
2364 table.contents = calloc(table.length, sizeof(char**));
2365 table.contents[0] = calloc(table.width, sizeof(char*));
2366 table.contents[0][0] = "Mask";
2367 table.contents[0][1] = "Owner";
2368 table.contents[0][2] = "Expires";
2369 table.contents[0][3] = "Reason";
2370 for (nn=1, gag=gagList; gag; nn++, gag=gag->next) {
2371 char expstr[INTERVALLEN];
2372 if (gag->expires)
2373 intervalString(expstr, gag->expires - now, user->handle_info);
2374 else
2375 strcpy(expstr, "Never");
2376 table.contents[nn] = calloc(table.width, sizeof(char*));
2377 table.contents[nn][0] = gag->mask;
2378 table.contents[nn][1] = gag->owner;
2379 table.contents[nn][2] = strdup(expstr);
2380 table.contents[nn][3] = gag->reason;
2381 }
2382 table_send(cmd->parent->bot, user->nick, 0, NULL, table);
2383 for (nn=1; nn<table.length; nn++) {
2384 free((char*)table.contents[nn][2]);
2385 free(table.contents[nn]);
2386 }
2387 free(table.contents[0]);
2388 free(table.contents);
2389 return 1;
2390}
2391
2392static MODCMD_FUNC(cmd_stats_timeq) {
2393 reply("OSMSG_TIMEQ_INFO", timeq_size(), timeq_next()-now);
2394 return 1;
2395}
2396
de9510bc 2397/*
d76ed9a9
AS
2398static MODCMD_FUNC(cmd_stats_warn) {
2399 dict_iterator_t it;
2400
2401 reply("OSMSG_WARN_LISTSTART");
2402 for (it=dict_first(opserv_chan_warn); it; it=iter_next(it))
2403 reply("OSMSG_WARN_LISTENTRY", iter_key(it), (char*)iter_data(it));
2404 reply("OSMSG_WARN_LISTEND");
2405 return 1;
2406}
de9510bc 2407*/
d76ed9a9 2408
f14e4f83 2409#if defined(WITH_MALLOC_X3)
ec1a68c8 2410static MODCMD_FUNC(cmd_stats_memory) {
2411 extern unsigned long alloc_count, alloc_size;
0d16e639 2412 send_message_type(MSG_TYPE_NOXLATE, user, cmd->parent->bot,
2413 "%u allocations totalling %u bytes.",
2414 alloc_count, alloc_size);
2415 return 1;
2416}
2417#elif defined(WITH_MALLOC_SLAB)
2418static MODCMD_FUNC(cmd_stats_memory) {
2419 extern unsigned long slab_alloc_count, slab_count, slab_alloc_size;
2420 extern unsigned long big_alloc_count, big_alloc_size;
2421 send_message_type(MSG_TYPE_NOXLATE, user, cmd->parent->bot,
2422 "%u allocations in %u slabs totalling %u bytes.",
2423 slab_alloc_count, slab_count, slab_alloc_size);
63c95a47 2424 send_message_type(MSG_TYPE_NOXLATE, user, cmd->parent->bot,
0d16e639 2425 "%u big allocations totalling %u bytes.",
63c95a47 2426 big_alloc_count, big_alloc_size);
ec1a68c8 2427 return 1;
2428}
2429#endif
2430
d76ed9a9
AS
2431static MODCMD_FUNC(cmd_dump)
2432{
b336c8db 2433 char linedup[MAXLEN], original[MAXLEN];
d76ed9a9 2434
b336c8db 2435 unsplit_string(argv+1, argc-1, original);
d76ed9a9
AS
2436 safestrncpy(linedup, original, sizeof(linedup));
2437 /* assume it's only valid IRC if we can parse it */
2438 if (parse_line(linedup, 1)) {
258d1427
AS
2439 irc_raw(original);
2440 reply("OSMSG_LINE_DUMPED");
d76ed9a9 2441 } else
258d1427 2442 reply("OSMSG_RAW_PARSE_ERROR");
d76ed9a9
AS
2443 return 1;
2444}
2445
2446static MODCMD_FUNC(cmd_raw)
2447{
b336c8db 2448 char linedup[MAXLEN], original[MAXLEN];
d76ed9a9 2449
b336c8db 2450 unsplit_string(argv+1, argc-1, original);
d76ed9a9
AS
2451 safestrncpy(linedup, original, sizeof(linedup));
2452 /* Try to parse the line before sending it; if it's too wrong,
2453 * maybe it will core us instead of our uplink. */
2454 parse_line(linedup, 1);
2455 irc_raw(original);
2456 reply("OSMSG_LINE_DUMPED");
2457 return 1;
2458}
2459
2460static struct userNode *
2461opserv_add_reserve(struct svccmd *cmd, struct userNode *user, const char *nick, const char *ident, const char *host, const char *desc)
2462{
2463 struct userNode *resv = GetUserH(nick);
2464 if (resv) {
258d1427
AS
2465 if (IsService(resv)) {
2466 reply("MSG_SERVICE_IMMUNE", resv->nick);
2467 return NULL;
2468 }
2469 if (resv->handle_info
2470 && resv->handle_info->opserv_level > user->handle_info->opserv_level) {
2471 reply("OSMSG_LEVEL_TOO_LOW");
2472 return NULL;
2473 }
d76ed9a9
AS
2474 }
2475 if ((resv = AddClone(nick, ident, host, desc))) {
2476 dict_insert(opserv_reserved_nick_dict, resv->nick, resv);
2477 }
2478 return resv;
2479}
2480
2481static MODCMD_FUNC(cmd_collide)
2482{
2483 struct userNode *resv;
2484
2485 resv = opserv_add_reserve(cmd, user, argv[1], argv[2], argv[3], unsplit_string(argv+4, argc-4, NULL));
2486 if (resv) {
258d1427
AS
2487 reply("OSMSG_COLLIDED_NICK", resv->nick);
2488 return 1;
d76ed9a9
AS
2489 } else {
2490 reply("OSMSG_CLONE_FAILED", argv[1]);
258d1427 2491 return 0;
d76ed9a9
AS
2492 }
2493}
2494
2495static MODCMD_FUNC(cmd_reserve)
2496{
2497 struct userNode *resv;
2498
2499 resv = opserv_add_reserve(cmd, user, argv[1], argv[2], argv[3], unsplit_string(argv+4, argc-4, NULL));
2500 if (resv) {
258d1427
AS
2501 resv->modes |= FLAGS_PERSISTENT;
2502 reply("OSMSG_RESERVED_NICK", resv->nick);
2503 return 1;
d76ed9a9
AS
2504 } else {
2505 reply("OSMSG_CLONE_FAILED", argv[1]);
258d1427 2506 return 0;
d76ed9a9
AS
2507 }
2508}
2509
2510static int
2511free_reserve(char *nick)
2512{
2513 struct userNode *resv;
2514 unsigned int rlen;
2515 char *reason;
2516
2517 resv = dict_find(opserv_reserved_nick_dict, nick, NULL);
2518 if (!resv)
2519 return 0;
2520
2521 rlen = strlen(resv->nick)+strlen(OSMSG_PART_REASON);
2522 reason = alloca(rlen);
2523 snprintf(reason, rlen, OSMSG_PART_REASON, resv->nick);
2524 DelUser(resv, NULL, 1, reason);
2525 dict_remove(opserv_reserved_nick_dict, nick);
2526 return 1;
2527}
2528
2529static MODCMD_FUNC(cmd_unreserve)
2530{
2531 if (free_reserve(argv[1]))
258d1427 2532 reply("OSMSG_NICK_UNRESERVED", argv[1]);
d76ed9a9 2533 else
258d1427 2534 reply("OSMSG_NOT_RESERVED", argv[1]);
d76ed9a9
AS
2535 return 1;
2536}
2537
2538static void
2539opserv_part_channel(void *data)
2540{
2541 DelChannelUser(opserv, data, "Leaving.", 0);
2542}
2543
2544static int alert_check_user(const char *key, void *data, void *extra);
2545
2546static int
2547opserv_new_user_check(struct userNode *user)
2548{
2549 struct opserv_hostinfo *ohi;
2550 struct gag_entry *gag;
2f61d1d7 2551 char addr[IRC_NTOP_MAX_SIZE];
d76ed9a9
AS
2552
2553 /* Check to see if we should ignore them entirely. */
2554 if (IsLocal(user) || IsService(user))
2555 return 0;
2556
2557 /* Check for alerts, and stop if we find one that kills them. */
2558 if (dict_foreach(opserv_user_alerts, alert_check_user, user))
2559 return 1;
2560
2561 /* Gag them if appropriate. */
2562 for (gag = gagList; gag; gag = gag->next) {
2f61d1d7 2563 if (user_matches_glob(user, gag->mask, MATCH_USENICK)) {
d76ed9a9
AS
2564 gag_helper_func(user, NULL);
2565 break;
2566 }
2567 }
2568
2569 /* Add to host info struct */
2f61d1d7 2570 irc_ntop(addr, sizeof(addr), &user->ip);
2571 if (!(ohi = dict_find(opserv_hostinfo_dict, addr, NULL))) {
d76ed9a9 2572 ohi = calloc(1, sizeof(*ohi));
2f61d1d7 2573 dict_insert(opserv_hostinfo_dict, strdup(addr), ohi);
d76ed9a9
AS
2574 userList_init(&ohi->clients);
2575 }
2576 userList_append(&ohi->clients, user);
2577
2578 /* Only warn of new user floods outside of bursts. */
2579 if (!user->uplink->burst) {
2580 if (!policer_conforms(&opserv_conf.new_user_policer, now, 10)) {
2581 if (!new_user_flood) {
2582 new_user_flood = 1;
2583 opserv_alert("Warning: Possible new-user flood.");
2584 }
2585 } else {
2586 new_user_flood = 0;
2587 }
2588 }
2589
08895577 2590 if (checkDefCon(DEFCON_NO_NEW_CLIENTS)) {
2591 irc_kill(opserv, user, DefConGlineReason);
2592 return 0;
2593 }
2594
0272358e 2595 if ( (checkDefCon(DEFCON_GLINE_NEW_CLIENTS) || checkDefCon(DEFCON_SHUN_NEW_CLIENTS)) && !IsOper(user)) {
2596 char target[IRC_NTOP_MAX_SIZE + 3] = { '*', '@', '\0' };
2597
2598 strcpy(target + 2, user->hostname);
2599 if (checkDefCon(DEFCON_GLINE_NEW_CLIENTS))
2600 gline_add(opserv->nick, target, DefConGlineExpire, DefConGlineReason, now, 1, 0);
2601 else if (checkDefCon(DEFCON_SHUN_NEW_CLIENTS))
2602 shun_add(opserv->nick, target, DefConGlineExpire, DefConGlineReason, now, 1);
2603
2604 return 0;
2605 }
2606
d76ed9a9 2607 /* Only warn or G-line if there's an untrusted max and their IP is sane. */
2f61d1d7 2608 if (opserv_conf.untrusted_max
2609 && irc_in_addr_is_valid(user->ip)
2610 && !irc_in_addr_is_loopback(user->ip)) {
2611 struct trusted_host *th = dict_find(opserv_trusted_hosts, addr, NULL);
d76ed9a9 2612 unsigned int limit = th ? th->limit : opserv_conf.untrusted_max;
08895577 2613
2614 if (checkDefCon(DEFCON_REDUCE_SESSION) && !th)
2615 limit = DefConSessionLimit;
2616
d76ed9a9
AS
2617 if (!limit) {
2618 /* 0 means unlimited hosts */
2619 } else if (ohi->clients.used == limit) {
2620 unsigned int nn;
2621 for (nn=0; nn<ohi->clients.used; nn++)
2622 send_message(ohi->clients.list[nn], opserv, "OSMSG_CLONE_WARNING");
2623 } else if (ohi->clients.used > limit) {
2f61d1d7 2624 char target[IRC_NTOP_MAX_SIZE + 3] = { '*', '@', '\0' };
2625 strcpy(target + 2, addr);
9a75756e 2626 gline_add(opserv->nick, target, opserv_conf.clone_gline_duration, "Excessive connections from a single host.", now, 1, 1);
d76ed9a9
AS
2627 }
2628 }
2629
2630 return 0;
2631}
2632
2633static void
2634opserv_user_cleanup(struct userNode *user, UNUSED_ARG(struct userNode *killer), UNUSED_ARG(const char *why))
2635{
2636 struct opserv_hostinfo *ohi;
2f61d1d7 2637 char addr[IRC_NTOP_MAX_SIZE];
d76ed9a9
AS
2638
2639 if (IsLocal(user)) {
2640 /* Try to remove it from the reserved nick dict without
2641 * calling free_reserve, because that would call DelUser(),
2642 * and we'd loop back to here. */
2643 dict_remove(opserv_reserved_nick_dict, user->nick);
2644 return;
2645 }
2f61d1d7 2646 irc_ntop(addr, sizeof(addr), &user->ip);
2647 if ((ohi = dict_find(opserv_hostinfo_dict, addr, NULL))) {
d76ed9a9 2648 userList_remove(&ohi->clients, user);
2f61d1d7 2649 if (ohi->clients.used == 0)
2650 dict_remove(opserv_hostinfo_dict, addr);
d76ed9a9
AS
2651 }
2652}
2653
2654int
2655opserv_bad_channel(const char *name)
2656{
2657 unsigned int found;
697f4c9a 2658 int present;
d76ed9a9 2659
697f4c9a 2660 dict_find(opserv_exempt_channels, name, &present);
2661 if (present)
d76ed9a9
AS
2662 return 0;
2663
2664 if (gline_find(name))
2665 return 1;
2666
2667 for (found=0; found<opserv_bad_words->used; ++found)
2668 if (irccasestr(name, opserv_bad_words->list[found]))
2669 return 1;
2670
2671 return 0;
2672}
2673
2674static void
2675opserv_shutdown_channel(struct chanNode *channel, const char *reason)
2676{
2677 struct mod_chanmode *change;
2678 unsigned int nn;
2679
2680 change = mod_chanmode_alloc(2);
2681 change->modes_set = MODE_SECRET | MODE_INVITEONLY;
2682 change->args[0].mode = MODE_CHANOP;
a32da4c7 2683 change->args[0].u.member = AddChannelUser(opserv, channel);
d76ed9a9 2684 change->args[1].mode = MODE_BAN;
a32da4c7 2685 change->args[1].u.hostmask = "*!*@*";
d76ed9a9
AS
2686 mod_chanmode_announce(opserv, channel, change);
2687 mod_chanmode_free(change);
2688 for (nn=channel->members.used; nn>0; ) {
2689 struct modeNode *mNode = channel->members.list[--nn];
2690 if (IsService(mNode->user))
2691 continue;
2692 KickChannelUser(mNode->user, channel, opserv, user_find_message(mNode->user, reason));
2693 }
2694 timeq_add(now + opserv_conf.purge_lock_delay, opserv_part_channel, channel);
2695}
2696
2697static void
2698opserv_channel_check(struct chanNode *newchan)
2699{
c52666c6 2700 /*char *warning; */
d76ed9a9
AS
2701
2702 if (!newchan->join_policer.params) {
2703 newchan->join_policer.last_req = now;
2704 newchan->join_policer.params = opserv_conf.join_policer_params;
2705 }
c52666c6 2706 /*
d76ed9a9 2707 if ((warning = dict_find(opserv_chan_warn, newchan->name, NULL))) {
57692f5e 2708 global_message_args(MESSAGE_RECIPIENT_OPERS, "OSMSG_CHANNEL_ACTIVITY_WARN", newchan->name, warning);
d76ed9a9 2709 }
c52666c6 2710 */
d76ed9a9 2711
c52666c6 2712 /* Wait until the join check to shut channels down. */
d76ed9a9
AS
2713 newchan->bad_channel = opserv_bad_channel(newchan->name);
2714}
2715
2716static void
2717opserv_channel_delete(struct chanNode *chan)
2718{
2719 timeq_del(0, opserv_part_channel, chan, TIMEQ_IGNORE_WHEN);
2720}
2721
0e08a8e0
AS
2722static void
2723opserv_notice_handler(struct userNode *user, struct userNode *bot, char *text, UNUSED_ARG(int server_qualified))
2724{
2725 char *cmd;
2726 /* if its a version reply, do an alert check (only alerts with version=something) */
2727 if(bot == opserv) {
2728 if(text[0] == '\001') {
2729 text++;
2730 cmd = mysep(&text, " ");
2731 if(!irccasecmp(cmd, "VERSION")) {
2732 char *version = mysep(&text, "\n");
2733 if(!version)
2734 version = "";
2735 /* opserv_debug("Opserv got CTCP VERSION Notice from %s: %s", user->nick, version); */
2736 user->version_reply = strdup(version);
2737 dict_foreach(opserv_user_alerts, alert_check_user, user);
2738 }
2739 }
2740 }
2741}
2742
d76ed9a9
AS
2743static int
2744opserv_join_check(struct modeNode *mNode)
2745{
2746 struct userNode *user = mNode->user;
2747 struct chanNode *channel = mNode->channel;
2748 const char *msg;
2749
2750 if (IsService(user))
2751 return 0;
2752
2753 dict_foreach(opserv_channel_alerts, alert_check_user, user);
2754
2755 if (channel->bad_channel) {
2756 opserv_debug("Found $b%s$b in bad-word channel $b%s$b; removing the user.", user->nick, channel->name);
2757 if (channel->name[0] != '#')
2758 DelUser(user, opserv, 1, "OSMSG_ILLEGAL_KILL_REASON");
2759 else if (!GetUserMode(channel, opserv))
2760 opserv_shutdown_channel(channel, "OSMSG_ILLEGAL_REASON");
2761 else {
2762 send_message(user, opserv, "OSMSG_ILLEGAL_CHANNEL", channel->name);
2763 msg = user_find_message(user, "OSMSG_ILLEGAL_REASON");
2764 KickChannelUser(user, channel, opserv, msg);
2765 }
2766 return 1;
2767 }
2768
2769 if (user->uplink->burst)
2770 return 0;
2771 if (policer_conforms(&channel->join_policer, now, 1.0)) {
2772 channel->join_flooded = 0;
2773 return 0;
2774 }
2775 if (!channel->join_flooded) {
2776 /* Don't moderate the channel unless it is activated and
2777 the number of users in the channel is over the threshold. */
2778 struct mod_chanmode change;
2779 mod_chanmode_init(&change);
2780 channel->join_flooded = 1;
2781 if (opserv_conf.join_flood_moderate && (channel->members.used > opserv_conf.join_flood_moderate_threshold)) {
2782 if (!GetUserMode(channel, opserv)) {
2783 /* If we aren't in the channel, join it. */
2784 change.args[0].mode = MODE_CHANOP;
a32da4c7 2785 change.args[0].u.member = AddChannelUser(opserv, channel);
d76ed9a9
AS
2786 change.argc++;
2787 }
2788 if (!(channel->modes & MODE_MODERATED))
2789 change.modes_set |= MODE_MODERATED;
2790 if (change.modes_set || change.argc)
2791 mod_chanmode_announce(opserv, channel, &change);
2792 send_target_message(0, channel->name, opserv, "OSMSG_FLOOD_MODERATE");
2793 opserv_alert("Warning: Possible join flood in %s (currently %d users; channel moderated).", channel->name, channel->members.used);
2794 } else {
2795 opserv_alert("Warning: Possible join flood in %s (currently %d users).", channel->name, channel->members.used);
2796 }
2797 }
2798 log_module(OS_LOG, LOG_INFO, "Join to %s during flood: "IDENT_FORMAT, channel->name, IDENT_DATA(user));
2799 return 0;
2800}
2801
2802static int
2803opserv_add_bad_word(struct svccmd *cmd, struct userNode *user, const char *new_bad) {
2804 unsigned int bad_idx;
2805
2806 for (bad_idx = 0; bad_idx < opserv_bad_words->used; ++bad_idx) {
2807 char *orig_bad = opserv_bad_words->list[bad_idx];
2808 if (irccasestr(new_bad, orig_bad)) {
2809 if (user)
2810 reply("OSMSG_BAD_REDUNDANT", new_bad, orig_bad);
2811 return 0;
2812 } else if (irccasestr(orig_bad, new_bad)) {
2813 if (user)
2814 reply("OSMSG_BAD_GROWING", orig_bad, new_bad);
2815 free(orig_bad);
2816 opserv_bad_words->list[bad_idx] = strdup(new_bad);
2817 for (bad_idx++; bad_idx < opserv_bad_words->used; bad_idx++) {
2818 orig_bad = opserv_bad_words->list[bad_idx];
2819 if (!irccasestr(orig_bad, new_bad))
2820 continue;
2821 if (user)
2822 reply("OSMSG_BAD_NUKING", orig_bad);
2823 string_list_delete(opserv_bad_words, bad_idx);
2824 bad_idx--;
2825 free(orig_bad);
2826 }
2827 return 1;
2828 }
2829 }
2830 string_list_append(opserv_bad_words, strdup(new_bad));
2831 if (user)
2832 reply("OSMSG_ADDED_BAD", new_bad);
2833 return 1;
2834}
2835
47956fc5
AS
2836static int
2837opserv_routing_plan_add_server(struct routingPlan *rp, const char *name, const char *uplink, const unsigned int port, int karma, const char *second, const unsigned int offline)
2838{
2839 struct routingPlanServer *rps;
2840 rps = calloc(1, sizeof(*rps));
2841 if(!rps)
2842 return 0;
2843 /* duplicate servers replace */
2844 rps->uplink = strdup(uplink);
2845 if(second)
2846 rps->secondaryuplink = strdup(second);
2847 else
2848 rps->secondaryuplink = NULL;
2849 rps->port = port ? port : 4400; /* lame hardcodede default port. maybe get from config file somewhere? */
2850 rps->karma = karma;
2851 rps->offline = offline; /* 1 = yes, 0 = no */
2852 dict_insert(rp->servers, strdup(name), rps);
2853 log_module(OS_LOG, LOG_DEBUG, "Adding rp server %s with uplink %s", name, uplink);
2854 return 1;
2855}
2856
2857static void
2858free_routing_plan_server(void *data)
2859{
2860 struct routingPlanServer *rps = data;
2861 free(rps->uplink);
2862 if(rps->secondaryuplink)
2863 free(rps->secondaryuplink);
2864 free(rps);
2865}
2866
2867struct routingPlan*
2868opserv_add_routing_plan(const char *name)
2869{
2870 struct routingPlan *rp;
2871 rp = calloc(1, sizeof(*rp));
2872 if (!rp)
2873 return NULL;
2874 if(dict_find(opserv_routing_plans, name, NULL))
2875 return NULL; /* plan already exists */
2876 rp->servers = dict_new();
2877 dict_set_free_data(rp->servers, free_routing_plan_server);
2878
2879 dict_insert(opserv_routing_plans, strdup(name), rp);
2880 /* TODO: check for duplicate */
2881 return rp;
2882}
2883
2884static void
2885free_routing_plan(void *data)
2886{
2887 struct routingPlan *rp = data;
2888 /* delete all the servers attached to this plan */
2889 dict_delete(rp->servers);
2890 /* free the plan struct */
2891 free(rp);
2892}
2893
47956fc5
AS
2894/*************************************************
2895* Functions to handle the active routing struct */
2896
2897struct routeList
2898*find_routeList_server(struct route *route, const char *server)
2899{
2900 struct routeList *rptr;
2901 if(!server)
2902 return(NULL);
2903 for(rptr = route->servers;rptr;rptr=rptr->next) {
2904 if(!strcasecmp(rptr->server, server))
2905 return(rptr);
2906 }
2907 return(NULL);
2908}
2909
2910/* Wipes out the routing structure, freeing properly.
2911 * note: does NOT free itself, we just re-use it usually.*/
2912void
2913wipe_route_list(struct route *route) {
2914 struct routeList *nextptr, *rptr;
2915 if(!route)
2916 return;
2917 for(rptr = opserv_route->servers; rptr; rptr=nextptr)
2918 {
2919 nextptr = rptr->next;
2920 free(rptr->server);
2921 if(rptr->uplink)
2922 free(rptr->uplink);
2923 if(rptr->secondaryuplink)
2924 free(rptr->secondaryuplink);
2925 free(rptr);
2926 }
2927 route->centered = true;
2928 route->count = 0;
2929 route->maxdepth = 0;
2930 route->servers = NULL;
2931}
2932
2933
2934int
2935rank_outside_rec(struct route *route, char *server, int count)
2936{
2937 struct routeList *rptr;
2938 int n, max = 0;
2939 int i = 0;
2940 if(count > 256) { /* XXX: 256 becomes max # of servers this works with, whats the real #? */
2941 return -1;
2942 }
2943 for(rptr = route->servers; rptr; rptr = rptr->next) {
2944 i++;
2945 if(!strcasecmp(server, rptr->uplink)) {
2946 log_module(MAIN_LOG, LOG_DEBUG, "%d:%d: rank_outside_rec(%s) calling rank_outside_rec(%s)", count, i, rptr->server, rptr->uplink);
2947 n = rank_outside_rec(route, rptr->server, count +1);
2948 if(n < 0) /* handle error condition */
2949 return n;
2950 if(n > max)
2951 max = n;
2952 }
2953 }
2954 if((rptr = find_routeList_server(route, server))) {
2955 rptr->outsideness = max;
2956 return(max + 1);
2957 }
2958 else {
2959 log_module(MAIN_LOG, LOG_ERROR, "routing struct rank_outsideness() couldnt find %s", server);
2960 return 0;
2961 }
2962}
2963
2964int
2965rank_outsideness(struct route *route)
2966{
2967 log_module(MAIN_LOG, LOG_DEBUG, "rank_outsideness(): Running...");
2968 route->maxdepth = rank_outside_rec(route, self->uplink->name, 0) - 1;
2969 if(route->maxdepth < 0) { /* if the rank failed, remove route */
2970 log_module(MAIN_LOG, LOG_WARNING, "The active routing plan has a loop! auto routing disabled.");
2971 wipe_route_list(route);
2972 return false;
2973 }
2974 return true;
2975}
2976
2977
2978/* Add servers to the routing structure */
2979void
2980add_routestruct_server(struct route *route, const char *server, unsigned int port, char *uplink, char *secondary)
2981{
2982 struct routeList *rptr;
2983 char *hname;
2984 if(find_routeList_server(route, server))
2985 {
2986 log_module(MAIN_LOG, LOG_WARNING, "Routing structure add server Skipping duplicate [%s]. This should never really be possible.", server);
2987 return;
2988 }
2989 rptr = calloc(1, sizeof(*rptr));
2990 rptr->server = strdup(server);
2991 rptr->port = port;
2992 if(!uplink) {
2993 hname = conf_get_data("server/hostname", RECDB_QSTRING);
2994 uplink = hname;
2995 }
2996 rptr->uplink = strdup(uplink);
2997 if(secondary)
2998 rptr->secondaryuplink = strdup(secondary);
2999 /* tack this server on the front of the list */
3000 rptr->next = route->servers;
3001 route->servers = rptr;
3002 route->count++;
3003
3004#ifdef notdef /* I dont quite get this. there could be uncentered things
3005 * added after our own uplink, and this function doesnt center
3006 * as it adds. -Rubin */
3007 /* If the map hasnt been centered yet... */
3008 if(route->centered == false) {
3009 /* AND we just added our own uplink to it... */
3010 if(!strcasecmp(server, self->uplink->name)) {
3011 change_route_uplinks(route); /* recenter it, n mark it centered. */
3012 }
3013 }
3014#endif
3015}
3016
3017/* Recenter the routing struct around our current uplink */
3018int
3019change_route_uplinks(struct route *route)
3020{
3021 struct routeList *rptr;
3022 char lastserver[MAXLEN];
3023 char nextserver[MAXLEN];
3024
3025 if(!route->servers)
3026 return false; /* no map to recenter */
3027 log_module(MAIN_LOG, LOG_DEBUG, "change_route_uplinks(): running...");
3028 char *servicename = conf_get_data("server/hostname", RECDB_QSTRING);
3029 strcpy(lastserver, servicename);
3030 rptr = find_routeList_server(route, self->uplink->name);
3031 if(!rptr) {
3032 log_module(MAIN_LOG, LOG_ERROR, "Cannot convert routing map to center: My uplink is not on the map! Marking map as uncentered.");
3033 route->centered = false;
3034 return false;
3035 }
3036 if(!strcasecmp(rptr->uplink, servicename)) {
3037 log_module(MAIN_LOG, LOG_DEBUG, "Already centered");
3038 }
3039 else { /* else, center it */
3040 while(rptr) {
3041 strcpy(nextserver, rptr->uplink);
3042 log_module(MAIN_LOG, LOG_DEBUG, "change_route_uplinks() changing %s uplink to %s.", rptr->server, lastserver);
3043 free(rptr->uplink);
3044 rptr->uplink = strdup(lastserver);
3045 strcpy(lastserver, rptr->server);
3046 rptr = find_routeList_server(route, nextserver);
3047 }
3048 }
3049 if(rank_outsideness(route) > 0) {
3050 route->centered = true;
3051 return true;
3052 }
3053 else
3054 return false;
3055}
3056
3057int
3058activate_routing(struct svccmd *cmd, struct userNode *user, char *plan_name)
3059{
3060 static struct routingPlan *rp;
3061 dict_iterator_t it;
3062 char *karma;
3063
3064 if(plan_name) { /* make this the new active plan */
3065 if(!strcmp(plan_name, "*")) {
3066 /* disable routing */
3067 dict_remove(opserv_routing_plan_options, "ACTIVE");
3068 plan_name = NULL;
3069 }
3070 else {
3071 rp = dict_find(opserv_routing_plans, plan_name, NULL);
3072 if(!rp) {
3073 if(cmd && user)
3074 reply("OSMSG_PLAN_NOT_FOUND", plan_name);
3075 else {
3076 /* since it doesnt exist, remove the active setting */
3077 dict_remove(opserv_routing_plan_options, plan_name);
3078 }
3079 log_module(MAIN_LOG, LOG_ERROR, "activate_routing() couldnt find active routing plan!");
3080 return 0;
3081 }
3082 }
3083 }
3084 else { /* find the active plan in settings */
3085 plan_name = dict_find(opserv_routing_plan_options, "ACTIVE", NULL);
3086 }
3087 if(!plan_name) { /* deactivated, or no plan was set active */
3088 /* TODO: delete routing map if it exists */
3089 wipe_route_list(opserv_route);
3090 return 1;
3091 }
3092
3093 karma = dict_find(opserv_routing_plan_options, "KARMA", NULL);
3094
3095 rp = dict_find(opserv_routing_plans, plan_name, NULL);
3096
3097 /* this should really be done during opserv init */
3098 if(!opserv_route)
3099 opserv_route = calloc(1, sizeof(*opserv_route));
3100
3101 /* Delete the existing active route */
3102 wipe_route_list(opserv_route);
3103
3104 for(it = dict_first(rp->servers); it; it = iter_next(it)) {
3105 const char* servername = iter_key(it);
3106 struct routingPlanServer *rps = iter_data(it),
3107 *rp_uplink, *rp_second = NULL;
3108 char *uplink = rps->uplink;
3109 rp_uplink = dict_find(rp->servers, rps->uplink, NULL);
3110 if(rps->secondaryuplink)
3111 rp_second = dict_find(rp->servers, rps->secondaryuplink, NULL);
3112
3113 /* If the normal uplink has bad karma, don't use it as a hub,
3114 * switch to the secondary uplink.
3115 */
3116 if(karma && enabled_string(karma) && rp_uplink && rp_uplink->karma < 0) {
3117 if(rps->secondaryuplink) {
3118 uplink = rps->secondaryuplink;
3119 /* unless the secondary uplinks karma is worse than the uplink. */
3120 if((rp_second = dict_find(rp->servers, uplink, NULL)) && rp_second->karma < rp_uplink->karma)
3121 uplink = rps->uplink;
3122 }
3123 }
3124 /*
3125 * If _WE_ have bad karma, don't link us to our normal uplink, maybe
3126 * its a bad route. switch to secondary. Important: dont neg karma when we arnt on
3127 * our primary uplink, or we'll get stuck on secondary when THAT link is worse.
3128 */
3129 if(karma && enabled_string(karma) && (rps->karma < 0 || rps->offline) ) {
3130 if(rps->secondaryuplink) {
3131 uplink = rps->secondaryuplink;
3132 }
3133 }
3134 log_module(MAIN_LOG, LOG_DEBUG, "activate_routing() adding %s:%d %s", servername, rps->port, uplink);
3135 add_routestruct_server(opserv_route, servername, rps->port, uplink, NULL);
3136 }
3137 if(change_route_uplinks(opserv_route))
3138 return 1;
3139 else if(user) {
3140 reply("OSMSG_ROUTING_ACTIVATION_ERROR");
3141 activate_routing(cmd, user, "*");
3142 return 0;
3143 }
3144 return 1;
3145}
3146
3147/*******************************************************
3148 * Functions to handle online route configuration via opserv
3149 */
3150static void route_show_option(struct svccmd *cmd, struct userNode *user, char *name)
3151{
3152 char *value = dict_find(opserv_routing_plan_options, name, NULL);
3153 if(value) {
3154 if(!strcmp("RETRY_PERIOD", name)) { /* Show as an interval */
3155 char buff[INTERVALLEN+1];
3156 reply("OSMSG_ROUTINGPLAN_OPTION", name, intervalString(buff, atoi(value), user->handle_info));
3157 }
3158 else if(!strcmp("ACTIVE", name)) {
3159 if(opserv_route && opserv_route->servers)
3160 reply("OSMSG_ROUTINGPLAN_ACTIVE", value);
3161 else
3162 reply("OSMSG_ROUTINGPLAN_OPTION_NOT_SET", name);
3163 }
3164 else {
3165 reply("OSMSG_ROUTINGPLAN_OPTION", name, value);
3166 }
3167 }
3168 else {
3169 reply("OSMSG_ROUTINGPLAN_OPTION_NOT_SET", name);
3170 }
3171}
3172
3173static void route_show_options(struct svccmd *cmd, struct userNode *user)
3174{
3175 char *options[] = {"ACTIVE", "RETRY_PERIOD", "CONN_PINGOUT", "CONN_READERROR", "KARMA", "DEFAULT_PORT", NULL};
3176 int i;
3177 for(i = 0; options[i]; i++) {
3178 route_show_option(cmd, user, options[i]);
3179 }
3180}
3181
3182/* called from timeq */
3183void routing_connect_timeout(void *data)
3184{
3185 struct waitingConnection *wc = data;
3186 struct server *target = GetServerH(wc->target);
3187 if(!target) {
3188 dict_remove(opserv_waiting_connections, wc->server);
3189 return; /* server we wanted to connect new server to is gone, just give up */
3190 }
3191 routing_handle_connect_failure(target, wc->server, "Connection timed out");
3192 /* the following invalidates server variable! */
3193 dict_remove(opserv_waiting_connections, wc->server);
3194}
3195
3196void routing_delete_connect_timer(char *server)
3197{
3198 struct waitingConnection *wc = dict_find(opserv_waiting_connections, server, 0);
3199 if(wc) {
3200 timeq_del(0, routing_connect_timeout, wc, TIMEQ_IGNORE_WHEN);
3201 dict_remove(opserv_waiting_connections, server);
3202 }
3203}
3204
3205
3206void
3207routing_connect_server(char *server, int port, struct server *to)
3208{
3209 struct waitingConnection *wc = calloc(sizeof(*wc), 1);
3210
3211 wc->server = strdup(server);
3212 wc->target = strdup(to->name);
e6dce34c
AS
3213 /* Just to make sure there isn't one left hanging
3214 * if 2 connections are attempted at once..
3215 * */
3216 routing_delete_connect_timer(server);
47956fc5
AS
3217 dict_insert(opserv_waiting_connections, strdup(server), wc);
3218 timeq_add(now + ROUTING_CONNECT_TIMEOUT, routing_connect_timeout, wc);
3219
3220 irc_connect(opserv, server, port, to);
3221}
3222
3223int
3224routing_connect_one(struct route *route, char *server)
3225{
3226 struct routeList *rptr;
3227 struct server *sptr, *suptr;
3228 for(rptr = route->servers; rptr; rptr = rptr->next) {
3229 if(!strcasecmp(rptr->server, server)) {
3230 /* this is the one, connect it */
3231 suptr = GetServerH(rptr->uplink);
3232 sptr = GetServerH(rptr->server);
3233 if(sptr)
3234 return 1; /* already linked */
3235 if(suptr) {
3236 routing_connect_server(rptr->server, rptr->port, suptr);
3237 return 1; /* attempted link */
3238 }
3239 return 0; /* its uplink isnt here to link to */
3240 }
3241 }
da0c436d
AS
3242 log_module(MAIN_LOG, LOG_DEBUG, "Tried to link %s but its not in the active routing struct!", server);
3243 return 0; /* server wasnt found in active route struct. */
47956fc5
AS
3244}
3245
3246int routing_connect_children(struct route *route, char *server)
3247{
3248 struct routeList *rptr;
3249 struct server *sptr, *suptr;
3250 for(rptr = route->servers; rptr; rptr = rptr->next) {
3251 if(!strcasecmp(rptr->uplink, server)) {
3252 /* this is the one, connect it */
3253 suptr = GetServerH(rptr->uplink);
3254 sptr = GetServerH(rptr->server);
3255 if(sptr)
3256 continue; /* already linked */
3257 if(suptr) {
3258 routing_connect_server(rptr->server, rptr->port, suptr);
3259 continue; /* attempted link */
3260 }
3261 continue; /* its uplink isnt here to link to */
3262 }
3263 }
3264 return 1; /* server wasnt found in active route struct ?! */
3265}
3266
3267int reroute(struct route *route, struct userNode *user, struct svccmd *cmd, char *directive)
3268{
3269 struct routeList *rptr;
3270 struct server *sptr, *suptr;
3271 int connect = 0, move = 0, missing = 0, i;
3272 char d = toupper(*directive);
3273
964abe6b
AS
3274 if(!route || !route->servers) {
3275 reply("OSMSG_REROUTING_NOTCONFIGURED");
3276 return 0;
3277 }
47956fc5
AS
3278 if(user) {
3279 if(d == 'N') { /* normal */
3280 irc_wallops("%s", "Attempting a reroute of the network according to loaded map...");
3281 reply("OSMSG_REROUTING_ACC_MAP");
3282 }
3283 else if(d == 'C') { /* only connect */
3284 reply("OSMSG_CONNECTING_MISSING_ONLY");
3285 }
3286 else if(d == 'T') { /* test */
3287 reply("OSMSG_TESTING_REROUTE");
3288 }
3289 else
3290 {
3291 reply("OSMSG_INVALID_DIRECTIVE", directive);
3292 return 0;
3293 }
3294 }
3295 for(i = 0; i <= route->maxdepth-1; i++) {
3296 for(rptr = route->servers; rptr; rptr = rptr->next) {
3297 if(rptr->outsideness == i) {
3298 /* debugging */
3299 if(user && d=='T')
3300 reply("OSMSG_INSPECTING_SERVER", rptr->server);
3301 suptr = GetServerH(rptr->uplink);
3302 if(!suptr) {
3303 if(rptr->secondaryuplink && (suptr = GetServerH(rptr->secondaryuplink))) {
3304 if(user)
3305 reply("OSMSG_COULDNT_FIND_SERVER", rptr->uplink, rptr->secondaryuplink, rptr->server);
3306 }
3307 }
3308 if(suptr) { /* if the proper uplink is connected.. */
3309 sptr = GetServerH(rptr->server);
3310 if(d == 'C' && sptr) {
3311 continue; /* Already linked */
3312 }
3313 /* If server is missing or the uplinks are not the same then... */
3314 else if(!sptr || strcasecmp(sptr->uplink->name, rptr->uplink)) {
3315 if(!sptr) {
3316 connect++;
3317 }
3318 else { /* Server is already connected somewhere */
3319 if(strcasecmp(sptr->uplink->name, rptr->uplink)) {
3320 if(d != 'T') { /* do it for real */
3321 irc_squit_route(sptr, "%s issued reroute.", user ? user->nick : opserv->nick);
3322 }
3323 else { /* just pretend */
3324 reply("OSMSG_SQUIT", rptr->server);
3325 }
3326 move++;
3327 }
3328 }
3329 if(d != 'T') /* do the real thing */
3330 routing_connect_server(rptr->server, rptr->port, suptr);
3331 else /* just pretend */
3332 reply("OSMSG_CONNECT", rptr->server, rptr->port, suptr->name);
3333 }
3334 }
3335 else {
3336 log_module(MAIN_LOG, LOG_DEBUG, "server uplink %s was not found, cant connect %s", rptr->uplink, rptr->server);
3337 missing++;
3338 }
3339 } /* outsideness = 1 */
3340 } /* rptr */
3341 } /* maxdepth */
3342 if(user) { /* report on what we did */
3343 if(!strcasecmp(directive, "C")) {
3344 if(connect > 0)
3345 reply("OSMSG_CONNECTING_MISSING", connect);
3346 else
3347 reply("OSMSG_NO_SERVERS_MISSING");
3348 }
3349 else {
3350 if(move+connect > 0)
3351 reply("OSMSG_REROUTE_COMPLETE", move, connect, move+connect);
3352 else
3353 reply("OSMSG_NO_ROUTING_NECESSARY");
3354 if(missing > 0)
3355 reply("OSMSG_UPLINKS_MISSING", missing);
3356 }
3357 }
3358 return(move+connect);
3359}
3360
3361static MODCMD_FUNC(cmd_reroute) {
3362 char* upper;
3363 upper = argv[1];
3364 if(reroute(opserv_route, user, cmd, upper))
3365 return 1;
3366 else
3367 return 0;
3368}
3369
3370/* reroute_timer(run)
3371 * run - if it is null, just setup the timer
3372 * but dont run reroute now. otherwise reroute
3373 * and setup timer.
3374 */
3375void reroute_timer(void *data) {
3376 if(!opserv_route || !opserv_route->servers)
3377 return; /* no active route */
3378 char *retry_period = dict_find(opserv_routing_plan_options, "RETRY_PERIOD", NULL);
3379 if(!retry_period)
3380 return; /* retry_period invalid */
3381 unsigned int freq = atoi(retry_period);
3382 if(freq < 1)
3383 return; /* retry_period set to 0, disable */
3384
3385 /* Do the reroute C attempt */
3386 if(data)
3387 reroute(opserv_route, NULL, NULL, "C");
3388
3389 /* Re-add ourselves to the timer queue */
3390 timeq_add(now + freq, reroute_timer, "run");
3391}
3392
3393void routing_change_karma(struct routingPlanServer *rps, const char *server, int change) {
3394
3395 int oldkarma = rps->karma;
3396 rps->karma += change;
3397 if(rps->karma < KARMA_MIN)
3398 rps->karma = KARMA_MIN;
3399 if(rps->karma > KARMA_MAX)
3400 rps->karma = KARMA_MAX;
3401 log_module(MAIN_LOG, LOG_DEBUG, "Changing %s karma by %d. new karma %d.", server, change, rps->karma);
3402 if(oldkarma > 0 && rps->karma < 0) {
3403 /* we just crossed over to negitive */
3404 log_module(MAIN_LOG, LOG_INFO, "Server %s just went negitive karma!", server);
3405 activate_routing(NULL, NULL, NULL);
3406 }
3407 else if(oldkarma < 0 && rps->karma > 0) {
3408 /* we just crossed over to positive */
3409 log_module(MAIN_LOG, LOG_INFO, "Server %s just went back positive karma.", server);
3410 activate_routing(NULL, NULL, NULL);
3411 }
3412}
3413
3414void routing_karma_timer(void *data) {
3415 time_t next;
3416 time_t timer_init = data ? atoi(data) : 0;
3417 char buf[MAXLEN];
3418
3419 log_module(MAIN_LOG, LOG_DEBUG, "routing_karma_timer() is running. timer_init=%d.", (unsigned int) timer_init);
3420
3421 /* If theres a time passed in, dont run unless that time is overdue. */
3422 if(!timer_init || (timer_init < now)) {
3423 if(opserv_route && opserv_route->servers) {
3424 char *active = dict_find(opserv_routing_plan_options, "ACTIVE", NULL);
3425 struct routingPlan *rp;
3426 if(active && (rp = dict_find(opserv_routing_plans, active, NULL))) {
3427 dict_iterator_t it;
3428 /* Walk through each server in the active routing plan.. */
3429 for(it = dict_first(rp->servers); it; it = iter_next(it)) {
3430 struct routingPlanServer *rps = iter_data(it);
3431 struct server *server = GetServerH(iter_key(it));
3432 /* Give everyone +KARMA_ENTROPE just for nothing */
3433 routing_change_karma(rps, iter_key(it), KARMA_ENTROPE);
3434 /* give an additonal +KARMA_RELIABLE to servers that
3435 * have been linked at least KARMA_TIMER seconds. */
3436 if(server && (server->link < (now - KARMA_TIMER) ) ) {
3437 routing_change_karma(rps, iter_key(it), KARMA_RELIABLE);
3438 }
3439 }
3440 }
3441 }
3442 }
3443 if(timer_init > now) /* loading a saved value */
3444 next = timer_init;
3445 else /* no scheduled timer, or we missed it. start from now */
3446 next = now + KARMA_TIMER;
3447 /* Save when karma_timer should run again in case we restart before then */
3448 log_module(MAIN_LOG, LOG_DEBUG, "routing_karma_timer() scheduling self to run again at %d", (unsigned int) next);
3449 sprintf(buf, "%u", (unsigned int) next);
3450 dict_insert(opserv_routing_plan_options, "KARMA_TIMER", strdup(buf));
3451 /* add a timer to run this again .. */
3452 timeq_add(next, routing_karma_timer, NULL);
3453}
3454
3455void routing_handle_neg_karma(char *server, char *uplink, int change)
3456{
3457 /* if server's primary uplink is uplink, OR, uplink's primary uplink is server,
3458 * then whichever one, gets its karma changed. */
3459 char *active = dict_find(opserv_routing_plan_options, "ACTIVE", NULL);
3460 struct routingPlan *rp;
3461 struct routingPlanServer *rps;
3462 if(!active)
3463 return;
3464 if(!(rp = dict_find(opserv_routing_plans, active, NULL)))
3465 return;
3466 if((rps = dict_find(rp->servers, server, NULL))) {
3467 if(!strcasecmp(rps->uplink, uplink)) {
3468 /* server's uplink is uplink */
3469 routing_change_karma(rps, server, change);
3470 return;
3471 }
3472 }
3473 if((rps = dict_find(rp->servers, uplink, NULL))) {
3474 if(!strcasecmp(rps->uplink, server)) {
3475 /* uplink's uplink is server */
3476 routing_change_karma(rps, uplink, change);
3477 return;
3478 }
3479 }
3480}
3481
3482void
3483routing_handle_squit(char *server, char *uplink, char *message)
3484{
3485 log_module(MAIN_LOG, LOG_DEBUG, "Routing_handle_squit(%s, %s)", server, message);
3486
3487 char *val;
3488
3489 if(match_ircglob(message, "Ping timeout")) {
3490 routing_handle_neg_karma(server, uplink, KARMA_PINGOUT);
3491 /* if conn_pingout is true, try to reconnect it obaying karma rules. */
3492
3493 val = dict_find(opserv_routing_plan_options, "CONN_PINGOUT", 0);
3494 if(val && enabled_string(val))
3495 routing_connect_one(opserv_route, server);
3496 }
3497 else if(match_ircglob(message, "Read error:*")) {
3498 routing_handle_neg_karma(server, uplink, KARMA_READERROR);
3499 /* if conn_readerror is true, try to reconnect it obaying karma rules. */
3500 val = dict_find(opserv_routing_plan_options, "CONN_READERROR", 0);
3501 if(val && enabled_string(val))
3502 routing_connect_one(opserv_route, server);
3503 }
3504 /* Else whats the message (an oper squit it?) dont interfere */
3505}
3506
3507void
3508routing_handle_connect(char *server, char *uplink)
3509{
3510 char *active;
3511 struct routingPlan *rp;
3512 struct routingPlanServer *rps;
3513 dict_iterator_t it;
3514
3515 log_module(MAIN_LOG, LOG_DEBUG, "routing_handle_connect(%s, %s)", server, uplink);
3516 /* delete a pending connection timer, if any */
3517 routing_delete_connect_timer(server);
3518 /* check if routing is active... */
3519 active = dict_find(opserv_routing_plan_options, "ACTIVE", NULL);
3520 if(!active)
3521 return;
3522 rp = dict_find(opserv_routing_plans, active, NULL);
3523 if(!rp)
3524 return;
3525
3526 /* If its offline, mark it online again.. */
3527 if((rps = dict_find(rp->servers, server, NULL))) {
3528 if(rps->offline == true) {
3529 rps->offline = false;
3530 if(rps->secondaryuplink) {
3531 /* re-activate to move it back to its primary */
3532 activate_routing(NULL, NULL, NULL);
3533 }
3534 }
3535 /* if there are any servers missing who have this server as uplink try to connect them. */
3536 routing_connect_children(opserv_route, server);
3537 }
3538 /* foreach server x3 knows about, if the uplink is this server, call this function on the child. */
3539 for (it=dict_first(servers); it; it=iter_next(it)) {
3540 struct server *sptr = iter_data(it);
3541 if(sptr && sptr->uplink && !strcasecmp(server, sptr->uplink->name)) {
3542 log_module(MAIN_LOG, LOG_DEBUG, "routing_handle_connect calling self on %s's leaf %s", server, sptr->name);
3543 routing_handle_connect(sptr->name, sptr->uplink->name);
3544 }
3545 }
3546}
3547
3548/* Handle a failed attempt at connecting servers
3549 * - we should only get here regarding servers X3 attempted to link, other
3550 * opers link messages go to them not to us
3551 */
3552void
3553routing_handle_connect_failure(struct server *source, char *server, char *message)
3554{
3555 char *active;
3556 struct routingPlan *rp;
3557 struct routingPlanServer *rps;
3558 log_module(MAIN_LOG, LOG_ERROR, "Failed to connect %s to %s: %s", server, source->name, message);
3559 /* remove the waiting connection n timeq */
3560 routing_delete_connect_timer(server);
3561 /* check if routing is active.. */
3562 active = dict_find(opserv_routing_plan_options, "ACTIVE", NULL);
3563 if(!active)
3564 return;
3565 rp = dict_find(opserv_routing_plans, active, NULL);
3566 if(!rp)
3567 return;
3568
3569 if( ((rps = dict_find(rp->servers, server, NULL)) && !strcasecmp(rps->uplink, source->name))) {
3570 /* failed to connect to its primary uplink */
3571 if(rps->offline == false) {
3572 rps->offline = true;
3573 if(rps->secondaryuplink) {
3574 /* re-activate routing so the secondary
3575 * becomes its uplink, and try again */
3576 activate_routing(NULL, NULL, NULL);
3577 /* attempt to link it again. */
3578 routing_connect_one(opserv_route, server);
bf93ca8d
AS
3579 /* TODO: reconnect any missing servers who
3580 * normally connect to server, using their backups.
3581 * Probably should just issue a reroute C here. */
47956fc5
AS
3582 }
3583 }
3584 }
3585}
3586
3587/* Delete any existing timers, and start the timer again
3588 * using the passed time for the first run.
3589 * - this is called during a retry_period change
3590 * before it has saved the new value. */
3591void reroute_timer_reset(unsigned int time)
3592{
3593 timeq_del(0, reroute_timer, NULL, TIMEQ_IGNORE_DATA & TIMEQ_IGNORE_WHEN);
3594 timeq_add(now + time, reroute_timer, "run");
3595}
3596
3597static MODCMD_FUNC(cmd_routing_set)
3598{
3599 char *option = argv[1];
3600 char *options[] = {"ACTIVE", "RETRY_PERIOD", "CONN_PINGOUT", "CONN_READERROR", "KARMA", "DEFAULT_PORT", NULL};
3601 int i;
3602 if(argc < 2) {
3603 route_show_options(cmd, user);
3604 }
3605 else {
3606 char *found_option = NULL;
3607 for(i = 0; options[i]; i++) {
3608 if(!strcasecmp(options[i], option))
3609 found_option = options[i];
3610 }
3611 if(!found_option) {
3612 reply("OSMSG_ROUTINGPLAN_OPTION_NOT_FOUND", option);
3613 return 0;
3614 }
3615 if(argc > 2) {
3616 char *value = argv[2];
3617 char buff[MAXLEN]; /* whats the max length of unsigned int as printf'd? */
3618 if(!strcmp(found_option, "ACTIVE")) { /* must be an existing route. */
3619 if(disabled_string(value) || false_string(value)) {
3620 /* make none of the maps active */
3621 activate_routing(cmd, user, "*");
3622 reply("OSMSG_ROUTING_DISABLED");
3623 return 1;
3624 }
3625 else if(!activate_routing(cmd, user, value)) {
3626 /* neg reply handled in activate_routing */
3627 return 0;
3628 }
3629 }
3630 if(!strcmp(found_option, "CONN_READERROR") || !strcmp(found_option, "CONN_PINGOUT") ||
3631 !strcmp(found_option, "KARMA") ) {
3632 if( enabled_string(value)) {
3633 value = "ENABLED";
3634 }
3635 else if( disabled_string(value) ) {
3636 value = "DISABLED";
3637 }
3638 else {
3639 reply("MSG_INVALID_BINARY", value);
3640 return 0;
3641 }
3642 }
3643 if(!strcmp(found_option, "RETRY_PERIOD")) {
3644 unsigned int duration = ParseInterval(value);
3645 sprintf(buff, "%d", duration);
3646 value = buff;
3647 reroute_timer_reset(duration);
3648 }
3649 /* set the value here */
3650 dict_remove(opserv_routing_plan_options, found_option);
3651 dict_insert(opserv_routing_plan_options, strdup(found_option), strdup(value));
3652 route_show_option(cmd, user, found_option);
3653 }
3654 else {
3655 /* show the current value */
3656 route_show_option(cmd, user, found_option);
3657 }
3658 }
3659 return 1;
3660}
3661
3662static MODCMD_FUNC(cmd_stats_routing_plans) {
3663 dict_iterator_t rpit;
3664 dict_iterator_t it;
3665 struct routingPlan *rp;
3666 reply("OSMSG_ROUTINGPLAN_LIST");
3667 reply("OSMSG_ROUTINGPLAN_BAR");
3668 for(rpit = dict_first(opserv_routing_plans); rpit; rpit = iter_next(rpit)) {
3669 const char* name = iter_key(rpit);
3670 rp = iter_data(rpit);
3671 reply("OSMSG_ROUTINGPLAN_NAME", name);
3672 for(it = dict_first(rp->servers); it; it = iter_next(it)) {
3673 const char* servername = iter_key(it);
3674 struct routingPlanServer *rps = iter_data(it);
3675 reply("OSMSG_ROUTINGPLAN_SERVER", servername, rps->port, rps->uplink, rps->karma, rps->offline? "offline" : "online", rps->secondaryuplink ? rps->secondaryuplink : "None");
3676 }
3677
3678 }
3679 reply("OSMSG_ROUTINGPLAN_END");
3680 route_show_options(cmd, user);
3681 return 1;
3682}
3683
3684
3685static MODCMD_FUNC(cmd_routing_addplan)
3686{
3687 char *name;
3688 name = argv[1];
3689 /* dont allow things like 'off', 'false', '0' because thats how we disable routing. */
3690 if(*name && !disabled_string(name) && !false_string(name)) {
3691 if(opserv_add_routing_plan(name)) {
3692 reply("OSMSG_ADDPLAN_SUCCESS", name);
3693 return 1;
3694 }
3695 else {
3696 reply("OSMSG_ADDPLAN_FAILED", name);
3697 return 0;
3698 }
3699 }
3700 else
3701 {
3702 reply("OSMSG_INVALID_PLAN");
3703 return 0;
3704 }
3705}
3706
3707static MODCMD_FUNC(cmd_routing_delplan)
3708{
3709 char *name = argv[1];
3710 if( dict_remove(opserv_routing_plans, name) ) {
3711 char *active = dict_find(opserv_routing_plan_options, "ACTIVE", NULL);
3712 if(active && !strcasecmp(active, name)) {
3713 /* if this was the active plan, disable routing */
3714 activate_routing(cmd, user, "*");
3715 reply("OSMSG_ROUTING_DISABLED");
3716 }
3717 reply("OSMSG_PLAN_DELETED");
3718 return 1;
3719 }
3720 else {
3721 reply("OSMSG_PLAN_NOT_FOUND", name);
3722 return 0;
3723 }
3724}
3725
3726static MODCMD_FUNC(cmd_routing_addserver)
3727{
3728 char *plan;
3729 char *server;
3730 char *portstr;
3731 char *uplink;
3732 char *second;
3733 unsigned int port;
3734 struct routingPlan *rp;
3735
3736 plan = argv[1];
3737 server = strdup(argv[2]);
3738 server = strtok(server, ":");
3739 portstr = strtok(NULL, ":");
3740 if(portstr)
3741 port = atoi(portstr);
3742 else {
3743 char *str = dict_find(opserv_routing_plan_options, "DEFAULT_PORT", NULL);
3744 uplink = argv[3];
3745 port = str ? atoi(str) : 0;
3746 }
3747 uplink = argv[3];
3748 if(argc > 4)
3749 second = argv[4];
3750 else
3751 second = NULL;
3752
3753 if( (rp = dict_find(opserv_routing_plans, plan, 0))) {
3754 char *active;
3755 opserv_routing_plan_add_server(rp, server, uplink, port, KARMA_DEFAULT, second, 0);
3756 reply("OSMSG_PLAN_SERVER_ADDED", server);
3757 if((active = dict_find(opserv_routing_plan_options, "ACTIVE", 0)) && !strcasecmp(plan, active)) {
3758 /* re-activate routing with new info */
3759 activate_routing(cmd, user, NULL);
3760 }
3761
3762 free(server);
3763 return 1;
3764 }
3765 else {
3766 reply("OSMSG_PLAN_NOT_FOUND", plan);
3767 free(server);
3768 return 0;
3769 }
3770}
3771
3772static MODCMD_FUNC(cmd_routing_delserver)
3773{
3774 char *plan;
3775 char *server;
3776 struct routingPlan *rp;
3777 plan = argv[1];
3778 server = argv[2];
3779 if( (rp = dict_find(opserv_routing_plans, plan, 0))) {
3780 if(dict_remove(rp->servers, server)) {
3781 char *active;
3782 reply("OSMSG_PLAN_SERVER_DELETED");
3783 if((active = dict_find(opserv_routing_plan_options, "ACTIVE", 0)) && !strcasecmp(plan, active)) {
3784 /* re-activate routing with new info */
3785 activate_routing(cmd, user, NULL);
3786 }
3787
3788 return 1;
3789 }
3790 else {
3791 reply("OSMSG_PLAN_SERVER_NOT_FOUND", server);
3792 return 0;
3793 }
3794 }
3795 else {
3796 reply("OSMSG_PLAN_NOT_FOUND", plan);
3797 return 0;
3798 }
3799}
3800
3801
3802/*************************************************
3803 * Functions to deal with 'route map' command */
3804
3805/* Figures out how many downlinks there are for proper
3806 * drawing of the route map */
3807int
3808num_route_downlinks(struct route *route, char *name)
3809{
3810 struct routeList *rptr;
3811 int num = 0;
3812 rptr = route->servers;
3813 while(rptr) {
3814 if(!strcasecmp(rptr->uplink, name))
3815 num++;
3816 rptr = rptr->next;
3817 }
3818 return num;
3819}
3820
3821void
3822show_route_downlinks(struct svccmd *cmd, struct route *route, struct userNode *user, char *name, char *prevpre, char *arrowchar, int reset)
3823{
3824 struct routeList *servPtr;
3825 struct server *sptr;
3826 int j;
3827 char pre[MAXLEN];
3828 char *nextpre;
3829 char *status;
3830 int num = 0;
3831 static int depth = 0;
3832
3833 if(reset)
3834 depth = 0;
3835
3836 nextpre = malloc(MAXLEN);
3837 strcpy(pre, prevpre);
3838
3839 sptr = GetServerH(name);
3840 if((servPtr = find_routeList_server(route, name))) {
3841 if(!sptr)
3842 status = " ";
3843 else if (!strcasecmp(sptr->uplink->name, servPtr->uplink))
3844 status = "X";
3845 else if(servPtr->secondaryuplink && !strcasecmp(sptr->name, servPtr->secondaryuplink))
3846 status = "/";
3847 else
3848 status = "!";
3849 reply("OSMSG_DOWNLINKS_FORMAT_A", pre, arrowchar, name, status);
3850 }
3851 else
3852 reply("OSMSG_DOWNLINKS_FORMAT_B", self->name);
3853 j = num_route_downlinks(route, name);
3854 servPtr = route->servers;
3855 while(servPtr) {
3856 if(!strcasecmp(servPtr->uplink, name)) {
3857 strcpy(nextpre, pre);
3858 if(depth++ > 0) {
3859 if(arrowchar[0] == '`')
3860 strcat(nextpre, " ");
3861 else
3862 strcat(nextpre, "| ");
3863 }
3864 if(j > ++num) {
3865 show_route_downlinks(cmd, route, user, servPtr->server, nextpre, "|", 0);
3866 }
3867 else {
3868 show_route_downlinks(cmd, route, user, servPtr->server, nextpre, "`", 0);
3869 }
3870 }
3871 servPtr = servPtr->next;
3872 }
3873 free(nextpre);
3874}
3875
3876int
3877show_route_map(struct route *route, struct userNode *user, struct svccmd *cmd)
3878{
3879 if(!route || !route->servers) {
3880 reply("OSMSG_ROUTELIST_EMPTY");
3881 return 0;
3882 }
3883
3884 char *serviceName = conf_get_data("server/hostname", RECDB_QSTRING);
3885 reply("OSMSG_ROUTELIST_AS_PLANNED");
3886 show_route_downlinks(cmd, route, user, serviceName, "", "`", 1);
3887 reply("OSMSG_MAP_CENTERED", route->centered ? "is" : "is not", route->maxdepth);
3888 return 1;
3889}
3890
3891static MODCMD_FUNC(cmd_routing_map)
3892{
3893 show_route_map(opserv_route, user, cmd);
3894 return 1;
3895}
3896
3897
3898
3899
3900/* End of auto routing functions *
3901 *********************************/
3902
d76ed9a9
AS
3903static MODCMD_FUNC(cmd_addbad)
3904{
3905 unsigned int arg, count;
3906 dict_iterator_t it;
3907 int bad_found, exempt_found;
3908
3909 /* Create the bad word if it doesn't exist. */
3910 bad_found = !opserv_add_bad_word(cmd, user, argv[1]);
3911
3912 /* Look for exception modifiers. */
3913 for (arg=2; arg<argc; arg++) {
3914 if (!irccasecmp(argv[arg], "except")) {
3915 reply("MSG_DEPRECATED_COMMAND", "addbad ... except", "addexempt");
3916 if (++arg > argc) {
3917 reply("MSG_MISSING_PARAMS", "except");
3918 break;
3919 }
3920 for (count = 0; (arg < argc) && IsChannelName(argv[arg]); arg++) {
3921 dict_find(opserv_exempt_channels, argv[arg], &exempt_found);
3922 if (!exempt_found) {
3923 dict_insert(opserv_exempt_channels, strdup(argv[arg]), NULL);
3924 count++;
3925 }
3926 }
3927 reply("OSMSG_ADDED_EXEMPTIONS", count);
3928 } else {
3929 reply("MSG_DEPRECATED_COMMAND", "addbad (with modifiers)", "addbad");
3930 reply("OSMSG_BAD_MODIFIER", argv[arg]);
3931 }
3932 }
3933
3934 /* Scan for existing channels that match the new bad word. */
3935 if (!bad_found) {
3936 for (it = dict_first(channels); it; it = iter_next(it)) {
3937 struct chanNode *channel = iter_data(it);
3938
3939 if (!opserv_bad_channel(channel->name))
3940 continue;
3941 channel->bad_channel = 1;
3942 if (channel->name[0] == '#')
3943 opserv_shutdown_channel(channel, "OSMSG_ILLEGAL_REASON");
3944 else {
3945 unsigned int nn;
3946 for (nn=0; nn<channel->members.used; nn++) {
3947 struct userNode *user = channel->members.list[nn]->user;
3948 DelUser(user, cmd->parent->bot, 1, "OSMSG_ILLEGAL_KILL_REASON");
3949 }
3950 }
3951 }
3952 }
3953
3954 return 1;
3955}
3956
3957static MODCMD_FUNC(cmd_delbad)
3958{
3959 dict_iterator_t it;
3960 unsigned int nn;
3961
3962 for (nn=0; nn<opserv_bad_words->used; nn++) {
3963 if (!irccasecmp(opserv_bad_words->list[nn], argv[1])) {
3964 string_list_delete(opserv_bad_words, nn);
3965 for (it = dict_first(channels); it; it = iter_next(it)) {
3966 channel = iter_data(it);
3967 if (irccasestr(channel->name, argv[1])
3968 && !opserv_bad_channel(channel->name)) {
3969 DelChannelUser(cmd->parent->bot, channel, "Channel name no longer contains a bad word.", 1);
3970 timeq_del(0, opserv_part_channel, channel, TIMEQ_IGNORE_WHEN);
3971 channel->bad_channel = 0;
3972 }
3973 }
3974 reply("OSMSG_REMOVED_BAD", argv[1]);
3975 return 1;
3976 }
3977 }
3978 reply("OSMSG_NOT_BAD_WORD", argv[1]);
3979 return 0;
3980}
3981
3982static MODCMD_FUNC(cmd_addexempt)
3983{
3984 const char *chanName;
3985
3986 if ((argc > 1) && IsChannelName(argv[1])) {
3987 chanName = argv[1];
3988 } else {
3989 reply("MSG_NOT_CHANNEL_NAME");
3990 OPSERV_SYNTAX();
3991 return 0;
3992 }
3993 dict_insert(opserv_exempt_channels, strdup(chanName), NULL);
3994 channel = GetChannel(chanName);
3995 if (channel) {
3996 if (channel->bad_channel) {
3997 DelChannelUser(cmd->parent->bot, channel, "Channel is now exempt from bad-word checking.", 1);
3998 timeq_del(0, opserv_part_channel, channel, TIMEQ_IGNORE_WHEN);
3999 }
4000 channel->bad_channel = 0;
4001 }
4002 reply("OSMSG_ADDED_EXEMPTION", chanName);
4003 return 1;
4004}
4005
4006static MODCMD_FUNC(cmd_delexempt)
4007{
4008 const char *chanName;
4009
4010 if ((argc > 1) && IsChannelName(argv[1])) {
4011 chanName = argv[1];
4012 } else {
4013 reply("MSG_NOT_CHANNEL_NAME");
4014 OPSERV_SYNTAX();
4015 return 0;
4016 }
4017 if (!dict_remove(opserv_exempt_channels, chanName)) {
4018 reply("OSMSG_NOT_EXEMPT", chanName);
4019 return 0;
4020 }
4021 reply("OSMSG_REMOVED_EXEMPTION", chanName);
4022 return 1;
4023}
4024
4025static void
4026opserv_expire_trusted_host(void *data)
4027{
4028 struct trusted_host *th = data;
4029 dict_remove(opserv_trusted_hosts, th->ipaddr);
4030}
4031
4032static void
4033opserv_add_trusted_host(const char *ipaddr, unsigned int limit, const char *issuer, time_t issued, time_t expires, const char *reason)
4034{
4035 struct trusted_host *th;
4036 th = calloc(1, sizeof(*th));
4037 if (!th)
4038 return;
4039 th->ipaddr = strdup(ipaddr);
4040 th->reason = reason ? strdup(reason) : NULL;
4041 th->issuer = issuer ? strdup(issuer) : NULL;
4042 th->issued = issued;
4043 th->limit = limit;
4044 th->expires = expires;
4045 dict_insert(opserv_trusted_hosts, th->ipaddr, th);
4046 if (th->expires)
4047 timeq_add(th->expires, opserv_expire_trusted_host, th);
4048}
4049
4050static void
4051free_trusted_host(void *data)
4052{
4053 struct trusted_host *th = data;
4054 free(th->ipaddr);
4055 free(th->reason);
4056 free(th->issuer);
4057 free(th);
4058}
4059
4060static MODCMD_FUNC(cmd_addtrust)
4061{
4062 unsigned long interval;
4063 char *reason, *tmp;
2f61d1d7 4064 irc_in_addr_t tmpaddr;
d76ed9a9
AS
4065 unsigned int count;
4066
4067 if (dict_find(opserv_trusted_hosts, argv[1], NULL)) {
4068 reply("OSMSG_ALREADY_TRUSTED", argv[1]);
4069 return 0;
4070 }
4071
2f61d1d7 4072 if (!irc_pton(&tmpaddr, NULL, argv[1])) {
d76ed9a9
AS
4073 reply("OSMSG_BAD_IP", argv[1]);
4074 return 0;
4075 }
4076
4077 count = strtoul(argv[2], &tmp, 10);
4078 if (*tmp != '\0') {
4079 reply("OSMSG_BAD_NUMBER", argv[2]);
4080 return 0;
4081 }
4082
4083 interval = ParseInterval(argv[3]);
4084 if (!interval && strcmp(argv[3], "0")) {
4085 reply("MSG_INVALID_DURATION", argv[3]);
4086 return 0;
4087 }
4088
4089 reason = unsplit_string(argv+4, argc-4, NULL);
4090 opserv_add_trusted_host(argv[1], count, user->handle_info->handle, now, interval ? (now + interval) : 0, reason);
4091 reply("OSMSG_ADDED_TRUSTED");
4092 return 1;
4093}
4094
4095static MODCMD_FUNC(cmd_edittrust)
4096{
4097 unsigned long interval;
4098 struct trusted_host *th;
4099 char *reason, *tmp;
4100 unsigned int count;
4101
4102 th = dict_find(opserv_trusted_hosts, argv[1], NULL);
4103 if (!th) {
4104 reply("OSMSG_NOT_TRUSTED", argv[1]);
4105 return 0;
4106 }
4107 count = strtoul(argv[2], &tmp, 10);
4108 if (!count || *tmp) {
4109 reply("OSMSG_BAD_NUMBER", argv[2]);
4110 return 0;
4111 }
4112 interval = ParseInterval(argv[3]);
4113 if (!interval && strcmp(argv[3], "0")) {
4114 reply("MSG_INVALID_DURATION", argv[3]);
4115 return 0;
4116 }
4117 reason = unsplit_string(argv+4, argc-4, NULL);
4118 if (th->expires)
4119 timeq_del(th->expires, opserv_expire_trusted_host, th, 0);
4120
4121 free(th->reason);
4122 th->reason = strdup(reason);
4123 free(th->issuer);
4124 th->issuer = strdup(user->handle_info->handle);
4125 th->issued = now;
4126 th->limit = count;
4127 if (interval) {
4128 th->expires = now + interval;
4129 timeq_add(th->expires, opserv_expire_trusted_host, th);
4130 } else
4131 th->expires = 0;
4132 reply("OSMSG_UPDATED_TRUSTED", th->ipaddr);
4133 return 1;
4134}
4135
4136static MODCMD_FUNC(cmd_deltrust)
4137{
4138 unsigned int n;
4139
4140 for (n=1; n<argc; n++) {
4141 struct trusted_host *th = dict_find(opserv_trusted_hosts, argv[n], NULL);
4142 if (!th)
4143 continue;
4144 if (th->expires)
4145 timeq_del(th->expires, opserv_expire_trusted_host, th, 0);
4146 dict_remove(opserv_trusted_hosts, argv[n]);
4147 }
4148 reply("OSMSG_REMOVED_TRUSTED");
4149 return 1;
4150}
4151
4152/* This doesn't use dict_t because it's a little simpler to open-code the
4153 * comparisons (and simpler arg-passing for the ADD subcommand).
4154 */
4155static MODCMD_FUNC(cmd_clone)
4156{
4157 int i;
4158 struct userNode *clone;
4159
4160 clone = GetUserH(argv[2]);
4161 if (!irccasecmp(argv[1], "ADD")) {
4162 char *userinfo;
4163 char ident[USERLEN+1];
4164
258d1427
AS
4165 if (argc < 5) {
4166 reply("MSG_MISSING_PARAMS", argv[1]);
4167 OPSERV_SYNTAX();
4168 return 0;
4169 }
4170 if (clone) {
4171 reply("OSMSG_CLONE_EXISTS", argv[2]);
4172 return 0;
4173 }
4174 userinfo = unsplit_string(argv+4, argc-4, NULL);
4175 for (i=0; argv[3][i] && (i<USERLEN); i++) {
4176 if (argv[3][i] == '@') {
4177 ident[i++] = 0;
4178 break;
4179 } else {
d76ed9a9
AS
4180 ident[i] = argv[3][i];
4181 }
258d1427
AS
4182 }
4183 if (!argv[3][i] || (i==USERLEN)) {
4184 reply("OSMSG_NOT_A_HOSTMASK");
4185 return 0;
4186 }
4187 if (!(clone = AddClone(argv[2], ident, argv[3]+i, userinfo))) {
d76ed9a9
AS
4188 reply("OSMSG_CLONE_FAILED", argv[2]);
4189 return 0;
4190 }
4191 reply("OSMSG_CLONE_ADDED", clone->nick);
258d1427 4192 return 1;
d76ed9a9
AS
4193 }
4194 if (!clone) {
258d1427
AS
4195 reply("MSG_NICK_UNKNOWN", argv[2]);
4196 return 0;
d76ed9a9
AS
4197 }
4198 if (clone->uplink != self || IsService(clone)) {
258d1427
AS
4199 reply("OSMSG_NOT_A_CLONE", clone->nick);
4200 return 0;
d76ed9a9
AS
4201 }
4202 if (!irccasecmp(argv[1], "REMOVE")) {
258d1427
AS
4203 const char *reason;
4204 if (argc > 3) {
4205 reason = unsplit_string(argv+3, argc-3, NULL);
4206 } else {
4207 char *tmp;
4208 tmp = alloca(strlen(clone->nick) + strlen(OSMSG_PART_REASON));
4209 sprintf(tmp, OSMSG_PART_REASON, clone->nick);
4210 reason = tmp;
4211 }
4212 DelUser(clone, NULL, 1, reason);
4213 reply("OSMSG_CLONE_REMOVED", argv[2]);
4214 return 1;
d76ed9a9
AS
4215 }
4216 if (argc < 4) {
258d1427
AS
4217 reply("MSG_MISSING_PARAMS", argv[1]);
4218 OPSERV_SYNTAX();
4219 return 0;
d76ed9a9
AS
4220 }
4221 channel = GetChannel(argv[3]);
4222 if (!irccasecmp(argv[1], "JOIN")) {
258d1427
AS
4223 if (!channel
4224 && !(channel = AddChannel(argv[3], now, NULL, NULL, NULL))) {
4225 reply("MSG_CHANNEL_UNKNOWN", argv[3]);
4226 return 0;
4227 }
4228 AddChannelUser(clone, channel);
4229 reply("OSMSG_CLONE_JOINED", clone->nick, channel->name);
4230 return 1;
d76ed9a9
AS
4231 }
4232 if (!irccasecmp(argv[1], "PART")) {
258d1427
AS
4233 if (!channel) {
4234 reply("MSG_CHANNEL_UNKNOWN", argv[3]);
4235 return 0;
4236 }
4237 if (!GetUserMode(channel, clone)) {
4238 reply("OSMSG_NOT_ON_CHANNEL", clone->nick, channel->name);
4239 return 0;
4240 }
4241 reply("OSMSG_CLONE_PARTED", clone->nick, channel->name);
4242 DelChannelUser(clone, channel, "Leaving.", 0);
4243 return 1;
d76ed9a9
AS
4244 }
4245 if (!irccasecmp(argv[1], "OP")) {
4246 struct mod_chanmode change;
258d1427
AS
4247 if (!channel) {
4248 reply("MSG_CHANNEL_UNKNOWN", argv[3]);
4249 return 0;
4250 }
d76ed9a9
AS
4251 mod_chanmode_init(&change);
4252 change.argc = 1;
4253 change.args[0].mode = MODE_CHANOP;
a32da4c7 4254 change.args[0].u.member = GetUserMode(channel, clone);
4255 if (!change.args[0].u.member) {
d76ed9a9
AS
4256 reply("OSMSG_NOT_ON_CHANNEL", clone->nick, channel->name);
4257 return 0;
258d1427 4258 }
d76ed9a9 4259 modcmd_chanmode_announce(&change);
258d1427
AS
4260 reply("OSMSG_OPS_GIVEN", channel->name, clone->nick);
4261 return 1;
d76ed9a9 4262 }
55342ce8 4263 if (!irccasecmp(argv[1], "HOP")) {
4264 struct mod_chanmode change;
4265 if (!channel) {
4266 reply("MSG_CHANNEL_UNKNOWN", argv[3]);
4267 return 0;
4268 }
4269 mod_chanmode_init(&change);
4270 change.argc = 1;
4271 change.args[0].mode = MODE_HALFOP;
11408ce4
AS
4272 change.args[0].u.member = GetUserMode(channel, clone);
4273 if (!change.args[0].u.member) {
55342ce8 4274 reply("OSMSG_NOT_ON_CHANNEL", clone->nick, channel->name);
4275 return 0;
4276 }
4277 modcmd_chanmode_announce(&change);
4278 reply("OSMSG_HOPS_GIVEN", channel->name, clone->nick);
4279 return 1;
4280 }
d76ed9a9 4281 if (argc < 5) {
258d1427
AS
4282 reply("MSG_MISSING_PARAMS", argv[1]);
4283 OPSERV_SYNTAX();
4284 return 0;
d76ed9a9
AS
4285 }
4286 if (!irccasecmp(argv[1], "SAY")) {
258d1427
AS
4287 char *text = unsplit_string(argv+4, argc-4, NULL);
4288 irc_privmsg(clone, argv[3], text);
4289 reply("OSMSG_CLONE_SAID", clone->nick, argv[3]);
4290 return 1;
d76ed9a9
AS
4291 }
4292 reply("OSMSG_UNKNOWN_SUBCOMMAND", argv[1], argv[0]);
4293 return 0;
4294}
4295
4296static struct helpfile_expansion
4297opserv_help_expand(const char *variable)
4298{
4299 extern struct userNode *message_source;
4300 struct helpfile_expansion exp;
4301 struct service *service;
4302 struct svccmd *cmd;
4303 dict_iterator_t it;
4304 int row;
4305 unsigned int level;
4306
4307 if (!(service = service_find(message_source->nick))) {
4308 exp.type = HF_STRING;
4309 exp.value.str = NULL;
4310 } else if (!irccasecmp(variable, "index")) {
4311 exp.type = HF_TABLE;
4312 exp.value.table.length = 1;
4313 exp.value.table.width = 2;
4314 exp.value.table.flags = TABLE_REPEAT_HEADERS | TABLE_REPEAT_ROWS;
4315 exp.value.table.contents = calloc(dict_size(service->commands)+1, sizeof(char**));
4316 exp.value.table.contents[0] = calloc(exp.value.table.width, sizeof(char*));
4317 exp.value.table.contents[0][0] = "Command";
4318 exp.value.table.contents[0][1] = "Level";
4319 for (it=dict_first(service->commands); it; it=iter_next(it)) {
4320 cmd = iter_data(it);
4321 row = exp.value.table.length++;
4322 exp.value.table.contents[row] = calloc(exp.value.table.width, sizeof(char*));
4323 exp.value.table.contents[row][0] = iter_key(it);
4324 level = cmd->min_opserv_level;
4325 if (!level_strings[level]) {
4326 level_strings[level] = malloc(16);
4327 snprintf(level_strings[level], 16, "%3d", level);
4328 }
4329 exp.value.table.contents[row][1] = level_strings[level];
4330 }
4331 } else if (!strncasecmp(variable, "level", 5)) {
4332 cmd = dict_find(service->commands, variable+6, NULL);
4333 exp.type = HF_STRING;
4334 if (cmd) {
4335 level = cmd->min_opserv_level;
4336 exp.value.str = malloc(16);
4337 snprintf(exp.value.str, 16, "%3d", level);
4338 } else {
4339 exp.value.str = NULL;
4340 }
4341 } else {
4342 exp.type = HF_STRING;
4343 exp.value.str = NULL;
4344 }
4345 return exp;
4346}
4347
4348struct modcmd *
4349opserv_define_func(const char *name, modcmd_func_t *func, int min_level, int reqchan, int min_argc)
4350{
4351 char buf[16], *flags = NULL;
4352 unsigned int iflags = 0;
4353 sprintf(buf, "%d", min_level);
4354 switch (reqchan) {
4355 case 1: flags = "+acceptchan"; break;
4356 case 3: flags = "+acceptpluschan"; /* fall through */
4357 case 2: iflags = MODCMD_REQUIRE_CHANNEL; break;
4358 }
4359 if (flags) {
4360 return modcmd_register(opserv_module, name, func, min_argc, iflags, "level", buf, "flags", flags, "flags", "+oper", NULL);
4361 } else {
4362 return modcmd_register(opserv_module, name, func, min_argc, iflags, "level", buf, "flags", "+oper", NULL);
4363 }
4364}
4365
4366int add_reserved(const char *key, void *data, void *extra)
4367{
7637f48f 4368 struct chanNode *chan;
d76ed9a9
AS
4369 struct record_data *rd = data;
4370 const char *ident, *hostname, *desc;
7637f48f 4371 unsigned int i;
d76ed9a9
AS
4372 struct userNode *reserve;
4373 ident = database_get_data(rd->d.object, KEY_IDENT, RECDB_QSTRING);
4374 if (!ident) {
258d1427
AS
4375 log_module(OS_LOG, LOG_ERROR, "Missing ident for reserve of %s", key);
4376 return 0;
d76ed9a9
AS
4377 }
4378 hostname = database_get_data(rd->d.object, KEY_HOSTNAME, RECDB_QSTRING);
4379 if (!hostname) {
258d1427
AS
4380 log_module(OS_LOG, LOG_ERROR, "Missing hostname for reserve of %s", key);
4381 return 0;
d76ed9a9
AS
4382 }
4383 desc = database_get_data(rd->d.object, KEY_DESC, RECDB_QSTRING);
4384 if (!desc) {
258d1427
AS
4385 log_module(OS_LOG, LOG_ERROR, "Missing description for reserve of %s", key);
4386 return 0;
d76ed9a9
AS
4387 }
4388 if ((reserve = AddClone(key, ident, hostname, desc))) {
4389 reserve->modes |= FLAGS_PERSISTENT;
4390 dict_insert(extra, reserve->nick, reserve);
4391 }
7637f48f 4392
4393 if (autojoin_channels && reserve) {
4394 for (i = 0; i < autojoin_channels->used; i++) {
4395 chan = AddChannel(autojoin_channels->list[i], now, "+nt", NULL, NULL);
4396 AddChannelUser(reserve, chan)->modes |= MODE_VOICE;
4397 }
4398 }
4399
d76ed9a9
AS
4400 return 0;
4401}
4402
4403static unsigned int
4404foreach_matching_user(const char *hostmask, discrim_search_func func, void *extra)
4405{
4406 discrim_t discrim;
4407 char *dupmask;
4408 unsigned int matched;
4409
4410 if (!self->uplink) return 0;
4411 discrim = calloc(1, sizeof(*discrim));
4412 discrim->limit = dict_size(clients);
4413 discrim->max_level = ~0;
4414 discrim->max_ts = now;
4415 discrim->max_channels = INT_MAX;
4416 discrim->authed = -1;
4417 discrim->info_space = -1;
63665495 4418 discrim->intra_scmp = 0;
4419 discrim->intra_dcmp = 0;
27eaa617 4420 discrim->use_regex = 0;
1c5f6697 4421 discrim->silent = 0;
d76ed9a9
AS
4422 dupmask = strdup(hostmask);
4423 if (split_ircmask(dupmask, &discrim->mask_nick, &discrim->mask_ident, &discrim->mask_host)) {
2f61d1d7 4424 if (!irc_pton(&discrim->ip_mask, &discrim->ip_mask_bits, discrim->mask_host))
4425 discrim->ip_mask_bits = 0;
d76ed9a9
AS
4426 matched = opserv_discrim_search(discrim, func, extra);
4427 } else {
258d1427 4428 log_module(OS_LOG, LOG_ERROR, "Couldn't split IRC mask for gag %s!", hostmask);
d76ed9a9
AS
4429 matched = 0;
4430 }
4431 free(discrim);
4432 free(dupmask);
4433 return matched;
4434}
4435
4436static unsigned int
4437gag_free(struct gag_entry *gag)
4438{
4439 unsigned int ungagged;
4440
4441 /* Remove from gag list */
4442 if (gagList == gag) {
4443 gagList = gag->next;
4444 } else {
4445 struct gag_entry *prev;
4446 for (prev = gagList; prev->next != gag; prev = prev->next) ;
4447 prev->next = gag->next;
4448 }
4449
4450 ungagged = foreach_matching_user(gag->mask, ungag_helper_func, NULL);
4451
4452 /* Deallocate storage */
4453 free(gag->reason);
4454 free(gag->owner);
4455 free(gag->mask);
4456 free(gag);
4457
4458 return ungagged;
4459}
4460
4461static void
4462gag_expire(void *data)
4463{
4464 gag_free(data);
4465}
4466
4467unsigned int
4468gag_create(const char *mask, const char *owner, const char *reason, time_t expires)
4469{
4470 struct gag_entry *gag;
4471
4472 /* Create gag and put it into linked list */
4473 gag = calloc(1, sizeof(*gag));
4474 gag->mask = strdup(mask);
4475 gag->owner = strdup(owner ? owner : "<unknown>");
4476 gag->reason = strdup(reason ? reason : "<unknown>");
4477 gag->expires = expires;
4478 if (gag->expires)
4479 timeq_add(gag->expires, gag_expire, gag);
4480 gag->next = gagList;
4481 gagList = gag;
4482
4483 /* If we're linked, see if who the gag applies to */
4484 return foreach_matching_user(mask, gag_helper_func, gag);
4485}
4486
4487static int
4488add_gag_helper(const char *key, void *data, UNUSED_ARG(void *extra))
4489{
4490 struct record_data *rd = data;
4491 char *owner, *reason, *expstr;
4492 time_t expires;
4493
4494 owner = database_get_data(rd->d.object, KEY_OWNER, RECDB_QSTRING);
4495 reason = database_get_data(rd->d.object, KEY_REASON, RECDB_QSTRING);
4496 expstr = database_get_data(rd->d.object, KEY_EXPIRES, RECDB_QSTRING);
4497 expires = expstr ? strtoul(expstr, NULL, 0) : 0;
4498 gag_create(key, owner, reason, expires);
4499
4500 return 0;
4501}
4502
4503static struct opserv_user_alert *
4504opserv_add_user_alert(struct userNode *req, const char *name, opserv_alert_reaction reaction, const char *text_discrim)
4505{
4506 unsigned int wordc;
4507 char *wordv[MAXNUMPARAMS], *discrim_copy;
4508 struct opserv_user_alert *alert;
4509 char *name_dup;
4510
4511 if (dict_find(opserv_user_alerts, name, NULL)) {
258d1427
AS
4512 send_message(req, opserv, "OSMSG_ALERT_EXISTS", name);
4513 return NULL;
d76ed9a9
AS
4514 }
4515 alert = malloc(sizeof(*alert));
4516 alert->owner = strdup(req->handle_info ? req->handle_info->handle : req->nick);
4517 alert->text_discrim = strdup(text_discrim);
4518 discrim_copy = strdup(text_discrim); /* save a copy of the discrim */
4519 wordc = split_line(discrim_copy, false, ArrayLength(wordv), wordv);
258d1427 4520 alert->discrim = opserv_discrim_create(req, opserv, wordc, wordv, 0);
39c1a4ef 4521 if (!alert->discrim || (reaction==REACT_SVSJOIN && !alert->discrim->chantarget) ||
4522 (reaction==REACT_SVSPART && !alert->discrim->chantarget)) {
d76ed9a9
AS
4523 free(alert->text_discrim);
4524 free(discrim_copy);
4525 free(alert);
4526 return NULL;
4527 }
4528 alert->split_discrim = discrim_copy;
4529 name_dup = strdup(name);
4530 if (!alert->discrim->reason)
4531 alert->discrim->reason = strdup(name);
4532 alert->reaction = reaction;
4533 dict_insert(opserv_user_alerts, name_dup, alert);
697f4c9a 4534 /* Stick the alert into the appropriate additional alert dict(s).
4535 * For channel alerts, we only use channels and min_channels;
4536 * max_channels would have to be checked on /part, which we do not
4537 * yet do, and which seems of questionable value.
4538 */
4539 if (alert->discrim->channel || alert->discrim->min_channels)
d76ed9a9 4540 dict_insert(opserv_channel_alerts, name_dup, alert);
697f4c9a 4541 if (alert->discrim->mask_nick)
d76ed9a9
AS
4542 dict_insert(opserv_nick_based_alerts, name_dup, alert);
4543 return alert;
4544}
4545
de9510bc 4546/*
d76ed9a9
AS
4547static int
4548add_chan_warn(const char *key, void *data, UNUSED_ARG(void *extra))
4549{
4550 struct record_data *rd = data;
4551 char *reason = GET_RECORD_QSTRING(rd);
4552
de9510bc 4553 * i hope this can't happen *
d76ed9a9
AS
4554 if (!reason)
4555 reason = "No Reason";
4556
4557 dict_insert(opserv_chan_warn, strdup(key), strdup(reason));
4558 return 0;
4559}
de9510bc 4560*/
d76ed9a9 4561
47956fc5 4562
d76ed9a9
AS
4563static int
4564add_user_alert(const char *key, void *data, UNUSED_ARG(void *extra))
4565{
4566 dict_t alert_dict;
4567 const char *discrim, *react, *owner;
4568 opserv_alert_reaction reaction;
4569 struct opserv_user_alert *alert;
4570
4571 if (!(alert_dict = GET_RECORD_OBJECT((struct record_data *)data))) {
4572 log_module(OS_LOG, LOG_ERROR, "Bad type (not a record) for alert %s.", key);
4573 return 1;
4574 }
4575 discrim = database_get_data(alert_dict, KEY_DISCRIM, RECDB_QSTRING);
4576 react = database_get_data(alert_dict, KEY_REACTION, RECDB_QSTRING);
4577 if (!react || !irccasecmp(react, "notice"))
4578 reaction = REACT_NOTICE;
4579 else if (!irccasecmp(react, "kill"))
4580 reaction = REACT_KILL;
1c5f6697 4581 /*
9a75756e 4582 else if (!irccasecmp(react, "silent"))
4583 reaction = REACT_SILENT;
1c5f6697 4584 */
d76ed9a9
AS
4585 else if (!irccasecmp(react, "gline"))
4586 reaction = REACT_GLINE;
ec311f39 4587 else if (!irccasecmp(react, "track"))
4588 reaction = REACT_TRACK;
d914d1cb 4589 else if (!irccasecmp(react, "shun"))
4590 reaction = REACT_SHUN;
c408f18a
AS
4591 else if (!irccasecmp(react, "svsjoin"))
4592 reaction = REACT_SVSJOIN;
39c1a4ef 4593 else if (!irccasecmp(react, "svspart"))
4594 reaction = REACT_SVSPART;
0e08a8e0
AS
4595 else if (!irccasecmp(react, "version"))
4596 reaction = REACT_VERSION;
d76ed9a9
AS
4597 else {
4598 log_module(OS_LOG, LOG_ERROR, "Invalid reaction %s for alert %s.", react, key);
4599 return 0;
4600 }
4601 alert = opserv_add_user_alert(opserv, key, reaction, discrim);
4602 if (!alert) {
4603 log_module(OS_LOG, LOG_ERROR, "Unable to create alert %s from database.", key);
4604 return 0;
4605 }
4606 owner = database_get_data(alert_dict, KEY_OWNER, RECDB_QSTRING);
4607 free(alert->owner);
4608 alert->owner = strdup(owner ? owner : "<unknown>");
4609 return 0;
4610}
4611
4612static int
4613trusted_host_read(const char *host, void *data, UNUSED_ARG(void *extra))
4614{
4615 struct record_data *rd = data;
4616 const char *limit, *str, *reason, *issuer;
4617 time_t issued, expires;
4618
4619 if (rd->type == RECDB_QSTRING) {
4620 /* old style host by itself */
4621 limit = GET_RECORD_QSTRING(rd);
4622 issued = 0;
4623 issuer = NULL;
4624 expires = 0;
4625 reason = NULL;
4626 } else if (rd->type == RECDB_OBJECT) {
4627 dict_t obj = GET_RECORD_OBJECT(rd);
4628 /* new style structure */
4629 limit = database_get_data(obj, KEY_LIMIT, RECDB_QSTRING);
4630 str = database_get_data(obj, KEY_EXPIRES, RECDB_QSTRING);
4631 expires = str ? ParseInterval(str) : 0;
4632 reason = database_get_data(obj, KEY_REASON, RECDB_QSTRING);
4633 issuer = database_get_data(obj, KEY_ISSUER, RECDB_QSTRING);
4634 str = database_get_data(obj, KEY_ISSUED, RECDB_QSTRING);
4635 issued = str ? ParseInterval(str) : 0;
4636 } else
4637 return 0;
4638
4639 if (expires && (expires < now))
4640 return 0;
4641 opserv_add_trusted_host(host, (limit ? strtoul(limit, NULL, 0) : 0), issuer, issued, expires, reason);
4642 return 0;
4643}
4644
47956fc5
AS
4645static int
4646add_routing_plan_server(const char *name, void *data, void *rp)
4647{
4648 struct record_data *rd = data;
4649 const char *uplink, *portstr, *karma, *second, *offline;
4650
4651 dict_t obj = GET_RECORD_OBJECT(rd);
4652 if(rd->type == RECDB_OBJECT) {
4653 uplink = database_get_data(obj, KEY_UPLINK, RECDB_QSTRING);
4654 second = database_get_data(obj, KEY_SECOND, RECDB_QSTRING);
4655 portstr = database_get_data(obj, KEY_PORT, RECDB_QSTRING);
4656 karma = database_get_data(obj, KEY_KARMA, RECDB_QSTRING);
4657 offline = database_get_data(obj, KEY_OFFLINE, RECDB_QSTRING);
4658 /* create routing plan server named key, with uplink uplink. */
4659 opserv_routing_plan_add_server(rp, name, uplink, portstr ? atoi(portstr) : 0,
4660 karma ? atoi(karma) : KARMA_DEFAULT, second,
4661 offline ? atoi(offline) : 0);
4662 }
4663 return 0;
4664
4665}
4666
4667static int
4668routing_plan_set_option(const char *name, void *data, UNUSED_ARG(void *extra))
4669{
4670 struct record_data *rd = data;
4671 if(rd->type == RECDB_QSTRING)
4672 {
4673 char *value = GET_RECORD_QSTRING(rd);
4674 dict_insert(opserv_routing_plan_options, strdup(name), strdup(value));
4675 }
4676 return 0;
4677}
4678
4679static int
4680add_routing_plan(const char *name, void *data, UNUSED_ARG(void *extra))
4681{
4682 struct record_data *rd = data;
4683 struct routingPlan *rp;
4684
4685 if(rd->type == RECDB_OBJECT) {
4686 dict_t obj = GET_RECORD_OBJECT(rd);
4687 rp = opserv_add_routing_plan(name);
4688 dict_foreach(obj, add_routing_plan_server, rp);
4689 }
4690 return 0;
4691}
4692
d76ed9a9
AS
4693static int
4694opserv_saxdb_read(struct dict *conf_db)
4695{
4696 dict_t object;
4697 struct record_data *rd;
4698 dict_iterator_t it;
4699 unsigned int nn;
4700
4701 if ((object = database_get_data(conf_db, KEY_RESERVES, RECDB_OBJECT)))
4702 dict_foreach(object, add_reserved, opserv_reserved_nick_dict);
4703 if ((rd = database_get_path(conf_db, KEY_BAD_WORDS))) {
4704 switch (rd->type) {
4705 case RECDB_STRING_LIST:
4706 /* Add words one by one just in case there are overlaps from an old DB. */
4707 for (nn=0; nn<rd->d.slist->used; ++nn)
4708 opserv_add_bad_word(NULL, NULL, rd->d.slist->list[nn]);
4709 break;
4710 case RECDB_OBJECT:
4711 for (it=dict_first(rd->d.object); it; it=iter_next(it)) {
4712 opserv_add_bad_word(NULL, NULL, iter_key(it));
4713 rd = iter_data(it);
4714 if (rd->type == RECDB_STRING_LIST)
4715 for (nn=0; nn<rd->d.slist->used; nn++)
4716 dict_insert(opserv_exempt_channels, strdup(rd->d.slist->list[nn]), NULL);
4717 }
4718 break;
4719 default:
4720 /* do nothing */;
4721 }
4722 }
4723 if ((rd = database_get_path(conf_db, KEY_EXEMPT_CHANNELS))
4724 && (rd->type == RECDB_STRING_LIST)) {
4725 for (nn=0; nn<rd->d.slist->used; ++nn)
4726 dict_insert(opserv_exempt_channels, strdup(rd->d.slist->list[nn]), NULL);
4727 }
4728 if ((object = database_get_data(conf_db, KEY_MAX_CLIENTS, RECDB_OBJECT))) {
4729 char *str;
4730 if ((str = database_get_data(object, KEY_MAX, RECDB_QSTRING)))
4731 max_clients = atoi(str);
4732 if ((str = database_get_data(object, KEY_TIME, RECDB_QSTRING)))
4733 max_clients_time = atoi(str);
4734 }
4735 if ((object = database_get_data(conf_db, KEY_TRUSTED_HOSTS, RECDB_OBJECT)))
4736 dict_foreach(object, trusted_host_read, opserv_trusted_hosts);
4737 if ((object = database_get_data(conf_db, KEY_GAGS, RECDB_OBJECT)))
4738 dict_foreach(object, add_gag_helper, NULL);
4739 if ((object = database_get_data(conf_db, KEY_ALERTS, RECDB_OBJECT)))
4740 dict_foreach(object, add_user_alert, NULL);
de9510bc 4741/*
d76ed9a9
AS
4742 if ((object = database_get_data(conf_db, KEY_WARN, RECDB_OBJECT)))
4743 dict_foreach(object, add_chan_warn, NULL);
de9510bc 4744*/
47956fc5
AS
4745
4746 if ((object = database_get_data(conf_db, KEY_ROUTINGPLAN, RECDB_OBJECT)))
4747 dict_foreach(object, add_routing_plan, NULL);
4748
4749 if ((object = database_get_data(conf_db, KEY_ROUTINGPLAN_OPTIONS, RECDB_OBJECT)))
4750 dict_foreach(object, routing_plan_set_option, NULL);
4751
d76ed9a9
AS
4752 return 0;
4753}
4754
4755static int
4756opserv_saxdb_write(struct saxdb_context *ctx)
4757{
4758 struct string_list *slist;
4759 dict_iterator_t it;
4760
4761 /* reserved nicks */
4762 if (dict_size(opserv_reserved_nick_dict)) {
4763 saxdb_start_record(ctx, KEY_RESERVES, 1);
4764 for (it = dict_first(opserv_reserved_nick_dict); it; it = iter_next(it)) {
4765 struct userNode *user = iter_data(it);
4766 if (!IsPersistent(user)) continue;
4767 saxdb_start_record(ctx, iter_key(it), 0);
4768 saxdb_write_string(ctx, KEY_IDENT, user->ident);
4769 saxdb_write_string(ctx, KEY_HOSTNAME, user->hostname);
4770 saxdb_write_string(ctx, KEY_DESC, user->info);
4771 saxdb_end_record(ctx);
4772 }
4773 saxdb_end_record(ctx);
4774 }
4775 /* bad word set */
4776 if (opserv_bad_words->used) {
4777 saxdb_write_string_list(ctx, KEY_BAD_WORDS, opserv_bad_words);
4778 }
47956fc5
AS
4779 /* routing plan options */
4780 if (dict_size(opserv_routing_plan_options)) {
4781 saxdb_start_record(ctx, KEY_ROUTINGPLAN_OPTIONS, 1);
4782 for(it = dict_first(opserv_routing_plan_options); it; it = iter_next(it)) {
4783 saxdb_write_string(ctx, iter_key(it), iter_data(it));
4784 }
4785 saxdb_end_record(ctx);
4786 }
4787 /* routing plans */
4788 if (dict_size(opserv_routing_plans)) {
4789 dict_iterator_t svrit;
4790 struct routingPlan *rp;
4791 struct routingPlanServer *rps;
4792 saxdb_start_record(ctx, KEY_ROUTINGPLAN, 1);
4793 for (it = dict_first(opserv_routing_plans); it; it = iter_next(it)) {
4794 rp = iter_data(it);
4795 saxdb_start_record(ctx, iter_key(it), 0);
4796 for(svrit = dict_first(rp->servers); svrit; svrit = iter_next(svrit)) {
4797 char buf[MAXLEN];
4798 rps = iter_data(svrit);
4799 saxdb_start_record(ctx, iter_key(svrit), 0);
4800 saxdb_write_string(ctx, KEY_UPLINK, rps->uplink);
4801 if(rps->secondaryuplink)
4802 saxdb_write_string(ctx, KEY_SECOND, rps->secondaryuplink);
4803 sprintf(buf, "%d", rps->port);
4804 saxdb_write_string(ctx, KEY_PORT, buf);
4805 sprintf(buf, "%d", rps->karma);
4806 saxdb_write_string(ctx, KEY_KARMA, buf);
4807 sprintf(buf, "%d", rps->offline);
4808 saxdb_write_string(ctx, KEY_OFFLINE, buf);
4809 saxdb_end_record(ctx);
4810 }
4811 saxdb_end_record(ctx);
4812 }
4813 saxdb_end_record(ctx);
4814 }
d76ed9a9
AS
4815 /* insert exempt channel names */
4816 if (dict_size(opserv_exempt_channels)) {
4817 slist = alloc_string_list(dict_size(opserv_exempt_channels));
4818 for (it=dict_first(opserv_exempt_channels); it; it=iter_next(it)) {
4819 string_list_append(slist, strdup(iter_key(it)));
4820 }
4821 saxdb_write_string_list(ctx, KEY_EXEMPT_CHANNELS, slist);
4822 free_string_list(slist);
4823 }
4824 /* trusted hosts takes a little more work */
4825 if (dict_size(opserv_trusted_hosts)) {
4826 saxdb_start_record(ctx, KEY_TRUSTED_HOSTS, 1);
4827 for (it = dict_first(opserv_trusted_hosts); it; it = iter_next(it)) {
4828 struct trusted_host *th = iter_data(it);
4829 saxdb_start_record(ctx, iter_key(it), 0);
4830 if (th->limit) saxdb_write_int(ctx, KEY_LIMIT, th->limit);
4831 if (th->expires) saxdb_write_int(ctx, KEY_EXPIRES, th->expires);
4832 if (th->issued) saxdb_write_int(ctx, KEY_ISSUED, th->issued);
4833 if (th->issuer) saxdb_write_string(ctx, KEY_ISSUER, th->issuer);
4834 if (th->reason) saxdb_write_string(ctx, KEY_REASON, th->reason);
4835 saxdb_end_record(ctx);
4836 }
4837 saxdb_end_record(ctx);
4838 }
4839 /* gags */
4840 if (gagList) {
4841 struct gag_entry *gag;
4842 saxdb_start_record(ctx, KEY_GAGS, 1);
4843 for (gag = gagList; gag; gag = gag->next) {
4844 saxdb_start_record(ctx, gag->mask, 0);
4845 saxdb_write_string(ctx, KEY_OWNER, gag->owner);
4846 saxdb_write_string(ctx, KEY_REASON, gag->reason);
4847 if (gag->expires) saxdb_write_int(ctx, KEY_EXPIRES, gag->expires);
4848 saxdb_end_record(ctx);
4849 }
4850 saxdb_end_record(ctx);
4851 }
4852 /* channel warnings */
de9510bc 4853 /*
d76ed9a9
AS
4854 if (dict_size(opserv_chan_warn)) {
4855 saxdb_start_record(ctx, KEY_WARN, 0);
4856 for (it = dict_first(opserv_chan_warn); it; it = iter_next(it)) {
4857 saxdb_write_string(ctx, iter_key(it), iter_data(it));
4858 }
4859 saxdb_end_record(ctx);
4860 }
de9510bc 4861 */
d76ed9a9
AS
4862 /* alerts */
4863 if (dict_size(opserv_user_alerts)) {
4864 saxdb_start_record(ctx, KEY_ALERTS, 1);
4865 for (it = dict_first(opserv_user_alerts); it; it = iter_next(it)) {
4866 struct opserv_user_alert *alert = iter_data(it);
4867 const char *reaction;
4868 saxdb_start_record(ctx, iter_key(it), 0);
4869 saxdb_write_string(ctx, KEY_DISCRIM, alert->text_discrim);
4870 saxdb_write_string(ctx, KEY_OWNER, alert->owner);
4871 switch (alert->reaction) {
4872 case REACT_NOTICE: reaction = "notice"; break;
4873 case REACT_KILL: reaction = "kill"; break;
1c5f6697 4874// case REACT_SILENT: reaction = "silent"; break;
d76ed9a9 4875 case REACT_GLINE: reaction = "gline"; break;
ec311f39 4876 case REACT_TRACK: reaction = "track"; break;
d914d1cb 4877 case REACT_SHUN: reaction = "shun"; break;
c408f18a 4878 case REACT_SVSJOIN: reaction = "svsjoin"; break;
39c1a4ef 4879 case REACT_SVSPART: reaction = "svspart"; break;
0e08a8e0 4880 case REACT_VERSION: reaction = "version"; break;
d76ed9a9
AS
4881 default:
4882 reaction = NULL;
4883 log_module(OS_LOG, LOG_ERROR, "Invalid reaction type %d for alert %s (while writing database).", alert->reaction, iter_key(it));
4884 break;
4885 }
4886 if (reaction) saxdb_write_string(ctx, KEY_REACTION, reaction);
4887 saxdb_end_record(ctx);
4888 }
4889 saxdb_end_record(ctx);
4890 }
4891 /* max clients */
4892 saxdb_start_record(ctx, KEY_MAX_CLIENTS, 0);
4893 saxdb_write_int(ctx, KEY_MAX, max_clients);
4894 saxdb_write_int(ctx, KEY_TIME, max_clients_time);
4895 saxdb_end_record(ctx);
4896 return 0;
4897}
4898
4899static int
4900query_keys_helper(const char *key, UNUSED_ARG(void *data), void *extra)
4901{
4902 send_message_type(4, extra, opserv, "$b%s$b", key);
4903 return 0;
4904}
4905
4906static MODCMD_FUNC(cmd_query)
4907{
4908 struct record_data *rd;
4909 unsigned int i;
4910 char *nodename;
4911
4912 if (argc < 2) {
258d1427
AS
4913 reply("OSMSG_OPTION_ROOT");
4914 conf_enum_root(query_keys_helper, user);
4915 return 1;
d76ed9a9
AS
4916 }
4917
4918 nodename = unsplit_string(argv+1, argc-1, NULL);
4919 if (!(rd = conf_get_node(nodename))) {
258d1427
AS
4920 reply("OSMSG_UNKNOWN_OPTION", nodename);
4921 return 0;
d76ed9a9
AS
4922 }
4923
4924 if (rd->type == RECDB_QSTRING)
258d1427 4925 reply("OSMSG_OPTION_IS", nodename, rd->d.qstring);
d76ed9a9 4926 else if (rd->type == RECDB_STRING_LIST) {
258d1427
AS
4927 reply("OSMSG_OPTION_LIST", nodename);
4928 if (rd->d.slist->used)
4929 for (i=0; i<rd->d.slist->used; i++)
4930 send_message_type(4, user, cmd->parent->bot, "$b%s$b", rd->d.slist->list[i]);
4931 else
4932 reply("OSMSG_OPTION_LIST_EMPTY");
d76ed9a9 4933 } else if (rd->type == RECDB_OBJECT) {
258d1427
AS
4934 reply("OSMSG_OPTION_KEYS", nodename);
4935 dict_foreach(rd->d.object, query_keys_helper, user);
d76ed9a9
AS
4936 }
4937
4938 return 1;
4939}
4940
4941static MODCMD_FUNC(cmd_set)
4942{
4943 struct record_data *rd;
4944
4945 /* I originally wanted to be able to fully manipulate the config
4946 db with this, but i wussed out. feel free to fix this - you'll
4947 need to handle quoted strings which have been split, and likely
4948 invent a syntax for it. -Zoot */
4949
4950 if (!(rd = conf_get_node(argv[1]))) {
258d1427
AS
4951 reply("OSMSG_SET_NOT_SET", argv[1]);
4952 return 0;
d76ed9a9
AS
4953 }
4954
4955 if (rd->type != RECDB_QSTRING) {
258d1427
AS
4956 reply("OSMSG_SET_BAD_TYPE", argv[1]);
4957 return 0;
d76ed9a9
AS
4958 }
4959
4960 free(rd->d.qstring);
4961 rd->d.qstring = strdup(argv[2]);
4962 conf_call_reload_funcs();
4963 reply("OSMSG_SET_SUCCESS", argv[1], argv[2]);
4964 return 1;
4965}
4966
4967static MODCMD_FUNC(cmd_settime)
4968{
4969 const char *srv_name_mask = "*";
4970 time_t new_time = now;
4971
4972 if (argc > 1)
4973 srv_name_mask = argv[1];
4974 if (argc > 2)
4975 new_time = time(NULL);
4976 irc_settime(srv_name_mask, new_time);
4977 reply("OSMSG_SETTIME_SUCCESS", srv_name_mask);
4978 return 1;
4979}
4980
4981static discrim_t
258d1427 4982opserv_discrim_create(struct userNode *user, struct userNode *bot, unsigned int argc, char *argv[], int allow_channel)
d76ed9a9
AS
4983{
4984 unsigned int i, j;
4985 discrim_t discrim;
4986
4987 discrim = calloc(1, sizeof(*discrim));
4988 discrim->limit = 250;
4989 discrim->max_level = ~0;
4990 discrim->max_ts = INT_MAX;
4991 discrim->domain_depth = 2;
4992 discrim->max_channels = INT_MAX;
4993 discrim->authed = -1;
4994 discrim->info_space = -1;
63665495 4995 discrim->intra_dcmp = 0;
4996 discrim->intra_scmp = 0;
1c5f6697
AS
4997 discrim->use_regex = 0;
4998 discrim->silent = 0;
d76ed9a9
AS
4999
5000 for (i=0; i<argc; i++) {
5001 if (irccasecmp(argv[i], "log") == 0) {
5002 discrim->option_log = 1;
5003 continue;
5004 }
5005 /* Assume all other criteria require arguments. */
5006 if (i == argc - 1) {
258d1427 5007 send_message(user, bot, "MSG_MISSING_PARAMS", argv[i]);
d76ed9a9
AS
5008 goto fail;
5009 }
63665495 5010 if (argv[i+1][0] == '&') {
5011 /* Looking for intra-userNode matches */
5012 char *tmp = &(argv[i+1][1]);
5013 if (strcasecmp(tmp, argv[i]) != 0) { /* Don't allow "nick &nick" etc */
5014 if (!strcasecmp(tmp, "nick"))
5015 discrim->intra_dcmp = 1;
5016 else if (!strcasecmp(tmp, "ident"))
5017 discrim->intra_dcmp = 2;
5018 else if (!strcasecmp(tmp, "info"))
5019 discrim->intra_dcmp = 3;
5020 }
5021 }
258d1427
AS
5022 if (irccasecmp(argv[i], "mask") == 0) {
5023 if (!is_ircmask(argv[++i])) {
5024 send_message(user, bot, "OSMSG_INVALID_IRCMASK", argv[i]);
5025 goto fail;
5026 }
5027 if (!split_ircmask(argv[i],
d76ed9a9
AS
5028 &discrim->mask_nick,
5029 &discrim->mask_ident,
5030 &discrim->mask_host)) {
258d1427
AS
5031 send_message(user, bot, "OSMSG_INVALID_IRCMASK", argv[i]);
5032 goto fail;
5033 }
5034 } else if (irccasecmp(argv[i], "nick") == 0) {
63665495 5035 i++;
5036 if (discrim->intra_dcmp > 0)
5037 discrim->intra_scmp = 1;
5038 else
5039 discrim->mask_nick = argv[i];
258d1427 5040 } else if (irccasecmp(argv[i], "ident") == 0) {
63665495 5041 i++;
5042 if (discrim->intra_dcmp > 0)
5043 discrim->intra_scmp = 2;
5044 else
5045 discrim->mask_ident = argv[i];
258d1427
AS
5046 } else if (irccasecmp(argv[i], "host") == 0) {
5047 discrim->mask_host = argv[++i];
5048 } else if (irccasecmp(argv[i], "info") == 0) {
63665495 5049 i++;
5050 if (discrim->intra_dcmp > 0)
5051 discrim->intra_scmp = 3;
5052 else
5053 discrim->mask_info = argv[i];
0e08a8e0
AS
5054 } else if (irccasecmp(argv[i], "version") == 0) {
5055 discrim->mask_version = argv[++i];
258d1427
AS
5056 } else if (irccasecmp(argv[i], "server") == 0) {
5057 discrim->server = argv[++i];
5058 } else if (irccasecmp(argv[i], "ip") == 0) {
2f61d1d7 5059 j = irc_pton(&discrim->ip_mask, &discrim->ip_mask_bits, argv[++i]);
5060 if (!j) {
c092fcad 5061 send_message(user, bot, "OSMSG_BAD_IP", argv[i]);
2f61d1d7 5062 goto fail;
5063 }
d76ed9a9
AS
5064 } else if (irccasecmp(argv[i], "account") == 0) {
5065 if (discrim->authed == 0) {
258d1427 5066 send_message(user, bot, "OSMSG_ACCOUNTMASK_AUTHED");
d76ed9a9
AS
5067 goto fail;
5068 }
5069 discrim->accountmask = argv[++i];
5070 discrim->authed = 1;
c408f18a
AS
5071 } else if (irccasecmp(argv[i], "chantarget") == 0) {
5072 if(!IsChannelName(argv[i+1])) {
5073 send_message(user, bot, "MSG_NOT_CHANNEL_NAME");
5074 goto fail;
5075 }
5076 discrim->chantarget = argv[++i];
d76ed9a9
AS
5077 } else if (irccasecmp(argv[i], "authed") == 0) {
5078 i++; /* true_string and false_string are macros! */
5079 if (true_string(argv[i])) {
5080 discrim->authed = 1;
5081 } else if (false_string(argv[i])) {
5082 if (discrim->accountmask) {
258d1427 5083 send_message(user, bot, "OSMSG_ACCOUNTMASK_AUTHED");
d76ed9a9
AS
5084 goto fail;
5085 }
5086 discrim->authed = 0;
5087 } else {
258d1427 5088 send_message(user, bot, "MSG_INVALID_BINARY", argv[i]);
d76ed9a9
AS
5089 goto fail;
5090 }
5091 } else if (irccasecmp(argv[i], "info_space") == 0) {
5092 /* XXX: A hack because you can't check explicitly for a space through
5093 * any other means */
5094 i++;
5095 if (true_string(argv[i])) {
5096 discrim->info_space = 1;
5097 } else if (false_string(argv[i])) {
5098 discrim->info_space = 0;
5099 } else {
258d1427 5100 send_message(user, bot, "MSG_INVALID_BINARY", argv[i]);
d76ed9a9
AS
5101 goto fail;
5102 }
27eaa617 5103 } else if (irccasecmp(argv[i], "regex") == 0) {
5104 i++;
5105 if (true_string(argv[i])) {
5106 discrim->use_regex = 1;
5107 } else if (false_string(argv[i])) {
5108 discrim->use_regex = 0;
5109 } else {
c092fcad 5110 send_message(user, bot, "MSG_INVALID_BINARY", argv[i]);
27eaa617 5111 goto fail;
5112 }
1c5f6697
AS
5113 } else if (irccasecmp(argv[i], "silent") == 0) {
5114 i++;
0c0adfe0 5115 if(user != opserv && !oper_has_access(user, opserv, opserv_conf.silent_level, 0)) {
1c5f6697
AS
5116 goto fail;
5117 } else if (true_string(argv[i])) {
5118 discrim->silent = 1;
5119 } else if (false_string(argv[i])) {
5120 discrim->silent = 0;
5121 } else {
c092fcad 5122 send_message(user, bot, "MSG_INVALID_BINARY", argv[i]);
1c5f6697
AS
5123 goto fail;
5124 }
d76ed9a9
AS
5125 } else if (irccasecmp(argv[i], "duration") == 0) {
5126 discrim->duration = ParseInterval(argv[++i]);
258d1427 5127 } else if (irccasecmp(argv[i], "channel") == 0) {
d76ed9a9
AS
5128 for (j=0, i++; ; j++) {
5129 switch (argv[i][j]) {
5130 case '#':
5131 goto find_channel;
5132 case '-':
55342ce8 5133 discrim->chan_no_modes |= MODE_CHANOP | MODE_HALFOP | MODE_VOICE;
d76ed9a9
AS
5134 break;
5135 case '+':
5136 discrim->chan_req_modes |= MODE_VOICE;
5137 discrim->chan_no_modes |= MODE_CHANOP;
55342ce8 5138 discrim->chan_no_modes |= MODE_HALFOP;
5139 break;
5140 case '%':
5141 discrim->chan_req_modes |= MODE_HALFOP;
5142 discrim->chan_no_modes |= MODE_CHANOP;
5143 discrim->chan_no_modes |= MODE_VOICE;
d76ed9a9
AS
5144 break;
5145 case '@':
5146 discrim->chan_req_modes |= MODE_CHANOP;
5147 break;
5148 case '\0':
258d1427 5149 send_message(user, bot, "MSG_NOT_CHANNEL_NAME");
d76ed9a9
AS
5150 goto fail;
5151 }
5152 }
5153 find_channel:
5154 discrim->chan_no_modes &= ~discrim->chan_req_modes;
258d1427 5155 if (!(discrim->channel = GetChannel(argv[i]+j))) {
d76ed9a9
AS
5156 /* secretly "allow_channel" now means "if a channel name is
5157 * specified, require that it currently exist" */
5158 if (allow_channel) {
258d1427 5159 send_message(user, bot, "MSG_CHANNEL_UNKNOWN", argv[i]);
d76ed9a9
AS
5160 goto fail;
5161 } else {
2aef5f4b 5162 discrim->channel = AddChannel(argv[i]+j, now, NULL, NULL, NULL);
d76ed9a9 5163 }
258d1427 5164 }
d76ed9a9
AS
5165 LockChannel(discrim->channel);
5166 } else if (irccasecmp(argv[i], "numchannels") == 0) {
5167 discrim->min_channels = discrim->max_channels = strtoul(argv[++i], NULL, 10);
258d1427
AS
5168 } else if (irccasecmp(argv[i], "limit") == 0) {
5169 discrim->limit = strtoul(argv[++i], NULL, 10);
d76ed9a9
AS
5170 } else if (irccasecmp(argv[i], "reason") == 0) {
5171 discrim->reason = strdup(unsplit_string(argv+i+1, argc-i-1, NULL));
5172 i = argc;
5173 } else if (irccasecmp(argv[i], "last") == 0) {
5174 discrim->min_ts = now - ParseInterval(argv[++i]);
5175 } else if ((irccasecmp(argv[i], "linked") == 0)
5176 || (irccasecmp(argv[i], "nickage") == 0)) {
5177 const char *cmp = argv[++i];
5178 if (cmp[0] == '<') {
5179 if (cmp[1] == '=') {
5180 discrim->min_ts = now - ParseInterval(cmp+2);
5181 } else {
5182 discrim->min_ts = now - (ParseInterval(cmp+1) - 1);
5183 }
5184 } else if (cmp[0] == '>') {
5185 if (cmp[1] == '=') {
5186 discrim->max_ts = now - ParseInterval(cmp+2);
5187 } else {
5188 discrim->max_ts = now - (ParseInterval(cmp+1) - 1);
5189 }
5190 } else {
5191 discrim->min_ts = now - ParseInterval(cmp+2);
5192 }
5193 } else if (irccasecmp(argv[i], "access") == 0) {
5194 const char *cmp = argv[++i];
5195 if (cmp[0] == '<') {
5196 if (discrim->min_level == 0) discrim->min_level = 1;
5197 if (cmp[1] == '=') {
5198 discrim->max_level = strtoul(cmp+2, NULL, 0);
5199 } else {
5200 discrim->max_level = strtoul(cmp+1, NULL, 0) - 1;
5201 }
5202 } else if (cmp[0] == '=') {
5203 discrim->min_level = discrim->max_level = strtoul(cmp+1, NULL, 0);
5204 } else if (cmp[0] == '>') {
5205 if (cmp[1] == '=') {
5206 discrim->min_level = strtoul(cmp+2, NULL, 0);
5207 } else {
5208 discrim->min_level = strtoul(cmp+1, NULL, 0) + 1;
5209 }
5210 } else {
5211 discrim->min_level = strtoul(cmp+2, NULL, 0);
5212 }
5213 } else if ((irccasecmp(argv[i], "abuse") == 0)
5214 && (irccasecmp(argv[++i], "opers") == 0)) {
5215 discrim->match_opers = 1;
5216 } else if (irccasecmp(argv[i], "depth") == 0) {
5217 discrim->domain_depth = strtoul(argv[++i], NULL, 0);
5218 } else if (irccasecmp(argv[i], "clones") == 0) {
5219 discrim->min_clones = strtoul(argv[++i], NULL, 0);
5220 } else {
258d1427 5221 send_message(user, bot, "MSG_INVALID_CRITERIA", argv[i]);
d76ed9a9
AS
5222 goto fail;
5223 }
5224 }
5225
5226 if (discrim->mask_nick && !strcmp(discrim->mask_nick, "*")) {
258d1427 5227 discrim->mask_nick = 0;
d76ed9a9
AS
5228 }
5229 if (discrim->mask_ident && !strcmp(discrim->mask_ident, "*")) {
5230 discrim->mask_ident = 0;
5231 }
5232 if (discrim->mask_info && !strcmp(discrim->mask_info, "*")) {
258d1427 5233 discrim->mask_info = 0;
d76ed9a9 5234 }
0e08a8e0
AS
5235 if (discrim->mask_version && !strcmp(discrim->mask_version, "*")) {
5236 discrim->mask_version = 0;
5237 }
d76ed9a9
AS
5238 if (discrim->mask_host && !discrim->mask_host[strspn(discrim->mask_host, "*.")]) {
5239 discrim->mask_host = 0;
5240 }
27eaa617 5241
5242 if(discrim->use_regex)
5243 {
5244 if(discrim->mask_nick)
5245 {
5246 int err = regcomp(&discrim->regex_nick, discrim->mask_nick, REG_EXTENDED|REG_ICASE|REG_NOSUB);
5247 discrim->has_regex_nick = !err;
5248 if(err)
5249 {
5250 char buff[256];
5251 buff[regerror(err, &discrim->regex_nick, buff, sizeof(buff))] = 0;
5252
c092fcad 5253 send_message(user, bot, "OSMSG_INVALID_REGEX", discrim->mask_nick, buff, err);
27eaa617 5254 goto regfail;
5255 }
5256 }
5257
5258 if(discrim->mask_ident)
5259 {
5260 int err = regcomp(&discrim->regex_ident, discrim->mask_ident, REG_EXTENDED|REG_ICASE|REG_NOSUB);
5261 discrim->has_regex_ident = !err;
5262 if(err)
5263 {
5264 char buff[256];
5265 buff[regerror(err, &discrim->regex_ident, buff, sizeof(buff))] = 0;
5266
c092fcad 5267 send_message(user, bot, "OSMSG_INVALID_REGEX", discrim->mask_ident, buff, err);
27eaa617 5268 goto regfail;
5269 }
5270 }
5271
5272 if(discrim->mask_host)
5273 {
5274 int err = regcomp(&discrim->regex_host, discrim->mask_host, REG_EXTENDED|REG_ICASE|REG_NOSUB);
5275 discrim->has_regex_host = !err;
5276 if(err)
5277 {
5278 char buff[256];
5279 buff[regerror(err, &discrim->regex_host, buff, sizeof(buff))] = 0;
5280
c092fcad 5281 send_message(user, bot, "OSMSG_INVALID_REGEX", discrim->mask_host, buff, err);
27eaa617 5282 goto regfail;
5283 }
5284 }
5285
5286 if(discrim->mask_info)
5287 {
5288 int err = regcomp(&discrim->regex_info, discrim->mask_info, REG_EXTENDED|REG_ICASE|REG_NOSUB);
5289 discrim->has_regex_info = !err;
5290 if(err)
5291 {
5292 char buff[256];
5293 buff[regerror(err, &discrim->regex_info, buff, sizeof(buff))] = 0;
5294
c092fcad 5295 send_message(user, bot, "OSMSG_INVALID_REGEX", discrim->mask_info, buff, err);
27eaa617 5296 goto regfail;
5297 }
5298 }
0e08a8e0
AS
5299
5300 if(discrim->mask_version)
5301 {
5302 int err = regcomp(&discrim->regex_version, discrim->mask_version, REG_EXTENDED|REG_ICASE|REG_NOSUB);
5303 discrim->has_regex_version = !err;
5304 if(err)
5305 {
5306 char buff[256];
5307 buff[regerror(err, &discrim->regex_version, buff, sizeof(buff))] = 0;
5308
5309 send_message(user, bot, "OSMSG_INVALID_REGEX", discrim->mask_version, buff, err);
5310 goto regfail;
5311 }
5312 }
27eaa617 5313 }
5314
d76ed9a9 5315 return discrim;
27eaa617 5316
d76ed9a9
AS
5317 fail:
5318 free(discrim);
5319 return NULL;
27eaa617 5320
5321 regfail:
5322 if(discrim->has_regex_nick)
5323 regfree(&discrim->regex_nick);
5324 if(discrim->has_regex_ident)
5325 regfree(&discrim->regex_ident);
5326 if(discrim->has_regex_host)
5327 regfree(&discrim->regex_host);
5328 if(discrim->has_regex_info)
5329 regfree(&discrim->regex_info);
5330
5331 free(discrim);
5332 return NULL;
d76ed9a9
AS
5333}
5334
5335static int
5336discrim_match(discrim_t discrim, struct userNode *user)
5337{
5338 unsigned int access;
63665495 5339 char *scmp=NULL, *dcmp=NULL;
d76ed9a9
AS
5340
5341 if ((user->timestamp < discrim->min_ts)
5342 || (user->timestamp > discrim->max_ts)
5343 || (user->channels.used < discrim->min_channels)
5344 || (user->channels.used > discrim->max_channels)
5345 || (discrim->authed == 0 && user->handle_info)
5346 || (discrim->authed == 1 && !user->handle_info)
5347 || (discrim->info_space == 0 && user->info[0] == ' ')
5348 || (discrim->info_space == 1 && user->info[0] != ' ')
d76ed9a9
AS
5349 || (discrim->server && !match_ircglob(user->uplink->name, discrim->server))
5350 || (discrim->accountmask && (!user->handle_info || !match_ircglob(user->handle_info->handle, discrim->accountmask)))
2f61d1d7 5351 || (discrim->ip_mask_bits && !irc_check_mask(&user->ip, &discrim->ip_mask, discrim->ip_mask_bits))
5352 )
5353 return 0;
5354
5355 if (discrim->channel && !GetUserMode(discrim->channel, user))
d76ed9a9 5356 return 0;
27eaa617 5357
5358 if(discrim->use_regex)
5359 {
5360 if((discrim->has_regex_nick && regexec(&discrim->regex_nick, user->nick, 0, 0, 0))
5361 || (discrim->has_regex_ident && regexec(&discrim->regex_ident, user->ident, 0, 0, 0))
5362 || (discrim->has_regex_host && regexec(&discrim->regex_host, user->hostname, 0, 0, 0))
0e08a8e0
AS
5363 || (discrim->has_regex_info && regexec(&discrim->regex_info, user->info, 0, 0, 0))
5364 || (discrim->has_regex_version && (!user->version_reply || regexec(&discrim->regex_version, user->version_reply, 0, 0, 0)))) {
27eaa617 5365 return 0;
5366 }
5367 }
5368 else
5369 {
5370 if ((discrim->mask_nick && !match_ircglob(user->nick, discrim->mask_nick))
5371 || (discrim->mask_ident && !match_ircglob(user->ident, discrim->mask_ident))
5372 || (discrim->mask_host && !match_ircglob(user->hostname, discrim->mask_host))
0e08a8e0
AS
5373 || (discrim->mask_info && !match_ircglob(user->info, discrim->mask_info))
5374 || (discrim->mask_version && (!user->version_reply || !match_ircglob(user->version_reply, discrim->mask_version))) ) {
27eaa617 5375 return 0;
5376 }
5377 }
5378
63665495 5379 if ((discrim->intra_scmp > 0 && discrim->intra_dcmp > 0)) {
5380 switch(discrim->intra_scmp) {
5381 case 1: scmp=user->nick; break;
5382 case 2: scmp=user->ident; break;
5383 case 3:
5384 scmp=user->info;
5385 if (discrim->info_space == 1) scmp++;
5386 break;
5387 }
5388 switch(discrim->intra_dcmp) {
5389 case 1: dcmp=user->nick; break;
5390 case 2: dcmp=user->ident; break;
5391 case 3: /* When checking INFO, and info_space is enabled
5392 * ignore the first character in a search
5393 * XXX: Should we ignore ALL leading whitespace?
5394 * Also, what about ignoring ~ in ident?
5395 */
5396 dcmp=user->info;
5397 if (discrim->info_space == 1) dcmp++;
5398 break;
5399 }
5400 if (irccasecmp(scmp,dcmp))
5401 return 0;
5402 }
5403
d76ed9a9
AS
5404 access = user->handle_info ? user->handle_info->opserv_level : 0;
5405 if ((access < discrim->min_level)
5406 || (access > discrim->max_level)) {
5407 return 0;
5408 }
d76ed9a9 5409 if (discrim->min_clones > 1) {
2f61d1d7 5410 struct opserv_hostinfo *ohi = dict_find(opserv_hostinfo_dict, irc_ntoa(&user->ip), NULL);
5411 if (!ohi || (ohi->clients.used < discrim->min_clones))
5412 return 0;
d76ed9a9
AS
5413 }
5414 return 1;
5415}
5416
5417static unsigned int
5418opserv_discrim_search(discrim_t discrim, discrim_search_func dsf, void *data)
5419{
5420 unsigned int nn, count;
5421 struct userList matched;
5422
5423 userList_init(&matched);
5424 /* Try most optimized search methods first */
5425 if (discrim->channel) {
5426 for (nn=0;
5427 (nn < discrim->channel->members.used)
5428 && (matched.used < discrim->limit);
5429 nn++) {
5430 struct modeNode *mn = discrim->channel->members.list[nn];
5431 if (((mn->modes & discrim->chan_req_modes) != discrim->chan_req_modes)
5432 || ((mn->modes & discrim->chan_no_modes) != 0)) {
5433 continue;
5434 }
5435 if (discrim_match(discrim, mn->user)) {
5436 userList_append(&matched, mn->user);
5437 }
5438 }
2f61d1d7 5439 } else if (discrim->ip_mask_bits == 128) {
5440 struct opserv_hostinfo *ohi = dict_find(opserv_hostinfo_dict, irc_ntoa(&discrim->ip_mask), NULL);
d76ed9a9
AS
5441 if (!ohi) {
5442 userList_clean(&matched);
5443 return 0;
5444 }
5445 for (nn=0; (nn<ohi->clients.used) && (matched.used < discrim->limit); nn++) {
5446 if (discrim_match(discrim, ohi->clients.list[nn])) {
5447 userList_append(&matched, ohi->clients.list[nn]);
5448 }
5449 }
5450 } else {
5451 dict_iterator_t it;
5452 for (it=dict_first(clients); it && (matched.used < discrim->limit); it=iter_next(it)) {
5453 if (discrim_match(discrim, iter_data(it))) {
5454 userList_append(&matched, iter_data(it));
5455 }
5456 }
5457 }
5458
5459 if (!matched.used) {
5460 userList_clean(&matched);
5461 return 0;
5462 }
5463
5464 if (discrim->option_log) {
5465 log_module(OS_LOG, LOG_INFO, "Logging matches for search:");
5466 }
5467 for (nn=0; nn<matched.used; nn++) {
5468 struct userNode *user = matched.list[nn];
5469 if (discrim->option_log) {
5470 log_module(OS_LOG, LOG_INFO, " %s!%s@%s", user->nick, user->ident, user->hostname);
5471 }
5472 if (dsf(user, data)) {
258d1427
AS
5473 /* If a search function returns true, it ran into a
5474 problem. Stop going through the list. */
5475 break;
5476 }
d76ed9a9
AS
5477 }
5478 if (discrim->option_log) {
5479 log_module(OS_LOG, LOG_INFO, "End of matching users.");
5480 }
5481 count = matched.used;
5482 userList_clean(&matched);
5483 return count;
5484}
5485
5486static int
5487trace_print_func(struct userNode *match, void *extra)
5488{
5489 struct discrim_and_source *das = extra;
5490 if (match->handle_info) {
258d1427 5491 send_message_type(4, das->source, das->destination, "%-15s\002 \002%10s\002@\002%s (%s)", match->nick, match->ident, match->hostname, match->handle_info->handle);
d76ed9a9 5492 } else {
258d1427 5493 send_message_type(4, das->source, das->destination, "%-15s\002 \002%10s\002@\002%s", match->nick, match->ident, match->hostname);
d76ed9a9
AS
5494 }
5495 return 0;
5496}
5497
5498static int
5499trace_count_func(UNUSED_ARG(struct userNode *match), UNUSED_ARG(void *extra))
5500{
5501 return 0;
5502}
5503
5504static int
5505is_oper_victim(struct userNode *user, struct userNode *target, int match_opers)
5506{
5507 return !(IsService(target)
5508 || (!match_opers && IsOper(target))
5509 || (target->handle_info
5510 && target->handle_info->opserv_level > user->handle_info->opserv_level));
5511}
5512
5513static int
5514trace_gline_func(struct userNode *match, void *extra)
5515{
5516 struct discrim_and_source *das = extra;
5517
5518 if (is_oper_victim(das->source, match, das->discrim->match_opers)) {
1c5f6697 5519 opserv_block(match, das->source->handle_info->handle, das->discrim->reason, das->discrim->duration, das->discrim->silent);
d76ed9a9
AS
5520 }
5521
5522 return 0;
5523}
5524
d914d1cb 5525static int
5526trace_shun_func(struct userNode *match, void *extra)
5527{
5528 struct discrim_and_source *das = extra;
5529
5530 if (is_oper_victim(das->source, match, das->discrim->match_opers)) {
5531 opserv_shun(match, das->source->handle_info->handle, das->discrim->reason, das->discrim->duration);
5532 }
5533
5534 return 0;
5535}
5536
d76ed9a9
AS
5537static int
5538trace_kill_func(struct userNode *match, void *extra)
5539{
5540 struct discrim_and_source *das = extra;
5541
5542 if (is_oper_victim(das->source, match, das->discrim->match_opers)) {
258d1427 5543 char *reason;
d76ed9a9
AS
5544 if (das->discrim->reason) {
5545 reason = das->discrim->reason;
5546 } else {
5547 reason = alloca(strlen(OSMSG_KILL_REQUESTED)+strlen(das->source->nick)+1);
5548 sprintf(reason, OSMSG_KILL_REQUESTED, das->source->nick);
5549 }
5550 DelUser(match, opserv, 1, reason);
5551 }
5552
5553 return 0;
5554}
5555
0e08a8e0
AS
5556static int
5557trace_svsjoin_func(struct userNode *match, void *extra)
5558{
5559 struct discrim_and_source *das = extra;
5560
5561 char *channame = das->discrim->chantarget;
5562 struct chanNode *channel;
5563
a62ba70c 5564 if(!channame || !IsChannelName(channame)) {
0e08a8e0
AS
5565 //reply("MSG_NOT_CHANNEL_NAME");
5566 return 1;
5567 }
5568
5569 if (!(channel = GetChannel(channame))) {
5570 channel = AddChannel(channame, now, NULL, NULL, NULL);
5571 }
5572 if (GetUserMode(channel, match)) {
5573// reply("OSMSG_ALREADY_THERE", channel->name);
5574 return 1;
5575 }
5576 irc_svsjoin(opserv, match, channel);
5577 // reply("OSMSG_SVSJOIN_SENT");
5578 return 0;
5579}
5580
39c1a4ef 5581static int
5582trace_svspart_func(struct userNode *match, void *extra)
5583{
5584 struct discrim_and_source *das = extra;
39c1a4ef 5585 char *channame = das->discrim->chantarget;
5586 struct chanNode *channel;
5587
5588 if(!channame || !IsChannelName(channame))
5589 return 1;
5590
5591 if (!(channel = GetChannel(channame)))
5592 return 1;
5593
5594 if (!GetUserMode(channel, match))
5595 return 1;
5596
5597 irc_svspart(opserv, match, channel);
5598 return 0;
5599}
5600
0e08a8e0
AS
5601static int
5602trace_version_func(struct userNode *match, UNUSED_ARG(void *extra))
5603{
5604 irc_version_user(opserv, match);
5605 return 0;
5606}
5607
d76ed9a9
AS
5608static int
5609is_gagged(char *mask)
5610{
5611 struct gag_entry *gag;
5612
5613 for (gag = gagList; gag; gag = gag->next) {
5614 if (match_ircglobs(gag->mask, mask)) return 1;
5615 }
5616 return 0;
5617}
5618
5619static int
5620trace_gag_func(struct userNode *match, void *extra)
5621{
5622 struct discrim_and_source *das = extra;
5623
5624 if (is_oper_victim(das->source, match, das->discrim->match_opers)) {
5625 char *reason, *mask;
5626 int masksize;
5627 if (das->discrim->reason) {
5628 reason = das->discrim->reason;
5629 } else {
5630 reason = alloca(strlen(OSMSG_GAG_REQUESTED)+strlen(das->source->nick)+1);
5631 sprintf(reason, OSMSG_GAG_REQUESTED, das->source->nick);
5632 }
258d1427
AS
5633 masksize = 5+strlen(match->hostname);
5634 mask = alloca(masksize);
d76ed9a9 5635 snprintf(mask, masksize, "*!*@%s", match->hostname);
258d1427 5636 if (!is_gagged(mask)) {
d76ed9a9
AS
5637 gag_create(mask, das->source->handle_info->handle, reason,
5638 das->discrim->duration ? (now + das->discrim->duration) : 0);
5639 }
5640 }
5641
5642 return 0;
5643}
5644
5645static int
5646trace_domains_func(struct userNode *match, void *extra)
5647{
5648 struct discrim_and_source *das = extra;
2f61d1d7 5649 irc_in_addr_t ip;
d76ed9a9
AS
5650 unsigned long *count;
5651 unsigned int depth;
5652 char *hostname;
2f61d1d7 5653 char ipmask[IRC_NTOP_MASK_MAX_SIZE];
d76ed9a9 5654
2f61d1d7 5655 if (irc_pton(&ip, NULL, match->hostname)) {
5656 if (irc_in_addr_is_ipv4(ip)) {
5657 unsigned long matchip = ntohl(ip.in6_32[3]);
5658 /* raw IP address.. use up to first three octets of IP */
5659 switch (das->discrim->domain_depth) {
5660 default:
5661 snprintf(ipmask, sizeof(ipmask), "%lu.%lu.%lu.*", (matchip>>24)&255, (matchip>>16)&255, (matchip>>8)&255);
5662 break;
5663 case 2:
5664 snprintf(ipmask, sizeof(ipmask), "%lu.%lu.*", (matchip>>24)&255, (matchip>>16)&255);
5665 break;
5666 case 1:
5667 snprintf(ipmask, sizeof(ipmask), "%lu.*", (matchip>>24)&255);
5668 break;
5669 }
5670 } else if (irc_in_addr_is_ipv6(ip)) {
5671 switch (das->discrim->domain_depth) {
5672 case 1: depth = 16; goto ipv6_pfx;
5673 case 2: depth = 24; goto ipv6_pfx;
5674 case 3: depth = 32; goto ipv6_pfx;
5675 default: depth = das->discrim->domain_depth;
5676 ipv6_pfx:
5677 irc_ntop_mask(ipmask, sizeof(ipmask), &ip, depth);
5678 }
5679 } else safestrncpy(ipmask, match->hostname, sizeof(ipmask));
5680 ipmask[sizeof(ipmask) - 1] = '\0';
d76ed9a9
AS
5681 hostname = ipmask;
5682 } else {
5683 hostname = match->hostname + strlen(match->hostname);
2f61d1d7 5684 for (depth=das->discrim->domain_depth;
d76ed9a9
AS
5685 depth && (hostname > match->hostname);
5686 depth--) {
5687 hostname--;
5688 while ((hostname > match->hostname) && (*hostname != '.')) hostname--;
5689 }
5690 if (*hostname == '.') hostname++; /* advance past last dot we saw */
5691 }
5692 if (!(count = dict_find(das->dict, hostname, NULL))) {
5693 count = calloc(1, sizeof(*count));
5694 dict_insert(das->dict, strdup(hostname), count);
5695 }
5696 (*count)++;
5697 return 0;
5698}
5699
5700static int
5701opserv_show_hostinfo(const char *key, void *data, void *extra)
5702{
5703 unsigned long *count = data;
5704 struct discrim_and_source *das = extra;
5705
258d1427 5706 send_message_type(4, das->source, das->destination, "%s %lu", key, *count);
d76ed9a9
AS
5707 return !--das->disp_limit;
5708}
5709
5710static MODCMD_FUNC(cmd_trace)
5711{
5712 struct discrim_and_source das;
5713 discrim_search_func action;
5714 unsigned int matches;
5715 struct svccmd *subcmd;
5716 char buf[MAXLEN];
a62ba70c 5717 int ret = 1;
d76ed9a9
AS
5718
5719 sprintf(buf, "trace %s", argv[1]);
258d1427
AS
5720 if (!(subcmd = dict_find(opserv_service->commands, buf, NULL))) {
5721 reply("OSMSG_BAD_ACTION", argv[1]);
d76ed9a9
AS
5722 return 0;
5723 }
258d1427 5724 if (!svccmd_can_invoke(user, opserv_service->bot, subcmd, channel, SVCCMD_NOISY))
d76ed9a9
AS
5725 return 0;
5726 if (!irccasecmp(argv[1], "print"))
5727 action = trace_print_func;
5728 else if (!irccasecmp(argv[1], "count"))
5729 action = trace_count_func;
5730 else if (!irccasecmp(argv[1], "domains"))
5731 action = trace_domains_func;
5732 else if (!irccasecmp(argv[1], "gline"))
5733 action = trace_gline_func;
d914d1cb 5734 else if (!irccasecmp(argv[1], "shun"))
5735 action = trace_shun_func;
d76ed9a9
AS
5736 else if (!irccasecmp(argv[1], "kill"))
5737 action = trace_kill_func;
5738 else if (!irccasecmp(argv[1], "gag"))
5739 action = trace_gag_func;
0e08a8e0
AS
5740 else if (!irccasecmp(argv[1], "svsjoin"))
5741 action = trace_svsjoin_func;
39c1a4ef 5742 else if (!irccasecmp(argv[1], "svspart"))
5743 action = trace_svspart_func;
0e08a8e0
AS
5744 else if (!irccasecmp(argv[1], "version"))
5745 action = trace_version_func;
d76ed9a9 5746 else {
258d1427
AS
5747 reply("OSMSG_BAD_ACTION", argv[1]);
5748 return 0;
d76ed9a9
AS
5749 }
5750
5751 if (user->handle_info->opserv_level < subcmd->min_opserv_level) {
5752 reply("OSMSG_LEVEL_TOO_LOW");
5753 return 0;
5754 }
5755
5756 das.dict = NULL;
5757 das.source = user;
258d1427
AS
5758 das.destination = cmd->parent->bot;
5759 das.discrim = opserv_discrim_create(user, cmd->parent->bot, argc-2, argv+2, 1);
d76ed9a9
AS
5760 if (!das.discrim)
5761 return 0;
5762
5763 if (action == trace_print_func)
de9510bc 5764 {
8e11460f 5765 reply("OSMSG_USER_SEARCH_RESULTS");
de9510bc
AS
5766 reply("OSMSG_USER_SEARCH_BAR");
5767 reply("OSMSG_USER_SEARCH_HEADER");
5768 reply("OSMSG_USER_SEARCH_BAR");
5769 }
d76ed9a9 5770 else if (action == trace_count_func)
258d1427 5771 das.discrim->limit = INT_MAX;
d76ed9a9
AS
5772 else if ((action == trace_gline_func) && !das.discrim->duration)
5773 das.discrim->duration = opserv_conf.block_gline_duration;
d914d1cb 5774 else if ((action == trace_shun_func) && !das.discrim->duration)
5775 das.discrim->duration = opserv_conf.block_shun_duration;
d76ed9a9
AS
5776 else if (action == trace_domains_func) {
5777 das.dict = dict_new();
5778 dict_set_free_data(das.dict, free);
5779 dict_set_free_keys(das.dict, free);
5780 das.disp_limit = das.discrim->limit;
5781 das.discrim->limit = INT_MAX;
5782 }
d76ed9a9 5783
a62ba70c
AS
5784 if (action == trace_svsjoin_func && !das.discrim->chantarget) {
5785 reply("OSMSG_SVSJOIN_NO_TARGET");
5786 ret = 0;
5787 }
39c1a4ef 5788 else if (action == trace_svspart_func && !das.discrim->chantarget) {
5789 reply("OSMSG_SVSPART_NO_TARGET");
5790 ret = 0;
5791 }
a62ba70c
AS
5792 else {
5793 matches = opserv_discrim_search(das.discrim, action, &das);
d76ed9a9 5794
a62ba70c
AS
5795 if (action == trace_domains_func)
5796 dict_foreach(das.dict, opserv_show_hostinfo, &das);
5797
5798 if (matches)
5799 {
5800 if(action == trace_print_func)
5801 reply("OSMSG_USER_SEARCH_COUNT_BAR", matches);
5802 else
5803 reply("OSMSG_USER_SEARCH_COUNT", matches);
5804 }
8e11460f 5805 else
a62ba70c 5806 reply("MSG_NO_MATCHES");
8e11460f 5807 }
d76ed9a9
AS
5808
5809 if (das.discrim->channel)
5810 UnlockChannel(das.discrim->channel);
5811 free(das.discrim->reason);
27eaa617 5812
5813 if(das.discrim->has_regex_nick)
5814 regfree(&das.discrim->regex_nick);
5815 if(das.discrim->has_regex_ident)
5816 regfree(&das.discrim->regex_ident);
5817 if(das.discrim->has_regex_host)
5818 regfree(&das.discrim->regex_host);
5819 if(das.discrim->has_regex_info)
5820 regfree(&das.discrim->regex_info);
0e08a8e0
AS
5821 if(das.discrim->has_regex_version)
5822 regfree(&das.discrim->regex_version);
27eaa617 5823
d76ed9a9
AS
5824 free(das.discrim);
5825 dict_delete(das.dict);
a62ba70c 5826 return ret;
d76ed9a9
AS
5827}
5828
258d1427 5829typedef void (*cdiscrim_search_func)(struct chanNode *match, void *data, struct userNode *bot);
d76ed9a9
AS
5830
5831typedef struct channel_discrim {
5832 char *name, *topic;
5833
5834 unsigned int min_users, max_users;
5835 time_t min_ts, max_ts;
5836 unsigned int limit;
5837} *cdiscrim_t;
5838
258d1427
AS
5839static cdiscrim_t opserv_cdiscrim_create(struct userNode *user, struct userNode *bot, unsigned int argc, char *argv[]);
5840static unsigned int opserv_cdiscrim_search(cdiscrim_t discrim, cdiscrim_search_func dsf, void *data, struct userNode *bot);
d76ed9a9
AS
5841
5842static time_t
5843smart_parse_time(const char *str) {
5844 /* If an interval-style string is given, treat as time before now.
5845 * If it's all digits, treat directly as a Unix timestamp. */
5846 return str[strspn(str, "0123456789")] ? (time_t)(now - ParseInterval(str)) : (time_t)atoi(str);
5847}
5848
5849static cdiscrim_t
258d1427 5850opserv_cdiscrim_create(struct userNode *user, struct userNode *bot, unsigned int argc, char *argv[])
d76ed9a9
AS
5851{
5852 cdiscrim_t discrim;
5853 unsigned int i;
5854
5855 discrim = calloc(1, sizeof(*discrim));
5856 discrim->limit = 25;
5857
5858 for (i = 0; i < argc; i++) {
258d1427
AS
5859 /* Assume all criteria require arguments. */
5860 if (i == (argc - 1)) {
5861 send_message(user, bot, "MSG_MISSING_PARAMS", argv[i]);
5862 return NULL;
5863 }
5864
5865 if (!irccasecmp(argv[i], "name"))
5866 discrim->name = argv[++i];
5867 else if (!irccasecmp(argv[i], "topic"))
5868 discrim->topic = argv[++i];
5869 else if (!irccasecmp(argv[i], "users")) {
5870 const char *cmp = argv[++i];
d76ed9a9
AS
5871 if (cmp[0] == '<') {
5872 if (cmp[1] == '=')
5873 discrim->max_users = strtoul(cmp+2, NULL, 0);
5874 else
5875 discrim->max_users = strtoul(cmp+1, NULL, 0) - 1;
5876 } else if (cmp[0] == '=') {
5877 discrim->min_users = discrim->max_users = strtoul(cmp+1, NULL, 0);
5878 } else if (cmp[0] == '>') {
5879 if (cmp[1] == '=')
5880 discrim->min_users = strtoul(cmp+2, NULL, 0);
5881 else
5882 discrim->min_users = strtoul(cmp+1, NULL, 0) + 1;
5883 } else {
5884 discrim->min_users = strtoul(cmp+2, NULL, 0);
5885 }
258d1427
AS
5886 } else if (!irccasecmp(argv[i], "timestamp")) {
5887 const char *cmp = argv[++i];
d76ed9a9
AS
5888 if (cmp[0] == '<') {
5889 if (cmp[1] == '=')
5890 discrim->max_ts = smart_parse_time(cmp+2);
5891 else
5892 discrim->max_ts = smart_parse_time(cmp+1)-1;
5893 } else if (cmp[0] == '=') {
5894 discrim->min_ts = discrim->max_ts = smart_parse_time(cmp+1);
5895 } else if (cmp[0] == '>') {
5896 if (cmp[1] == '=')
5897 discrim->min_ts = smart_parse_time(cmp+2);
5898 else
5899 discrim->min_ts = smart_parse_time(cmp+1)+1;
5900 } else {
5901 discrim->min_ts = smart_parse_time(cmp);
5902 }
258d1427
AS
5903 } else if (!irccasecmp(argv[i], "limit")) {
5904 discrim->limit = strtoul(argv[++i], NULL, 10);
5905 } else {
5906 send_message(user, bot, "MSG_INVALID_CRITERIA", argv[i]);
5907 goto fail;
5908 }
d76ed9a9
AS
5909 }
5910
5911 if (discrim->name && !strcmp(discrim->name, "*"))
258d1427 5912 discrim->name = 0;
d76ed9a9 5913 if (discrim->topic && !strcmp(discrim->topic, "*"))
258d1427 5914 discrim->topic = 0;
d76ed9a9
AS
5915
5916 return discrim;
5917 fail:
5918 free(discrim);
5919 return NULL;
5920}
5921
5922static int
5923cdiscrim_match(cdiscrim_t discrim, struct chanNode *chan)
5924{
5925 if ((discrim->name && !match_ircglob(chan->name, discrim->name)) ||
5926 (discrim->topic && !match_ircglob(chan->topic, discrim->topic)) ||
5927 (discrim->min_users && chan->members.used < discrim->min_users) ||
5928 (discrim->max_users && chan->members.used > discrim->max_users) ||
5929 (discrim->min_ts && chan->timestamp < discrim->min_ts) ||
5930 (discrim->max_ts && chan->timestamp > discrim->max_ts)) {
258d1427 5931 return 0;
d76ed9a9
AS
5932 }
5933 return 1;
5934}
5935
258d1427 5936static unsigned int opserv_cdiscrim_search(cdiscrim_t discrim, cdiscrim_search_func dsf, void *data, struct userNode *bot)
d76ed9a9
AS
5937{
5938 unsigned int count = 0;
5939 dict_iterator_t it, next;
5940
5941 for (it = dict_first(channels); it && count < discrim->limit ; it = next) {
258d1427 5942 struct chanNode *chan = iter_data(it);
d76ed9a9 5943
258d1427
AS
5944 /* Hold on to the next channel in case we decide to
5945 add actions that destructively modify the channel. */
5946 next = iter_next(it);
5947 if ((chan->members.used > 0) && cdiscrim_match(discrim, chan)) {
5948 dsf(chan, data, bot);
5949 count++;
5950 }
d76ed9a9
AS
5951 }
5952
5953 return count;
5954}
5955
258d1427 5956void channel_count(UNUSED_ARG(struct chanNode *channel), UNUSED_ARG(void *data), UNUSED_ARG(struct userNode *bot))
d76ed9a9
AS
5957{
5958}
5959
258d1427 5960void channel_print(struct chanNode *channel, void *data, struct userNode *bot)
d76ed9a9
AS
5961{
5962 char modes[MAXLEN];
5963 irc_make_chanmode(channel, modes);
258d1427 5964 send_message(data, bot, "OSMSG_CSEARCH_CHANNEL_INFO", channel->name, channel->members.used, modes, channel->topic);
d76ed9a9
AS
5965}
5966
5967static MODCMD_FUNC(cmd_csearch)
5968{
5969 cdiscrim_t discrim;
5970 unsigned int matches;
5971 cdiscrim_search_func action;
5972 struct svccmd *subcmd;
5973 char buf[MAXLEN];
5974
5975 if (!irccasecmp(argv[1], "count"))
258d1427 5976 action = channel_count;
d76ed9a9 5977 else if (!irccasecmp(argv[1], "print"))
258d1427 5978 action = channel_print;
d76ed9a9 5979 else {
258d1427
AS
5980 reply("OSMSG_BAD_ACTION", argv[1]);
5981 return 0;
d76ed9a9
AS
5982 }
5983
5984 sprintf(buf, "%s %s", argv[0], argv[0]);
258d1427
AS
5985 if ((subcmd = dict_find(opserv_service->commands, buf, NULL))
5986 && !svccmd_can_invoke(user, opserv_service->bot, subcmd, channel, SVCCMD_NOISY)) {
d76ed9a9
AS
5987 return 0;
5988 }
5989
258d1427 5990 discrim = opserv_cdiscrim_create(user, cmd->parent->bot, argc - 2, argv + 2);
d76ed9a9 5991 if (!discrim)
258d1427 5992 return 0;
d76ed9a9
AS
5993
5994 if (action == channel_print)
258d1427 5995 reply("OSMSG_CHANNEL_SEARCH_RESULTS");
d76ed9a9 5996 else if (action == channel_count)
258d1427 5997 discrim->limit = INT_MAX;
d76ed9a9 5998
258d1427 5999 matches = opserv_cdiscrim_search(discrim, action, user, cmd->parent->bot);
d76ed9a9
AS
6000
6001 if (matches)
258d1427 6002 reply("MSG_MATCH_COUNT", matches);
d76ed9a9 6003 else
258d1427 6004 reply("MSG_NO_MATCHES");
d76ed9a9
AS
6005
6006 free(discrim);
6007 return 1;
6008}
6009
6010static MODCMD_FUNC(cmd_gsync)
6011{
6012 struct server *src;
6013 if (argc > 1) {
6014 src = GetServerH(argv[1]);
6015 if (!src) {
6016 reply("MSG_SERVER_UNKNOWN", argv[1]);
6017 return 0;
6018 }
6019 } else {
6020 src = self->uplink;
6021 }
6022 irc_stats(cmd->parent->bot, src, 'G');
6023 reply("OSMSG_GSYNC_RUNNING", src->name);
6024 return 1;
6025}
6026
d914d1cb 6027static MODCMD_FUNC(cmd_ssync)
6028{
6029 struct server *src;
6030 if (argc > 1) {
6031 src = GetServerH(argv[1]);
6032 if (!src) {
6033 reply("MSG_SERVER_UNKNOWN", argv[1]);
6034 return 0;
6035 }
6036 } else {
6037 src = self->uplink;
6038 }
6039 irc_stats(cmd->parent->bot, src, 'S');
6040 reply("OSMSG_SSYNC_RUNNING", src->name);
6041 return 1;
6042}
6043
d76ed9a9
AS
6044struct gline_extra {
6045 struct userNode *user;
6046 struct string_list *glines;
258d1427 6047 struct userNode *bot;
d76ed9a9
AS
6048};
6049
6050static void
6051gtrace_print_func(struct gline *gline, void *extra)
6052{
6053 struct gline_extra *xtra = extra;
6054 char *when_text, set_text[20];
6055 strftime(set_text, sizeof(set_text), "%Y-%m-%d", localtime(&gline->issued));
6056 when_text = asctime(localtime(&gline->expires));
6057 when_text[strlen(when_text)-1] = 0; /* strip lame \n */
258d1427 6058 send_message(xtra->user, xtra->bot, "OSMSG_GTRACE_FORMAT", gline->target, set_text, gline->issuer, when_text, gline->reason);
d76ed9a9
AS
6059}
6060
6061static void
6062gtrace_count_func(UNUSED_ARG(struct gline *gline), UNUSED_ARG(void *extra))
6063{
6064}
6065
6066static void
6067gtrace_ungline_func(struct gline *gline, void *extra)
6068{
6069 struct gline_extra *xtra = extra;
6070 string_list_append(xtra->glines, strdup(gline->target));
6071}
6072
6073static MODCMD_FUNC(cmd_gtrace)
6074{
6075 struct gline_discrim *discrim;
6076 gline_search_func action;
6077 unsigned int matches, nn;
6078 struct gline_extra extra;
6079 struct svccmd *subcmd;
6080 char buf[MAXLEN];
6081
6082 if (!irccasecmp(argv[1], "print"))
6083 action = gtrace_print_func;
6084 else if (!irccasecmp(argv[1], "count"))
6085 action = gtrace_count_func;
6086 else if (!irccasecmp(argv[1], "ungline"))
6087 action = gtrace_ungline_func;
6088 else {
6089 reply("OSMSG_BAD_ACTION", argv[1]);
6090 return 0;
6091 }
6092 sprintf(buf, "%s %s", argv[0], argv[0]);
258d1427
AS
6093 if ((subcmd = dict_find(opserv_service->commands, buf, NULL))
6094 && !svccmd_can_invoke(user, opserv_service->bot, subcmd, channel, SVCCMD_NOISY)) {
d76ed9a9
AS
6095 return 0;
6096 }
6097
6098 discrim = gline_discrim_create(user, cmd->parent->bot, argc-2, argv+2);
6099 if (!discrim)
6100 return 0;
6101
6102 if (action == gtrace_print_func)
6103 reply("OSMSG_GLINE_SEARCH_RESULTS");
6104 else if (action == gtrace_count_func)
6105 discrim->limit = INT_MAX;
6106
6107 extra.user = user;
6108 extra.glines = alloc_string_list(4);
258d1427 6109 extra.bot = cmd->parent->bot;
d76ed9a9
AS
6110 matches = gline_discrim_search(discrim, action, &extra);
6111
6112 if (action == gtrace_ungline_func)
6113 for (nn=0; nn<extra.glines->used; nn++)
6114 gline_remove(extra.glines->list[nn], 1);
6115 free_string_list(extra.glines);
6116
6117 if (matches)
6118 reply("MSG_MATCH_COUNT", matches);
6119 else
6120 reply("MSG_NO_MATCHES");
6121 free(discrim->alt_target_mask);
6122 free(discrim);
6123 return 1;
6124}
6125
d914d1cb 6126struct shun_extra {
6127 struct userNode *user;
6128 struct string_list *shuns;
6129 struct userNode *bot;
6130};
6131
6132static void
6133strace_print_func(struct shun *shun, void *extra)
6134{
6135 struct shun_extra *xtra = extra;
6136 char *when_text, set_text[20];
6137 strftime(set_text, sizeof(set_text), "%Y-%m-%d", localtime(&shun->issued));
6138 when_text = asctime(localtime(&shun->expires));
6139 when_text[strlen(when_text)-1] = 0; /* strip lame \n */
6140 send_message(xtra->user, xtra->bot, "OSMSG_STRACE_FORMAT", shun->target, set_text, shun->issuer, when_text, shun->reason);
6141}
6142
6143static void
6144strace_count_func(UNUSED_ARG(struct shun *shun), UNUSED_ARG(void *extra))
6145{
6146}
6147
6148static void
6149strace_unshun_func(struct shun *shun, void *extra)
6150{
6151 struct shun_extra *xtra = extra;
6152 string_list_append(xtra->shuns, strdup(shun->target));
6153}
6154
6155static MODCMD_FUNC(cmd_strace)
6156{
6157 struct shun_discrim *discrim;
6158 shun_search_func action;
6159 unsigned int matches, nn;
6160 struct shun_extra extra;
6161 struct svccmd *subcmd;
6162 char buf[MAXLEN];
6163
6164 if (!irccasecmp(argv[1], "print"))
6165 action = strace_print_func;
6166 else if (!irccasecmp(argv[1], "count"))
6167 action = strace_count_func;
6168 else if (!irccasecmp(argv[1], "unshun"))
6169 action = strace_unshun_func;
6170 else {
6171 reply("OSMSG_BAD_ACTION", argv[1]);
6172 return 0;
6173 }
6174 sprintf(buf, "%s %s", argv[0], argv[0]);
6175 if ((subcmd = dict_find(opserv_service->commands, buf, NULL))
6176 && !svccmd_can_invoke(user, opserv_service->bot, subcmd, channel, SVCCMD_NOISY)) {
6177 return 0;
6178 }
6179
6180 discrim = shun_discrim_create(user, cmd->parent->bot, argc-2, argv+2);
6181 if (!discrim)
6182 return 0;
6183
6184 if (action == strace_print_func)
6185 reply("OSMSG_SHUN_SEARCH_RESULTS");
6186 else if (action == strace_count_func)
6187 discrim->limit = INT_MAX;
6188
6189 extra.user = user;
6190 extra.shuns = alloc_string_list(4);
6191 extra.bot = cmd->parent->bot;
6192 matches = shun_discrim_search(discrim, action, &extra);
6193
6194 if (action == strace_unshun_func)
6195 for (nn=0; nn<extra.shuns->used; nn++)
6196 shun_remove(extra.shuns->list[nn], 1);
6197 free_string_list(extra.shuns);
6198
6199 if (matches)
6200 reply("MSG_MATCH_COUNT", matches);
6201 else
6202 reply("MSG_NO_MATCHES");
6203 free(discrim->alt_target_mask);
6204 free(discrim);
6205 return 1;
6206}
6207
d76ed9a9
AS
6208static int
6209alert_check_user(const char *key, void *data, void *extra)
6210{
6211 struct opserv_user_alert *alert = data;
6212 struct userNode *user = extra;
6213
6214 if (!discrim_match(alert->discrim, user))
6215 return 0;
6216
6217 if ((alert->reaction != REACT_NOTICE)
6218 && IsOper(user)
6219 && !alert->discrim->match_opers) {
6220 return 0;
6221 }
6222
6223 /* The user matches the alert criteria, so trigger the reaction. */
6224 if (alert->discrim->option_log)
6225 log_module(OS_LOG, LOG_INFO, "Alert %s triggered by user %s!%s@%s (%s).", key, user->nick, user->ident, user->hostname, alert->discrim->reason);
6226
6227 /* Return 1 to halt alert matching, such as when killing the user
6228 that triggered the alert. */
6229 switch (alert->reaction) {
6230 case REACT_KILL:
6231 DelUser(user, opserv, 1, alert->discrim->reason);
6232 return 1;
6233 case REACT_GLINE:
1c5f6697 6234 opserv_block(user, alert->owner, alert->discrim->reason, alert->discrim->duration, alert->discrim->silent);
d76ed9a9 6235 return 1;
d914d1cb 6236 case REACT_SHUN:
6237 opserv_shun(user, alert->owner, alert->discrim->reason, alert->discrim->duration);
6238 return 1;
c408f18a
AS
6239 case REACT_SVSJOIN:
6240 opserv_svsjoin(user, alert->owner, alert->discrim->reason, alert->discrim->chantarget);
0e08a8e0 6241 break;
39c1a4ef 6242 case REACT_SVSPART:
6243 opserv_svspart(user, alert->owner, alert->discrim->reason, alert->discrim->chantarget);
6244 break;
0e08a8e0
AS
6245 case REACT_VERSION:
6246 /* Don't auto-version a user who we already have a version on, because the version reply itself
6247 * re-triggers this check...
6248 * TODO: maybe safer if we didn't even check react_version type alerts for the 2nd check?
6249 * sort of like we only look at channel alerts on join. -Rubin
6250 */
6251 if(!user->version_reply)
6252 opserv_version(user);
6253 break;
d76ed9a9
AS
6254 default:
6255 log_module(OS_LOG, LOG_ERROR, "Invalid reaction type %d for alert %s.", alert->reaction, key);
6256 /* fall through to REACT_NOTICE case */
6257 case REACT_NOTICE:
6258 opserv_alert("Alert $b%s$b triggered by user $b%s$b!%s@%s (%s).", key, user->nick, user->ident, user->hostname, alert->discrim->reason);
6259 break;
ec311f39 6260 case REACT_TRACK:
6261#ifdef HAVE_TRACK
6262 opserv_alert("Alert $b%s$b triggered by user $b%s$b!%s@%s (%s) (Tracking).", key, user->nick, user->ident, user->hostname, alert->discrim->reason);
6263 add_track_user(user);
6264#endif
6265 break;
d76ed9a9
AS
6266 }
6267 return 0;
6268}
6269
6270static void
6271opserv_alert_check_nick(struct userNode *user, UNUSED_ARG(const char *old_nick))
6272{
6273 struct gag_entry *gag;
6274 dict_foreach(opserv_nick_based_alerts, alert_check_user, user);
6275 /* Gag them if appropriate (and only if). */
6276 user->modes &= ~FLAGS_GAGGED;
6277 for (gag = gagList; gag; gag = gag->next) {
2f61d1d7 6278 if (user_matches_glob(user, gag->mask, MATCH_USENICK)) {
d76ed9a9
AS
6279 gag_helper_func(user, NULL);
6280 break;
6281 }
6282 }
6283}
6284
6285static void
6286opserv_staff_alert(struct userNode *user, UNUSED_ARG(struct handle_info *old_handle))
6287{
6288 const char *type;
6289
6290 if (!opserv_conf.staff_auth_channel
6291 || user->uplink->burst
6292 || !user->handle_info)
6293 return;
6294 else if (user->handle_info->opserv_level)
6295 type = "OPER";
6296 else if (IsNetworkHelper(user))
6297 type = "NETWORK HELPER";
6298 else if (IsSupportHelper(user))
6299 type = "SUPPORT HELPER";
6300 else
6301 return;
6302
2f61d1d7 6303 if (irc_in_addr_is_valid(user->ip))
d76ed9a9
AS
6304 send_channel_notice(opserv_conf.staff_auth_channel, opserv, IDENT_FORMAT" authed to %s account %s", IDENT_DATA(user), type, user->handle_info->handle);
6305 else
6306 send_channel_notice(opserv_conf.staff_auth_channel, opserv, "%s [%s@%s] authed to %s account %s", user->nick, user->ident, user->hostname, type, user->handle_info->handle);
6307}
6308
6309static MODCMD_FUNC(cmd_log)
6310{
6311 struct logSearch *discrim;
6312 unsigned int matches;
6313 struct logReport report;
6314
6315 discrim = log_discrim_create(cmd->parent->bot, user, argc, argv);
6316 if (!discrim)
6317 return 0;
6318
6319 reply("OSMSG_LOG_SEARCH_RESULTS");
6320 report.reporter = opserv;
6321 report.user = user;
6322 matches = log_entry_search(discrim, log_report_entry, &report);
6323
6324 if (matches)
258d1427 6325 reply("MSG_MATCH_COUNT", matches);
d76ed9a9 6326 else
258d1427 6327 reply("MSG_NO_MATCHES");
d76ed9a9
AS
6328
6329 free(discrim);
6330 return 1;
6331}
6332
6333static int
6334gag_helper_func(struct userNode *match, UNUSED_ARG(void *extra))
6335{
6336 if (IsOper(match) || IsLocal(match))
6337 return 0;
6338 match->modes |= FLAGS_GAGGED;
6339 return 0;
6340}
6341
6342static MODCMD_FUNC(cmd_gag)
6343{
6344 struct gag_entry *gag;
6345 unsigned int gagged;
6346 unsigned long duration;
6347 char *reason;
6348
6349 reason = unsplit_string(argv + 3, argc - 3, NULL);
6350
6351 if (!is_ircmask(argv[1])) {
258d1427 6352 reply("OSMSG_INVALID_IRCMASK", argv[1]);
d76ed9a9
AS
6353 return 0;
6354 }
6355
6356 for (gag = gagList; gag; gag = gag->next)
258d1427 6357 if (match_ircglobs(gag->mask, argv[1]))
d76ed9a9
AS
6358 break;
6359
6360 if (gag) {
258d1427
AS
6361 reply("OSMSG_REDUNDANT_GAG", argv[1]);
6362 return 0;
d76ed9a9
AS
6363 }
6364
6365 duration = ParseInterval(argv[2]);
6366 gagged = gag_create(argv[1], user->handle_info->handle, reason, (duration?now+duration:0));
6367
6368 if (gagged)
258d1427 6369 reply("OSMSG_GAG_APPLIED", argv[1], gagged);
d76ed9a9 6370 else
258d1427 6371 reply("OSMSG_GAG_ADDED", argv[1]);
d76ed9a9
AS
6372 return 1;
6373}
6374
6375static int
6376ungag_helper_func(struct userNode *match, UNUSED_ARG(void *extra))
6377{
6378 match->modes &= ~FLAGS_GAGGED;
6379 return 0;
6380}
6381
6382static MODCMD_FUNC(cmd_ungag)
6383{
6384 struct gag_entry *gag;
6385 unsigned int ungagged;
6386
6387 for (gag = gagList; gag; gag = gag->next)
258d1427 6388 if (!strcmp(gag->mask, argv[1]))
d76ed9a9
AS
6389 break;
6390
6391 if (!gag) {
258d1427
AS
6392 reply("OSMSG_GAG_NOT_FOUND", argv[1]);
6393 return 0;
d76ed9a9
AS
6394 }
6395
6396 timeq_del(gag->expires, gag_expire, gag, 0);
6397 ungagged = gag_free(gag);
6398
6399 if (ungagged)
258d1427 6400 reply("OSMSG_UNGAG_APPLIED", argv[1], ungagged);
d76ed9a9 6401 else
258d1427 6402 reply("OSMSG_UNGAG_ADDED", argv[1]);
d76ed9a9
AS
6403 return 1;
6404}
6405
6406static MODCMD_FUNC(cmd_addalert)
6407{
6408 opserv_alert_reaction reaction;
6409 struct svccmd *subcmd;
6410 const char *name;
6411 char buf[MAXLEN];
6412
6413 name = argv[1];
6414 sprintf(buf, "addalert %s", argv[2]);
258d1427
AS
6415 if (!(subcmd = dict_find(opserv_service->commands, buf, NULL))) {
6416 reply("OSMSG_UNKNOWN_REACTION", argv[2]);
6417 return 0;
d76ed9a9
AS
6418 }
6419 if (!irccasecmp(argv[2], "notice"))
6420 reaction = REACT_NOTICE;
6421 else if (!irccasecmp(argv[2], "kill"))
6422 reaction = REACT_KILL;
6423 else if (!irccasecmp(argv[2], "gline"))
6424 reaction = REACT_GLINE;
ec311f39 6425 else if (!irccasecmp(argv[2], "track")) {
6426#ifndef HAVE_TRACK
c092fcad 6427 reply("OSMSG_TRACK_DISABLED");
ec311f39 6428 return 0;
6429#else
6430 reaction = REACT_TRACK;
6431#endif
6432 } else if (!irccasecmp(argv[2], "shun"))
d914d1cb 6433 reaction = REACT_SHUN;
c408f18a
AS
6434 else if(!irccasecmp(argv[2], "svsjoin"))
6435 reaction = REACT_SVSJOIN;
39c1a4ef 6436 else if(!irccasecmp(argv[2], "svspart"))
6437 reaction = REACT_SVSPART;
0e08a8e0
AS
6438 else if(!irccasecmp(argv[2], "version"))
6439 reaction = REACT_VERSION;
d76ed9a9 6440 else {
258d1427
AS
6441 reply("OSMSG_UNKNOWN_REACTION", argv[2]);
6442 return 0;
d76ed9a9 6443 }
258d1427 6444 if (!svccmd_can_invoke(user, opserv_service->bot, subcmd, channel, SVCCMD_NOISY)
a62ba70c
AS
6445 || !opserv_add_user_alert(user, name, reaction, unsplit_string(argv + 3, argc - 3, NULL))) {
6446 reply("OSMSG_ALERT_ADD_FAILED");
d76ed9a9 6447 return 0;
a62ba70c 6448 }
d76ed9a9
AS
6449 reply("OSMSG_ADDED_ALERT", name);
6450 return 1;
6451}
6452
6453static MODCMD_FUNC(cmd_delalert)
6454{
6455 unsigned int i;
6456 for (i=1; i<argc; i++) {
6457 dict_remove(opserv_nick_based_alerts, argv[i]);
6458 dict_remove(opserv_channel_alerts, argv[i]);
258d1427
AS
6459 if (dict_remove(opserv_user_alerts, argv[i]))
6460 reply("OSMSG_REMOVED_ALERT", argv[i]);
d76ed9a9 6461 else
258d1427 6462 reply("OSMSG_NO_SUCH_ALERT", argv[i]);
d76ed9a9
AS
6463 }
6464 return 1;
6465}
6466
6467static void
6468opserv_conf_read(void)
6469{
7637f48f 6470 struct chanNode *chan;
6471 unsigned int i;
d76ed9a9
AS
6472 struct record_data *rd;
6473 dict_t conf_node, child;
6474 const char *str, *str2;
6475 struct policer_params *pp;
6476 dict_iterator_t it;
6477
6478 rd = conf_get_node(OPSERV_CONF_NAME);
6479 if (!rd || rd->type != RECDB_OBJECT) {
258d1427
AS
6480 log_module(OS_LOG, LOG_ERROR, "config node `%s' is missing or has wrong type.", OPSERV_CONF_NAME);
6481 return;
d76ed9a9
AS
6482 }
6483 conf_node = rd->d.object;
6484 str = database_get_data(conf_node, KEY_DEBUG_CHANNEL, RECDB_QSTRING);
6485 if (opserv && str) {
6486 str2 = database_get_data(conf_node, KEY_DEBUG_CHANNEL_MODES, RECDB_QSTRING);
6487 if (!str2)
6488 str2 = "+tinms";
258d1427 6489 opserv_conf.debug_channel = AddChannel(str, now, str2, NULL, NULL);
d76ed9a9
AS
6490 AddChannelUser(opserv, opserv_conf.debug_channel)->modes |= MODE_CHANOP;
6491 } else {
258d1427 6492 opserv_conf.debug_channel = NULL;
d76ed9a9
AS
6493 }
6494 str = database_get_data(conf_node, KEY_ALERT_CHANNEL, RECDB_QSTRING);
6495 if (opserv && str) {
6496 str2 = database_get_data(conf_node, KEY_ALERT_CHANNEL_MODES, RECDB_QSTRING);
6497 if (!str2)
6498 str2 = "+tns";
258d1427 6499 opserv_conf.alert_channel = AddChannel(str, now, str2, NULL, NULL);
d76ed9a9
AS
6500 AddChannelUser(opserv, opserv_conf.alert_channel)->modes |= MODE_CHANOP;
6501 } else {
258d1427 6502 opserv_conf.alert_channel = NULL;
d76ed9a9
AS
6503 }
6504 str = database_get_data(conf_node, KEY_STAFF_AUTH_CHANNEL, RECDB_QSTRING);
6505 if (opserv && str) {
6506 str2 = database_get_data(conf_node, KEY_STAFF_AUTH_CHANNEL_MODES, RECDB_QSTRING);
6507 if (!str2)
6508 str2 = "+timns";
2aef5f4b 6509 opserv_conf.staff_auth_channel = AddChannel(str, now, str2, NULL, NULL);
d76ed9a9
AS
6510 AddChannelUser(opserv, opserv_conf.staff_auth_channel)->modes |= MODE_CHANOP;
6511 } else {
6512 opserv_conf.staff_auth_channel = NULL;
6513 }
5a1daaab
AS
6514
6515 str = database_get_data(conf_node, KEY_ADMIN_LEVEL, RECDB_QSTRING);
6516 opserv_conf.admin_level = str ? strtoul(str, NULL, 0): 800;
6517
1c5f6697
AS
6518 str = database_get_data(conf_node, KEY_SILENT_LEVEL, RECDB_QSTRING);
6519 opserv_conf.silent_level = str ? strtoul(str, NULL, 0): 700;
6520
d76ed9a9
AS
6521 str = database_get_data(conf_node, KEY_UNTRUSTED_MAX, RECDB_QSTRING);
6522 opserv_conf.untrusted_max = str ? strtoul(str, NULL, 0) : 5;
6523 str = database_get_data(conf_node, KEY_PURGE_LOCK_DELAY, RECDB_QSTRING);
6524 opserv_conf.purge_lock_delay = str ? strtoul(str, NULL, 0) : 60;
6525 str = database_get_data(conf_node, KEY_JOIN_FLOOD_MODERATE, RECDB_QSTRING);
6526 opserv_conf.join_flood_moderate = str ? strtoul(str, NULL, 0) : 1;
6527 str = database_get_data(conf_node, KEY_JOIN_FLOOD_MODERATE_THRESH, RECDB_QSTRING);
6528 opserv_conf.join_flood_moderate_threshold = str ? strtoul(str, NULL, 0) : 50;
6529 str = database_get_data(conf_node, KEY_NICK, RECDB_QSTRING);
6530 if (opserv && str)
6531 NickChange(opserv, str, 0);
d914d1cb 6532
d76ed9a9
AS
6533 str = database_get_data(conf_node, KEY_CLONE_GLINE_DURATION, RECDB_QSTRING);
6534 opserv_conf.clone_gline_duration = str ? ParseInterval(str) : 3600;
6535 str = database_get_data(conf_node, KEY_BLOCK_GLINE_DURATION, RECDB_QSTRING);
6536 opserv_conf.block_gline_duration = str ? ParseInterval(str) : 3600;
6537
7637f48f 6538 free_string_list(autojoin_channels);
6539 autojoin_channels = database_get_data(conf_node, KEY_AUTOJOIN_CHANNELS, RECDB_STRING_LIST);
6540
6541 if(autojoin_channels)
6542 autojoin_channels = string_list_copy(autojoin_channels);
6543
6544 if (autojoin_channels && opserv) {
6545 for (i = 0; i < autojoin_channels->used; i++) {
6546 chan = AddChannel(autojoin_channels->list[i], now, "+nt", NULL, NULL);
6547 AddChannelUser(opserv, chan)->modes |= MODE_CHANOP;
6548 }
6549 }
6550
d914d1cb 6551 str = database_get_data(conf_node, KEY_BLOCK_SHUN_DURATION, RECDB_QSTRING);
6552 opserv_conf.block_shun_duration = str ? ParseInterval(str) : 3600;
6553
d76ed9a9
AS
6554 if (!opserv_conf.join_policer_params)
6555 opserv_conf.join_policer_params = policer_params_new();
6556 policer_params_set(opserv_conf.join_policer_params, "size", "20");
6557 policer_params_set(opserv_conf.join_policer_params, "drain-rate", "1");
6558 if ((child = database_get_data(conf_node, KEY_JOIN_POLICER, RECDB_OBJECT)))
258d1427 6559 dict_foreach(child, set_policer_param, opserv_conf.join_policer_params);
d76ed9a9
AS
6560
6561 for (it = dict_first(channels); it; it = iter_next(it)) {
6562 struct chanNode *cNode = iter_data(it);
6563 cNode->join_policer.params = opserv_conf.join_policer_params;
6564 }
6565
6566 if (opserv_conf.new_user_policer.params)
6567 pp = opserv_conf.new_user_policer.params;
6568 else
6569 pp = opserv_conf.new_user_policer.params = policer_params_new();
6570 policer_params_set(pp, "size", "200");
6571 policer_params_set(pp, "drain-rate", "3");
6572 if ((child = database_get_data(conf_node, KEY_NEW_USER_POLICER, RECDB_OBJECT)))
258d1427 6573 dict_foreach(child, set_policer_param, pp);
08895577 6574
6575 /* Defcon configuration */
6576 DefCon[0] = 0;
6577 str = database_get_data(conf_node, KEY_DEFCON1, RECDB_QSTRING);
6578 DefCon[1] = str ? atoi(str) : 415;
6579 str = database_get_data(conf_node, KEY_DEFCON2, RECDB_QSTRING);
6580 DefCon[2] = str ? atoi(str) : 159;
6581 str = database_get_data(conf_node, KEY_DEFCON3, RECDB_QSTRING);
6582 DefCon[3] = str ? atoi(str) : 31;
6583 str = database_get_data(conf_node, KEY_DEFCON4, RECDB_QSTRING);
6584 DefCon[4] = str? atoi(str) : 23;
6585 DefCon[5] = 0;
6586
6587 str = database_get_data(conf_node, KEY_DEFCON_LEVEL, RECDB_QSTRING);
6588 DefConLevel = str ? atoi(str) : 5;
6589
6590 str = database_get_data(conf_node, KEY_DEFCON_CHANMODES, RECDB_QSTRING);
6591 DefConChanModes = str ? strdup(str) : "+r";
6592
6593 str = database_get_data(conf_node, KEY_DEFCON_SESSION_LIMIT, RECDB_QSTRING);
6594 DefConSessionLimit = str ? atoi(str) : 2;
6595
6596 str = database_get_data(conf_node, KEY_DEFCON_TIMEOUT, RECDB_QSTRING);
6597 DefConTimeOut = str ? ParseInterval(str) : 900;
6598
6599 str = database_get_data(conf_node, KEY_DEFCON_GLINE_DURATION, RECDB_QSTRING);
6600 DefConGlineExpire = str ? ParseInterval(str) : 300;
6601
08895577 6602 str = database_get_data(conf_node, KEY_DEFCON_GLOBAL, RECDB_QSTRING);
6603 GlobalOnDefcon = str ? atoi(str) : 0;
6604
6605 str = database_get_data(conf_node, KEY_DEFCON_GLOBAL_MORE, RECDB_QSTRING);
6606 GlobalOnDefconMore = str ? atoi(str) : 0;
6607
6608 str = database_get_data(conf_node, KEY_DEFCON_MESSAGE, RECDB_QSTRING);
6609 DefConMessage = str ? strdup(str) : "Put your message to send your users here. Dont forget to uncomment GlobalOnDefconMore";
6610
6611 str = database_get_data(conf_node, KEY_DEFCON_OFF_MESSAGE, RECDB_QSTRING);
6612 DefConOffMessage = str? strdup(str) : "Services are now back to normal, sorry for any inconvenience";
6613
6614 str = database_get_data(conf_node, KEY_DEFCON_GLINE_REASON, RECDB_QSTRING);
6615 DefConGlineReason = str ? strdup(str) : "This network is currently not accepting connections, please try again later";
d76ed9a9
AS
6616}
6617
5a1daaab
AS
6618/* lame way to export opserv_conf value to nickserv.c ... */
6619unsigned int
6620opserv_conf_admin_level()
6621{
6622 return(opserv_conf.admin_level);
6623}
6624
d76ed9a9
AS
6625static void
6626opserv_db_init(void) {
6627 /* set up opserv_trusted_hosts dict */
6628 dict_delete(opserv_trusted_hosts);
6629 opserv_trusted_hosts = dict_new();
6630 dict_set_free_data(opserv_trusted_hosts, free_trusted_host);
47956fc5
AS
6631
6632 opserv_routing_plan_options = dict_new();
6633
6634 opserv_routing_plans = dict_new();
6635 dict_set_free_data(opserv_routing_plans, free_routing_plan);
d76ed9a9 6636 /* set up opserv_chan_warn dict */
de9510bc
AS
6637
6638/* alert trace notice channel #x replaces warnings
d76ed9a9
AS
6639 dict_delete(opserv_chan_warn);
6640 opserv_chan_warn = dict_new();
6641 dict_set_free_keys(opserv_chan_warn, free);
6642 dict_set_free_data(opserv_chan_warn, free);
de9510bc 6643*/
d76ed9a9
AS
6644 /* set up opserv_user_alerts */
6645 dict_delete(opserv_channel_alerts);
6646 opserv_channel_alerts = dict_new();
6647 dict_delete(opserv_nick_based_alerts);
6648 opserv_nick_based_alerts = dict_new();
6649 dict_delete(opserv_user_alerts);
6650 opserv_user_alerts = dict_new();
6651 dict_set_free_keys(opserv_user_alerts, free);
6652 dict_set_free_data(opserv_user_alerts, opserv_free_user_alert);
6653 /* set up opserv_bad_words */
6654 free_string_list(opserv_bad_words);
6655 opserv_bad_words = alloc_string_list(4);
6656 /* and opserv_exempt_channels */
6657 dict_delete(opserv_exempt_channels);
6658 opserv_exempt_channels = dict_new();
6659 dict_set_free_keys(opserv_exempt_channels, free);
6660}
6661
6662static void
6663opserv_db_cleanup(void)
6664{
6665 unsigned int nn;
6666
de9510bc 6667/* dict_delete(opserv_chan_warn); */
d76ed9a9
AS
6668 dict_delete(opserv_reserved_nick_dict);
6669 free_string_list(opserv_bad_words);
6670 dict_delete(opserv_exempt_channels);
6671 dict_delete(opserv_trusted_hosts);
6672 unreg_del_user_func(opserv_user_cleanup);
6673 dict_delete(opserv_hostinfo_dict);
6674 dict_delete(opserv_nick_based_alerts);
6675 dict_delete(opserv_channel_alerts);
6676 dict_delete(opserv_user_alerts);
6677 for (nn=0; nn<ArrayLength(level_strings); ++nn)
6678 free(level_strings[nn]);
6679 while (gagList)
6680 gag_free(gagList);
6681 policer_params_delete(opserv_conf.join_policer_params);
6682 policer_params_delete(opserv_conf.new_user_policer.params);
6683}
6684
6685void
6686init_opserv(const char *nick)
6687{
6688 OS_LOG = log_register_type("OpServ", "file:opserv.log");
a32da4c7 6689 if (nick) {
6690 const char *modes = conf_get_data("services/opserv/modes", RECDB_QSTRING);
6691 opserv = AddService(nick, modes ? modes : NULL, "Oper Services", NULL);
6692 }
d76ed9a9
AS
6693 conf_register_reload(opserv_conf_read);
6694
6695 memset(level_strings, 0, sizeof(level_strings));
6696 opserv_module = module_register("OpServ", OS_LOG, "opserv.help", opserv_help_expand);
6697 opserv_define_func("ACCESS", cmd_access, 0, 0, 0);
6698 opserv_define_func("ADDALERT", cmd_addalert, 800, 0, 4);
6699 opserv_define_func("ADDALERT NOTICE", NULL, 0, 0, 0);
9a75756e 6700 opserv_define_func("ADDALERT SILENT", NULL, 900, 0, 0);
d76ed9a9 6701 opserv_define_func("ADDALERT GLINE", NULL, 900, 0, 0);
d914d1cb 6702 opserv_define_func("ADDALERT SHUN", NULL, 900, 0, 0);
ec311f39 6703 opserv_define_func("ADDALERT TRACK", NULL, 900, 0, 0);
d76ed9a9 6704 opserv_define_func("ADDALERT KILL", NULL, 900, 0, 0);
c408f18a 6705 opserv_define_func("ADDALERT SVSJOIN", NULL, 999, 0, 0);
39c1a4ef 6706 opserv_define_func("ADDALERT SVSPART", NULL, 999, 0, 0);
0e08a8e0 6707 opserv_define_func("ADDALERT VERSION", NULL, 999, 0, 0);
d76ed9a9
AS
6708 opserv_define_func("ADDBAD", cmd_addbad, 800, 0, 2);
6709 opserv_define_func("ADDEXEMPT", cmd_addexempt, 800, 0, 2);
6710 opserv_define_func("ADDTRUST", cmd_addtrust, 800, 0, 5);
6711 opserv_define_func("BAN", cmd_ban, 100, 2, 2);
6712 opserv_define_func("BLOCK", cmd_block, 100, 0, 2);
6713 opserv_define_func("CHANINFO", cmd_chaninfo, 0, 3, 0);
6714 opserv_define_func("CLEARBANS", cmd_clearbans, 300, 2, 0);
6715 opserv_define_func("CLEARMODES", cmd_clearmodes, 400, 2, 0);
6716 opserv_define_func("CLONE", cmd_clone, 999, 0, 3);
6717 opserv_define_func("COLLIDE", cmd_collide, 800, 0, 5);
6718 opserv_define_func("CSEARCH", cmd_csearch, 100, 0, 3);
6719 opserv_define_func("CSEARCH COUNT", cmd_csearch, 0, 0, 0);
6720 opserv_define_func("CSEARCH PRINT", cmd_csearch, 0, 0, 0);
6721 opserv_define_func("DELALERT", cmd_delalert, 800, 0, 2);
6722 opserv_define_func("DELBAD", cmd_delbad, 800, 0, 2);
6723 opserv_define_func("DELEXEMPT", cmd_delexempt, 800, 0, 2);
6724 opserv_define_func("DELTRUST", cmd_deltrust, 800, 0, 2);
6725 opserv_define_func("DEOP", cmd_deop, 100, 2, 2);
6726 opserv_define_func("DEOPALL", cmd_deopall, 400, 2, 0);
08895577 6727 opserv_define_func("DEFCON", cmd_defcon, 900, 0, 0);
55342ce8 6728 opserv_define_func("DEHOP", cmd_dehop, 100, 2, 2);
6729 opserv_define_func("DEHOPALL", cmd_dehopall, 400, 2, 0);
d76ed9a9
AS
6730 opserv_define_func("DEVOICEALL", cmd_devoiceall, 300, 2, 0);
6731 opserv_define_func("DIE", cmd_die, 900, 0, 2);
6732 opserv_define_func("DUMP", cmd_dump, 999, 0, 2);
6733 opserv_define_func("EDITTRUST", cmd_edittrust, 800, 0, 5);
6734 opserv_define_func("GAG", cmd_gag, 600, 0, 4);
6735 opserv_define_func("GLINE", cmd_gline, 600, 0, 4);
6736 opserv_define_func("GSYNC", cmd_gsync, 600, 0, 0);
6737 opserv_define_func("GTRACE", cmd_gtrace, 100, 0, 3);
6738 opserv_define_func("GTRACE COUNT", NULL, 0, 0, 0);
6739 opserv_define_func("GTRACE PRINT", NULL, 0, 0, 0);
d914d1cb 6740 opserv_define_func("SBLOCK", cmd_sblock, 100, 0, 2);
6741 opserv_define_func("SHUN", cmd_shun, 600, 0, 4);
6742 opserv_define_func("SSYNC", cmd_ssync, 600, 0, 0);
6743 opserv_define_func("STRACE", cmd_strace, 100, 0, 3);
6744 opserv_define_func("STRACE COUNT", NULL, 0, 0, 0);
6745 opserv_define_func("STRACE PRINT", NULL, 0, 0, 0);
d76ed9a9
AS
6746 opserv_define_func("INVITE", cmd_invite, 100, 2, 0);
6747 opserv_define_func("INVITEME", cmd_inviteme, 100, 0, 0);
6748 opserv_define_func("JOIN", cmd_join, 601, 0, 2);
c408f18a 6749 opserv_define_func("SVSJOIN", cmd_svsjoin, 999, 0, 3);
39c1a4ef 6750 opserv_define_func("SVSPART", cmd_svspart, 999, 0, 3);
d76ed9a9
AS
6751 opserv_define_func("JUMP", cmd_jump, 900, 0, 2);
6752 opserv_define_func("JUPE", cmd_jupe, 900, 0, 4);
6753 opserv_define_func("KICK", cmd_kick, 100, 2, 2);
6754 opserv_define_func("KICKALL", cmd_kickall, 400, 2, 0);
6755 opserv_define_func("KICKBAN", cmd_kickban, 100, 2, 2);
6756 opserv_define_func("KICKBANALL", cmd_kickbanall, 450, 2, 0);
6757 opserv_define_func("LOG", cmd_log, 900, 0, 2);
6758 opserv_define_func("MODE", cmd_mode, 100, 2, 2);
6759 opserv_define_func("OP", cmd_op, 100, 2, 2);
6760 opserv_define_func("OPALL", cmd_opall, 400, 2, 0);
55342ce8 6761 opserv_define_func("HOP", cmd_hop, 100, 2, 2);
6762 opserv_define_func("HOPALL", cmd_hopall, 400, 2, 0);
47956fc5 6763 opserv_define_func("MAP", cmd_stats_links, 0, 0, 0);
6c34bb5a 6764 opserv_define_func("PRIVSET", cmd_privset, 900, 0, 3);
d76ed9a9
AS
6765 opserv_define_func("PART", cmd_part, 601, 0, 2);
6766 opserv_define_func("QUERY", cmd_query, 0, 0, 0);
6767 opserv_define_func("RAW", cmd_raw, 999, 0, 2);
6768 opserv_define_func("RECONNECT", cmd_reconnect, 900, 0, 0);
6769 opserv_define_func("REFRESHG", cmd_refreshg, 600, 0, 0);
d914d1cb 6770 opserv_define_func("REFRESHS", cmd_refreshs, 600, 0, 0);
d76ed9a9
AS
6771 opserv_define_func("REHASH", cmd_rehash, 900, 0, 0);
6772 opserv_define_func("REOPEN", cmd_reopen, 900, 0, 0);
7637f48f 6773 opserv_define_func("RESETMAX", cmd_resetmax, 900, 0, 0);
d76ed9a9
AS
6774 opserv_define_func("RESERVE", cmd_reserve, 800, 0, 5);
6775 opserv_define_func("RESTART", cmd_restart, 900, 0, 2);
47956fc5
AS
6776 opserv_define_func("ROUTING ADDPLAN", cmd_routing_addplan, 800, 0, 2);
6777 opserv_define_func("ROUTING DELPLAN", cmd_routing_delplan, 800, 0, 2);
6778 opserv_define_func("ROUTING ADDSERVER", cmd_routing_addserver, 800, 0, 4);
6779 opserv_define_func("ROUTING DELSERVER", cmd_routing_delserver, 800, 0, 3);
6780 opserv_define_func("ROUTING MAP", cmd_routing_map, 800, 0, 0);
6781 opserv_define_func("ROUTING SET", cmd_routing_set, 800, 0, 0);
6782 opserv_define_func("REROUTE", cmd_reroute, 800, 0, 2);
d76ed9a9
AS
6783 opserv_define_func("SET", cmd_set, 900, 0, 3);
6784 opserv_define_func("SETTIME", cmd_settime, 901, 0, 0);
6785 opserv_define_func("STATS ALERTS", cmd_stats_alerts, 0, 0, 0);
6786 opserv_define_func("STATS BAD", cmd_stats_bad, 0, 0, 0);
6787 opserv_define_func("STATS GAGS", cmd_stats_gags, 0, 0, 0);
6788 opserv_define_func("STATS GLINES", cmd_stats_glines, 0, 0, 0);
d914d1cb 6789 opserv_define_func("STATS SHUNS", cmd_stats_shuns, 0, 0, 0);
d76ed9a9
AS
6790 opserv_define_func("STATS LINKS", cmd_stats_links, 0, 0, 0);
6791 opserv_define_func("STATS MAX", cmd_stats_max, 0, 0, 0);
6792 opserv_define_func("STATS NETWORK", cmd_stats_network, 0, 0, 0);
6793 opserv_define_func("STATS NETWORK2", cmd_stats_network2, 0, 0, 0);
6794 opserv_define_func("STATS RESERVED", cmd_stats_reserved, 0, 0, 0);
47956fc5 6795 opserv_define_func("STATS ROUTING", cmd_stats_routing_plans, 0, 0, 0);
d76ed9a9
AS
6796 opserv_define_func("STATS TIMEQ", cmd_stats_timeq, 0, 0, 0);
6797 opserv_define_func("STATS TRUSTED", cmd_stats_trusted, 0, 0, 0);
6798 opserv_define_func("STATS UPLINK", cmd_stats_uplink, 0, 0, 0);
6799 opserv_define_func("STATS UPTIME", cmd_stats_uptime, 0, 0, 0);
de9510bc 6800/* opserv_define_func("STATS WARN", cmd_stats_warn, 0, 0, 0); */
f14e4f83 6801#if defined(WITH_MALLOC_X3) || defined(WITH_MALLOC_SLAB)
ec1a68c8 6802 opserv_define_func("STATS MEMORY", cmd_stats_memory, 0, 0, 0);
6803#endif
d76ed9a9
AS
6804 opserv_define_func("TRACE", cmd_trace, 100, 0, 3);
6805 opserv_define_func("TRACE PRINT", NULL, 0, 0, 0);
6806 opserv_define_func("TRACE COUNT", NULL, 0, 0, 0);
6807 opserv_define_func("TRACE DOMAINS", NULL, 0, 0, 0);
6808 opserv_define_func("TRACE GLINE", NULL, 600, 0, 0);
d914d1cb 6809 opserv_define_func("TRACE SHUN", NULL, 600, 0, 0);
d76ed9a9
AS
6810 opserv_define_func("TRACE GAG", NULL, 600, 0, 0);
6811 opserv_define_func("TRACE KILL", NULL, 600, 0, 0);
0e08a8e0
AS
6812 opserv_define_func("TRACE VERSION", NULL, 999, 0, 0);
6813 opserv_define_func("TRACE SVSJOIN", NULL, 999, 0, 0);
39c1a4ef 6814 opserv_define_func("TRACE SVSPART", NULL, 999, 0, 0);
d76ed9a9
AS
6815 opserv_define_func("UNBAN", cmd_unban, 100, 2, 2);
6816 opserv_define_func("UNGAG", cmd_ungag, 600, 0, 2);
6817 opserv_define_func("UNGLINE", cmd_ungline, 600, 0, 2);
6818 modcmd_register(opserv_module, "GTRACE UNGLINE", NULL, 0, 0, "template", "ungline", NULL);
d914d1cb 6819 opserv_define_func("UNSHUN", cmd_unshun, 600, 0, 2);
6820 modcmd_register(opserv_module, "GTRACE UNSHUN", NULL, 0, 0, "template", "unshun", NULL);
d76ed9a9
AS
6821 opserv_define_func("UNJUPE", cmd_unjupe, 900, 0, 2);
6822 opserv_define_func("UNRESERVE", cmd_unreserve, 800, 0, 2);
de9510bc 6823/* opserv_define_func("UNWARN", cmd_unwarn, 800, 0, 0); */
d76ed9a9 6824 opserv_define_func("VOICEALL", cmd_voiceall, 300, 2, 0);
de9510bc 6825/* opserv_define_func("WARN", cmd_warn, 800, 0, 2); */
d76ed9a9
AS
6826 opserv_define_func("WHOIS", cmd_whois, 0, 0, 2);
6827
6828 opserv_reserved_nick_dict = dict_new();
6829 opserv_hostinfo_dict = dict_new();
47956fc5 6830
d76ed9a9
AS
6831 dict_set_free_keys(opserv_hostinfo_dict, free);
6832 dict_set_free_data(opserv_hostinfo_dict, opserv_free_hostinfo);
6833
47956fc5
AS
6834 opserv_waiting_connections = dict_new();
6835 dict_set_free_data(opserv_waiting_connections, opserv_free_waiting_connection);
6836
d76ed9a9
AS
6837 reg_new_user_func(opserv_new_user_check);
6838 reg_nick_change_func(opserv_alert_check_nick);
6839 reg_del_user_func(opserv_user_cleanup);
c52666c6 6840 reg_new_channel_func(opserv_channel_check);
d76ed9a9
AS
6841 reg_del_channel_func(opserv_channel_delete);
6842 reg_join_func(opserv_join_check);
6843 reg_auth_func(opserv_staff_alert);
0e08a8e0 6844 reg_notice_func(opserv, opserv_notice_handler);
d76ed9a9
AS
6845
6846 opserv_db_init();
6847 saxdb_register("OpServ", opserv_saxdb_read, opserv_saxdb_write);
6848 if (nick)
258d1427
AS
6849 {
6850 opserv_service = service_register(opserv);
6851 opserv_service->trigger = '?';
6852 }
d76ed9a9 6853
47956fc5
AS
6854 /* start auto-routing system */
6855 reroute_timer(NULL);
6856 /* start the karma timer, using the saved one if available */
6857 routing_karma_timer(dict_find(opserv_routing_plan_options, "KARMA_TIMER", NULL));
6858
d76ed9a9
AS
6859 reg_exit_func(opserv_db_cleanup);
6860 message_register_table(msgtab);
6861}