]> jfr.im git - irc/evilnet/x3.git/blame - src/chanserv.c
Added a missing file plus fixed a crash with .set
[irc/evilnet/x3.git] / src / chanserv.c
CommitLineData
d76ed9a9
AS
1/* chanserv.c - Channel service bot
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
AS
9 * (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with srvx; if not, write to the Free Software Foundation,
18 * Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
19 */
20
21#include "chanserv.h"
22#include "conf.h"
23#include "global.h"
b404335b 24#include "gline.h"
25#include "ioset.h"
d76ed9a9
AS
26#include "modcmd.h"
27#include "opserv.h" /* for opserv_bad_channel() */
1136f709 28#include "nickserv.h" /* for oper_outranks() */
d76ed9a9 29#include "saxdb.h"
b404335b 30#include "shun.h"
63c95a47 31#include "spamserv.h"
d76ed9a9
AS
32#include "timeq.h"
33
34#define CHANSERV_CONF_NAME "services/chanserv"
35
36/* ChanServ options */
37#define KEY_SUPPORT_CHANNEL "support_channel"
38#define KEY_SUPPORT_CHANNEL_MODES "support_channel_modes"
39#define KEY_DB_BACKUP_FREQ "db_backup_freq"
40#define KEY_INFO_DELAY "info_delay"
41#define KEY_MAX_GREETLEN "max_greetlen"
42#define KEY_ADJUST_THRESHOLD "adjust_threshold"
43#define KEY_ADJUST_DELAY "adjust_delay"
44#define KEY_CHAN_EXPIRE_FREQ "chan_expire_freq"
45#define KEY_CHAN_EXPIRE_DELAY "chan_expire_delay"
1136f709 46#define KEY_DNR_EXPIRE_FREQ "dnr_expire_freq"
31f23f13 47#define KEY_BAN_TIMEOUT_FREQ "ban_timeout_freq"
d76ed9a9
AS
48#define KEY_MAX_CHAN_USERS "max_chan_users"
49#define KEY_MAX_CHAN_BANS "max_chan_bans"
50#define KEY_NICK "nick"
51#define KEY_OLD_CHANSERV_NAME "old_chanserv_name"
d76ed9a9
AS
52#define KEY_8BALL_RESPONSES "8ball"
53#define KEY_OLD_BAN_NAMES "old_ban_names"
54#define KEY_REFRESH_PERIOD "refresh_period"
55#define KEY_CTCP_SHORT_BAN_DURATION "ctcp_short_ban_duration"
56#define KEY_CTCP_LONG_BAN_DURATION "ctcp_long_ban_duration"
57#define KEY_MAX_OWNED "max_owned"
58#define KEY_IRC_OPERATOR_EPITHET "irc_operator_epithet"
59#define KEY_NETWORK_HELPER_EPITHET "network_helper_epithet"
60#define KEY_SUPPORT_HELPER_EPITHET "support_helper_epithet"
61#define KEY_NODELETE_LEVEL "nodelete_level"
62#define KEY_MAX_USERINFO_LENGTH "max_userinfo_length"
a32da4c7 63#define KEY_GIVEOWNERSHIP_PERIOD "giveownership_timeout"
d3abe0df 64#define KEY_VALID_CHANNEL_REGEX "valid_channel_regex"
d76ed9a9
AS
65
66/* ChanServ database */
c8ca69a0 67#define KEY_VERSION_CONTROL "version_control"
d76ed9a9
AS
68#define KEY_CHANNELS "channels"
69#define KEY_NOTE_TYPES "note_types"
70
c8ca69a0
AS
71/* version control paramiter */
72#define KEY_VERSION_NUMBER "version_number"
73
d76ed9a9
AS
74/* Note type parameters */
75#define KEY_NOTE_OPSERV_ACCESS "opserv_access"
76#define KEY_NOTE_CHANNEL_ACCESS "channel_access"
77#define KEY_NOTE_SETTER_ACCESS "setter_access"
78#define KEY_NOTE_VISIBILITY "visibility"
79#define KEY_NOTE_VIS_PRIVILEGED "privileged"
80#define KEY_NOTE_VIS_CHANNEL_USERS "channel_users"
81#define KEY_NOTE_VIS_ALL "all"
82#define KEY_NOTE_MAX_LENGTH "max_length"
83#define KEY_NOTE_SETTER "setter"
84#define KEY_NOTE_NOTE "note"
85
86/* Do-not-register channels */
87#define KEY_DNR "dnr"
88#define KEY_DNR_SET "set"
89#define KEY_DNR_SETTER "setter"
90#define KEY_DNR_REASON "reason"
91
92/* Channel data */
93#define KEY_REGISTERED "registered"
94#define KEY_REGISTRAR "registrar"
95#define KEY_SUSPENDED "suspended"
96#define KEY_PREVIOUS "previous"
97#define KEY_SUSPENDER "suspender"
98#define KEY_ISSUED "issued"
99#define KEY_REVOKED "revoked"
100#define KEY_SUSPEND_EXPIRES "suspend_expires"
101#define KEY_SUSPEND_REASON "suspend_reason"
82f37c08 102#define KEY_GIVEOWNERSHIP "giveownership"
103#define KEY_STAFF_ISSUER "staff_issuer"
104#define KEY_OLD_OWNER "old_owner"
105#define KEY_TARGET "target"
106#define KEY_TARGET_ACCESS "target_access"
d76ed9a9
AS
107#define KEY_VISITED "visited"
108#define KEY_TOPIC "topic"
109#define KEY_GREETING "greeting"
110#define KEY_USER_GREETING "user_greeting"
111#define KEY_MODES "modes"
112#define KEY_FLAGS "flags"
113#define KEY_OPTIONS "options"
114#define KEY_USERS "users"
c8273589 115#define KEY_BANS "bans" /* for lamers */
d76ed9a9
AS
116#define KEY_MAX "max"
117#define KEY_NOTES "notes"
118#define KEY_TOPIC_MASK "topic_mask"
a32da4c7 119#define KEY_OWNER_TRANSFER "owner_transfer"
8b9e7d45 120#define KEY_MAXSETINFO "maxsetinfo"
d76ed9a9
AS
121
122/* User data */
123#define KEY_LEVEL "level"
124#define KEY_INFO "info"
125#define KEY_SEEN "seen"
dfaa28a4 126#define KEY_ACCESSEXPIRY "accessexpiry"
127#define KEY_CLVLEXPIRY "clvlexpiry"
128#define KEY_LASTLEVEL "lastlevel"
d76ed9a9
AS
129
130/* Ban data */
131#define KEY_OWNER "owner"
132#define KEY_REASON "reason"
133#define KEY_SET "set"
134#define KEY_DURATION "duration"
135#define KEY_EXPIRES "expires"
136#define KEY_TRIGGERED "triggered"
137
7637f48f 138#define KEY_GOD_TIMEOUT "god_timeout"
139
1136f709 140#define CHANNEL_DEFAULT_FLAGS (CHANNEL_OFFCHANNEL | CHANNEL_UNREVIEWED)
141#define CHANNEL_PRESERVED_FLAGS (CHANNEL_UNREVIEWED)
d76ed9a9
AS
142#define CHANNEL_DEFAULT_OPTIONS "lmoooanpcnat"
143
144/* Administrative messages */
145static const struct message_entry msgtab[] = {
146 { "CSMSG_CHANNELS_EXPIRED", "%i channels expired." },
147
148/* Channel registration */
149 { "CSMSG_REG_SUCCESS", "You now have ownership of $b%s$b." },
150 { "CSMSG_PROXY_SUCCESS", "%s now has ownership of $b%s$b." },
151 { "CSMSG_ALREADY_REGGED", "$b%s$b is registered to someone else." },
f8f30e75 152 { "CSMSG_MUST_BE_OPPED", "You must be a channel operator (+o) in $b%s$b to register it." },
d76ed9a9 153 { "CSMSG_PROXY_FORBIDDEN", "You may not register a channel for someone else." },
f8f30e75
AS
154 { "CSMSG_OWN_TOO_MANY", "%s already owns more than the limit of %d channels. Use FORCE to override." },
155 { "CSMSG_YOU_OWN_TOO_MANY", "You already own more than the limit of %d channels. Ask a staff member for help." },
156 { "CSMSG_ANOTHER_SERVICE", "Another service bot is in that channel already. Ask a staff member for help." },
d76ed9a9
AS
157
158/* Do-not-register channels */
159 { "CSMSG_NOT_DNR", "$b%s$b is not a valid channel name or *account." },
de9510bc 160 { "CSMSG_DNR_SEARCH_RESULTS", "$bDo-Not-Register Channels$b" },
1136f709 161 { "CSMSG_DNR_INFO", "$b%s$b is do-not-register (by $b%s$b): %s" },
162 { "CSMSG_DNR_INFO_SET", "$b%s$b is do-not-register (set %s by $b%s$b): %s" },
163 { "CSMSG_DNR_INFO_SET_EXPIRES", "$b%s$b is do-not-register (set %s by $b%s$b; expires %s): %s" },
d76ed9a9
AS
164 { "CSMSG_MORE_DNRS", "%d more do-not-register entries skipped." },
165 { "CSMSG_DNR_CHANNEL", "Only network staff may register $b%s$b." },
166 { "CSMSG_DNR_CHANNEL_MOVE", "Only network staff may move $b%s$b." },
167 { "CSMSG_DNR_ACCOUNT", "Only network staff may register channels to $b%s$b." },
168 { "CSMSG_NOREGISTER_CHANNEL", "$b%s$b has been added to the do-not-register list." },
169 { "CSMSG_NO_SUCH_DNR", "$b%s$b is not in the do-not-register list." },
170 { "CSMSG_DNR_REMOVED", "$b%s$b has been removed from the do-not-register list." },
1136f709 171 { "CSMSG_DNR_BAD_ACTION", "$b%s$b is not a recognized do-not-register action." },
172 { "CSMSG_DNR_SEARCH_RESULTS", "The following do-not-registers were found:" },
d76ed9a9
AS
173
174/* Channel unregistration */
175 { "CSMSG_UNREG_SUCCESS", "$b%s$b has been unregistered." },
176 { "CSMSG_UNREG_NODELETE", "$b%s$b is protected from unregistration." },
177 { "CSMSG_CHAN_SUSPENDED", "$b$C$b access to $b%s$b has been temporarily suspended (%s)." },
fa0fac3e 178 { "CSMSG_CONFIRM_UNREG", "To confirm this unregistration, you must use 'unregister %s %s'." },
d76ed9a9
AS
179
180/* Channel moving */
181 { "CSMSG_MOVE_SUCCESS", "Channel registration has been moved to $b%s$b." },
182 { "CSMSG_MOVE_NODELETE", "$b%s$b is protected from unregistration, and cannot be moved." },
183
184/* Channel merging */
185 { "CSMSG_MERGE_SUCCESS", "Channel successfully merged into $b%s$b." },
186 { "CSMSG_MERGE_SELF", "Merging cannot be performed if the source and target channels are the same." },
187 { "CSMSG_MERGE_NODELETE", "You may not merge a channel that is marked NoDelete." },
188 { "CSMSG_MERGE_SUSPENDED", "Merging cannot be performed if the source or target channel is suspended." },
189 { "CSMSG_MERGE_NOT_OWNER", "You must be the owner of the target channel (or a helper) to merge into the channel." },
190
191/* Handle unregistration */
192 { "CSMSG_HANDLE_UNREGISTERED", "As a result of your account unregistration, you have been deleted from all of your channels' userlists." },
193
194/* Error messages */
195 { "CSMSG_NOT_USER", "You lack access to $b%s$b." },
196 { "CSMSG_NO_CHAN_USER", "%s lacks access to $b%s$b." },
197 { "CSMSG_NO_ACCESS", "You lack sufficient access to use this command." },
198 { "CSMSG_NOT_REGISTERED", "$b%s$b has not been registered with $b$C$b." },
c8273589 199 { "CSMSG_MAXIMUM_LAMERS", "This channel has reached the lamer count limit of $b%d$b." },
d76ed9a9
AS
200 { "CSMSG_MAXIMUM_USERS", "This channel has reached the user count limit of $b%d$b." },
201 { "CSMSG_ILLEGAL_CHANNEL", "$b%s$b is an illegal channel, and cannot be registered." },
202 { "CSMSG_GODMODE_UP", "You may not use $b%s$b to op yourself unless you are on the user list. Use the $bop$b command instead." },
203 { "CSMSG_ALREADY_OPPED", "You are already opped in $b%s$b." },
55342ce8 204 { "CSMSG_ALREADY_HALFOPPED", "You are already halfopped in $b%s$b." },
d76ed9a9 205 { "CSMSG_ALREADY_VOICED", "You are already voiced in $b%s$b." },
55342ce8 206 { "CSMSG_ALREADY_DOWN", "You are not opped, halfopped, or voiced in $b%s$b." },
d76ed9a9 207 { "CSMSG_ALREADY_OPCHANNED", "There has been no net.join since the last opchan in $b%s$b." },
1136f709 208 { "CSMSG_OUT_OF_CHANNEL", "For some reason I don't seem to be in $b%s$b." },
d76ed9a9 209 { "CSMSG_OPCHAN_DONE", "I have (re-)opped myself in $b%s$b." },
6ff42e24 210 { "CSMSG_NOT_IN_CHANNEL", "I am not in %s." },
d76ed9a9
AS
211
212/* Removing yourself from a channel. */
213 { "CSMSG_NO_OWNER_DELETEME", "You cannot delete your owner access in $b%s$b." },
214 { "CSMSG_CONFIRM_DELETEME", "To really remove yourself, you must use 'deleteme %s'." },
215 { "CSMSG_DELETED_YOU", "Your $b%d$b access has been deleted from $b%s$b." },
216
217/* User management */
fd20b142 218 { "CSMSG_AUTO_DELETED", "Your %s access has expired in %s." },
dfaa28a4 219 { "CSMSG_CLVL_EXPIRED", "Your CLVL access has expired in %s." },
de9510bc 220 { "CSMSG_ADDED_USER", "Added %s to the %s user list with access %s (%d)." },
d76ed9a9
AS
221 { "CSMSG_DELETED_USER", "Deleted %s (with access %d) from the %s user list." },
222 { "CSMSG_BAD_RANGE", "Invalid access range; minimum (%d) must be greater than maximum (%d)." },
223 { "CSMSG_DELETED_USERS", "Deleted accounts matching $b%s$b with access from $b%d$b to $b%d$b from the %s user list." },
224 { "CSMSG_TRIMMED_USERS", "Trimmed $b%d users$b with access from %d to %d from the %s user list who were inactive for at least %s." },
a915f7d4
AS
225 { "CSMSG_INCORRECT_ACCESS", "%s has access $b%s$b, not %s." },
226 { "CSMSG_USER_EXISTS", "%s is already on the $b%s$b user list (with %s access)." },
5ca901c3 227 { "CSMSG_ADDUSER_PENDING", "I have sent him/her a message letting them know, and if they auth or register soon, I will finish adding them automatically." },
ac3bdc8d 228 { "CSMSG_ADDUSER_PENDING_ALREADY", "He or she is already pending addition to %s once he/she auths with $b$N$b." },
1d957482
AS
229 { "CSMSG_ADDUSER_PENDING_HEADER", "Users to add to channels pending logins:" }, /* Remove after testing? */
230 { "CSMSG_ADDUSER_PENDING_LIST", "Channel %s user %s" }, /* Remove after testing? */
1bcd5f19 231 { "CSMSG_ADDUSER_PENDING_FOOTER", "--------- End of pending list ----------" }, /* Remove after testing? */
ac3bdc8d 232 /*{ "CSMSG_ADDUSER_PENDING_NOTINCHAN", "That user is not in %s, and is not auth'd." }, */
1bcd5f19 233 { "CSMSG_ADDUSER_PENDING_TARGET", "Channel Services bot here: %s would like to add you to my userlist in channel %s, but you are not authenticated to $b$N$b. Please authenticate now and you will be added. If you do not have an account, type /msg $N help register" },
d76ed9a9
AS
234 { "CSMSG_CANNOT_TRIM", "You must include a minimum inactivity duration of at least 60 seconds to trim." },
235
236 { "CSMSG_NO_SELF_CLVL", "You cannot change your own access." },
237 { "CSMSG_NO_BUMP_ACCESS", "You cannot give users access greater than or equal to your own." },
dfaa28a4 238 { "CSMSG_NO_BUMP_EXPIRY", "You cannot give users timed $bCLVL$b's when they already have timed access." },
d76ed9a9 239 { "CSMSG_MULTIPLE_OWNERS", "There is more than one owner in %s; please use $bCLVL$b, $bDELOWNER$b and/or $bADDOWNER$b instead." },
5678501c 240 { "CSMSG_NO_OWNER", "There is no owner for %s; please use $bCLVL$b and/or $bADDOWNER$b instead." },
a32da4c7 241 { "CSMSG_TRANSFER_WAIT", "You must wait %s before you can give ownership of $b%s$b to someone else." },
d76ed9a9 242 { "CSMSG_NO_TRANSFER_SELF", "You cannot give ownership to your own account." },
1136f709 243 { "CSMSG_CONFIRM_GIVEOWNERSHIP", "To really give ownership to $b%1$s$b, you must use 'giveownership *%1$s %2$s'." },
d76ed9a9
AS
244 { "CSMSG_OWNERSHIP_GIVEN", "Ownership of $b%s$b has been transferred to account $b%s$b." },
245
246/* Ban management */
c8273589
AS
247 { "CSMSG_LAMER_ADDED", "Added $b%s$b to %s LAMERs." },
248 { "CSMSG_TIMED_LAMER_ADDED", "LAMERed $b%s$b on %s for %s." },
d76ed9a9
AS
249 { "CSMSG_KICK_BAN_DONE", "Kickbanned $b%s$b from %s." },
250 { "CSMSG_BAN_DONE", "Banned $b%s$b from %s." },
c8273589
AS
251 { "CSMSG_REASON_CHANGE", "Reason for LAMER $b%s$b changed." },
252 { "CSMSG_LAMER_EXTENDED", "Extended LAMER for $b%s$b, now expires in %s." },
9a8aa132 253 { "CSMSG_BAN_REMOVED", "Ban(s) and LAMER(s) matching $b%s$b were removed." },
c8273589 254 { "CSMSG_TRIMMED_LAMERS", "Trimmed $b%d LAMERs$b from the %s LAMER list that were inactive for at least %s." },
d76ed9a9 255 { "CSMSG_REDUNDANT_BAN", "$b%s$b is already banned in %s." },
c8273589 256 { "CSMSG_REDUNDANT_LAMER", "$b%s$b is already LAMER'd in %s." },
d76ed9a9
AS
257 { "CSMSG_DURATION_TOO_LOW", "Timed bans must last for at least 15 seconds." },
258 { "CSMSG_DURATION_TOO_HIGH", "Timed bans must last for less than 2 years." },
259 { "CSMSG_LAME_MASK", "$b%s$b is a little too general. Try making it more specific." },
260 { "CSMSG_MASK_PROTECTED", "Sorry, ban for $b%s$b conflicts with a protected user's hostmask." },
261 { "CSMSG_NO_MATCHING_USERS", "No one in $b%s$b has a hostmask matching $b%s$b." },
c8273589 262 { "CSMSG_BAN_NOT_FOUND", "Sorry, no ban or LAMER found: $b%s$b." },
d76ed9a9
AS
263 { "CSMSG_BANLIST_FULL", "The $b%s$b channel ban list is $bfull$b." },
264
265 { "CSMSG_INVALID_TRIM", "$b%s$b isn't a valid trim target." },
266
267/* Channel management */
268 { "CSMSG_CHANNEL_OPENED", "$b%s$b has been opened." },
269 { "CSMSG_WIPED_INFO_LINE", "Removed $b%s$b's infoline in $b%s$b." },
270 { "CSMSG_RESYNCED_USERS", "Synchronized users in $b%s$b with the userlist." },
271
272 { "CSMSG_TOPIC_SET", "Topic is now '%s'." },
273 { "CSMSG_NO_TOPIC", "$b%s$b does not have a default topic." },
274 { "CSMSG_TOPICMASK_CONFLICT1", "I do not know how to make that topic work with the current topic mask in $b%s$b, which is: %s" },
a915f7d4 275 { "CSMSG_TOPICMASK_CONFLICT2", "Please make sure your topic is at most %d characters and matches the topic mask pattern." },
d76ed9a9
AS
276 { "CSMSG_TOPIC_LOCKED", "The %s topic is locked." },
277 { "CSMSG_MASK_BUT_NO_TOPIC", "Warning: $b%s$b does not have a default topic, but you just set the topic mask." },
278 { "CSMSG_TOPIC_MISMATCH", "Warning: The default topic for $b%s$b does not match the topic mask; changing it anyway." },
279
280 { "CSMSG_MODES_SET", "Channel modes are now $b%s$b." },
281 { "CSMSG_DEFAULTED_MODES", "Channel modes for $b%s$b are set to their defaults." },
282 { "CSMSG_NO_MODES", "$b%s$b does not have any default modes." },
283 { "CSMSG_MODE_LOCKED", "Modes conflicting with $b%s$b are not allowed in %s." },
284 { "CSMSG_CANNOT_SET", "That setting is above your current level, so you cannot change it." },
285 { "CSMSG_OWNER_DEFAULTS", "You must have access 500 in %s to reset it to the default options." },
286 { "CSMSG_CONFIRM_DEFAULTS", "To reset %s's settings to the defaults, you must use 'set defaults %s'." },
287 { "CSMSG_SETTINGS_DEFAULTED", "All settings for %s have been reset to default values." },
288 { "CSMSG_BAD_SETLEVEL", "You cannot change any setting to above your level." },
d76ed9a9
AS
289 { "CSMSG_BAD_SETTERS", "You cannot change Setters to above your level." },
290 { "CSMSG_INVALID_MODE_LOCK", "$b%s$b is an invalid mode lock." },
7553c653 291 { "CSMSG_INVALID_NUMERIC", "$b%d$b is not a valid choice. Choose one:" },
d76ed9a9
AS
292 { "CSMSG_SET_DEFAULT_TOPIC", "$bDefaultTopic$b %s" },
293 { "CSMSG_SET_TOPICMASK", "$bTopicMask $b %s" },
294 { "CSMSG_SET_GREETING", "$bGreeting $b %s" },
295 { "CSMSG_SET_USERGREETING", "$bUserGreeting$b %s" },
296 { "CSMSG_SET_MODES", "$bModes $b %s" },
297 { "CSMSG_SET_NODELETE", "$bNoDelete $b %s" },
35caf917 298 { "CSMSG_SET_DYNLIMIT", "$bDynLimit $b %s - +l joinflood protection." },
d76ed9a9 299 { "CSMSG_SET_OFFCHANNEL", "$bOffChannel $b %s" },
35caf917 300 { "CSMSG_SET_USERINFO", "$bUserInfo $b %d - and above userinfos are shown." },
d76ed9a9 301 { "CSMSG_SET_TOPICSNARF", "$bTopicSnarf $b %d" },
35caf917
AS
302 { "CSMSG_SET_INVITEME", "$bInviteMe $b %d - Userlevel required to invite self." },
303 { "CSMSG_SET_ENFOPS", "$bEnfOps $b %d - level and above can op unknown users." },
304 { "CSMSG_SET_ENFHALFOPS", "$bEnfHalfOps $b %d - level and above can hop unknown users." },
35caf917
AS
305 { "CSMSG_SET_ENFMODES", "$bEnfModes $b %d - and above can change channel modes." },
306 { "CSMSG_SET_ENFTOPIC", "$bEnfTopic $b %d - and above can set the topic." },
307 { "CSMSG_SET_PUBCMD", "$bPubCmd $b %d - and above can use public commands." },
308 { "CSMSG_SET_SETTERS", "$bSetters $b %d - and above can change these settings." },
4b6129c0 309 { "CSMSG_SET_AUTOMODE", "$bAutoMode $b %d - %s" },
d76ed9a9
AS
310 { "CSMSG_SET_PROTECT", "$bProtect $b %d - %s" },
311 { "CSMSG_SET_TOYS", "$bToys $b %d - %s" },
312 { "CSMSG_SET_CTCPREACTION", "$bCTCPReaction$b %d - %s" },
313 { "CSMSG_SET_TOPICREFRESH", "$bTopicRefresh$b %d - %s" },
1136f709 314 { "CSMSG_SET_UNREVIEWED", "$bUnreviewed $b %d - %s" },
7637f48f 315 { "CSMSG_SET_RESYNC", "$bResync $b %d - %s" },
31f23f13 316 { "CSMSG_SET_BANTIMEOUT", "$bBanTimeout $b %d - %s" },
8b9e7d45 317 { "CSMSG_SET_MAXSETINFO", "$bMaxSetInfo $b %d - maximum characters in a setinfo line." },
4b6129c0 318
c8ca69a0
AS
319 { "CSMSG_USET_AUTOOP", "$bAutoOp $b %s" },
320 { "CSMSG_USET_AUTOVOICE", "$bAutoVoice $b %s" },
d76ed9a9 321 { "CSMSG_USET_AUTOINVITE", "$bAutoInvite $b %s" },
cd25f2e9 322 { "CSMSG_USET_AUTOJOIN", "$bAutoJoin $b %s" },
d76ed9a9
AS
323 { "CSMSG_USET_INFO", "$bInfo $b %s" },
324
325 { "CSMSG_USER_PROTECTED", "Sorry, $b%s$b is protected." },
31f23f13 326 { "CSMSG_USER_PROTECTED_KICK", "That user is protected." }, /* No $ or %s replacements! */
d76ed9a9 327 { "CSMSG_OPBY_LOCKED", "You may not op users who lack op or greater access." },
55342ce8 328 { "CSMSG_HOPBY_LOCKED", "You may not halfop users who lack halfop or greater access." },
d76ed9a9
AS
329 { "CSMSG_PROCESS_FAILED", "$b$C$b could not process some of the nicks you provided." },
330 { "CSMSG_OPPED_USERS", "Opped users in $b%s$b." },
55342ce8 331 { "CSMSG_HALFOPPED_USERS", "Halfopped users in $b%s$b." },
d76ed9a9 332 { "CSMSG_DEOPPED_USERS", "Deopped users in $b%s$b." },
55342ce8 333 { "CSMSG_DEHALFOPPED_USERS", "DeHalfopped users in $b%s$b." },
d76ed9a9
AS
334 { "CSMSG_VOICED_USERS", "Voiced users in $b%s$b." },
335 { "CSMSG_DEVOICED_USERS", "Devoiced users in $b%s$b." },
4b6129c0
AS
336
337 { "CSMSG_AUTOMODE_NONE", "Noone will be automatically oped, half-oped, or voiced." },
2a4ca4f5 338 { "CSMSG_AUTOMODE_NORMAL", "Give voice to pals, half-op to halfops, and op to ops." },
639bdb1d
AS
339 { "CSMSG_AUTOMODE_VOICE", "#1 plus give voice to everyone." },
340 { "CSMSG_AUTOMODE_HOP", "#1 plus give halfops to everyone." },
341 { "CSMSG_AUTOMODE_OP", "#1 plus give ops to everyone (not advised)" },
342 { "CSMSG_AUTOMODE_MUTE", "Give half-op to halfops, and op to ops only." },
4d69a3b1 343 { "CSMSG_AUTOMODE_ONLYVOICE", "Just voice everyone with access." },
4b6129c0 344
d76ed9a9
AS
345 { "CSMSG_PROTECT_ALL", "Non-users and users will be protected from those of equal or lower access." },
346 { "CSMSG_PROTECT_EQUAL", "Users will be protected from those of equal or lower access." },
347 { "CSMSG_PROTECT_LOWER", "Users will be protected from those of lower access." },
348 { "CSMSG_PROTECT_NONE", "No users will be protected." },
349 { "CSMSG_TOYS_DISABLED", "Toys are completely disabled." },
350 { "CSMSG_TOYS_PRIVATE", "Toys will only reply privately." },
351 { "CSMSG_TOYS_PUBLIC", "Toys will reply publicly." },
31f23f13 352
d76ed9a9
AS
353 { "CSMSG_TOPICREFRESH_NEVER", "Never refresh topic." },
354 { "CSMSG_TOPICREFRESH_3_HOURS", "Refresh every 3 hours." },
355 { "CSMSG_TOPICREFRESH_6_HOURS", "Refresh every 6 hours." },
356 { "CSMSG_TOPICREFRESH_12_HOURS", "Refresh every 12 hours." },
357 { "CSMSG_TOPICREFRESH_24_HOURS", "Refresh every 24 hours." },
31f23f13 358
7637f48f 359 { "CSMSG_RESYNC_NEVER", "Never automaticly resync userlist." },
360 { "CSMSG_RESYNC_3_HOURS", "Resync userlist every 3 hours." },
361 { "CSMSG_RESYNC_6_HOURS", "Resync userlist every 6 hours." },
362 { "CSMSG_RESYNC_12_HOURS", "Resync userlist every 12 hours." },
363 { "CSMSG_RESYNC_24_HOURS", "Resync userlist every 24 hours." },
364
35caf917 365 { "CSMSG_CTCPREACTION_NONE", "CTCPs are allowed" },
d76ed9a9
AS
366 { "CSMSG_CTCPREACTION_KICK", "Kick on disallowed CTCPs" },
367 { "CSMSG_CTCPREACTION_KICKBAN", "Kickban on disallowed CTCPs" },
368 { "CSMSG_CTCPREACTION_SHORTBAN", "Short timed ban on disallowed CTCPs" },
369 { "CSMSG_CTCPREACTION_LONGBAN", "Long timed ban on disallowed CTCPs" },
31f23f13
AS
370
371 { "CSMSG_BANTIMEOUT_NONE", "Bans will not be removed automatically."},
372 { "CSMSG_BANTIMEOUT_10M", "Bans will be removed after 10 minutes."},
373 { "CSMSG_BANTIMEOUT_2H", "Bans will be removed after 2 hours."},
374 { "CSMSG_BANTIMEOUT_4H", "Bans will be removed after 4 hours."},
375 { "CSMSG_BANTIMEOUT_1D", "Bans will be removed after 24 hours."},
376 { "CSMSG_BANTIMEOUT_1W", "Bans will be removed after 1 week."},
d76ed9a9
AS
377
378 { "CSMSG_INVITED_USER", "Invited $b%s$b to join %s." },
379 { "CSMSG_INVITING_YOU_REASON", "$b%s$b invites you to join %s: %s" },
380 { "CSMSG_INVITING_YOU", "$b%s$b invites you to join %s." },
5f1600ab 381 { "CSMSG_CANNOT_INVITE", "You cannot invite %s to %s." },
d76ed9a9
AS
382 { "CSMSG_ALREADY_PRESENT", "%s is already in $b%s$b." },
383 { "CSMSG_YOU_ALREADY_PRESENT", "You are already in $b%s$b." },
1136f709 384 { "CSMSG_LOW_CHANNEL_ACCESS", "You lack sufficient access in %s for $S to invite you." },
d76ed9a9
AS
385 { "CSMSG_INFOLINE_TOO_LONG", "Your infoline may not exceed %u characters." },
386 { "CSMSG_BAD_INFOLINE", "You may not use the character \\%03o in your infoline." },
387
388 { "CSMSG_KICK_DONE", "Kicked $b%s$b from %s." },
c8273589 389 { "CSMSG_NO_BANS", "No bans found on $b%s$b." },
d76ed9a9
AS
390 { "CSMSG_BANS_REMOVED", "Removed all channel bans from $b%s$b." },
391
392/* Channel userlist */
338a82b5
AS
393 { "CSMSG_ACCESS_ALL_HEADER_NORMAL", "$b%s Users From Level %s To %s$b" },
394 { "CSMSG_ACCESS_SEARCH_HEADER_NORMAL", "$b%s Users From Level %s To %s Matching %s$b" },
395 /* uncomment if needed to adujust styles (and change code below)
396 { "CSMSG_ACCESS_ALL_HEADER_CLEAN", "$b%s Users From Level %s To %s$b" },
397 { "CSMSG_ACCESS_SEARCH_HEADER_CLEAN", "$b%s Users From Level %s To %s Matching %s$b" },
398 { "CSMSG_ACCESS_ALL_HEADER_ADVANCED", "$b%s Users From Level %s To %s$b" },
399 { "CSMSG_ACCESS_SEARCH_HEADER_ADVANCED", "$b%s Users From Level %s To %s Matching %s$b" },
d9896a83
AS
400 { "CSMSG_ACCESS_ALL_HEADER_CLASSIC", "$b%s Users From Level %s To %s$b" },
401 { "CSMSG_ACCESS_SEARCH_HEADER_CLASSIC", "$b%s Users From Level %s To %s Matching %s$b" },
338a82b5 402 */
d76ed9a9 403 { "CSMSG_INVALID_ACCESS", "$b%s$b is an invalid access level." },
de9510bc 404 { "CSMSG_CHANGED_ACCESS", "%s now has access $b%s$b (%u) in %s." },
c8273589 405 { "CSMSG_LAMERS_HEADER", "$bLamers in %s$b" },
d76ed9a9
AS
406
407/* Channel note list */
408 { "CSMSG_NOTELIST_HEADER", "Notes for $b%s$b:" },
409 { "CSMSG_REPLACED_NOTE", "Replaced old $b%s$b note on %s (set by %s): %s" },
410 { "CSMSG_NOTE_FORMAT", "%s (set by %s): %s" },
411 { "CSMSG_NOTELIST_END", "End of notes for $b%s$b." },
412 { "CSMSG_NOTELIST_EMPTY", "There are no (visible) notes for $b%s$b." },
413 { "CSMSG_NO_SUCH_NOTE", "Channel $b%s$b does not have a note named $b%s$b." },
414 { "CSMSG_BAD_NOTE_TYPE", "Note type $b%s$b does not exist." },
415 { "CSMSG_NOTE_SET", "Note $b%s$b set in channel $b%s$b." },
416 { "CSMSG_NOTE_REMOVED", "Note $b%s$b removed in channel $b%s$b." },
417 { "CSMSG_BAD_NOTE_ACCESS", "$b%s$b is not a valid note access type." },
418 { "CSMSG_BAD_MAX_LENGTH", "$b%s$b is not a valid maximum length (must be between 20 and 450 inclusive)." },
419 { "CSMSG_NOTE_MODIFIED", "Note type $b%s$b modified." },
420 { "CSMSG_NOTE_CREATED", "Note type $b%s$b created." },
421 { "CSMSG_NOTE_TYPE_USED", "Note type $b%s$b is in use; give the FORCE argument to delete it." },
422 { "CSMSG_NOTE_DELETED", "Note type $b%s$b deleted." },
423
424/* Channel [un]suspension */
425 { "CSMSG_ALREADY_SUSPENDED", "$b%s$b is already suspended." },
426 { "CSMSG_NOT_SUSPENDED", "$b%s$b is not suspended." },
427 { "CSMSG_SUSPENDED", "$b$C$b access to $b%s$b has been temporarily suspended." },
428 { "CSMSG_UNSUSPENDED", "$b$C$b access to $b%s$b has been restored." },
429 { "CSMSG_SUSPEND_NODELETE", "$b%s$b is protected from unregistration, and cannot be suspended." },
430 { "CSMSG_USER_SUSPENDED", "$b%s$b's access to $b%s$b has been suspended." },
431 { "CSMSG_USER_UNSUSPENDED", "$b%s$b's access to $b%s$b has been restored." },
432
433/* Access information */
434 { "CSMSG_IS_CHANSERV", "$b$C$b is the $bchannel service bot$b." },
435 { "CSMSG_MYACCESS_SELF_ONLY", "You may only see the list of infolines for yourself (by using $b%s$b with no arguments)." },
436 { "CSMSG_SQUAT_ACCESS", "$b%s$b does not have access to any channels." },
437 { "CSMSG_INFOLINE_LIST", "Showing all channel entries for account $b%s$b:" },
438 { "CSMSG_USER_NO_ACCESS", "%s lacks access to %s." },
de9510bc 439 { "CSMSG_USER_HAS_ACCESS", "%s has $b%s$b access (%d) in %s." },
d76ed9a9 440 { "CSMSG_HELPER_NO_ACCESS", "%s lacks access to %s but has $bsecurity override$b enabled." },
de9510bc 441 { "CSMSG_HELPER_HAS_ACCESS", "%s has $b%s$b access (%d) in %s and has $bsecurity override$b enabled." },
d76ed9a9
AS
442 { "CSMSG_LAZY_SMURF_TARGET", "%s is %s ($bIRCOp$b; not logged in)." },
443 { "CSMSG_SMURF_TARGET", "%s is %s ($b%s$b)." },
1136f709 444 { "CSMSG_OPERATOR_TITLE", "IRC operator" },
445 { "CSMSG_UC_H_TITLE", "network helper" },
446 { "CSMSG_LC_H_TITLE", "support helper" },
d76ed9a9
AS
447 { "CSMSG_LAME_SMURF_TARGET", "%s is an IRC operator." },
448
449/* Seen information */
450 { "CSMSG_NEVER_SEEN", "%s has never been seen in $b%s$b." },
451 { "CSMSG_USER_SEEN", "%s was last seen in $b%s$b %s ago." },
452 { "CSMSG_USER_VACATION", "%s is currently on vacation." },
453 { "CSMSG_USER_PRESENT", "%s is in the channel $bright now$b." },
454
455/* Names information */
456 { "CSMSG_CHANNEL_NAMES", "Users in $b%s$b:%s" },
457 { "CSMSG_END_NAMES", "End of names in $b%s$b" },
458
459/* Channel information */
de9510bc
AS
460 { "CSMSG_CHANNEL_INFO", "$bInformation About %s$b" },
461 { "CSMSG_BAR", "----------------------------------------"},
d76ed9a9
AS
462 { "CSMSG_CHANNEL_TOPIC", "$bDefault Topic: $b%s" },
463 { "CSMSG_CHANNEL_MODES", "$bMode Lock: $b%s" },
464 { "CSMSG_CHANNEL_NOTE", "$b%s:%*s$b%s" },
465 { "CSMSG_CHANNEL_MAX", "$bRecord Visitors: $b%i" },
c8273589
AS
466 { "CSMSG_CHANNEL_OWNER", "$bOwner: $b%s" },
467 { "CSMSG_CHANNEL_LAMERS", "$bLamer Count: $b%i" },
d76ed9a9
AS
468 { "CSMSG_CHANNEL_USERS", "$bTotal User Count: $b%i" },
469 { "CSMSG_CHANNEL_REGISTRAR", "$bRegistrar: $b%s" },
470 { "CSMSG_CHANNEL_SUSPENDED", "$b%s$b is suspended:" },
471 { "CSMSG_CHANNEL_HISTORY", "Suspension history for $b%s$b:" },
472 { "CSMSG_CHANNEL_SUSPENDED_0", " by %s: %s" },
473 { "CSMSG_CHANNEL_SUSPENDED_1", " by %s; expires in %s: %s" },
474 { "CSMSG_CHANNEL_SUSPENDED_2", " by %s; expired %s ago: %s" },
475 { "CSMSG_CHANNEL_SUSPENDED_3", " by %s; revoked %s ago: %s" },
476 { "CSMSG_CHANNEL_SUSPENDED_4", " %s ago by %s: %s" },
477 { "CSMSG_CHANNEL_SUSPENDED_5", " %s ago by %s; expires in %s: %s" },
478 { "CSMSG_CHANNEL_SUSPENDED_6", " %s ago by %s; expired %s ago: %s" },
479 { "CSMSG_CHANNEL_SUSPENDED_7", " %s ago by %s; revoked %s ago: %s" },
480 { "CSMSG_CHANNEL_REGISTERED", "$bRegistered: $b%s ago." },
481 { "CSMSG_CHANNEL_VISITED", "$bVisited: $b%s ago." },
82f37c08 482 { "CSMSG_CHANNEL_OWNERSHIP_HISTORY", "Ownership transfer history for $b%s$b" },
483 { "CSMSG_CHANNEL_OWNERSHIP_NORMAL", "from %s to %s (%d access) on %s" },
484 { "CSMSG_CHANNEL_OWNERSHIP_STAFF_REASON", "from %s to %s (%d access) by %s on %s reason: %s" },
485 { "CSMSG_CHANNEL_OWNERSHIP_STAFF", "from %s to %s (%d access) by %s on %s" },
de9510bc 486 { "CSMSG_CHANNEL_END", "---------------End of Info--------------"},
cde20a3f 487 { "CSMSG_CHANNEL_END_CLEAN", "End of Info"},
d76ed9a9 488
de9510bc 489 { "CSMSG_PEEK_INFO", "$bStatus of %s$b" },
d76ed9a9
AS
490 { "CSMSG_PEEK_TOPIC", "$bTopic: $b%s" },
491 { "CSMSG_PEEK_MODES", "$bModes: $b%s" },
492 { "CSMSG_PEEK_USERS", "$bTotal users: $b%d" },
493 { "CSMSG_PEEK_OPS", "$bOps:$b" },
494 { "CSMSG_PEEK_NO_OPS", "$bOps: $bNone present" },
de9510bc 495 { "CSMSG_PEEK_END", "-------------End of Status--------------" },
d76ed9a9
AS
496
497/* Network information */
498 { "CSMSG_NETWORK_INFO", "Network Information:" },
499 { "CSMSG_NETWORK_SERVERS", "$bServers: $b%i" },
500 { "CSMSG_NETWORK_USERS", "$bTotal Users: $b%i" },
c8273589 501 { "CSMSG_NETWORK_LAMERS", "$bTotal Lamer Count: $b%i" },
d76ed9a9
AS
502 { "CSMSG_NETWORK_CHANUSERS", "$bTotal User Count: $b%i" },
503 { "CSMSG_NETWORK_OPERS", "$bIRC Operators: $b%i" },
504 { "CSMSG_NETWORK_CHANNELS","$bRegistered Channels: $b%i" },
505 { "CSMSG_SERVICES_UPTIME", "$bServices Uptime: $b%s" },
506 { "CSMSG_BURST_LENGTH", "$bLast Burst Length: $b%s" },
507
508/* Staff list */
509 { "CSMSG_NETWORK_STAFF", "$bOnline Network Staff:$b" },
510 { "CSMSG_STAFF_OPERS", "$bIRC Operators:$b" },
511 { "CSMSG_STAFF_HELPERS", "$bHelpers:$b" },
512
513/* Channel searches */
514 { "CSMSG_ACTION_INVALID", "$b%s$b is not a recognized search action." },
515 { "CSMSG_UNVISITED_HEADER", "Showing a maximum of %d channels unvisited for $b%s$b:" },
516 { "CSMSG_UNVISITED_DATA", "%s: $b%s$b" },
de9510bc 517 { "CSMSG_CHANNEL_SEARCH_RESULTS", "$bChannels Found Matching Search$b" },
d76ed9a9
AS
518
519/* Channel configuration */
520 { "CSMSG_INVALID_OPTION", "$b%s$b is not a valid %s option." },
1136f709 521 { "CSMSG_INVALID_CFLAG", "$b%s$b is not a recognized channel flag." },
de9510bc
AS
522 { "CSMSG_CHANNEL_OPTIONS", "$bChannel Options for %s$b" },
523 { "CSMSG_CHANNEL_OPTIONS_END", "-------------End of Options-------------" },
d76ed9a9
AS
524 { "CSMSG_GREETING_TOO_LONG", "Your greeting ($b%d$b characters) must be shorter than $b%d$b characters." },
525
526/* User settings */
527 { "CSMSG_USER_OPTIONS", "User Options:" },
35caf917 528// { "CSMSG_USER_PROTECTED", "That user is protected." },
d76ed9a9
AS
529
530/* Toys */
531 { "CSMSG_UNF_RESPONSE", "I don't want to be part of your sick fantasies!" },
532 { "CSMSG_PING_RESPONSE", "Pong!" },
533 { "CSMSG_WUT_RESPONSE", "wut" },
534 { "CSMSG_BAD_NUMBER", "$b%s$b is an invalid number. Please use a number greater than 1 with this command." },
535 { "CSMSG_BAD_DIE_FORMAT", "I do not understand $b%s$b. Please use either a single number or standard 4d6+3 format." },
536 { "CSMSG_BAD_DICE_COUNT", "%lu is too many dice. Please use at most %lu." },
537 { "CSMSG_DICE_ROLL", "The total is $b%lu$b from rolling %lud%lu+%lu." },
538 { "CSMSG_DIE_ROLL", "A $b%lu$b shows on the %lu-sided die." },
539 { "CSMSG_HUGGLES_HIM", "\001ACTION huggles %s\001" },
540 { "CSMSG_HUGGLES_YOU", "\001ACTION huggles you\001" },
87708af4 541 { "CSMSG_ROULETTE_LOADS", "\001ACTION loads the gun and sets it on the table\001" },
a6fa0035 542 { "CSMSG_ROULETTE_NEW", "Please type %croulette to start a new round" } ,
87708af4 543 { "CSMSG_ROULETTE_BETTER_LUCK", "Better luck next time, %s" },
544 { "CSMSG_ROULETTE_BANG", "Bang!!!" } ,
545 { "CSMSG_ROULETTE_CLICK", "Click" } ,
d76ed9a9 546
b404335b 547 { "CSMSG_SPIN_WHEEL1", "\001ACTION spins the wheel of misfortune for: %s\001" } ,
548 { "CSMSG_SPIN_WHEEL2", "Round and round she goes, where she stops, nobody knows...!" } ,
549 { "CSMSG_SPIN_WHEEL3", "The wheel of misfortune has stopped on..." } ,
550
5e6460e4
AS
551 { "CSMSG_SPIN_PEER", "Peer: Peer's gonna eat you!!!!" } ,
552 { "CSMSG_SPIN_PARTALL", "Part all: Part all channels" } ,
553 { "CSMSG_SPIN_Gline", "Gline: /gline for random amount of time" } ,
554 { "CSMSG_SPIN_SHUN", "Shun: /shun for random amount of time" } ,
555 { "CSMSG_SPIN_NOTHING", "Nothing: Absolutely nothing" } ,
556 { "CSMSG_SPIN_RANDJOIN", "Random join: Join a bunch of random channels, then /part all of 'em several times" } ,
557 { "CSMSG_SPIN_ABUSEWHOIS", "Abuse whois: Abuse line added to /whois info" } ,
558 { "CSMSG_SPIN_KICKALL", "Kick all: /kick from each channel you're in" } ,
559 { "CSMSG_SPIN_NICKCHANGE", "Nick change: Random Nick Change" } ,
560 { "CSMSG_SPIN_KILL", "Kill: /kill" } ,
561 { "CSMSG_SPIN_SVSIGNORE", "Ignore: Services ignore for random amount of time" } ,
562 { "CSMSG_SPIN_SVSIGNORE_OPER", "Ignore: I'm trying REALLY hard to ignore you, but your IRCOp smell is overwhelming!" } ,
563 { "CSMSG_SPIN_KICKBANALL", "Kickban all: /kick and ban from each channel your're in" } ,
564 { "CSMSG_SPIN_UNKNOWN", "Error: I don't know how to '%s' you, so you live for now..." },
b404335b 565
d76ed9a9 566/* Other things */
de9510bc 567 { "CSMSG_EVENT_SEARCH_RESULTS", "$bChannel Events for %s$b" },
23475fc6 568 { "CSMSG_LAST_INVALID", "Invalid argument. must be 1-200" },
08895577 569 { "CSMSG_DEFCON_NO_NEW_CHANNELS", "You cannot register new channels at this time, please try again soon." },
570 { "CSMSG_DEFCON_NO_MODE_CHANGE", "You cannot change the MODE at this time, please try again soon." },
d76ed9a9
AS
571 { NULL, NULL }
572};
573
574/* eject_user and unban_user flags */
575#define ACTION_KICK 0x0001
576#define ACTION_BAN 0x0002
c8273589
AS
577#define ACTION_ADD_LAMER 0x0004
578#define ACTION_ADD_TIMED_LAMER 0x0008
d76ed9a9 579#define ACTION_UNBAN 0x0010
c8273589 580#define ACTION_DEL_LAMER 0x0020
d76ed9a9
AS
581
582/* The 40 allows for [+-ntlksimprD] and lots of fudge factor. */
583#define MODELEN 40 + KEYLEN
584#define PADLEN 21
585#define ACCESSLEN 10
586
587#define CSFUNC_ARGS user, channel, argc, argv, cmd
588
589#define CHANSERV_FUNC(NAME) MODCMD_FUNC(NAME)
567a5f26 590#define CHANSERV_SYNTAX() svccmd_send_help_brief(user, chanserv, cmd)
d76ed9a9
AS
591#define REQUIRE_PARAMS(N) if(argc < (N)) { \
592 reply("MSG_MISSING_PARAMS", argv[0]); \
593 CHANSERV_SYNTAX(); \
594 return 0; }
595
596DECLARE_LIST(dnrList, struct do_not_register *);
1136f709 597DEFINE_LIST(dnrList, struct do_not_register *)
d76ed9a9
AS
598
599static int eject_user(struct userNode *user, struct chanNode *channel, unsigned int argc, char *argv[], struct svccmd *cmd, int action);
600
601struct userNode *chanserv;
602dict_t note_types;
603int off_channel;
7637f48f 604extern struct string_list *autojoin_channels;
d76ed9a9
AS
605static dict_t plain_dnrs, mask_dnrs, handle_dnrs;
606static struct log_type *CS_LOG;
ac3bdc8d
AS
607struct adduserPending* adduser_pendings = NULL;
608unsigned int adduser_pendings_count = 0;
7637f48f 609unsigned long god_timeout;
d76ed9a9
AS
610
611static struct
612{
613 struct channelList support_channels;
614 struct mod_chanmode default_modes;
615
616 unsigned long db_backup_frequency;
617 unsigned long channel_expire_frequency;
31f23f13 618 unsigned long ban_timeout_frequency;
1136f709 619 unsigned long dnr_expire_frequency;
d76ed9a9
AS
620
621 long info_delay;
622 unsigned int adjust_delay;
623 long channel_expire_delay;
624 unsigned int nodelete_level;
625
626 unsigned int adjust_threshold;
627 int join_flood_threshold;
628
629 unsigned int greeting_length;
630 unsigned int refresh_period;
a32da4c7 631 unsigned int giveownership_period;
d76ed9a9
AS
632
633 unsigned int max_owned;
634 unsigned int max_chan_users;
c8273589 635 unsigned int max_chan_bans; /* lamers */
d76ed9a9 636 unsigned int max_userinfo_length;
d3abe0df 637 unsigned int valid_channel_regex_set : 1;
638
639 regex_t valid_channel_regex;
d76ed9a9
AS
640
641 struct string_list *set_shows;
642 struct string_list *eightball;
643 struct string_list *old_ban_names;
5e6460e4 644 struct string_list *wheel;
d76ed9a9
AS
645
646 const char *ctcp_short_ban_duration;
647 const char *ctcp_long_ban_duration;
648
649 const char *irc_operator_epithet;
650 const char *network_helper_epithet;
651 const char *support_helper_epithet;
652} chanserv_conf;
653
654struct listData
655{
656 struct userNode *user;
657 struct userNode *bot;
658 struct chanNode *channel;
659 const char *search;
660 unsigned short lowest;
661 unsigned short highest;
662 struct userData **users;
663 struct helpfile_table table;
664};
665
666enum note_access_type
667{
668 NOTE_SET_CHANNEL_ACCESS,
669 NOTE_SET_CHANNEL_SETTER,
670 NOTE_SET_PRIVILEGED
671};
672
673enum note_visible_type
674{
675 NOTE_VIS_ALL,
676 NOTE_VIS_CHANNEL_USERS,
677 NOTE_VIS_PRIVILEGED
678};
679
b404335b 680struct io_fd *socket_io_fd;
681extern struct cManagerNode cManager;
682
d76ed9a9
AS
683struct note_type
684{
685 enum note_access_type set_access_type;
686 union {
687 unsigned int min_opserv;
688 unsigned short min_ulevel;
689 } set_access;
690 enum note_visible_type visible_type;
691 unsigned int max_length;
692 unsigned int refs;
693 char name[1];
694};
695
696struct note
697{
698 struct note_type *type;
699 char setter[NICKSERV_HANDLE_LEN+1];
700 char note[1];
701};
702
703static unsigned int registered_channels;
704static unsigned int banCount;
705
706static const struct {
707 char *name;
708 char *title;
709 unsigned short level;
710 char ch;
a915f7d4 711} accessLevels[] = { /* MUST be orderd less to most! */
2a4ca4f5 712 { "pal", "Pal", UL_PEON, '+' },
d76ed9a9 713 { "peon", "Peon", UL_PEON, '+' },
55342ce8 714 { "halfop", "HalfOp", UL_HALFOP, '%' },
d76ed9a9 715 { "op", "Op", UL_OP, '@' },
4048352e 716 { "manager", "Manager", UL_MANAGER, '%' },
d76ed9a9
AS
717 { "coowner", "Coowner", UL_COOWNER, '*' },
718 { "owner", "Owner", UL_OWNER, '!' },
719 { "helper", "BUG:", UL_HELPER, 'X' }
720};
721
35caf917 722/* If you change this, change the enum in chanserv.h also, or stack smashing will commence. */
d76ed9a9
AS
723static const struct {
724 char *format_name;
725 char *db_name;
726 unsigned short default_value;
727 unsigned int old_idx;
728 unsigned int old_flag;
729 unsigned short flag_value;
730} levelOptions[] = {
e3b2f789
AS
731 { "CSMSG_SET_ENFOPS", "enfops", 300, 1, 0, 0 },
732 { "CSMSG_SET_ENFHALFOPS", "enfhalfops", 300, 1, 0, 0 },
733 { "CSMSG_SET_ENFMODES", "enfmodes", 200, 3, 0, 0 },
734 { "CSMSG_SET_ENFTOPIC", "enftopic", 200, 4, 0, 0 },
735 { "CSMSG_SET_PUBCMD", "pubcmd", 0, 5, 0, 0 },
736 { "CSMSG_SET_SETTERS", "setters", 400, 7, 0, 0 },
737 { "CSMSG_SET_USERINFO", "userinfo", 1, ~0, CHANNEL_INFO_LINES, 1 },
738 { "CSMSG_SET_INVITEME", "inviteme", 1, ~0, CHANNEL_PEON_INVITE, 200 },
d76ed9a9
AS
739 { "CSMSG_SET_TOPICSNARF", "topicsnarf", 501, ~0, CHANNEL_TOPIC_SNARF, 1 }
740};
741
742struct charOptionValues {
743 char value;
744 char *format_name;
4b6129c0
AS
745} automodeValues[] = {
746 { 'n', "CSMSG_AUTOMODE_NONE" },
747 { 'y', "CSMSG_AUTOMODE_NORMAL" },
748 { 'v', "CSMSG_AUTOMODE_VOICE" },
749 { 'h', "CSMSG_AUTOMODE_HOP" },
750 { 'o', "CSMSG_AUTOMODE_OP" },
4d69a3b1
AS
751 { 'm', "CSMSG_AUTOMODE_MUTE" },
752 { 'l', "CSMSG_AUTOMODE_ONLYVOICE" }
c8ca69a0 753}, protectValues[] = {
d76ed9a9
AS
754 { 'a', "CSMSG_PROTECT_ALL" },
755 { 'e', "CSMSG_PROTECT_EQUAL" },
756 { 'l', "CSMSG_PROTECT_LOWER" },
757 { 'n', "CSMSG_PROTECT_NONE" }
758}, toysValues[] = {
759 { 'd', "CSMSG_TOYS_DISABLED" },
760 { 'n', "CSMSG_TOYS_PRIVATE" },
761 { 'p', "CSMSG_TOYS_PUBLIC" }
762}, topicRefreshValues[] = {
763 { 'n', "CSMSG_TOPICREFRESH_NEVER" },
764 { '1', "CSMSG_TOPICREFRESH_3_HOURS" },
765 { '2', "CSMSG_TOPICREFRESH_6_HOURS" },
766 { '3', "CSMSG_TOPICREFRESH_12_HOURS" },
767 { '4', "CSMSG_TOPICREFRESH_24_HOURS" }
768}, ctcpReactionValues[] = {
35caf917 769 { 'n', "CSMSG_CTCPREACTION_NONE" },
d76ed9a9
AS
770 { 'k', "CSMSG_CTCPREACTION_KICK" },
771 { 'b', "CSMSG_CTCPREACTION_KICKBAN" },
772 { 't', "CSMSG_CTCPREACTION_SHORTBAN" },
773 { 'T', "CSMSG_CTCPREACTION_LONGBAN" }
31f23f13
AS
774}, banTimeoutValues[] = {
775 { '0', "CSMSG_BANTIMEOUT_NONE" },
776 { '1', "CSMSG_BANTIMEOUT_10M" },
777 { '2', "CSMSG_BANTIMEOUT_2H" },
778 { '3', "CSMSG_BANTIMEOUT_4H" },
779 { '4', "CSMSG_BANTIMEOUT_1D" },
780 { '5', "CSMSG_BANTIMEOUT_1W" }
d3347099
AS
781},
782resyncValues[] = {
7637f48f 783 { 'n', "CSMSG_RESYNC_NEVER" },
784 { '1', "CSMSG_RESYNC_3_HOURS" },
785 { '2', "CSMSG_RESYNC_6_HOURS" },
786 { '3', "CSMSG_RESYNC_12_HOURS" },
787 { '4', "CSMSG_RESYNC_24_HOURS" }
d76ed9a9
AS
788};
789
790static const struct {
791 char *format_name;
792 char *db_name;
793 char default_value;
794 unsigned int old_idx;
795 unsigned char count;
796 struct charOptionValues *values;
797} charOptions[] = {
4b6129c0 798 { "CSMSG_SET_AUTOMODE", "automode", 'y', 99, ArrayLength(automodeValues), automodeValues },
c8ca69a0
AS
799 { "CSMSG_SET_PROTECT", "protect", 'l', 0, ArrayLength(protectValues), protectValues },
800 { "CSMSG_SET_TOYS", "toys", 'p', 6, ArrayLength(toysValues), toysValues },
d76ed9a9 801 { "CSMSG_SET_TOPICREFRESH", "topicrefresh", 'n', 8, ArrayLength(topicRefreshValues), topicRefreshValues },
31f23f13 802 { "CSMSG_SET_CTCPREACTION", "ctcpreaction", 'n', 10, ArrayLength(ctcpReactionValues), ctcpReactionValues },
7637f48f 803 { "CSMSG_SET_BANTIMEOUT", "bantimeout", '0', 11, ArrayLength(banTimeoutValues), banTimeoutValues },
804 { "CSMSG_SET_RESYNC", "resync", 'n', 12, ArrayLength(resyncValues), resyncValues },
d76ed9a9
AS
805};
806
807struct userData *helperList;
808struct chanData *channelList;
809static struct module *chanserv_module;
810static unsigned int userCount;
c8ca69a0
AS
811unsigned int chanserv_read_version = 0; /* db version control */
812
813#define CHANSERV_DB_VERSION 2
d76ed9a9 814
d76ed9a9
AS
815#define GetChannelAccess(channel, handle) _GetChannelUser(channel, handle, 0, 0)
816#define GetTrueChannelAccess(channel, handle) _GetChannelUser(channel, handle, 0, 1)
817
b404335b 818void sputsock(const char *text, ...) PRINTF_LIKE(1, 2);
819
820void
821sputsock(const char *text, ...)
822{
823 va_list arg_list;
824 char buffer[MAXLEN];
825 int pos;
826
827 if (!cManager.uplink || cManager.uplink->state == DISCONNECTED) return;
828 buffer[0] = '\0';
829 va_start(arg_list, text);
830 pos = vsnprintf(buffer, MAXLEN - 2, text, arg_list);
831 va_end(arg_list);
832 if (pos < 0 || pos > (MAXLEN - 2)) pos = MAXLEN - 2;
833 buffer[pos] = 0;
834 log_replay(MAIN_LOG, true, buffer);
835 buffer[pos++] = '\n';
836 buffer[pos] = 0;
837 ioset_write(socket_io_fd, buffer, pos);
838}
839
d76ed9a9
AS
840unsigned short
841user_level_from_name(const char *name, unsigned short clamp_level)
842{
843 unsigned int level = 0, ii;
844 if(isdigit(name[0]))
845 level = strtoul(name, NULL, 10);
846 else for(ii = 0; (ii < ArrayLength(accessLevels)) && !level; ++ii)
847 if(!irccasecmp(name, accessLevels[ii].name))
848 level = accessLevels[ii].level;
849 if(level > clamp_level)
850 return 0;
851 return level;
852}
853
e6892204
AS
854char *
855user_level_name_from_level(int level)
856{
857 unsigned int ii;
a915f7d4
AS
858 char* highest;
859
860 highest = "None";
861 if(level >= 1)
2a4ca4f5 862 highest = "Pal";
e6892204 863 for(ii = 0; (ii < ArrayLength(accessLevels)); ii++)
a915f7d4
AS
864 if(level >= accessLevels[ii].level)
865 highest = accessLevels[ii].title;
866 return(highest);
e6892204
AS
867}
868
869
d76ed9a9
AS
870int
871parse_level_range(unsigned short *minl, unsigned short *maxl, const char *arg)
872{
873 char *sep;
874 *minl = strtoul(arg, &sep, 10);
875 if(*sep == '\0')
876 {
877 *maxl = *minl;
878 return 1;
879 }
880 else if(*sep == '-')
881 {
882 *maxl = strtoul(sep+1, &sep, 10);
883 return *sep == '\0';
884 }
885 else
886 return 0;
887}
888
889struct userData*
890_GetChannelUser(struct chanData *channel, struct handle_info *handle, int override, int allow_suspended)
891{
892 struct userData *uData, **head;
893
894 if(!channel || !handle)
895 return NULL;
896
897 if(override && HANDLE_FLAGGED(handle, HELPING)
898 && ((handle->opserv_level >= chanserv_conf.nodelete_level) || !IsProtected(channel)))
899 {
900 for(uData = helperList;
901 uData && uData->handle != handle;
902 uData = uData->next);
903
904 if(!uData)
905 {
906 uData = calloc(1, sizeof(struct userData));
907 uData->handle = handle;
908
909 uData->access = UL_HELPER;
910 uData->seen = 0;
911
912 uData->info = NULL;
913
914 uData->prev = NULL;
915 uData->next = helperList;
916 if(helperList)
917 helperList->prev = uData;
918 helperList = uData;
919 }
920
921 head = &helperList;
922 }
923 else
924 {
925 for(uData = channel->users; uData; uData = uData->next)
926 if((uData->handle == handle) && (allow_suspended || !IsUserSuspended(uData)))
927 break;
928
929 head = &(channel->users);
930 }
931
932 if(uData && (uData != *head))
933 {
934 /* Shuffle the user to the head of whatever list he was in. */
935 if(uData->next)
936 uData->next->prev = uData->prev;
937 if(uData->prev)
938 uData->prev->next = uData->next;
939
940 uData->prev = NULL;
941 uData->next = *head;
942
943 if(*head)
944 (**head).prev = uData;
945 *head = uData;
946 }
947
948 return uData;
949}
950
951/* Returns non-zero if user has at least the minimum access.
952 * exempt_owner is set when handling !set, so the owner can set things
953 * to/from >500.
954 */
955int check_user_level(struct chanNode *channel, struct userNode *user, enum levelOption opt, int allow_override, int exempt_owner)
956{
957 struct userData *uData;
958 struct chanData *cData = channel->channel_info;
959 unsigned short minimum = cData->lvlOpts[opt];
960 if(!minimum)
961 return 1;
962 uData = _GetChannelUser(cData, user->handle_info, allow_override, 0);
963 if(!uData)
964 return 0;
965 if(minimum <= uData->access)
966 return 1;
967 if((minimum > UL_OWNER) && (uData->access == UL_OWNER) && exempt_owner)
968 return 1;
969 return 0;
970}
971
972/* Scan for other users authenticated to the same handle
973 still in the channel. If so, keep them listed as present.
974
975 user is optional, if not null, it skips checking that userNode
976 (for the handle_part function) */
977static void
978scan_user_presence(struct userData *uData, struct userNode *user)
979{
980 struct modeNode *mn;
981
982 if(IsSuspended(uData->channel)
983 || IsUserSuspended(uData)
984 || !(mn = find_handle_in_channel(uData->channel->channel, uData->handle, user)))
985 {
986 uData->present = 0;
987 }
988 else
989 {
990 uData->present = 1;
991 uData->seen = now;
992 }
993}
994
995static void
1136f709 996chanserv_ctcp_check(struct userNode *user, struct chanNode *channel, const char *text, UNUSED_ARG(struct userNode *bot), UNUSED_ARG(unsigned int is_notice))
d76ed9a9
AS
997{
998 unsigned int eflags, argc;
999 char *argv[4];
1000 static char *bad_ctcp_reason = "CTCPs to this channel are forbidden.";
1001
1002 /* Bail early if channel is inactive or doesn't restrict CTCPs, or sender is a service */
1003 if(!channel->channel_info
1004 || IsSuspended(channel->channel_info)
1005 || IsService(user)
1006 || !ircncasecmp(text, "ACTION ", 7))
1007 return;
35caf917
AS
1008 /* We dont punish people we know -Rubin
1009 * * Figure out the minimum level needed to CTCP the channel *
1010 *
1011 * if(check_user_level(channel, user, lvlCTCPUsers, 1, 0))
1012 * return;
1013 */
1014 /* If they are a user of the channel, they are exempt */
1015 if(_GetChannelUser(channel->channel_info, user->handle_info, 0, 0))
1016 return;
d76ed9a9
AS
1017 /* We need to enforce against them; do so. */
1018 eflags = 0;
1136f709 1019 argv[0] = (char*)text;
d76ed9a9
AS
1020 argv[1] = user->nick;
1021 argc = 2;
1022 if(GetUserMode(channel, user))
1023 eflags |= ACTION_KICK;
1024 switch(channel->channel_info->chOpts[chCTCPReaction]) {
35caf917
AS
1025 default: case 'n': return;
1026 case 'k':
1027 eflags |= ACTION_KICK;
1028 break;
d76ed9a9
AS
1029 case 'b':
1030 eflags |= ACTION_BAN;
1031 break;
1032 case 't':
c8273589 1033 eflags |= ACTION_BAN | ACTION_ADD_LAMER | ACTION_ADD_TIMED_LAMER;
d76ed9a9
AS
1034 argv[argc++] = (char*)chanserv_conf.ctcp_short_ban_duration;
1035 break;
1036 case 'T':
c8273589 1037 eflags |= ACTION_BAN | ACTION_ADD_LAMER | ACTION_ADD_TIMED_LAMER;
d76ed9a9
AS
1038 argv[argc++] = (char*)chanserv_conf.ctcp_long_ban_duration;
1039 break;
1040 }
1041 argv[argc++] = bad_ctcp_reason;
1042 eject_user(chanserv, channel, argc, argv, NULL, eflags);
1043}
1044
1045struct note_type *
1046chanserv_create_note_type(const char *name)
1047{
1048 struct note_type *ntype = calloc(1, sizeof(*ntype) + strlen(name));
1049 strcpy(ntype->name, name);
1050 ntype->refs = 1;
1051 dict_insert(note_types, ntype->name, ntype);
1052 return ntype;
1053}
1054
1055static void
1056chanserv_deref_note_type(void *data)
1057{
1058 struct note_type *ntype = data;
1059
1060 if(--ntype->refs > 0)
1061 return;
1062 free(ntype);
1063}
1064
1065static void
1066chanserv_flush_note_type(struct note_type *ntype)
1067{
1068 struct chanData *cData;
1069 for(cData = channelList; cData; cData = cData->next)
1070 dict_remove(cData->notes, ntype->name);
1071}
1072
1073static void
1074chanserv_truncate_notes(struct note_type *ntype)
1075{
1076 struct chanData *cData;
1077 struct note *note;
1078 unsigned int size = sizeof(*note) + ntype->max_length;
1079
1080 for(cData = channelList; cData; cData = cData->next) {
1081 note = dict_find(cData->notes, ntype->name, NULL);
1082 if(!note)
1083 continue;
1084 if(strlen(note->note) <= ntype->max_length)
1085 continue;
1086 dict_remove2(cData->notes, ntype->name, 1);
1087 note = realloc(note, size);
1088 note->note[ntype->max_length] = 0;
1089 dict_insert(cData->notes, ntype->name, note);
1090 }
1091}
1092
1093static int note_type_visible_to_user(struct chanData *channel, struct note_type *ntype, struct userNode *user);
1094
1095static struct note *
1096chanserv_add_channel_note(struct chanData *channel, struct note_type *type, const char *setter, const char *text)
1097{
1098 struct note *note;
1099 unsigned int len = strlen(text);
1100
1101 if(len > type->max_length) len = type->max_length;
1102 note = calloc(1, sizeof(*note) + len);
1103 note->type = type;
1104 strncpy(note->setter, setter, sizeof(note->setter)-1);
1105 memcpy(note->note, text, len);
1106 note->note[len] = 0;
1107 dict_insert(channel->notes, type->name, note);
1108 type->refs++;
1109 return note;
1110}
1111
1112static void
1113chanserv_free_note(void *data)
1114{
1115 struct note *note = data;
1116
1117 chanserv_deref_note_type(note->type);
1118 assert(note->type->refs > 0); /* must use delnote to remove the type */
1119 free(note);
1120}
1121
1122static MODCMD_FUNC(cmd_createnote) {
1123 struct note_type *ntype;
1124 unsigned int arg = 1, existed = 0, max_length;
1125
1126 if((ntype = dict_find(note_types, argv[1], NULL)))
1127 existed = 1;
1128 else
1129 ntype = chanserv_create_note_type(argv[arg]);
1130 if(!irccasecmp(argv[++arg], "privileged"))
1131 {
1132 arg++;
1133 ntype->set_access_type = NOTE_SET_PRIVILEGED;
1134 ntype->set_access.min_opserv = strtoul(argv[arg], NULL, 0);
1135 }
1136 else if(!irccasecmp(argv[arg], "channel"))
1137 {
1138 unsigned short ulvl = user_level_from_name(argv[++arg], UL_OWNER);
1139 if(!ulvl)
1140 {
1141 reply("CSMSG_INVALID_ACCESS", argv[arg]);
1142 goto fail;
1143 }
1144 ntype->set_access_type = NOTE_SET_CHANNEL_ACCESS;
1145 ntype->set_access.min_ulevel = ulvl;
1146 }
1147 else if(!irccasecmp(argv[arg], "setter"))
1148 {
1149 ntype->set_access_type = NOTE_SET_CHANNEL_SETTER;
1150 }
1151 else
1152 {
1153 reply("CSMSG_BAD_NOTE_ACCESS", argv[arg]);
1154 goto fail;
1155 }
1156
1157 if(!irccasecmp(argv[++arg], "privileged"))
1158 ntype->visible_type = NOTE_VIS_PRIVILEGED;
1159 else if(!irccasecmp(argv[arg], "channel_users"))
1160 ntype->visible_type = NOTE_VIS_CHANNEL_USERS;
1161 else if(!irccasecmp(argv[arg], "all"))
1162 ntype->visible_type = NOTE_VIS_ALL;
1163 else {
1164 reply("CSMSG_BAD_NOTE_ACCESS", argv[arg]);
1165 goto fail;
1166 }
1167
1168 if((arg+1) >= argc) {
1169 reply("MSG_MISSING_PARAMS", argv[0]);
1170 goto fail;
1171 }
1172 max_length = strtoul(argv[++arg], NULL, 0);
1173 if(max_length < 20 || max_length > 450)
1174 {
1175 reply("CSMSG_BAD_MAX_LENGTH", argv[arg]);
1176 goto fail;
1177 }
1178 if(existed && (max_length < ntype->max_length))
1179 {
1180 ntype->max_length = max_length;
1181 chanserv_truncate_notes(ntype);
1182 }
1183 ntype->max_length = max_length;
1184
1185 if(existed)
1186 reply("CSMSG_NOTE_MODIFIED", ntype->name);
1187 else
1188 reply("CSMSG_NOTE_CREATED", ntype->name);
1189 return 1;
1190
1191fail:
1192 if(!existed)
1193 dict_remove(note_types, ntype->name);
1194 return 0;
1195}
1196
1197static MODCMD_FUNC(cmd_removenote) {
1198 struct note_type *ntype;
1199 int force;
1200
1201 ntype = dict_find(note_types, argv[1], NULL);
1202 force = (argc > 2) && !irccasecmp(argv[2], "force");
1203 if(!ntype)
1204 {
1205 reply("CSMSG_BAD_NOTE_TYPE", argv[1]);
1206 return 0;
1207 }
1208 if(ntype->refs > 1)
1209 {
1210 if(!force)
1211 {
1212 reply("CSMSG_NOTE_TYPE_USED", ntype->name);
1213 return 0;
1214 }
1215 chanserv_flush_note_type(ntype);
1216 }
1217 dict_remove(note_types, argv[1]);
1218 reply("CSMSG_NOTE_DELETED", argv[1]);
1219 return 1;
1220}
1221
1222static int
1223mode_lock_violated(const struct mod_chanmode *orig, const struct mod_chanmode *change)
1224{
1225 if(!orig)
1226 return 0;
1227 if(orig->modes_set & change->modes_clear)
1228 return 1;
1229 if(orig->modes_clear & change->modes_set)
1230 return 1;
1231 if((orig->modes_set & MODE_KEY) && (change->modes_set & MODE_KEY)
1232 && strcmp(orig->new_key, change->new_key))
1233 return 1;
1234 if((orig->modes_set & MODE_LIMIT) && (change->modes_set & MODE_LIMIT)
1235 && (orig->new_limit != change->new_limit))
1236 return 1;
1237 return 0;
1238}
1239
1240static char max_length_text[MAXLEN+1][16];
1241
1242static struct helpfile_expansion
1243chanserv_expand_variable(const char *variable)
1244{
1245 struct helpfile_expansion exp;
1246
1247 if(!irccasecmp(variable, "notes"))
1248 {
1249 dict_iterator_t it;
1250 exp.type = HF_TABLE;
1251 exp.value.table.length = 1;
1252 exp.value.table.width = 3;
1253 exp.value.table.flags = 0;
1254 exp.value.table.contents = calloc(dict_size(note_types)+1, sizeof(char**));
1255 exp.value.table.contents[0] = calloc(exp.value.table.width, sizeof(char*));
1256 exp.value.table.contents[0][0] = "Note Type";
1257 exp.value.table.contents[0][1] = "Visibility";
1258 exp.value.table.contents[0][2] = "Max Length";
1259 for(it=dict_first(note_types); it; it=iter_next(it))
1260 {
1261 struct note_type *ntype = iter_data(it);
1262 int row;
1263
1264 if(!note_type_visible_to_user(NULL, ntype, message_dest)) continue;
1265 row = exp.value.table.length++;
1266 exp.value.table.contents[row] = calloc(exp.value.table.width, sizeof(char*));
1267 exp.value.table.contents[row][0] = ntype->name;
1268 exp.value.table.contents[row][1] = (ntype->visible_type == NOTE_VIS_ALL) ? "all" :
1269 (ntype->visible_type == NOTE_VIS_CHANNEL_USERS) ? "chan users" :
1270 "unknown";
1271 if(!max_length_text[ntype->max_length][0])
1272 snprintf(max_length_text[ntype->max_length], sizeof(max_length_text[ntype->max_length]), "%u", ntype->max_length);
1273 exp.value.table.contents[row][2] = max_length_text[ntype->max_length];
1274 }
1275 return exp;
1276 }
1277
1278 exp.type = HF_STRING;
1279 exp.value.str = NULL;
1280 return exp;
1281}
1282
1283static struct chanData*
1284register_channel(struct chanNode *cNode, char *registrar)
1285{
1286 struct chanData *channel;
1287 enum levelOption lvlOpt;
1288 enum charOption chOpt;
1289
1290 channel = calloc(1, sizeof(struct chanData));
1291
1292 channel->notes = dict_new();
1293 dict_set_free_data(channel->notes, chanserv_free_note);
1294
1295 channel->registrar = strdup(registrar);
1296 channel->registered = now;
1297 channel->visited = now;
1298 channel->limitAdjusted = now;
a32da4c7 1299 channel->ownerTransfer = now;
d76ed9a9
AS
1300 channel->flags = CHANNEL_DEFAULT_FLAGS;
1301 for(lvlOpt = 0; lvlOpt < NUM_LEVEL_OPTIONS; ++lvlOpt)
1302 channel->lvlOpts[lvlOpt] = levelOptions[lvlOpt].default_value;
1303 for(chOpt = 0; chOpt < NUM_CHAR_OPTIONS; ++chOpt)
1304 channel->chOpts[chOpt] = charOptions[chOpt].default_value;
1305
1306 channel->prev = NULL;
1307 channel->next = channelList;
1308
1309 if(channelList)
1310 channelList->prev = channel;
1311 channelList = channel;
1312 registered_channels++;
1313
1314 channel->channel = cNode;
1315 LockChannel(cNode);
1316 cNode->channel_info = channel;
1317
1318 return channel;
1319}
1320
1321static struct userData*
1136f709 1322add_channel_user(struct chanData *channel, struct handle_info *handle, unsigned short access_level, time_t seen, const char *info, time_t accessexpiry)
d76ed9a9
AS
1323{
1324 struct userData *ud;
1325
1136f709 1326 if(access_level > UL_OWNER)
d76ed9a9
AS
1327 return NULL;
1328
1329 ud = calloc(1, sizeof(*ud));
1330 ud->channel = channel;
1331 ud->handle = handle;
1332 ud->seen = seen;
1136f709 1333 ud->access = access_level;
d76ed9a9 1334 ud->info = info ? strdup(info) : NULL;
dfaa28a4 1335 ud->accessexpiry = accessexpiry ? accessexpiry : 0;
1336 ud->clvlexpiry = 0;
1337 ud->lastaccess = 0;
d76ed9a9
AS
1338
1339 ud->prev = NULL;
1340 ud->next = channel->users;
1341 if(channel->users)
1342 channel->users->prev = ud;
1343 channel->users = ud;
1344
1345 channel->userCount++;
1346 userCount++;
1347
1348 ud->u_prev = NULL;
1349 ud->u_next = ud->handle->channels;
1350 if(ud->u_next)
1351 ud->u_next->u_prev = ud;
1352 ud->handle->channels = ud;
1353
c8ca69a0 1354 ud->flags = USER_FLAGS_DEFAULT;
d76ed9a9
AS
1355 return ud;
1356}
1357
1358static void unregister_channel(struct chanData *channel, const char *reason);
1359
fd20b142 1360static void
1361chanserv_expire_tempuser(void *data)
1362{
1363 struct userData *uData = data;
1364 char *handle;
1365
1366 if (data) {
1367 handle = strdup(uData->handle->handle);
dfaa28a4 1368 if (uData->accessexpiry > 0) {
fd20b142 1369 if (uData->present) {
1370 struct userNode *user, *next_un = NULL;
1371 struct handle_info *hi;
1372
1373 hi = get_handle_info(handle);
1374 for (user = hi->users; user; user = next_un) {
1375 struct mod_chanmode *change;
1376 struct modeNode *mn;
1377 unsigned int count = 0;
1378
1379 send_message(user, chanserv, "CSMSG_AUTO_DELETED", chanserv->nick, uData->channel->channel->name);
1380 if (!(mn = GetUserMode(uData->channel->channel, user)) || !(mn->modes & MODE_CHANOP)) {
1381 next_un = user->next_authed;
1382 continue;
1383 }
1384
1385 change = mod_chanmode_alloc(2);
1386 change->args[count].mode = MODE_REMOVE | MODE_CHANOP;
1387 change->args[count++].u.member = mn;
1388
1389 if (count) {
1390 change->argc = count;
1391 mod_chanmode_announce(chanserv, uData->channel->channel, change);
1392 }
1393 mod_chanmode_free(change);
1394 next_un = user->next_authed;
1395 }
1396 }
1397 del_channel_user(uData, 1);
1398 }
1399 }
1400}
1401
dfaa28a4 1402static void
1403chanserv_expire_tempclvl(void *data)
1404{
1405 struct userData *uData = data;
1406 char *handle;
1407
1408 if (data) {
1409 handle = strdup(uData->handle->handle);
1410 if (uData->clvlexpiry > 0) {
1411 int changemodes = 0;
1412 unsigned int mode = 0;
1413
1414 if (((uData->lastaccess == UL_PEON) || (uData->lastaccess == UL_HALFOP)) && (uData->access >= UL_OP)) {
1415 changemodes = 1;
1416 mode = MODE_REMOVE | MODE_CHANOP;
1417 } else if ((uData->lastaccess == UL_PEON) && (uData->access == UL_HALFOP)) {
1418 changemodes = 1;
1419 mode = MODE_REMOVE | MODE_HALFOP;
1420 } else
1421 changemodes = 0;
1422
1423 if (uData->present) {
1424 struct userNode *user, *next_un = NULL;
1425 struct handle_info *hi;
1426
1427 hi = get_handle_info(handle);
1428 for (user = hi->users; user; user = next_un) {
1429 struct mod_chanmode *change;
1430 struct modeNode *mn;
1431 unsigned int count = 0;
1432
1433 send_message(user, chanserv, "CSMSG_CLVL_EXPIRED", uData->channel->channel->name);
1434 if (!(mn = GetUserMode(uData->channel->channel, user))) {
1435 next_un = user->next_authed;
1436 continue;
1437 }
1438
1439 if (changemodes == 0) {
1440 next_un = user->next_authed;
1441 continue;
1442 }
1443
1444 change = mod_chanmode_alloc(2);
1445 change->args[count].mode = mode;
1446 change->args[count++].u.member = mn;
1447
1448 if (count) {
1449 change->argc = count;
1450 mod_chanmode_announce(chanserv, uData->channel->channel, change);
1451 }
1452 mod_chanmode_free(change);
1453 next_un = user->next_authed;
1454 }
1455 }
1456
1457 uData->access = uData->lastaccess;
1458 uData->lastaccess = 0;
1459 uData->clvlexpiry = 0;
1460 }
1461 }
1462}
1463
d76ed9a9
AS
1464void
1465del_channel_user(struct userData *user, int do_gc)
1466{
1467 struct chanData *channel = user->channel;
1468
1469 channel->userCount--;
1470 userCount--;
1471
fd20b142 1472 timeq_del(0, chanserv_expire_tempuser, user, TIMEQ_IGNORE_WHEN);
dfaa28a4 1473 timeq_del(0, chanserv_expire_tempclvl, user, TIMEQ_IGNORE_WHEN);
fd20b142 1474
d76ed9a9
AS
1475 if(user->prev)
1476 user->prev->next = user->next;
1477 else
1478 channel->users = user->next;
1479 if(user->next)
1480 user->next->prev = user->prev;
1481
1482 if(user->u_prev)
1483 user->u_prev->u_next = user->u_next;
1484 else
1485 user->handle->channels = user->u_next;
1486 if(user->u_next)
1487 user->u_next->u_prev = user->u_prev;
1488
1489 free(user->info);
1490 free(user);
63c95a47 1491 if(do_gc && !channel->users && !IsProtected(channel)) {
1492 spamserv_cs_unregister(NULL, channel->channel, lost_all_users, NULL);
d76ed9a9 1493 unregister_channel(channel, "lost all users.");
63c95a47 1494 }
d76ed9a9
AS
1495}
1496
ac3bdc8d
AS
1497static struct adduserPending*
1498add_adduser_pending(struct chanNode *channel, struct userNode *user, int level)
1499{
1500 struct adduserPending *ap;
1501 ap = calloc(1,sizeof(struct adduserPending));
1502 ap->channel = channel;
1503 ap->user = user;
1504 ap->level = level;
1505 ap->created = time(NULL);
1506
1507 /* ap->prev defaults to NULL already.. */
1508 ap->next = adduser_pendings;
1509 if(adduser_pendings)
1510 adduser_pendings->prev = ap;
1511 adduser_pendings = ap;
1512 adduser_pendings_count++;
1513 return(ap);
1514}
1515
1516static void
1517del_adduser_pending(struct adduserPending *ap)
1518{
1519 if(ap->prev)
1520 ap->prev->next = ap->next;
1521 else
1522 adduser_pendings = ap->next;
1523
1524 if(ap->next)
1525 ap->next->prev = ap->prev;
1526 free(ap);
1527}
1528
1529static void expire_adduser_pending();
1530
1531/* find_adduser_pending(channel, user) will find an arbitrary record
1532 * from user, channel, or user and channel.
1533 * if user or channel are NULL, they will match any records.
1534 */
1535static struct adduserPending*
1536find_adduser_pending(struct chanNode *channel, struct userNode *user)
1537{
1538 struct adduserPending *ap;
1539
1540 expire_adduser_pending(); /* why not here.. */
1541
1542 if(!channel && !user) /* 2 nulls matches all */
1543 return(adduser_pendings);
1544 for(ap = adduser_pendings;ap;ap = ap->next)
1545 {
1546 if((channel == ap->channel && (user == NULL || user == ap->user)) || (user==ap->user && channel==NULL))
1547 return ap;
1548 }
1549 return NULL;
1550}
1551
1552
1553/* Remove all pendings for a user or channel
1554 *
1555 * called in nickserv.c DelUser() and proto-* unregister_channel()
1556 */
1557void
1558wipe_adduser_pending(struct chanNode *channel, struct userNode *user)
1559{
1560 struct adduserPending *ap;
1561
1562 /* So this is a bit wastefull, i hate dealing with linked lists.
1563 * if its a problem we'll rewrite it right */
1564 while((ap = find_adduser_pending(channel, user))) {
1565 del_adduser_pending(ap);
1566 }
1567}
1568
1569/* Called from nickserv.c cmd_auth after someone auths */
1570void
1571process_adduser_pending(struct userNode *user)
1572{
1573 struct adduserPending *ap;
a03d6c77
AS
1574 if(!user->handle_info)
1575 return; /* not associated with an account */
ac3bdc8d
AS
1576 while((ap = find_adduser_pending(NULL, user)))
1577 {
1578 struct userData *actee;
5ca901c3
AS
1579 if(GetTrueChannelAccess(ap->channel->channel_info, ap->user->handle_info))
1580 {
1581 /* Already on the userlist. do nothing*/
1582 }
1583 else
1584 {
fd20b142 1585 actee = add_channel_user(ap->channel->channel_info, ap->user->handle_info, ap->level, 0, NULL, 0);
5ca901c3
AS
1586 scan_user_presence(actee, NULL);
1587 }
ac3bdc8d
AS
1588 del_adduser_pending(ap);
1589 }
1590}
1591
1592static void
1593expire_adduser_pending()
1594{
1595 struct adduserPending *ap, *ap_next;
1596 ap = adduser_pendings;
1597 while(ap)
1598 {
1599 if((ap->created + ADDUSER_PENDING_EXPIRE) < time(NULL))
1600 { /* expire it */
1601 ap_next = ap->next; /* save next */
1602 del_adduser_pending(ap); /* free and relink */
1603 ap = ap_next; /* advance */
1604 }
1605 else
1606 ap = ap->next;
1607 }
1608}
1609
d76ed9a9
AS
1610static void expire_ban(void *data);
1611
63c95a47 1612struct banData*
d76ed9a9
AS
1613add_channel_ban(struct chanData *channel, const char *mask, char *owner, time_t set, time_t triggered, time_t expires, char *reason)
1614{
1615 struct banData *bd;
1616 unsigned int ii, l1, l2;
1617
1618 if(!mask)
1619 return NULL;
1620
1621 bd = malloc(sizeof(struct banData));
1622
1623 bd->channel = channel;
1624 bd->set = set;
1625 bd->triggered = triggered;
1626 bd->expires = expires;
1627
1628 for(ii = 0; ii < chanserv_conf.old_ban_names->used; ++ii)
1629 {
1630 extern const char *hidden_host_suffix;
1631 const char *old_name = chanserv_conf.old_ban_names->list[ii];
1632 char *new_mask;
1633
1634 l1 = strlen(mask);
1635 l2 = strlen(old_name);
1636 if(l2+2 > l1)
1637 continue;
1638 if(irccasecmp(mask + l1 - l2, old_name))
1639 continue;
1640 new_mask = alloca(MAXLEN);
a32da4c7 1641 sprintf(new_mask, "%.*s%s", (int)(l1-l2), mask, hidden_host_suffix);
d76ed9a9
AS
1642 mask = new_mask;
1643 }
1644 safestrncpy(bd->mask, mask, sizeof(bd->mask));
1645 if(owner)
1646 safestrncpy(bd->owner, owner, sizeof(bd->owner));
ec1a68c8 1647 bd->reason = strdup(reason);
d76ed9a9
AS
1648
1649 if(expires)
1650 timeq_add(expires, expire_ban, bd);
1651
1652 bd->prev = NULL;
c8273589 1653 bd->next = channel->bans; /* lamers */
d76ed9a9
AS
1654 if(channel->bans)
1655 channel->bans->prev = bd;
1656 channel->bans = bd;
1657 channel->banCount++;
1658 banCount++;
1659
1660 return bd;
1661}
1662
1663static void
1664del_channel_ban(struct banData *ban)
1665{
1666 ban->channel->banCount--;
1667 banCount--;
1668
1669 if(ban->prev)
1670 ban->prev->next = ban->next;
1671 else
1672 ban->channel->bans = ban->next;
1673
1674 if(ban->next)
1675 ban->next->prev = ban->prev;
1676
1677 if(ban->expires)
1678 timeq_del(0, expire_ban, ban, TIMEQ_IGNORE_WHEN);
1679
1680 if(ban->reason)
1681 free(ban->reason);
1682
1683 free(ban);
1684}
1685
1686static void
c8273589 1687expire_ban(void *data) /* lamer.. */
d76ed9a9
AS
1688{
1689 struct banData *bd = data;
1690 if(!IsSuspended(bd->channel))
1691 {
1692 struct banList bans;
1693 struct mod_chanmode change;
1694 unsigned int ii;
1695 bans = bd->channel->channel->banlist;
1696 mod_chanmode_init(&change);
1697 for(ii=0; ii<bans.used; ii++)
1698 {
1699 if(!strcmp(bans.list[ii]->ban, bd->mask))
1700 {
1701 change.argc = 1;
1702 change.args[0].mode = MODE_REMOVE|MODE_BAN;
a32da4c7 1703 change.args[0].u.hostmask = bd->mask;
d76ed9a9
AS
1704 mod_chanmode_announce(chanserv, bd->channel->channel, &change);
1705 break;
1706 }
1707 }
1708 }
1709 bd->expires = 0;
1710 del_channel_ban(bd);
1711}
1712
1713static void chanserv_expire_suspension(void *data);
1714
1715static void
1716unregister_channel(struct chanData *channel, const char *reason)
1717{
1718 struct mod_chanmode change;
1719 char msgbuf[MAXLEN];
1720
1721 /* After channel unregistration, the following must be cleaned
1722 up:
1723 - Channel information.
1724 - Channel users.
c8273589 1725 - Channel bans. (lamers)
d76ed9a9 1726 - Channel suspension data.
ac3bdc8d 1727 - adduser_pending data.
d76ed9a9
AS
1728 - Timeq entries. (Except timed bans, which are handled elsewhere.)
1729 */
1730
1731 if(!channel)
1732 return;
1733
1734 timeq_del(0, NULL, channel, TIMEQ_IGNORE_FUNC | TIMEQ_IGNORE_WHEN);
1735
1117fc5a 1736 if(off_channel > 0)
a32da4c7 1737 {
1738 mod_chanmode_init(&change);
1739 change.modes_clear |= MODE_REGISTERED;
1740 mod_chanmode_announce(chanserv, channel->channel, &change);
1741 }
d76ed9a9 1742
ac3bdc8d
AS
1743 wipe_adduser_pending(channel->channel, NULL);
1744
d76ed9a9
AS
1745 while(channel->users)
1746 del_channel_user(channel->users, 0);
1747
1748 while(channel->bans)
1749 del_channel_ban(channel->bans);
1750
1751 free(channel->topic);
1752 free(channel->registrar);
1753 free(channel->greeting);
1754 free(channel->user_greeting);
1755 free(channel->topic_mask);
1756
1757 if(channel->prev)
1758 channel->prev->next = channel->next;
1759 else
1760 channelList = channel->next;
1761
1762 if(channel->next)
1763 channel->next->prev = channel->prev;
1764
1765 if(channel->suspended)
1766 {
1767 struct chanNode *cNode = channel->channel;
1768 struct suspended *suspended, *next_suspended;
1769
1770 for(suspended = channel->suspended; suspended; suspended = next_suspended)
1771 {
1772 next_suspended = suspended->previous;
1773 free(suspended->suspender);
1774 free(suspended->reason);
1775 if(suspended->expires)
1776 timeq_del(suspended->expires, chanserv_expire_suspension, suspended, 0);
1777 free(suspended);
1778 }
1779
1780 if(cNode)
1781 cNode->channel_info = NULL;
1782 }
1783 channel->channel->channel_info = NULL;
1784
1785 dict_delete(channel->notes);
1786 sprintf(msgbuf, "%s %s", channel->channel->name, reason);
1787 if(!IsSuspended(channel))
1788 DelChannelUser(chanserv, channel->channel, msgbuf, 0);
1789 global_message(MESSAGE_RECIPIENT_OPERS | MESSAGE_RECIPIENT_HELPERS, msgbuf);
1790 UnlockChannel(channel->channel);
1791 free(channel);
1792 registered_channels--;
1793}
1794
1795static void
1796expire_channels(UNUSED_ARG(void *data))
1797{
1798 struct chanData *channel, *next;
1799 struct userData *user;
1800 char delay[INTERVALLEN], reason[INTERVALLEN + 64];
1801
1802 intervalString(delay, chanserv_conf.channel_expire_delay, NULL);
1803 sprintf(reason, "Channel registration automatically expired after %s of disuse.", delay);
1804
1805 for(channel = channelList; channel; channel = next)
1806 {
1807 next = channel->next;
1808
1809 /* See if the channel can be expired. */
1810 if(((now - channel->visited) <= chanserv_conf.channel_expire_delay)
1811 || IsProtected(channel))
1812 continue;
1813
1814 /* Make sure there are no high-ranking users still in the channel. */
1815 for(user=channel->users; user; user=user->next)
1816 if(user->present && (user->access >= UL_PRESENT))
1817 break;
1818 if(user)
1819 continue;
1820
1821 /* Unregister the channel */
1822 log_module(CS_LOG, LOG_INFO, "(%s) Channel registration expired.", channel->channel->name);
63c95a47 1823 spamserv_cs_unregister(NULL, channel->channel, expire, NULL);
d76ed9a9
AS
1824 unregister_channel(channel, "registration expired.");
1825 }
1826
1827 if(chanserv_conf.channel_expire_frequency)
1828 timeq_add(now + chanserv_conf.channel_expire_frequency, expire_channels, NULL);
1829}
1830
1136f709 1831static void
1832expire_dnrs(UNUSED_ARG(void *data))
1833{
1834 dict_iterator_t it, next;
1835 struct do_not_register *dnr;
1836
1837 for(it = dict_first(handle_dnrs); it; it = next)
1838 {
1839 dnr = iter_data(it);
1840 next = iter_next(it);
1841 if(dnr->expires && dnr->expires <= now)
1842 dict_remove(handle_dnrs, dnr->chan_name + 1);
1843 }
1844 for(it = dict_first(plain_dnrs); it; it = next)
1845 {
1846 dnr = iter_data(it);
1847 next = iter_next(it);
1848 if(dnr->expires && dnr->expires <= now)
1849 dict_remove(plain_dnrs, dnr->chan_name + 1);
1850 }
1851 for(it = dict_first(mask_dnrs); it; it = next)
1852 {
1853 dnr = iter_data(it);
1854 next = iter_next(it);
1855 if(dnr->expires && dnr->expires <= now)
1856 dict_remove(mask_dnrs, dnr->chan_name + 1);
1857 }
1858
1859 if(chanserv_conf.dnr_expire_frequency)
1860 timeq_add(now + chanserv_conf.dnr_expire_frequency, expire_dnrs, NULL);
1861}
1862
d76ed9a9 1863static int
ff5f1ab2 1864protect_user(const struct userNode *victim, const struct userNode *aggressor, struct chanData *channel, int protect_invitables)
d76ed9a9
AS
1865{
1866 char protect = channel->chOpts[chProtect];
1867 struct userData *cs_victim, *cs_aggressor;
1868
ff5f1ab2
AS
1869 /* If victim access level is greater than set invitelevel, don't let
1870 * us kick them, but don't consider it punishment if someone else does
1871 */
d76ed9a9 1872
ff5f1ab2
AS
1873
1874 if(victim == aggressor)
1875 return 0;
d76ed9a9
AS
1876 /* Don't protect if the victim isn't authenticated (because they
1877 can't be a channel user), unless we are to protect non-users
1878 also. */
ff5f1ab2 1879
d76ed9a9 1880 cs_victim = GetChannelAccess(channel, victim->handle_info);
ff5f1ab2
AS
1881
1882 /* If they have enough access to invite themselvs through a ban,
1883 * and its us kicking them, don't. -Rubin */
1884 if(protect_invitables==true && cs_victim && (cs_victim->access >= channel->lvlOpts[lvlInviteMe]))
1885 return 1;
1886
1887 if(protect == 'n')
1888 return 0;
1889
d76ed9a9
AS
1890 if(protect != 'a' && !cs_victim)
1891 return 0;
1892
1893 /* Protect if the aggressor isn't a user because at this point,
1894 the aggressor can only be less than or equal to the victim. */
ff5f1ab2
AS
1895
1896 /* Not protected from chanserv except above */
1897 /* XXX: need to generic-ize chanserv to "one of x3's services" somehow.. */
1898 if(aggressor == chanserv)
1899 return 0;
1900
d76ed9a9
AS
1901 cs_aggressor = GetChannelAccess(channel, aggressor->handle_info);
1902 if(!cs_aggressor)
1903 return 1;
1904
1905 /* If the aggressor was a user, then the victim can't be helped. */
1906 if(!cs_victim)
1907 return 0;
1908
1909 switch(protect)
1910 {
1911 case 'l':
1912 if(cs_victim->access > cs_aggressor->access)
1913 return 1;
1914 break;
1915 case 'a':
1916 case 'e':
1917 if(cs_victim->access >= cs_aggressor->access)
1918 return 1;
1919 break;
1920 }
1921
1922 return 0;
1923}
1924
1925static int
c092fcad 1926validate_op(struct svccmd *cmd, struct userNode *user, struct chanNode *channel, struct userNode *victim)
d76ed9a9
AS
1927{
1928 struct chanData *cData = channel->channel_info;
1929 struct userData *cs_victim;
1930
1931 if((!(cs_victim = GetChannelUser(cData, victim->handle_info))
b75e24a3 1932 || (cs_victim->access < UL_OP /* cData->lvlOpts[lvlGiveOps]*/))
d76ed9a9
AS
1933 && !check_user_level(channel, user, lvlEnfOps, 0, 0))
1934 {
c092fcad
AS
1935 if(cmd)
1936 reply("CSMSG_OPBY_LOCKED");
1937 else
1938 send_message(user, chanserv, "CSMSG_OPBY_LOCKED");
d76ed9a9
AS
1939 return 0;
1940 }
1941
1942 return 1;
1943}
1944
55342ce8 1945static int
c092fcad 1946validate_halfop(struct svccmd *cmd, struct userNode *user, struct chanNode *channel, struct userNode *victim)
55342ce8 1947{
1948 struct chanData *cData = channel->channel_info;
1949 struct userData *cs_victim;
1950
1951 if((!(cs_victim = GetChannelUser(cData, victim->handle_info))
b75e24a3 1952 || (cs_victim->access < UL_HALFOP /* cData->lvlOpts[lvlGiveHalfOps] */))
55342ce8 1953 && !check_user_level(channel, user, lvlEnfHalfOps, 0, 0))
1954 {
c092fcad 1955 reply("CSMSG_HOPBY_LOCKED");
55342ce8 1956 return 0;
1957 }
1958
1959 return 1;
1960}
1961
1962
d76ed9a9 1963static int
c092fcad 1964validate_deop(struct svccmd *cmd, struct userNode *user, struct chanNode *channel, struct userNode *victim)
d76ed9a9
AS
1965{
1966 if(IsService(victim))
1967 {
c092fcad 1968 reply("MSG_SERVICE_IMMUNE", victim->nick);
d76ed9a9
AS
1969 return 0;
1970 }
1971
ff5f1ab2 1972 if(protect_user(victim, user, channel->channel_info, false))
d76ed9a9 1973 {
c092fcad 1974 reply("CSMSG_USER_PROTECTED", victim->nick);
d76ed9a9
AS
1975 return 0;
1976 }
1977
1978 return 1;
1979}
1980
55342ce8 1981static int
c092fcad 1982validate_dehop(struct svccmd *cmd, struct userNode *user, struct chanNode *channel, struct userNode *victim)
55342ce8 1983{
1984 if(IsService(victim))
1985 {
c092fcad 1986 reply("MSG_SERVICE_IMMUNE", victim->nick);
55342ce8 1987 return 0;
1988 }
1989
ff5f1ab2 1990 if(protect_user(victim, user, channel->channel_info, false))
55342ce8 1991 {
c092fcad 1992 reply("CSMSG_USER_PROTECTED", victim->nick);
55342ce8 1993 return 0;
1994 }
1995
1996 return 1;
1997}
1998
d76ed9a9 1999static struct do_not_register *
1136f709 2000chanserv_add_dnr(const char *chan_name, const char *setter, time_t expires, const char *reason)
d76ed9a9
AS
2001{
2002 struct do_not_register *dnr = calloc(1, sizeof(*dnr)+strlen(reason));
2003 safestrncpy(dnr->chan_name, chan_name, sizeof(dnr->chan_name));
2004 safestrncpy(dnr->setter, setter, sizeof(dnr->setter));
2005 strcpy(dnr->reason, reason);
2006 dnr->set = now;
1136f709 2007 dnr->expires = expires;
d76ed9a9
AS
2008 if(dnr->chan_name[0] == '*')
2009 dict_insert(handle_dnrs, dnr->chan_name+1, dnr);
2010 else if(strpbrk(dnr->chan_name, "*?"))
2011 dict_insert(mask_dnrs, dnr->chan_name, dnr);
2012 else
2013 dict_insert(plain_dnrs, dnr->chan_name, dnr);
2014 return dnr;
2015}
2016
2017static struct dnrList
1136f709 2018chanserv_find_dnrs(const char *chan_name, const char *handle, unsigned int max)
d76ed9a9
AS
2019{
2020 struct dnrList list;
1136f709 2021 dict_iterator_t it, next;
d76ed9a9
AS
2022 struct do_not_register *dnr;
2023
2024 dnrList_init(&list);
1136f709 2025
1117fc5a 2026 if(handle && (dnr = dict_find(handle_dnrs, handle, NULL)))
1136f709 2027 {
2028 if(dnr->expires && dnr->expires <= now)
2029 dict_remove(handle_dnrs, handle);
2030 else if (list.used < max)
2031 dnrList_append(&list, dnr);
2032 }
2033
d76ed9a9 2034 if(chan_name && (dnr = dict_find(plain_dnrs, chan_name, NULL)))
1136f709 2035 {
2036 if(dnr->expires && dnr->expires <= now)
2037 dict_remove(plain_dnrs, chan_name);
2038 else if (list.used < max)
2039 dnrList_append(&list, dnr);
2040 }
d76ed9a9 2041 if(chan_name)
1136f709 2042 {
2043 for(it = dict_first(mask_dnrs); it && list.used < max; it = next)
2044 {
2045 next = iter_next(it);
2046 if(!match_ircglob(chan_name, iter_key(it)))
2047 continue;
2048 dnr = iter_data(it);
2049 if(dnr->expires && dnr->expires <= now)
2050 dict_remove(mask_dnrs, iter_key(it));
2051 else
2052 dnrList_append(&list, dnr);
2053 }
2054 }
d76ed9a9
AS
2055 return list;
2056}
2057
1136f709 2058static int dnr_print_func(struct do_not_register *dnr, void *extra)
2059{
2060 struct userNode *user;
2061 char buf1[INTERVALLEN];
2062 char buf2[INTERVALLEN];
2063
2064 user = extra;
2065 if(dnr->set)
2066 strftime(buf1, sizeof(buf1), "%d %b %Y", localtime(&dnr->set));
2067 if(dnr->expires)
2068 {
2069 strftime(buf2, sizeof(buf2), "%d %b %Y", localtime(&dnr->expires));
2070 send_message(user, chanserv, "CSMSG_DNR_INFO_SET_EXPIRES", dnr->chan_name, buf1, dnr->setter, buf2, dnr->reason);
2071 }
2072 else if(dnr->set)
2073 {
2074 send_message(user, chanserv, "CSMSG_DNR_INFO_SET", dnr->chan_name, buf1, dnr->setter, dnr->reason);
2075 }
2076 else
2077 send_message(user, chanserv, "CSMSG_DNR_INFO", dnr->chan_name, dnr->setter, dnr->reason);
2078 return 0;
2079}
2080
d76ed9a9 2081static unsigned int
1117fc5a 2082chanserv_show_dnrs(struct userNode *user, struct svccmd *cmd, const char *chan_name, const char *handle)
d76ed9a9
AS
2083{
2084 struct dnrList list;
d76ed9a9 2085 unsigned int ii;
d76ed9a9 2086
1136f709 2087 list = chanserv_find_dnrs(chan_name, handle, UINT_MAX);
d76ed9a9 2088 for(ii = 0; (ii < list.used) && (ii < 10); ++ii)
1136f709 2089 dnr_print_func(list.list[ii], user);
d76ed9a9
AS
2090 if(ii < list.used)
2091 reply("CSMSG_MORE_DNRS", list.used - ii);
2092 free(list.list);
2093 return ii;
2094}
2095
2096struct do_not_register *
2097chanserv_is_dnr(const char *chan_name, struct handle_info *handle)
2098{
1136f709 2099 struct dnrList list;
d76ed9a9 2100 struct do_not_register *dnr;
d76ed9a9 2101
1136f709 2102 list = chanserv_find_dnrs(chan_name, handle ? handle->handle : NULL, 1);
2103 dnr = list.used ? list.list[0] : NULL;
2104 free(list.list);
2105 return dnr;
2106}
2107
2108static unsigned int send_dnrs(struct userNode *user, dict_t dict)
2109{
2110 struct do_not_register *dnr;
2111 dict_iterator_t it, next;
2112 unsigned int matches = 0;
2113
2114 for(it = dict_first(dict); it; it = next)
d76ed9a9 2115 {
1136f709 2116 dnr = iter_data(it);
2117 next = iter_next(it);
2118 if(dnr->expires && dnr->expires <= now)
2119 {
2120 dict_remove(dict, iter_key(it));
2121 continue;
2122 }
2123 dnr_print_func(dnr, user);
2124 matches++;
d76ed9a9 2125 }
1136f709 2126
2127 return matches;
d76ed9a9
AS
2128}
2129
2130static CHANSERV_FUNC(cmd_noregister)
2131{
2132 const char *target;
1136f709 2133 const char *reason;
2134 time_t expiry, duration;
d76ed9a9
AS
2135 unsigned int matches;
2136
2137 if(argc < 2)
2138 {
d76ed9a9 2139 reply("CSMSG_DNR_SEARCH_RESULTS");
1136f709 2140 matches = send_dnrs(user, handle_dnrs);
2141 matches += send_dnrs(user, plain_dnrs);
2142 matches += send_dnrs(user, mask_dnrs);
d76ed9a9
AS
2143 if(matches)
2144 reply("MSG_MATCH_COUNT", matches);
2145 else
2146 reply("MSG_NO_MATCHES");
2147 return 0;
2148 }
2149
2150 target = argv[1];
2151
2152 if(!IsChannelName(target) && (*target != '*'))
2153 {
2154 reply("CSMSG_NOT_DNR", target);
2155 return 0;
2156 }
2157
2158 if(argc > 2)
2159 {
1136f709 2160 if(argc == 3)
2161 {
2162 reply("MSG_INVALID_DURATION", argv[2]);
2163 return 0;
2164 }
2165
2166 if(!strcmp(argv[2], "0"))
2167 expiry = 0;
2168 else if((duration = ParseInterval(argv[2])))
2169 expiry = now + duration;
2170 else
2171 {
2172 reply("MSG_INVALID_DURATION", argv[2]);
2173 return 0;
2174 }
2175
2176 reason = unsplit_string(argv + 3, argc - 3, NULL);
2177
d76ed9a9
AS
2178 if((*target == '*') && !get_handle_info(target + 1))
2179 {
2180 reply("MSG_HANDLE_UNKNOWN", target + 1);
2181 return 0;
2182 }
1136f709 2183 chanserv_add_dnr(target, user->handle_info->handle, expiry, reason);
d76ed9a9
AS
2184 reply("CSMSG_NOREGISTER_CHANNEL", target);
2185 return 1;
2186 }
2187
2188 reply("CSMSG_DNR_SEARCH_RESULTS");
0f6fe38c 2189 if(user->handle_info && user->handle_info->userlist_style != HI_STYLE_CLEAN)
2190 reply("CSMSG_BAR");
d76ed9a9 2191 if(*target == '*')
1117fc5a 2192 matches = chanserv_show_dnrs(user, cmd, NULL, target + 1);
d76ed9a9
AS
2193 else
2194 matches = chanserv_show_dnrs(user, cmd, target, NULL);
2195 if(!matches)
2196 reply("MSG_NO_MATCHES");
2197 return 0;
2198}
2199
2200static CHANSERV_FUNC(cmd_allowregister)
2201{
2202 const char *chan_name = argv[1];
2203
1136f709 2204 if(((chan_name[0] == '*') && dict_remove(handle_dnrs, chan_name+1))
2205 || dict_remove(plain_dnrs, chan_name)
2206 || dict_remove(mask_dnrs, chan_name))
d76ed9a9 2207 {
d76ed9a9 2208 reply("CSMSG_DNR_REMOVED", chan_name);
1136f709 2209 return 1;
ff3b058a 2210 }
1136f709 2211 reply("CSMSG_NO_SUCH_DNR", chan_name);
2212 return 0;
2213}
2214
2215struct dnr_search {
2216 struct userNode *source;
2217 char *chan_mask;
2218 char *setter_mask;
2219 char *reason_mask;
2220 time_t min_set, max_set;
2221 time_t min_expires, max_expires;
2222 unsigned int limit;
2223};
2224
2225static int
2226dnr_search_matches(const struct do_not_register *dnr, const struct dnr_search *search)
2227{
2228 return !((dnr->set < search->min_set)
2229 || (dnr->set > search->max_set)
2230 || (dnr->expires < search->min_expires)
2231 || (search->max_expires
2232 && ((dnr->expires == 0)
2233 || (dnr->expires > search->max_expires)))
2234 || (search->chan_mask
2235 && !match_ircglob(dnr->chan_name, search->chan_mask))
2236 || (search->setter_mask
2237 && !match_ircglob(dnr->setter, search->setter_mask))
2238 || (search->reason_mask
2239 && !match_ircglob(dnr->reason, search->reason_mask)));
2240}
2241
2242static struct dnr_search *
2243dnr_search_create(struct userNode *user, struct svccmd *cmd, unsigned int argc, char *argv[])
2244{
2245 struct dnr_search *discrim;
2246 unsigned int ii;
2247
2248 discrim = calloc(1, sizeof(*discrim));
2249 discrim->source = user;
2250 discrim->chan_mask = NULL;
2251 discrim->setter_mask = NULL;
2252 discrim->reason_mask = NULL;
2253 discrim->max_set = INT_MAX;
2254 discrim->limit = 50;
2255
2256 for(ii=0; ii<argc; ++ii)
d76ed9a9 2257 {
1136f709 2258 if(ii == argc - 1)
2259 {
2260 reply("MSG_MISSING_PARAMS", argv[ii]);
2261 goto fail;
2262 }
2263 else if(0 == irccasecmp(argv[ii], "channel"))
2264 {
2265 discrim->chan_mask = argv[++ii];
2266 }
2267 else if(0 == irccasecmp(argv[ii], "setter"))
2268 {
2269 discrim->setter_mask = argv[++ii];
2270 }
2271 else if(0 == irccasecmp(argv[ii], "reason"))
2272 {
2273 discrim->reason_mask = argv[++ii];
2274 }
2275 else if(0 == irccasecmp(argv[ii], "limit"))
2276 {
2277 discrim->limit = strtoul(argv[++ii], NULL, 0);
2278 }
2279 else if(0 == irccasecmp(argv[ii], "set"))
2280 {
2281 const char *cmp = argv[++ii];
2282 if(cmp[0] == '<') {
2283 if(cmp[1] == '=')
2284 discrim->min_set = now - ParseInterval(cmp + 2);
2285 else
2286 discrim->min_set = now - (ParseInterval(cmp + 1) - 1);
2287 } else if(cmp[0] == '=') {
2288 discrim->min_set = discrim->max_set = now - ParseInterval(cmp + 1);
2289 } else if(cmp[0] == '>') {
2290 if(cmp[1] == '=')
2291 discrim->max_set = now - ParseInterval(cmp + 2);
2292 else
2293 discrim->max_set = now - (ParseInterval(cmp + 1) - 1);
2294 } else {
2295 discrim->max_set = now - (ParseInterval(cmp) - 1);
2296 }
2297 }
2298 else if(0 == irccasecmp(argv[ii], "expires"))
2299 {
2300 const char *cmp = argv[++ii];
2301 if(cmp[0] == '<') {
2302 if(cmp[1] == '=')
2303 discrim->max_expires = now + ParseInterval(cmp + 2);
2304 else
2305 discrim->max_expires = now + (ParseInterval(cmp + 1) - 1);
2306 } else if(cmp[0] == '=') {
2307 discrim->min_expires = discrim->max_expires = now + ParseInterval(cmp + 1);
2308 } else if(cmp[0] == '>') {
2309 if(cmp[1] == '=')
2310 discrim->min_expires = now + ParseInterval(cmp + 2);
2311 else
2312 discrim->min_expires = now + (ParseInterval(cmp + 1) - 1);
2313 } else {
2314 discrim->min_expires = now + (ParseInterval(cmp) - 1);
2315 }
2316 }
2317 else
2318 {
2319 reply("MSG_INVALID_CRITERIA", argv[ii]);
2320 goto fail;
2321 }
ff3b058a 2322 }
1136f709 2323 return discrim;
2324
2325 fail:
2326 free(discrim);
2327 return NULL;
2328}
2329
2330typedef int (*dnr_search_func)(struct do_not_register *match, void *extra);
2331
2332static unsigned int
2333dnr_search(struct dnr_search *discrim, dnr_search_func dsf, void *data)
2334{
2335 struct do_not_register *dnr;
2336 dict_iterator_t next;
2337 dict_iterator_t it;
2338 unsigned int count;
2339 int target_fixed;
2340
2341 /* Initialize local variables. */
2342 count = 0;
2343 target_fixed = 0;
2344 if(discrim->chan_mask)
ff3b058a 2345 {
1136f709 2346 int shift = (discrim->chan_mask[0] == '\\' && discrim->chan_mask[1] == '*') ? 2 : 0;
2347 if('\0' == discrim->chan_mask[shift + strcspn(discrim->chan_mask+shift, "*?")])
2348 target_fixed = 1;
2349 }
2350
2351 if(target_fixed && discrim->chan_mask[0] == '\\' && discrim->chan_mask[1] == '*')
2352 {
2353 /* Check against account-based DNRs. */
2354 dnr = dict_find(handle_dnrs, discrim->chan_mask + 2, NULL);
2355 if(dnr && dnr_search_matches(dnr, discrim) && (count++ < discrim->limit))
2356 dsf(dnr, data);
2357 }
2358 else if(target_fixed)
2359 {
2360 /* Check against channel-based DNRs. */
2361 dnr = dict_find(plain_dnrs, discrim->chan_mask, NULL);
2362 if(dnr && dnr_search_matches(dnr, discrim) && (count++ < discrim->limit))
2363 dsf(dnr, data);
2364 }
2365 else
2366 {
2367 /* Exhaustively search account DNRs. */
2368 for(it = dict_first(handle_dnrs); it; it = next)
2369 {
2370 next = iter_next(it);
2371 dnr = iter_data(it);
2372 if(dnr_search_matches(dnr, discrim) && (count++ < discrim->limit) && dsf(dnr, data))
2373 break;
2374 }
2375
2376 /* Do the same for channel DNRs. */
2377 for(it = dict_first(plain_dnrs); it; it = next)
2378 {
2379 next = iter_next(it);
2380 dnr = iter_data(it);
2381 if(dnr_search_matches(dnr, discrim) && (count++ < discrim->limit) && dsf(dnr, data))
2382 break;
2383 }
2384
2385 /* Do the same for wildcarded channel DNRs. */
2386 for(it = dict_first(mask_dnrs); it; it = next)
2387 {
2388 next = iter_next(it);
2389 dnr = iter_data(it);
2390 if(dnr_search_matches(dnr, discrim) && (count++ < discrim->limit) && dsf(dnr, data))
2391 break;
2392 }
2393 }
2394 return count;
2395}
2396
2397static int
2398dnr_remove_func(struct do_not_register *match, void *extra)
2399{
2400 struct userNode *user;
2401 char *chan_name;
2402
2403 chan_name = alloca(strlen(match->chan_name) + 1);
2404 strcpy(chan_name, match->chan_name);
2405 user = extra;
2406 if(((chan_name[0] == '*') && dict_remove(handle_dnrs, chan_name+1))
2407 || dict_remove(plain_dnrs, chan_name)
2408 || dict_remove(mask_dnrs, chan_name))
2409 {
2410 send_message(user, chanserv, "CSMSG_DNR_REMOVED", chan_name);
2411 }
2412 return 0;
2413}
2414
2415static int
2416dnr_count_func(struct do_not_register *match, void *extra)
2417{
2418 return 0; (void)match; (void)extra;
2419}
2420
2421static MODCMD_FUNC(cmd_dnrsearch)
2422{
2423 struct dnr_search *discrim;
2424 dnr_search_func action;
2425 struct svccmd *subcmd;
2426 unsigned int matches;
2427 char buf[MAXLEN];
2428
2429 sprintf(buf, "dnrsearch %s", argv[1]);
2430 subcmd = dict_find(cmd->parent->commands, buf, NULL);
2431 if(!subcmd)
2432 {
2433 reply("CSMSG_DNR_BAD_ACTION", argv[1]);
2434 return 0;
d76ed9a9 2435 }
1136f709 2436 if(!svccmd_can_invoke(user, cmd->parent->bot, subcmd, channel, SVCCMD_NOISY))
2437 return 0;
2438 if(!irccasecmp(argv[1], "print"))
2439 action = dnr_print_func;
2440 else if(!irccasecmp(argv[1], "remove"))
2441 action = dnr_remove_func;
2442 else if(!irccasecmp(argv[1], "count"))
2443 action = dnr_count_func;
ff3b058a 2444 else
2445 {
1136f709 2446 reply("CSMSG_DNR_BAD_ACTION", argv[1]);
ff3b058a 2447 return 0;
2448 }
1136f709 2449
2450 discrim = dnr_search_create(user, cmd, argc-2, argv+2);
2451 if(!discrim)
2452 return 0;
2453
2454 if(action == dnr_print_func)
2455 reply("CSMSG_DNR_SEARCH_RESULTS");
2456 matches = dnr_search(discrim, action, user);
2457 if(matches)
2458 reply("MSG_MATCH_COUNT", matches);
2459 else
2460 reply("MSG_NO_MATCHES");
2461 free(discrim);
d76ed9a9
AS
2462 return 1;
2463}
2464
2465unsigned int
2466chanserv_get_owned_count(struct handle_info *hi)
2467{
2468 struct userData *cList;
2469 unsigned int owned;
2470
2471 for(owned=0, cList=hi->channels; cList; cList=cList->u_next)
2472 if(cList->access == UL_OWNER)
2473 owned++;
2474 return owned;
2475}
2476
2477static CHANSERV_FUNC(cmd_register)
2478{
d76ed9a9
AS
2479 struct handle_info *handle;
2480 struct chanData *cData;
2481 struct modeNode *mn;
2482 char reason[MAXLEN];
2483 char *chan_name;
2484 unsigned int new_channel, force=0;
2485 struct do_not_register *dnr;
f8f30e75
AS
2486 unsigned int n;
2487
08895577 2488 if (checkDefCon(DEFCON_NO_NEW_CHANNELS) && !IsOper(user)) {
2489 reply("CSMSG_DEFCON_NO_NEW_CHANNELS");
2490 return 0;
2491 }
d76ed9a9
AS
2492
2493 if(channel)
2494 {
2495 if(channel->channel_info)
2496 {
2497 reply("CSMSG_ALREADY_REGGED", channel->name);
2498 return 0;
2499 }
2500
2501 if(channel->bad_channel)
2502 {
2503 reply("CSMSG_ILLEGAL_CHANNEL", channel->name);
2504 return 0;
2505 }
2506
f8f30e75 2507 if(!IsHelping(user) && (!(mn = GetUserMode(channel, user)) || !(mn->modes & MODE_CHANOP)))
d76ed9a9
AS
2508 {
2509 reply("CSMSG_MUST_BE_OPPED", channel->name);
2510 return 0;
2511 }
2512
2513 new_channel = 0;
2514 chan_name = channel->name;
2515 }
2516 else
2517 {
fc8798ec
AS
2518 if(argc < 2)
2519 {
2520 reply("MSG_MISSING_PARAMS", cmd->name);
567a5f26 2521 svccmd_send_help_brief(user, chanserv, cmd);
fc8798ec
AS
2522 return 0;
2523 }
2524 if(!IsChannelName(argv[1]))
d76ed9a9
AS
2525 {
2526 reply("MSG_NOT_CHANNEL_NAME");
2527 return 0;
2528 }
2529
2530 if(opserv_bad_channel(argv[1]))
2531 {
2532 reply("CSMSG_ILLEGAL_CHANNEL", argv[1]);
2533 return 0;
2534 }
2535
2536 new_channel = 1;
2537 chan_name = argv[1];
2538 }
2539
2540 if(argc >= (new_channel+2))
2541 {
2542 if(!IsHelping(user))
2543 {
2544 reply("CSMSG_PROXY_FORBIDDEN");
2545 return 0;
2546 }
2547
2548 if(!(handle = modcmd_get_handle_info(user, argv[new_channel+1])))
2549 return 0;
2550 force = (argc > (new_channel+2)) && !irccasecmp(argv[new_channel+2], "force");
2551 dnr = chanserv_is_dnr(chan_name, handle);
f8f30e75
AS
2552
2553 /* Check if they are over the limit.. */
2554 if((chanserv_get_owned_count(handle) >= chanserv_conf.max_owned) && !force)
2555 {
2556 reply("CSMSG_OWN_TOO_MANY", handle->handle, chanserv_conf.max_owned);
2557 return 0;
2558 }
2559
d76ed9a9
AS
2560 }
2561 else
2562 {
f8f30e75 2563 handle = user->handle_info;
d76ed9a9 2564 dnr = chanserv_is_dnr(chan_name, handle);
f8f30e75
AS
2565 /* Check if they are over the limit.. */
2566 if((chanserv_get_owned_count(handle) >= chanserv_conf.max_owned) && !force)
2567 {
2568 reply("CSMSG_YOU_OWN_TOO_MANY", chanserv_conf.max_owned);
2569 return 0;
2570 }
2571 /* Check if another service is in the channel */
2572 if(channel)
2573 for(n = 0; n < channel->members.used; n++)
2574 {
2575 mn = channel->members.list[n];
2576 if((mn && mn->user && (mn->user->modes & FLAGS_SERVICE)) || IsLocal(mn->user))
2577 {
2578 reply("CSMSG_ANOTHER_SERVICE");
2579 return 0;
2580 }
2581 }
d76ed9a9
AS
2582 }
2583 if(dnr && !force)
2584 {
2585 if(!IsHelping(user))
2586 reply("CSMSG_DNR_CHANNEL", chan_name);
2587 else
1117fc5a 2588 chanserv_show_dnrs(user, cmd, chan_name, handle->handle);
d76ed9a9
AS
2589 return 0;
2590 }
2591
f8f30e75 2592 /* now handled above for message specilization *
d76ed9a9
AS
2593 if((chanserv_get_owned_count(handle) >= chanserv_conf.max_owned) && !force)
2594 {
2595 reply("CSMSG_OWN_TOO_MANY", handle->handle, chanserv_conf.max_owned);
2596 return 0;
2597 }
f8f30e75 2598 */
d76ed9a9 2599
d3abe0df 2600 if (chanserv_conf.valid_channel_regex_set) {
ed5c805e 2601 int err = regexec(&chanserv_conf.valid_channel_regex, chan_name, 0, 0, 0);
d3abe0df 2602 if (err) {
2603 char buff[256];
2604 buff[regerror(err, &chanserv_conf.valid_channel_regex, buff, sizeof(buff))] = 0;
2605 log_module(CS_LOG, LOG_INFO, "regexec error: %s (%d)", buff, err);
2606 }
2607 if(err == REG_NOMATCH) {
ed5c805e 2608 reply("CSMSG_ILLEGAL_CHANNEL", chan_name);
d3abe0df 2609 return 0;
2610 }
2611 }
2612
d76ed9a9 2613 if(new_channel)
ed5c805e 2614 channel = AddChannel(chan_name, now, NULL, NULL, NULL);
d76ed9a9
AS
2615
2616 cData = register_channel(channel, user->handle_info->handle);
fd20b142 2617 scan_user_presence(add_channel_user(cData, handle, UL_OWNER, 0, NULL, 0), NULL);
d76ed9a9 2618 cData->modes = chanserv_conf.default_modes;
1117fc5a 2619 if(off_channel > 0)
a32da4c7 2620 cData->modes.modes_set |= MODE_REGISTERED;
ec1a68c8 2621 if (IsOffChannel(cData))
2622 {
2623 mod_chanmode_announce(chanserv, channel, &cData->modes);
2624 }
2625 else
2626 {
2627 struct mod_chanmode *change = mod_chanmode_dup(&cData->modes, 1);
2628 change->args[change->argc].mode = MODE_CHANOP;
2629 change->args[change->argc].u.member = AddChannelUser(chanserv, channel);
2630 change->argc++;
2631 mod_chanmode_announce(chanserv, channel, change);
2632 mod_chanmode_free(change);
2633 }
d76ed9a9
AS
2634
2635 /* Initialize the channel's max user record. */
2636 cData->max = channel->members.used;
8b9e7d45 2637 cData->maxsetinfo = chanserv_conf.max_userinfo_length;
d76ed9a9
AS
2638
2639 if(handle != user->handle_info)
2640 reply("CSMSG_PROXY_SUCCESS", handle->handle, channel->name);
2641 else
d76ed9a9
AS
2642
2643 sprintf(reason, "%s registered to %s by %s.", channel->name, handle->handle, user->handle_info->handle);
09a3057c 2644 global_message_args(MESSAGE_RECIPIENT_OPERS | MESSAGE_RECIPIENT_HELPERS, "CSMSG_REGISTERED_TO", channel->name,
2645 handle->handle, user->handle_info->handle);
d76ed9a9
AS
2646 return 1;
2647}
2648
2649static const char *
2650make_confirmation_string(struct userData *uData)
2651{
2652 static char strbuf[16];
2653 char *src;
2654 unsigned int accum;
2655
2656 accum = 0;
2657 for(src = uData->handle->handle; *src; )
2658 accum = accum * 31 + toupper(*src++);
2659 if(uData->channel)
2660 for(src = uData->channel->channel->name; *src; )
2661 accum = accum * 31 + toupper(*src++);
2662 sprintf(strbuf, "%08x", accum);
2663 return strbuf;
2664}
2665
2666static CHANSERV_FUNC(cmd_unregister)
2667{
2668 char *name;
2669 char reason[MAXLEN];
2670 struct chanData *cData;
2671 struct userData *uData;
2672
2673 cData = channel->channel_info;
2674 if(!cData)
2675 {
2676 reply("CSMSG_NOT_REGISTERED", channel->name);
2677 return 0;
2678 }
2679
2680 uData = GetChannelUser(cData, user->handle_info);
2681 if(!uData || (uData->access < UL_OWNER))
2682 {
2683 reply("CSMSG_NO_ACCESS");
2684 return 0;
2685 }
2686
2687 if(IsProtected(cData))
2688 {
2689 reply("CSMSG_UNREG_NODELETE", channel->name);
2690 return 0;
2691 }
2692
2693 if(!IsHelping(user))
2694 {
2695 const char *confirm_string;
2696 if(IsSuspended(cData))
2697 {
2698 reply("CSMSG_CHAN_SUSPENDED", channel->name, cData->suspended->reason);
2699 return 0;
2700 }
2701 confirm_string = make_confirmation_string(uData);
2702 if((argc < 2) || strcmp(argv[1], confirm_string))
2703 {
fa0fac3e 2704 reply("CSMSG_CONFIRM_UNREG", channel->name, confirm_string);
d76ed9a9
AS
2705 return 0;
2706 }
2707 }
2708
2709 sprintf(reason, "unregistered by %s.", user->handle_info->handle);
2710 name = strdup(channel->name);
2711 unregister_channel(cData, reason);
63c95a47 2712 spamserv_cs_unregister(user, channel, manually, "unregistered");
d76ed9a9
AS
2713 reply("CSMSG_UNREG_SUCCESS", name);
2714 free(name);
2715 return 1;
2716}
2717
63c95a47 2718static void
2719ss_cs_join_channel(struct chanNode *channel, int spamserv_join)
2720{
2721 extern struct userNode *spamserv;
2722 struct mod_chanmode *change;
2723
2724 if(spamserv && spamserv_join && get_chanInfo(channel->name))
2725 {
2726 change = mod_chanmode_alloc(2);
2727 change->argc = 2;
2728 change->args[0].mode = MODE_CHANOP;
2729 change->args[0].u.member = AddChannelUser(chanserv, channel);
2730 change->args[1].mode = MODE_CHANOP;
2731 change->args[1].u.member = AddChannelUser(spamserv, channel);
2732 }
2733 else
2734 {
2735 change = mod_chanmode_alloc(1);
2736 change->argc = 1;
2737 change->args[0].mode = MODE_CHANOP;
2738 change->args[0].u.member = AddChannelUser(chanserv, channel);
2739 }
2740
2741 mod_chanmode_announce(chanserv, channel, change);
2742 mod_chanmode_free(change);
2743}
2744
d76ed9a9
AS
2745static CHANSERV_FUNC(cmd_move)
2746{
1117fc5a 2747 struct mod_chanmode change;
d76ed9a9
AS
2748 struct chanNode *target;
2749 struct modeNode *mn;
2750 struct userData *uData;
d76ed9a9 2751 struct do_not_register *dnr;
63c95a47 2752 int chanserv_join = 0, spamserv_join;
d76ed9a9
AS
2753
2754 REQUIRE_PARAMS(2);
2755
2756 if(IsProtected(channel->channel_info))
2757 {
2758 reply("CSMSG_MOVE_NODELETE", channel->name);
2759 return 0;
2760 }
2761
2762 if(!IsChannelName(argv[1]))
2763 {
2764 reply("MSG_NOT_CHANNEL_NAME");
2765 return 0;
2766 }
2767
2768 if(opserv_bad_channel(argv[1]))
2769 {
2770 reply("CSMSG_ILLEGAL_CHANNEL", argv[1]);
2771 return 0;
2772 }
2773
2774 if(!IsHelping(user) || (argc < 3) || irccasecmp(argv[2], "force"))
2775 {
2776 for(uData = channel->channel_info->users; uData; uData = uData->next)
2777 {
2778 if((uData->access == UL_OWNER) && (dnr = chanserv_is_dnr(argv[1], uData->handle)))
2779 {
2780 if(!IsHelping(user))
2781 reply("CSMSG_DNR_CHANNEL_MOVE", argv[1]);
2782 else
1117fc5a 2783 chanserv_show_dnrs(user, cmd, argv[1], uData->handle->handle);
d76ed9a9
AS
2784 return 0;
2785 }
2786 }
2787 }
2788
1117fc5a 2789 mod_chanmode_init(&change);
d76ed9a9
AS
2790 if(!(target = GetChannel(argv[1])))
2791 {
2aef5f4b 2792 target = AddChannel(argv[1], now, NULL, NULL, NULL);
d76ed9a9 2793 if(!IsSuspended(channel->channel_info))
63c95a47 2794 chanserv_join = 1;
d76ed9a9
AS
2795 }
2796 else if(target->channel_info)
2797 {
2798 reply("CSMSG_ALREADY_REGGED", target->name);
2799 return 0;
2800 }
2801 else if((!(mn = GetUserMode(target, user)) || !(mn->modes && MODE_CHANOP))
2802 && !IsHelping(user))
2803 {
2804 reply("CSMSG_MUST_BE_OPPED", target->name);
2805 return 0;
2806 }
2807 else if(!IsSuspended(channel->channel_info))
63c95a47 2808 chanserv_join = 1;
d76ed9a9 2809
1117fc5a 2810 if(off_channel > 0)
2811 {
2812 /* Clear MODE_REGISTERED from old channel, add it to new. */
2813 change.argc = 0;
2814 change.modes_clear = MODE_REGISTERED;
2815 mod_chanmode_announce(chanserv, channel, &change);
2816 change.modes_clear = 0;
2817 change.modes_set = MODE_REGISTERED;
2818 mod_chanmode_announce(chanserv, target, &change);
2819 }
2820
d76ed9a9
AS
2821 /* Move the channel_info to the target channel; it
2822 shouldn't be necessary to clear timeq callbacks
2823 for the old channel. */
2824 target->channel_info = channel->channel_info;
2825 target->channel_info->channel = target;
2826 channel->channel_info = NULL;
2827
63c95a47 2828 spamserv_join = spamserv_cs_move_merge(user, channel, target, 1);
2829
2830 if (chanserv_join)
2831 ss_cs_join_channel(target, spamserv_join);
d76ed9a9 2832
d76ed9a9
AS
2833 if(!IsSuspended(target->channel_info))
2834 {
2835 char reason2[MAXLEN];
09a3057c 2836 sprintf(reason2, "Channel moved to %s by %s.", target->name, user->handle_info->handle);
2837 DelChannelUser(chanserv, channel, reason2, 0);
d76ed9a9 2838 }
09a3057c 2839
d76ed9a9
AS
2840 UnlockChannel(channel);
2841 LockChannel(target);
09a3057c 2842 global_message_args(MESSAGE_RECIPIENT_OPERS | MESSAGE_RECIPIENT_HELPERS, "CSMSG_CHANNEL_MOVED",
2843 channel->name, target->name, user->handle_info->handle);
2844
63c95a47 2845 reply("CSMSG_MOVE_SUCCESS", target->name);
d76ed9a9
AS
2846 return 1;
2847}
2848
2849static void
2850merge_users(struct chanData *source, struct chanData *target)
2851{
2852 struct userData *suData, *tuData, *next;
2853 dict_iterator_t it;
2854 dict_t merge;
2855
2856 merge = dict_new();
2857
2858 /* Insert the source's users into the scratch area. */
2859 for(suData = source->users; suData; suData = suData->next)
2860 dict_insert(merge, suData->handle->handle, suData);
2861
2862 /* Iterate through the target's users, looking for
2863 users common to both channels. The lower access is
2864 removed from either the scratch area or target user
2865 list. */
2866 for(tuData = target->users; tuData; tuData = next)
2867 {
2868 struct userData *choice;
2869
2870 next = tuData->next;
2871
2872 /* If a source user exists with the same handle as a target
2873 channel's user, resolve the conflict by removing one. */
2874 suData = dict_find(merge, tuData->handle->handle, NULL);
2875 if(!suData)
2876 continue;
2877
2878 /* Pick the data we want to keep. */
2879 /* If the access is the same, use the later seen time. */
2880 if(suData->access == tuData->access)
2881 choice = (suData->seen > tuData->seen) ? suData : tuData;
2882 else /* Otherwise, keep the higher access level. */
2883 choice = (suData->access > tuData->access) ? suData : tuData;
2884
2885 /* Remove the user that wasn't picked. */
2886 if(choice == tuData)
2887 {
2888 dict_remove(merge, suData->handle->handle);
2889 del_channel_user(suData, 0);
2890 }
2891 else
2892 del_channel_user(tuData, 0);
2893 }
2894
2895 /* Move the remaining users to the target channel. */
2896 for(it = dict_first(merge); it; it = iter_next(it))
2897 {
2898 suData = iter_data(it);
2899
2900 /* Insert the user into the target channel's linked list. */
2901 suData->prev = NULL;
2902 suData->next = target->users;
2903 suData->channel = target;
2904
2905 if(target->users)
2906 target->users->prev = suData;
2907 target->users = suData;
2908
2909 /* Update the user counts for the target channel; the
2910 source counts are left alone. */
2911 target->userCount++;
2912 }
2913
2914 /* Possible to assert (source->users == NULL) here. */
2915 source->users = NULL;
2916 dict_delete(merge);
2917}
2918
2919static void
2920merge_bans(struct chanData *source, struct chanData *target)
2921{
2922 struct banData *sbData, *tbData, *sNext, *tNext, *tFront;
2923
2924 /* Hold on to the original head of the target ban list
2925 to avoid comparing source bans with source bans. */
2926 tFront = target->bans;
2927
2928 /* Perform a totally expensive O(n*m) merge, ick. */
2929 for(sbData = source->bans; sbData; sbData = sNext)
2930 {
2931 /* Flag to track whether the ban's been moved
2932 to the destination yet. */
2933 int moved = 0;
2934
2935 /* Possible to assert (sbData->prev == NULL) here. */
2936 sNext = sbData->next;
2937
2938 for(tbData = tFront; tbData; tbData = tNext)
2939 {
2940 tNext = tbData->next;
2941
2942 /* Perform two comparisons between each source
2943 and target ban, conflicts are resolved by
2944 keeping the broader ban and copying the later
2945 expiration and triggered time. */
2946 if(match_ircglobs(tbData->mask, sbData->mask))
2947 {
2948 /* There is a broader ban in the target channel that
2949 overrides one in the source channel; remove the
2950 source ban and break. */
2951 if(sbData->expires > tbData->expires)
2952 tbData->expires = sbData->expires;
2953 if(sbData->triggered > tbData->triggered)
2954 tbData->triggered = sbData->triggered;
2955 del_channel_ban(sbData);
2956 break;
2957 }
2958 else if(match_ircglobs(sbData->mask, tbData->mask))
2959 {
2960 /* There is a broader ban in the source channel that
2961 overrides one in the target channel; remove the
2962 target ban, fall through and move the source over. */
2963 if(tbData->expires > sbData->expires)
2964 sbData->expires = tbData->expires;
2965 if(tbData->triggered > sbData->triggered)
2966 sbData->triggered = tbData->triggered;
2967 if(tbData == tFront)
2968 tFront = tNext;
2969 del_channel_ban(tbData);
2970 }
2971
2972 /* Source bans can override multiple target bans, so
2973 we allow a source to run through this loop multiple
2974 times, but we can only move it once. */
2975 if(moved)
2976 continue;
2977 moved = 1;
2978
2979 /* Remove the source ban from the source ban list. */
2980 if(sbData->next)
2981 sbData->next->prev = sbData->prev;
2982
2983 /* Modify the source ban's associated channel. */
2984 sbData->channel = target;
2985
2986 /* Insert the ban into the target channel's linked list. */
2987 sbData->prev = NULL;
2988 sbData->next = target->bans;
2989
2990 if(target->bans)
2991 target->bans->prev = sbData;
2992 target->bans = sbData;
2993
2994 /* Update the user counts for the target channel. */
2995 target->banCount++;
2996 }
2997 }
2998
2999 /* Possible to assert (source->bans == NULL) here. */
3000 source->bans = NULL;
3001}
3002
3003static void
3004merge_data(struct chanData *source, struct chanData *target)
3005{
697f4c9a 3006 /* Use more recent visited and owner-transfer time; use older
3007 * registered time. Bitwise or may_opchan. Use higher max.
3008 * Do not touch last_refresh, ban count or user counts.
3009 */
d76ed9a9
AS
3010 if(source->visited > target->visited)
3011 target->visited = source->visited;
697f4c9a 3012 if(source->registered < target->registered)
3013 target->registered = source->registered;
3014 if(source->ownerTransfer > target->ownerTransfer)
3015 target->ownerTransfer = source->ownerTransfer;
3016 if(source->may_opchan)
3017 target->may_opchan = 1;
3018 if(source->max > target->max)
3019 target->max = source->max;
d76ed9a9
AS
3020}
3021
3022static void
3023merge_channel(struct chanData *source, struct chanData *target)
3024{
3025 merge_users(source, target);
3026 merge_bans(source, target);
3027 merge_data(source, target);
3028}
3029
3030static CHANSERV_FUNC(cmd_merge)
3031{
3032 struct userData *target_user;
3033 struct chanNode *target;
3034 char reason[MAXLEN];
b45fd7d7 3035 int nodelete = 0;
d76ed9a9
AS
3036
3037 REQUIRE_PARAMS(2);
3038
3039 /* Make sure the target channel exists and is registered to the user
3040 performing the command. */
3041 if(!(target = GetChannel(argv[1])))
3042 {
3043 reply("MSG_INVALID_CHANNEL");
3044 return 0;
3045 }
3046
b45fd7d7 3047 if (argc > 2) {
3048 if (!irccasecmp("nodelete", argv[2]))
3049 nodelete = 1;
3050 }
3051
d76ed9a9
AS
3052 if(!target->channel_info)
3053 {
3054 reply("CSMSG_NOT_REGISTERED", target->name);
3055 return 0;
3056 }
3057
3058 if(IsProtected(channel->channel_info))
3059 {
3060 reply("CSMSG_MERGE_NODELETE");
3061 return 0;
3062 }
3063
3064 if(IsSuspended(target->channel_info))
3065 {
3066 reply("CSMSG_MERGE_SUSPENDED");
3067 return 0;
3068 }
3069
3070 if(channel == target)
3071 {
3072 reply("CSMSG_MERGE_SELF");
3073 return 0;
3074 }
3075
3076 target_user = GetChannelUser(target->channel_info, user->handle_info);
3077 if(!target_user || (target_user->access < UL_OWNER))
3078 {
3079 reply("CSMSG_MERGE_NOT_OWNER");
3080 return 0;
3081 }
3082
3083 /* Merge the channel structures and associated data. */
3084 merge_channel(channel->channel_info, target->channel_info);
63c95a47 3085 spamserv_cs_move_merge(user, channel, target, 0);
d76ed9a9 3086 sprintf(reason, "merged into %s by %s.", target->name, user->handle_info->handle);
b45fd7d7 3087 if (!nodelete)
3088 unregister_channel(channel->channel_info, reason);
d76ed9a9
AS
3089 reply("CSMSG_MERGE_SUCCESS", target->name);
3090 return 1;
3091}
3092
3093static CHANSERV_FUNC(cmd_opchan)
3094{
3095 struct mod_chanmode change;
3096 if(!IsHelping(user) && !channel->channel_info->may_opchan)
3097 {
3098 reply("CSMSG_ALREADY_OPCHANNED", channel->name);
3099 return 0;
3100 }
6ff42e24 3101 if(!IsInChannel(channel,chanserv)) {
3102 reply("CSMSG_NOT_IN_CHANNEL", channel->name);
3103 return 0;
3104 }
d76ed9a9
AS
3105 channel->channel_info->may_opchan = 0;
3106 mod_chanmode_init(&change);
3107 change.argc = 1;
3108 change.args[0].mode = MODE_CHANOP;
a32da4c7 3109 change.args[0].u.member = GetUserMode(channel, chanserv);
1136f709 3110 if(!change.args[0].u.member)
3111 {
3112 reply("CSMSG_OUT_OF_CHANNEL", channel->name);
3113 return 0;
3114 }
d76ed9a9
AS
3115 mod_chanmode_announce(chanserv, channel, &change);
3116 reply("CSMSG_OPCHAN_DONE", channel->name);
3117 return 1;
3118}
3119
3120static CHANSERV_FUNC(cmd_adduser)
3121{
3122 struct userData *actee;
1136f709 3123 struct userData *actor, *real_actor;
3124 struct handle_info *handle = NULL;
3125 struct adduserPending *tmp;
3126 unsigned short access_level, override = 0;
d76ed9a9
AS
3127
3128 REQUIRE_PARAMS(3);
3129
3130 if(channel->channel_info->userCount >= chanserv_conf.max_chan_users)
3131 {
3132 reply("CSMSG_MAXIMUM_USERS", chanserv_conf.max_chan_users);
3133 return 0;
3134 }
3135
1136f709 3136 access_level = user_level_from_name(argv[2], UL_OWNER);
3137 if(!access_level)
d76ed9a9
AS
3138 {
3139 reply("CSMSG_INVALID_ACCESS", argv[2]);
3140 return 0;
3141 }
3142
3143 actor = GetChannelUser(channel->channel_info, user->handle_info);
1136f709 3144 real_actor = GetTrueChannelAccess(channel->channel_info, user->handle_info);
3145
3146 if(actor->access <= access_level)
d76ed9a9
AS
3147 {
3148 reply("CSMSG_NO_BUMP_ACCESS");
3149 return 0;
3150 }
3151
1136f709 3152 /* Trying to add someone with equal/more access */
3153 if (!real_actor || real_actor->access <= access_level)
3154 override = CMD_LOG_OVERRIDE;
3155
3156 if((actee = GetChannelAccess(channel->channel_info, handle)))
ac3bdc8d 3157 {
1136f709 3158 /* 'kevin must first authenticate with AuthServ'. is sent to user */
ac3bdc8d
AS
3159 struct userNode *unode;
3160 unode = GetUserH(argv[1]); /* find user struct by nick */
3161 if(unode)
3162 {
3163 if(find_adduser_pending(channel, unode)) {
3164 reply("CSMSG_ADDUSER_PENDING_ALREADY", channel->name);
3165 }
3166 else {
3167 if(IsInChannel(channel, unode)) {
3168 reply("CSMSG_ADDUSER_PENDING");
1136f709 3169 tmp = add_adduser_pending(channel, unode, access_level);
ac3bdc8d
AS
3170 send_message_type(1,unode, chanserv, "CSMSG_ADDUSER_PENDING_TARGET", user->nick, channel->name);
3171 }
3172 /* this results in user must auth AND not in chan errors. too confusing..
3173 else {
3174 reply("CSMSG_ADDUSER_PENDING_NOTINCHAN", channel->name);
3175 }
3176 */
3177 }
3178 }
d76ed9a9 3179 return 0;
ac3bdc8d 3180 }
d76ed9a9
AS
3181
3182 if((actee = GetTrueChannelAccess(channel->channel_info, handle)))
3183 {
a915f7d4 3184 reply("CSMSG_USER_EXISTS", handle->handle, channel->name, user_level_name_from_level(actee->access));
d76ed9a9
AS
3185 return 0;
3186 }
3187
dfaa28a4 3188 time_t accessexpiry = 0;
fd20b142 3189 unsigned int duration = 0;
3190 if (argc > 3) {
3191 if ((duration = ParseInterval(argv[3])))
dfaa28a4 3192 accessexpiry = now + duration;
fd20b142 3193 }
3194
1136f709 3195 actee = add_channel_user(channel->channel_info, handle, access_level, 0, NULL, accessexpiry);
d76ed9a9 3196 scan_user_presence(actee, NULL);
fd20b142 3197
3198 if (duration > 0)
dfaa28a4 3199 timeq_add(accessexpiry, chanserv_expire_tempuser, actee);
fd20b142 3200
1136f709 3201 reply("CSMSG_ADDED_USER", handle->handle, channel->name, user_level_name_from_level(access_level), access);
3202 return 1 | override;
d76ed9a9
AS
3203}
3204
3205static CHANSERV_FUNC(cmd_clvl)
3206{
3207 struct handle_info *handle;
3208 struct userData *victim;
1136f709 3209 struct userData *actor, *real_actor;
3210 unsigned short new_access, override = 0;
d76ed9a9
AS
3211 int privileged = IsHelping(user) && ((user->handle_info->opserv_level >= chanserv_conf.nodelete_level) || !IsProtected(channel->channel_info));
3212
3213 REQUIRE_PARAMS(3);
3214
3215 actor = GetChannelUser(channel->channel_info, user->handle_info);
1136f709 3216 real_actor = GetChannelAccess(channel->channel_info, user->handle_info);
d76ed9a9
AS
3217
3218 if(!(handle = modcmd_get_handle_info(user, argv[1])))
3219 return 0;
3220
3221 if(handle == user->handle_info && !privileged)
3222 {
3223 reply("CSMSG_NO_SELF_CLVL");
3224 return 0;
3225 }
3226
3227 if(!(victim = GetTrueChannelAccess(channel->channel_info, handle)))
3228 {
3229 reply("CSMSG_NO_CHAN_USER", handle->handle, channel->name);
3230 return 0;
3231 }
3232
3233 if(actor->access <= victim->access && !privileged)
3234 {
3235 reply("MSG_USER_OUTRANKED", handle->handle);
3236 return 0;
3237 }
3238
3239 new_access = user_level_from_name(argv[2], UL_OWNER);
3240
3241 if(!new_access)
3242 {
3243 reply("CSMSG_INVALID_ACCESS", argv[2]);
3244 return 0;
3245 }
3246
3247 if(new_access >= actor->access && !privileged)
3248 {
3249 reply("CSMSG_NO_BUMP_ACCESS");
3250 return 0;
3251 }
3252
dfaa28a4 3253 time_t clvlexpiry = 0;
3254 unsigned int duration = 0;
3255 if (argc > 3) {
3256 if ((duration = ParseInterval(argv[3])))
3257 clvlexpiry = now + duration;
3258 }
3259
3260 if (duration > 0) {
3261 if (victim->accessexpiry > 0) {
3262 reply("CSMSG_NO_BUMP_EXPIRY");
3263 return 0;
3264 }
3265
3266 victim->clvlexpiry = clvlexpiry;
3267 victim->lastaccess = victim->access;
3268 timeq_add(clvlexpiry, chanserv_expire_tempclvl, victim);
3269 }
3270
1136f709 3271 /* Trying to clvl a equal/higher user */
3272 if(!real_actor || (real_actor->access <= victim->access && handle != user->handle_info))
3273 override = CMD_LOG_OVERRIDE;
3274 /* Trying to clvl someone to equal/higher access */
3275 if(!real_actor || new_access >= real_actor->access)
3276 override = CMD_LOG_OVERRIDE;
3277 /* Helpers clvling themselves get caught by the "clvl someone to equal/higher access" check.
3278 * If they lower their own access it's not a big problem.
3279 */
d76ed9a9 3280 victim->access = new_access;
de9510bc 3281 reply("CSMSG_CHANGED_ACCESS", handle->handle, user_level_name_from_level(new_access), new_access, channel->name);
1136f709 3282 return 1 | override;
d76ed9a9
AS
3283}
3284
3285static CHANSERV_FUNC(cmd_deluser)
3286{
3287 struct handle_info *handle;
3288 struct userData *victim;
1136f709 3289 struct userData *actor, *real_actor;
3290 unsigned short access_level, override = 0;
d76ed9a9
AS
3291 char *chan_name;
3292
3293 REQUIRE_PARAMS(2);
3294
3295 actor = GetChannelUser(channel->channel_info, user->handle_info);
1136f709 3296 real_actor = GetChannelAccess(channel->channel_info, user->handle_info);
3297
d76ed9a9
AS
3298 if(!(handle = modcmd_get_handle_info(user, argv[argc-1])))
3299 return 0;
3300
3301 if(!(victim = GetTrueChannelAccess(channel->channel_info, handle)))
3302 {
3303 reply("CSMSG_NO_CHAN_USER", handle->handle, channel->name);
3304 return 0;
3305 }
3306
3307 if(argc > 2)
3308 {
1136f709 3309 access_level = user_level_from_name(argv[1], UL_OWNER);
7b0150f8 3310 char *useraccess = user_level_name_from_level(victim->access);
1136f709 3311 if(!access_level)
d76ed9a9
AS
3312 {
3313 reply("CSMSG_INVALID_ACCESS", argv[1]);
3314 return 0;
3315 }
7b0150f8 3316 if(strcasecmp(argv[1], useraccess))
d76ed9a9 3317 {
a915f7d4 3318 reply("CSMSG_INCORRECT_ACCESS", handle->handle, user_level_name_from_level(victim->access), argv[1]);
d76ed9a9
AS
3319 return 0;
3320 }
3321 }
3322 else
3323 {
1136f709 3324 access_level = victim->access;
d76ed9a9
AS
3325 }
3326
3327 if((actor->access <= victim->access) && !IsHelping(user))
3328 {
3329 reply("MSG_USER_OUTRANKED", victim->handle->handle);
3330 return 0;
3331 }
3332
1136f709 3333 /* If people delete themselves it is an override, but they could've used deleteme so we don't log it as an override */
3334 if(!real_actor || (real_actor->access <= victim->access && real_actor != victim))
3335 override = CMD_LOG_OVERRIDE;
3336
d76ed9a9
AS
3337 chan_name = strdup(channel->name);
3338 del_channel_user(victim, 1);
1136f709 3339 reply("CSMSG_DELETED_USER", handle->handle, access_level, chan_name);
d76ed9a9 3340 free(chan_name);
1136f709 3341 return 1 | override;
d76ed9a9
AS
3342}
3343
3344static int
3345cmd_mdel_user(struct userNode *user, struct chanNode *channel, unsigned short min_access, unsigned short max_access, char *mask, struct svccmd *cmd)
3346{
1136f709 3347 struct userData *actor, *real_actor, *uData, *next;
3348 unsigned int override = 0;
d76ed9a9
AS
3349
3350 actor = GetChannelUser(channel->channel_info, user->handle_info);
1136f709 3351 real_actor = GetChannelAccess(channel->channel_info, user->handle_info);
3352
d76ed9a9
AS
3353 if(min_access > max_access)
3354 {
3355 reply("CSMSG_BAD_RANGE", min_access, max_access);
3356 return 0;
3357 }
3358
3359 if((actor->access <= max_access) && !IsHelping(user))
3360 {
3361 reply("CSMSG_NO_ACCESS");
3362 return 0;
3363 }
3364
1136f709 3365 if(!real_actor || real_actor->access <= max_access)
3366 override = CMD_LOG_OVERRIDE;
3367
d76ed9a9
AS
3368 for(uData = channel->channel_info->users; uData; uData = next)
3369 {
3370 next = uData->next;
3371
3372 if((uData->access >= min_access)
3373 && (uData->access <= max_access)
3374 && match_ircglob(uData->handle->handle, mask))
3375 del_channel_user(uData, 1);
3376 }
3377
3378 reply("CSMSG_DELETED_USERS", mask, min_access, max_access, channel->name);
1136f709 3379 return 1 | override;
d76ed9a9
AS
3380}
3381
3382static CHANSERV_FUNC(cmd_mdelowner)
3383{
3384 return cmd_mdel_user(user, channel, UL_OWNER, UL_OWNER, argv[1], cmd);
3385}
3386
3387static CHANSERV_FUNC(cmd_mdelcoowner)
3388{
11d3c79b 3389 return cmd_mdel_user(user, channel, UL_COOWNER, UL_OWNER-1, argv[1], cmd);
d76ed9a9
AS
3390}
3391
4048352e 3392static CHANSERV_FUNC(cmd_mdelmanager)
d76ed9a9 3393{
11d3c79b 3394 return cmd_mdel_user(user, channel, UL_MANAGER, UL_COOWNER-1, argv[1], cmd);
d76ed9a9
AS
3395}
3396
3397static CHANSERV_FUNC(cmd_mdelop)
3398{
11d3c79b 3399 return cmd_mdel_user(user, channel, UL_OP, UL_MANAGER-1, argv[1], cmd);
d76ed9a9
AS
3400}
3401
11d3c79b 3402static CHANSERV_FUNC(cmd_mdelhalfop)
d76ed9a9 3403{
11d3c79b 3404 return cmd_mdel_user(user, channel, UL_HALFOP, UL_OP-1, argv[1], cmd);
d76ed9a9
AS
3405}
3406
11d3c79b 3407static CHANSERV_FUNC(cmd_mdelpeon)
55342ce8 3408{
11d3c79b 3409 return cmd_mdel_user(user, channel, UL_PEON, UL_HALFOP-1, argv[1], cmd);
55342ce8 3410}
3411
2a4ca4f5
MB
3412static CHANSERV_FUNC(cmd_mdelpal)
3413{
3414 return cmd_mdel_user(user, channel, UL_PEON, UL_HALFOP-1, argv[1], cmd);
3415}
55342ce8 3416
c9bf23fe
AS
3417static CHANSERV_FUNC(cmd_levels)
3418{
3419 struct helpfile_table tbl;
3420 int ii = 0;
3421
3422 tbl.length = 6 + 1; // 6 levels
3423 tbl.width = 4;
3424 tbl.flags = 0;
3425 tbl.contents = calloc(tbl.length,sizeof(tbl.contents[0]));
3426 tbl.contents[0] = calloc(tbl.width,sizeof(tbl.contents[0][0]));
3427 tbl.contents[0][0] = "Level";
3428 tbl.contents[0][1] = "From";
3429 tbl.contents[0][2] = "-";
3430 tbl.contents[0][3] = "To";
3431
3432 tbl.contents[++ii] = calloc(tbl.width, sizeof(tbl.contents[0][0]));
3433 tbl.contents[ii][0] = strdup(user_level_name_from_level(UL_OWNER));
3434 tbl.contents[ii][1] = msnprintf(4, "%d", UL_OWNER);
7a278540 3435 tbl.contents[ii][2] = msnprintf(2, " ");
c9bf23fe
AS
3436 tbl.contents[ii][3] = msnprintf(1, "");
3437
3438 tbl.contents[++ii] = calloc(tbl.width, sizeof(tbl.contents[0][0]));
3439 tbl.contents[ii][0] = strdup(user_level_name_from_level(UL_COOWNER));
3440 tbl.contents[ii][1] = msnprintf(4, "%d", UL_COOWNER);
7a278540 3441 tbl.contents[ii][2] = msnprintf(2, "-");
c9bf23fe
AS
3442 tbl.contents[ii][3] = msnprintf(4, "%d", UL_OWNER-1);
3443
3444 tbl.contents[++ii] = calloc(tbl.width, sizeof(tbl.contents[0][0]));
3445 tbl.contents[ii][0] = strdup(user_level_name_from_level(UL_MANAGER));
3446 tbl.contents[ii][1] = msnprintf(4, "%d", UL_MANAGER);
7a278540 3447 tbl.contents[ii][2] = msnprintf(2, "-");
c9bf23fe
AS
3448 tbl.contents[ii][3] = msnprintf(4, "%d", UL_COOWNER-1);
3449
3450 tbl.contents[++ii] = calloc(tbl.width, sizeof(tbl.contents[0][0]));
3451 tbl.contents[ii][0] = strdup(user_level_name_from_level(UL_OP));
3452 tbl.contents[ii][1] = msnprintf(4, "%d", UL_OP);
7a278540 3453 tbl.contents[ii][2] = msnprintf(2, "-");
c9bf23fe
AS
3454 tbl.contents[ii][3] = msnprintf(4, "%d", UL_MANAGER-1);
3455
3456 tbl.contents[++ii] = calloc(tbl.width, sizeof(tbl.contents[0][0]));
3457 tbl.contents[ii][0] = strdup(user_level_name_from_level(UL_HALFOP));
3458 tbl.contents[ii][1] = msnprintf(4, "%d", UL_HALFOP);
7a278540 3459 tbl.contents[ii][2] = msnprintf(2, "-");
c9bf23fe
AS
3460 tbl.contents[ii][3] = msnprintf(4, "%d", UL_OP-1);
3461
3462 tbl.contents[++ii] = calloc(tbl.width, sizeof(tbl.contents[0][0]));
3463 tbl.contents[ii][0] = strdup(user_level_name_from_level(UL_PEON));
3464 tbl.contents[ii][1] = msnprintf(4, "%d", UL_PEON);
7a278540 3465 tbl.contents[ii][2] = msnprintf(2, "-");
c9bf23fe
AS
3466 tbl.contents[ii][3] = msnprintf(4, "%d", UL_HALFOP-1);
3467
3468 table_send(cmd->parent->bot, user->nick, 0, NULL, tbl);
3469 return 0;
3470
3471/*
3472 reply("CSMSG_LEVELS_HEADER");
3473 reply("CSMSG_LEVELS", user_level_name_from_level(UL_OWNER), UL_OWNER, UL_OWNER);
3474 reply("CSMSG_LEVELS", user_level_name_from_level(UL_COOWNER), UL_COOWNER, UL_OWNER-1);
3475 reply("CSMSG_LEVELS", user_level_name_from_level(UL_MANAGER), UL_MANAGER, UL_COOWNER-1);
3476 reply("CSMSG_LEVELS", user_level_name_from_level(UL_OP), UL_OP, UL_MANAGER-1);
3477 reply("CSMSG_LEVELS", user_level_name_from_level(UL_HALFOP), UL_HALFOP, UL_OP-1);
3478 reply("CSMSG_LEVELS", user_level_name_from_level(UL_PEON), UL_PEON, UL_HALFOP-1);
3479 reply("CSMSG_BAR");
3480 */
3481}
3482
c8273589 3483/* trim_lamers.. */
d76ed9a9 3484static int
c092fcad 3485cmd_trim_bans(struct svccmd *cmd, struct userNode *user, struct chanNode *channel, unsigned long duration)
d76ed9a9
AS
3486{
3487 struct banData *bData, *next;
3488 char interval[INTERVALLEN];
3489 unsigned int count;
3490 time_t limit;
3491
3492 count = 0;
3493 limit = now - duration;
3494 for(bData = channel->channel_info->bans; bData; bData = next)
3495 {
3496 next = bData->next;
3497
3498 if((bData->triggered && bData->triggered >= limit) || (bData->set && bData->set >= limit))
3499 continue;
3500
3501 del_channel_ban(bData);
3502 count++;
3503 }
3504
3505 intervalString(interval, duration, user->handle_info);
c092fcad 3506 reply("CSMSG_TRIMMED_LAMERS", count, channel->name, interval);
d76ed9a9
AS
3507 return 1;
3508}
3509
3510static int
c092fcad 3511cmd_trim_users(struct svccmd *cmd, struct userNode *user, struct chanNode *channel, unsigned short min_access, unsigned short max_access, unsigned long duration, int vacation)
d76ed9a9
AS
3512{
3513 struct userData *actor, *uData, *next;
3514 char interval[INTERVALLEN];
3515 unsigned int count;
3516 time_t limit;
3517
1136f709 3518 actor = GetChannelAccess(channel->channel_info, user->handle_info);
d76ed9a9
AS
3519 if(min_access > max_access)
3520 {
c092fcad 3521 reply("CSMSG_BAD_RANGE", min_access, max_access);
d76ed9a9
AS
3522 return 0;
3523 }
3524
1136f709 3525 if(!actor || actor->access <= max_access)
d76ed9a9 3526 {
c092fcad 3527 reply("CSMSG_NO_ACCESS");
d76ed9a9
AS
3528 return 0;
3529 }
3530
3531 count = 0;
3532 limit = now - duration;
3533 for(uData = channel->channel_info->users; uData; uData = next)
3534 {
3535 next = uData->next;
3536
2f61d1d7 3537 if((uData->seen > limit)
3538 || uData->present
3539 || (HANDLE_FLAGGED(uData->handle, FROZEN) && !vacation))
d76ed9a9
AS
3540 continue;
3541
3542 if(((uData->access >= min_access) && (uData->access <= max_access))
3543 || (!max_access && (uData->access < actor->access)))
3544 {
3545 del_channel_user(uData, 1);
3546 count++;
3547 }
3548 }
3549
3550 if(!max_access)
3551 {
3552 min_access = 1;
a32da4c7 3553 max_access = (actor->access > UL_OWNER) ? UL_OWNER : (actor->access - 1);
d76ed9a9 3554 }
c092fcad 3555 reply("CSMSG_TRIMMED_USERS", count, min_access, max_access, channel->name, intervalString(interval, duration, user->handle_info));
d76ed9a9
AS
3556 return 1;
3557}
3558
3559static CHANSERV_FUNC(cmd_trim)
3560{
3561 unsigned long duration;
3562 unsigned short min_level, max_level;
2f61d1d7 3563 int vacation;
d76ed9a9
AS
3564
3565 REQUIRE_PARAMS(3);
3566
2f61d1d7 3567 vacation = argc > 3 && !strcmp(argv[3], "vacation");
d76ed9a9
AS
3568 duration = ParseInterval(argv[2]);
3569 if(duration < 60)
3570 {
3571 reply("CSMSG_CANNOT_TRIM");
3572 return 0;
3573 }
3574
c8273589 3575 if(!irccasecmp(argv[1], "lamers"))
d76ed9a9 3576 {
c092fcad 3577 cmd_trim_bans(cmd, user, channel, duration); /* trim_lamers.. */
d76ed9a9
AS
3578 return 1;
3579 }
3580 else if(!irccasecmp(argv[1], "users"))
3581 {
c092fcad 3582 cmd_trim_users(cmd, user, channel, 0, 0, duration, vacation);
d76ed9a9
AS
3583 return 1;
3584 }
3585 else if(parse_level_range(&min_level, &max_level, argv[1]))
3586 {
c092fcad 3587 cmd_trim_users(cmd, user, channel, min_level, max_level, duration, vacation);
d76ed9a9
AS
3588 return 1;
3589 }
3590 else if((min_level = user_level_from_name(argv[1], UL_OWNER)))
3591 {
c092fcad 3592 cmd_trim_users(cmd, user, channel, min_level, min_level, duration, vacation);
d76ed9a9
AS
3593 return 1;
3594 }
3595 else
3596 {
3597 reply("CSMSG_INVALID_TRIM", argv[1]);
3598 return 0;
3599 }
3600}
3601
3602/* If argc is 0 in cmd_up or cmd_down, no notices will be sent
3603 to the user. cmd_all takes advantage of this. */
3604static CHANSERV_FUNC(cmd_up)
3605{
3606 struct mod_chanmode change;
3607 struct userData *uData;
3608 const char *errmsg;
3609
3610 mod_chanmode_init(&change);
3611 change.argc = 1;
a32da4c7 3612 change.args[0].u.member = GetUserMode(channel, user);
3613 if(!change.args[0].u.member)
d76ed9a9
AS
3614 {
3615 if(argc)
3616 reply("MSG_CHANNEL_ABSENT", channel->name);
3617 return 0;
3618 }
3619
3620 uData = GetChannelAccess(channel->channel_info, user->handle_info);
3621 if(!uData)
3622 {
3623 if(argc)
3624 reply("CSMSG_GODMODE_UP", argv[0]);
3625 return 0;
3626 }
4b6129c0 3627 else if(uData->access >= UL_OP)
d76ed9a9
AS
3628 {
3629 change.args[0].mode = MODE_CHANOP;
3630 errmsg = "CSMSG_ALREADY_OPPED";
3631 }
4b6129c0 3632 else if(uData->access >= UL_HALFOP )
55342ce8 3633 {
3634 change.args[0].mode = MODE_HALFOP;
3635 errmsg = "CSMSG_ALREADY_HALFOPPED";
3636 }
4b6129c0 3637 else if(uData->access >= UL_PEON && (channel->channel_info->chOpts[chAutomode] != 'm' ))
d76ed9a9
AS
3638 {
3639 change.args[0].mode = MODE_VOICE;
3640 errmsg = "CSMSG_ALREADY_VOICED";
3641 }
3642 else
3643 {
3644 if(argc)
3645 reply("CSMSG_NO_ACCESS");
3646 return 0;
3647 }
a32da4c7 3648 change.args[0].mode &= ~change.args[0].u.member->modes;
d76ed9a9
AS
3649 if(!change.args[0].mode)
3650 {
3651 if(argc)
3652 reply(errmsg, channel->name);
3653 return 0;
3654 }
3655 modcmd_chanmode_announce(&change);
3656 return 1;
3657}
3658
3659static CHANSERV_FUNC(cmd_down)
3660{
3661 struct mod_chanmode change;
3662
3663 mod_chanmode_init(&change);
3664 change.argc = 1;
a32da4c7 3665 change.args[0].u.member = GetUserMode(channel, user);
3666 if(!change.args[0].u.member)
d76ed9a9
AS
3667 {
3668 if(argc)
3669 reply("MSG_CHANNEL_ABSENT", channel->name);
3670 return 0;
3671 }
3672
a32da4c7 3673 if(!change.args[0].u.member->modes)
d76ed9a9
AS
3674 {
3675 if(argc)
3676 reply("CSMSG_ALREADY_DOWN", channel->name);
3677 return 0;
3678 }
3679
a32da4c7 3680 change.args[0].mode = MODE_REMOVE | change.args[0].u.member->modes;
d76ed9a9
AS
3681 modcmd_chanmode_announce(&change);
3682 return 1;
3683}
3684
3685static int cmd_all(struct userNode *user, UNUSED_ARG(struct chanNode *channel), UNUSED_ARG(unsigned int argc), UNUSED_ARG(char *argv[]), struct svccmd *cmd, modcmd_func_t mcmd)
3686{
3687 struct userData *cList;
3688
3689 for(cList = user->handle_info->channels; cList; cList = cList->u_next)
3690 {
3691 if(IsSuspended(cList->channel)
3692 || IsUserSuspended(cList)
3693 || !GetUserMode(cList->channel->channel, user))
3694 continue;
3695
3696 mcmd(user, cList->channel->channel, 0, NULL, cmd);
3697 }
3698
3699 return 1;
3700}
3701
3702static CHANSERV_FUNC(cmd_upall)
3703{
3704 return cmd_all(CSFUNC_ARGS, cmd_up);
3705}
3706
3707static CHANSERV_FUNC(cmd_downall)
3708{
3709 return cmd_all(CSFUNC_ARGS, cmd_down);
3710}
3711
c092fcad 3712typedef int validate_func_t(struct svccmd *cmd, struct userNode *user, struct chanNode *channel, struct userNode *victim);
d76ed9a9
AS
3713typedef void process_func_t(unsigned int num, struct userNode **newops, struct chanNode *channel, struct userNode *who, int announce);
3714
3715static int
3716modify_users(struct userNode *user, struct chanNode *channel, unsigned int argc, char *argv[], struct svccmd *cmd, validate_func_t validate, chan_mode_t mode, char *action)
3717{
3718 unsigned int ii, valid;
3719 struct userNode *victim;
3720 struct mod_chanmode *change;
3721
3722 change = mod_chanmode_alloc(argc - 1);
3723
3724 for(ii=valid=0; ++ii < argc; )
3725 {
3726 if(!(victim = GetUserH(argv[ii])))
3727 continue;
3728 change->args[valid].mode = mode;
a32da4c7 3729 change->args[valid].u.member = GetUserMode(channel, victim);
3730 if(!change->args[valid].u.member)
d76ed9a9 3731 continue;
c092fcad 3732 if(validate && !validate(cmd, user, channel, victim))
d76ed9a9
AS
3733 continue;
3734 valid++;
3735 }
3736
3737 change->argc = valid;
3738 if(valid < (argc-1))
3739 reply("CSMSG_PROCESS_FAILED");
3740 if(valid)
3741 {
3742 modcmd_chanmode_announce(change);
3743 reply(action, channel->name);
3744 }
3745 mod_chanmode_free(change);
3746 return 1;
3747}
3748
3749static CHANSERV_FUNC(cmd_op)
3750{
3751 return modify_users(CSFUNC_ARGS, validate_op, MODE_CHANOP, "CSMSG_OPPED_USERS");
3752}
3753
55342ce8 3754static CHANSERV_FUNC(cmd_hop)
3755{
3756 return modify_users(CSFUNC_ARGS, validate_halfop, MODE_HALFOP, "CSMSG_HALFOPPED_USERS");
3757}
3758
d76ed9a9
AS
3759static CHANSERV_FUNC(cmd_deop)
3760{
3761 return modify_users(CSFUNC_ARGS, validate_deop, MODE_REMOVE|MODE_CHANOP, "CSMSG_DEOPPED_USERS");
3762}
3763
55342ce8 3764static CHANSERV_FUNC(cmd_dehop)
3765{
3766 return modify_users(CSFUNC_ARGS, validate_dehop, MODE_REMOVE|MODE_HALFOP, "CSMSG_DEHALFOPPED_USERS");
3767}
3768
d76ed9a9
AS
3769static CHANSERV_FUNC(cmd_voice)
3770{
3771 return modify_users(CSFUNC_ARGS, NULL, MODE_VOICE, "CSMSG_VOICED_USERS");
3772}
3773
3774static CHANSERV_FUNC(cmd_devoice)
3775{
3776 return modify_users(CSFUNC_ARGS, NULL, MODE_REMOVE|MODE_VOICE, "CSMSG_DEVOICED_USERS");
3777}
3778
3779static int
697f4c9a 3780bad_channel_ban(struct chanNode *channel, struct userNode *user, const char *ban, unsigned int *victimCount, struct modeNode **victims)
d76ed9a9
AS
3781{
3782 unsigned int ii;
3783
3784 if(victimCount)
3785 *victimCount = 0;
3786 for(ii=0; ii<channel->members.used; ii++)
3787 {
3788 struct modeNode *mn = channel->members.list[ii];
3789
3790 if(IsService(mn->user))
3791 continue;
3792
2f61d1d7 3793 if(!user_matches_glob(mn->user, ban, MATCH_USENICK | MATCH_VISIBLE))
d76ed9a9
AS
3794 continue;
3795
ff5f1ab2 3796 if(protect_user(mn->user, user, channel->channel_info, false))
d76ed9a9
AS
3797 return 1;
3798
3799 if(victims)
3800 victims[(*victimCount)++] = mn;
3801 }
3802 return 0;
3803}
3804
3805static int
3806eject_user(struct userNode *user, struct chanNode *channel, unsigned int argc, char *argv[], struct svccmd *cmd, int action)
3807{
3808 struct userNode *victim;
3809 struct modeNode **victims;
3810 unsigned int offset, n, victimCount, duration = 0;
d3347099 3811 char *reason = "Bye.", *ban, *name;
d76ed9a9
AS
3812 char interval[INTERVALLEN];
3813
c8273589 3814 offset = (action & ACTION_ADD_TIMED_LAMER) ? 3 : 2;
d76ed9a9
AS
3815 REQUIRE_PARAMS(offset);
3816 if(argc > offset)
3817 {
3818 reason = unsplit_string(argv + offset, argc - offset, NULL);
3819 if(strlen(reason) > (TOPICLEN - (NICKLEN + 3)))
3820 {
3821 /* Truncate the reason to a length of TOPICLEN, as
3822 the ircd does; however, leave room for an ellipsis
3823 and the kicker's nick. */
3824 sprintf(reason + (TOPICLEN - (NICKLEN + 6)), "...");
3825 }
3826 }
3827
3828 if((victim = GetUserH(argv[1])))
3829 {
3830 victims = alloca(sizeof(victims[0]));
3831 victims[0] = GetUserMode(channel, victim);
3832 /* XXX: The comparison with ACTION_KICK is just because all
3833 * other actions can work on users outside the channel, and we
3834 * want to allow those (e.g. unbans) in that case. If we add
3835 * some other ejection action for in-channel users, change
3836 * this too. */
3837 victimCount = victims[0] ? 1 : 0;
3838
3839 if(IsService(victim))
3840 {
35caf917
AS
3841 if(cmd)
3842 reply("MSG_SERVICE_IMMUNE", victim->nick);
d76ed9a9
AS
3843 return 0;
3844 }
3845
3846 if((action == ACTION_KICK) && !victimCount)
3847 {
35caf917
AS
3848 if(cmd)
3849 reply("MSG_CHANNEL_USER_ABSENT", victim->nick, channel->name);
d76ed9a9
AS
3850 return 0;
3851 }
3852
ff5f1ab2 3853 if(protect_user(victim, user, channel->channel_info, false))
d76ed9a9 3854 {
35caf917
AS
3855 // This translates to send_message(user, cmd->parent->bot, ...)
3856 // if user is x3 (ctcp action) cmd is null and segfault.
3857 if(cmd)
3858 reply("CSMSG_USER_PROTECTED", victim->nick);
d76ed9a9
AS
3859 return 0;
3860 }
3861
d3347099 3862 ban = generate_hostmask(victim, GENMASK_STRICT_HOST|GENMASK_ANY_IDENT);
d76ed9a9
AS
3863 name = victim->nick;
3864 }
1136f709 3865 else if(!is_ircmask(argv[1]) && (*argv[1] == '*'))
3866 {
3867 struct handle_info *hi;
3868 char banmask[NICKLEN + USERLEN + HOSTLEN + 3];
3869 const char *accountname = argv[1] + 1;
3870
3871 if(!(hi = get_handle_info(accountname)))
3872 {
3873 reply("MSG_HANDLE_UNKNOWN", accountname);
3874 return 0;
3875 }
3876
3877 snprintf(banmask, sizeof(banmask), "*!*@%s.*", hi->handle);
3878 victims = alloca(sizeof(victims[0]) * channel->members.used);
3879
3880 if(bad_channel_ban(channel, user, banmask, &victimCount, victims))
3881 {
3882 reply("CSMSG_MASK_PROTECTED", banmask);
3883 return 0;
3884 }
3885
3886 if((action == ACTION_KICK) && (victimCount == 0))
3887 {
3888 reply("CSMSG_NO_MATCHING_USERS", channel->name, banmask);
3889 return 0;
3890 }
3891
3892 name = ban = strdup(banmask);
3893 }
d76ed9a9
AS
3894 else
3895 {
3896 if(!is_ircmask(argv[1]))
3897 {
35caf917
AS
3898 if(cmd)
3899 reply("MSG_NICK_UNKNOWN", argv[1]);
d76ed9a9
AS
3900 return 0;
3901 }
3902
3903 victims = alloca(sizeof(victims[0]) * channel->members.used);
3904
3905 if(bad_channel_ban(channel, user, argv[1], &victimCount, victims))
3906 {
35caf917
AS
3907 if(cmd)
3908 reply("CSMSG_MASK_PROTECTED", argv[1]);
d76ed9a9
AS
3909 return 0;
3910 }
d1a65675
AS
3911/* If i want to ban *.nl and theres 5 of them, what is it to the bot?!?
3912// if((victimCount > 4) && ((victimCount * 3) > channel->members.used) && !IsOper(user))
3913 And, ^^^^^^^^^ BAH!
3914 We use x2 style over-mask detection instead because it doesnt stop channel owners from doing
3915 reasonable bans, but does stop *@*, *@*a* *@*b* etc type masks. Yes, you can defeat it with
3916 some creativity, but its not x3's job to be the ban censor anyway. */
3917 if(is_overmask(argv[1]))
d76ed9a9 3918 {
35caf917
AS
3919 if(cmd)
3920 reply("CSMSG_LAME_MASK", argv[1]);
d76ed9a9
AS
3921 return 0;
3922 }
3923
3924 if((action == ACTION_KICK) && (victimCount == 0))
3925 {
35caf917
AS
3926 if(cmd)
3927 reply("CSMSG_NO_MATCHING_USERS", channel->name, argv[1]);
d76ed9a9
AS
3928 return 0;
3929 }
3930
3931 name = ban = strdup(argv[1]);
3932 }
3933
3934 /* Truncate the ban in place if necessary; we must ensure
3935 that 'ban' is a valid ban mask before sanitizing it. */
3936 sanitize_ircmask(ban);
3937
c8273589 3938 if(action & ACTION_ADD_LAMER)
d76ed9a9
AS
3939 {
3940 struct banData *bData, *next;
3941
c8273589 3942 if(channel->channel_info->banCount >= chanserv_conf.max_chan_bans) /* ..lamers.. */
d76ed9a9 3943 {
35caf917
AS
3944 if(cmd)
3945 reply("CSMSG_MAXIMUM_LAMERS", chanserv_conf.max_chan_bans); /* ..lamers.. */
d76ed9a9
AS
3946 free(ban);
3947 return 0;
3948 }
3949
c8273589 3950 if(action & ACTION_ADD_TIMED_LAMER)
d76ed9a9
AS
3951 {
3952 duration = ParseInterval(argv[2]);
3953
3954 if(duration < 15)
3955 {
35caf917
AS
3956 if(cmd)
3957 reply("CSMSG_DURATION_TOO_LOW");
d76ed9a9
AS
3958 free(ban);
3959 return 0;
3960 }
3961 else if(duration > (86400 * 365 * 2))
3962 {
35caf917
AS
3963 if(cmd)
3964 reply("CSMSG_DURATION_TOO_HIGH");
d76ed9a9
AS
3965 free(ban);
3966 return 0;
3967 }
3968 }
3969
c8273589 3970 /* lamers... */
d76ed9a9
AS
3971 for(bData = channel->channel_info->bans; bData; bData = next)
3972 {
3973 if(match_ircglobs(bData->mask, ban))
3974 {
3975 int exact = !irccasecmp(bData->mask, ban);
3976
3977 /* The ban is redundant; there is already a ban
3978 with the same effect in place. */
3979 if(exact)
3980 {
3981 if(bData->reason)
3982 free(bData->reason);
3983 bData->reason = strdup(reason);
3984 safestrncpy(bData->owner, (user->handle_info ? user->handle_info->handle : user->nick), sizeof(bData->owner));
3985 if(cmd)
3986 reply("CSMSG_REASON_CHANGE", ban);
3987 if(!bData->expires)
3988 goto post_add_ban;
3989 }
3990 if(exact && bData->expires)
3991 {
3992 int reset = 0;
3993
3994 /* If the ban matches an existing one exactly,
3995 extend the expiration time if the provided
3996 duration is longer. */
3997 if(duration && ((time_t)(now + duration) > bData->expires))
3998 {
3999 bData->expires = now + duration;
4000 reset = 1;
4001 }
4002 else if(!duration)
4003 {
4004 bData->expires = 0;
4005 reset = 1;
4006 }
4007
4008 if(reset)
4009 {
4010 /* Delete the expiration timeq entry and
4011 requeue if necessary. */
4012 timeq_del(0, expire_ban, bData, TIMEQ_IGNORE_WHEN);
4013
4014 if(bData->expires)
4015 timeq_add(bData->expires, expire_ban, bData);
4016
4017 if(!cmd)
4018 {
35caf917 4019 /* automated kickban, dont reply */
d76ed9a9
AS
4020 }
4021 else if(duration)
c8273589 4022 reply("CSMSG_LAMER_EXTENDED", ban, intervalString(interval, duration, user->handle_info));
d76ed9a9 4023 else
c8273589 4024 reply("CSMSG_LAMER_ADDED", name, channel->name);
d76ed9a9
AS
4025
4026 goto post_add_ban;
4027 }
4028 }
4029 if(cmd)
c8273589 4030 reply("CSMSG_REDUNDANT_LAMER", name, channel->name);
d76ed9a9
AS
4031
4032 free(ban);
4033 return 0;
4034 }
4035
4036 next = bData->next;
4037 if(match_ircglobs(ban, bData->mask))
4038 {
4039 /* The ban we are adding makes previously existing
4040 bans redundant; silently remove them. */
4041 del_channel_ban(bData);
4042 }
4043 }
4044
4045 bData = add_channel_ban(channel->channel_info, ban, (user->handle_info ? user->handle_info->handle : user->nick), now, (victimCount ? now : 0), (duration ? now + duration : 0), reason);
4046 free(ban);
4047 name = ban = strdup(bData->mask);
4048 }
4049 else if(ban)
4050 {
19fe6b99 4051 /* WHAT DOES THIS DO?? -Rubin */
d76ed9a9
AS
4052 for(n = 0; n < chanserv_conf.old_ban_names->used; ++n)
4053 {
4054 extern const char *hidden_host_suffix;
4055 const char *old_name = chanserv_conf.old_ban_names->list[n];
4056 char *new_mask;
4057 unsigned int l1, l2;
4058
4059 l1 = strlen(ban);
4060 l2 = strlen(old_name);
4061 if(l2+2 > l1)
4062 continue;
4063 if(irccasecmp(ban + l1 - l2, old_name))
4064 continue;
4065 new_mask = malloc(MAXLEN);
a32da4c7 4066 sprintf(new_mask, "%.*s%s", (int)(l1-l2), ban, hidden_host_suffix);
d76ed9a9
AS
4067 free(ban);
4068 name = ban = new_mask;
4069 }
4070 }
4071
4072 post_add_ban:
4073 if(action & ACTION_BAN)
4074 {
4075 unsigned int exists;
4076 struct mod_chanmode *change;
4077
4078 if(channel->banlist.used >= MAXBANS)
4079 {
4080 if(cmd)
4081 reply("CSMSG_BANLIST_FULL", channel->name);
4082 free(ban);
4083 return 0;
4084 }
4085
4086 exists = ChannelBanExists(channel, ban);
4087 change = mod_chanmode_alloc(victimCount + 1);
4088 for(n = 0; n < victimCount; ++n)
4089 {
55342ce8 4090 change->args[n].mode = MODE_REMOVE|MODE_CHANOP|MODE_HALFOP|MODE_VOICE;
a32da4c7 4091 change->args[n].u.member = victims[n];
d76ed9a9
AS
4092 }
4093 if(!exists)
4094 {
4095 change->args[n].mode = MODE_BAN;
a32da4c7 4096 change->args[n++].u.hostmask = ban;
d76ed9a9
AS
4097 }
4098 change->argc = n;
4099 if(cmd)
4100 modcmd_chanmode_announce(change);
4101 else
4102 mod_chanmode_announce(chanserv, channel, change);
4103 mod_chanmode_free(change);
4104
4105 if(exists && (action == ACTION_BAN))
4106 {
4107 if(cmd)
4108 reply("CSMSG_REDUNDANT_BAN", name, channel->name);
4109 free(ban);
4110 return 0;
4111 }
4112 }
4113
ff5f1ab2
AS
4114 if(action & ACTION_ADD_LAMER)
4115 {
4116 char kick_reason[MAXLEN];
4117 sprintf(kick_reason, "(%s) %s", user->nick, reason);
4118
4119 for(n = 0; n < victimCount; n++) {
4120 if(!protect_user(victims[n]->user, user, channel->channel_info, true)) {
4121 KickChannelUser(victims[n]->user, channel, chanserv, kick_reason);
4122 }
4123 }
4124 }
4125 else if(action & ACTION_KICK)
d76ed9a9
AS
4126 {
4127 char kick_reason[MAXLEN];
4128 sprintf(kick_reason, "(%s) %s", user->nick, reason);
4129
ff5f1ab2 4130 for(n = 0; n < victimCount; n++) {
d76ed9a9 4131 KickChannelUser(victims[n]->user, channel, chanserv, kick_reason);
ff5f1ab2 4132 }
d76ed9a9
AS
4133 }
4134
4135 if(!cmd)
4136 {
4137 /* No response, since it was automated. */
4138 }
c8273589 4139 else if(action & ACTION_ADD_LAMER)
d76ed9a9
AS
4140 {
4141 if(duration)
c8273589 4142 reply("CSMSG_TIMED_LAMER_ADDED", name, channel->name, intervalString(interval, duration, user->handle_info));
d76ed9a9 4143 else
c8273589 4144 reply("CSMSG_LAMER_ADDED", name, channel->name);
d76ed9a9
AS
4145 }
4146 else if((action & (ACTION_BAN | ACTION_KICK)) == (ACTION_BAN | ACTION_KICK))
4147 reply("CSMSG_KICK_BAN_DONE", name, channel->name);
4148 else if(action & ACTION_BAN)
4149 reply("CSMSG_BAN_DONE", name, channel->name);
4150 else if(action & ACTION_KICK && victimCount)
4151 reply("CSMSG_KICK_DONE", name, channel->name);
4152
4153 free(ban);
4154 return 1;
4155}
4156
4157static CHANSERV_FUNC(cmd_kickban)
4158{
4159 return eject_user(CSFUNC_ARGS, ACTION_KICK | ACTION_BAN);
4160}
4161
4162static CHANSERV_FUNC(cmd_kick)
4163{
4164 return eject_user(CSFUNC_ARGS, ACTION_KICK);
4165}
4166
4167static CHANSERV_FUNC(cmd_ban)
4168{
4169 return eject_user(CSFUNC_ARGS, ACTION_BAN);
4170}
4171
c8273589 4172static CHANSERV_FUNC(cmd_addlamer)
d76ed9a9 4173{
c8273589 4174 return eject_user(CSFUNC_ARGS, ACTION_KICK | ACTION_BAN | ACTION_ADD_LAMER);
d76ed9a9
AS
4175}
4176
c8273589 4177static CHANSERV_FUNC(cmd_addtimedlamer)
d76ed9a9 4178{
c8273589 4179 return eject_user(CSFUNC_ARGS, ACTION_KICK | ACTION_BAN | ACTION_ADD_LAMER | ACTION_ADD_TIMED_LAMER);
d76ed9a9
AS
4180}
4181
4182static struct mod_chanmode *
4183find_matching_bans(struct banList *bans, struct userNode *actee, const char *mask)
4184{
4185 struct mod_chanmode *change;
4186 unsigned char *match;
4187 unsigned int ii, count;
4188
4189 match = alloca(bans->used);
4190 if(actee)
4191 {
4192 for(ii = count = 0; ii < bans->used; ++ii)
4193 {
2f61d1d7 4194 match[ii] = user_matches_glob(actee, bans->list[ii]->ban,
4195 MATCH_USENICK | MATCH_VISIBLE);
d76ed9a9
AS
4196 if(match[ii])
4197 count++;
4198 }
4199 }
4200 else
4201 {
4202 for(ii = count = 0; ii < bans->used; ++ii)
4203 {
4204 match[ii] = match_ircglobs(mask, bans->list[ii]->ban);
4205 if(match[ii])
4206 count++;
4207 }
4208 }
4209 if(!count)
4210 return NULL;
4211 change = mod_chanmode_alloc(count);
4212 for(ii = count = 0; ii < bans->used; ++ii)
4213 {
4214 if(!match[ii])
4215 continue;
4216 change->args[count].mode = MODE_REMOVE | MODE_BAN;
ec1a68c8 4217 change->args[count++].u.hostmask = strdup(bans->list[ii]->ban);
d76ed9a9 4218 }
ec1a68c8 4219 assert(count == change->argc);
d76ed9a9
AS
4220 return change;
4221}
4222
7153d2c7
AS
4223void expire_bans(UNUSED_ARG(void* data)) /* Real bans, not lamers */
4224{
4225 unsigned int jj, ii, count;
4226 struct banNode *bn;
4227 struct chanData *channel;
4228 time_t bantimeout;
4229 struct mod_chanmode *change;
4230
4231 log_module(CS_LOG, LOG_DEBUG, "Checking for expired bans");
4232 /* Walk through every channel */
4233 for(channel = channelList; channel; channel = channel->next) {
4234 switch(channel->chOpts[chBanTimeout])
4235 {
4236 default: case '0': continue; /* Dont remove bans in this chan */
4237 case '1': bantimeout = now - (10 * 60); break; /* 10 minutes */
4238 case '2': bantimeout = now - (2 * 60 * 60); break; /* 2 hours */
4239 case '3': bantimeout = now - (4 * 60 * 60); break; /* 4 hours */
4240 case '4': bantimeout = now - (24 * 60 * 60); break; /* 24 hours */
4241 case '5': bantimeout = now - (7 * 24 * 60 * 60); break; /* 1 week */
4242 }
4243 count = 0;
4244 /* First find out how many bans were going to unset */
4245 for (jj=0; jj < channel->channel->banlist.used; ++jj) {
4246 if(channel->channel->banlist.list[jj]->set < bantimeout)
4247 count++;
4248 }
4249 if(count > 0) {
4250 /* At least one ban, so setup a removal */
4251 change = mod_chanmode_alloc(count);
4252 ii = 0;
4253 /* Walk over every ban in this channel.. */
4254 for (jj=0; jj < channel->channel->banlist.used; ++jj) {
4255 bn = channel->channel->banlist.list[jj];
4256 if (bn->set < bantimeout) {
4257 log_module(CS_LOG, LOG_DEBUG, "Removing ban %s from %s", bn->ban, channel->channel->name);
4258
4259 /* Add this ban to the mode change */
4260 change->args[ii].mode = MODE_REMOVE | MODE_BAN;
4261 change->args[ii].u.hostmask = strdup(bn->ban);
4262 ii++;
4263 /* Pull this ban out of the list */
4264 banList_remove(&(channel->channel->banlist), bn);
4265 jj--;
4266 free(bn);
4267 }
4268 }
4269 /* Send the modes to IRC */
4270 mod_chanmode_announce(chanserv, channel->channel, change);
4271
4272 /* free memory from strdup above */
4273 for(ii = 0; ii < count; ++ii)
4274 free((char*)change->args[ii].u.hostmask);
4275
4276 mod_chanmode_free(change);
4277 }
4278 }
4279 /* Set this function to run again */
4280 if(chanserv_conf.ban_timeout_frequency)
4281 timeq_add(now + chanserv_conf.ban_timeout_frequency, expire_bans, NULL);
4282}
4283
4284
d76ed9a9
AS
4285static int
4286unban_user(struct userNode *user, struct chanNode *channel, unsigned int argc, char *argv[], struct svccmd *cmd, int action)
4287{
4288 struct userNode *actee;
4289 char *mask = NULL;
4290 int acted = 0;
4291
4292 REQUIRE_PARAMS(2);
4293
4294 /* may want to allow a comma delimited list of users... */
4295 if(!(actee = GetUserH(argv[1])))
4296 {
1136f709 4297 if(!is_ircmask(argv[1]) && *argv[1] == '*')
4298 {
4299 char banmask[NICKLEN + USERLEN + HOSTLEN + 3];
4300 const char *accountname = argv[1] + 1;
4301
4302 snprintf(banmask, sizeof(banmask), "*!*@%s.*", accountname);
4303 mask = strdup(banmask);
4304 }
4305 else if(!is_ircmask(argv[1]))
0f6fe38c 4306 {
4307 reply("MSG_NICK_UNKNOWN", argv[1]);
4308 return 0;
ff3b058a 4309 }
1136f709 4310 else
4311 {
4312 mask = strdup(argv[1]);
4313 }
d76ed9a9
AS
4314 }
4315
4316 /* We don't sanitize the mask here because ircu
4317 doesn't do it. */
4318 if(action & ACTION_UNBAN)
4319 {
4320 struct mod_chanmode *change;
4321 change = find_matching_bans(&channel->banlist, actee, mask);
4322 if(change)
4323 {
ec1a68c8 4324 unsigned int ii;
4325
aa222db2 4326 modcmd_chanmode_announce(change);
ec1a68c8 4327 for(ii = 0; ii < change->argc; ++ii)
4328 free((char*)change->args[ii].u.hostmask);
d76ed9a9
AS
4329 mod_chanmode_free(change);
4330 acted = 1;
4331 }
4332 }
4333
c8273589 4334 if(action & ACTION_DEL_LAMER)
d76ed9a9
AS
4335 {
4336 struct banData *ban, *next;
4337
c8273589 4338 ban = channel->channel_info->bans; /* lamers */
d76ed9a9
AS
4339 while(ban)
4340 {
4341 if(actee)
2f61d1d7 4342 for( ; ban && !user_matches_glob(actee, ban->mask, MATCH_USENICK | MATCH_VISIBLE);
d76ed9a9
AS
4343 ban = ban->next);
4344 else
4345 for( ; ban && !match_ircglobs(mask, ban->mask);
4346 ban = ban->next);
4347 if(!ban)
4348 break;
4349 next = ban->next;
4350 del_channel_ban(ban);
4351 ban = next;
4352 acted = 1;
4353 }
4354 }
4355
4356 if(!acted)
4357 reply("CSMSG_BAN_NOT_FOUND", actee ? actee->nick : mask);
4358 else
4359 reply("CSMSG_BAN_REMOVED", actee ? actee->nick : mask);
4360 if(mask)
4361 free(mask);
4362 return 1;
4363}
4364
4365static CHANSERV_FUNC(cmd_unban)
4366{
4367 return unban_user(CSFUNC_ARGS, ACTION_UNBAN);
4368}
4369
c8273589 4370static CHANSERV_FUNC(cmd_dellamer)
d76ed9a9
AS
4371{
4372 /* it doesn't necessarily have to remove the channel ban - may want
4373 to make that an option. */
c8273589 4374 return unban_user(CSFUNC_ARGS, ACTION_UNBAN | ACTION_DEL_LAMER);
d76ed9a9
AS
4375}
4376
4377static CHANSERV_FUNC(cmd_unbanme)
4378{
4379 struct userData *uData = GetChannelUser(channel->channel_info, user->handle_info);
4380 long flags = ACTION_UNBAN;
4381
4382 /* remove permanent bans if the user has the proper access. */
4048352e 4383 if(uData->access >= UL_MANAGER)
c8273589 4384 flags |= ACTION_DEL_LAMER;
d76ed9a9
AS
4385
4386 argv[1] = user->nick;
4387 return unban_user(user, channel, 2, argv, cmd, flags);
4388}
4389
4390static CHANSERV_FUNC(cmd_unbanall)
4391{
4392 struct mod_chanmode *change;
4393 unsigned int ii;
4394
4395 if(!channel->banlist.used)
4396 {
4397 reply("CSMSG_NO_BANS", channel->name);
4398 return 0;
4399 }
4400
4401 change = mod_chanmode_alloc(channel->banlist.used);
4402 for(ii=0; ii<channel->banlist.used; ii++)
4403 {
4404 change->args[ii].mode = MODE_REMOVE | MODE_BAN;
ec1a68c8 4405 change->args[ii].u.hostmask = strdup(channel->banlist.list[ii]->ban);
d76ed9a9
AS
4406 }
4407 modcmd_chanmode_announce(change);
ec1a68c8 4408 for(ii = 0; ii < change->argc; ++ii)
4409 free((char*)change->args[ii].u.hostmask);
d76ed9a9
AS
4410 mod_chanmode_free(change);
4411 reply("CSMSG_BANS_REMOVED", channel->name);
4412 return 1;
4413}
4414
4415static CHANSERV_FUNC(cmd_open)
4416{
4417 struct mod_chanmode *change;
ec1a68c8 4418 unsigned int ii;
d76ed9a9
AS
4419
4420 change = find_matching_bans(&channel->banlist, user, NULL);
4421 if(!change)
4422 change = mod_chanmode_alloc(0);
4423 change->modes_clear |= MODE_INVITEONLY | MODE_LIMIT | MODE_KEY;
4424 if(!check_user_level(channel, user, lvlEnfModes, 1, 0)
4425 && channel->channel_info->modes.modes_set)
4426 change->modes_clear &= ~channel->channel_info->modes.modes_set;
4427 modcmd_chanmode_announce(change);
4428 reply("CSMSG_CHANNEL_OPENED", channel->name);
ec1a68c8 4429 for(ii = 0; ii < change->argc; ++ii)
4430 free((char*)change->args[ii].u.hostmask);
d76ed9a9
AS
4431 mod_chanmode_free(change);
4432 return 1;
4433}
4434
4435static CHANSERV_FUNC(cmd_myaccess)
4436{
4437 static struct string_buffer sbuf;
4438 struct handle_info *target_handle;
4439 struct userData *uData;
4440
4441 if(argc < 2)
4442 target_handle = user->handle_info;
1136f709 4443 else if(!IsStaff(user))
d76ed9a9
AS
4444 {
4445 reply("CSMSG_MYACCESS_SELF_ONLY", argv[0]);
4446 return 0;
4447 }
1136f709 4448 else if(!(target_handle = modcmd_get_handle_info(user, argv[1])))
4449 return 0;
4450
4451 if(!oper_outranks(user, target_handle))
4452 return 0;
4453
d76ed9a9
AS
4454 if(!target_handle->channels)
4455 {
4456 reply("CSMSG_SQUAT_ACCESS", target_handle->handle);
4457 return 1;
4458 }
4459
4460 reply("CSMSG_INFOLINE_LIST", target_handle->handle);
4461 for(uData = target_handle->channels; uData; uData = uData->u_next)
4462 {
4463 struct chanData *cData = uData->channel;
4464
4465 if(uData->access > UL_OWNER)
4466 continue;
4467 if(IsProtected(cData)
4468 && (target_handle != user->handle_info)
4469 && !GetTrueChannelAccess(cData, user->handle_info))
4470 continue;
4471 sbuf.used = 0;
4472 string_buffer_append_printf(&sbuf, "[%s (%d", cData->channel->name, uData->access);
c8ca69a0 4473 if(uData->flags == USER_AUTO_OP)
d76ed9a9
AS
4474 string_buffer_append(&sbuf, ',');
4475 if(IsUserSuspended(uData))
4476 string_buffer_append(&sbuf, 's');
4477 if(IsUserAutoOp(uData))
4478 {
4b6129c0 4479 if(uData->access >= UL_OP )
d76ed9a9 4480 string_buffer_append(&sbuf, 'o');
4b6129c0 4481 else if(uData->access >= UL_HALFOP )
55342ce8 4482 string_buffer_append(&sbuf, 'h');
4b6129c0 4483 else if(uData->access >= UL_PEON )
d76ed9a9
AS
4484 string_buffer_append(&sbuf, 'v');
4485 }
4486 if(IsUserAutoInvite(uData) && (uData->access >= cData->lvlOpts[lvlInviteMe]))
4487 string_buffer_append(&sbuf, 'i');
cd25f2e9 4488 if(IsUserAutoJoin(uData) && (uData->access >= cData->lvlOpts[lvlInviteMe]))
4489 string_buffer_append(&sbuf, 'j');
d76ed9a9
AS
4490 if(uData->info)
4491 string_buffer_append_printf(&sbuf, ")] %s", uData->info);
4492 else
4493 string_buffer_append_string(&sbuf, ")]");
4494 string_buffer_append(&sbuf, '\0');
a32da4c7 4495 send_message_type(4, user, cmd->parent->bot, "%s", sbuf.list);
d76ed9a9
AS
4496 }
4497
4498 return 1;
4499}
4500
4501static CHANSERV_FUNC(cmd_access)
4502{
4503 struct userNode *target;
4504 struct handle_info *target_handle;
4505 struct userData *uData;
4506 int helping;
4507 char prefix[MAXLEN];
4508
4509 if(argc < 2)
4510 {
4511 target = user;
4512 target_handle = target->handle_info;
4513 }
4514 else if((target = GetUserH(argv[1])))
4515 {
4516 target_handle = target->handle_info;
4517 }
4518 else if(argv[1][0] == '*')
4519 {
4520 if(!(target_handle = get_handle_info(argv[1]+1)))
4521 {
4522 reply("MSG_HANDLE_UNKNOWN", argv[1]+1);
4523 return 0;
4524 }
4525 }
4526 else
4527 {
4528 reply("MSG_NICK_UNKNOWN", argv[1]);
4529 return 0;
4530 }
4531
4532 assert(target || target_handle);
4533
4534 if(target == chanserv)
4535 {
4536 reply("CSMSG_IS_CHANSERV");
4537 return 1;
4538 }
4539
4540 if(!target_handle)
4541 {
4542 if(IsOper(target))
4543 {
4544 reply("CSMSG_LAZY_SMURF_TARGET", target->nick, chanserv_conf.irc_operator_epithet);
4545 return 0;
4546 }
4547 if(target != user)
4548 {
4549 reply("MSG_USER_AUTHENTICATE", target->nick);
4550 return 0;
4551 }
4552 reply("MSG_AUTHENTICATE");
4553 return 0;
4554 }
4555
4556 if(target)
4557 {
4558 const char *epithet = NULL, *type = NULL;
4559 if(IsOper(target))
4560 {
4561 epithet = chanserv_conf.irc_operator_epithet;
1136f709 4562 type = user_find_message(user, "CSMSG_OPERATOR_TITLE");
d76ed9a9
AS
4563 }
4564 else if(IsNetworkHelper(target))
4565 {
4566 epithet = chanserv_conf.network_helper_epithet;
1136f709 4567 type = user_find_message(user, "CSMSG_UC_H_TITLE");
d76ed9a9
AS
4568 }
4569 else if(IsSupportHelper(target))
4570 {
4571 epithet = chanserv_conf.support_helper_epithet;
1136f709 4572 type = user_find_message(user, "CSMSG_LC_H_TITLE");
d76ed9a9
AS
4573 }
4574 if(epithet)
4575 {
4576 if(target_handle->epithet)
4577 reply("CSMSG_SMURF_TARGET", target->nick, target_handle->epithet, type);
4578 else if(epithet)
4579 reply("CSMSG_SMURF_TARGET", target->nick, epithet, type);
4580 }
4581 sprintf(prefix, "%s (%s)", target->nick, target_handle->handle);
4582 }
4583 else
4584 {
4585 sprintf(prefix, "%s", target_handle->handle);
4586 }
4587
4588 if(!channel->channel_info)
4589 {
4590 reply("CSMSG_NOT_REGISTERED", channel->name);
4591 return 1;
4592 }
4593
4594 helping = HANDLE_FLAGGED(target_handle, HELPING)
4595 && ((target_handle->opserv_level >= chanserv_conf.nodelete_level) || !IsProtected(channel->channel_info));
4596 if((uData = GetTrueChannelAccess(channel->channel_info, target_handle)))
4597 {
de9510bc 4598 reply((helping ? "CSMSG_HELPER_HAS_ACCESS" : "CSMSG_USER_HAS_ACCESS"), prefix, user_level_name_from_level(uData->access), uData->access, channel->name);
d76ed9a9
AS
4599 /* To prevent possible information leaks, only show infolines
4600 * if the requestor is in the channel or it's their own
4601 * handle. */
4602 if(uData->info && (GetUserMode(channel, user) || (target_handle == user->handle_info)))
4603 {
4604 send_message_type(4, user, cmd->parent->bot, "[%s] %s", (target ? target->nick : target_handle->handle), uData->info);
4605 }
4606 /* Likewise, only say it's suspended if the user has active
4607 * access in that channel or it's their own entry. */
4608 if(IsUserSuspended(uData)
4609 && (GetChannelUser(channel->channel_info, user->handle_info)
4610 || (user->handle_info == uData->handle)))
4611 {
4612 reply("CSMSG_USER_SUSPENDED", (target ? target->nick : target_handle->handle), channel->name);
4613 }
4614 }
4615 else
4616 {
4617 reply((helping ? "CSMSG_HELPER_NO_ACCESS" : "CSMSG_USER_NO_ACCESS"), prefix, channel->name);
4618 }
4619
4620 return 1;
4621}
4622
23475fc6 4623/* This is never used...
d76ed9a9
AS
4624static void
4625zoot_list(struct listData *list)
4626{
4627 struct userData *uData;
4628 unsigned int start, curr, highest, lowest;
4629 struct helpfile_table tmp_table;
4630 const char **temp, *msg;
4631
4632 if(list->table.length == 1)
4633 {
4634 if(list->search)
a915f7d4 4635 send_message(list->user, list->bot, "CSMSG_ACCESS_SEARCH_HEADER", list->channel->name, user_level_name_from_level(list->lowest), user_level_name_from_level(list->highest), list->search);
d76ed9a9 4636 else
a915f7d4 4637 send_message(list->user, list->bot, "CSMSG_ACCESS_ALL_HEADER", list->channel->name, user_level_name_from_level(list->lowest), user_level_name_from_level(list->highest));
d76ed9a9
AS
4638 msg = user_find_message(list->user, "MSG_NONE");
4639 send_message_type(4, list->user, list->bot, " %s", msg);
4640 }
4641 tmp_table.width = list->table.width;
4642 tmp_table.flags = list->table.flags;
4643 list->table.contents[0][0] = " ";
4644 highest = list->highest;
4645 if(list->lowest != 0)
4646 lowest = list->lowest;
4647 else if(highest < 100)
4648 lowest = 1;
4649 else
4650 lowest = highest - 100;
4651 for(start = curr = 1; curr < list->table.length; )
4652 {
4653 uData = list->users[curr-1];
4654 list->table.contents[curr++][0] = " ";
4655 if((curr == list->table.length) || (list->users[curr-1]->access < lowest))
4656 {
4657 if(list->search)
a915f7d4 4658 send_message(list->user, list->bot, "CSMSG_ACCESS_SEARCH_HEADER", list->channel->name, user_level_name_from_level(lowest), user_level_name_from_level(highest), list->search);
d76ed9a9 4659 else
a915f7d4 4660 send_message(list->user, list->bot, "CSMSG_ACCESS_ALL_HEADER", list->channel->name, user_level_name_from_level(lowest), user_level_name_from_level(highest));
d76ed9a9
AS
4661 temp = list->table.contents[--start];
4662 list->table.contents[start] = list->table.contents[0];
4663 tmp_table.contents = list->table.contents + start;
4664 tmp_table.length = curr - start;
4665 table_send(list->bot, list->user->nick, 0, NULL, tmp_table);
4666 list->table.contents[start] = temp;
4667 start = curr;
4668 highest = lowest - 1;
4669 lowest = (highest < 100) ? 0 : (highest - 99);
4670 }
4671 }
4672}
23475fc6 4673*/
d76ed9a9
AS
4674
4675static void
338a82b5 4676normal_list(struct listData *list)
d76ed9a9
AS
4677{
4678 const char *msg;
4679 if(list->search)
338a82b5 4680 send_message(list->user, list->bot, "CSMSG_ACCESS_SEARCH_HEADER_NORMAL", list->channel->name, user_level_name_from_level(list->lowest), user_level_name_from_level(list->highest), list->search);
d76ed9a9 4681 else
338a82b5 4682 send_message(list->user, list->bot, "CSMSG_ACCESS_ALL_HEADER_NORMAL", list->channel->name, user_level_name_from_level(list->lowest), user_level_name_from_level(list->highest));
d76ed9a9
AS
4683 if(list->table.length == 1)
4684 {
4685 msg = user_find_message(list->user, "MSG_NONE");
4686 send_message_type(4, list->user, list->bot, " %s", msg);
4687 }
8e11460f
AS
4688 else
4689 table_send(list->bot, list->user->nick, 0, NULL, list->table);
d76ed9a9
AS
4690}
4691
338a82b5
AS
4692/* if these need changed, uncomment and customize
4693static void
4694clean_list(struct listData *list)
4695{
4696 const char *msg;
4697 if(list->search)
4698 send_message(list->user, list->bot, "CSMSG_ACCESS_SEARCH_HEADER_CLEAN", list->channel->name, user_level_name_from_level(list->lowest), user_level_name_from_level(list->highest), list->search);
4699 else
4700 send_message(list->user, list->bot, "CSMSG_ACCESS_ALL_HEADER_CLEAN", list->channel->name, user_level_name_from_level(list->lowest), user_level_name_from_level(list->highest));
4701 if(list->table.length == 1)
4702 {
4703 msg = user_find_message(list->user, "MSG_NONE");
4704 send_message_type(4, list->user, list->bot, " %s", msg);
4705 }
4706 else
4707 table_send(list->bot, list->user->nick, 0, NULL, list->table);
4708}
4709
4710static void
4711advanced_list(struct listData *list)
4712{
4713 const char *msg;
4714 if(list->search)
4715 send_message(list->user, list->bot, "CSMSG_ACCESS_SEARCH_HEADER_ADVANCED", list->channel->name, user_level_name_from_level(list->lowest), user_level_name_from_level(list->highest), list->search);
4716 else
4717 send_message(list->user, list->bot, "CSMSG_ACCESS_ALL_HEADER_ADVANCED", list->channel->name, user_level_name_from_level(list->lowest), user_level_name_from_level(list->highest));
4718 if(list->table.length == 1)
4719 {
4720 msg = user_find_message(list->user, "MSG_NONE");
4721 send_message_type(4, list->user, list->bot, " %s", msg);
4722 }
4723 else
4724 table_send(list->bot, list->user->nick, 0, NULL, list->table);
4725}
4726
d9896a83
AS
4727static void
4728classic_list(struct listData *list)
4729{
4730 const char *msg;
4731 if(list->search)
4732 send_message(list->user, list->bot, "CSMSG_ACCESS_SEARCH_HEADER_CLASSIC", list->channel->name, list->lowest, list->highest, list->search);
4733 else
4734 send_message(list->user, list->bot, "CSMSG_ACCESS_ALL_HEADER_CLASSIC", list->channel->name, list->lowest, list->highest);
4735 if(list->table.length == 1)
4736 {
4737 msg = user_find_message(list->user, "MSG_NONE");
4738 send_message_type(4, list->user, list->bot, " %s", msg);
4739 }
4740 else
4741 table_send(list->bot, list->user->nick, 0, NULL, list->table);
4742}
338a82b5
AS
4743*/
4744
d76ed9a9
AS
4745static int
4746userData_access_comp(const void *arg_a, const void *arg_b)
4747{
4748 const struct userData *a = *(struct userData**)arg_a;
4749 const struct userData *b = *(struct userData**)arg_b;
4750 int res;
4751 if(a->access != b->access)
4752 res = b->access - a->access;
4753 else
4754 res = irccasecmp(a->handle->handle, b->handle->handle);
4755 return res;
4756}
4757
4758static int
4759cmd_list_users(struct userNode *user, struct chanNode *channel, unsigned int argc, char *argv[], struct svccmd *cmd, unsigned short lowest, unsigned short highest)
4760{
4761 void (*send_list)(struct listData *);
4762 struct userData *uData;
4763 struct listData lData;
4764 unsigned int matches;
4765 const char **ary;
338a82b5
AS
4766 int i = 0;
4767 int seen_index;
d76ed9a9
AS
4768
4769 lData.user = user;
4770 lData.bot = cmd->parent->bot;
4771 lData.channel = channel;
4772 lData.lowest = lowest;
4773 lData.highest = highest;
4774 lData.search = (argc > 1) ? argv[1] : NULL;
338a82b5 4775 send_list = normal_list;
e6892204 4776 /* What does the following line do exactly?? */
8e11460f 4777 /*(void)zoot_list; ** since it doesn't show user levels */
d76ed9a9 4778
338a82b5 4779 /*
d76ed9a9
AS
4780 if(user->handle_info)
4781 {
338a82b5
AS
4782 switch(user->handle_info->userlist_style)
4783 {
4784 case HI_STYLE_CLEAN:
4785 send_list = clean_list;
4786 break;
4787 case HI_STYLE_ADVANCED:
4788 send_list = advanced_list;
4789 break;
d9896a83
AS
4790 case HI_STYLE_CLASSIC:
4791 send_list = classic_list;
4792 break;
338a82b5
AS
4793 case HI_STYLE_NORMAL:
4794 default:
4795 send_list = normal_list;
4796 break;
4797 }
d76ed9a9 4798 }
e6892204 4799 */
338a82b5 4800 send_list = normal_list;
d76ed9a9
AS
4801
4802 lData.users = alloca(channel->channel_info->userCount * sizeof(struct userData *));
4803 matches = 0;
4804 for(uData = channel->channel_info->users; uData; uData = uData->next)
4805 {
4806 if((uData->access < lowest)
4807 || (uData->access > highest)
4808 || (lData.search && !match_ircglob(uData->handle->handle, lData.search)))
4809 continue;
4810 lData.users[matches++] = uData;
4811 }
4812 qsort(lData.users, matches, sizeof(lData.users[0]), userData_access_comp);
4813
4814 lData.table.length = matches+1;
d76ed9a9
AS
4815 lData.table.flags = TABLE_NO_FREE;
4816 lData.table.contents = malloc(lData.table.length*sizeof(*lData.table.contents));
338a82b5 4817
3498a411 4818 if(user->handle_info && user->handle_info->userlist_style == HI_STYLE_ADVANCED)
fd20b142 4819 lData.table.width = 6; /* with level = 6 */
338a82b5 4820 else
fd20b142 4821 lData.table.width = 5; /* without = 5 */
d76ed9a9
AS
4822 ary = malloc(lData.table.width*sizeof(**lData.table.contents));
4823 lData.table.contents[0] = ary;
d9896a83
AS
4824 if(user->handle_info) {
4825 switch(user->handle_info->userlist_style) {
4826 case HI_STYLE_CLASSIC:
4827 ary[i++] = "Level";
4828 break;
4829 case HI_STYLE_ADVANCED:
4830 ary[i++] = "Access";
4831 ary[i++] = "Level";
4832 break;
4833 case HI_STYLE_CLEAN:
4834 ary[i++] = "Access";
4835 break;
4836 case HI_STYLE_NORMAL:
4837 default:
4838 ary[i++] = "Access";
4839 break;
4840 }
4841 }
4842 else {
4843 ary[i++] = "Access";
4844 }
338a82b5
AS
4845 ary[i++] = "Account";
4846 ary[i] = "Last Seen";
4847 seen_index = i++;
4848 ary[i++] = "Status";
fd20b142 4849 ary[i++] = "Expiry";
d76ed9a9
AS
4850 for(matches = 1; matches < lData.table.length; ++matches)
4851 {
d76ed9a9
AS
4852 char seen[INTERVALLEN];
4853
1136f709 4854
4855 uData = lData.users[matches-1];
338a82b5 4856 i = 0;
d76ed9a9
AS
4857 ary = malloc(lData.table.width*sizeof(**lData.table.contents));
4858 lData.table.contents[matches] = ary;
d9896a83
AS
4859 if(user->handle_info) {
4860 switch(user->handle_info->userlist_style) {
4861 case HI_STYLE_CLASSIC:
4862 ary[i++] = strtab(uData->access);
4863 break;
4864 case HI_STYLE_ADVANCED:
4865 ary[i++] = user_level_name_from_level(uData->access);
4866 ary[i++] = strtab(uData->access);
4867 break;
4868 case HI_STYLE_CLEAN:
4869 ary[i++] = user_level_name_from_level(uData->access);
4870 break;
4871 case HI_STYLE_NORMAL:
4872 default:
4873 ary[i++] = user_level_name_from_level(uData->access);
4874 break;
4875 }
4876 }
4877 else {
4878 ary[i++] = user_level_name_from_level(uData->access);
4879 }
338a82b5 4880 ary[i++] = uData->handle->handle;
d76ed9a9 4881 if(uData->present)
338a82b5 4882 ary[i] = "Here";
d76ed9a9 4883 else if(!uData->seen)
338a82b5 4884 ary[i] = "Never";
d76ed9a9 4885 else
338a82b5
AS
4886 ary[i] = intervalString(seen, now - uData->seen, user->handle_info);
4887 ary[i] = strdup(ary[i]);
4888 i++;
d76ed9a9 4889 if(IsUserSuspended(uData))
338a82b5 4890 ary[i++] = "Suspended";
d76ed9a9 4891 else if(HANDLE_FLAGGED(uData->handle, FROZEN))
338a82b5 4892 ary[i++] = "Vacation";
d76ed9a9 4893 else
338a82b5 4894 ary[i++] = "Normal";
fd20b142 4895
dfaa28a4 4896 if ((uData->accessexpiry > 0) || (uData->clvlexpiry > 0)) {
fd20b142 4897 char delay[INTERVALLEN];
4898 time_t diff;
4899
dfaa28a4 4900 if (uData->accessexpiry > 0) {
4901 diff = uData->accessexpiry - now;
4902 intervalString(delay, diff, user->handle_info);
4903 } else {
4904 diff = uData->clvlexpiry - now;
4905 intervalString(delay, diff, user->handle_info);
4906 }
fd20b142 4907 ary[i++] = delay;
4908 } else
4909 ary[i++] = "Never";
d76ed9a9 4910 }
fd20b142 4911
d76ed9a9
AS
4912 send_list(&lData);
4913 for(matches = 1; matches < lData.table.length; ++matches)
4914 {
338a82b5
AS
4915 /* Free strdup above */
4916 free((char*)lData.table.contents[matches][seen_index]);
d76ed9a9
AS
4917 free(lData.table.contents[matches]);
4918 }
4919 free(lData.table.contents[0]);
4920 free(lData.table.contents);
4921 return 1;
4922}
4923
1d957482
AS
4924/* Remove this now that debugging is over? or improve it for
4925 * users? Would it be better tied into USERS somehow? -Rubin */
ac3bdc8d
AS
4926static CHANSERV_FUNC(cmd_pending)
4927{
4928 struct adduserPending *ap;
1d957482 4929 reply("CSMSG_ADDUSER_PENDING_HEADER");
cde20a3f
AS
4930 if(user->handle_info && user->handle_info->userlist_style != HI_STYLE_CLEAN)
4931 reply("CSMSG_BAR");
ac3bdc8d
AS
4932 for(ap = adduser_pendings;ap;ap = ap->next)
4933 reply("CSMSG_ADDUSER_PENDING_LIST", ap->channel->name, ap->user->nick);
1d957482 4934 reply("CSMSG_ADDUSER_PENDING_FOOTER");
ac3bdc8d
AS
4935 return 1;
4936}
4937
d76ed9a9
AS
4938static CHANSERV_FUNC(cmd_users)
4939{
4940 return cmd_list_users(CSFUNC_ARGS, 1, UL_OWNER);
4941}
4942
4943static CHANSERV_FUNC(cmd_wlist)
4944{
4945 return cmd_list_users(CSFUNC_ARGS, UL_OWNER, UL_OWNER);
4946}
4947
4948static CHANSERV_FUNC(cmd_clist)
4949{
4950 return cmd_list_users(CSFUNC_ARGS, UL_COOWNER, UL_OWNER-1);
4951}
4952
4953static CHANSERV_FUNC(cmd_mlist)
4954{
4048352e 4955 return cmd_list_users(CSFUNC_ARGS, UL_MANAGER, UL_COOWNER-1);
d76ed9a9
AS
4956}
4957
4958static CHANSERV_FUNC(cmd_olist)
4959{
4048352e 4960 return cmd_list_users(CSFUNC_ARGS, UL_OP, UL_MANAGER-1);
d76ed9a9
AS
4961}
4962
55342ce8 4963static CHANSERV_FUNC(cmd_hlist)
4964{
4965 return cmd_list_users(CSFUNC_ARGS, UL_HALFOP, UL_OP-1);
4966}
4967
d76ed9a9
AS
4968static CHANSERV_FUNC(cmd_plist)
4969{
55342ce8 4970 return cmd_list_users(CSFUNC_ARGS, 1, UL_HALFOP-1);
d76ed9a9
AS
4971}
4972
c8273589 4973static CHANSERV_FUNC(cmd_lamers)
d76ed9a9 4974{
1136f709 4975 struct userNode *search_u = NULL;
d76ed9a9 4976 struct helpfile_table tbl;
1136f709 4977 unsigned int matches = 0, timed = 0, search_wilds = 0, ii;
d76ed9a9
AS
4978 char t_buffer[INTERVALLEN], e_buffer[INTERVALLEN], *search;
4979 const char *msg_never, *triggered, *expires;
c8273589 4980 struct banData *ban, **bans; /* lamers */
d76ed9a9 4981
1136f709 4982 if(argc < 2)
d76ed9a9 4983 search = NULL;
1136f709 4984 else if(strchr(search = argv[1], '!'))
4985 {
4986 search = argv[1];
4987 search_wilds = search[strcspn(search, "?*")];
4988 }
4989 else if(!(search_u = GetUserH(search)))
4990 reply("MSG_NICK_UNKNOWN", search);
d76ed9a9 4991
c8273589
AS
4992 reply("CSMSG_LAMERS_HEADER", channel->name);
4993 bans = alloca(channel->channel_info->banCount * sizeof(struct banData *)); /* lamers */
d76ed9a9 4994
c8273589 4995 /* lamers */
d76ed9a9
AS
4996 for(ban = channel->channel_info->bans; ban; ban = ban->next)
4997 {
1136f709 4998 if(search_u)
4999 {
5000 if(!user_matches_glob(search_u, ban->mask, MATCH_USENICK | MATCH_VISIBLE))
5001 continue;
5002 }
5003 else if(search)
5004 {
5005 if(search_wilds ? !match_ircglobs(search, ban->mask) : !match_ircglob(search, ban->mask))
5006 continue;
5007 }
d76ed9a9
AS
5008 bans[matches++] = ban;
5009 if(ban->expires)
5010 timed = 1;
5011 }
5012
5013 tbl.length = matches + 1;
5014 tbl.width = 4 + timed;
5015 tbl.flags = 0;
5016 tbl.flags = TABLE_NO_FREE;
5017 tbl.contents = malloc(tbl.length * sizeof(tbl.contents[0]));
5018 tbl.contents[0] = malloc(tbl.width * sizeof(tbl.contents[0][0]));
5019 tbl.contents[0][0] = "Mask";
5020 tbl.contents[0][1] = "Set By";
5021 tbl.contents[0][2] = "Triggered";
5022 if(timed)
5023 {
5024 tbl.contents[0][3] = "Expires";
5025 tbl.contents[0][4] = "Reason";
5026 }
5027 else
5028 tbl.contents[0][3] = "Reason";
5029 if(!matches)
5030 {
5031 table_send(cmd->parent->bot, user->nick, 0, NULL, tbl);
075d7932 5032/* reply("MSG_NONE"); */
d76ed9a9
AS
5033 free(tbl.contents[0]);
5034 free(tbl.contents);
5035 return 0;
5036 }
5037
5038 msg_never = user_find_message(user, "MSG_NEVER");
5039 for(ii = 0; ii < matches; )
5040 {
5041 ban = bans[ii];
5042
5043 if(!timed)
5044 expires = "";
5045 else if(ban->expires)
5046 expires = intervalString(e_buffer, ban->expires - now, user->handle_info);
5047 else
5048 expires = msg_never;
5049
5050 if(ban->triggered)
5051 triggered = intervalString(t_buffer, now - ban->triggered, user->handle_info);
5052 else
5053 triggered = msg_never;
5054
5055 tbl.contents[++ii] = malloc(tbl.width * sizeof(tbl.contents[0][0]));
5056 tbl.contents[ii][0] = ban->mask;
5057 tbl.contents[ii][1] = ban->owner;
5058 tbl.contents[ii][2] = strdup(triggered);
5059 if(timed)
5060 {
5061 tbl.contents[ii][3] = strdup(expires);
5062 tbl.contents[ii][4] = ban->reason;
5063 }
5064 else
5065 tbl.contents[ii][3] = ban->reason;
5066 }
5067 table_send(cmd->parent->bot, user->nick, 0, NULL, tbl);
de9510bc 5068 /* reply("MSG_MATCH_COUNT", matches); */
d76ed9a9
AS
5069 for(ii = 1; ii < tbl.length; ++ii)
5070 {
5071 free((char*)tbl.contents[ii][2]);
5072 if(timed)
5073 free((char*)tbl.contents[ii][3]);
5074 free(tbl.contents[ii]);
5075 }
5076 free(tbl.contents[0]);
5077 free(tbl.contents);
5078 return 1;
5079}
5080
b75e24a3
AS
5081/* bad_topic
5082 *
5083 * return + if the user does NOT have the right to set the topic, and
5084 * the topic is changed.
5085 */
d76ed9a9
AS
5086static int
5087bad_topic(struct chanNode *channel, struct userNode *user, const char *new_topic)
5088{
5089 struct chanData *cData = channel->channel_info;
5090 if(check_user_level(channel, user, lvlEnfTopic, 1, 0))
5091 return 0;
d76ed9a9
AS
5092 else if(cData->topic)
5093 return irccasecmp(new_topic, cData->topic);
5094 else
5095 return 0;
5096}
5097
b75e24a3
AS
5098/* conform_topic
5099 *
5100 * Makes a givin topic fit into a givin topic mask and returns
5101 * the results.
5102 *
5103 * topic_mask - the mask to conform to
5104 * topic - the topic to make conform
5105 * new_topic - the pre-allocated char* to put the new topic into
5106 *
5107 * modifies: new_topic
5108 */
5109void
5110conform_topic(char* topic_mask, char* topic, char *new_topic)
5111{
5112 //char *topic_mask = cData->topic_mask;
5113 char tchar;
5114 int pos=0, starpos=-1, dpos=0, len;
5115
5116 while((tchar = topic_mask[pos++]) && (dpos <= TOPICLEN))
5117 {
5118 switch(tchar)
5119 {
5120 case '*':
5121 if(starpos != -1)
5122 {
5123 strcpy(new_topic, "");
5124 return;
5125 }
5126 len = strlen(topic);
5127 if((dpos + len) > TOPICLEN)
5128 len = TOPICLEN + 1 - dpos;
5129 memcpy(new_topic+dpos, topic, len);
5130 dpos += len;
5131 starpos = pos;
5132 break;
5133 case '\\': tchar = topic_mask[pos++]; /* and fall through */
5134 default: new_topic[dpos++] = tchar; break;
5135 }
5136 }
5137 if((dpos > TOPICLEN) || tchar)
5138 {
5139 strcpy(new_topic, "");
5140 return;
5141 }
5142 new_topic[dpos] = 0;
5143 return;
5144}
5145
d76ed9a9
AS
5146static CHANSERV_FUNC(cmd_topic)
5147{
5148 struct chanData *cData;
5149 char *topic;
7fda2b52 5150 int p10 = 0;
5151
5152#ifdef WITH_PROTOCOL_P10
5153 p10 = 1;
5154#endif
d76ed9a9
AS
5155
5156 cData = channel->channel_info;
5157 if(argc < 2)
5158 {
b75e24a3
AS
5159 if(cData->topic)
5160 {
75ef8cdc 5161 /*XXX Why would we ever want to send chanserv as the setter? I dont understand -Rubin */
7fda2b52 5162 SetChannelTopic(channel, chanserv, p10 ? user : chanserv, cData->topic, 1);
b75e24a3 5163 reply("CSMSG_TOPIC_SET", cData->topic);
d76ed9a9 5164 return 1;
b75e24a3 5165 }
d76ed9a9
AS
5166
5167 reply("CSMSG_NO_TOPIC", channel->name);
5168 return 0;
5169 }
5170
5171 topic = unsplit_string(argv + 1, argc - 1, NULL);
5172 /* If they say "!topic *", use an empty topic. */
5173 if((topic[0] == '*') && (topic[1] == 0))
5174 topic[0] = 0;
b75e24a3 5175
d76ed9a9
AS
5176 if(bad_topic(channel, user, topic))
5177 {
b75e24a3
AS
5178 reply("CSMSG_TOPIC_LOCKED", channel->name);
5179 return 0;
5180 }
5181 else
5182 {
5183 /* If there is a topicmask set, and the new topic doesnt match, make it */
5184 if(cData->topic_mask && !match_ircglob(topic, cData->topic_mask))
d76ed9a9 5185 {
b75e24a3
AS
5186 char *topic_mask = cData->topic_mask;
5187 char new_topic[TOPICLEN+1];
d76ed9a9 5188
b75e24a3
AS
5189 /* make a new topic fitting mask */
5190 conform_topic(topic_mask, topic, new_topic);
5191 if(!*new_topic)
d76ed9a9 5192 {
b75e24a3 5193 /* Topic couldnt fit into mask, was too long */
d76ed9a9
AS
5194 reply("CSMSG_TOPICMASK_CONFLICT1", channel->name, topic_mask);
5195 reply("CSMSG_TOPICMASK_CONFLICT2", TOPICLEN);
5196 return 0;
5197 }
7fda2b52 5198 SetChannelTopic(channel, chanserv, p10 ? user : chanserv, new_topic, 1);
d76ed9a9 5199 }
b75e24a3 5200 else /* No mask set, just set the topic */
7fda2b52 5201 SetChannelTopic(channel, chanserv, p10 ? user : chanserv, topic, 1);
d76ed9a9 5202 }
d76ed9a9
AS
5203
5204 if(check_user_level(channel, user, lvlTopicSnarf, 1, 0))
5205 {
5206 /* Grab the topic and save it as the default topic. */
5207 free(cData->topic);
5208 cData->topic = strdup(channel->topic);
5209 }
5210
5211 return 1;
5212}
5213
5214static CHANSERV_FUNC(cmd_mode)
5215{
2f61d1d7 5216 struct userData *uData;
d76ed9a9 5217 struct mod_chanmode *change;
2f61d1d7 5218 short base_oplevel;
d76ed9a9
AS
5219
5220 if(argc < 2)
5221 {
08895577 5222 if (checkDefCon(DEFCON_NO_MODE_CHANGE) && !IsOper(user)) {
5223 reply("CSMSG_DEFCON_NO_MODE_CHANGE");
5224 return 0;
5225 }
5226
d76ed9a9
AS
5227 change = &channel->channel_info->modes;
5228 if(change->modes_set || change->modes_clear) {
5229 modcmd_chanmode_announce(change);
5230 reply("CSMSG_DEFAULTED_MODES", channel->name);
5231 } else
5232 reply("CSMSG_NO_MODES", channel->name);
5233 return 1;
5234 }
5235
2f61d1d7 5236 uData = GetChannelUser(channel->channel_info, user->handle_info);
5237 if (!uData)
5238 base_oplevel = MAXOPLEVEL;
5239 else if (uData->access >= UL_OWNER)
5240 base_oplevel = 1;
5241 else
5242 base_oplevel = 1 + UL_OWNER - uData->access;
5243 change = mod_chanmode_parse(channel, argv+1, argc-1, MCP_KEY_FREE|MCP_REGISTERED, base_oplevel);
5244
d76ed9a9
AS
5245 if(!change)
5246 {
5247 reply("MSG_INVALID_MODES", unsplit_string(argv+1, argc-1, NULL));
5248 return 0;
5249 }
5250
5251 if(!check_user_level(channel, user, lvlEnfModes, 1, 0)
5252 && mode_lock_violated(&channel->channel_info->modes, change))
5253 {
5254 char modes[MAXLEN];
5255 mod_chanmode_format(&channel->channel_info->modes, modes);
5256 reply("CSMSG_MODE_LOCKED", modes, channel->name);
5257 return 0;
5258 }
5259
5260 modcmd_chanmode_announce(change);
5261 mod_chanmode_free(change);
5262 reply("CSMSG_MODES_SET", unsplit_string(argv+1, argc-1, NULL));
5263 return 1;
5264}
5265
5266static CHANSERV_FUNC(cmd_invite)
5267{
5268 struct userData *uData;
5269 struct userNode *invite;
5270
5271 uData = GetChannelUser(channel->channel_info, user->handle_info);
5272
5273 if(argc > 1)
5274 {
5275 if(!(invite = GetUserH(argv[1])))
5276 {
5277 reply("MSG_NICK_UNKNOWN", argv[1]);
5278 return 0;
5279 }
5280 }
5281 else
5282 invite = user;
5283
5284 if(GetUserMode(channel, invite))
5285 {
5286 reply("CSMSG_ALREADY_PRESENT", invite->nick, channel->name);
5287 return 0;
5288 }
5289
5290 if(user != invite)
5291 {
5292 if(argc > 2)
5293 {
5294 char *reason = unsplit_string(argv + 2, argc - 2, NULL);
5295 send_message(invite, chanserv, "CSMSG_INVITING_YOU_REASON", user->nick, channel->name, reason);
5296 }
5297 else
5298 send_message(invite, chanserv, "CSMSG_INVITING_YOU", user->nick, channel->name);
5299 }
5f1600ab 5300
22d5d8cf 5301 if (invite->handle_info && invite->handle_info->ignores->used && (argc > 1)) {
5f1600ab 5302 unsigned int i;
5303 for (i=0; i < invite->handle_info->ignores->used; i++) {
5304 if (user_matches_glob(user, invite->handle_info->ignores->list[i], MATCH_USENICK)) {
5305 reply("CSMSG_CANNOT_INVITE", argv[1], channel->name);
5306 return 0;
5307 }
5308 }
5309 }
5310
d76ed9a9
AS
5311 irc_invite(chanserv, invite, channel);
5312 if(argc > 1)
5313 reply("CSMSG_INVITED_USER", argv[1], channel->name);
5314
5315 return 1;
5316}
5317
5318static CHANSERV_FUNC(cmd_inviteme)
5319{
5320 if(GetUserMode(channel, user))
5321 {
5322 reply("CSMSG_YOU_ALREADY_PRESENT", channel->name);
5323 return 0;
5324 }
5325 if(channel->channel_info
5326 && !check_user_level(channel, user, lvlInviteMe, 1, 0))
5327 {
5328 reply("CSMSG_LOW_CHANNEL_ACCESS", channel->name);
5329 return 0;
5330 }
5331 irc_invite(cmd->parent->bot, user, channel);
5332 return 1;
5333}
5334
5335static void
5336show_suspension_info(struct svccmd *cmd, struct userNode *user, struct suspended *suspended)
5337{
5338 unsigned int combo;
5339 char buf1[INTERVALLEN], buf2[INTERVALLEN];
5340
5341 /* We display things based on two dimensions:
5342 * - Issue time: present or absent
5343 * - Expiration: revoked, expired, expires in future, or indefinite expiration
5344 * (in order of precedence, so something both expired and revoked
5345 * only counts as revoked)
5346 */
5347 combo = (suspended->issued ? 4 : 0)
5348 + (suspended->revoked ? 3 : suspended->expires ? ((suspended->expires < now) ? 2 : 1) : 0);
5349 switch(combo) {
5350 case 0: /* no issue time, indefinite expiration */
5351 reply("CSMSG_CHANNEL_SUSPENDED_0", suspended->suspender, suspended->reason);
5352 break;
5353 case 1: /* no issue time, expires in future */
5354 intervalString(buf1, suspended->expires-now, user->handle_info);
5355 reply("CSMSG_CHANNEL_SUSPENDED_1", suspended->suspender, buf1, suspended->reason);
5356 break;
5357 case 2: /* no issue time, expired */
5358 intervalString(buf1, now-suspended->expires, user->handle_info);
5359 reply("CSMSG_CHANNEL_SUSPENDED_2", suspended->suspender, buf1, suspended->reason);
5360 break;
5361 case 3: /* no issue time, revoked */
5362 intervalString(buf1, now-suspended->revoked, user->handle_info);
5363 reply("CSMSG_CHANNEL_SUSPENDED_3", suspended->suspender, buf1, suspended->reason);
5364 break;
5365 case 4: /* issue time set, indefinite expiration */
5366 intervalString(buf1, now-suspended->issued, user->handle_info);
5367 reply("CSMSG_CHANNEL_SUSPENDED_4", buf1, suspended->suspender, suspended->reason);
5368 break;
5369 case 5: /* issue time set, expires in future */
5370 intervalString(buf1, now-suspended->issued, user->handle_info);
5371 intervalString(buf2, suspended->expires-now, user->handle_info);
5372 reply("CSMSG_CHANNEL_SUSPENDED_5", buf1, suspended->suspender, buf2, suspended->reason);
5373 break;
5374 case 6: /* issue time set, expired */
5375 intervalString(buf1, now-suspended->issued, user->handle_info);
5376 intervalString(buf2, now-suspended->expires, user->handle_info);
5377 reply("CSMSG_CHANNEL_SUSPENDED_6", buf1, suspended->suspender, buf2, suspended->reason);
5378 break;
5379 case 7: /* issue time set, revoked */
5380 intervalString(buf1, now-suspended->issued, user->handle_info);
5381 intervalString(buf2, now-suspended->revoked, user->handle_info);
5382 reply("CSMSG_CHANNEL_SUSPENDED_7", buf1, suspended->suspender, buf2, suspended->reason);
5383 break;
5384 default:
5385 log_module(CS_LOG, LOG_ERROR, "Invalid combo value %d in show_suspension_info()", combo);
5386 return;
5387 }
5388}
5389
82f37c08 5390static void
5391show_giveownership_info(struct svccmd *cmd, struct userNode *user, struct giveownership *giveownership)
5392{
5393 char buf[MAXLEN];
5394 const char *fmt = "%a %b %d %H:%M %Y";
5395 strftime(buf, sizeof(buf), fmt, localtime(&giveownership->issued));
5396
5397 if(giveownership->staff_issuer)
5398 {
5399 if(giveownership->reason)
5400 reply("CSMSG_CHANNEL_OWNERSHIP_STAFF_REASON", giveownership->old_owner,
5401 giveownership->target, giveownership->target_access,
5402 giveownership->staff_issuer, buf, giveownership->reason);
5403 else
5404 reply("CSMSG_CHANNEL_OWNERSHIP_STAFF", giveownership->old_owner,
5405 giveownership->target, giveownership->target_access,
5406 giveownership->staff_issuer, buf);
5407 }
5408 else
5409 {
5410 reply("CSMSG_CHANNEL_OWNERSHIP_NORMAL", giveownership->old_owner, giveownership->target, giveownership->target_access, buf);
5411 }
5412}
5413
5414
d76ed9a9
AS
5415static CHANSERV_FUNC(cmd_info)
5416{
5417 char modes[MAXLEN], buffer[INTERVALLEN];
5418 struct userData *uData, *owner;
5419 struct chanData *cData;
5420 struct do_not_register *dnr;
5421 struct note *note;
5422 dict_iterator_t it;
5423 int privileged;
5424
5425 cData = channel->channel_info;
5426 reply("CSMSG_CHANNEL_INFO", channel->name);
cde20a3f
AS
5427 if(user->handle_info && user->handle_info->userlist_style != HI_STYLE_CLEAN)
5428 reply("CSMSG_BAR");
d76ed9a9
AS
5429
5430 uData = GetChannelUser(cData, user->handle_info);
b75e24a3 5431 if(uData && (uData->access >= UL_OP /*cData->lvlOpts[lvlGiveOps]*/))
d76ed9a9
AS
5432 {
5433 mod_chanmode_format(&cData->modes, modes);
5434 reply("CSMSG_CHANNEL_TOPIC", cData->topic);
5435 reply("CSMSG_CHANNEL_MODES", modes[0] ? modes : user_find_message(user, "MSG_NONE"));
5436 }
5437
5438 for(it = dict_first(cData->notes); it; it = iter_next(it))
5439 {
5440 int padding;
5441
5442 note = iter_data(it);
5443 if(!note_type_visible_to_user(cData, note->type, user))
5444 continue;
5445
5446 padding = PADLEN - 1 - strlen(iter_key(it));
5447 reply("CSMSG_CHANNEL_NOTE", iter_key(it), padding > 0 ? padding : 1, "", note->note);
5448 }
5449
5450 reply("CSMSG_CHANNEL_MAX", cData->max);
5451 for(owner = cData->users; owner; owner = owner->next)
5452 if(owner->access == UL_OWNER)
5453 reply("CSMSG_CHANNEL_OWNER", owner->handle->handle);
5454 reply("CSMSG_CHANNEL_USERS", cData->userCount);
c8273589 5455 reply("CSMSG_CHANNEL_LAMERS", cData->banCount);
d76ed9a9 5456 reply("CSMSG_CHANNEL_VISITED", intervalString(buffer, now - cData->visited, user->handle_info));
d76ed9a9
AS
5457
5458 privileged = IsStaff(user);
20517d48 5459 /* if(privileged) */
697f4c9a 5460 reply("CSMSG_CHANNEL_REGISTERED", intervalString(buffer, now - cData->registered, user->handle_info));
20517d48 5461 if(/*((uData && uData->access >= UL_COOWNER) || privileged) && */cData->registrar)
d76ed9a9
AS
5462 reply("CSMSG_CHANNEL_REGISTRAR", cData->registrar);
5463
5464 if(privileged && (dnr = chanserv_is_dnr(channel->name, NULL)))
5465 chanserv_show_dnrs(user, cmd, channel->name, NULL);
5466
5467 if(cData->suspended && ((uData && (uData->access >= UL_COOWNER)) || IsHelping(user)))
5468 {
5469 struct suspended *suspended;
5470 reply((IsSuspended(cData) ? "CSMSG_CHANNEL_SUSPENDED" : "CSMSG_CHANNEL_HISTORY"), channel->name);
5471 for(suspended = cData->suspended; suspended; suspended = suspended->previous)
5472 show_suspension_info(cmd, user, suspended);
5473 }
5474 else if(IsSuspended(cData))
5475 {
5476 reply("CSMSG_CHANNEL_SUSPENDED", channel->name);
5477 show_suspension_info(cmd, user, cData->suspended);
5478 }
82f37c08 5479 if(cData->giveownership && ((uData && (uData->access >= UL_COOWNER)) || IsStaff(user)))
5480 {
5481 struct giveownership *giveownership;
5482 reply("CSMSG_CHANNEL_OWNERSHIP_HISTORY", channel->name);
5483 for(giveownership = cData->giveownership; giveownership; giveownership = giveownership->previous)
5484 show_giveownership_info(cmd, user, giveownership);
5485 }
cde20a3f
AS
5486 if(user->handle_info && user->handle_info->userlist_style != HI_STYLE_CLEAN)
5487 reply("CSMSG_CHANNEL_END");
5488 else
5489 reply("CSMSG_CHANNEL_END_CLEAN");
d76ed9a9
AS
5490 return 1;
5491}
5492
5493static CHANSERV_FUNC(cmd_netinfo)
5494{
5495 extern time_t boot_time;
5496 extern unsigned long burst_length;
5497 char interval[INTERVALLEN];
5498
5499 reply("CSMSG_NETWORK_INFO");
5500 reply("CSMSG_NETWORK_SERVERS", dict_size(servers));
5501 reply("CSMSG_NETWORK_USERS", dict_size(clients));
5502 reply("CSMSG_NETWORK_OPERS", curr_opers.used);
5503 reply("CSMSG_NETWORK_CHANNELS", registered_channels);
c8273589 5504 reply("CSMSG_NETWORK_LAMERS", banCount);
d76ed9a9
AS
5505 reply("CSMSG_NETWORK_CHANUSERS", userCount);
5506 reply("CSMSG_SERVICES_UPTIME", intervalString(interval, time(NULL) - boot_time, user->handle_info));
5507 reply("CSMSG_BURST_LENGTH", intervalString(interval, burst_length, user->handle_info));
5508 return 1;
5509}
5510
5511static void
5512send_staff_list(struct userNode *to, struct userList *list, int skip_flags)
5513{
5514 struct helpfile_table table;
5515 unsigned int nn;
5516 struct userNode *user;
5517 char *nick;
5518
5519 table.length = 0;
5520 table.width = 1;
5521 table.flags = TABLE_REPEAT_ROWS | TABLE_NO_FREE | TABLE_NO_HEADERS;
5522 table.contents = alloca(list->used*sizeof(*table.contents));
5523 for(nn=0; nn<list->used; nn++)
5524 {
5525 user = list->list[nn];
5526 if(user->modes & skip_flags)
5527 continue;
5528 if(IsBot(user))
5529 continue;
5530 table.contents[table.length] = alloca(table.width*sizeof(**table.contents));
5531 if(IsAway(user))
5532 {
5533 nick = alloca(strlen(user->nick)+3);
5534 sprintf(nick, "(%s)", user->nick);
5535 }
5536 else
5537 nick = user->nick;
5538 table.contents[table.length][0] = nick;
5539 table.length++;
5540 }
5541 table_send(chanserv, to->nick, 0, NULL, table);
5542}
5543
5544static CHANSERV_FUNC(cmd_ircops)
5545{
5546 reply("CSMSG_STAFF_OPERS");
5547 send_staff_list(user, &curr_opers, FLAGS_SERVICE);
5548 return 1;
5549}
5550
5551static CHANSERV_FUNC(cmd_helpers)
5552{
5553 reply("CSMSG_STAFF_HELPERS");
5554 send_staff_list(user, &curr_helpers, FLAGS_OPER);
5555 return 1;
5556}
5557
5558static CHANSERV_FUNC(cmd_staff)
5559{
5560 reply("CSMSG_NETWORK_STAFF");
5561 cmd_ircops(CSFUNC_ARGS);
5562 cmd_helpers(CSFUNC_ARGS);
5563 return 1;
5564}
5565
5566static CHANSERV_FUNC(cmd_peek)
5567{
5568 struct modeNode *mn;
5569 char modes[MODELEN];
5570 unsigned int n;
5571 struct helpfile_table table;
5572
5573 irc_make_chanmode(channel, modes);
5574
5575 reply("CSMSG_PEEK_INFO", channel->name);
cde20a3f
AS
5576 if(user->handle_info && user->handle_info->userlist_style != HI_STYLE_CLEAN)
5577 reply("CSMSG_BAR");
d76ed9a9
AS
5578 reply("CSMSG_PEEK_TOPIC", channel->topic);
5579 reply("CSMSG_PEEK_MODES", modes);
5580 reply("CSMSG_PEEK_USERS", channel->members.used);
5581
5582 table.length = 0;
5583 table.width = 1;
5584 table.flags = TABLE_REPEAT_ROWS | TABLE_NO_FREE | TABLE_NO_HEADERS;
5585 table.contents = alloca(channel->members.used*sizeof(*table.contents));
5586 for(n = 0; n < channel->members.used; n++)
5587 {
5588 mn = channel->members.list[n];
5589 if(!(mn->modes & MODE_CHANOP) || IsLocal(mn->user))
5590 continue;
5591 table.contents[table.length] = alloca(sizeof(**table.contents));
5592 table.contents[table.length][0] = mn->user->nick;
5593 table.length++;
5594 }
5595 if(table.length)
5596 {
5597 reply("CSMSG_PEEK_OPS");
5598 table_send(chanserv, user->nick, 0, NULL, table);
5599 }
5600 else
5601 reply("CSMSG_PEEK_NO_OPS");
de9510bc 5602 reply("CSMSG_PEEK_END");
d76ed9a9
AS
5603 return 1;
5604}
5605
5606static MODCMD_FUNC(cmd_wipeinfo)
5607{
5608 struct handle_info *victim;
1136f709 5609 struct userData *ud, *actor, *real_actor;
5610 unsigned int override = 0;
d76ed9a9
AS
5611
5612 REQUIRE_PARAMS(2);
5613 actor = GetChannelUser(channel->channel_info, user->handle_info);
1136f709 5614 real_actor = GetChannelAccess(channel->channel_info, user->handle_info);
d76ed9a9
AS
5615 if(!(victim = modcmd_get_handle_info(user, argv[1])))
5616 return 0;
5617 if(!(ud = GetTrueChannelAccess(channel->channel_info, victim)))
5618 {
5619 reply("CSMSG_NO_CHAN_USER", argv[1], channel->name);
5620 return 0;
5621 }
5622 if((ud->access >= actor->access) && (ud != actor))
5623 {
5624 reply("MSG_USER_OUTRANKED", victim->handle);
5625 return 0;
5626 }
1136f709 5627 if((ud != real_actor) && (!real_actor || (ud->access >= real_actor->access)))
5628 override = CMD_LOG_OVERRIDE;
d76ed9a9
AS
5629 if(ud->info)
5630 free(ud->info);
5631 ud->info = NULL;
5632 reply("CSMSG_WIPED_INFO_LINE", argv[1], channel->name);
1136f709 5633 return 1 | override;
d76ed9a9
AS
5634}
5635
7637f48f 5636static void
5637resync_channel(struct chanNode *channel)
d76ed9a9
AS
5638{
5639 struct mod_chanmode *changes;
5640 struct chanData *cData = channel->channel_info;
5641 unsigned int ii, used;
5642
e3b2f789
AS
5643 /* 6 = worst case -ovh+ovh on everyone */
5644 changes = mod_chanmode_alloc(channel->members.used * 6);
d76ed9a9
AS
5645 for(ii = used = 0; ii < channel->members.used; ++ii)
5646 {
5647 struct modeNode *mn = channel->members.list[ii];
5648 struct userData *uData;
5649
5650 if(IsService(mn->user))
5651 continue;
5652
e3b2f789 5653
d76ed9a9 5654 uData = GetChannelAccess(cData, mn->user->handle_info);
e3b2f789
AS
5655
5656 /* If the channel is in no-mode mode, de-mode EVERYONE */
5657 if(cData->chOpts[chAutomode] == 'n')
55342ce8 5658 {
e3b2f789
AS
5659 if(mn->modes)
5660 {
5661 changes->args[used].mode = MODE_REMOVE | mn->modes;
5662 changes->args[used++].u.member = mn;
5663 }
55342ce8 5664 }
e3b2f789 5665 else /* Give various userlevels their modes.. */
d76ed9a9 5666 {
e3b2f789 5667 if(uData && uData->access >= UL_OP )
d76ed9a9 5668 {
e3b2f789
AS
5669 if(!(mn->modes & MODE_CHANOP))
5670 {
5671 changes->args[used].mode = MODE_CHANOP;
5672 changes->args[used++].u.member = mn;
5673 }
d76ed9a9 5674 }
e3b2f789 5675 else if(uData && uData->access >= UL_HALFOP)
55342ce8 5676 {
e3b2f789
AS
5677 if(mn->modes & MODE_CHANOP)
5678 {
5679 changes->args[used].mode = MODE_REMOVE | MODE_CHANOP;
5680 changes->args[used++].u.member = mn;
5681 }
5682 if(!(mn->modes & MODE_HALFOP))
5683 {
5684 changes->args[used].mode = MODE_HALFOP;
5685 changes->args[used++].u.member = mn;
5686 }
55342ce8 5687 }
e3b2f789 5688 else if(uData && uData->access >= UL_PEON )
d76ed9a9 5689 {
e3b2f789
AS
5690 if(mn->modes & MODE_CHANOP)
5691 {
5692 changes->args[used].mode = MODE_REMOVE | MODE_CHANOP;
5693 changes->args[used++].u.member = mn;
5694 }
5695 if(mn->modes & MODE_HALFOP)
5696 {
5697 changes->args[used].mode = MODE_REMOVE | MODE_HALFOP;
5698 changes->args[used++].u.member = mn;
5699 }
5700 /* Don't voice peons if were in mode m */
5701 if( cData->chOpts[chAutomode] == 'm')
5702 {
5703 if(mn->modes & MODE_VOICE)
5704 {
5705 changes->args[used].mode = MODE_REMOVE | MODE_VOICE;
5706 changes->args[used++].u.member = mn;
5707 }
5708 }
5709 /* otherwise, make user they do have voice */
5710 else if(!(mn->modes & MODE_VOICE))
5711 {
5712 changes->args[used].mode = MODE_VOICE;
5713 changes->args[used++].u.member = mn;
5714 }
d76ed9a9 5715 }
e3b2f789 5716 else /* They arnt on the userlist.. */
d76ed9a9 5717 {
e3b2f789
AS
5718 /* If we voice everyone, but they dont.. */
5719 if(cData->chOpts[chAutomode] == 'v')
5720 {
5721 /* Remove anything except v */
5722 if(mn->modes & ~MODE_VOICE)
5723 {
5724 changes->args[used].mode = MODE_REMOVE | (mn->modes & ~MODE_VOICE);
5725 changes->args[used++].u.member = mn;
5726 }
5727 /* Add v */
5728 if(!(mn->modes & MODE_VOICE))
5729 {
5730 changes->args[used].mode = MODE_VOICE;
5731 changes->args[used++].u.member = mn;
5732 }
5733 }
5734 /* If we hop everyone, but they dont.. */
5735 else if(cData->chOpts[chAutomode] == 'h')
5736 {
5737 /* Remove anything except h */
5738 if(mn->modes & ~MODE_HALFOP)
5739 {
5740 changes->args[used].mode = MODE_REMOVE | (mn->modes & ~MODE_HALFOP);
5741 changes->args[used++].u.member = mn;
5742 }
5743 /* Add h */
5744 if(!(mn->modes & MODE_HALFOP))
5745 {
5746 changes->args[used].mode = MODE_HALFOP;
5747 changes->args[used++].u.member = mn;
5748 }
5749 }
5750 /* If we op everyone, but they dont.. */
5751 else if(cData->chOpts[chAutomode] == 'o')
5752 {
5753 /* Remove anything except h */
5754 if(mn->modes & ~MODE_CHANOP)
5755 {
5756 changes->args[used].mode = MODE_REMOVE | (mn->modes & ~MODE_CHANOP);
5757 changes->args[used++].u.member = mn;
5758 }
5759 /* Add h */
5760 if(!(mn->modes & MODE_CHANOP))
5761 {
5762 changes->args[used].mode = MODE_CHANOP;
5763 changes->args[used++].u.member = mn;
5764 }
5765 }
5766 /* they have no excuse for having modes, de-everything them */
5767 else
5768 {
5769 if(mn->modes)
5770 {
5771 changes->args[used].mode = MODE_REMOVE | mn->modes;
5772 changes->args[used++].u.member = mn;
5773 }
5774 }
d76ed9a9
AS
5775 }
5776 }
5777 }
5778 changes->argc = used;
7637f48f 5779 mod_chanmode_announce(chanserv, channel, changes);
d76ed9a9 5780 mod_chanmode_free(changes);
7637f48f 5781}
5782
5783static CHANSERV_FUNC(cmd_resync)
5784{
5785 resync_channel(channel);
d76ed9a9
AS
5786 reply("CSMSG_RESYNCED_USERS", channel->name);
5787 return 1;
5788}
5789
5790static CHANSERV_FUNC(cmd_seen)
5791{
5792 struct userData *uData;
5793 struct handle_info *handle;
5794 char seen[INTERVALLEN];
5795
5796 REQUIRE_PARAMS(2);
5797
5798 if(!irccasecmp(argv[1], chanserv->nick))
5799 {
5800 reply("CSMSG_IS_CHANSERV");
5801 return 1;
5802 }
5803
5804 if(!(handle = get_handle_info(argv[1])))
5805 {
5806 reply("MSG_HANDLE_UNKNOWN", argv[1]);
5807 return 0;
5808 }
5809
5810 if(!(uData = GetTrueChannelAccess(channel->channel_info, handle)))
5811 {
5812 reply("CSMSG_NO_CHAN_USER", handle->handle, channel->name);
5813 return 0;
5814 }
5815
5816 if(uData->present)
5817 reply("CSMSG_USER_PRESENT", handle->handle);
5818 else if(uData->seen)
5819 reply("CSMSG_USER_SEEN", handle->handle, channel->name, intervalString(seen, now - uData->seen, user->handle_info));
5820 else
5821 reply("CSMSG_NEVER_SEEN", handle->handle, channel->name);
5822
5823 if(!uData->present && HANDLE_FLAGGED(handle, FROZEN))
5824 reply("CSMSG_USER_VACATION", handle->handle);
5825
5826 return 1;
5827}
5828
5829static MODCMD_FUNC(cmd_names)
5830{
5831 struct userNode *targ;
5832 struct userData *targData;
5833 unsigned int ii, pos;
5834 char buf[400];
5835
5836 for(ii=pos=0; ii<channel->members.used; ++ii)
5837 {
5838 targ = channel->members.list[ii]->user;
5839 targData = GetTrueChannelAccess(channel->channel_info, targ->handle_info);
5840 if(!targData)
5841 continue;
5842 if(pos + strlen(targ->nick) + strlen(targ->handle_info->handle) + 8 > sizeof(buf))
5843 {
5844 buf[pos] = 0;
5845 reply("CSMSG_CHANNEL_NAMES", channel->name, buf);
5846 pos = 0;
5847 }
5848 buf[pos++] = ' ';
5849 if(IsUserSuspended(targData))
5850 buf[pos++] = 's';
5851 pos += sprintf(buf+pos, "%d:%s(%s)", targData->access, targ->nick, targ->handle_info->handle);
5852 }
5853 buf[pos] = 0;
5854 reply("CSMSG_CHANNEL_NAMES", channel->name, buf);
5855 reply("CSMSG_END_NAMES", channel->name);
5856 return 1;
5857}
5858
5859static int
5860note_type_visible_to_user(struct chanData *channel, struct note_type *ntype, struct userNode *user)
5861{
5862 switch(ntype->visible_type)
5863 {
5864 case NOTE_VIS_ALL: return 1;
5865 case NOTE_VIS_CHANNEL_USERS: return !channel || !user || (user->handle_info && GetChannelUser(channel, user->handle_info));
5866 case NOTE_VIS_PRIVILEGED: default: return user && (IsOper(user) || IsSupportHelper(user) || IsNetworkHelper(user));
5867 }
5868}
5869
5870static int
5871note_type_settable_by_user(struct chanNode *channel, struct note_type *ntype, struct userNode *user)
5872{
5873 struct userData *uData;
5874
5875 switch(ntype->set_access_type)
5876 {
338a82b5
AS
5877 case NOTE_SET_CHANNEL_ACCESS:
5878 if(!user->handle_info)
5879 return 0;
5880 if(!(uData = GetChannelUser(channel->channel_info, user->handle_info)))
5881 return 0;
5882 return uData->access >= ntype->set_access.min_ulevel;
5883 case NOTE_SET_CHANNEL_SETTER:
5884 return check_user_level(channel, user, lvlSetters, 1, 0);
5885 case NOTE_SET_PRIVILEGED: default:
5886 return IsHelping(user) && (user->handle_info->opserv_level >= ntype->set_access.min_opserv);
d76ed9a9
AS
5887 }
5888}
5889
5890static CHANSERV_FUNC(cmd_note)
5891{
5892 struct chanData *cData;
5893 struct note *note;
5894 struct note_type *ntype;
5895
5896 cData = channel->channel_info;
5897 if(!cData)
5898 {
5899 reply("CSMSG_NOT_REGISTERED", channel->name);
5900 return 0;
5901 }
5902
5903 /* If no arguments, show all visible notes for the channel. */
5904 if(argc < 2)
5905 {
5906 dict_iterator_t it;
5907 unsigned int count;
5908
5909 for(count=0, it=dict_first(cData->notes); it; it=iter_next(it))
5910 {
5911 note = iter_data(it);
5912 if(!note_type_visible_to_user(cData, note->type, user))
5913 continue;
5914 if(!count++)
5915 reply("CSMSG_NOTELIST_HEADER", channel->name);
5916 reply("CSMSG_NOTE_FORMAT", iter_key(it), note->setter, note->note);
5917 }
5918 if(count)
5919 reply("CSMSG_NOTELIST_END", channel->name);
5920 else
5921 reply("CSMSG_NOTELIST_EMPTY", channel->name);
5922 }
5923 /* If one argument, show the named note. */
5924 else if(argc == 2)
5925 {
5926 if((note = dict_find(cData->notes, argv[1], NULL))
5927 && note_type_visible_to_user(cData, note->type, user))
5928 {
5929 reply("CSMSG_NOTE_FORMAT", note->type->name, note->setter, note->note);
5930 }
5931 else if((ntype = dict_find(note_types, argv[1], NULL))
5932 && note_type_visible_to_user(NULL, ntype, user))
5933 {
5934 reply("CSMSG_NO_SUCH_NOTE", channel->name, ntype->name);
5935 return 0;
5936 }
5937 else
5938 {
5939 reply("CSMSG_BAD_NOTE_TYPE", argv[1]);
5940 return 0;
5941 }
5942 }
5943 /* Assume they're trying to set a note. */
5944 else
5945 {
5946 char *note_text;
5947 ntype = dict_find(note_types, argv[1], NULL);
5948 if(!ntype)
5949 {
5950 reply("CSMSG_BAD_NOTE_TYPE", argv[1]);
5951 return 0;
5952 }
5953 else if(note_type_settable_by_user(channel, ntype, user))
5954 {
5955 note_text = unsplit_string(argv+2, argc-2, NULL);
5956 if((note = dict_find(cData->notes, argv[1], NULL)))
5957 reply("CSMSG_REPLACED_NOTE", ntype->name, channel->name, note->setter, note->note);
5958 chanserv_add_channel_note(cData, ntype, user->handle_info->handle, note_text);
5959 reply("CSMSG_NOTE_SET", ntype->name, channel->name);
5960
5961 if(ntype->visible_type == NOTE_VIS_PRIVILEGED)
5962 {
5963 /* The note is viewable to staff only, so return 0
5964 to keep the invocation from getting logged (or
5965 regular users can see it in !events). */
5966 return 0;
5967 }
5968 }
5969 else
5970 {
5971 reply("CSMSG_NO_ACCESS");
5972 return 0;
5973 }
5974 }
5975 return 1;
5976}
5977
5978static CHANSERV_FUNC(cmd_delnote)
5979{
5980 struct note *note;
5981
5982 REQUIRE_PARAMS(2);
5983 if(!(note = dict_find(channel->channel_info->notes, argv[1], NULL))
5984 || !note_type_settable_by_user(channel, note->type, user))
5985 {
5986 reply("CSMSG_NO_SUCH_NOTE", channel->name, argv[1]);
5987 return 0;
5988 }
5989 dict_remove(channel->channel_info->notes, note->type->name);
5990 reply("CSMSG_NOTE_REMOVED", argv[1], channel->name);
5991 return 1;
5992}
5993
23475fc6 5994static CHANSERV_FUNC(cmd_last)
5995{
5996 int numoflines;
5997
5998 REQUIRE_PARAMS(1);
5999
6000 numoflines = (argc > 1) ? atoi(argv[1]) : 10;
6001
6002 if(numoflines < 1 || numoflines > 200)
6003 {
6004 reply("CSMSG_LAST_INVALID");
6005 return 0;
6006 }
6007 ShowLog(user, channel, "*", "*", "*", "*", numoflines);
6008 return 1;
6009}
6010
d76ed9a9
AS
6011static CHANSERV_FUNC(cmd_events)
6012{
6013 struct logSearch discrim;
6014 struct logReport report;
6015 unsigned int matches, limit;
6016
6017 limit = (argc > 1) ? atoi(argv[1]) : 10;
6018 if(limit < 1 || limit > 200)
6019 limit = 10;
6020
6021 memset(&discrim, 0, sizeof(discrim));
6022 discrim.masks.bot = chanserv;
6023 discrim.masks.channel_name = channel->name;
6024 if(argc > 2)
6025 discrim.masks.command = argv[2];
6026 discrim.limit = limit;
6027 discrim.max_time = INT_MAX;
6028 discrim.severities = 1 << LOG_COMMAND;
6029 report.reporter = chanserv;
6030 report.user = user;
de9510bc 6031 reply("CSMSG_EVENT_SEARCH_RESULTS", channel->name);
cde20a3f
AS
6032 if(user->handle_info && user->handle_info->userlist_style != HI_STYLE_CLEAN)
6033 reply("CSMSG_BAR");
d76ed9a9
AS
6034 matches = log_entry_search(&discrim, log_report_entry, &report);
6035 if(matches)
6036 reply("MSG_MATCH_COUNT", matches);
6037 else
6038 reply("MSG_NO_MATCHES");
6039 return 1;
6040}
6041
6042static CHANSERV_FUNC(cmd_say)
6043{
6044 char *msg;
6045 if(channel)
6046 {
6047 REQUIRE_PARAMS(2);
6048 msg = unsplit_string(argv + 1, argc - 1, NULL);
6049 send_channel_message(channel, cmd->parent->bot, "%s", msg);
6050 }
1136f709 6051 else if(*argv[1] == '*' && argv[1][1] != '\0')
6052 {
6053 struct handle_info *hi;
6054 struct userNode *authed;
6055
6056 REQUIRE_PARAMS(3);
6057 msg = unsplit_string(argv + 2, argc - 2, NULL);
6058
6059 if (!(hi = get_handle_info(argv[1] + 1)))
6060 {
6061 reply("MSG_HANDLE_UNKNOWN", argv[1] + 1);
6062 return 0;
6063 }
6064
6065 for (authed = hi->users; authed; authed = authed->next_authed)
6066 send_target_message(5, authed->nick, cmd->parent->bot, "%s", msg);
6067 }
d76ed9a9
AS
6068 else if(GetUserH(argv[1]))
6069 {
6070 REQUIRE_PARAMS(3);
6071 msg = unsplit_string(argv + 2, argc - 2, NULL);
6072 send_target_message(5, argv[1], cmd->parent->bot, "%s", msg);
6073 }
6074 else
6075 {
6076 reply("MSG_NOT_TARGET_NAME");
6077 return 0;
6078 }
6079 return 1;
6080}
6081
6082static CHANSERV_FUNC(cmd_emote)
6083{
6084 char *msg;
6085 assert(argc >= 2);
6086 if(channel)
6087 {
6088 /* CTCP is so annoying. */
6089 msg = unsplit_string(argv + 1, argc - 1, NULL);
6090 send_channel_message(channel, cmd->parent->bot, "\001ACTION %s\001", msg);
6091 }
1136f709 6092 else if(*argv[1] == '*' && argv[1][1] != '\0')
6093 {
6094 struct handle_info *hi;
6095 struct userNode *authed;
6096
6097 REQUIRE_PARAMS(3);
6098 msg = unsplit_string(argv + 2, argc - 2, NULL);
6099
6100 if (!(hi = get_handle_info(argv[1] + 1)))
6101 {
6102 reply("MSG_HANDLE_UNKNOWN", argv[1] + 1);
6103 return 0;
6104 }
6105
6106 for (authed = hi->users; authed; authed = authed->next_authed)
6107 send_target_message(5, authed->nick, cmd->parent->bot, "\001ACTION %s\001", msg);
6108 }
d76ed9a9
AS
6109 else if(GetUserH(argv[1]))
6110 {
6111 msg = unsplit_string(argv + 2, argc - 2, NULL);
6112 send_target_message(5, argv[1], cmd->parent->bot, "\001ACTION %s\001", msg);
6113 }
6114 else
6115 {
6116 reply("MSG_NOT_TARGET_NAME");
6117 return 0;
6118 }
6119 return 1;
6120}
6121
6122struct channelList *
6123chanserv_support_channels(void)
6124{
6125 return &chanserv_conf.support_channels;
6126}
6127
6128static CHANSERV_FUNC(cmd_expire)
6129{
6130 int channel_count = registered_channels;
6131 expire_channels(NULL);
6132 reply("CSMSG_CHANNELS_EXPIRED", channel_count - registered_channels);
6133 return 1;
6134}
6135
6136static void
6137chanserv_expire_suspension(void *data)
6138{
6139 struct suspended *suspended = data;
6140 struct chanNode *channel;
1136f709 6141 unsigned int ii;
d76ed9a9 6142
1136f709 6143 /* Update the channel registration data structure. */
d76ed9a9
AS
6144 if(!suspended->expires || (now < suspended->expires))
6145 suspended->revoked = now;
6146 channel = suspended->cData->channel;
6147 suspended->cData->channel = channel;
6148 suspended->cData->flags &= ~CHANNEL_SUSPENDED;
1136f709 6149
6150 /* If appropriate, re-join ChanServ to the channel. */
0d16e639 6151 if(!IsOffChannel(suspended->cData))
6152 {
63c95a47 6153 spamserv_cs_suspend(channel, 0, 0, NULL);
6154 ss_cs_join_channel(channel, 1);
0d16e639 6155 }
1136f709 6156
6157 /* Mark everyone currently in the channel as present. */
6158 for(ii = 0; ii < channel->members.used; ++ii)
6159 {
6160 struct userData *uData = GetChannelAccess(suspended->cData, channel->members.list[ii]->user->handle_info);
6161 if(uData)
6162 {
6163 uData->present = 1;
6164 uData->seen = now;
6165 }
6166 }
d76ed9a9
AS
6167}
6168
6169static CHANSERV_FUNC(cmd_csuspend)
6170{
6171 struct suspended *suspended;
6172 char reason[MAXLEN];
6173 time_t expiry, duration;
6174 struct userData *uData;
6175
6176 REQUIRE_PARAMS(3);
6177
6178 if(IsProtected(channel->channel_info))
6179 {
6180 reply("CSMSG_SUSPEND_NODELETE", channel->name);
6181 return 0;
6182 }
6183
6184 if(argv[1][0] == '!')
6185 argv[1]++;
6186 else if(IsSuspended(channel->channel_info))
6187 {
6188 reply("CSMSG_ALREADY_SUSPENDED", channel->name);
6189 show_suspension_info(cmd, user, channel->channel_info->suspended);
6190 return 0;
6191 }
6192
6193 if(!strcmp(argv[1], "0"))
6194 expiry = 0;
6195 else if((duration = ParseInterval(argv[1])))
6196 expiry = now + duration;
6197 else
6198 {
6199 reply("MSG_INVALID_DURATION", argv[1]);
6200 return 0;
6201 }
6202
6203 unsplit_string(argv + 2, argc - 2, reason);
6204
6205 suspended = calloc(1, sizeof(*suspended));
6206 suspended->revoked = 0;
6207 suspended->issued = now;
6208 suspended->suspender = strdup(user->handle_info->handle);
6209 suspended->expires = expiry;
6210 suspended->reason = strdup(reason);
6211 suspended->cData = channel->channel_info;
6212 suspended->previous = suspended->cData->suspended;
6213 suspended->cData->suspended = suspended;
6214
6215 if(suspended->expires)
6216 timeq_add(suspended->expires, chanserv_expire_suspension, suspended);
6217
6218 if(IsSuspended(channel->channel_info))
6219 {
6220 suspended->previous->revoked = now;
6221 if(suspended->previous->expires)
6222 timeq_del(suspended->previous->expires, chanserv_expire_suspension, suspended->previous, 0);
09a3057c 6223
6224 global_message_args(MESSAGE_RECIPIENT_OPERS | MESSAGE_RECIPIENT_HELPERS, "CSMSG_SUSPENSION_MODIFIED",
6225 channel->name, suspended->suspender);
d76ed9a9
AS
6226 }
6227 else
6228 {
6229 /* Mark all users in channel as absent. */
6230 for(uData = channel->channel_info->users; uData; uData = uData->next)
6231 {
6232 if(uData->present)
6233 {
6234 uData->seen = now;
6235 uData->present = 0;
6236 }
6237 }
6238
6239 /* Mark the channel as suspended, then part. */
6240 channel->channel_info->flags |= CHANNEL_SUSPENDED;
63c95a47 6241 spamserv_cs_suspend(channel, expiry, 1, suspended->reason);
d76ed9a9
AS
6242 DelChannelUser(chanserv, channel, suspended->reason, 0);
6243 reply("CSMSG_SUSPENDED", channel->name);
09a3057c 6244 global_message_args(MESSAGE_RECIPIENT_OPERS | MESSAGE_RECIPIENT_HELPERS, "CSMSG_SUSPENDED_BY",
6245 channel->name, suspended->suspender);
d76ed9a9
AS
6246 }
6247 return 1;
6248}
6249
6250static CHANSERV_FUNC(cmd_cunsuspend)
6251{
6252 struct suspended *suspended;
d76ed9a9
AS
6253
6254 if(!IsSuspended(channel->channel_info))
6255 {
6256 reply("CSMSG_NOT_SUSPENDED", channel->name);
6257 return 0;
6258 }
6259
6260 suspended = channel->channel_info->suspended;
6261
6262 /* Expire the suspension and join ChanServ to the channel. */
6263 timeq_del(suspended->expires, chanserv_expire_suspension, suspended, 0);
6264 chanserv_expire_suspension(suspended);
6265 reply("CSMSG_UNSUSPENDED", channel->name);
09a3057c 6266 global_message_args(MESSAGE_RECIPIENT_OPERS|MESSAGE_RECIPIENT_HELPERS, "CSMSG_UNSUSPENDED_BY",
6267 channel->name, user->handle_info->handle);
d76ed9a9
AS
6268 return 1;
6269}
6270
6271typedef struct chanservSearch
6272{
6273 char *name;
6274 char *registrar;
6275
6276 time_t unvisited;
6277 time_t registered;
6278
6279 unsigned long flags;
6280 unsigned int limit;
6281} *search_t;
6282
6283typedef void (*channel_search_func)(struct chanData *channel, void *data);
6284
6285static search_t
c092fcad 6286chanserv_search_create(struct svccmd *cmd, struct userNode *user, unsigned int argc, char *argv[])
d76ed9a9
AS
6287{
6288 search_t search;
6289 unsigned int i;
6290
6291 search = malloc(sizeof(struct chanservSearch));
6292 memset(search, 0, sizeof(*search));
6293 search->limit = 25;
6294
6295 for(i = 0; i < argc; i++)
6296 {
6297 /* Assume all criteria require arguments. */
6298 if(i == (argc - 1))
6299 {
c092fcad 6300 reply("MSG_MISSING_PARAMS", argv[i]);
d76ed9a9
AS
6301 goto fail;
6302 }
6303
6304 if(!irccasecmp(argv[i], "name"))
6305 search->name = argv[++i];
6306 else if(!irccasecmp(argv[i], "registrar"))
6307 search->registrar = argv[++i];
6308 else if(!irccasecmp(argv[i], "unvisited"))
6309 search->unvisited = ParseInterval(argv[++i]);
6310 else if(!irccasecmp(argv[i], "registered"))
6311 search->registered = ParseInterval(argv[++i]);
6312 else if(!irccasecmp(argv[i], "flags"))
6313 {
6314 i++;
6315 if(!irccasecmp(argv[i], "nodelete"))
6316 search->flags |= CHANNEL_NODELETE;
6317 else if(!irccasecmp(argv[i], "suspended"))
6318 search->flags |= CHANNEL_SUSPENDED;
1136f709 6319 else if(!irccasecmp(argv[i], "unreviewed"))
6320 search->flags |= CHANNEL_UNREVIEWED;
d76ed9a9
AS
6321 else
6322 {
c092fcad 6323 reply("CSMSG_INVALID_CFLAG", argv[i]);
d76ed9a9
AS
6324 goto fail;
6325 }
6326 }
6327 else if(!irccasecmp(argv[i], "limit"))
6328 search->limit = strtoul(argv[++i], NULL, 10);
6329 else
6330 {
c092fcad 6331 reply("MSG_INVALID_CRITERIA", argv[i]);
d76ed9a9
AS
6332 goto fail;
6333 }
6334 }
6335
6336 if(search->name && !strcmp(search->name, "*"))
6337 search->name = 0;
6338 if(search->registrar && !strcmp(search->registrar, "*"))
6339 search->registrar = 0;
6340
6341 return search;
6342 fail:
6343 free(search);
6344 return NULL;
6345}
6346
6347static int
6348chanserv_channel_match(struct chanData *channel, search_t search)
6349{
6350 const char *name = channel->channel->name;
6351 if((search->name && !match_ircglob(name, search->name)) ||
6352 (search->registrar && !channel->registrar) ||
6353 (search->registrar && !match_ircglob(channel->registrar, search->registrar)) ||
6354 (search->unvisited && (now - channel->visited) < search->unvisited) ||
6355 (search->registered && (now - channel->registered) > search->registered) ||
6356 (search->flags && ((search->flags & channel->flags) != search->flags)))
6357 return 0;
6358
6359 return 1;
6360}
6361
6362static unsigned int
6363chanserv_channel_search(search_t search, channel_search_func smf, void *data)
6364{
6365 struct chanData *channel;
6366 unsigned int matches = 0;
6367
6368 for(channel = channelList; channel && matches < search->limit; channel = channel->next)
6369 {
6370 if(!chanserv_channel_match(channel, search))
6371 continue;
6372 matches++;
6373 smf(channel, data);
6374 }
6375
6376 return matches;
6377}
6378
6379static void
6380search_count(UNUSED_ARG(struct chanData *channel), UNUSED_ARG(void *data))
6381{
6382}
6383
6384static void
6385search_print(struct chanData *channel, void *data)
6386{
6387 send_message_type(4, data, chanserv, "%s", channel->channel->name);
6388}
6389
6390static CHANSERV_FUNC(cmd_search)
6391{
6392 search_t search;
6393 unsigned int matches;
6394 channel_search_func action;
6395
6396 REQUIRE_PARAMS(3);
6397
6398 if(!irccasecmp(argv[1], "count"))
6399 action = search_count;
6400 else if(!irccasecmp(argv[1], "print"))
6401 action = search_print;
6402 else
6403 {
6404 reply("CSMSG_ACTION_INVALID", argv[1]);
6405 return 0;
6406 }
6407
c092fcad 6408 search = chanserv_search_create(cmd, user, argc - 2, argv + 2);
d76ed9a9
AS
6409 if(!search)
6410 return 0;
6411
6412 if(action == search_count)
6413 search->limit = INT_MAX;
6414
6415 if(action == search_print)
de9510bc 6416 {
cde20a3f
AS
6417 reply("CSMSG_CHANNEL_SEARCH_RESULTS");
6418 if(user->handle_info && user->handle_info->userlist_style != HI_STYLE_CLEAN)
6419 reply("CSMSG_BAR");
de9510bc 6420 }
d76ed9a9
AS
6421
6422 matches = chanserv_channel_search(search, action, user);
6423
6424 if(matches)
6425 reply("MSG_MATCH_COUNT", matches);
6426 else
6427 reply("MSG_NO_MATCHES");
6428
6429 free(search);
6430 return 1;
6431}
6432
6433static CHANSERV_FUNC(cmd_unvisited)
6434{
6435 struct chanData *cData;
6436 time_t interval = chanserv_conf.channel_expire_delay;
6437 char buffer[INTERVALLEN];
6438 unsigned int limit = 25, matches = 0;
6439
6440 if(argc > 1)
6441 {
6442 interval = ParseInterval(argv[1]);
6443 if(argc > 2)
6444 limit = atoi(argv[2]);
6445 }
6446
6447 intervalString(buffer, interval, user->handle_info);
6448 reply("CSMSG_UNVISITED_HEADER", limit, buffer);
6449
6450 for(cData = channelList; cData && matches < limit; cData = cData->next)
6451 {
6452 if((now - cData->visited) < interval)
6453 continue;
6454
6455 intervalString(buffer, now - cData->visited, user->handle_info);
6456 reply("CSMSG_UNVISITED_DATA", cData->channel->name, buffer);
6457 matches++;
6458 }
6459
6460 return 1;
6461}
6462
1136f709 6463static MODCMD_FUNC(chan_opt_unreviewed)
6464{
6465 struct chanData *cData = channel->channel_info;
6466 int value = (cData->flags & CHANNEL_UNREVIEWED) ? 1 : 0;
6467
6468 if(argc > 1)
6469 {
6470 int new_value;
6471
6472 /* The two directions can have different ACLs. */
6473 if(enabled_string(argv[1]))
6474 new_value = 1;
6475 else if(disabled_string(argv[1]))
6476 new_value = 0;
6477 else
6478 {
6479 reply("MSG_INVALID_BINARY", argv[1]);
6480 return 0;
6481 }
6482
6483 if (new_value != value)
6484 {
6485 struct svccmd *subcmd;
6486 char subcmd_name[32];
6487
6488 snprintf(subcmd_name, sizeof(subcmd_name), "%s %s", argv[0], (new_value ? "on" : "off"));
6489 subcmd = dict_find(cmd->parent->commands, subcmd_name, NULL);
6490 if(!subcmd)
6491 {
6492 reply("MSG_COMMAND_DISABLED", subcmd_name);
6493 return 0;
6494 }
6495 else if(!svccmd_can_invoke(user, cmd->parent->bot, subcmd, channel, SVCCMD_NOISY))
6496 return 0;
6497
6498 if (new_value)
6499 cData->flags |= CHANNEL_UNREVIEWED;
6500 else
6501 {
6502 free(cData->registrar);
6503 cData->registrar = strdup(user->handle_info->handle);
6504 cData->flags &= ~CHANNEL_UNREVIEWED;
6505 }
6506 value = new_value;
6507 }
6508 }
6509
6510 if(value)
6511 reply("CSMSG_SET_UNREVIEWED", user_find_message(user, "MSG_ON"));
6512 else
6513 reply("CSMSG_SET_UNREVIEWED", user_find_message(user, "MSG_OFF"));
6514 return 1;
6515}
6516
d76ed9a9
AS
6517static MODCMD_FUNC(chan_opt_defaulttopic)
6518{
6519 if(argc > 1)
6520 {
6521 char *topic;
6522
6523 if(!check_user_level(channel, user, lvlEnfTopic, 1, 0))
6524 {
6525 reply("CSMSG_TOPIC_LOCKED", channel->name);
6526 return 0;
6527 }
6528
6529 topic = unsplit_string(argv+1, argc-1, NULL);
6530
6531 free(channel->channel_info->topic);
6532 if(topic[0] == '*' && topic[1] == 0)
6533 {
6534 topic = channel->channel_info->topic = NULL;
6535 }
6536 else
6537 {
6538 topic = channel->channel_info->topic = strdup(topic);
6539 if(channel->channel_info->topic_mask
6540 && !match_ircglob(channel->channel_info->topic, channel->channel_info->topic_mask))
6541 reply("CSMSG_TOPIC_MISMATCH", channel->name);
6542 }
75ef8cdc 6543 SetChannelTopic(channel, chanserv, user, topic ? topic : "", 1);
d76ed9a9
AS
6544 }
6545
6546 if(channel->channel_info->topic)
6547 reply("CSMSG_SET_DEFAULT_TOPIC", channel->channel_info->topic);
6548 else
6549 reply("CSMSG_SET_DEFAULT_TOPIC", user_find_message(user, "MSG_NONE"));
6550 return 1;
6551}
6552
6553static MODCMD_FUNC(chan_opt_topicmask)
6554{
6555 if(argc > 1)
6556 {
6557 struct chanData *cData = channel->channel_info;
6558 char *mask;
6559
6560 if(!check_user_level(channel, user, lvlEnfTopic, 1, 0))
6561 {
6562 reply("CSMSG_TOPIC_LOCKED", channel->name);
6563 return 0;
6564 }
6565
6566 mask = unsplit_string(argv+1, argc-1, NULL);
6567
6568 if(cData->topic_mask)
6569 free(cData->topic_mask);
6570 if(mask[0] == '*' && mask[1] == 0)
6571 {
6572 cData->topic_mask = 0;
6573 }
6574 else
6575 {
6576 cData->topic_mask = strdup(mask);
6577 if(!cData->topic)
6578 reply("CSMSG_MASK_BUT_NO_TOPIC", channel->name);
6579 else if(!match_ircglob(cData->topic, cData->topic_mask))
6580 reply("CSMSG_TOPIC_MISMATCH", channel->name);
6581 }
6582 }
6583
6584 if(channel->channel_info->topic_mask)
6585 reply("CSMSG_SET_TOPICMASK", channel->channel_info->topic_mask);
6586 else
6587 reply("CSMSG_SET_TOPICMASK", user_find_message(user, "MSG_NONE"));
6588 return 1;
6589}
6590
6591int opt_greeting_common(struct userNode *user, struct svccmd *cmd, int argc, char *argv[], char *name, char **data)
6592{
6593 if(argc > 1)
6594 {
6595 char *greeting = unsplit_string(argv+1, argc-1, NULL);
6596 char *previous;
6597
6598 previous = *data;
6599 if(greeting[0] == '*' && greeting[1] == 0)
6600 *data = NULL;
6601 else
6602 {
6603 unsigned int length = strlen(greeting);
6604 if(length > chanserv_conf.greeting_length)
6605 {
6606 reply("CSMSG_GREETING_TOO_LONG", length, chanserv_conf.greeting_length);
6607 return 0;
6608 }
6609 *data = strdup(greeting);
6610 }
6611 if(previous)
6612 free(previous);
6613 }
6614
6615 if(*data)
6616 reply(name, *data);
6617 else
6618 reply(name, user_find_message(user, "MSG_NONE"));
6619 return 1;
6620}
6621
6622static MODCMD_FUNC(chan_opt_greeting)
6623{
6624 return opt_greeting_common(user, cmd, argc, argv, "CSMSG_SET_GREETING", &channel->channel_info->greeting);
6625}
6626
6627static MODCMD_FUNC(chan_opt_usergreeting)
6628{
6629 return opt_greeting_common(user, cmd, argc, argv, "CSMSG_SET_USERGREETING", &channel->channel_info->user_greeting);
6630}
6631
8b9e7d45 6632static MODCMD_FUNC(chan_opt_maxsetinfo)
6633{
6634 unsigned int charmax;
6635
6636 if(argc > 1) {
6637 charmax = atoi(argv[1]);
6638 if ((charmax > 0) && (charmax < chanserv_conf.max_userinfo_length))
6639 channel->channel_info->maxsetinfo = charmax;
6640 }
6641
6642 reply("CSMSG_SET_MAXSETINFO", channel->channel_info->maxsetinfo);
6643 return 1;
6644}
6645
d76ed9a9
AS
6646static MODCMD_FUNC(chan_opt_modes)
6647{
6648 struct mod_chanmode *new_modes;
6649 char modes[MODELEN];
6650
6651 if(argc > 1)
6652 {
08895577 6653 if (checkDefCon(DEFCON_NO_MODE_CHANGE) && !IsOper(user)) {
6654 reply("CSMSG_DEFCON_NO_MODE_CHANGE");
6655 return 0;
6656 }
6657
d76ed9a9
AS
6658 if(!check_user_level(channel, user, lvlEnfModes, 1, 0))
6659 {
6660 reply("CSMSG_NO_ACCESS");
6661 return 0;
6662 }
6663 if(argv[1][0] == '*' && argv[1][1] == 0)
6664 {
6665 memset(&channel->channel_info->modes, 0, sizeof(channel->channel_info->modes));
6666 }
2f61d1d7 6667 else if(!(new_modes = mod_chanmode_parse(channel, argv+1, argc-1,MCP_KEY_FREE|MCP_REGISTERED, 0)))
d76ed9a9
AS
6668 {
6669 reply("CSMSG_INVALID_MODE_LOCK", unsplit_string(argv+1, argc-1, NULL));
6670 return 0;
6671 }
6672 else if(new_modes->argc > 1)
6673 {
6674 reply("CSMSG_INVALID_MODE_LOCK", unsplit_string(argv+1, argc-1, NULL));
6675 mod_chanmode_free(new_modes);
6676 return 0;
6677 }
6678 else
6679 {
6680 channel->channel_info->modes = *new_modes;
6681 modcmd_chanmode_announce(new_modes);
6682 mod_chanmode_free(new_modes);
6683 }
6684 }
6685
6686 mod_chanmode_format(&channel->channel_info->modes, modes);
6687 if(modes[0])
6688 reply("CSMSG_SET_MODES", modes);
6689 else
6690 reply("CSMSG_SET_MODES", user_find_message(user, "MSG_NONE"));
6691 return 1;
6692}
6693
6694#define CHANNEL_BINARY_OPTION(MSG, FLAG) return channel_binary_option(MSG, FLAG, CSFUNC_ARGS);
6695static int
6696channel_binary_option(char *name, unsigned long mask, struct userNode *user, struct chanNode *channel, int argc, char *argv[], struct svccmd *cmd)
6697{
6698 struct chanData *cData = channel->channel_info;
6699 int value;
6700
6701 if(argc > 1)
6702 {
6703 /* Set flag according to value. */
6704 if(enabled_string(argv[1]))
6705 {
6706 cData->flags |= mask;
6707 value = 1;
6708 }
6709 else if(disabled_string(argv[1]))
6710 {
6711 cData->flags &= ~mask;
6712 value = 0;
6713 }
6714 else
6715 {
6716 reply("MSG_INVALID_BINARY", argv[1]);
6717 return 0;
6718 }
6719 }
6720 else
6721 {
6722 /* Find current option value. */
6723 value = (cData->flags & mask) ? 1 : 0;
6724 }
6725
6726 if(value)
6727 reply(name, user_find_message(user, "MSG_ON"));
6728 else
6729 reply(name, user_find_message(user, "MSG_OFF"));
6730 return 1;
6731}
6732
6733static MODCMD_FUNC(chan_opt_nodelete)
6734{
6735 if((argc > 1) && (!IsOper(user) || !user->handle_info || (user->handle_info->opserv_level < chanserv_conf.nodelete_level)))
6736 {
6737 reply("MSG_SETTING_PRIVILEGED", argv[0]);
6738 return 0;
6739 }
6740
6741 CHANNEL_BINARY_OPTION("CSMSG_SET_NODELETE", CHANNEL_NODELETE);
6742}
6743
6744static MODCMD_FUNC(chan_opt_dynlimit)
6745{
97e51197 6746 struct mod_chanmode change;
6747
6748 if (argc > 1) {
6749 if (disabled_string(argv[1])) {
6750 mod_chanmode_init(&change);
6751 change.modes_clear |= MODE_LIMIT;
6752 mod_chanmode_announce(chanserv, channel, &change);
6753 }
6754 }
6755
d76ed9a9
AS
6756 CHANNEL_BINARY_OPTION("CSMSG_SET_DYNLIMIT", CHANNEL_DYNAMIC_LIMIT);
6757}
6758
6759static MODCMD_FUNC(chan_opt_offchannel)
6760{
6761 struct chanData *cData = channel->channel_info;
6762 int value;
6763
6764 if(argc > 1)
6765 {
6766 /* Set flag according to value. */
6767 if(enabled_string(argv[1]))
6768 {
6769 if(!IsOffChannel(cData))
6770 DelChannelUser(chanserv, channel, "Going off-channel.", 0);
6771 cData->flags |= CHANNEL_OFFCHANNEL;
6772 value = 1;
6773 }
6774 else if(disabled_string(argv[1]))
6775 {
6776 if(IsOffChannel(cData))
6777 {
6778 struct mod_chanmode change;
6779 mod_chanmode_init(&change);
6780 change.argc = 1;
6781 change.args[0].mode = MODE_CHANOP;
a32da4c7 6782 change.args[0].u.member = AddChannelUser(chanserv, channel);
d76ed9a9
AS
6783 mod_chanmode_announce(chanserv, channel, &change);
6784 }
6785 cData->flags &= ~CHANNEL_OFFCHANNEL;
6786 value = 0;
6787 }
6788 else
6789 {
6790 reply("MSG_INVALID_BINARY", argv[1]);
6791 return 0;
6792 }
6793 }
6794 else
6795 {
6796 /* Find current option value. */
6797 value = (cData->flags & CHANNEL_OFFCHANNEL) ? 1 : 0;
6798 }
6799
6800 if(value)
6801 reply("CSMSG_SET_OFFCHANNEL", user_find_message(user, "MSG_ON"));
6802 else
6803 reply("CSMSG_SET_OFFCHANNEL", user_find_message(user, "MSG_OFF"));
6804 return 1;
6805}
6806
6807static MODCMD_FUNC(chan_opt_defaults)
6808{
6809 struct userData *uData;
6810 struct chanData *cData;
6811 const char *confirm;
6812 enum levelOption lvlOpt;
6813 enum charOption chOpt;
6814
6815 cData = channel->channel_info;
6816 uData = GetChannelUser(cData, user->handle_info);
6817 if(!uData || (uData->access < UL_OWNER))
6818 {
6819 reply("CSMSG_OWNER_DEFAULTS", channel->name);
6820 return 0;
6821 }
6822 confirm = make_confirmation_string(uData);
6823 if((argc < 2) || strcmp(argv[1], confirm))
6824 {
6825 reply("CSMSG_CONFIRM_DEFAULTS", channel->name, confirm);
6826 return 0;
6827 }
1136f709 6828 cData->flags = (CHANNEL_DEFAULT_FLAGS & ~CHANNEL_PRESERVED_FLAGS)
6829 | (cData->flags & CHANNEL_PRESERVED_FLAGS);
d76ed9a9
AS
6830 cData->modes = chanserv_conf.default_modes;
6831 for(lvlOpt = 0; lvlOpt < NUM_LEVEL_OPTIONS; ++lvlOpt)
6832 cData->lvlOpts[lvlOpt] = levelOptions[lvlOpt].default_value;
6833 for(chOpt = 0; chOpt < NUM_CHAR_OPTIONS; ++chOpt)
6834 cData->chOpts[chOpt] = charOptions[chOpt].default_value;
6835 reply("CSMSG_SETTINGS_DEFAULTED", channel->name);
6836 return 1;
6837}
6838
6839static int
6840channel_level_option(enum levelOption option, struct userNode *user, struct chanNode *channel, int argc, char *argv[], struct svccmd *cmd)
6841{
6842 struct chanData *cData = channel->channel_info;
6843 struct userData *uData;
6844 unsigned short value;
6845
6846 if(argc > 1)
6847 {
6848 if(!check_user_level(channel, user, option, 1, 1))
6849 {
6850 reply("CSMSG_CANNOT_SET");
6851 return 0;
6852 }
6853 value = user_level_from_name(argv[1], UL_OWNER+1);
6854 if(!value && strcmp(argv[1], "0"))
6855 {
6856 reply("CSMSG_INVALID_ACCESS", argv[1]);
6857 return 0;
6858 }
6859 uData = GetChannelUser(cData, user->handle_info);
6860 if(!uData || ((uData->access < UL_OWNER) && (value > uData->access)))
6861 {
6862 reply("CSMSG_BAD_SETLEVEL");
6863 return 0;
6864 }
6865 switch(option)
6866 {
4b6129c0
AS
6867 case lvlSetters:
6868 /* This test only applies to owners, since non-owners
6869 * trying to set an option to above their level get caught
6870 * by the CSMSG_BAD_SETLEVEL test above.
6871 */
6872 if(value > uData->access)
6873 {
6874 reply("CSMSG_BAD_SETTERS");
6875 return 0;
6876 }
6877 break;
6878 default:
6879 break;
d76ed9a9
AS
6880 }
6881 cData->lvlOpts[option] = value;
6882 }
6883 reply(levelOptions[option].format_name, cData->lvlOpts[option]);
6884 return argc > 1;
6885}
6886
6887static MODCMD_FUNC(chan_opt_enfops)
6888{
6889 return channel_level_option(lvlEnfOps, CSFUNC_ARGS);
6890}
6891
55342ce8 6892static MODCMD_FUNC(chan_opt_enfhalfops)
6893{
6894 return channel_level_option(lvlEnfHalfOps, CSFUNC_ARGS);
6895}
d76ed9a9
AS
6896static MODCMD_FUNC(chan_opt_enfmodes)
6897{
6898 return channel_level_option(lvlEnfModes, CSFUNC_ARGS);
6899}
6900
6901static MODCMD_FUNC(chan_opt_enftopic)
6902{
6903 return channel_level_option(lvlEnfTopic, CSFUNC_ARGS);
6904}
6905
6906static MODCMD_FUNC(chan_opt_pubcmd)
6907{
6908 return channel_level_option(lvlPubCmd, CSFUNC_ARGS);
6909}
6910
6911static MODCMD_FUNC(chan_opt_setters)
6912{
6913 return channel_level_option(lvlSetters, CSFUNC_ARGS);
6914}
6915
d76ed9a9
AS
6916static MODCMD_FUNC(chan_opt_userinfo)
6917{
6918 return channel_level_option(lvlUserInfo, CSFUNC_ARGS);
6919}
6920
d76ed9a9
AS
6921static MODCMD_FUNC(chan_opt_topicsnarf)
6922{
6923 return channel_level_option(lvlTopicSnarf, CSFUNC_ARGS);
6924}
6925
6926static MODCMD_FUNC(chan_opt_inviteme)
6927{
6928 return channel_level_option(lvlInviteMe, CSFUNC_ARGS);
6929}
6930
31f23f13
AS
6931/* TODO: Make look like this when no args are
6932 * given:
6933 * -X3- -------------------------------
6934 * -X3- BanTimeout: Bans are removed:
6935 * -X3- ----- * indicates current -----
6936 * -X3- 0: [*] Never.
6937 * -X3- 1: [ ] After 10 minutes.
6938 * -X3- 2: [ ] After 2 hours.
6939 * -X3- 3: [ ] After 4 hours.
6940 * -X3- 4: [ ] After 24 hours.
6941 * -X3- 5: [ ] After one week.
6942 * -X3- ------------- End -------------
6943 */
d76ed9a9
AS
6944static int
6945channel_multiple_option(enum charOption option, struct userNode *user, struct chanNode *channel, int argc, char *argv[], struct svccmd *cmd)
6946{
6947 struct chanData *cData = channel->channel_info;
1136f709 6948 int count = charOptions[option].count, idx;
d76ed9a9
AS
6949
6950 if(argc > 1)
6951 {
1136f709 6952 idx = atoi(argv[1]);
d76ed9a9 6953
1136f709 6954 if(!isdigit(argv[1][0]) || (idx < 0) || (idx >= count))
d76ed9a9 6955 {
1136f709 6956 reply("CSMSG_INVALID_NUMERIC", idx);
d76ed9a9 6957 /* Show possible values. */
1136f709 6958 for(idx = 0; idx < count; idx++)
6959 reply(charOptions[option].format_name, idx, user_find_message(user, charOptions[option].values[idx].format_name));
d76ed9a9
AS
6960 return 0;
6961 }
6962
1136f709 6963 cData->chOpts[option] = charOptions[option].values[idx].value;
d76ed9a9
AS
6964 }
6965 else
6966 {
6967 /* Find current option value. */
6968 find_value:
1136f709 6969 for(idx = 0;
6970 (idx < count) && (cData->chOpts[option] != charOptions[option].values[idx].value);
6971 idx++);
6972 if(idx == count)
d76ed9a9
AS
6973 {
6974 /* Somehow, the option value is corrupt; reset it to the default. */
6975 cData->chOpts[option] = charOptions[option].default_value;
6976 goto find_value;
6977 }
6978 }
6979
1136f709 6980 reply(charOptions[option].format_name, idx, user_find_message(user, charOptions[option].values[idx].format_name));
d76ed9a9
AS
6981 return 1;
6982}
6983
4b6129c0 6984static MODCMD_FUNC(chan_opt_automode)
c8ca69a0 6985{
1136f709 6986 if(check_user_level(channel, user, lvlInviteMe, 1, 0))
6987 {
6988 reply("CSMSG_LOW_CHANNEL_ACCESS", channel->name);
6989 }
4b6129c0 6990 return channel_multiple_option(chAutomode, CSFUNC_ARGS);
c8ca69a0
AS
6991}
6992
d76ed9a9
AS
6993static MODCMD_FUNC(chan_opt_protect)
6994{
6995 return channel_multiple_option(chProtect, CSFUNC_ARGS);
6996}
6997
6998static MODCMD_FUNC(chan_opt_toys)
6999{
7000 return channel_multiple_option(chToys, CSFUNC_ARGS);
7001}
7002
7003static MODCMD_FUNC(chan_opt_ctcpreaction)
7004{
7005 return channel_multiple_option(chCTCPReaction, CSFUNC_ARGS);
7006}
7007
31f23f13
AS
7008static MODCMD_FUNC(chan_opt_bantimeout)
7009{
7010 return channel_multiple_option(chBanTimeout, CSFUNC_ARGS);
7011}
7012
d76ed9a9
AS
7013static MODCMD_FUNC(chan_opt_topicrefresh)
7014{
7015 return channel_multiple_option(chTopicRefresh, CSFUNC_ARGS);
7016}
7017
7637f48f 7018static MODCMD_FUNC(chan_opt_resync)
7019{
7020 return channel_multiple_option(chResync, CSFUNC_ARGS);
7021}
7022
d76ed9a9
AS
7023static struct svccmd_list set_shows_list;
7024
7025static void
7026handle_svccmd_unbind(struct svccmd *target) {
7027 unsigned int ii;
7028 for(ii=0; ii<set_shows_list.used; ++ii)
7029 if(target == set_shows_list.list[ii])
7030 set_shows_list.used = 0;
7031}
7032
7033static CHANSERV_FUNC(cmd_set)
7034{
7035 struct svccmd *subcmd;
7036 char buf[MAXLEN];
7037 unsigned int ii;
7038
7039 /* Check if we need to (re-)initialize set_shows_list. */
7040 if(!set_shows_list.used)
7041 {
7042 if(!set_shows_list.size)
7043 {
7044 set_shows_list.size = chanserv_conf.set_shows->used;
7045 set_shows_list.list = calloc(set_shows_list.size, sizeof(set_shows_list.list[0]));
7046 }
7047 for(ii = 0; ii < chanserv_conf.set_shows->used; ii++)
7048 {
7049 const char *name = chanserv_conf.set_shows->list[ii];
7050 sprintf(buf, "%s %s", argv[0], name);
7051 subcmd = dict_find(cmd->parent->commands, buf, NULL);
7052 if(!subcmd)
7053 {
7054 log_module(CS_LOG, LOG_ERROR, "Unable to find set option \"%s\".", name);
7055 continue;
7056 }
7057 svccmd_list_append(&set_shows_list, subcmd);
7058 }
7059 }
7060
7061 if(argc < 2)
7062 {
cde20a3f
AS
7063 reply("CSMSG_CHANNEL_OPTIONS", channel->name);
7064 if(user->handle_info && user->handle_info->userlist_style != HI_STYLE_CLEAN)
7065 reply("CSMSG_BAR");
d76ed9a9
AS
7066 for(ii = 0; ii < set_shows_list.used; ii++)
7067 {
7068 subcmd = set_shows_list.list[ii];
7069 subcmd->command->func(user, channel, 1, argv+1, subcmd);
7070 }
de9510bc 7071 reply("CSMSG_CHANNEL_OPTIONS_END");
d76ed9a9
AS
7072 return 1;
7073 }
7074
7075 sprintf(buf, "%s %s", argv[0], argv[1]);
7076 subcmd = dict_find(cmd->parent->commands, buf, NULL);
7077 if(!subcmd)
7078 {
7079 reply("CSMSG_INVALID_OPTION", argv[1], argv[0]);
7080 return 0;
7081 }
7082 if((argc > 2) && !check_user_level(channel, user, lvlSetters, 1, 0))
7083 {
7084 reply("CSMSG_NO_ACCESS");
7085 return 0;
7086 }
7087
1136f709 7088 argv[0] = "";
7089 argv[1] = buf;
d76ed9a9
AS
7090 return subcmd->command->func(user, channel, argc - 1, argv + 1, subcmd);
7091}
7092
7093static int
7094user_binary_option(char *name, unsigned long mask, struct userNode *user, struct chanNode *channel, int argc, char *argv[], struct svccmd *cmd)
7095{
7096 struct userData *uData;
7097
7098 uData = GetChannelAccess(channel->channel_info, user->handle_info);
7099 if(!uData)
7100 {
7101 reply("CSMSG_NOT_USER", channel->name);
7102 return 0;
7103 }
7104
7105 if(argc < 2)
7106 {
7107 /* Just show current option value. */
7108 }
7109 else if(enabled_string(argv[1]))
7110 {
7111 uData->flags |= mask;
7112 }
7113 else if(disabled_string(argv[1]))
7114 {
7115 uData->flags &= ~mask;
7116 }
7117 else
7118 {
7119 reply("MSG_INVALID_BINARY", argv[1]);
7120 return 0;
7121 }
7122
7123 reply(name, user_find_message(user, (uData->flags & mask) ? "MSG_ON" : "MSG_OFF"));
7124 return 1;
7125}
7126
c8ca69a0 7127static MODCMD_FUNC(user_opt_autoop)
d76ed9a9
AS
7128{
7129 struct userData *uData;
7130
7131 uData = GetChannelAccess(channel->channel_info, user->handle_info);
7132 if(!uData)
7133 {
7134 reply("CSMSG_NOT_USER", channel->name);
7135 return 0;
7136 }
90e75ffd 7137 if(uData->access < UL_HALFOP /*channel->channel_info->lvlOpts[lvlGiveOps]*/)
c8ca69a0 7138 return user_binary_option("CSMSG_USET_AUTOVOICE", USER_AUTO_OP, CSFUNC_ARGS);
d76ed9a9 7139 else
c8ca69a0 7140 return user_binary_option("CSMSG_USET_AUTOOP", USER_AUTO_OP, CSFUNC_ARGS);
d76ed9a9
AS
7141}
7142
7143static MODCMD_FUNC(user_opt_autoinvite)
7144{
1136f709 7145 if((argc > 1) && !check_user_level(channel, user, lvlInviteMe, 1, 0))
7146 {
7147 reply("CSMSG_LOW_CHANNEL_ACCESS", channel->name);
7148 }
d76ed9a9
AS
7149 return user_binary_option("CSMSG_USET_AUTOINVITE", USER_AUTO_INVITE, CSFUNC_ARGS);
7150}
7151
cd25f2e9 7152static MODCMD_FUNC(user_opt_autojoin)
7153{
7154 return user_binary_option("CSMSG_USET_AUTOJOIN", USER_AUTO_JOIN, CSFUNC_ARGS);
7155}
7156
d76ed9a9
AS
7157static MODCMD_FUNC(user_opt_info)
7158{
7159 struct userData *uData;
7160 char *infoline;
7161
7162 uData = GetChannelAccess(channel->channel_info, user->handle_info);
7163
7164 if(!uData)
7165 {
7166 /* If they got past the command restrictions (which require access)
7167 * but fail this test, we have some fool with security override on.
7168 */
7169 reply("CSMSG_NOT_USER", channel->name);
7170 return 0;
7171 }
7172
7173 if(argc > 1)
7174 {
7175 size_t bp;
7176 infoline = unsplit_string(argv + 1, argc - 1, NULL);
8b9e7d45 7177 if(strlen(infoline) > channel->channel_info->maxsetinfo)
d76ed9a9 7178 {
8b9e7d45 7179 reply("CSMSG_INFOLINE_TOO_LONG", channel->channel_info->maxsetinfo);
d76ed9a9
AS
7180 return 0;
7181 }
7182 bp = strcspn(infoline, "\001");
7183 if(infoline[bp])
7184 {
7185 reply("CSMSG_BAD_INFOLINE", infoline[bp]);
7186 return 0;
7187 }
7188 if(uData->info)
7189 free(uData->info);
7190 if(infoline[0] == '*' && infoline[1] == 0)
7191 uData->info = NULL;
7192 else
7193 uData->info = strdup(infoline);
7194 }
7195 if(uData->info)
7196 reply("CSMSG_USET_INFO", uData->info);
7197 else
7198 reply("CSMSG_USET_INFO", user_find_message(user, "MSG_NONE"));
7199 return 1;
7200}
7201
7202struct svccmd_list uset_shows_list;
7203
7204static CHANSERV_FUNC(cmd_uset)
7205{
7206 struct svccmd *subcmd;
7207 char buf[MAXLEN];
7208 unsigned int ii;
7209
7210 /* Check if we need to (re-)initialize uset_shows_list. */
7211 if(!uset_shows_list.used)
7212 {
7213 char *options[] =
7214 {
cd25f2e9 7215 "AutoOp", "AutoInvite", "AutoJoin", "Info"
d76ed9a9
AS
7216 };
7217
7218 if(!uset_shows_list.size)
7219 {
7220 uset_shows_list.size = ArrayLength(options);
7221 uset_shows_list.list = calloc(uset_shows_list.size, sizeof(uset_shows_list.list[0]));
7222 }
7223 for(ii = 0; ii < ArrayLength(options); ii++)
7224 {
7225 const char *name = options[ii];
7226 sprintf(buf, "%s %s", argv[0], name);
7227 subcmd = dict_find(cmd->parent->commands, buf, NULL);
7228 if(!subcmd)
7229 {
7230 log_module(CS_LOG, LOG_ERROR, "Unable to find uset option %s.", name);
7231 continue;
7232 }
7233 svccmd_list_append(&uset_shows_list, subcmd);
7234 }
7235 }
7236
7237 if(argc < 2)
7238 {
7239 /* Do this so options are presented in a consistent order. */
7240 reply("CSMSG_USER_OPTIONS");
7241 for(ii = 0; ii < uset_shows_list.used; ii++)
7242 uset_shows_list.list[ii]->command->func(user, channel, 1, argv+1, uset_shows_list.list[ii]);
7243 return 1;
7244 }
7245
7246 sprintf(buf, "%s %s", argv[0], argv[1]);
7247 subcmd = dict_find(cmd->parent->commands, buf, NULL);
7248 if(!subcmd)
7249 {
7250 reply("CSMSG_INVALID_OPTION", argv[1], argv[0]);
7251 return 0;
7252 }
7253
7254 return subcmd->command->func(user, channel, argc - 1, argv + 1, subcmd);
7255}
7256
7257static CHANSERV_FUNC(cmd_giveownership)
7258{
7259 struct handle_info *new_owner_hi;
1136f709 7260 struct userData *new_owner;
7261 struct userData *curr_user;
7262 struct userData *invoker;
d76ed9a9
AS
7263 struct chanData *cData = channel->channel_info;
7264 struct do_not_register *dnr;
82f37c08 7265 struct giveownership *giveownership;
1136f709 7266 const char *confirm;
82f37c08 7267 unsigned int force, override;
7268 unsigned short co_access, new_owner_old_access;
09a3057c 7269 char transfer_reason[MAXLEN];
d76ed9a9
AS
7270
7271 REQUIRE_PARAMS(2);
7272 curr_user = GetChannelAccess(cData, user->handle_info);
7273 force = IsHelping(user) && (argc > 2) && !irccasecmp(argv[2], "force");
82f37c08 7274
7275 struct userData *uData = _GetChannelUser(channel->channel_info, user->handle_info, 1, 0);
7276 override = ((cmd->effective_flags & MODCMD_REQUIRE_CHANUSER)
7277 && (uData->access > 500)
7278 && (!(uData = _GetChannelUser(channel->channel_info, user->handle_info, 0, 0))
7279 || uData->access < 500));
7280
7281
d76ed9a9
AS
7282 if(!curr_user || (curr_user->access != UL_OWNER))
7283 {
7284 struct userData *owner = NULL;
7285 for(curr_user = channel->channel_info->users;
7286 curr_user;
7287 curr_user = curr_user->next)
7288 {
7289 if(curr_user->access != UL_OWNER)
7290 continue;
7291 if(owner)
7292 {
7293 reply("CSMSG_MULTIPLE_OWNERS", channel->name);
7294 return 0;
7295 }
7296 owner = curr_user;
7297 }
7298 curr_user = owner;
7299 }
1136f709 7300 else if(!force && (now < (time_t)(cData->ownerTransfer + chanserv_conf.giveownership_period)))
a32da4c7 7301 {
7302 char delay[INTERVALLEN];
7303 intervalString(delay, cData->ownerTransfer + chanserv_conf.giveownership_period - now, user->handle_info);
7304 reply("CSMSG_TRANSFER_WAIT", delay, channel->name);
7305 return 0;
7306 }
5678501c 7307 if (!curr_user) {
7308 reply("CSMSG_NO_OWNER", channel->name);
7309 return 0;
7310 }
d76ed9a9
AS
7311 if(!(new_owner_hi = modcmd_get_handle_info(user, argv[1])))
7312 return 0;
7313 if(new_owner_hi == user->handle_info)
7314 {
7315 reply("CSMSG_NO_TRANSFER_SELF");
7316 return 0;
7317 }
7318 new_owner = GetChannelAccess(cData, new_owner_hi);
7319 if(!new_owner)
7320 {
0d16e639 7321 if(force)
7322 {
1136f709 7323 new_owner = add_channel_user(cData, new_owner_hi, UL_OWNER - 1, 0, NULL, 0);
0d16e639 7324 }
7325 else
7326 {
7327 reply("CSMSG_NO_CHAN_USER", new_owner_hi->handle, channel->name);
7328 return 0;
7329 }
d76ed9a9
AS
7330 }
7331 if((chanserv_get_owned_count(new_owner_hi) >= chanserv_conf.max_owned) && !force)
7332 {
7333 reply("CSMSG_OWN_TOO_MANY", new_owner_hi->handle, chanserv_conf.max_owned);
7334 return 0;
7335 }
7336 if((dnr = chanserv_is_dnr(NULL, new_owner_hi)) && !force) {
7337 if(!IsHelping(user))
7338 reply("CSMSG_DNR_ACCOUNT", new_owner_hi->handle);
7339 else
1117fc5a 7340 chanserv_show_dnrs(user, cmd, NULL, new_owner_hi->handle);
d76ed9a9
AS
7341 return 0;
7342 }
82f37c08 7343
1136f709 7344 invoker = GetChannelUser(cData, user->handle_info);
7345 if(invoker->access <= UL_OWNER)
7346 {
7347 confirm = make_confirmation_string(curr_user);
7348 if((argc < 3) || strcmp(argv[2], confirm))
7349 {
7350 reply("CSMSG_CONFIRM_GIVEOWNERSHIP", new_owner_hi->handle, confirm);
7351 return 0;
7352 }
7353 }
7354
82f37c08 7355 new_owner_old_access = new_owner->access;
d76ed9a9
AS
7356 if(new_owner->access >= UL_COOWNER)
7357 co_access = new_owner->access;
7358 else
7359 co_access = UL_COOWNER;
7360 new_owner->access = UL_OWNER;
7361 if(curr_user)
7362 curr_user->access = co_access;
a32da4c7 7363 cData->ownerTransfer = now;
82f37c08 7364
7365 giveownership = calloc(1, sizeof(*giveownership));
7366 giveownership->issued = now;
8ce9df05
AS
7367 giveownership->old_owner = strdup(curr_user->handle->handle);
7368 giveownership->target = strdup(new_owner_hi->handle);
82f37c08 7369 giveownership->target_access = new_owner_old_access;
7370 if(override)
7371 {
7372 if(argc > (2 + force))
7373 {
7374 unsplit_string(argv + 2 + force, argc - 2 - force, transfer_reason);
7375 giveownership->reason = strdup(transfer_reason);
7376 }
7377 giveownership->staff_issuer = strdup(user->handle_info->handle);
7378 }
7379
7380 giveownership->previous = channel->channel_info->giveownership;
7381 channel->channel_info->giveownership = giveownership;
7382
d76ed9a9 7383 reply("CSMSG_OWNERSHIP_GIVEN", channel->name, new_owner_hi->handle);
09a3057c 7384 global_message_args(MESSAGE_RECIPIENT_OPERS | MESSAGE_RECIPIENT_HELPERS, "CSMSG_OWNERSHIP_TRANSFERRED",
7385 channel->name, new_owner_hi->handle, user->handle_info->handle);
d76ed9a9
AS
7386 return 1;
7387}
7388
b10abdb2 7389static void
7390chanserv_expire_user_suspension(void *data)
7391{
7392 struct userData *target = data;
7393
7394 target->expires = 0;
7395 target->flags &= ~USER_SUSPENDED;
7396}
7397
d76ed9a9
AS
7398static CHANSERV_FUNC(cmd_suspend)
7399{
7400 struct handle_info *hi;
1136f709 7401 struct userData *actor, *real_actor, *target;
7402 unsigned int override = 0;
b10abdb2 7403 time_t expiry;
d76ed9a9 7404
b10abdb2 7405 REQUIRE_PARAMS(3);
d76ed9a9 7406 if(!(hi = modcmd_get_handle_info(user, argv[1]))) return 0;
1136f709 7407 actor = GetChannelUser(channel->channel_info, user->handle_info);
7408 real_actor = GetChannelAccess(channel->channel_info, user->handle_info);
d76ed9a9
AS
7409 if(!(target = GetTrueChannelAccess(channel->channel_info, hi)))
7410 {
7411 reply("CSMSG_NO_CHAN_USER", hi->handle, channel->name);
7412 return 0;
7413 }
1136f709 7414 if(target->access >= actor->access)
d76ed9a9
AS
7415 {
7416 reply("MSG_USER_OUTRANKED", hi->handle);
7417 return 0;
7418 }
7419 if(target->flags & USER_SUSPENDED)
7420 {
7421 reply("CSMSG_ALREADY_SUSPENDED", hi->handle);
7422 return 0;
7423 }
7424 if(target->present)
7425 {
7426 target->present = 0;
7427 target->seen = now;
7428 }
b10abdb2 7429 if(!strcmp(argv[2], "0"))
7430 expiry = 0;
7431 else
7432 {
7433 unsigned int duration;
7434 if(!(duration = ParseInterval(argv[2])))
7435 {
7436 reply("MSG_INVALID_DURATION", argv[2]);
7437 return 0;
7438 }
7439 expiry = now + duration;
7440 }
7441
7442 target->expires = expiry;
7443
7444 if(target->expires)
7445 timeq_add(target->expires, chanserv_expire_user_suspension, target);
7446
1136f709 7447 if(!real_actor || target->access >= real_actor->access)
7448 override = CMD_LOG_OVERRIDE;
d76ed9a9
AS
7449 target->flags |= USER_SUSPENDED;
7450 reply("CSMSG_USER_SUSPENDED", hi->handle, channel->name);
1136f709 7451 return 1 | override;
d76ed9a9
AS
7452}
7453
7454static CHANSERV_FUNC(cmd_unsuspend)
7455{
7456 struct handle_info *hi;
1136f709 7457 struct userData *actor = NULL, *real_actor = NULL, *target;
7458 unsigned int override = 0;
d76ed9a9
AS
7459
7460 REQUIRE_PARAMS(2);
7461 if(!(hi = modcmd_get_handle_info(user, argv[1]))) return 0;
1136f709 7462 real_actor = GetChannelAccess(channel->channel_info, user->handle_info);
d76ed9a9
AS
7463 if(!(target = GetTrueChannelAccess(channel->channel_info, hi)))
7464 {
7465 reply("CSMSG_NO_CHAN_USER", hi->handle, channel->name);
7466 return 0;
7467 }
1136f709 7468 if(target->access >= actor->access)
d76ed9a9
AS
7469 {
7470 reply("MSG_USER_OUTRANKED", hi->handle);
7471 return 0;
7472 }
7473 if(!(target->flags & USER_SUSPENDED))
7474 {
7475 reply("CSMSG_NOT_SUSPENDED", hi->handle);
7476 return 0;
7477 }
1136f709 7478 if(!real_actor || target->access >= real_actor->access)
7479 override = CMD_LOG_OVERRIDE;
d76ed9a9 7480 target->flags &= ~USER_SUSPENDED;
a32da4c7 7481 scan_user_presence(target, NULL);
b10abdb2 7482 timeq_del(target->expires, chanserv_expire_user_suspension, target, 0);
d76ed9a9 7483 reply("CSMSG_USER_UNSUSPENDED", hi->handle, channel->name);
1136f709 7484 return 1 | override;
d76ed9a9
AS
7485}
7486
7487static MODCMD_FUNC(cmd_deleteme)
7488{
7489 struct handle_info *hi;
7490 struct userData *target;
7491 const char *confirm_string;
7492 unsigned short access;
7493 char *channel_name;
7494
7495 hi = user->handle_info;
7496 if(!(target = GetTrueChannelAccess(channel->channel_info, hi)))
7497 {
7498 reply("CSMSG_NO_CHAN_USER", hi->handle, channel->name);
7499 return 0;
7500 }
7501 if(target->access == UL_OWNER)
7502 {
7503 reply("CSMSG_NO_OWNER_DELETEME", channel->name);
7504 return 0;
7505 }
7506 confirm_string = make_confirmation_string(target);
7507 if((argc < 2) || strcmp(argv[1], confirm_string))
7508 {
7509 reply("CSMSG_CONFIRM_DELETEME", confirm_string);
7510 return 0;
7511 }
7512 access = target->access;
7513 channel_name = strdup(channel->name);
7514 del_channel_user(target, 1);
7515 reply("CSMSG_DELETED_YOU", access, channel_name);
7516 free(channel_name);
7517 return 1;
7518}
7519
7520static void
7521chanserv_refresh_topics(UNUSED_ARG(void *data))
7522{
1136f709 7523 unsigned int refresh_num = (now - self->link_time) / chanserv_conf.refresh_period;
d76ed9a9
AS
7524 struct chanData *cData;
7525 char opt;
7526
7527 for(cData = channelList; cData; cData = cData->next)
7528 {
7529 if(IsSuspended(cData))
7530 continue;
7531 opt = cData->chOpts[chTopicRefresh];
7532 if(opt == 'n')
7533 continue;
7534 if((refresh_num - cData->last_refresh) < (unsigned int)(1 << (opt - '1')))
7535 continue;
7536 if(cData->topic)
7fda2b52 7537 SetChannelTopic(cData->channel, chanserv, chanserv, cData->topic, 1);
d76ed9a9
AS
7538 cData->last_refresh = refresh_num;
7539 }
7540 timeq_add(now + chanserv_conf.refresh_period, chanserv_refresh_topics, NULL);
7541}
7542
7637f48f 7543static void
7544chanserv_auto_resync(UNUSED_ARG(void *data))
7545{
1136f709 7546 unsigned int refresh_num = (now - self->link_time) / chanserv_conf.refresh_period;
7637f48f 7547 struct chanData *cData;
7548 char opt;
7549
7550 for(cData = channelList; cData; cData = cData->next)
7551 {
7552 if(IsSuspended(cData)) continue;
7553 opt = cData->chOpts[chResync];
7554 if(opt == 'n') continue;
7555 if((refresh_num - cData->last_resync) < (unsigned int)(1 << (opt - '1'))) continue;
7556 resync_channel(cData->channel);
7557 cData->last_resync = refresh_num;
7558 }
7559 timeq_add(now + chanserv_conf.refresh_period, chanserv_auto_resync, NULL);
7560}
7561
d76ed9a9
AS
7562static CHANSERV_FUNC(cmd_unf)
7563{
7564 if(channel)
7565 {
7566 char response[MAXLEN];
7567 const char *fmt = user_find_message(user, "CSMSG_UNF_RESPONSE");
7568 sprintf(response, "\ 2%s\ 2: %s", user->nick, fmt);
7569 irc_privmsg(cmd->parent->bot, channel->name, response);
7570 }
7571 else
7572 reply("CSMSG_UNF_RESPONSE");
7573 return 1;
7574}
7575
7576static CHANSERV_FUNC(cmd_ping)
7577{
7578 if(channel)
7579 {
7580 char response[MAXLEN];
7581 const char *fmt = user_find_message(user, "CSMSG_PING_RESPONSE");
7582 sprintf(response, "\ 2%s\ 2: %s", user->nick, fmt);
7583 irc_privmsg(cmd->parent->bot, channel->name, response);
7584 }
7585 else
7586 reply("CSMSG_PING_RESPONSE");
7587 return 1;
7588}
7589
7590static CHANSERV_FUNC(cmd_wut)
7591{
7592 if(channel)
7593 {
7594 char response[MAXLEN];
7595 const char *fmt = user_find_message(user, "CSMSG_WUT_RESPONSE");
7596 sprintf(response, "\ 2%s\ 2: %s", user->nick, fmt);
7597 irc_privmsg(cmd->parent->bot, channel->name, response);
7598 }
7599 else
7600 reply("CSMSG_WUT_RESPONSE");
7601 return 1;
7602}
7603
87708af4 7604static CHANSERV_FUNC(cmd_roulette)
7605{
b404335b 7606 if(channel) {
7607 struct chanData *cData = channel->channel_info;
87708af4 7608
b404335b 7609 if (cData) {
7610 if (cData->roulette_chamber) {
fba880b8 7611 DelUser(user, chanserv, 1, "BANG - Don't stuff bullets into a loaded gun");
b404335b 7612 return 1;
7613 }
7614
7615 send_target_message(1, channel->name, cmd->parent->bot, "CSMSG_ROULETTE_LOADS");
7616 cData->roulette_chamber = 1 + rand() % 6;
7617 }
7618 }
7619
7620 return 1;
7621}
7622static CHANSERV_FUNC(cmd_shoot)
7623{
7624 if(channel) {
7625 struct chanData *cData = channel->channel_info;
7626
7627 if (cData->roulette_chamber <= 0) {
a6fa0035
MB
7628 struct service *service;
7629 if ((service = service_find(chanserv->nick))) {
7630 reply("CSMSG_ROULETTE_NEW", service->trigger);
7631 }
87708af4 7632 return 1;
7633 }
7634
b404335b 7635 cData->roulette_chamber--;
7636
7637 if (cData->roulette_chamber == 0) {
7638 reply("CSMSG_ROULETTE_BANG");
7639 reply("CSMSG_ROULETTE_BETTER_LUCK", user->nick);
fba880b8 7640 DelUser(user, chanserv, 1, "BANG!!!!");
b404335b 7641 } else
7642 reply("CSMSG_ROULETTE_CLICK");
87708af4 7643 }
b404335b 7644
87708af4 7645 return 1;
7646}
b404335b 7647
7648static void
7649chanserv_remove_abuse(void *data)
87708af4 7650{
c64a32cb
AS
7651 char *remnick = data;
7652 struct userNode *user;
7653 /* sometimes the clone was killed and maybe even the user took their nick back
7654 * (ie, an oper) so dont kill them here after all unless they are local. */
7655 if( (user = GetUserH(remnick)) )
7656 if(IsLocal(user) )
7657 DelUser(user, NULL, 1, "");
b404335b 7658}
87708af4 7659
e03ec3dc 7660int lamepart(struct userNode *nick) {
7661 struct modeNode *mn;
7662 unsigned int count, n;
7663
7664 for (n=count=0; n<nick->channels.used; n++) {
7665 mn = nick->channels.list[n];
7666 irc_svspart(chanserv, nick, mn->channel);
7667 }
7668
7669 return 0;
7670}
7671
b404335b 7672static CHANSERV_FUNC(cmd_spin)
7673{
7674 if(!channel)
87708af4 7675 return 1;
b404335b 7676
4cb36ef0 7677 int type = 0, lamep = 1;
e03ec3dc 7678 char *tstr;
7679
7680 tstr = conf_get_data("server/type", RECDB_QSTRING);
7681 if(tstr) {
7682 type = atoi(tstr);
7683 if (type > 6)
7684 lamep = 0;
7685 }
7686
b404335b 7687
7688 send_target_message(1, channel->name, chanserv, "CSMSG_SPIN_WHEEL1", user->nick);
7689 send_target_message(1, channel->name, chanserv, "CSMSG_SPIN_WHEEL2");
7690 send_target_message(1, channel->name, chanserv, "CSMSG_SPIN_WHEEL3");
7691
5e6460e4
AS
7692 if(chanserv_conf.wheel->used < 1) {
7693 /* wheel actions not defined! eek */
7694 return 1;
7695 }
7696
7697 const char *wheel = chanserv_conf.wheel->list[ (int) ( (chanserv_conf.wheel->used) * (rand() / (RAND_MAX + 1.0)) ) ];
7698 if(!wheel && *wheel)
7699 return 1;
7700
92f63dd4 7701/* enable this to be able to manually specify a result for testing:
b08a5f8b 7702 log_module(MAIN_LOG, LOG_DEBUG,"Testing wheel randomness: %s\n", wheel);
5e6460e4
AS
7703 if(argc > 1) {
7704 wheel = argv[1];
7705 }
b08a5f8b 7706*/
92f63dd4 7707
5e6460e4
AS
7708 /* connection reset by peer */
7709 if (!strcasecmp(wheel, "peer")) {
7710 send_target_message(1, channel->name, chanserv, "CSMSG_SPIN_PEER");
e91c6c1e 7711 if (type < 7)
7712 irc_kill(chanserv, user, "Connection reset by peer");
7713 else
5e6460e4 7714 irc_svsquit(chanserv, user, "Connection reset by peer");
b404335b 7715 }
5e6460e4
AS
7716 /* part all channels */
7717 else if (!strcasecmp(wheel, "partall")) {
7718 send_target_message(1, channel->name, chanserv, "CSMSG_SPIN_PARTALL");
e03ec3dc 7719 if (lamep)
7720 lamepart(user);
7721 else
7722 sputsock("%s SJ %s 0 "FMT_TIME_T, self->numeric, user->numeric, now);
87708af4 7723 }
5e6460e4
AS
7724 /* random time gline */
7725 else if (!strcasecmp(wheel, "gline")) {
7726 char target[IRC_NTOP_MAX_SIZE + 3];
b404335b 7727 int wtime = 120 + rand() % 600;
87708af4 7728
5e6460e4
AS
7729 strcpy(target, "*@");
7730 strcat(target, user->hostname);
7731 send_target_message(1, channel->name, chanserv, "CSMSG_SPIN_GLINE");
87708af4 7732
1136f709 7733 gline_add(chanserv->nick, target, wtime, "Reward for spinning the wheel of misfortune!", now, now, 1, 0);
8deb31d3 7734// irc_kill(chanserv, user, "Reward for spinning the wheel of misfortune!");
b404335b 7735 }
5e6460e4
AS
7736 /* random shun */
7737 else if (!strcasecmp(wheel, "shun")) {
7738 char target[IRC_NTOP_MAX_SIZE + 3];
b404335b 7739 int wtime = 120 + rand() % 600;
87708af4 7740
5e6460e4
AS
7741 strcpy(target, "*@");
7742 strcat(target, user->hostname);
7743 send_target_message(1, channel->name, chanserv, "CSMSG_SPIN_SHUN");
b404335b 7744
1136f709 7745 shun_add(chanserv->nick, target, wtime, "Reward for spinning the wheel of misfortune!", now, now, 1);
b404335b 7746 }
5e6460e4
AS
7747 /* absolutely nothing */
7748 else if (!strcasecmp(wheel, "nothing")) {
7749 send_target_message(1, channel->name, chanserv, "CSMSG_SPIN_NOTHING");
b404335b 7750 }
5e6460e4
AS
7751 /* join random chans and part em several times */
7752 else if (!strcasecmp(wheel, "randjoin")) {
b404335b 7753 int complete = 0;
7754 int rndchans = 0;
7755 int chango = 0;
7756 int roundz0r = 0;
7757
5e6460e4 7758 send_target_message(1, channel->name, chanserv, "CSMSG_SPIN_RANDJOIN");
b404335b 7759 while(complete != 1) {
7760 if (rndchans != 15) {
7761 chango = 120 + rand() % 600;
a837dcf5 7762 sputsock("%s SJ %s #%d "FMT_TIME_T, self->numeric, user->numeric, chango, now);
b404335b 7763 rndchans++;
7764 } else {
7765 if (roundz0r != 1) {
e03ec3dc 7766 if (lamep)
7767 lamepart(user);
7768 else
7769 sputsock("%s SJ %s 0 "FMT_TIME_T, self->numeric, user->numeric, now);
b404335b 7770 roundz0r = 1;
7771 rndchans = 0;
7772 } else {
e03ec3dc 7773 if (lamep)
7774 lamepart(user);
7775 else
7776 sputsock("%s SJ %s 0 "FMT_TIME_T, self->numeric, user->numeric, now);
7777 complete = 1;
b404335b 7778 }
7779 }
7780 }
7781 }
5e6460e4
AS
7782 /* abuse line added to /whois */
7783 else if (!strcasecmp(wheel, "abusewhois")) {
7784 send_target_message(1, channel->name, chanserv, "CSMSG_SPIN_ABUSEWHOIS");
b404335b 7785 irc_swhois(chanserv, user, "is being defecated on by services");
7786 }
5e6460e4
AS
7787 /* kick from each channel your in */
7788 else if (!strcasecmp(wheel, "kickall")) {
b404335b 7789 unsigned int count, n;
7790 struct modeNode *mn;
7791
5e6460e4 7792 send_target_message(1, channel->name, chanserv, "CSMSG_SPIN_KICKALL");
b404335b 7793
7794 for (n=count=0; n<user->channels.used; n++) {
7795 mn = user->channels.list[n];
7796 irc_kick(chanserv, user, mn->channel, "Reward for spinning the wheel of misfortune!");
7797 }
7798 }
5e6460e4
AS
7799 /* random nick change */
7800 else if (!strcasecmp(wheel, "nickchange")) {
7801 send_target_message(1, channel->name, chanserv, "CSMSG_SPIN_NICKCHANGE");
b404335b 7802
7803 char *oldnick = NULL;
7804 char *oldident = NULL;
7805 char *oldhost = NULL;
7806 char abusednick[NICKLEN] = "";
5e6460e4 7807 int abusednum = 1 + (int) (10000.0 * (rand() / (RAND_MAX + 1.0)));
b404335b 7808 struct userNode *clone;
7809
7810 oldnick = strdup(user->nick);
7811 oldident = strdup(user->ident);
7812 oldhost = strdup(user->hostname);
7813
5e6460e4 7814 //snprintf(abusednick, NICKLEN, "Abused%d", abusednum+(1 + rand() % 120));
b404335b 7815 while (1) {
b404335b 7816 snprintf(abusednick, NICKLEN, "Abused%d", abusednum+(1 + rand() % 120));
5e6460e4
AS
7817 log_module(MAIN_LOG, LOG_DEBUG, "Abused Nick: %s, Client Nick: %s", abusednick, user->nick);
7818 if(!GetUserH(abusednick))
b404335b 7819 break;
7820 }
7821
cf8bedff 7822 SVSNickChange(user, abusednick);
b404335b 7823 irc_svsnick(chanserv, user, abusednick);
1136f709 7824 clone = AddLocalUser(oldnick, oldident, oldhost, "I got abused by the wheel of misfortune :D", "+i");
c64a32cb 7825 timeq_add(now + 300, chanserv_remove_abuse, clone->nick);
b404335b 7826 }
5e6460e4
AS
7827 /* kill */
7828 else if (!strcasecmp(wheel, "kill")) {
7829 send_target_message(1, channel->name, chanserv, "CSMSG_SPIN_KILL");
b404335b 7830
8deb31d3
AS
7831 DelUser(user, chanserv, 1, "Reward for spinning the wheel of misfortune!");
7832 //irc_kill(chanserv, user, "Reward for spinning the wheel of misfortune!");
b404335b 7833 }
5e6460e4
AS
7834 /* service ignore */
7835 else if (!strcasecmp(wheel, "svsignore")) {
b404335b 7836 int gagged, ignoretime = 0;
5e6460e4 7837 char target[IRC_NTOP_MAX_SIZE + 13];
b404335b 7838
5e6460e4
AS
7839 if(IsOper(user)) {
7840 /* we cant gag opers, so just verbally abuse them */
7841 send_target_message(1, channel->name, chanserv, "CSMSG_SPIN_SVSIGNORE_OPER");
7842 return 1;
7843 }
7844 send_target_message(1, channel->name, chanserv, "CSMSG_SPIN_SVSIGNORE");
b404335b 7845
5e6460e4
AS
7846 strcpy(target, "*!*@");
7847 strcat(target, user->hostname);
b404335b 7848 ignoretime = now + (1 + rand() % 120);
7849
7850 gagged = gag_create(target, "wheelofabuse", "Reward for spinning the wheel of misfortune!", ignoretime);
7851 }
5e6460e4
AS
7852 /* kick and ban from each channel your in */
7853 else if (!strcasecmp(wheel, "kickbanall")) {
b404335b 7854 unsigned int count, n;
7855 struct modeNode *mn;
5e6460e4 7856 //char ban[IRC_NTOP_MAX_SIZE + 1];
b404335b 7857
5e6460e4 7858 send_target_message(1, channel->name, chanserv, "CSMSG_SPIN_KICKBANALL");
b404335b 7859
5e6460e4 7860 //snprintf(ban, sizeof(ban), "*!*@%s", user->hostname);
b404335b 7861 for (n=count=0; n<user->channels.used; n++) {
5e6460e4 7862 struct mod_chanmode *change;
97e51197 7863/* struct banData *bData; */
5e6460e4 7864 unsigned int exists;
97e51197 7865/* int duration = 300; */
5e6460e4 7866 char *ban;
92f63dd4 7867
5e6460e4 7868 ban = generate_hostmask(user, GENMASK_STRICT_HOST|GENMASK_ANY_IDENT|GENMASK_USENICK);
92f63dd4 7869
5e6460e4 7870 log_module(MAIN_LOG, LOG_DEBUG, "Generated ban %s", ban);
b404335b 7871 mn = user->channels.list[n];
5e6460e4
AS
7872 if(mn->channel->banlist.used >= MAXBANS) {
7873 reply("CSMSG_BANLIST_FULL", mn->channel->name);
7874 free(ban);
7875 continue;
7876 }
7877
5a3261ed 7878/* bData = add_channel_ban(mn->channel->channel_info, ban, chanserv->nick, now, now, now + duration, "Reward for spinning the wheel of misfortune!"); */
92f63dd4 7879
7880 change = mod_chanmode_alloc(1);
5e6460e4
AS
7881 change->args[0].mode = MODE_REMOVE|MODE_CHANOP|MODE_HALFOP|MODE_VOICE;
7882 change->args[0].u.member = GetUserMode(mn->channel, user);
7883 change->argc = 1;
92f63dd4 7884
5e6460e4
AS
7885 mod_chanmode_announce(chanserv, mn->channel, change);
7886 mod_chanmode_free(change);
92f63dd4 7887
7888 exists = ChannelBanExists(mn->channel, ban);
7889 if(!exists) {
7890 change = mod_chanmode_alloc(1);
7891 change->args[0].mode = MODE_BAN;
7892 change->args[0].u.hostmask = ban;
7893 change->argc = 1;
7894 mod_chanmode_announce(chanserv, mn->channel, change);
7895 mod_chanmode_free(change);
7896 }
7897
7898 if(exists) {
5e6460e4
AS
7899 reply("CSMSG_REDUNDANT_BAN", ban, mn->channel->name);
7900 free(ban);
7901 }
7902
b404335b 7903 irc_kick(chanserv, user, mn->channel, "Reward for spinning the wheel of misfortune!");
7904 }
7905 }
5e6460e4
AS
7906 else {
7907 send_target_message(1, channel->name, chanserv, "CSMSG_SPIN_UNKNOWN", wheel);
7908 }
b404335b 7909
5e6460e4 7910 return 1;
87708af4 7911}
7912
240a3274 7913#ifdef lame8ball
d76ed9a9
AS
7914static CHANSERV_FUNC(cmd_8ball)
7915{
7916 unsigned int i, j, accum;
7917 const char *resp;
7918
7919 REQUIRE_PARAMS(2);
7920 accum = 0;
7921 for(i=1; i<argc; i++)
7922 for(j=0; argv[i][j]; j++)
7923 accum = (accum << 5) - accum + toupper(argv[i][j]);
7924 resp = chanserv_conf.eightball->list[accum % chanserv_conf.eightball->used];
7925 if(channel)
7926 {
7927 char response[MAXLEN];
7928 sprintf(response, "\ 2%s\ 2: %s", user->nick, resp);
7929 irc_privmsg(cmd->parent->bot, channel->name, response);
7930 }
7931 else
7932 send_message_type(4, user, cmd->parent->bot, "%s", resp);
7933 return 1;
7934}
7935
240a3274
AS
7936#else /* Use cool 8ball instead */
7937
7938void eightball(char *outcome, int method, unsigned int seed)
7939{
7940 int answer = 0;
7941
7942#define NUMOFCOLORS 18
7943 char ballcolors[50][50] = {"blue", "red", "green", "yellow",
7944 "white", "black", "grey", "brown",
7945 "yellow", "pink", "purple", "orange", "teal", "burgandy",
7946 "fuchsia","turquoise","magenta", "cyan"};
7947#define NUMOFLOCATIONS 50
7948 char balllocations[50][55] = {
7949 "Locke's house", "Oregon", "California", "Indiana", "Canada",
7950 "Russia", "Japan", "Florida", "the Bahamas", "Hiroshima",
7951 "the Caribbean", "the Everglades", "your head", "your pants", "your school",
7952 "the Statue of Liberty", "Mt. Fugi", "your mother's house", "IRC", "OSU",
7953 "Locke's cat", "the closet", "the washroom", "the lake", "Spain",
7954 "the bathtub", "the toilet", "the sewer", "a horse", "Jupiter",
7955 "Uranus", "Pluto", "a dark place", "your undies", "your shirt",
7956 "your bra", "your hair", "your bed", "the couch", "the wall",
7957 "Reed", "here --> [X]", "your brain", "Italy", "the Netherlands",
7958 "Mars", "my hardware", "the bar", "Neverland Ranch", "Germany" };
7959#define NUMOFPREPS 15
7960 char ballpreps[50][50] = {
7961 "Near", "Somewhere near", "In", "In", "In",
7962 "In", "Hiding in", "Under", "Next to", "Over",
7963 "Crying in", "Right beside", "Nowhere near", "North of", "Trying to find"};
7964#define NUMOFNUMS 34
7965 char ballnums[50][50] = {
7966 "A hundred", "A thousand", "A few", "42",
7967 "About 1", "About 2", "About 3", "About 4", "About 5", "About 6", "About 7", "About 8", "About 9", "About 10",
7968 "1", "2", "3", "4", "5", "6", "7", "8", "9", "Ten",
7969 "1", "2", "3", "4", "5", "6", "7", "8", "9", "Ten",
7970 };
7971#define NUMOFMULTS 8
7972 char ballmults[50][50] = { " million", " or so", " thousand", "", " or less", " or more", "", ""};
7973
7974 /* Method:
7975 * 0: normal (Not used in x3)
7976 * 1: color
7977 * 2: where is
7978 * 3: how many
7979 */
7980
7981 srand(seed);
7982 if (method == 1) /* A Color */
7983 {
7984 char tmp[MAXLEN];
7985
7986 answer = (rand() % 12); /* Make sure this is the # of entries */
7987 switch(answer)
7988 {
7989 case 0: strcpy(tmp, "Very bright %s, I'd say.");
7990 break;
7991 case 1: strcpy(tmp, "Sort of a light %s color.");
7992 break;
7993 case 2: strcpy(tmp, "Dark and dreary %s.");
7994 break;
7995 case 3: strcpy(tmp, "Quite a pale shade of %s.");
7996 break;
7997 case 4: strcpy(tmp, "A gross kind of mucky %s.");
7998 break;
7999 case 5: strcpy(tmp, "Brilliant whiteish %s.");
338a82b5
AS
8000 break;
8001 case 6: case 7: case 8: case 9: strcpy(tmp, "%s.");
8002 break;
8003 case 10: strcpy(tmp, "Solid %s.");
8004 break;
8005 case 11: strcpy(tmp, "Transparent %s.");
8006 break;
240a3274
AS
8007 default: strcpy(outcome, "An invalid random number was generated.");
8008 return;
8009 }
8010 sprintf(outcome, tmp, ballcolors[rand() % NUMOFCOLORS]);
8011 return;
8012 }
8013 else if (method == 2) /* Location */
8014 {
8015 sprintf(outcome, "%s %s.", ballpreps[rand() % NUMOFPREPS], balllocations[rand() % NUMOFLOCATIONS]);
8016 }
8017 else if (method == 3) /* Number of ___ */
8018 {
8019 sprintf(outcome, "%s%s.", ballnums[rand() % NUMOFNUMS], ballmults[rand() % NUMOFMULTS]);
8020 }
8021 else
8022 {
8023 //Debug(DBGWARNING, "Error in 8ball.");
8024 }
8025 return;
8026}
8027
8028static CHANSERV_FUNC(cmd_8ball)
8029{
8030 char *word1, *word2, *word3;
8031 static char eb[MAXLEN];
8032 unsigned int accum, i, j;
8033
132f7859 8034 REQUIRE_PARAMS(2);
240a3274
AS
8035 accum = 0;
8036 for(i=1; i<argc; i++)
8037 for(j=0; argv[i][j]; j++)
8038 accum = (accum << 5) - accum + toupper(argv[i][j]);
8039
8040 accum += time(NULL)/3600;
8041 word1 = argv[1];
8042 word2 = argc>2?argv[2]:"";
8043 word3 = argc>3?argv[3]:"";
8044
8045/*** COLOR *****/
8046 if((word2) && strcasecmp(word1, "what") == 0 && strcasecmp(word2, "color") == 0)
8047 eightball(eb, 1, accum);
8048 else if((word3) && strcasecmp(word1, "what's") == 0 && strcasecmp(word2, "the") == 0 && strcasecmp(word3, "color") == 0)
8049 eightball(eb, 1, accum);
8050 else if((word3) && strcasecmp(word1, "whats") == 0 && strcasecmp(word2, "the") == 0 && strcasecmp(word3, "color") == 0)
8051 eightball(eb, 1, accum);
8052/*** LOCATION *****/
8053 else if(
8054 (
8055 word2 &&
8056 (
8057 (strcasecmp(word1, "where") == 0) &&
8058 (strcasecmp(word2, "is") == 0)
8059 )
8060 ) ||
8061 (
8062 strcasecmp(word1, "where's") == 0
8063 )
8064 )
8065 eightball(eb, 2, accum);
8066/*** NUMBER *****/
8067 else if((word2) && strcasecmp(word1, "how") == 0 && strcasecmp(word2, "many") == 0)
8068 eightball(eb, 3, accum);
8069/*** GENERIC *****/
8070 else
8071 {
8072 /* Generic 8ball question.. so pull from x3.conf srvx style */
8073 const char *resp;
8074
8075 resp = chanserv_conf.eightball->list[accum % chanserv_conf.eightball->used];
8076 if(channel)
8077 {
8078 char response[MAXLEN];
8079 sprintf(response, "\002%s\002: %s", user->nick, resp);
8080 irc_privmsg(cmd->parent->bot, channel->name, response);
8081 }
8082 else
8083 send_message_type(4, user, cmd->parent->bot, "%s", resp);
8084 return 1;
8085 }
8086
8087 if(channel)
8088 {
8089 char response[MAXLEN];
8090 sprintf(response, "\002%s\002: %s", user->nick, eb);
8091 irc_privmsg(cmd->parent->bot, channel->name, response);
8092 }
8093 else
8094 send_message_type(4, user, cmd->parent->bot, "%s", eb);
8095 return 1;
8096}
8097#endif
8098
d76ed9a9
AS
8099static CHANSERV_FUNC(cmd_d)
8100{
8101 unsigned long sides, count, modifier, ii, total;
8102 char response[MAXLEN], *sep;
8103 const char *fmt;
8104
8105 REQUIRE_PARAMS(2);
8106 if((count = strtoul(argv[1], &sep, 10)) < 1)
8107 goto no_dice;
8108 if(sep[0] == 0)
8109 {
8110 if(count == 1)
8111 goto no_dice;
8112 sides = count;
8113 count = 1;
8114 modifier = 0;
8115 }
8116 else if(((sep[0] == 'd') || (sep[0] == 'D')) && isdigit(sep[1])
8117 && (sides = strtoul(sep+1, &sep, 10)) > 1)
8118 {
8119 if(sep[0] == 0)
8120 modifier = 0;
8121 else if((sep[0] == '-') && isdigit(sep[1]))
8122 modifier = strtoul(sep, NULL, 10);
8123 else if((sep[0] == '+') && isdigit(sep[1]))
8124 modifier = strtoul(sep+1, NULL, 10);
8125 else
8126 goto no_dice;
8127 }
8128 else
8129 {
8130 no_dice:
8131 reply("CSMSG_BAD_DIE_FORMAT", argv[1]);
8132 return 0;
8133 }
8134 if(count > 10)
8135 {
8136 reply("CSMSG_BAD_DICE_COUNT", count, 10);
8137 return 0;
8138 }
8139 for(total = ii = 0; ii < count; ++ii)
8140 total += (rand() % sides) + 1;
8141 total += modifier;
8142
8143 if((count > 1) || modifier)
8144 {
8145 fmt = user_find_message(user, "CSMSG_DICE_ROLL");
8146 sprintf(response, fmt, total, count, sides, modifier);
8147 }
8148 else
8149 {
8150 fmt = user_find_message(user, "CSMSG_DIE_ROLL");
8151 sprintf(response, fmt, total, sides);
8152 }
8153 if(channel)
8154 send_channel_message(channel, cmd->parent->bot, "$b%s$b: %s", user->nick, response);
8155 else
8156 send_message_type(4, user, cmd->parent->bot, "%s", response);
8157 return 1;
8158}
8159
8160static CHANSERV_FUNC(cmd_huggle)
8161{
8162 /* CTCP must be via PRIVMSG, never notice */
8163 if(channel)
8164 send_target_message(1, channel->name, cmd->parent->bot, "CSMSG_HUGGLES_HIM", user->nick);
8165 else
8166 send_target_message(1, user->nick, cmd->parent->bot, "CSMSG_HUGGLES_YOU");
8167 return 1;
8168}
8169
e5a8f7cd 8170static CHANSERV_FUNC(cmd_calc)
8171{
8172 char response[MAXLEN];
8173
8174 REQUIRE_PARAMS(2);
8175 do_math(response, unsplit_string(argv + 1, argc - 1, NULL));
8176
8177 if(channel)
d8cf9c21 8178 send_channel_message(channel, cmd->parent->bot, "$b%s$b: %s", user->nick, response);
e5a8f7cd 8179 else
8180 send_message_type(4, user, cmd->parent->bot, "%s", response);
8181 return 1;
8182}
8183
33741441
AS
8184static CHANSERV_FUNC(cmd_reply)
8185{
8186
8187 REQUIRE_PARAMS(2);
8188 unsplit_string(argv + 1, argc - 1, NULL);
8189
8190 if(channel)
8191 send_channel_message(channel, cmd->parent->bot, "$b%s$b: %s", user->nick, unsplit_string(argv + 1, argc - 1, NULL));
8192 else
8193 send_message_type(4, user, cmd->parent->bot, "%s", unsplit_string(argv + 1, argc - 1, NULL));
8194 return 1;
8195}
8196
d76ed9a9
AS
8197static void
8198chanserv_adjust_limit(void *data)
8199{
8200 struct mod_chanmode change;
8201 struct chanData *cData = data;
8202 struct chanNode *channel = cData->channel;
8203 unsigned int limit;
8204
8205 if(IsSuspended(cData))
8206 return;
8207
8208 cData->limitAdjusted = now;
8209 limit = channel->members.used + chanserv_conf.adjust_threshold + 5;
8210 if(cData->modes.modes_set & MODE_LIMIT)
8211 {
8212 if(limit > cData->modes.new_limit)
8213 limit = cData->modes.new_limit;
8214 else if(limit == cData->modes.new_limit)
8215 return;
8216 }
8217
8218 mod_chanmode_init(&change);
8219 change.modes_set = MODE_LIMIT;
8220 change.new_limit = limit;
8221 mod_chanmode_announce(chanserv, channel, &change);
8222}
8223
8224static void
8225handle_new_channel(struct chanNode *channel)
8226{
8227 struct chanData *cData;
8228
8229 if(!(cData = channel->channel_info))
8230 return;
8231
8232 if(cData->modes.modes_set || cData->modes.modes_clear)
8233 mod_chanmode_announce(chanserv, cData->channel, &cData->modes);
8234
8235 if(self->uplink && !self->uplink->burst && channel->channel_info->topic)
7fda2b52 8236 SetChannelTopic(channel, chanserv, chanserv, channel->channel_info->topic, 1);
d76ed9a9
AS
8237}
8238
5aa400d2 8239int
8240trace_check_bans(struct userNode *user, struct chanNode *chan)
8241{
5aa400d2 8242 struct banData *bData;
27fa6acf 8243 struct mod_chanmode *change;
5aa400d2 8244
27fa6acf 8245 change = find_matching_bans(&chan->banlist, user, NULL);
8246 if (change)
8247 return 1;
8248
27fa6acf 8249 /* lamer list */
1e993296 8250 if (chan->channel_info) {
8251 for(bData = chan->channel_info->bans; bData; bData = bData->next) {
5aa400d2 8252
1e993296 8253 if(!user_matches_glob(user, bData->mask, MATCH_USENICK))
8254 continue;
8255
8256 if(bData)
8257 return 1;
5aa400d2 8258 }
8259 }
8260
8261 return 0;
8262}
8263
c99dcaf6 8264int
1b6b5e2f 8265check_bans(struct userNode *user, const char *channel)
c99dcaf6 8266{
8267 struct chanNode *chan;
8268 struct mod_chanmode change;
8269 struct chanData *cData;
8270 struct banData *bData;
8271
8272 if (!(chan = GetChannel(channel)))
8273 return 0;
8274
8275 if(!(cData = chan->channel_info))
8276 return 0;
8277
1b6b5e2f 8278 mod_chanmode_init(&change);
8279 change.argc = 1;
8280
c99dcaf6 8281 if(chan->banlist.used < MAXBANS)
8282 {
8283 /* Not joining through a ban. */
8284 for(bData = cData->bans;
8285 bData && !user_matches_glob(user, bData->mask, MATCH_USENICK);
8286 bData = bData->next);
8287
8288 if(bData)
8289 {
8290 char kick_reason[MAXLEN];
8291 sprintf(kick_reason, "(%s) %s", bData->owner, bData->reason);
8292
8293 bData->triggered = now;
8294 if(bData != cData->bans)
8295 {
8296 /* Shuffle the ban to the head of the list. */
8297 if(bData->next)
8298 bData->next->prev = bData->prev;
8299 if(bData->prev)
8300 bData->prev->next = bData->next;
8301
8302 bData->prev = NULL;
8303 bData->next = cData->bans;
8304
8305 if(cData->bans)
8306 cData->bans->prev = bData;
8307
8308 cData->bans = bData;
8309 }
8310
8311 change.args[0].mode = MODE_BAN;
8312 change.args[0].u.hostmask = bData->mask;
8313 mod_chanmode_announce(chanserv, chan, &change);
8314 KickChannelUser(user, chan, chanserv, kick_reason);
8315 return 1;
8316 }
8317 }
8318 return 0;
8319}
8320
5aef35cf
AS
8321int
8322channel_user_is_exempt(struct userNode *user, struct chanNode *channel)
8323{
8324 unsigned int ii;
8325 for(ii = 0; ii < channel->exemptlist.used; ii++)
8326 {
8327 if(user_matches_glob(user, channel->exemptlist.list[ii]->exempt, MATCH_USENICK))
8328 return true;
8329 }
8330 return false;
8331}
8332
c99dcaf6 8333
d76ed9a9
AS
8334/* Welcome to my worst nightmare. Warning: Read (or modify)
8335 the code below at your own risk. */
8336static int
8337handle_join(struct modeNode *mNode)
8338{
8339 struct mod_chanmode change;
8340 struct userNode *user = mNode->user;
8341 struct chanNode *channel = mNode->channel;
8342 struct chanData *cData;
8343 struct userData *uData = NULL;
8344 struct banData *bData;
8345 struct handle_info *handle;
8346 unsigned int modes = 0, info = 0;
8347 char *greeting;
8348
ed7ac86b 8349 if(IsLocal(user) || !channel || !channel->channel_info || IsSuspended(channel->channel_info))
d76ed9a9
AS
8350 return 0;
8351
8352 cData = channel->channel_info;
8353 if(channel->members.used > cData->max)
8354 cData->max = channel->members.used;
8355
12673a59 8356#ifdef notdef
2f61d1d7 8357 /* Check for bans. If they're joining through a ban, one of two
8358 * cases applies:
8359 * 1: Join during a netburst, by riding the break. Kick them
8360 * unless they have ops or voice in the channel.
8361 * 2: They're allowed to join through the ban (an invite in
8362 * ircu2.10, or a +e on Hybrid, or something).
8363 * If they're not joining through a ban, and the banlist is not
8364 * full, see if they're on the banlist for the channel. If so,
8365 * kickban them.
8366 */
8367 if(user->uplink->burst && !mNode->modes)
8368 {
8369 unsigned int ii;
8370 for(ii = 0; ii < channel->banlist.used; ii++)
8371 {
8372 if(user_matches_glob(user, channel->banlist.list[ii]->ban, MATCH_USENICK))
8373 {
8374 /* Riding a netburst. Naughty. */
8375 KickChannelUser(user, channel, chanserv, "User from far side of netsplit should have been banned - bye.");
8376 return 1;
8377 }
8378 }
8379 }
12673a59 8380#endif
2f61d1d7 8381
ff5f1ab2
AS
8382 if(user->handle_info)
8383 {
8384 handle = user->handle_info;
8385 if(handle)
8386 {
8387 uData = GetTrueChannelAccess(cData, handle);
8388 }
8389 }
8390
8391
d76ed9a9
AS
8392 mod_chanmode_init(&change);
8393 change.argc = 1;
d76ed9a9 8394
ff5f1ab2
AS
8395 /* TODO: maybe only people above inviteme level? -Rubin */
8396 /* We don't kick people with access */
5aef35cf 8397 if(!uData && !channel_user_is_exempt(user, channel))
ff5f1ab2
AS
8398 {
8399 if(channel->banlist.used < MAXBANS)
d76ed9a9 8400 {
ff5f1ab2
AS
8401 /* Not joining through a ban. */
8402 for(bData = cData->bans;
8403 bData && !user_matches_glob(user, bData->mask, MATCH_USENICK);
8404 bData = bData->next);
d76ed9a9 8405
ff5f1ab2 8406 if(bData)
d76ed9a9 8407 {
ff5f1ab2
AS
8408 char kick_reason[MAXLEN];
8409 sprintf(kick_reason, "(%s) %s", bData->owner, bData->reason);
d76ed9a9 8410
ff5f1ab2
AS
8411 bData->triggered = now;
8412 if(bData != cData->bans)
8413 {
8414 /* Shuffle the ban to the head of the list. */
8415 if(bData->next)
8416 bData->next->prev = bData->prev;
8417 if(bData->prev)
8418 bData->prev->next = bData->next;
8419
8420 bData->prev = NULL;
8421 bData->next = cData->bans;
8422
8423 if(cData->bans)
8424 cData->bans->prev = bData;
8425 cData->bans = bData;
8426 }
8427
8428 change.args[0].mode = MODE_BAN;
8429 change.args[0].u.hostmask = bData->mask;
8430 mod_chanmode_announce(chanserv, channel, &change);
8431 KickChannelUser(user, channel, chanserv, kick_reason);
8432 return 1;
8433 }
d76ed9a9
AS
8434 }
8435 }
8436
8437 /* ChanServ will not modify the limits in join-flooded channels.
8438 It will also skip DynLimit processing when the user (or srvx)
8439 is bursting in, because there are likely more incoming. */
8440 if((cData->flags & CHANNEL_DYNAMIC_LIMIT)
8441 && !user->uplink->burst
8442 && !channel->join_flooded
8443 && (channel->limit - channel->members.used) < chanserv_conf.adjust_threshold)
8444 {
8445 /* The user count has begun "bumping" into the channel limit,
8446 so set a timer to raise the limit a bit. Any previous
8447 timers are removed so three incoming users within the delay
8448 results in one limit change, not three. */
8449
8450 timeq_del(0, chanserv_adjust_limit, cData, TIMEQ_IGNORE_WHEN);
8451 timeq_add(now + chanserv_conf.adjust_delay, chanserv_adjust_limit, cData);
8452 }
8453
4b6129c0
AS
8454 /* Give automodes exept during join-floods */
8455 if(!channel->join_flooded)
d76ed9a9 8456 {
4b6129c0
AS
8457 if(cData->chOpts[chAutomode] == 'v')
8458 modes |= MODE_VOICE;
8459 else if(cData->chOpts[chAutomode] == 'h')
8460 modes |= MODE_HALFOP;
8461 else if(cData->chOpts[chAutomode] == 'o')
8462 modes |= MODE_CHANOP;
d76ed9a9 8463 }
c8ca69a0 8464
d76ed9a9
AS
8465 greeting = cData->greeting;
8466 if(user->handle_info)
8467 {
ff5f1ab2 8468/* handle = user->handle_info; */
d76ed9a9
AS
8469
8470 if(IsHelper(user) && !IsHelping(user))
8471 {
8472 unsigned int ii;
8473 for(ii = 0; ii < chanserv_conf.support_channels.used; ++ii)
8474 {
8475 if(channel == chanserv_conf.support_channels.list[ii])
8476 {
8477 HANDLE_SET_FLAG(user->handle_info, HELPING);
8478 break;
8479 }
8480 }
8481 }
8482
ff5f1ab2 8483/* uData = GetTrueChannelAccess(cData, handle); */
d76ed9a9
AS
8484 if(uData && !IsUserSuspended(uData))
8485 {
4b6129c0
AS
8486 /* non users getting automodes are handled above. */
8487 if(IsUserAutoOp(uData) && cData->chOpts[chAutomode] != 'n')
d76ed9a9 8488 {
4d69a3b1
AS
8489 /* just op everyone with access */
8490 if(uData->access >= UL_PEON && cData->chOpts[chAutomode] == 'l')
8491 modes |= MODE_VOICE;
8492 /* or do their access level */
8493 else if(uData->access >= UL_OP )
d76ed9a9 8494 modes |= MODE_CHANOP;
4b6129c0 8495 else if(uData->access >= UL_HALFOP )
55342ce8 8496 modes |= MODE_HALFOP;
4b6129c0 8497 else if(uData->access >= UL_PEON && cData->chOpts[chAutomode] != 'm')
d76ed9a9
AS
8498 modes |= MODE_VOICE;
8499 }
8500 if(uData->access >= UL_PRESENT)
8501 cData->visited = now;
8502 if(cData->user_greeting)
8503 greeting = cData->user_greeting;
8504 if(uData->info
8505 && (uData->access >= cData->lvlOpts[lvlUserInfo])
8506 && ((now - uData->seen) >= chanserv_conf.info_delay)
8507 && !uData->present)
8508 info = 1;
8509 uData->seen = now;
8510 uData->present = 1;
8511 }
8512 }
2f61d1d7 8513
8514 /* If user joining normally (not during burst), apply op or voice,
8515 * and send greeting/userinfo as appropriate.
8516 */
d76ed9a9
AS
8517 if(!user->uplink->burst)
8518 {
8519 if(modes)
8520 {
4b6129c0 8521 /* -- I'd rather have ops get voice too, if automode is v. -Rubin
55342ce8 8522 if(modes & MODE_CHANOP) {
8523 modes &= ~MODE_HALFOP;
d76ed9a9 8524 modes &= ~MODE_VOICE;
55342ce8 8525 }
4b6129c0 8526 */
d76ed9a9 8527 change.args[0].mode = modes;
a32da4c7 8528 change.args[0].u.member = mNode;
d76ed9a9
AS
8529 mod_chanmode_announce(chanserv, channel, &change);
8530 }
2f61d1d7 8531 if(greeting)
d76ed9a9
AS
8532 send_message_type(4, user, chanserv, "(%s) %s", channel->name, greeting);
8533 if(uData && info)
8534 send_target_message(5, channel->name, chanserv, "[%s] %s", user->nick, uData->info);
8535 }
8536 return 0;
8537}
8538
dd019452 8539static void
ffb204b6 8540chanserv_autojoin_channels(struct userNode *user)
dd019452 8541{
8542 struct userData *channel;
dd019452 8543
8544 for(channel = user->handle_info->channels; channel; channel = channel->u_next)
8545 {
8546 struct chanNode *cn;
8547 struct modeNode *mn;
8548
8549 if(IsUserSuspended(channel)
8550 || IsSuspended(channel->channel)
8551 || !(cn = channel->channel->channel))
8552 continue;
8553
8554 mn = GetUserMode(cn, user);
8555 if(!mn)
8556 {
8557 if(!IsUserSuspended(channel)
8558 && IsUserAutoJoin(channel)
8559 && (channel->access >= channel->channel->lvlOpts[lvlInviteMe])
8560 && !self->burst
8561 && !user->uplink->burst)
8562 irc_svsjoin(chanserv, user, cn);
8563 }
8564 }
8565}
8566
d76ed9a9
AS
8567static void
8568handle_auth(struct userNode *user, UNUSED_ARG(struct handle_info *old_handle))
8569{
8570 struct mod_chanmode change;
8571 struct userData *channel;
5177fd21 8572 unsigned int ii, jj, i;
d76ed9a9
AS
8573
8574 if(!user->handle_info)
8575 return;
8576
8577 mod_chanmode_init(&change);
8578 change.argc = 1;
8579 for(channel = user->handle_info->channels; channel; channel = channel->u_next)
8580 {
8581 struct chanNode *cn;
10fb34f6 8582 struct chanData *cData;
d76ed9a9
AS
8583 struct modeNode *mn;
8584 if(IsUserSuspended(channel)
8585 || IsSuspended(channel->channel)
8586 || !(cn = channel->channel->channel))
8587 continue;
8588
10fb34f6 8589 cData = cn->channel_info;
d76ed9a9
AS
8590 mn = GetUserMode(cn, user);
8591 if(!mn)
8592 {
8593 if(!IsUserSuspended(channel)
8594 && IsUserAutoInvite(channel)
8595 && (channel->access >= channel->channel->lvlOpts[lvlInviteMe])
8596 && !self->burst
8597 && !user->uplink->burst)
8598 irc_invite(chanserv, user, cn);
8599 continue;
8600 }
8601
8602 if(channel->access >= UL_PRESENT)
8603 channel->channel->visited = now;
8604
10fb34f6 8605 if(IsUserAutoOp(channel) && cData->chOpts[chAutomode] != 'n')
d76ed9a9 8606 {
4b6129c0 8607 if(channel->access >= UL_OP )
d76ed9a9 8608 change.args[0].mode = MODE_CHANOP;
4b6129c0 8609 else if(channel->access >= UL_HALFOP )
55342ce8 8610 change.args[0].mode = MODE_HALFOP;
4b6129c0 8611 else if(channel->access >= UL_PEON )
d76ed9a9
AS
8612 change.args[0].mode = MODE_VOICE;
8613 else
8614 change.args[0].mode = 0;
a32da4c7 8615 change.args[0].u.member = mn;
d76ed9a9
AS
8616 if(change.args[0].mode)
8617 mod_chanmode_announce(chanserv, cn, &change);
8618 }
8619
8620 channel->seen = now;
8621 channel->present = 1;
8622 }
8623
8624 for(ii = 0; ii < user->channels.used; ++ii)
8625 {
1136f709 8626 struct chanNode *chan = user->channels.list[ii]->channel;
d76ed9a9
AS
8627 struct banData *ban;
8628
55342ce8 8629 if((user->channels.list[ii]->modes & (MODE_CHANOP|MODE_HALFOP|MODE_VOICE))
1136f709 8630 || !chan->channel_info
8631 || IsSuspended(chan->channel_info))
d76ed9a9 8632 continue;
1136f709 8633 if(protect_user(user, chanserv, chan->channel_info, true))
ff5f1ab2 8634 continue;
1136f709 8635 for(jj = 0; jj < chan->banlist.used; ++jj)
8636 if(user_matches_glob(user, chan->banlist.list[jj]->ban, MATCH_USENICK))
d76ed9a9 8637 break;
1136f709 8638 if(jj < chan->banlist.used)
d76ed9a9 8639 continue;
1136f709 8640 for(ban = chan->channel_info->bans; ban; ban = ban->next)
d76ed9a9
AS
8641 {
8642 char kick_reason[MAXLEN];
2f61d1d7 8643 if(!user_matches_glob(user, ban->mask,MATCH_USENICK | MATCH_VISIBLE))
d76ed9a9
AS
8644 continue;
8645 change.args[0].mode = MODE_BAN;
a32da4c7 8646 change.args[0].u.hostmask = ban->mask;
1136f709 8647 mod_chanmode_announce(chanserv, chan, &change);
d76ed9a9 8648 sprintf(kick_reason, "(%s) %s", ban->owner, ban->reason);
1136f709 8649 KickChannelUser(user, chan, chanserv, kick_reason);
d76ed9a9
AS
8650 ban->triggered = now;
8651 break;
8652 }
8653 }
8654
8655 if(IsSupportHelper(user))
8656 {
8657 for(ii = 0; ii < chanserv_conf.support_channels.used; ++ii)
8658 {
8659 if(GetUserMode(chanserv_conf.support_channels.list[ii], user))
8660 {
8661 HANDLE_SET_FLAG(user->handle_info, HELPING);
8662 break;
8663 }
8664 }
8665 }
5177fd21 8666
8667 if (user->handle_info->ignores->used) {
8668 for (i=0; i < user->handle_info->ignores->used; i++) {
8669 irc_silence(user, user->handle_info->ignores->list[i], 1);
8670 }
8671 }
56958740 8672
8673 if (user->handle_info->epithet)
8674 irc_swhois(chanserv, user, user->handle_info->epithet);
dd019452 8675
8676 /* process autojoin channels 5 seconds later as this sometimes
8677 happens before autohide */
ffb204b6
AS
8678// timeq_add(now + 5, chanserv_autojoin_channels, user);
8679 chanserv_autojoin_channels(user);
d76ed9a9
AS
8680}
8681
8682static void
8683handle_part(struct modeNode *mn, UNUSED_ARG(const char *reason))
8684{
8685 struct chanData *cData;
8686 struct userData *uData;
8687
8688 cData = mn->channel->channel_info;
8689 if(!cData || IsSuspended(cData) || IsLocal(mn->user))
8690 return;
8691
8692 if((cData->flags & CHANNEL_DYNAMIC_LIMIT) && !mn->channel->join_flooded)
8693 {
8694 /* Allow for a bit of padding so that the limit doesn't
8695 track the user count exactly, which could get annoying. */
8696 if((mn->channel->limit - mn->channel->members.used) > chanserv_conf.adjust_threshold + 5)
8697 {
8698 timeq_del(0, chanserv_adjust_limit, cData, TIMEQ_IGNORE_WHEN);
8699 timeq_add(now + chanserv_conf.adjust_delay, chanserv_adjust_limit, cData);
8700 }
8701 }
8702
8703 if((uData = GetTrueChannelAccess(cData, mn->user->handle_info)))
8704 {
8705 scan_user_presence(uData, mn->user);
8706 uData->seen = now;
1136f709 8707 if (uData->access >= UL_PRESENT)
8708 cData->visited = now;
8709 }
d76ed9a9
AS
8710
8711 if(IsHelping(mn->user) && IsSupportHelper(mn->user))
8712 {
1136f709 8713 unsigned int ii;
d76ed9a9 8714 for(ii = 0; ii < chanserv_conf.support_channels.used; ++ii)
1136f709 8715 if(find_handle_in_channel(chanserv_conf.support_channels.list[ii], mn->user->handle_info, mn->user))
d76ed9a9 8716 break;
1136f709 8717 if(ii == chanserv_conf.support_channels.used)
d76ed9a9
AS
8718 HANDLE_CLEAR_FLAG(mn->user->handle_info, HELPING);
8719 }
8720}
8721
8722static void
8723handle_kick(struct userNode *kicker, struct userNode *victim, struct chanNode *channel)
8724{
8725 struct userData *uData;
8726
8727 if(!channel->channel_info || !kicker || IsService(kicker)
8728 || (kicker == victim) || IsSuspended(channel->channel_info)
8729 || (kicker->handle_info && kicker->handle_info == victim->handle_info))
8730 return;
8731
ff5f1ab2 8732 if(protect_user(victim, kicker, channel->channel_info, false))
d76ed9a9 8733 {
31f23f13 8734 const char *reason = user_find_message(kicker, "CSMSG_USER_PROTECTED_KICK");
d76ed9a9
AS
8735 KickChannelUser(kicker, channel, chanserv, reason);
8736 }
8737
8738 if((uData = GetTrueChannelAccess(channel->channel_info, victim->handle_info)))
8739 uData->seen = now;
8740}
8741
8742static int
8743handle_topic(struct userNode *user, struct chanNode *channel, const char *old_topic)
8744{
8745 struct chanData *cData;
8746
8747 if(!channel->channel_info || !user || IsSuspended(channel->channel_info) || IsService(user))
8748 return 0;
8749
8750 cData = channel->channel_info;
8751 if(bad_topic(channel, user, channel->topic))
b75e24a3 8752 { /* User doesnt have privs to set topics. Undo it */
d76ed9a9 8753 send_message(user, chanserv, "CSMSG_TOPIC_LOCKED", channel->name);
7fda2b52 8754 SetChannelTopic(channel, chanserv, chanserv, old_topic, 1);
d76ed9a9
AS
8755 return 1;
8756 }
b75e24a3
AS
8757 /* If there is a topic mask set, and the new topic doesnt match,
8758 * set the topic to mask + new_topic */
8759 if(cData->topic_mask && !match_ircglob(channel->topic, cData->topic_mask))
8760 {
8761 char new_topic[TOPICLEN+1];
8762 conform_topic(cData->topic_mask, channel->topic, new_topic);
8763 if(*new_topic)
8764 {
75ef8cdc 8765 SetChannelTopic(channel, chanserv, user, new_topic, 1);
b75e24a3
AS
8766 /* and fall through to topicsnarf code below.. */
8767 }
8768 else /* Topic couldnt fit into mask, was too long */
8769 {
75ef8cdc 8770 SetChannelTopic(channel, chanserv, user, old_topic, 1);
b75e24a3
AS
8771 send_message(user, chanserv, "CSMSG_TOPICMASK_CONFLICT1", channel->name, cData->topic_mask);
8772 send_message(user, chanserv, "CSMSG_TOPICMASK_CONFLICT2", TOPICLEN);
8773 return 1;
8774 }
8775 }
d76ed9a9
AS
8776 /* With topicsnarf, grab the topic and save it as the default topic. */
8777 if(check_user_level(channel, user, lvlTopicSnarf, 0, 0))
8778 {
8779 free(cData->topic);
8780 cData->topic = strdup(channel->topic);
8781 }
8782 return 0;
8783}
8784
8785static void
8786handle_mode(struct chanNode *channel, struct userNode *user, const struct mod_chanmode *change)
8787{
8788 struct mod_chanmode *bounce = NULL;
8789 unsigned int bnc, ii;
8790 char deopped = 0;
8791
8792 if(!channel->channel_info || IsLocal(user) || IsSuspended(channel->channel_info) || IsService(user))
8793 return;
8794
8795 if(!check_user_level(channel, user, lvlEnfModes, 1, 0)
8796 && mode_lock_violated(&channel->channel_info->modes, change))
8797 {
8798 char correct[MAXLEN];
8799 bounce = mod_chanmode_dup(&channel->channel_info->modes, change->argc + 1);
8800 mod_chanmode_format(&channel->channel_info->modes, correct);
8801 send_message(user, chanserv, "CSMSG_MODE_LOCKED", correct, channel->name);
8802 }
8803 for(ii = bnc = 0; ii < change->argc; ++ii)
8804 {
8805 if((change->args[ii].mode & (MODE_REMOVE|MODE_CHANOP)) == (MODE_REMOVE|MODE_CHANOP))
8806 {
a32da4c7 8807 const struct userNode *victim = change->args[ii].u.member->user;
ff5f1ab2 8808 if(!protect_user(victim, user, channel->channel_info, false))
d76ed9a9
AS
8809 continue;
8810 if(!bounce)
8811 bounce = mod_chanmode_alloc(change->argc + 1 - ii);
8812 if(!deopped)
8813 {
8814 bounce->args[bnc].mode = MODE_REMOVE | MODE_CHANOP;
a32da4c7 8815 bounce->args[bnc].u.member = GetUserMode(channel, user);
8816 if(bounce->args[bnc].u.member)
d76ed9a9
AS
8817 bnc++;
8818 deopped = 1;
8819 }
8820 bounce->args[bnc].mode = MODE_CHANOP;
a32da4c7 8821 bounce->args[bnc].u.member = change->args[ii].u.member;
d76ed9a9
AS
8822 bnc++;
8823 send_message(user, chanserv, "CSMSG_USER_PROTECTED", victim->nick);
8824 }
8825 else if(change->args[ii].mode & MODE_CHANOP)
8826 {
a32da4c7 8827 const struct userNode *victim = change->args[ii].u.member->user;
c092fcad 8828 if(IsService(victim) || validate_op(NULL, user, channel, (struct userNode*)victim))
d76ed9a9
AS
8829 continue;
8830 if(!bounce)
8831 bounce = mod_chanmode_alloc(change->argc + 1 - ii);
8832 bounce->args[bnc].mode = MODE_REMOVE | MODE_CHANOP;
a32da4c7 8833 bounce->args[bnc].u.member = change->args[ii].u.member;
d76ed9a9
AS
8834 bnc++;
8835 }
8836 else if((change->args[ii].mode & (MODE_REMOVE | MODE_BAN)) == MODE_BAN)
8837 {
a32da4c7 8838 const char *ban = change->args[ii].u.hostmask;
d76ed9a9
AS
8839 if(!bad_channel_ban(channel, user, ban, NULL, NULL))
8840 continue;
8841 if(!bounce)
8842 bounce = mod_chanmode_alloc(change->argc + 1 - ii);
8843 bounce->args[bnc].mode = MODE_REMOVE | MODE_BAN;
ec1a68c8 8844 bounce->args[bnc].u.hostmask = strdup(ban);
d76ed9a9
AS
8845 bnc++;
8846 send_message(user, chanserv, "CSMSG_MASK_PROTECTED", ban);
8847 }
8848 }
8849 if(bounce)
8850 {
8851 if((bounce->argc = bnc) || bounce->modes_set || bounce->modes_clear)
8852 mod_chanmode_announce(chanserv, channel, bounce);
ec1a68c8 8853 for(ii = 0; ii < change->argc; ++ii)
8854 if(bounce->args[ii].mode == (MODE_REMOVE | MODE_BAN))
8855 free((char*)bounce->args[ii].u.hostmask);
d76ed9a9
AS
8856 mod_chanmode_free(bounce);
8857 }
8858}
8859
8860static void
8861handle_nick_change(struct userNode *user, UNUSED_ARG(const char *old_nick))
8862{
8863 struct chanNode *channel;
8864 struct banData *bData;
8865 struct mod_chanmode change;
8866 unsigned int ii, jj;
8867 char kick_reason[MAXLEN];
8868
8869 mod_chanmode_init(&change);
8870 change.argc = 1;
8871 change.args[0].mode = MODE_BAN;
8872 for(ii = 0; ii < user->channels.used; ++ii)
8873 {
8874 channel = user->channels.list[ii]->channel;
8875 /* Need not check for bans if they're opped or voiced. */
4b6129c0
AS
8876 /* TODO: does this make sense in automode v, h, and o? *
8877 * lets still enforce on voice people anyway, and see how that goes -Rubin */
ff5f1ab2 8878 if(user->channels.list[ii]->modes & (MODE_CHANOP|MODE_HALFOP|MODE_VOICE ))
d76ed9a9
AS
8879 continue;
8880 /* Need not check for bans unless channel registration is active. */
8881 if(!channel->channel_info || IsSuspended(channel->channel_info))
8882 continue;
8883 /* Look for a matching ban already on the channel. */
8884 for(jj = 0; jj < channel->banlist.used; ++jj)
2f61d1d7 8885 if(user_matches_glob(user, channel->banlist.list[jj]->ban, MATCH_USENICK))
d76ed9a9
AS
8886 break;
8887 /* Need not act if we found one. */
8888 if(jj < channel->banlist.used)
8889 continue;
ff5f1ab2
AS
8890 /* don't kick someone on the userlist */
8891 if(protect_user(user, chanserv, channel->channel_info, true))
8892 continue;
d76ed9a9
AS
8893 /* Look for a matching ban in this channel. */
8894 for(bData = channel->channel_info->bans; bData; bData = bData->next)
8895 {
2f61d1d7 8896 if(!user_matches_glob(user, bData->mask, MATCH_USENICK | MATCH_VISIBLE))
d76ed9a9 8897 continue;
a32da4c7 8898 change.args[0].u.hostmask = bData->mask;
d76ed9a9
AS
8899 mod_chanmode_announce(chanserv, channel, &change);
8900 sprintf(kick_reason, "(%s) %s", bData->owner, bData->reason);
8901 KickChannelUser(user, channel, chanserv, kick_reason);
8902 bData->triggered = now;
8903 break; /* we don't need to check any more bans in the channel */
8904 }
8905 }
8906}
8907
8908static void handle_rename(struct handle_info *handle, const char *old_handle)
8909{
8910 struct do_not_register *dnr = dict_find(handle_dnrs, old_handle, NULL);
8911
8912 if(dnr)
8913 {
8914 dict_remove2(handle_dnrs, old_handle, 1);
8915 safestrncpy(dnr->chan_name + 1, handle->handle, sizeof(dnr->chan_name) - 1);
8916 dict_insert(handle_dnrs, dnr->chan_name + 1, dnr);
8917 }
8918}
8919
8920static void
8921handle_unreg(UNUSED_ARG(struct userNode *user), struct handle_info *handle)
8922{
8923 struct userNode *h_user;
8924
8925 if(handle->channels)
8926 {
8927 for(h_user = handle->users; h_user; h_user = h_user->next_authed)
8928 send_message(h_user, chanserv, "CSMSG_HANDLE_UNREGISTERED");
8929
8930 while(handle->channels)
8931 del_channel_user(handle->channels, 1);
8932 }
8933}
8934
0ab7b4bc 8935static int
d76ed9a9
AS
8936handle_server_link(UNUSED_ARG(struct server *server))
8937{
8938 struct chanData *cData;
8939
8940 for(cData = channelList; cData; cData = cData->next)
8941 {
8942 if(!IsSuspended(cData))
8943 cData->may_opchan = 1;
8944 if((cData->flags & CHANNEL_DYNAMIC_LIMIT)
8945 && !cData->channel->join_flooded
8946 && ((cData->channel->limit - cData->channel->members.used)
8947 < chanserv_conf.adjust_threshold))
8948 {
8949 timeq_del(0, chanserv_adjust_limit, cData, TIMEQ_IGNORE_WHEN);
8950 timeq_add(now + chanserv_conf.adjust_delay, chanserv_adjust_limit, cData);
8951 }
8952 }
0ab7b4bc 8953 return 0;
d76ed9a9
AS
8954}
8955
8956static void
8957chanserv_conf_read(void)
8958{
8959 dict_t conf_node;
8960 const char *str;
8961 char mode_line[MAXLEN], *modes[MAXNUMPARAMS];
8962 struct mod_chanmode *change;
8963 struct string_list *strlist;
8964 struct chanNode *chan;
8965 unsigned int ii;
8966
8967 if(!(conf_node = conf_get_data(CHANSERV_CONF_NAME, RECDB_OBJECT)))
8968 {
8969 log_module(CS_LOG, LOG_ERROR, "Invalid config node `%s'.", CHANSERV_CONF_NAME);
8970 return;
8971 }
8972 for(ii = 0; ii < chanserv_conf.support_channels.used; ++ii)
8973 UnlockChannel(chanserv_conf.support_channels.list[ii]);
8974 chanserv_conf.support_channels.used = 0;
8975 if((strlist = database_get_data(conf_node, KEY_SUPPORT_CHANNEL, RECDB_STRING_LIST)))
8976 {
8977 for(ii = 0; ii < strlist->used; ++ii)
8978 {
8979 const char *str2 = database_get_data(conf_node, KEY_SUPPORT_CHANNEL_MODES, RECDB_QSTRING);
8980 if(!str2)
8981 str2 = "+nt";
2aef5f4b 8982 chan = AddChannel(strlist->list[ii], now, str2, NULL, NULL);
d76ed9a9
AS
8983 LockChannel(chan);
8984 channelList_append(&chanserv_conf.support_channels, chan);
8985 }
8986 }
8987 else if((str = database_get_data(conf_node, KEY_SUPPORT_CHANNEL, RECDB_QSTRING)))
8988 {
8989 const char *str2;
8990 str2 = database_get_data(conf_node, KEY_SUPPORT_CHANNEL_MODES, RECDB_QSTRING);
8991 if(!str2)
8992 str2 = "+nt";
2aef5f4b 8993 chan = AddChannel(str, now, str2, NULL, NULL);
d76ed9a9
AS
8994 LockChannel(chan);
8995 channelList_append(&chanserv_conf.support_channels, chan);
8996 }
8997 str = database_get_data(conf_node, KEY_DB_BACKUP_FREQ, RECDB_QSTRING);
8998 chanserv_conf.db_backup_frequency = str ? ParseInterval(str) : 7200;
8999 str = database_get_data(conf_node, KEY_INFO_DELAY, RECDB_QSTRING);
9000 chanserv_conf.info_delay = str ? ParseInterval(str) : 180;
9001 str = database_get_data(conf_node, KEY_MAX_GREETLEN, RECDB_QSTRING);
1117fc5a 9002 chanserv_conf.greeting_length = str ? atoi(str) : 200;
d76ed9a9
AS
9003 str = database_get_data(conf_node, KEY_ADJUST_THRESHOLD, RECDB_QSTRING);
9004 chanserv_conf.adjust_threshold = str ? atoi(str) : 15;
9005 str = database_get_data(conf_node, KEY_ADJUST_DELAY, RECDB_QSTRING);
9006 chanserv_conf.adjust_delay = str ? ParseInterval(str) : 30;
9007 str = database_get_data(conf_node, KEY_CHAN_EXPIRE_FREQ, RECDB_QSTRING);
9008 chanserv_conf.channel_expire_frequency = str ? ParseInterval(str) : 86400;
31f23f13
AS
9009 str = database_get_data(conf_node, KEY_BAN_TIMEOUT_FREQ, RECDB_QSTRING);
9010 chanserv_conf.ban_timeout_frequency = str ? ParseInterval(str) : 600;
d76ed9a9
AS
9011 str = database_get_data(conf_node, KEY_CHAN_EXPIRE_DELAY, RECDB_QSTRING);
9012 chanserv_conf.channel_expire_delay = str ? ParseInterval(str) : 86400*30;
1136f709 9013 str = database_get_data(conf_node, KEY_DNR_EXPIRE_FREQ, RECDB_QSTRING);
9014 chanserv_conf.dnr_expire_frequency = str ? ParseInterval(str) : 3600;
d76ed9a9
AS
9015 str = database_get_data(conf_node, KEY_NODELETE_LEVEL, RECDB_QSTRING);
9016 chanserv_conf.nodelete_level = str ? atoi(str) : 1;
9017 str = database_get_data(conf_node, KEY_MAX_CHAN_USERS, RECDB_QSTRING);
9018 chanserv_conf.max_chan_users = str ? atoi(str) : 512;
9019 str = database_get_data(conf_node, KEY_MAX_CHAN_BANS, RECDB_QSTRING);
9020 chanserv_conf.max_chan_bans = str ? atoi(str) : 512;
9021 str = database_get_data(conf_node, KEY_MAX_USERINFO_LENGTH, RECDB_QSTRING);
9022 chanserv_conf.max_userinfo_length = str ? atoi(str) : 400;
9023 str = database_get_data(conf_node, KEY_NICK, RECDB_QSTRING);
9024 if(chanserv && str)
9025 NickChange(chanserv, str, 0);
9026 str = database_get_data(conf_node, KEY_REFRESH_PERIOD, RECDB_QSTRING);
9027 chanserv_conf.refresh_period = str ? ParseInterval(str) : 3*60*60;
a32da4c7 9028 str = database_get_data(conf_node, KEY_GIVEOWNERSHIP_PERIOD, RECDB_QSTRING);
9029 chanserv_conf.giveownership_period = str ? ParseInterval(str) : 0;
d76ed9a9
AS
9030 str = database_get_data(conf_node, KEY_CTCP_SHORT_BAN_DURATION, RECDB_QSTRING);
9031 chanserv_conf.ctcp_short_ban_duration = str ? str : "3m";
9032 str = database_get_data(conf_node, KEY_CTCP_LONG_BAN_DURATION, RECDB_QSTRING);
9033 chanserv_conf.ctcp_long_ban_duration = str ? str : "1h";
9034 str = database_get_data(conf_node, KEY_MAX_OWNED, RECDB_QSTRING);
9035 chanserv_conf.max_owned = str ? atoi(str) : 5;
9036 str = database_get_data(conf_node, KEY_IRC_OPERATOR_EPITHET, RECDB_QSTRING);
9037 chanserv_conf.irc_operator_epithet = str ? str : "a megalomaniacal power hungry tyrant";
9038 str = database_get_data(conf_node, KEY_NETWORK_HELPER_EPITHET, RECDB_QSTRING);
9039 chanserv_conf.network_helper_epithet = str ? str : "a wannabe tyrant";
9040 str = database_get_data(conf_node, KEY_SUPPORT_HELPER_EPITHET, RECDB_QSTRING);
9041 chanserv_conf.support_helper_epithet = str ? str : "a wannabe tyrant";
7637f48f 9042 str = database_get_data(conf_node, KEY_GOD_TIMEOUT, RECDB_QSTRING);
9043 god_timeout = str ? ParseInterval(str) : 60*15;
d76ed9a9
AS
9044 str = database_get_data(conf_node, "default_modes", RECDB_QSTRING);
9045 if(!str)
9046 str = "+nt";
9047 safestrncpy(mode_line, str, sizeof(mode_line));
9048 ii = split_line(mode_line, 0, ArrayLength(modes), modes);
2f61d1d7 9049 if((change = mod_chanmode_parse(NULL, modes, ii, MCP_KEY_FREE, 0))
9050 && (change->argc < 2))
d76ed9a9
AS
9051 {
9052 chanserv_conf.default_modes = *change;
9053 mod_chanmode_free(change);
9054 }
d3abe0df 9055 str = database_get_data(conf_node, KEY_VALID_CHANNEL_REGEX, RECDB_QSTRING);
9056 if (chanserv_conf.valid_channel_regex_set)
9057 regfree(&chanserv_conf.valid_channel_regex);
9058 if (str) {
9059 int err = regcomp(&chanserv_conf.valid_channel_regex, str, REG_EXTENDED|REG_ICASE|REG_NOSUB);
9060 chanserv_conf.valid_channel_regex_set = !err;
9061 if (err) log_module(CS_LOG, LOG_ERROR, "Bad valid_channel_regex (error %d)", err);
9062 } else {
9063 chanserv_conf.valid_channel_regex_set = 0;
9064 }
5e6460e4
AS
9065 free_string_list(chanserv_conf.wheel);
9066 strlist = database_get_data(conf_node, "wheel", RECDB_STRING_LIST);
9067 if(strlist)
9068 strlist = string_list_copy(strlist);
9069 else
9070 {
9071 static const char *list[] = {
9072 "peer", "partall", "gline", /* "shun", */
9073 "nothing", "randjoin", "abusewhois", "kickall",
19c83de8
AS
9074 "nickchange", "kill", "svsignore", "kickbanall",
9075 NULL};
5e6460e4
AS
9076 unsigned int ii;
9077 strlist = alloc_string_list(ArrayLength(list)-1);
9078 for(ii=0; list[ii]; ii++)
9079 string_list_append(strlist, strdup(list[ii]));
9080 }
9081 chanserv_conf.wheel = strlist;
9082
d76ed9a9
AS
9083 free_string_list(chanserv_conf.set_shows);
9084 strlist = database_get_data(conf_node, "set_shows", RECDB_STRING_LIST);
9085 if(strlist)
9086 strlist = string_list_copy(strlist);
9087 else
9088 {
9089 static const char *list[] = {
9090 /* free form text */
9091 "DefaultTopic", "TopicMask", "Greeting", "UserGreeting", "Modes",
9092 /* options based on user level */
4b6129c0
AS
9093 "PubCmd", "InviteMe", "UserInfo","EnfOps",
9094 "EnfHalfOps", "EnfModes", "EnfTopic", "TopicSnarf", "Setters",
d76ed9a9 9095 /* multiple choice options */
d3347099 9096 "AutoMode", "CtcpReaction", "Protect", "Toys", "TopicRefresh", "Resync",
d76ed9a9 9097 /* binary options */
31f23f13 9098 "DynLimit", "NoDelete", "BanTimeout",
d76ed9a9
AS
9099 /* delimiter */
9100 NULL
9101 };
d76ed9a9
AS
9102 strlist = alloc_string_list(ArrayLength(list)-1);
9103 for(ii=0; list[ii]; ii++)
9104 string_list_append(strlist, strdup(list[ii]));
9105 }
9106 chanserv_conf.set_shows = strlist;
9107 /* We don't look things up now, in case the list refers to options
9108 * defined by modules initialized after this point. Just mark the
9109 * function list as invalid, so it will be initialized.
9110 */
9111 set_shows_list.used = 0;
5e6460e4 9112
d76ed9a9
AS
9113 free_string_list(chanserv_conf.eightball);
9114 strlist = database_get_data(conf_node, KEY_8BALL_RESPONSES, RECDB_STRING_LIST);
9115 if(strlist)
9116 {
9117 strlist = string_list_copy(strlist);
9118 }
9119 else
9120 {
9121 strlist = alloc_string_list(4);
9122 string_list_append(strlist, strdup("Yes."));
9123 string_list_append(strlist, strdup("No."));
9124 string_list_append(strlist, strdup("Maybe so."));
9125 }
9126 chanserv_conf.eightball = strlist;
5e6460e4 9127
d76ed9a9
AS
9128 free_string_list(chanserv_conf.old_ban_names);
9129 strlist = database_get_data(conf_node, KEY_OLD_BAN_NAMES, RECDB_STRING_LIST);
9130 if(strlist)
9131 strlist = string_list_copy(strlist);
9132 else
9133 strlist = alloc_string_list(2);
9134 chanserv_conf.old_ban_names = strlist;
d76ed9a9 9135 str = database_get_data(conf_node, "off_channel", RECDB_QSTRING);
1117fc5a 9136 off_channel = str ? atoi(str) : 0;
d76ed9a9
AS
9137}
9138
9139static void
9140chanserv_note_type_read(const char *key, struct record_data *rd)
9141{
9142 dict_t obj;
9143 struct note_type *ntype;
9144 const char *str;
9145
9146 if(!(obj = GET_RECORD_OBJECT(rd)))
9147 {
9148 log_module(CS_LOG, LOG_ERROR, "Invalid note type %s.", key);
9149 return;
9150 }
9151 if(!(ntype = chanserv_create_note_type(key)))
9152 {
9153 log_module(CS_LOG, LOG_ERROR, "Memory allocation failed for note %s.", key);
9154 return;
9155 }
9156
9157 /* Figure out set access */
9158 if((str = database_get_data(obj, KEY_NOTE_OPSERV_ACCESS, RECDB_QSTRING)))
9159 {
9160 ntype->set_access_type = NOTE_SET_PRIVILEGED;
9161 ntype->set_access.min_opserv = strtoul(str, NULL, 0);
9162 }
9163 else if((str = database_get_data(obj, KEY_NOTE_CHANNEL_ACCESS, RECDB_QSTRING)))
9164 {
9165 ntype->set_access_type = NOTE_SET_CHANNEL_ACCESS;
9166 ntype->set_access.min_ulevel = strtoul(str, NULL, 0);
9167 }
9168 else if((str = database_get_data(obj, KEY_NOTE_SETTER_ACCESS, RECDB_QSTRING)))
9169 {
9170 ntype->set_access_type = NOTE_SET_CHANNEL_SETTER;
9171 }
9172 else
9173 {
9174 log_module(CS_LOG, LOG_ERROR, "Could not find access type for note %s; defaulting to OpServ access level 0.", key);
9175 ntype->set_access_type = NOTE_SET_PRIVILEGED;
9176 ntype->set_access.min_opserv = 0;
9177 }
9178
9179 /* Figure out visibility */
9180 if(!(str = database_get_data(obj, KEY_NOTE_VISIBILITY, RECDB_QSTRING)))
9181 ntype->visible_type = NOTE_VIS_PRIVILEGED;
9182 else if(!irccasecmp(str, KEY_NOTE_VIS_PRIVILEGED))
9183 ntype->visible_type = NOTE_VIS_PRIVILEGED;
9184 else if(!irccasecmp(str, KEY_NOTE_VIS_CHANNEL_USERS))
9185 ntype->visible_type = NOTE_VIS_CHANNEL_USERS;
9186 else if(!irccasecmp(str, KEY_NOTE_VIS_ALL))
9187 ntype->visible_type = NOTE_VIS_ALL;
9188 else
9189 ntype->visible_type = NOTE_VIS_PRIVILEGED;
9190
9191 str = database_get_data(obj, KEY_NOTE_MAX_LENGTH, RECDB_QSTRING);
9192 ntype->max_length = str ? strtoul(str, NULL, 0) : 400;
9193}
9194
9195static void
9196user_read_helper(const char *key, struct record_data *rd, struct chanData *chan)
9197{
9198 struct handle_info *handle;
9199 struct userData *uData;
dfaa28a4 9200 char *seen, *inf, *flags, *expires, *accessexpiry, *clvlexpiry, *lstacc;
d76ed9a9 9201 time_t last_seen;
1136f709 9202 unsigned short access_level, lastaccess = 0;
d76ed9a9
AS
9203
9204 if(rd->type != RECDB_OBJECT || !dict_size(rd->d.object))
9205 {
9206 log_module(CS_LOG, LOG_ERROR, "Invalid user in %s.", chan->channel->name);
9207 return;
9208 }
9209
1136f709 9210 access_level = atoi(database_get_data(rd->d.object, KEY_LEVEL, RECDB_QSTRING));
9211 if(access_level > UL_OWNER)
d76ed9a9
AS
9212 {
9213 log_module(CS_LOG, LOG_ERROR, "Invalid access level for %s in %s.", key, chan->channel->name);
9214 return;
9215 }
9216
9217 inf = database_get_data(rd->d.object, KEY_INFO, RECDB_QSTRING);
9218 seen = database_get_data(rd->d.object, KEY_SEEN, RECDB_QSTRING);
9219 last_seen = seen ? (signed)strtoul(seen, NULL, 0) : now;
9220 flags = database_get_data(rd->d.object, KEY_FLAGS, RECDB_QSTRING);
b10abdb2 9221 expires = database_get_data(rd->d.object, KEY_EXPIRES, RECDB_QSTRING);
dfaa28a4 9222 accessexpiry = database_get_data(rd->d.object, KEY_ACCESSEXPIRY, RECDB_QSTRING);
9223 clvlexpiry = database_get_data(rd->d.object, KEY_CLVLEXPIRY, RECDB_QSTRING);
9224 lstacc = database_get_data(rd->d.object, KEY_LASTLEVEL, RECDB_QSTRING);
9225 lastaccess = lstacc ? atoi(lstacc) : 0;
9226
d76ed9a9
AS
9227 handle = get_handle_info(key);
9228 if(!handle)
9229 {
9230 log_module(CS_LOG, LOG_ERROR, "Nonexistent account %s in %s.", key, chan->channel->name);
9231 return;
9232 }
9233
1136f709 9234 uData = add_channel_user(chan, handle, access_level, last_seen, inf, 0);
d76ed9a9 9235 uData->flags = flags ? strtoul(flags, NULL, 0) : 0;
b10abdb2 9236 uData->expires = expires ? strtoul(expires, NULL, 0) : 0;
dfaa28a4 9237
9238 uData->accessexpiry = accessexpiry ? strtoul(accessexpiry, NULL, 0) : 0;
9239 if (uData->accessexpiry > 0)
9240 timeq_add(uData->accessexpiry, chanserv_expire_tempuser, uData);
9241
9242 uData->clvlexpiry = clvlexpiry ? strtoul(clvlexpiry, NULL, 0) : 0;
9243 if (uData->clvlexpiry > 0)
9244 timeq_add(uData->clvlexpiry, chanserv_expire_tempclvl, uData);
9245
9246 uData->lastaccess = lastaccess;
b10abdb2 9247
9248 if((uData->flags & USER_SUSPENDED) && uData->expires)
9249 {
9250 if(uData->expires > now)
9251 timeq_add(uData->expires, chanserv_expire_user_suspension, uData);
9252 else
9253 uData->flags &= ~USER_SUSPENDED;
9254 }
c8ca69a0
AS
9255
9256 /* Upgrade: set autoop to the inverse of noautoop */
9257 if(chanserv_read_version < 2)
9258 {
9259 /* if noautoop is true, set autoop false, and vice versa */
9260 if(uData->flags & USER_NOAUTO_OP)
9261 uData->flags = uData->flags & ~USER_AUTO_OP;
9262 else
9263 uData->flags = uData->flags | USER_AUTO_OP;
9264 log_module(CS_LOG, LOG_INFO, "UPGRADE: to db version 2 from %u. Changing flag to %d for %s in %s.", chanserv_read_version, uData->flags, key, chan->channel->name);
9265 }
9266
d76ed9a9
AS
9267}
9268
9269static void
9270ban_read_helper(const char *key, struct record_data *rd, struct chanData *chan)
9271{
9272 struct banData *bData;
9273 char *set, *triggered, *s_duration, *s_expires, *reason, *owner;
9274 time_t set_time, triggered_time, expires_time;
9275
9276 if(rd->type != RECDB_OBJECT || !dict_size(rd->d.object))
9277 {
9278 log_module(CS_LOG, LOG_ERROR, "Invalid ban in %s.", chan->channel->name);
9279 return;
9280 }
9281
9282 set = database_get_data(rd->d.object, KEY_SET, RECDB_QSTRING);
9283 triggered = database_get_data(rd->d.object, KEY_TRIGGERED, RECDB_QSTRING);
9284 s_duration = database_get_data(rd->d.object, KEY_DURATION, RECDB_QSTRING);
9285 s_expires = database_get_data(rd->d.object, KEY_EXPIRES, RECDB_QSTRING);
9286 owner = database_get_data(rd->d.object, KEY_OWNER, RECDB_QSTRING);
9287 reason = database_get_data(rd->d.object, KEY_REASON, RECDB_QSTRING);
0d16e639 9288 if (!reason || !owner)
9289 return;
d76ed9a9
AS
9290
9291 set_time = set ? (time_t)strtoul(set, NULL, 0) : now;
9292 triggered_time = triggered ? (time_t)strtoul(triggered, NULL, 0) : 0;
9293 if(s_expires)
9294 expires_time = (time_t)strtoul(s_expires, NULL, 0);
9295 else if(s_duration)
9296 expires_time = set_time + atoi(s_duration);
9297 else
9298 expires_time = 0;
9299
ec1a68c8 9300 if(!reason || (expires_time && (expires_time < now)))
d76ed9a9
AS
9301 return;
9302
9303 bData = add_channel_ban(chan, key, owner, set_time, triggered_time, expires_time, reason);
9304}
9305
9306static struct suspended *
9307chanserv_read_suspended(dict_t obj)
9308{
9309 struct suspended *suspended = calloc(1, sizeof(*suspended));
9310 char *str;
9311 dict_t previous;
9312
9313 str = database_get_data(obj, KEY_EXPIRES, RECDB_QSTRING);
9314 suspended->expires = str ? (time_t)strtoul(str, NULL, 0) : 0;
9315 str = database_get_data(obj, KEY_REVOKED, RECDB_QSTRING);
9316 suspended->revoked = str ? (time_t)strtoul(str, NULL, 0) : 0;
9317 str = database_get_data(obj, KEY_ISSUED, RECDB_QSTRING);
9318 suspended->issued = str ? (time_t)strtoul(str, NULL, 0) : 0;
9319 suspended->suspender = strdup(database_get_data(obj, KEY_SUSPENDER, RECDB_QSTRING));
9320 suspended->reason = strdup(database_get_data(obj, KEY_REASON, RECDB_QSTRING));
9321 previous = database_get_data(obj, KEY_PREVIOUS, RECDB_OBJECT);
9322 suspended->previous = previous ? chanserv_read_suspended(previous) : NULL;
9323 return suspended;
9324}
9325
82f37c08 9326static struct giveownership *
9327chanserv_read_giveownership(dict_t obj)
9328{
9329 struct giveownership *giveownership = calloc(1, sizeof(*giveownership));
9330 char *str;
9331 dict_t previous;
9332
9333 str = database_get_data(obj, KEY_STAFF_ISSUER, RECDB_QSTRING);
9334 giveownership->staff_issuer = str ? strdup(str) : NULL;
9335
9336 giveownership->old_owner = strdup(database_get_data(obj, KEY_OLD_OWNER, RECDB_QSTRING));
9337
9338 giveownership->target = strdup(database_get_data(obj, KEY_TARGET, RECDB_QSTRING));
9339 giveownership->target_access = atoi(database_get_data(obj, KEY_TARGET_ACCESS, RECDB_QSTRING));
9340
9341 str = database_get_data(obj, KEY_REASON, RECDB_QSTRING);
9342 giveownership->reason = str ? strdup(str) : NULL;
9343 str = database_get_data(obj, KEY_ISSUED, RECDB_QSTRING);
9344 giveownership->issued = str ? (time_t)strtoul(str, NULL, 0) : 0;
9345
9346 previous = database_get_data(obj, KEY_PREVIOUS, RECDB_OBJECT);
9347 giveownership->previous = previous ? chanserv_read_giveownership(previous) : NULL;
9348 return giveownership;
9349}
9350
d76ed9a9
AS
9351static int
9352chanserv_channel_read(const char *key, struct record_data *hir)
9353{
9354 struct suspended *suspended;
82f37c08 9355 struct giveownership *giveownership;
d76ed9a9
AS
9356 struct mod_chanmode *modes;
9357 struct chanNode *cNode;
9358 struct chanData *cData;
9359 struct dict *channel, *obj;
9360 char *str, *argv[10];
9361 dict_iterator_t it;
9362 unsigned int argc;
9363
9364 channel = hir->d.object;
9365
9366 str = database_get_data(channel, KEY_REGISTRAR, RECDB_QSTRING);
9367 if(!str)
9368 str = "<unknown>";
2aef5f4b 9369 cNode = AddChannel(key, now, NULL, NULL, NULL);
d76ed9a9
AS
9370 if(!cNode)
9371 {
9372 log_module(CS_LOG, LOG_ERROR, "Unable to create registered channel %s.", key);
9373 return 0;
9374 }
9375 cData = register_channel(cNode, str);
9376 if(!cData)
9377 {
9378 log_module(CS_LOG, LOG_ERROR, "Unable to register channel %s from database.", key);
9379 return 0;
9380 }
9381
9382 if((obj = database_get_data(channel, KEY_OPTIONS, RECDB_OBJECT)))
9383 {
9384 enum levelOption lvlOpt;
9385 enum charOption chOpt;
9386
9387 if((str = database_get_data(obj, KEY_FLAGS, RECDB_QSTRING)))
9388 cData->flags = atoi(str);
9389
9390 for(lvlOpt = 0; lvlOpt < NUM_LEVEL_OPTIONS; ++lvlOpt)
9391 {
9392 str = database_get_data(obj, levelOptions[lvlOpt].db_name, RECDB_QSTRING);
9393 if(str)
9394 cData->lvlOpts[lvlOpt] = user_level_from_name(str, UL_OWNER+1);
9395 else if(levelOptions[lvlOpt].old_flag)
9396 {
9397 if(cData->flags & levelOptions[lvlOpt].old_flag)
9398 cData->lvlOpts[lvlOpt] = levelOptions[lvlOpt].flag_value;
9399 else
9400 cData->lvlOpts[lvlOpt] = levelOptions[lvlOpt].default_value;
9401 }
9402 }
9403
9404 for(chOpt = 0; chOpt < NUM_CHAR_OPTIONS; ++chOpt)
9405 {
9406 if(!(str = database_get_data(obj, charOptions[chOpt].db_name, RECDB_QSTRING)))
9407 continue;
9408 cData->chOpts[chOpt] = str[0];
9409 }
9410 }
9411 else if((str = database_get_data(channel, KEY_FLAGS, RECDB_QSTRING)))
9412 {
9413 enum levelOption lvlOpt;
9414 enum charOption chOpt;
9415 unsigned int count;
9416
9417 cData->flags = base64toint(str, 5);
9418 count = strlen(str += 5);
9419 for(lvlOpt = 0; lvlOpt < NUM_LEVEL_OPTIONS; ++lvlOpt)
9420 {
9421 unsigned short lvl;
9422 if(levelOptions[lvlOpt].old_flag)
9423 {
9424 if(cData->flags & levelOptions[lvlOpt].old_flag)
9425 lvl = levelOptions[lvlOpt].flag_value;
9426 else
9427 lvl = levelOptions[lvlOpt].default_value;
9428 }
9429 else switch(((count <= levelOptions[lvlOpt].old_idx) ? str : CHANNEL_DEFAULT_OPTIONS)[levelOptions[lvlOpt].old_idx])
9430 {
9431 case 'c': lvl = UL_COOWNER; break;
4048352e 9432 case 'm': lvl = UL_MANAGER; break;
d76ed9a9
AS
9433 case 'n': lvl = UL_OWNER+1; break;
9434 case 'o': lvl = UL_OP; break;
9435 case 'p': lvl = UL_PEON; break;
55342ce8 9436 case 'h': lvl = UL_HALFOP; break;
d76ed9a9
AS
9437 case 'w': lvl = UL_OWNER; break;
9438 default: lvl = 0; break;
9439 }
9440 cData->lvlOpts[lvlOpt] = lvl;
9441 }
9442 for(chOpt = 0; chOpt < NUM_CHAR_OPTIONS; ++chOpt)
9443 cData->chOpts[chOpt] = ((count <= charOptions[chOpt].old_idx) ? str : CHANNEL_DEFAULT_OPTIONS)[charOptions[chOpt].old_idx];
9444 }
9445
9446 if((obj = database_get_data(hir->d.object, KEY_SUSPENDED, RECDB_OBJECT)))
9447 {
9448 suspended = chanserv_read_suspended(obj);
9449 cData->suspended = suspended;
9450 suspended->cData = cData;
9451 /* We could use suspended->expires and suspended->revoked to
9452 * set the CHANNEL_SUSPENDED flag, but we don't. */
9453 }
a32da4c7 9454 else if(IsSuspended(cData) && (str = database_get_data(hir->d.object, KEY_SUSPENDER, RECDB_QSTRING)))
d76ed9a9
AS
9455 {
9456 suspended = calloc(1, sizeof(*suspended));
9457 suspended->issued = 0;
9458 suspended->revoked = 0;
a32da4c7 9459 suspended->suspender = strdup(str);
d76ed9a9
AS
9460 str = database_get_data(hir->d.object, KEY_SUSPEND_EXPIRES, RECDB_QSTRING);
9461 suspended->expires = str ? atoi(str) : 0;
d76ed9a9
AS
9462 str = database_get_data(hir->d.object, KEY_SUSPEND_REASON, RECDB_QSTRING);
9463 suspended->reason = strdup(str ? str : "No reason");
9464 suspended->previous = NULL;
9465 cData->suspended = suspended;
9466 suspended->cData = cData;
9467 }
9468 else
a32da4c7 9469 {
9470 cData->flags &= ~CHANNEL_SUSPENDED;
d76ed9a9 9471 suspended = NULL; /* to squelch a warning */
a32da4c7 9472 }
d76ed9a9
AS
9473
9474 if(IsSuspended(cData)) {
9475 if(suspended->expires > now)
9476 timeq_add(suspended->expires, chanserv_expire_suspension, suspended);
9477 else if(suspended->expires)
9478 cData->flags &= ~CHANNEL_SUSPENDED;
9479 }
9480
82f37c08 9481 if((obj = database_get_data(hir->d.object, KEY_GIVEOWNERSHIP, RECDB_OBJECT)))
9482 {
9483 giveownership = chanserv_read_giveownership(obj);
9484 cData->giveownership = giveownership;
9485 }
9486
d76ed9a9
AS
9487 if((!off_channel || !IsOffChannel(cData)) && !IsSuspended(cData)) {
9488 struct mod_chanmode change;
9489 mod_chanmode_init(&change);
9490 change.argc = 1;
9491 change.args[0].mode = MODE_CHANOP;
a32da4c7 9492 change.args[0].u.member = AddChannelUser(chanserv, cNode);
d76ed9a9
AS
9493 mod_chanmode_announce(chanserv, cNode, &change);
9494 }
9495
9496 str = database_get_data(channel, KEY_REGISTERED, RECDB_QSTRING);
9497 cData->registered = str ? (time_t)strtoul(str, NULL, 0) : now;
9498 str = database_get_data(channel, KEY_VISITED, RECDB_QSTRING);
9499 cData->visited = str ? (time_t)strtoul(str, NULL, 0) : now;
a32da4c7 9500 str = database_get_data(channel, KEY_OWNER_TRANSFER, RECDB_QSTRING);
9501 cData->ownerTransfer = str ? (time_t)strtoul(str, NULL, 0) : 0;
d76ed9a9
AS
9502 str = database_get_data(channel, KEY_MAX, RECDB_QSTRING);
9503 cData->max = str ? atoi(str) : 0;
9504 str = database_get_data(channel, KEY_GREETING, RECDB_QSTRING);
9505 cData->greeting = str ? strdup(str) : NULL;
9506 str = database_get_data(channel, KEY_USER_GREETING, RECDB_QSTRING);
9507 cData->user_greeting = str ? strdup(str) : NULL;
9508 str = database_get_data(channel, KEY_TOPIC_MASK, RECDB_QSTRING);
9509 cData->topic_mask = str ? strdup(str) : NULL;
9510 str = database_get_data(channel, KEY_TOPIC, RECDB_QSTRING);
9511 cData->topic = str ? strdup(str) : NULL;
9512
8b9e7d45 9513 str = database_get_data(channel, KEY_MAXSETINFO, RECDB_QSTRING);
9514 cData->maxsetinfo = str ? strtoul(str, NULL, 0) : chanserv_conf.max_userinfo_length;
9515
d76ed9a9
AS
9516 if(!IsSuspended(cData)
9517 && (str = database_get_data(channel, KEY_MODES, RECDB_QSTRING))
9518 && (argc = split_line(str, 0, ArrayLength(argv), argv))
2f61d1d7 9519 && (modes = mod_chanmode_parse(cNode, argv, argc, MCP_KEY_FREE, 0))) {
d76ed9a9 9520 cData->modes = *modes;
1117fc5a 9521 if(off_channel > 0)
a32da4c7 9522 cData->modes.modes_set |= MODE_REGISTERED;
d76ed9a9
AS
9523 if(cData->modes.argc > 1)
9524 cData->modes.argc = 1;
9525 mod_chanmode_announce(chanserv, cNode, &cData->modes);
9526 mod_chanmode_free(modes);
9527 }
9528
9529 obj = database_get_data(channel, KEY_USERS, RECDB_OBJECT);
9530 for(it = dict_first(obj); it; it = iter_next(it))
9531 user_read_helper(iter_key(it), iter_data(it), cData);
9532
9533 if(!cData->users && !IsProtected(cData))
9534 {
9535 log_module(CS_LOG, LOG_ERROR, "Channel %s had no users in database, unregistering it.", key);
9536 unregister_channel(cData, "has empty user list.");
9537 return 0;
9538 }
9539
9540 obj = database_get_data(channel, KEY_BANS, RECDB_OBJECT);
9541 for(it = dict_first(obj); it; it = iter_next(it))
9542 ban_read_helper(iter_key(it), iter_data(it), cData);
9543
9544 obj = database_get_data(channel, KEY_NOTES, RECDB_OBJECT);
9545 for(it = dict_first(obj); it; it = iter_next(it))
9546 {
9547 struct note_type *ntype = dict_find(note_types, iter_key(it), NULL);
9548 struct record_data *rd = iter_data(it);
9549 const char *note, *setter;
9550
9551 if(rd->type != RECDB_OBJECT)
9552 {
9553 log_module(CS_LOG, LOG_ERROR, "Bad record type for note %s in channel %s.", iter_key(it), key);
9554 }
9555 else if(!ntype)
9556 {
9557 log_module(CS_LOG, LOG_ERROR, "Bad note type name %s in channel %s.", iter_key(it), key);
9558 }
9559 else if(!(note = database_get_data(rd->d.object, KEY_NOTE_NOTE, RECDB_QSTRING)))
9560 {
9561 log_module(CS_LOG, LOG_ERROR, "Missing note text for note %s in channel %s.", iter_key(it), key);
9562 }
9563 else
9564 {
9565 setter = database_get_data(rd->d.object, KEY_NOTE_SETTER, RECDB_QSTRING);
9566 if(!setter) setter = "<unknown>";
9567 chanserv_add_channel_note(cData, ntype, setter, note);
9568 }
9569 }
9570
9571 return 0;
9572}
9573
9574static void
9575chanserv_dnr_read(const char *key, struct record_data *hir)
9576{
9577 const char *setter, *reason, *str;
9578 struct do_not_register *dnr;
1136f709 9579 time_t expiry;
d76ed9a9
AS
9580
9581 setter = database_get_data(hir->d.object, KEY_DNR_SETTER, RECDB_QSTRING);
9582 if(!setter)
9583 {
9584 log_module(CS_LOG, LOG_ERROR, "Missing setter for DNR %s.", key);
9585 return;
9586 }
9587 reason = database_get_data(hir->d.object, KEY_DNR_REASON, RECDB_QSTRING);
9588 if(!reason)
9589 {
9590 log_module(CS_LOG, LOG_ERROR, "Missing reason for DNR %s.", key);
9591 return;
9592 }
1136f709 9593 str = database_get_data(hir->d.object, KEY_EXPIRES, RECDB_QSTRING);
9594 expiry = str ? (time_t)strtoul(str, NULL, 0) : 0;
9595 if(expiry && expiry <= now)
9596 return;
9597 dnr = chanserv_add_dnr(key, setter, expiry, reason);
d76ed9a9
AS
9598 if(!dnr)
9599 return;
9600 str = database_get_data(hir->d.object, KEY_DNR_SET, RECDB_QSTRING);
9601 if(str)
9602 dnr->set = atoi(str);
9603 else
9604 dnr->set = 0;
9605}
9606
c8ca69a0
AS
9607static void
9608chanserv_version_read(struct dict *section)
9609{
9610 /* global var.. */
9611 char *str;
9612 str = database_get_data(section, KEY_VERSION_NUMBER, RECDB_QSTRING);
9613 if(str)
9614 chanserv_read_version = atoi(str);
9615 log_module(CS_LOG, LOG_DEBUG, "Chanserv db version is %d.", chanserv_read_version);
9616}
9617
d76ed9a9
AS
9618static int
9619chanserv_saxdb_read(struct dict *database)
9620{
9621 struct dict *section;
9622 dict_iterator_t it;
9623
c8ca69a0
AS
9624 if((section = database_get_data(database, KEY_VERSION_CONTROL, RECDB_OBJECT)))
9625 chanserv_version_read(section);
9626
d76ed9a9
AS
9627 if((section = database_get_data(database, KEY_NOTE_TYPES, RECDB_OBJECT)))
9628 for(it = dict_first(section); it; it = iter_next(it))
9629 chanserv_note_type_read(iter_key(it), iter_data(it));
9630
9631 if((section = database_get_data(database, KEY_CHANNELS, RECDB_OBJECT)))
9632 for(it = dict_first(section); it; it = iter_next(it))
9633 chanserv_channel_read(iter_key(it), iter_data(it));
9634
9635 if((section = database_get_data(database, KEY_DNR, RECDB_OBJECT)))
9636 for(it = dict_first(section); it; it = iter_next(it))
9637 chanserv_dnr_read(iter_key(it), iter_data(it));
9638
9639 return 0;
9640}
9641
9642static int
9643chanserv_write_users(struct saxdb_context *ctx, struct userData *uData)
9644{
9645 int high_present = 0;
9646 saxdb_start_record(ctx, KEY_USERS, 1);
9647 for(; uData; uData = uData->next)
9648 {
9649 if((uData->access >= UL_PRESENT) && uData->present)
9650 high_present = 1;
9651 saxdb_start_record(ctx, uData->handle->handle, 0);
9652 saxdb_write_int(ctx, KEY_LEVEL, uData->access);
9653 saxdb_write_int(ctx, KEY_SEEN, uData->seen);
dfaa28a4 9654 saxdb_write_int(ctx, KEY_ACCESSEXPIRY, uData->accessexpiry);
9655 saxdb_write_int(ctx, KEY_CLVLEXPIRY, uData->clvlexpiry);
9656 saxdb_write_int(ctx, KEY_LASTLEVEL, uData->lastaccess);
d76ed9a9
AS
9657 if(uData->flags)
9658 saxdb_write_int(ctx, KEY_FLAGS, uData->flags);
b10abdb2 9659 if(uData->expires)
9660 saxdb_write_int(ctx, KEY_EXPIRES, uData->expires);
d76ed9a9
AS
9661 if(uData->info)
9662 saxdb_write_string(ctx, KEY_INFO, uData->info);
9663 saxdb_end_record(ctx);
9664 }
9665 saxdb_end_record(ctx);
9666 return high_present;
9667}
9668
9669static void
9670chanserv_write_bans(struct saxdb_context *ctx, struct banData *bData)
9671{
9672 if(!bData)
9673 return;
9674 saxdb_start_record(ctx, KEY_BANS, 1);
9675 for(; bData; bData = bData->next)
9676 {
9677 saxdb_start_record(ctx, bData->mask, 0);
9678 saxdb_write_int(ctx, KEY_SET, bData->set);
9679 if(bData->triggered)
9680 saxdb_write_int(ctx, KEY_TRIGGERED, bData->triggered);
9681 if(bData->expires)
9682 saxdb_write_int(ctx, KEY_EXPIRES, bData->expires);
9683 if(bData->owner[0])
9684 saxdb_write_string(ctx, KEY_OWNER, bData->owner);
9685 if(bData->reason)
9686 saxdb_write_string(ctx, KEY_REASON, bData->reason);
9687 saxdb_end_record(ctx);
9688 }
9689 saxdb_end_record(ctx);
9690}
9691
9692static void
9693chanserv_write_suspended(struct saxdb_context *ctx, const char *name, struct suspended *susp)
9694{
9695 saxdb_start_record(ctx, name, 0);
9696 saxdb_write_string(ctx, KEY_SUSPENDER, susp->suspender);
9697 saxdb_write_string(ctx, KEY_REASON, susp->reason);
9698 if(susp->issued)
9699 saxdb_write_int(ctx, KEY_ISSUED, susp->issued);
9700 if(susp->expires)
9701 saxdb_write_int(ctx, KEY_EXPIRES, susp->expires);
9702 if(susp->revoked)
9703 saxdb_write_int(ctx, KEY_REVOKED, susp->revoked);
9704 if(susp->previous)
9705 chanserv_write_suspended(ctx, KEY_PREVIOUS, susp->previous);
9706 saxdb_end_record(ctx);
9707}
9708
82f37c08 9709static void
9710chanserv_write_giveownership(struct saxdb_context *ctx, const char *name, struct giveownership *giveownership)
9711{
9712 saxdb_start_record(ctx, name, 0);
9713 if(giveownership->staff_issuer)
9714 saxdb_write_string(ctx, KEY_STAFF_ISSUER, giveownership->staff_issuer);
9715 if(giveownership->old_owner)
9716 saxdb_write_string(ctx, KEY_OLD_OWNER, giveownership->old_owner);
9717 if(giveownership->target)
9718 saxdb_write_string(ctx, KEY_TARGET, giveownership->target);
9719 if(giveownership->target_access)
9720 saxdb_write_int(ctx, KEY_TARGET_ACCESS, giveownership->target_access);
9721 if(giveownership->reason)
9722 saxdb_write_string(ctx, KEY_REASON, giveownership->reason);
9723 if(giveownership->issued)
9724 saxdb_write_int(ctx, KEY_ISSUED, giveownership->issued);
9725 if(giveownership->previous)
9726 chanserv_write_giveownership(ctx, KEY_PREVIOUS, giveownership->previous);
9727 saxdb_end_record(ctx);
9728}
9729
d76ed9a9
AS
9730static void
9731chanserv_write_channel(struct saxdb_context *ctx, struct chanData *channel)
9732{
9733 char buf[MAXLEN];
9734 int high_present;
9735 enum levelOption lvlOpt;
9736 enum charOption chOpt;
9737
9738 saxdb_start_record(ctx, channel->channel->name, 1);
9739
9740 saxdb_write_int(ctx, KEY_REGISTERED, channel->registered);
9741 saxdb_write_int(ctx, KEY_MAX, channel->max);
9742 if(channel->topic)
9743 saxdb_write_string(ctx, KEY_TOPIC, channel->topic);
9744 if(channel->registrar)
9745 saxdb_write_string(ctx, KEY_REGISTRAR, channel->registrar);
9746 if(channel->greeting)
9747 saxdb_write_string(ctx, KEY_GREETING, channel->greeting);
9748 if(channel->user_greeting)
9749 saxdb_write_string(ctx, KEY_USER_GREETING, channel->user_greeting);
9750 if(channel->topic_mask)
9751 saxdb_write_string(ctx, KEY_TOPIC_MASK, channel->topic_mask);
9752 if(channel->suspended)
9753 chanserv_write_suspended(ctx, "suspended", channel->suspended);
82f37c08 9754 if(channel->giveownership)
9755 chanserv_write_giveownership(ctx, "giveownership", channel->giveownership);
d76ed9a9
AS
9756
9757 saxdb_start_record(ctx, KEY_OPTIONS, 0);
9758 saxdb_write_int(ctx, KEY_FLAGS, channel->flags);
9759 for(lvlOpt = 0; lvlOpt < NUM_LEVEL_OPTIONS; ++lvlOpt)
9760 saxdb_write_int(ctx, levelOptions[lvlOpt].db_name, channel->lvlOpts[lvlOpt]);
9761 for(chOpt = 0; chOpt < NUM_CHAR_OPTIONS; ++chOpt)
9762 {
9763 buf[0] = channel->chOpts[chOpt];
9764 buf[1] = '\0';
9765 saxdb_write_string(ctx, charOptions[chOpt].db_name, buf);
9766 }
9767 saxdb_end_record(ctx);
9768
8b9e7d45 9769 if (channel->maxsetinfo)
9770 saxdb_write_int(ctx, KEY_MAXSETINFO, channel->maxsetinfo);
9771
d76ed9a9
AS
9772 if(channel->modes.modes_set || channel->modes.modes_clear)
9773 {
9774 mod_chanmode_format(&channel->modes, buf);
9775 saxdb_write_string(ctx, KEY_MODES, buf);
9776 }
9777
9778 high_present = chanserv_write_users(ctx, channel->users);
9779 chanserv_write_bans(ctx, channel->bans);
9780
9781 if(dict_size(channel->notes))
9782 {
9783 dict_iterator_t it;
9784
9785 saxdb_start_record(ctx, KEY_NOTES, 1);
9786 for(it = dict_first(channel->notes); it; it = iter_next(it))
9787 {
9788 struct note *note = iter_data(it);
9789 saxdb_start_record(ctx, iter_key(it), 0);
9790 saxdb_write_string(ctx, KEY_NOTE_SETTER, note->setter);
9791 saxdb_write_string(ctx, KEY_NOTE_NOTE, note->note);
9792 saxdb_end_record(ctx);
9793 }
9794 saxdb_end_record(ctx);
9795 }
9796
a32da4c7 9797 if(channel->ownerTransfer)
9798 saxdb_write_int(ctx, KEY_OWNER_TRANSFER, channel->ownerTransfer);
d76ed9a9
AS
9799 saxdb_write_int(ctx, KEY_VISITED, high_present ? now : channel->visited);
9800 saxdb_end_record(ctx);
9801}
9802
9803static void
9804chanserv_write_note_type(struct saxdb_context *ctx, struct note_type *ntype)
9805{
9806 const char *str;
9807
9808 saxdb_start_record(ctx, ntype->name, 0);
9809 switch(ntype->set_access_type)
9810 {
338a82b5
AS
9811 case NOTE_SET_CHANNEL_ACCESS:
9812 saxdb_write_int(ctx, KEY_NOTE_CHANNEL_ACCESS, ntype->set_access.min_ulevel);
9813 break;
9814 case NOTE_SET_CHANNEL_SETTER:
9815 saxdb_write_int(ctx, KEY_NOTE_SETTER_ACCESS, 1);
9816 break;
9817 case NOTE_SET_PRIVILEGED: default:
9818 saxdb_write_int(ctx, KEY_NOTE_OPSERV_ACCESS, ntype->set_access.min_opserv);
9819 break;
d76ed9a9
AS
9820 }
9821 switch(ntype->visible_type)
9822 {
338a82b5
AS
9823 case NOTE_VIS_ALL: str = KEY_NOTE_VIS_ALL; break;
9824 case NOTE_VIS_CHANNEL_USERS: str = KEY_NOTE_VIS_CHANNEL_USERS; break;
9825 case NOTE_VIS_PRIVILEGED:
9826 default: str = KEY_NOTE_VIS_PRIVILEGED; break;
d76ed9a9
AS
9827 }
9828 saxdb_write_string(ctx, KEY_NOTE_VISIBILITY, str);
9829 saxdb_write_int(ctx, KEY_NOTE_MAX_LENGTH, ntype->max_length);
9830 saxdb_end_record(ctx);
9831}
9832
9833static void
9834write_dnrs_helper(struct saxdb_context *ctx, struct dict *dnrs)
9835{
9836 struct do_not_register *dnr;
1136f709 9837 dict_iterator_t it, next;
d76ed9a9 9838
1136f709 9839 for(it = dict_first(dnrs); it; it = next)
d76ed9a9 9840 {
1136f709 9841 next = iter_next(it);
d76ed9a9 9842 dnr = iter_data(it);
1136f709 9843 if(dnr->expires && dnr->expires <= now)
9844 continue;
d76ed9a9
AS
9845 saxdb_start_record(ctx, dnr->chan_name, 0);
9846 if(dnr->set)
9847 saxdb_write_int(ctx, KEY_DNR_SET, dnr->set);
1136f709 9848 if(dnr->expires)
9849 {
9850 dict_remove(dnrs, iter_key(it));
9851 saxdb_write_int(ctx, KEY_EXPIRES, dnr->expires);
9852 }
d76ed9a9
AS
9853 saxdb_write_string(ctx, KEY_DNR_SETTER, dnr->setter);
9854 saxdb_write_string(ctx, KEY_DNR_REASON, dnr->reason);
9855 saxdb_end_record(ctx);
9856 }
9857}
9858
9859static int
9860chanserv_saxdb_write(struct saxdb_context *ctx)
9861{
9862 dict_iterator_t it;
9863 struct chanData *channel;
9864
c8ca69a0
AS
9865 /* Version Control*/
9866 saxdb_start_record(ctx, KEY_VERSION_CONTROL, 1);
9867 saxdb_write_int(ctx, KEY_VERSION_NUMBER, CHANSERV_DB_VERSION);
9868 saxdb_end_record(ctx);
9869
d76ed9a9
AS
9870 /* Notes */
9871 saxdb_start_record(ctx, KEY_NOTE_TYPES, 1);
9872 for(it = dict_first(note_types); it; it = iter_next(it))
9873 chanserv_write_note_type(ctx, iter_data(it));
9874 saxdb_end_record(ctx);
9875
9876 /* DNRs */
9877 saxdb_start_record(ctx, KEY_DNR, 1);
9878 write_dnrs_helper(ctx, handle_dnrs);
9879 write_dnrs_helper(ctx, plain_dnrs);
9880 write_dnrs_helper(ctx, mask_dnrs);
9881 saxdb_end_record(ctx);
9882
9883 /* Channels */
9884 saxdb_start_record(ctx, KEY_CHANNELS, 1);
9885 for(channel = channelList; channel; channel = channel->next)
9886 chanserv_write_channel(ctx, channel);
9887 saxdb_end_record(ctx);
9888
9889 return 0;
9890}
9891
9892static void
9893chanserv_db_cleanup(void) {
9894 unsigned int ii;
9895 unreg_part_func(handle_part);
9896 while(channelList)
9897 unregister_channel(channelList, "terminating.");
9898 for(ii = 0; ii < chanserv_conf.support_channels.used; ++ii)
9899 UnlockChannel(chanserv_conf.support_channels.list[ii]);
9900 free(chanserv_conf.support_channels.list);
9901 dict_delete(handle_dnrs);
9902 dict_delete(plain_dnrs);
9903 dict_delete(mask_dnrs);
9904 dict_delete(note_types);
9905 free_string_list(chanserv_conf.eightball);
9906 free_string_list(chanserv_conf.old_ban_names);
5e6460e4 9907 free_string_list(chanserv_conf.wheel);
d76ed9a9
AS
9908 free_string_list(chanserv_conf.set_shows);
9909 free(set_shows_list.list);
9910 free(uset_shows_list.list);
9911 while(helperList)
9912 {
9913 struct userData *helper = helperList;
9914 helperList = helperList->next;
9915 free(helper);
9916 }
9917}
9918
1136f709 9919#if defined(GCC_VARMACROS)
9920# define DEFINE_COMMAND(NAME, MIN_ARGC, FLAGS, ARGS...) modcmd_register(chanserv_module, #NAME, cmd_##NAME, MIN_ARGC, FLAGS, ARGS)
9921#elif defined(C99_VARMACROS)
9922# define DEFINE_COMMAND(NAME, MIN_ARGC, FLAGS, ...) modcmd_register(chanserv_module, #NAME, cmd_##NAME, MIN_ARGC, FLAGS, __VA_ARGS__)
9923#endif
d76ed9a9
AS
9924#define DEFINE_CHANNEL_OPTION(NAME) modcmd_register(chanserv_module, "set "#NAME, chan_opt_##NAME, 1, 0, NULL)
9925#define DEFINE_USER_OPTION(NAME) modcmd_register(chanserv_module, "uset "#NAME, user_opt_##NAME, 1, MODCMD_REQUIRE_REGCHAN, NULL)
9926
9927void
9928init_chanserv(const char *nick)
9929{
7637f48f 9930 struct chanNode *chan;
e03ec3dc 9931 unsigned int i;
87708af4 9932
d76ed9a9
AS
9933 CS_LOG = log_register_type("ChanServ", "file:chanserv.log");
9934 conf_register_reload(chanserv_conf_read);
9935
1136f709 9936 if (nick) {
9937 reg_server_link_func(handle_server_link);
9938 reg_new_channel_func(handle_new_channel);
9939 reg_join_func(handle_join);
9940 reg_part_func(handle_part);
9941 reg_kick_func(handle_kick);
9942 reg_topic_func(handle_topic);
9943 reg_mode_change_func(handle_mode);
9944 reg_nick_change_func(handle_nick_change);
9945 reg_auth_func(handle_auth);
9946 }
ff3b058a 9947
d76ed9a9
AS
9948 reg_handle_rename_func(handle_rename);
9949 reg_unreg_func(handle_unreg);
9950
9951 handle_dnrs = dict_new();
9952 dict_set_free_data(handle_dnrs, free);
9953 plain_dnrs = dict_new();
9954 dict_set_free_data(plain_dnrs, free);
9955 mask_dnrs = dict_new();
9956 dict_set_free_data(mask_dnrs, free);
9957
9958 reg_svccmd_unbind_func(handle_svccmd_unbind);
9959 chanserv_module = module_register("ChanServ", CS_LOG, "chanserv.help", chanserv_expand_variable);
f21ec816 9960 DEFINE_COMMAND(register, 1, MODCMD_REQUIRE_AUTHED, "flags", "+acceptchan,+channel", NULL);
d76ed9a9
AS
9961 DEFINE_COMMAND(noregister, 1, MODCMD_REQUIRE_AUTHED, "flags", "+helping", NULL);
9962 DEFINE_COMMAND(allowregister, 2, 0, "template", "noregister", NULL);
1136f709 9963 DEFINE_COMMAND(dnrsearch, 3, 0, "template", "noregister", NULL);
9964 modcmd_register(chanserv_module, "dnrsearch print", NULL, 0, 0, NULL);
9965 modcmd_register(chanserv_module, "dnrsearch remove", NULL, 0, 0, NULL);
9966 modcmd_register(chanserv_module, "dnrsearch count", NULL, 0, 0, NULL);
d76ed9a9
AS
9967 DEFINE_COMMAND(move, 1, MODCMD_REQUIRE_AUTHED|MODCMD_REQUIRE_REGCHAN, "template", "register", NULL);
9968 DEFINE_COMMAND(csuspend, 2, MODCMD_REQUIRE_AUTHED|MODCMD_REQUIRE_REGCHAN, "flags", "+helping", NULL);
9969 DEFINE_COMMAND(cunsuspend, 1, MODCMD_REQUIRE_AUTHED|MODCMD_REQUIRE_REGCHAN, "flags", "+helping", NULL);
9970 DEFINE_COMMAND(createnote, 5, 0, "level", "800", NULL);
9971 DEFINE_COMMAND(removenote, 2, 0, "level", "800", NULL);
9972
ac3bdc8d
AS
9973 DEFINE_COMMAND(pending, 1, MODCMD_REQUIRE_AUTHED, "flags", "+helping", NULL);
9974
d76ed9a9
AS
9975 DEFINE_COMMAND(unregister, 1, MODCMD_REQUIRE_AUTHED|MODCMD_REQUIRE_REGCHAN, "flags", "+loghostmask", NULL);
9976 DEFINE_COMMAND(merge, 2, MODCMD_REQUIRE_AUTHED|MODCMD_REQUIRE_REGCHAN, "access", "owner", NULL);
9977
4048352e
AS
9978 DEFINE_COMMAND(adduser, 3, MODCMD_REQUIRE_CHANUSER, "access", "manager", NULL);
9979 DEFINE_COMMAND(deluser, 2, MODCMD_REQUIRE_CHANUSER, "access", "manager", NULL);
9980 DEFINE_COMMAND(suspend, 2, MODCMD_REQUIRE_CHANUSER, "access", "manager", NULL);
9981 DEFINE_COMMAND(unsuspend, 2, MODCMD_REQUIRE_CHANUSER, "access", "manager", NULL);
d76ed9a9
AS
9982 DEFINE_COMMAND(deleteme, 1, MODCMD_REQUIRE_CHANUSER, NULL);
9983
9984 DEFINE_COMMAND(mdelowner, 2, MODCMD_REQUIRE_CHANUSER, "flags", "+helping", NULL);
9985 DEFINE_COMMAND(mdelcoowner, 2, MODCMD_REQUIRE_CHANUSER, "access", "owner", NULL);
4048352e
AS
9986 DEFINE_COMMAND(mdelmanager, 2, MODCMD_REQUIRE_CHANUSER, "access", "coowner", NULL);
9987 DEFINE_COMMAND(mdelop, 2, MODCMD_REQUIRE_CHANUSER, "access", "manager", NULL);
9988 DEFINE_COMMAND(mdelpeon, 2, MODCMD_REQUIRE_CHANUSER, "access", "manager", NULL);
2a4ca4f5 9989 DEFINE_COMMAND(mdelpal, 2, MODCMD_REQUIRE_CHANUSER, "access", "manager", NULL);
55342ce8 9990 DEFINE_COMMAND(mdelhalfop, 2, MODCMD_REQUIRE_CHANUSER, "access", "manager", NULL);
d76ed9a9 9991
c9bf23fe
AS
9992 DEFINE_COMMAND(levels, 1, 0, NULL);
9993
4048352e 9994 DEFINE_COMMAND(trim, 3, MODCMD_REQUIRE_CHANUSER, "access", "manager", NULL);
d76ed9a9 9995 DEFINE_COMMAND(opchan, 1, MODCMD_REQUIRE_REGCHAN|MODCMD_NEVER_CSUSPEND, "access", "1", NULL);
4048352e 9996 DEFINE_COMMAND(clvl, 3, MODCMD_REQUIRE_CHANUSER, "access", "manager", NULL);
d76ed9a9
AS
9997 DEFINE_COMMAND(giveownership, 2, MODCMD_REQUIRE_CHANUSER, "access", "owner", "flags", "+loghostmask", NULL);
9998
9999 DEFINE_COMMAND(up, 1, MODCMD_REQUIRE_CHANUSER, NULL);
10000 DEFINE_COMMAND(down, 1, MODCMD_REQUIRE_REGCHAN, NULL);
10001 DEFINE_COMMAND(upall, 1, MODCMD_REQUIRE_AUTHED, NULL);
10002 DEFINE_COMMAND(downall, 1, MODCMD_REQUIRE_AUTHED, NULL);
55342ce8 10003 DEFINE_COMMAND(hop, 2, MODCMD_REQUIRE_CHANNEL, "access", "op", NULL);
d76ed9a9
AS
10004 DEFINE_COMMAND(op, 2, MODCMD_REQUIRE_CHANNEL, "access", "op", NULL);
10005 DEFINE_COMMAND(deop, 2, MODCMD_REQUIRE_CHANNEL, "template", "op", NULL);
55342ce8 10006 DEFINE_COMMAND(dehop, 2, MODCMD_REQUIRE_CHANNEL, "template", "op", NULL);
d76ed9a9
AS
10007 DEFINE_COMMAND(voice, 2, MODCMD_REQUIRE_CHANNEL, "template", "op", NULL);
10008 DEFINE_COMMAND(devoice, 2, MODCMD_REQUIRE_CHANNEL, "template", "op", NULL);
10009
c8273589
AS
10010 DEFINE_COMMAND(kickban, 2, MODCMD_REQUIRE_REGCHAN, "template", "hop", NULL);
10011 DEFINE_COMMAND(kick, 2, MODCMD_REQUIRE_REGCHAN, "template", "hop", NULL);
10012 DEFINE_COMMAND(ban, 2, MODCMD_REQUIRE_REGCHAN, "template", "hop", NULL);
10013 DEFINE_COMMAND(unban, 2, 0, "template", "hop", NULL);
10014 DEFINE_COMMAND(unbanall, 1, 0, "template", "hop", NULL);
10015 DEFINE_COMMAND(unbanme, 1, MODCMD_REQUIRE_CHANUSER, "template", "hop", NULL);
d76ed9a9 10016 DEFINE_COMMAND(open, 1, MODCMD_REQUIRE_CHANUSER, "template", "op", NULL);
c8273589 10017 DEFINE_COMMAND(topic, 1, MODCMD_REQUIRE_REGCHAN, "template", "hop", "flags", "+never_csuspend", NULL);
d76ed9a9
AS
10018 DEFINE_COMMAND(mode, 1, MODCMD_REQUIRE_REGCHAN, "template", "op", NULL);
10019 DEFINE_COMMAND(inviteme, 1, MODCMD_REQUIRE_CHANNEL, "access", "1", NULL);
4048352e 10020 DEFINE_COMMAND(invite, 1, MODCMD_REQUIRE_CHANNEL, "access", "manager", NULL);
d76ed9a9 10021 DEFINE_COMMAND(set, 1, MODCMD_REQUIRE_CHANUSER, "access", "op", NULL);
4048352e
AS
10022 DEFINE_COMMAND(wipeinfo, 2, MODCMD_REQUIRE_CHANUSER, "access", "manager", NULL);
10023 DEFINE_COMMAND(resync, 1, MODCMD_REQUIRE_CHANUSER, "access", "manager", NULL);
d76ed9a9 10024
c8273589 10025 DEFINE_COMMAND(events, 1, MODCMD_REQUIRE_REGCHAN, "flags", "+nolog", "access", "manager", NULL);
23475fc6 10026 DEFINE_COMMAND(last, 1, MODCMD_REQUIRE_REGCHAN, "flags", "+nolog", "access", "manager", NULL);
c8273589
AS
10027 DEFINE_COMMAND(addlamer, 2, MODCMD_REQUIRE_REGCHAN, "access", "manager", NULL);
10028 DEFINE_COMMAND(addtimedlamer, 3, MODCMD_REQUIRE_REGCHAN, "access", "manager", NULL);
10029
10030 /* if you change dellamer access, see also places
10031 * like unbanme which have manager hardcoded. */
10032 DEFINE_COMMAND(dellamer, 2, MODCMD_REQUIRE_REGCHAN, "access", "manager", NULL);
d76ed9a9
AS
10033 DEFINE_COMMAND(uset, 1, MODCMD_REQUIRE_CHANUSER, "access", "1", NULL);
10034
c8273589
AS
10035 DEFINE_COMMAND(lamers, 1, MODCMD_REQUIRE_REGCHAN, "access", "1", "flags", "+nolog", NULL);
10036
d76ed9a9
AS
10037 DEFINE_COMMAND(peek, 1, MODCMD_REQUIRE_REGCHAN, "access", "op", "flags", "+nolog", NULL);
10038
10039 DEFINE_COMMAND(myaccess, 1, MODCMD_REQUIRE_AUTHED, NULL);
10040 DEFINE_COMMAND(access, 1, MODCMD_REQUIRE_REGCHAN, "flags", "+nolog,+joinable", NULL);
10041 DEFINE_COMMAND(users, 1, MODCMD_REQUIRE_REGCHAN, "flags", "+nolog,+joinable", NULL);
10042 DEFINE_COMMAND(wlist, 1, MODCMD_REQUIRE_REGCHAN, "flags", "+nolog,+joinable", NULL);
10043 DEFINE_COMMAND(clist, 1, MODCMD_REQUIRE_REGCHAN, "flags", "+nolog,+joinable", NULL);
10044 DEFINE_COMMAND(mlist, 1, MODCMD_REQUIRE_REGCHAN, "flags", "+nolog,+joinable", NULL);
10045 DEFINE_COMMAND(olist, 1, MODCMD_REQUIRE_REGCHAN, "flags", "+nolog,+joinable", NULL);
55342ce8 10046 DEFINE_COMMAND(hlist, 1, MODCMD_REQUIRE_REGCHAN, "flags", "+nolog,+joinable", NULL);
d76ed9a9
AS
10047 DEFINE_COMMAND(plist, 1, MODCMD_REQUIRE_REGCHAN, "flags", "+nolog,+joinable", NULL);
10048 DEFINE_COMMAND(info, 1, MODCMD_REQUIRE_REGCHAN, "flags", "+nolog,+joinable", NULL);
10049 DEFINE_COMMAND(seen, 2, MODCMD_REQUIRE_REGCHAN, "flags", "+nolog,+joinable", NULL);
10050 DEFINE_COMMAND(names, 1, MODCMD_REQUIRE_REGCHAN, "flags", "+nolog,+joinable", NULL);
10051
10052 DEFINE_COMMAND(note, 1, MODCMD_REQUIRE_REGCHAN, "flags", "+joinable,+acceptchan", NULL);
10053 DEFINE_COMMAND(delnote, 2, MODCMD_REQUIRE_CHANUSER, NULL);
10054
10055 DEFINE_COMMAND(netinfo, 1, 0, "flags", "+nolog", NULL);
10056 DEFINE_COMMAND(ircops, 1, 0, "flags", "+nolog", NULL);
10057 DEFINE_COMMAND(helpers, 1, 0, "flags", "+nolog", NULL);
10058 DEFINE_COMMAND(staff, 1, 0, "flags", "+nolog", NULL);
10059
10060 DEFINE_COMMAND(say, 2, 0, "flags", "+oper,+acceptchan", NULL);
10061 DEFINE_COMMAND(emote, 2, 0, "flags", "+oper,+acceptchan", NULL);
10062 DEFINE_COMMAND(expire, 1, 0, "flags", "+oper", NULL);
10063 DEFINE_COMMAND(search, 3, 0, "flags", "+nolog,+helping", NULL);
10064 DEFINE_COMMAND(unvisited, 1, 0, "flags", "+nolog,+helping", NULL);
10065
10066 DEFINE_COMMAND(unf, 1, 0, "flags", "+nolog,+toy,+acceptchan", NULL);
10067 DEFINE_COMMAND(ping, 1, 0, "flags", "+nolog,+toy,+acceptchan", NULL);
10068 DEFINE_COMMAND(wut, 1, 0, "flags", "+nolog,+toy,+acceptchan", NULL);
10069 DEFINE_COMMAND(8ball, 1, 0, "flags", "+nolog,+toy,+acceptchan", NULL);
10070 DEFINE_COMMAND(d, 1, 0, "flags", "+nolog,+toy,+acceptchan", NULL);
10071 DEFINE_COMMAND(huggle, 1, 0, "flags", "+nolog,+toy,+acceptchan", NULL);
b8daef0f 10072 DEFINE_COMMAND(calc, 1, 0, "flags", "+nolog,+toy,+acceptchan", NULL);
33741441 10073 DEFINE_COMMAND(reply, 1, 0, "flags", "+nolog,+toy,+acceptchan", NULL);
87708af4 10074 DEFINE_COMMAND(roulette, 1, 0, "flags", "+nolog,+toy,+acceptchan", NULL);
10075 DEFINE_COMMAND(shoot, 1, 0, "flags", "+nolog,+toy,+acceptchan", NULL);
ea54b136 10076 DEFINE_COMMAND(spin, 1, MODCMD_REQUIRE_AUTHED, "spin", "+nolog,+toy,+acceptchan", NULL);
b404335b 10077
d76ed9a9
AS
10078 /* Channel options */
10079 DEFINE_CHANNEL_OPTION(defaulttopic);
10080 DEFINE_CHANNEL_OPTION(topicmask);
10081 DEFINE_CHANNEL_OPTION(greeting);
10082 DEFINE_CHANNEL_OPTION(usergreeting);
10083 DEFINE_CHANNEL_OPTION(modes);
10084 DEFINE_CHANNEL_OPTION(enfops);
55342ce8 10085 DEFINE_CHANNEL_OPTION(enfhalfops);
4b6129c0 10086 DEFINE_CHANNEL_OPTION(automode);
d76ed9a9
AS
10087 DEFINE_CHANNEL_OPTION(protect);
10088 DEFINE_CHANNEL_OPTION(enfmodes);
10089 DEFINE_CHANNEL_OPTION(enftopic);
10090 DEFINE_CHANNEL_OPTION(pubcmd);
d76ed9a9
AS
10091 DEFINE_CHANNEL_OPTION(userinfo);
10092 DEFINE_CHANNEL_OPTION(dynlimit);
10093 DEFINE_CHANNEL_OPTION(topicsnarf);
10094 DEFINE_CHANNEL_OPTION(nodelete);
10095 DEFINE_CHANNEL_OPTION(toys);
10096 DEFINE_CHANNEL_OPTION(setters);
10097 DEFINE_CHANNEL_OPTION(topicrefresh);
7637f48f 10098 DEFINE_CHANNEL_OPTION(resync);
d76ed9a9 10099 DEFINE_CHANNEL_OPTION(ctcpreaction);
31f23f13 10100 DEFINE_CHANNEL_OPTION(bantimeout);
d76ed9a9 10101 DEFINE_CHANNEL_OPTION(inviteme);
1136f709 10102 DEFINE_CHANNEL_OPTION(unreviewed);
10103 modcmd_register(chanserv_module, "set unreviewed on", NULL, 0, 0, "flags", "+helping", NULL);
10104 modcmd_register(chanserv_module, "set unreviewed off", NULL, 0, 0, "flags", "+oper", NULL);
8b9e7d45 10105 DEFINE_CHANNEL_OPTION(maxsetinfo);
1117fc5a 10106 if(off_channel > 1)
d76ed9a9
AS
10107 DEFINE_CHANNEL_OPTION(offchannel);
10108 modcmd_register(chanserv_module, "set defaults", chan_opt_defaults, 1, 0, "access", "owner", NULL);
10109
10110 /* Alias set topic to set defaulttopic for compatibility. */
10111 modcmd_register(chanserv_module, "set topic", chan_opt_defaulttopic, 1, 0, NULL);
10112
10113 /* User options */
d76ed9a9 10114 DEFINE_USER_OPTION(autoinvite);
cd25f2e9 10115 DEFINE_USER_OPTION(autojoin);
d76ed9a9 10116 DEFINE_USER_OPTION(info);
c8ca69a0 10117 DEFINE_USER_OPTION(autoop);
d76ed9a9
AS
10118
10119 /* Alias uset autovoice to uset autoop. */
c8ca69a0 10120 modcmd_register(chanserv_module, "uset autovoice", user_opt_autoop, 1, 0, NULL);
d76ed9a9
AS
10121
10122 note_types = dict_new();
10123 dict_set_free_data(note_types, chanserv_deref_note_type);
10124 if(nick)
10125 {
a32da4c7 10126 const char *modes = conf_get_data("services/chanserv/modes", RECDB_QSTRING);
1136f709 10127 chanserv = AddLocalUser(nick, nick, NULL, "Channel Services", modes);
d76ed9a9
AS
10128 service_register(chanserv)->trigger = '!';
10129 reg_chanmsg_func('\001', chanserv, chanserv_ctcp_check);
10130 }
c8ca69a0 10131
d76ed9a9
AS
10132 saxdb_register("ChanServ", chanserv_saxdb_read, chanserv_saxdb_write);
10133
10134 if(chanserv_conf.channel_expire_frequency)
10135 timeq_add(now + chanserv_conf.channel_expire_frequency, expire_channels, NULL);
10136
1136f709 10137 if(chanserv_conf.dnr_expire_frequency)
10138 timeq_add(now + chanserv_conf.dnr_expire_frequency, expire_dnrs, NULL);
10139
31f23f13
AS
10140 if(chanserv_conf.ban_timeout_frequency)
10141 timeq_add(now + chanserv_conf.ban_timeout_frequency, expire_bans, NULL);
10142
d76ed9a9
AS
10143 if(chanserv_conf.refresh_period)
10144 {
10145 time_t next_refresh;
10146 next_refresh = (now + chanserv_conf.refresh_period - 1) / chanserv_conf.refresh_period * chanserv_conf.refresh_period;
10147 timeq_add(next_refresh, chanserv_refresh_topics, NULL);
7637f48f 10148 timeq_add(next_refresh, chanserv_auto_resync, NULL);
10149 }
10150
10151 if (autojoin_channels && chanserv) {
10152 for (i = 0; i < autojoin_channels->used; i++) {
10153 chan = AddChannel(autojoin_channels->list[i], now, "+nt", NULL, NULL);
10154 AddChannelUser(chanserv, chan)->modes |= MODE_CHANOP;
10155 }
d76ed9a9 10156 }
1117fc5a 10157
d76ed9a9
AS
10158 reg_exit_func(chanserv_db_cleanup);
10159 message_register_table(msgtab);
10160}
dfaa28a4 10161