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