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