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