]> jfr.im git - irc/SurrealServices/srsv.git/blame - branches/0.4.3/modules/serviceslibs/chanserv.pm
fix typo in clones_exist()
[irc/SurrealServices/srsv.git] / branches / 0.4.3 / modules / serviceslibs / chanserv.pm
CommitLineData
7b261bb8 1# This file is part of SurrealServices.
2#
3# SurrealServices is free software; you can redistribute it and/or modify
4# it under the terms of the GNU General Public License as published by
5# the Free Software Foundation; either version 2 of the License, or
6# (at your option) any later version.
7#
8# SurrealServices is distributed in the hope that it will be useful,
9# but WITHOUT ANY WARRANTY; without even the implied warranty of
10# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11# GNU General Public License for more details.
12#
13# You should have received a copy of the GNU General Public License
14# along with SurrealServices; if not, write to the Free Software
15# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
16package chanserv;
17
18use strict;
7b261bb8 19
20use SrSv::Timer qw(add_timer);
44b46f5b 21use Data::Dumper;
7b261bb8 22use SrSv::Message qw(current_message);
23use SrSv::IRCd::State qw($ircline synced initial_synced %IRCd_capabilities);
24use SrSv::Message qw(message current_message);
25use SrSv::HostMask qw(normalize_hostmask make_hostmask parse_mask);
8f6b92b2 26#FIXME: This needs to be abstracted into a proper SrSv::IRCd module
7b261bb8 27use SrSv::Unreal::Modes qw(@opmodes %opmodes $scm $ocm $acm sanitize_mlockable);
8f6b92b2 28use SrSv::IRCd::Validate qw( valid_nick validate_chmodes validate_ban );
7b261bb8 29use SrSv::Agent;
30
31use SrSv::Shared qw(%enforcers $chanuser_table);
32
33#use SrSv::Conf qw(services);
f80900e4 34use SrSv::Conf2Consts qw( services sql main );
7b261bb8 35
36use SrSv::Time;
37use SrSv::Text::Format qw( columnar enum );
38use SrSv::Errors;
39
40use SrSv::Log;
41
faaf4d4c 42use SrSv::User qw(
43 get_user_nick get_user_agent get_user_id
44 is_online :user_flags get_host get_vhost
45 :flags :flood
46 );
7b261bb8 47use SrSv::User::Notice;
48use SrSv::Help qw( sendhelp );
49
50use SrSv::ChanReg::Flags;
51
52use SrSv::NickReg::Flags;
a36c659a 53use SrSv::NickReg::NickText;
7b261bb8 54use SrSv::NickReg::User qw(is_identified get_nick_users get_nick_user_nicks);
55
b4760065 56use SrSv::MySQL qw( $dbh :sql_types );
7b261bb8 57use SrSv::MySQL::Glob;
58
59use SrSv::Util qw( makeSeqList );
60
61use constant {
62 UOP => 1,
63 VOP => 2,
64 HOP => 3,
65 AOP => 4,
66 SOP => 5,
67 COFOUNDER => 6,
68 FOUNDER => 7,
69
70 # Maybe this should be a config option
71 DEFAULT_BANTYPE => 10,
72
73 CRT_TOPIC => 1,
74 CRT_AKICK => 2,
75};
76
77*get_root_nick = \&nickserv::get_root_nick;
78
79our @levels = ("no", "UOp", "VOp", "HOp", "AOp", "SOp", "co-founder", "founder");
80our @ops;
81if(!ircd::PREFIXAQ_DISABLE()) {
82 @ops = (0, 0, 1, 2, 4, 8, 16, 16); # PREFIX_AQ
83} else { # lame IRC scripts and admins who don't enable PREFIX_AQ
84 @ops = (0, 0, 1, 2, 4, 12, 20, 20); # normal
85}
86our @plevels = ('AKICK', 'anyone', 'UOp', 'VOp', 'HOp', 'AOp', 'SOp', 'co-founder', 'founder', 'disabled');
87our $plzero = 1;
88
89our @override = (
90 ['SERVOP',
91 {
92 ACCCHANGE => 1,
93 SET => 1,
94 MEMO => 1,
95 SETTOPIC => 1,
96 AKICK => 1,
97 LEVELS => 1,
98 COPY => 1,
42508b05 99 WELCOME => 1,
7b261bb8 100 }
101 ],
102 ['SUPER',
103 {
104 BAN => 1,
764bd447 105 UNBANSELF => 1,
22a334cc 106 UNBAN => 1,
7b261bb8 107 KICK => 1,
108 VOICE => 1,
109 HALFOP => 1,
110 OP => 1,
111 ADMIN => 1,
112 OWNER => 1,
113 SETTOPIC => 1,
114 INVITE => 1,
115 INVITESELF => 1,
7b261bb8 116 CLEAR => 1,
117 AKICKENFORCE => 1,
118 UPDOWN => 1,
119 MODE => 1,
120 }
121 ],
122 ['HELP',
123 {
8239d128 124 JOIN => 1,
7b261bb8 125 ACCLIST => 1,
126 LEVELSLIST => 1,
127 AKICKLIST => 1,
128 INFO => 1,
129 GETKEY => 1
130 }
131 ],
132 ['BOT',
133 {
134 BOTSAY => 1,
135 BOTASSIGN => 1
136 }
137 ]
138);
139
140$chanuser_table = 0;
141
142our $csnick_default = 'ChanServ';
143our $csnick = $csnick_default;
144
145our ($cur_lock, $cnt_lock);
146
147our (
148 $get_joinpart_lock, $get_modelock_lock, $get_update_modes_lock,
149
0b380dbc 150 $chanjoin, $chanpart, $chanpart2, $chop, $chdeop, $get_op, $get_user_chans, $get_user_chans_recent,
7b261bb8 151 $get_all_closed_chans, $get_user_count,
152
153 $is_in_chan,
154
155 #$lock_chanuser, $get_all_chan_users,
156 $unlock_tables,
157 $get_chan_users, $get_chan_users_noacc, $get_chan_users_mask, $get_chan_users_mask_noacc,
158
159 $get_users_nochans, $get_users_nochans_noid,
160
161 $get_using_nick_chans,
162
163 $get_lock, $release_lock, $is_free_lock,
164
165 $chan_create, $chan_delete, $get_chanmodes, $set_chanmodes,
166
167 $is_registered, $get_modelock, $set_modelock, $set_descrip,
168
169 $get_topic, $set_topic1, $set_topic2,
170
171 $get_acc, $set_acc1, $set_acc2, $del_acc, $get_acc_list, $get_acc_list2, $get_acc_list_mask, $get_acc_list2_mask,
172 $wipe_acc_list,
173 $get_best_acc, $get_all_acc, $get_highrank, $get_acc_count,
174 $copy_acc, $copy_acc_rank,
175
176 $get_eos_lock, $get_status_all, $get_status_all_server, $get_modelock_all,
177
178 $get_akick, $get_akick_allchan, $get_akick_alluser, $get_akick_all, $add_akick, $del_akick,
179 $get_akick_list, $get_akick_by_num,
180
181 $add_nick_akick, $del_nick_akick, $get_nick_akick, $drop_nick_akick,
182 $copy_akick,
183
184 $is_level, $get_level, $get_levels, $add_level, $set_level, $reset_level, $clear_levels, $get_level_max,
185 $copy_levels,
186
187 $get_founder, $get_successor,
188 $set_founder, $set_successor, $del_successor,
189
190 $get_nick_own_chans, $delete_successors,
191
7b261bb8 192 $get_info,
193
194 $register, $drop_acc, $drop_lvl, $drop_akick, $drop,
195 $copy_chanreg,
196
197 $get_expired,
198
199 $get_close, $set_close, $del_close,
200
201 $add_welcome, $del_welcome, $list_welcome, $get_welcomes, $drop_welcome,
202 $count_welcome, $consolidate_welcome,
203
204 $add_ban, $delete_bans, $delete_ban,
205 $get_all_bans, $get_ban_num,
206 $find_bans, $list_bans, $wipe_bans,
207 $find_bans_chan_user, $delete_bans_chan_user,
208
209 $add_auth, $list_auth_chan, $get_auth_nick, $get_auth_num, $find_auth,
210
211 $set_bantype, $get_bantype,
212
213 $drop_chantext, $drop_nicktext,
0b380dbc 214 $get_host,
215 $get_host_inchan,
44b46f5b 216 $add_tempban,
217 $get_expired_bans,
218 $del_tmpban,
219 $get_bantime,
220 $set_bantime,
7b261bb8 221);
222
223sub init() {
224 #$chan_create = $dbh->prepare("INSERT IGNORE INTO chan SET id=(RAND()*294967293)+1, chan=?");
225 $get_joinpart_lock = $dbh->prepare("LOCK TABLES chan WRITE, chanuser WRITE");
226 $get_modelock_lock = $dbh->prepare("LOCK TABLES chanreg READ LOCAL, chan WRITE");
227 $get_update_modes_lock = $dbh->prepare("LOCK TABLES chan WRITE");
228
229 $chanjoin = $dbh->prepare("REPLACE INTO chanuser (seq,nickid,chan,op,joined) VALUES (?, ?, ?, ?, 1)");
230 $chanpart = $dbh->prepare("UPDATE chanuser SET joined=0, seq=?
231 WHERE nickid=? AND chan=? AND (seq <= ? OR seq > ?)");
0b380dbc 232 $chanpart2 = $dbh->prepare("UPDATE chanuser SET joined=0 WHERE nickid=? AND chan=?");
7b261bb8 233 #$chop = $dbh->prepare("UPDATE chanuser SET op=op+? WHERE nickid=? AND chan=?");
234 $chop = $dbh->prepare("UPDATE chanuser SET op=IF(op & ?, op, op ^ ?) WHERE nickid=? AND chan=?");
235 $chdeop = $dbh->prepare("UPDATE chanuser SET op=IF(op & ?, op ^ ?, op) WHERE nickid=? AND chan=?");
236 $get_op = $dbh->prepare("SELECT op FROM chanuser WHERE nickid=? AND chan=?");
237 $get_user_chans = $dbh->prepare("SELECT chan, op FROM chanuser WHERE nickid=? AND joined=1 AND (seq <= ? OR seq > ?)");
238 $get_user_chans_recent = $dbh->prepare("SELECT chan, joined, op FROM chanuser WHERE nickid=?");
239
240 $get_all_closed_chans = $dbh->prepare("SELECT chanclose.chan, chanclose.type, chanclose.reason, chanclose.nick, chanclose.time FROM chanreg, chanuser, chanclose WHERE chanreg.chan=chanuser.chan AND chanreg.chan=chanclose.chan AND chanreg.flags & ? GROUP BY chanclose.chan ORDER BY NULL");
241 $get_user_count = $dbh->prepare("SELECT COUNT(*) FROM chanuser WHERE chan=? AND joined=1");
242
243 $is_in_chan = $dbh->prepare("SELECT 1 FROM chanuser WHERE nickid=? AND chan=? AND joined=1");
244
245 #$lock_chanuser = $dbh->prepare("LOCK TABLES chanuser READ, user READ");
246 #$get_all_chan_users = $dbh->prepare("SELECT user.nick, chanuser.nickid, chanuser.chan FROM chanuser, user WHERE user.id=chanuser.nickid AND chanuser.joined=1");
247 $unlock_tables = $dbh->prepare("UNLOCK TABLES");
248
249 $get_chan_users = $dbh->prepare("SELECT user.nick, user.id FROM chanuser, user
250 WHERE chanuser.chan=? AND user.id=chanuser.nickid AND chanuser.joined=1");
251 my $chan_users_noacc_tables = 'user '.
252 'JOIN chanuser ON (chanuser.nickid=user.id AND chanuser.joined=1 AND user.online=1) '.
253 'LEFT JOIN nickid ON (chanuser.nickid=nickid.id) '.
254 'LEFT JOIN chanacc ON (nickid.nrid=chanacc.nrid AND chanuser.chan=chanacc.chan)';
255 $get_chan_users_noacc = $dbh->prepare("SELECT user.nick, user.id FROM $chan_users_noacc_tables
256 WHERE chanuser.chan=?
257 GROUP BY user.id HAVING MAX(IF(chanacc.level IS NULL, 0, chanacc.level)) <= 0
258 ORDER BY NULL");
259 my $check_mask = "((user.nick LIKE ?) AND (user.ident LIKE ?)
260 AND ((user.vhost LIKE ?) OR (user.host LIKE ?) OR (user.cloakhost LIKE ?)))";
261 $get_chan_users_mask = $dbh->prepare("SELECT user.nick, user.id FROM chanuser, user
262 WHERE chanuser.chan=? AND user.id=chanuser.nickid AND chanuser.joined=1 AND $check_mask");
263 $get_chan_users_mask_noacc = $dbh->prepare("SELECT user.nick, user.id FROM $chan_users_noacc_tables
264 WHERE chanuser.chan=? AND $check_mask
265 GROUP BY user.id HAVING MAX(IF(chanacc.level IS NULL, 0, chanacc.level)) <= 0
266 ORDER BY NULL");
267
268 $get_users_nochans = $dbh->prepare("SELECT user.nick, user.id
269 FROM user LEFT JOIN chanuser ON (chanuser.nickid=user.id AND chanuser.joined=1)
270 WHERE chanuser.chan IS NULL AND user.online=1");
271 $get_users_nochans_noid = $dbh->prepare("SELECT user.nick, user.id
272 FROM user LEFT JOIN chanuser ON (chanuser.nickid=user.id AND chanuser.joined=1)
273 LEFT JOIN nickid ON (nickid.id=user.id)
274 WHERE chanuser.chan IS NULL AND nickid.id IS NULL
275 AND user.online=1");
276
277 $get_using_nick_chans = $dbh->prepare("SELECT user.nick FROM user, nickid, nickreg, chanuser
278 WHERE user.id=nickid.id AND user.id=chanuser.nickid AND nickid.nrid=nickreg.id AND chanuser.joined=1
279 AND nickreg.nick=? AND chanuser.chan=?");
280
281 $get_lock = $dbh->prepare("SELECT GET_LOCK(?, 3)");
282 $release_lock = $dbh->prepare("DO RELEASE_LOCK(?)");
283 $is_free_lock = $dbh->prepare("SELECT IS_FREE_LOCK(?)");
284
285 $chan_create = $dbh->prepare("INSERT IGNORE INTO chan SET seq=?, chan=?");
286 $chan_delete = $dbh->prepare("DELETE FROM chan WHERE chan=?");
287 $get_chanmodes = $dbh->prepare("SELECT modes FROM chan WHERE chan=?");
288 $set_chanmodes = $dbh->prepare("REPLACE INTO chan SET modes=?, chan=?");
289
290 $is_registered = $dbh->prepare("SELECT 1 FROM chanreg WHERE chan=?");
291 $get_modelock = $dbh->prepare("SELECT modelock FROM chanreg WHERE chan=?");
292 $set_modelock = $dbh->prepare("UPDATE chanreg SET modelock=? WHERE chan=?");
293
294 $set_descrip = $dbh->prepare("UPDATE chanreg SET descrip=? WHERE chan=?");
295
296 $get_topic = $dbh->prepare("SELECT chantext.data, topicer, topicd FROM chanreg, chantext
297 WHERE chanreg.chan=chantext.chan AND chantext.chan=?");
298 $set_topic1 = $dbh->prepare("UPDATE chanreg SET chanreg.topicer=?, chanreg.topicd=?
299 WHERE chanreg.chan=?");
300 $set_topic2 = $dbh->prepare("REPLACE INTO chantext SET chan=?, type=".CRT_TOPIC().", data=?");
301
302 $get_acc = $dbh->prepare("SELECT chanacc.level FROM chanacc, nickalias
303 WHERE chanacc.chan=? AND chanacc.nrid=nickalias.nrid AND nickalias.alias=?");
304 $set_acc1 = $dbh->prepare("INSERT IGNORE INTO chanacc SELECT ?, nrid, ?, NULL, UNIX_TIMESTAMP(), 0
305 FROM nickalias WHERE alias=?");
306 $set_acc2 = $dbh->prepare("UPDATE chanacc, nickalias
307 SET chanacc.level=?, chanacc.adder=?, chanacc.time=UNIX_TIMESTAMP()
308 WHERE chanacc.chan=? AND chanacc.nrid=nickalias.nrid AND nickalias.alias=?");
309 $del_acc = $dbh->prepare("DELETE FROM chanacc USING chanacc, nickalias
310 WHERE chanacc.chan=? AND chanacc.nrid=nickalias.nrid AND nickalias.alias=?");
311 $wipe_acc_list = $dbh->prepare("DELETE FROM chanacc WHERE chan=? AND level=?");
312 $get_acc_list = $dbh->prepare("SELECT nickreg.nick, chanacc.adder, chanacc.time,
313 chanacc.last, nickreg.ident, nickreg.vhost
314 FROM chanacc, nickreg
315 WHERE chanacc.chan=? AND chanacc.level=? AND chanacc.nrid=nickreg.id AND chanacc.level > 0 ORDER BY nickreg.nick");
316 $get_acc_list2 = $dbh->prepare("SELECT nickreg.nick, chanacc.adder, chanacc.level, chanacc.time,
317 chanacc.last, nickreg.ident, nickreg.vhost
318 FROM chanacc, nickreg
319 WHERE chanacc.chan=? AND chanacc.nrid=nickreg.id AND chanacc.level > 0 ORDER BY nickreg.nick");
320 $get_acc_list_mask = $dbh->prepare("SELECT IF (nickreg.nick LIKE ?, nickreg.nick, nickalias.alias), chanacc.adder, chanacc.time,
321 chanacc.last, nickreg.ident, nickreg.vhost, COUNT(nickreg.id) as c
322 FROM chanacc, nickalias, nickreg
323 WHERE chanacc.chan=? AND chanacc.level=? AND chanacc.nrid=nickalias.nrid AND nickreg.id=nickalias.nrid
324 AND chanacc.level > 0
325 AND nickalias.alias LIKE ? AND nickreg.ident LIKE ? AND nickreg.vhost LIKE ?
326 GROUP BY nickreg.id
327 ORDER BY nickalias.alias");
328 $get_acc_list2_mask = $dbh->prepare("SELECT IF (nickreg.nick LIKE ?, nickreg.nick, nickalias.alias),
329 chanacc.adder, chanacc.level, chanacc.time,
330 chanacc.last, nickreg.ident, nickreg.vhost, COUNT(nickreg.id) as c
331 FROM chanacc, nickalias, nickreg
332 WHERE chanacc.chan=? AND chanacc.nrid=nickalias.nrid AND nickreg.id=nickalias.nrid
333 AND chanacc.level > 0
334 AND nickalias.alias LIKE ? AND nickreg.ident LIKE ? AND nickreg.vhost LIKE ?
335 GROUP BY nickreg.id
336 ORDER BY nickalias.alias");
337
338 $get_best_acc = $dbh->prepare("SELECT nickreg.nick, chanacc.level
339 FROM nickid, nickalias, nickreg, chanacc
340 WHERE nickid.nrid=nickreg.id AND nickalias.nrid=nickreg.id AND nickid.id=?
341 AND chanacc.nrid=nickreg.id AND chanacc.chan=? ORDER BY chanacc.level DESC LIMIT 1");
342 $get_all_acc = $dbh->prepare("SELECT nickreg.nick, chanacc.level
343 FROM nickid, nickreg, chanacc
344 WHERE nickid.nrid=nickreg.id AND nickid.id=? AND chanacc.nrid=nickreg.id
345 AND chanacc.chan=? ORDER BY chanacc.level");
346 $get_highrank = $dbh->prepare("SELECT user.nick, chanacc.level FROM chanuser, nickid, chanacc, user WHERE chanuser.chan=? AND chanuser.joined=1 AND chanuser.chan=chanacc.chan AND chanuser.nickid=nickid.id AND user.id=nickid.id AND nickid.nrid=chanacc.nrid ORDER BY chanacc.level DESC LIMIT 1");
347 $get_acc_count = $dbh->prepare("SELECT COUNT(*) FROM chanacc WHERE chan=? AND level=?");
348 $copy_acc = $dbh->prepare("REPLACE INTO chanacc
349 ( chan, nrid, level, adder, time)
350 SELECT ?, nrid, level, adder, time FROM chanacc JOIN nickreg ON (chanacc.nrid=nickreg.id)
351 WHERE chan=? AND nickreg.nick!=? AND chanacc.level!=7");
352 $copy_acc_rank = $dbh->prepare("REPLACE INTO chanacc
353 ( chan, nrid, level, adder, time)
354 SELECT ?, nrid, level, adder, time FROM chanacc
355 WHERE chan=? AND chanacc.level=?");
356
357 $get_eos_lock = $dbh->prepare("LOCK TABLES akick READ LOCAL, welcome READ LOCAL, chanuser WRITE, user WRITE,
358 user AS u1 READ, user AS u2 READ, chan WRITE, chanreg WRITE, nickid READ LOCAL, nickreg READ LOCAL,
359 nickalias READ LOCAL, chanacc READ LOCAL, chanban WRITE, svsop READ");
360 my $get_status_all_1 = "SELECT chanuser.chan, chanreg.flags, chanreg.bot, user.nick, user.id, user.flags, MAX(chanacc.level), chanuser.op, MAX(nickreg.flags & ".NRF_NEVEROP().")
361 FROM user, chanreg, chanuser
362 LEFT JOIN nickid ON(nickid.id=chanuser.nickid)
363 LEFT JOIN nickreg ON(nickid.nrid=nickreg.id)
364 LEFT JOIN chanacc ON(chanacc.chan=chanuser.chan AND chanacc.nrid=nickid.nrid AND (nickreg.flags & ".NRF_NEVEROP().")=0)
365 WHERE";
366 my $get_status_all_2 = "(user.flags & ".UF_FINISHED().")=0 AND chanuser.joined=1 AND (chanreg.flags & ".(CRF_CLOSE|CRF_DRONE).") = 0 AND chanreg.chan=chanuser.chan AND user.id=chanuser.nickid AND (nickid.nrid IS NULL OR nickreg.id IS NOT NULL)
367 GROUP BY chanuser.chan, chanuser.nickid ORDER BY NULL";
368 $get_status_all = $dbh->prepare("$get_status_all_1 $get_status_all_2");
369 $get_status_all_server = $dbh->prepare("$get_status_all_1 user.server=? AND $get_status_all_2");
370
371 $get_modelock_all = $dbh->prepare("SELECT chanuser.chan, chan.modes, chanreg.modelock FROM chanreg, chan, chanuser WHERE chanuser.joined=1 AND chanreg.chan=chan.chan AND chanreg.chan=chanuser.chan GROUP BY chanreg.chan ORDER BY NULL");
372
373 my $akick_rows = "user.nick, akick.nick, akick.ident, akick.host, akick.reason";
374 my $akick_no_zerolen = "(akick.ident != '' AND akick.host != '')";
375 my $akick_single_cond = "$akick_no_zerolen AND user.nick LIKE akick.nick AND user.ident LIKE akick.ident ".
376 "AND ( (user.host LIKE akick.host) OR (user.vhost LIKE akick.host) OR ".
377 "(IF((user.ip IS NOT NULL) AND (user.ip != 0), INET_NTOA(user.ip) LIKE akick.host, 0)) OR ".
378 "(IF(user.cloakhost IS NOT NULL, user.cloakhost LIKE akick.host, 0)) )";
379 my $akick_multi_cond = "chanuser.chan=akick.chan AND $akick_single_cond";
380
381 $get_akick = $dbh->prepare("SELECT $akick_rows FROM akick, user ".
382 "WHERE user.id=? AND akick.chan=? AND $akick_single_cond LIMIT 1");
383 $get_akick_allchan = $dbh->prepare("SELECT $akick_rows FROM $chan_users_noacc_tables
384 JOIN akick ON($akick_multi_cond)
385 WHERE akick.chan=?
386 GROUP BY user.id HAVING MAX(IF(chanacc.level IS NULL, 0, chanacc.level)) <= 0
387 ORDER BY NULL");
388 $get_akick_alluser = $dbh->prepare("SELECT akick.chan, $akick_rows FROM $chan_users_noacc_tables
389 JOIN akick ON($akick_multi_cond)
390 WHERE chanuser.nickid=?
391 GROUP BY user.id HAVING MAX(IF(chanacc.level IS NULL, 0, chanacc.level)) <= 0
392 ORDER BY NULL");
393 $get_akick_all = $dbh->prepare("SELECT akick.chan, $akick_rows FROM $chan_users_noacc_tables
394 JOIN akick ON($akick_multi_cond)
395 GROUP BY akick.chan, user.id HAVING MAX(IF(chanacc.level IS NULL, 0, chanacc.level)) <= 0
396 ORDER BY NULL");
397
398 $add_akick = $dbh->prepare("INSERT INTO akick SET chan=?, nick=?, ident=?, host=?, adder=?, reason=?, time=UNIX_TIMESTAMP()");
399 $add_akick->{PrintError} = 0;
400 $del_akick = $dbh->prepare("DELETE FROM akick WHERE chan=? AND nick=? AND ident=? AND host=?");
401 $get_akick_list = $dbh->prepare("SELECT nick, ident, host, adder, reason, time FROM akick WHERE chan=? ORDER BY time");
402
403 $add_nick_akick = $dbh->prepare("INSERT INTO akick SELECT ?, nickalias.nrid, '', '', ?, ?, UNIX_TIMESTAMP()
404 FROM nickalias WHERE alias=?");
405 $del_nick_akick = $dbh->prepare("DELETE FROM akick USING akick, nickalias
406 WHERE akick.chan=? AND akick.nick=nickalias.nrid AND akick.ident='' AND akick.host='' AND nickalias.alias=?");
407 $get_nick_akick = $dbh->prepare("SELECT reason FROM akick, nickalias
408 WHERE akick.chan=? AND akick.nick=nickalias.nrid AND akick.ident='' AND akick.host='' AND nickalias.alias=?");
409 $drop_nick_akick = $dbh->prepare("DELETE FROM akick USING akick, nickreg
410 WHERE akick.nick=nickreg.id AND akick.ident='' AND akick.host='' AND nickreg.nick=?");
411 $copy_akick = $dbh->prepare("REPLACE INTO akick
412 ( chan, nick, ident, host, adder, reason, time)
413 SELECT ?, nick, ident, host, adder, reason, time FROM akick WHERE chan=?");
414 $get_akick_by_num = $dbh->prepare("SELECT akick.nick, akick.ident, akick.host FROM akick WHERE chan=?
415 ORDER BY time LIMIT 1 OFFSET ?");
416 $get_akick_by_num->bind_param(2, 0, SQL_INTEGER);
417
418 $is_level = $dbh->prepare("SELECT 1 FROM chanperm WHERE chanperm.name=?");
419 $get_level = $dbh->prepare("SELECT IF(chanlvl.level IS NULL, chanperm.level, chanlvl.level), chanlvl.level
420 FROM chanperm LEFT JOIN chanlvl ON chanlvl.perm=chanperm.id AND chanlvl.chan=?
421 WHERE chanperm.name=?");
422 $get_levels = $dbh->prepare("SELECT chanperm.name, chanperm.level, chanlvl.level FROM chanperm LEFT JOIN chanlvl ON chanlvl.perm=chanperm.id AND chanlvl.chan=? ORDER BY chanperm.name");
423 $add_level = $dbh->prepare("INSERT IGNORE INTO chanlvl SELECT ?, chanperm.id, chanperm.level FROM chanperm WHERE chanperm.name=?");
424 $set_level = $dbh->prepare("UPDATE chanlvl, chanperm SET chanlvl.level=? WHERE chanlvl.chan=? AND chanperm.id=chanlvl.perm AND chanperm.name=?");
425 $reset_level = $dbh->prepare("DELETE FROM chanlvl USING chanlvl, chanperm WHERE chanperm.name=? AND chanlvl.perm=chanperm.id AND chanlvl.chan=?");
426 $clear_levels = $dbh->prepare("DELETE FROM chanlvl WHERE chan=?");
427 $get_level_max = $dbh->prepare("SELECT max FROM chanperm WHERE name=?");
428 $copy_levels = $dbh->prepare("REPLACE INTO chanlvl
429 ( chan, perm, level)
430 SELECT ?, perm, level FROM chanlvl WHERE chan=?");
431
432 $get_founder = $dbh->prepare("SELECT nickreg.nick FROM chanreg, nickreg WHERE chanreg.chan=? AND chanreg.founderid=nickreg.id");
433 $get_successor = $dbh->prepare("SELECT nickreg.nick FROM chanreg, nickreg WHERE chanreg.chan=? AND chanreg.successorid=nickreg.id");
434 $set_founder = $dbh->prepare("UPDATE chanreg, nickreg SET chanreg.founderid=nickreg.id WHERE nickreg.nick=? AND chanreg.chan=?");
435 $set_successor = $dbh->prepare("UPDATE chanreg, nickreg SET chanreg.successorid=nickreg.id WHERE nickreg.nick=? AND chanreg.chan=?");
436 $del_successor = $dbh->prepare("UPDATE chanreg SET chanreg.successorid=NULL WHERE chanreg.chan=?");
437
438 $get_nick_own_chans = $dbh->prepare("SELECT chanreg.chan FROM chanreg, nickreg WHERE nickreg.nick=? AND chanreg.founderid=nickreg.id");
439 $delete_successors = $dbh->prepare("UPDATE chanreg, nickreg SET chanreg.successorid=NULL WHERE nickreg.nick=? AND chanreg.successorid=nickreg.id");
440
7b261bb8 441
442 $get_info = $dbh->prepare("SELECT chanreg.descrip, chanreg.regd, chanreg.last, chantext.data,
44b46f5b 443 chanreg.topicer, chanreg.modelock, foundernick.nick, successornick.nick, chanreg.bot, chanreg.bantype, chanreg.bantime
7b261bb8 444 FROM nickreg AS foundernick, chanreg
445 LEFT JOIN nickreg AS successornick ON(successornick.id=chanreg.successorid)
446 LEFT JOIN chantext ON (chanreg.chan=chantext.chan AND chantext.type=".CRT_TOPIC().")
447 WHERE chanreg.chan=? AND foundernick.id=chanreg.founderid");
448
449 $register = $dbh->prepare("INSERT INTO chanreg
450 SELECT ?, ?, UNIX_TIMESTAMP(), UNIX_TIMESTAMP(), NULL, NULL,
44b46f5b 451 NULL, id, NULL, NULL, NULL, ".DEFAULT_BANTYPE().",0 FROM nickreg WHERE nick=?");
7b261bb8 452 $register->{PrintError} = 0;
453 $copy_chanreg = $dbh->prepare("INSERT INTO chanreg
44b46f5b 454 ( chan, descrip, regd, last, modelock, founderid, successorid, bot, flags, bantype,bantime)
455 SELECT ?, descrip, UNIX_TIMESTAMP(), UNIX_TIMESTAMP(), modelock, founderid, successorid, bot, flags, bantype,bantime
7b261bb8 456 FROM chanreg WHERE chan=?");
44b46f5b 457 $get_bantime = $dbh->prepare ("SELECT bantime FROM chanreg WHERE chan=?");
458 $set_bantime = $dbh->prepare ("UPDATE chanreg set bantime=? WHERE chan=?");
7b261bb8 459 $drop_acc = $dbh->prepare("DELETE FROM chanacc WHERE chan=?");
460 $drop_lvl = $dbh->prepare("DELETE FROM chanlvl WHERE chan=?");
461 $drop_akick = $dbh->prepare("DELETE FROM akick WHERE chan=?");
462 $drop = $dbh->prepare("DELETE FROM chanreg WHERE chan=?");
463
464 $get_expired = $dbh->prepare("SELECT chanreg.chan, nickreg.nick FROM nickreg, chanreg
465 LEFT JOIN chanuser ON(chanreg.chan=chanuser.chan AND chanuser.op!=0)
466 WHERE chanreg.founderid=nickreg.id AND chanuser.chan IS NULL AND chanreg.last<? AND
467 !(chanreg.flags & " . CRF_HOLD . ")");
468
469 $get_close = $dbh->prepare("SELECT reason, nick, time FROM chanclose WHERE chan=?");
470 $set_close = $dbh->prepare("REPLACE INTO chanclose SET chan=?, reason=?, nick=?, time=UNIX_TIMESTAMP(), type=?");
471 $del_close = $dbh->prepare("DELETE FROM chanclose WHERE chan=?");
472
473 $add_welcome = $dbh->prepare("REPLACE INTO welcome SET chan=?, id=?, adder=?, time=UNIX_TIMESTAMP(), msg=?");
474 $del_welcome = $dbh->prepare("DELETE FROM welcome WHERE chan=? AND id=?");
475 $list_welcome = $dbh->prepare("SELECT id, time, adder, msg FROM welcome WHERE chan=? ORDER BY id");
476 $get_welcomes = $dbh->prepare("SELECT msg FROM welcome WHERE chan=? ORDER BY id");
477 $drop_welcome = $dbh->prepare("DELETE FROM welcome WHERE chan=?");
478 $count_welcome = $dbh->prepare("SELECT COUNT(*) FROM welcome WHERE chan=?");
479 $consolidate_welcome = $dbh->prepare("UPDATE welcome SET id=id-1 WHERE chan=? AND id>?");
44b46f5b 480 $add_tempban = $dbh->prepare ("INSERT INTO tmpban values (?,?,UNIX_TIMESTAMP()+?,UNIX_TIMESTAMP())");
a56ea8a9 481 $add_ban = $dbh->prepare("INSERT IGNORE INTO chanban SET chan=?, mask=?, setter=?, type=?, time=UNIX_TIMESTAMP()");
7b261bb8 482 $delete_bans = $dbh->prepare("DELETE FROM chanban WHERE chan=? AND ? LIKE mask AND type=?");
483 # likely need a better name for this or for the above.
484 $delete_ban = $dbh->prepare("DELETE FROM chanban WHERE chan=? AND mask=? AND type=?");
485 $find_bans = $dbh->prepare("SELECT mask FROM chanban WHERE chan=? AND ? LIKE mask AND type=?");
486 $get_all_bans = $dbh->prepare("SELECT mask FROM chanban WHERE chan=? AND type=?");
b736639c 487 $get_ban_num = $dbh->prepare("SELECT mask FROM chanban WHERE chan=? AND type=? ORDER BY time, mask LIMIT 1 OFFSET ?");
488 $get_ban_num->bind_param(3, 0, SQL_INTEGER);
7b261bb8 489 $list_bans = $dbh->prepare("SELECT mask, setter, time FROM chanban WHERE chan=? AND type=? ORDER BY time, mask");
490 $wipe_bans = $dbh->prepare("DELETE FROM chanban WHERE chan=?");
491
492 my $chanban_mask = "((CONCAT(user.nick, '!', user.ident, '\@', user.host) LIKE chanban.mask) ".
493 "OR (CONCAT(user.nick , '!' , user.ident , '\@' , user.vhost) LIKE chanban.mask) ".
494 "OR IF(user.cloakhost IS NOT NULL, ".
495 "(CONCAT(user.nick , '!' , user.ident , '\@' , user.cloakhost) LIKE chanban.mask), 0))";
496 $find_bans_chan_user = $dbh->prepare("SELECT mask FROM chanban,user
497 WHERE chan=? AND user.id=? AND type=? AND $chanban_mask");
498 $delete_bans_chan_user = $dbh->prepare("DELETE FROM chanban USING chanban,user
499 WHERE chan=? AND user.id=? AND type=? AND $chanban_mask");
500
501 $add_auth = $dbh->prepare("REPLACE INTO nicktext
a36c659a 502 SELECT nickalias.nrid, (".NTF_AUTH()."), 1, ?, ? FROM nickalias WHERE nickalias.alias=?");
7b261bb8 503 $list_auth_chan = $dbh->prepare("SELECT nickreg.nick, nicktext.data FROM nickreg, nicktext
a36c659a 504 WHERE nickreg.id=nicktext.nrid AND nicktext.type=(".NTF_AUTH().") AND nicktext.chan=?");
7b261bb8 505 $get_auth_nick = $dbh->prepare("SELECT nicktext.data FROM nickreg, nickalias, nicktext
a36c659a 506 WHERE nickreg.id=nicktext.nrid AND nickreg.id=nickalias.nrid AND nicktext.type=(".NTF_AUTH().")
7b261bb8 507 AND nicktext.chan=? AND nickalias.alias=?");
508 $get_auth_num = $dbh->prepare("SELECT nickreg.nick, nicktext.data FROM nickreg, nickalias, nicktext
a36c659a 509 WHERE nickreg.id=nicktext.nrid AND nickreg.id=nickalias.nrid AND nicktext.type=(".NTF_AUTH().")
7b261bb8 510 AND nicktext.chan=? LIMIT 1 OFFSET ?");
511 $get_auth_num->bind_param(2, 0, SQL_INTEGER);
512 $find_auth = $dbh->prepare("SELECT 1 FROM nickalias, nicktext
a36c659a 513 WHERE nickalias.nrid=nicktext.nrid AND nicktext.type=(".NTF_AUTH().")
7b261bb8 514 AND nicktext.chan=? AND nickalias.alias=?");
515
516 $set_bantype = $dbh->prepare("UPDATE chanreg SET bantype=? WHERE chan=?");
517 $get_bantype = $dbh->prepare("SELECT bantype FROM chanreg WHERE chan=?");
518
519 $drop_chantext = $dbh->prepare("DELETE FROM chantext WHERE chan=?");
520 $drop_nicktext = $dbh->prepare("DELETE nicktext.* FROM nicktext WHERE nicktext.chan=?");
0b380dbc 521 $get_host = $dbh->prepare ("SELECT user.host from user where user.nick=?");
48fb1f98 522 $get_host_inchan= $dbh->prepare ("select clonedUsers.nick from user as curUser join user as clonedUsers on (curUser.host=clonedUsers.host) join chanuser on (chanuser.nickid=clonedUsers.id) where clonedUsers.id!=curUser.id and curUser.id=? and chanuser.chan=? and chanuser.joined=1");
44b46f5b 523 $get_expired_bans = $dbh->prepare("select * from tmpban where expiry < UNIX_TIMESTAMP()");
524 $del_tmpban = $dbh->prepare("delete from tmpban where channel=? and banmask like ?");
7b261bb8 525}
526
9fe48026 527use SrSv::MySQL::Stub {
528 set_lastop => ['NULL', "UPDATE chanreg SET last=UNIX_TIMESTAMP() WHERE chan=?"],
529 set_lastused => ['NULL', "UPDATE chanacc, nickid SET chanacc.last=UNIX_TIMESTAMP() WHERE
530 chanacc.chan=? AND nickid.id=? AND chanacc.nrid=nickid.nrid AND chanacc.level > 0"],
6173575a 531 get_recent_private_chans => ['COLUMN', "SELECT DISTINCT chanuser.chan FROM chanuser
532 JOIN chanacc ON (chanuser.chan=chanacc.chan AND chanuser.joined=0)
533 JOIN chanlvl ON (chanlvl.level <= chanacc.level AND chanlvl.level > 0 AND chanuser.chan=chanlvl.chan)
534 JOIN chanperm ON (chanlvl.perm=chanperm.id)
535 JOIN nickid ON (chanuser.nickid=nickid.id AND chanacc.nrid=nickid.nrid)
536 WHERE chanperm.name='Join'
537 AND nickid.id=?"],
9fe48026 538};
539
7b261bb8 540### CHANSERV COMMANDS ###
541
92bdb293 542our %high_priority_cmds = (
543 kick => 1,
544 mode => 1,
545 kb => 1,
546 kickban => 1,
547 kickb => 1,
548 kban => 1,
549 down => 1,
550);
44b46f5b 551sub check_expired_bans() {
552 add_timer('ChanServ Expire', 10, __PACKAGE__, 'chanserv::check_expired_bans');
553 $get_expired_bans->execute();
554 while (my ($cn, $ban) = $get_expired_bans->fetchrow_array()) {
555 my $chan = {CHAN=>$cn};
556 ircd::setmode(agent($chan), $cn, '-b', $ban);
557 ircd::flushmodes();
558 }
559}
560sub tempban($$$@) {
561 my ($chan, $user, $mask, $args) = @_;
562 my $expiry = 0;
563 my @argz = split (" ",$args);
564 $expiry = parse_time(shift @argz) if $argz[0] =~ /\+/;
565 #kickban($chan, $user, undef, 'No clones allowed in this channel.');
566 my $reason = join(' ', @argz);
567 $reason =~ s/^\:// if $reason;
44b46f5b 568 kickban ($chan, $user, $mask, $reason, undef, $expiry);
44b46f5b 569}
0b380dbc 570sub clones_exist ($$) {
571 my ($user, $chan) = @_;
572 my $cn = $chan->{CHAN};
573 unless(cr_chk_flag($chan, CRF_NOCLONES)) {
574 return;
575 }
576 my $nick = $user->{NICK};
958994a5 577 $get_host_inchan->execute(get_user_id($user), $cn);
578 my ($joined) = $get_host_inchan->fetchrow_array;
e0b4538a 579 $get_host_inchan->finish();
0b380dbc 580 if ($joined) {
581 return $joined;
582 }
583 return 0;
584}
7b261bb8 585sub dispatch($$$) {
586 my ($src, $dst, $msg) = @_;
5b9e7750 587
7b261bb8 588 $msg =~ s/^\s+//;
589 my @args = split(/\s+/, $msg);
590 my $cmd = shift @args;
591
592 my $user = { NICK => $src, AGENT => $dst };
593
920a4abc 594 return if flood_check($user);
7b261bb8 595
92bdb293 596 if(!defined($high_priority_cmds{lc $cmd}) &&
597 !adminserv::is_svsop($user) &&
f80900e4 598 $SrSv::IRCd::State::queue_depth > main_conf_highqueue)
92bdb293 599 {
600 notice($user, get_user_agent($user)." is too busy right now. Please try your command again later.");
601 return;
602 }
603
7b261bb8 604 if($cmd =~ /^register$/i) {
605 if(@args >= 1) {
606 my @args = split(/\s+/, $msg, 4);
607 cs_register($user, { CHAN => $args[1] }, $args[2], $args[3]);
608 } else {
609 notice($user, 'Syntax: REGISTER <#channel> [password] [description]');
610 }
611 }
44b46f5b 612 elsif ($cmd =~ /^t((e)?mp)?b(an)?$/i) {
613 my @args = split(/\s+/, $msg, 4); shift @args;
614 my ($cn) = $args[0];
615 my ($target) = $args[1];
616 my ($reason) = $args[2];
617 my $chan = {CHAN=>$cn};
618 cs_tempban ($user, $chan, $target, $reason);
619 }
620
621
7b261bb8 622 elsif($cmd =~ /^(?:[uvhas]op|co?f(ounder)?)$/i) {
623 my ($cn, $cmd2) = splice(@args, 0, 2);
624 my $chan = { CHAN => $cn };
625
626 if($cmd2 =~ /^add$/i) {
627 if(@args == 1) {
628 cs_xop_add($user, $chan, $cmd, $args[0]);
629 } else {
630 notice($user, 'Syntax: '.uc $cmd.' <#channel> ADD <nick>');
631 }
632 }
633 elsif($cmd2 =~ /^del(ete)?$/i) {
634 if(@args == 1) {
635 cs_xop_del($user, $chan, $cmd, $args[0]);
636 } else {
637 notice($user, 'Syntax: '.uc $cmd.' <#channel> DEL <nick>');
638 }
639 }
640 elsif($cmd2 =~ /^list$/i) {
641 if(@args >= 0) {
642 cs_xop_list($user, $chan, $cmd, $args[0]);
643 } else {
644 notice($user, 'Syntax: '.uc $cmd.' <#channel> LIST [mask]');
645 }
646 }
647 elsif($cmd2 =~ /^(wipe|clear)$/i) {
648 if(@args == 0) {
649 cs_xop_wipe($user, $chan, $cmd);
650 } else {
651 notice($user, 'Syntax: '.uc $cmd.' <#channel> WIPE');
652 }
653 }
654 else {
655 notice($user, 'Syntax: '.uc $cmd.' <#channel> <ADD|DEL|LIST|WIPE>');
656 }
657 }
658 elsif($cmd =~ /^levels$/i) {
659 if(@args < 2) {
660 notice($user, 'Syntax: LEVELS <#channel> <SET|RESET|LIST|CLEAR>');
661 return;
662 }
663
664 my $cmd2 = lc(splice(@args, 1, 1));
665
666 if($cmd2 eq 'set') {
667 if(@args == 3) {
668 cs_levels_set($user, { CHAN => $args[0] }, $args[1], $args[2]);
669 } else {
670 notice($user, 'Syntax: LEVELS <#channel> SET <permission> <level>');
671 }
672 }
673 elsif($cmd2 eq 'reset') {
674 if(@args == 2) {
675 cs_levels_set($user, { CHAN => $args[0] }, $args[1]);
676 } else {
677 notice($user, 'Syntax: LEVELS <#channel> RESET <permission>');
678 }
679 }
680 elsif($cmd2 eq 'list') {
681 if(@args == 1) {
682 cs_levels_list($user, { CHAN => $args[0] });
683 } else {
684 notice($user, 'Syntax: LEVELS <#channel> LIST');
685 }
686 }
687 elsif($cmd2 eq 'clear') {
688 if(@args == 1) {
689 cs_levels_clear($user, { CHAN => $args[0] });
690 } else {
691 notice($user, 'Syntax: LEVELS <#channel> CLEAR');
692 }
693 }
694 else {
695 notice($user, 'Syntax: LEVELS <#channel> <SET|RESET|LIST|CLEAR>');
696 }
697 }
698 elsif($cmd =~ /^akick$/i) {
699 if(@args < 2) {
700 notice($user, 'Syntax: AKICK <#channel> <ADD|DEL|LIST|WIPE|CLEAR>');
701 return;
702 }
703
704 #my $cmd2 = lc($args[1]);
705 my $cmd2 = lc(splice(@args, 1, 1));
706
707 if($cmd2 eq 'add') {
708 if(@args >= 2) {
709 my @args = split(/\s+/, $msg, 5);
710 cs_akick_add($user, { CHAN => $args[1] }, $args[3], $args[4]);
711 } else {
712 notice($user, 'Syntax: AKICK <#channel> ADD <nick|mask> <reason>');
713 }
714 }
715 elsif($cmd2 eq 'del') {
716 if(@args >= 2) {
717 cs_akick_del($user, { CHAN => $args[0] }, $args[1]);
718 } else {
719 notice($user, 'Syntax: AKICK <#channel> DEL <nick|mask|num|seq>');
720 }
721 }
722 elsif($cmd2 eq 'list') {
723 if(@args == 1) {
724 cs_akick_list($user, { CHAN => $args[0] });
725 } else {
726 notice($user, 'Syntax: AKICK <#channel> LIST');
727 }
728 }
729 elsif($cmd2 =~ /^(wipe|clear)$/i) {
730 if(@args == 1) {
731 cs_akick_wipe($user, { CHAN => $args[0] });
732 } else {
733 notice($user, 'Syntax: AKICK <#channel> WIPE');
734 }
735 }
736 elsif($cmd2 =~ /^enforce$/i) {
737 if(@args == 1) {
738 cs_akick_enforce($user, { CHAN => $args[0] });
739 } else {
740 notice($user, 'Syntax: AKICK <#channel> ENFORCE');
741 }
742 }
743 else {
744 notice($user, 'Syntax: AKICK <#channel> <ADD|DEL|LIST|WIPE|CLEAR>');
745 }
746 }
747 elsif($cmd =~ /^info$/i) {
748 if(@args == 1) {
749 cs_info($user, { CHAN => $args[0] });
750 } else {
751 notice($user, 'Syntax: INFO <channel>');
752 }
753 }
754 elsif($cmd =~ /^set$/i) {
755 if(@args == 2 and lc($args[1]) eq 'unsuccessor') {
756 cs_set($user, { CHAN => $args[0] }, $args[1]);
757 }
758 elsif(@args >= 3 and (
759 $args[1] =~ /m(?:ode)?lock/i or
760 lc($args[1]) eq 'desc'
761 )) {
762 my @args = split(/\s+/, $msg, 4);
763 cs_set($user, { CHAN => $args[1] }, $args[2], $args[3]);
764 }
765 elsif(@args == 3) {
766 cs_set($user, { CHAN => $args[0] }, $args[1], $args[2]);
767 }
768 else {
769 notice($user, 'Syntax: SET <channel> <option> <value>');
770 }
771 }
772 elsif($cmd =~ /^why$/i) {
773 if(@args == 1) {
774 cs_why($user, { CHAN => shift @args }, $src);
775 }
776 elsif(@args >= 2) {
777 cs_why($user, { CHAN => shift @args }, @args);
778 } else {
779 notice($user, 'Syntax: WHY <channel> <nick> [nick [nick ...]]');
780 return;
781 }
782 }
783 elsif($cmd =~ /^(de)?(voice|h(alf)?op|op|protect|admin|owner)$/i) {
784 if(@args >= 1) {
785 cs_setmodes($user, $cmd, { CHAN => shift(@args) }, @args);
786 } else {
787 notice($user, 'Syntax: '.uc($cmd).' <channel> [nick [nick ...]]');
788 }
789 }
790 elsif($cmd =~ /^(up|down)$/i) {
791 cs_updown($user, $cmd, @args);
792 }
793 elsif($cmd =~ /^drop$/i) {
794 if(@args == 1) {
795 cs_drop($user, { CHAN => $args[0] });
796 } else {
797 notice($user, 'Syntax: DROP <channel>');
798 }
799 }
800 elsif($cmd =~ /^help$/i) {
801 sendhelp($user, 'chanserv', @args)
802 }
803 elsif($cmd =~ /^count$/i) {
804 if(@args == 1) {
805 cs_count($user, { CHAN => $args[0] });
806 } else {
807 notice($user, 'Syntax: COUNT <channel>');
808 }
809 }
061cc42a 810 elsif($cmd =~ /^k(?:ick)?$/i) {
7b261bb8 811 my @args = split(/\s+/, $msg, 4); shift @args;
812 if(@args >= 2) {
813 cs_kick($user, { CHAN => $args[0] }, $args[1], 0, $args[2])
814 }
815 else {
816 notice($user, 'Syntax: KICK <channel> <nick> [reason]');
817 }
818 }
819 elsif($cmd =~ /^(k(ick)?b(an)?|b(an)?k(ick)?)$/i) {
820 my @args = split(/\s+/, $msg, 4); shift @args;
821 if(@args >= 2) {
822 cs_kick($user, { CHAN => $args[0] }, $args[1], 1, $args[2]);
823 } else {
824 notice($user, 'Syntax: KICKBAN <channel> <nick> [reason]');
825 }
826 }
827 elsif($cmd =~ /^k(ick)?m(ask)?$/i) {
828 my @args = split(/\s+/, $msg, 4); shift @args;
829 if(@args >= 2) {
830 cs_kickmask($user, { CHAN => $args[0] }, $args[1], 0, $args[2])
831 }
832 else {
833 notice($user, 'Syntax: KICKMASK <channel> <mask> [reason]');
834 }
835 }
836 elsif($cmd =~ /^(k(ick)?b(an)?|b(an)?k(ick)?)m(ask)?$/i) {
837 my @args = split(/\s+/, $msg, 4); shift @args;
838 if(@args >= 2) {
839 cs_kickmask($user, { CHAN => $args[0] }, $args[1], 1, $args[2]);
840 } else {
841 notice($user, 'Syntax: KICKBANMASK <channel> <mask> [reason]');
842 }
843 }
844 elsif($cmd =~ /^invite$/i) {
845 my $chan = shift @args;
846 if(@args == 0) {
847 cs_invite($user, { CHAN => $chan }, $src)
848 }
849 elsif(@args >= 1) {
850 cs_invite($user, { CHAN => $chan }, @args)
851 }
852 else {
853 notice($user, 'Syntax: INVITE <channel> <nick>');
854 }
855 }
856 elsif($cmd =~ /^(close|forbid)$/i) {
857 if(@args > 1) {
858 my @args = split(/\s+/, $msg, 3);
859 cs_close($user, { CHAN => $args[1] }, $args[2], CRF_CLOSE);
860 }
861 else {
862 notice($user, 'Syntax: CLOSE <chan> <reason>');
863 }
864 }
865 elsif($cmd =~ /^drone$/i) {
866 if(@args > 1) {
867 my @args = split(/\s+/, $msg, 3);
868 cs_close($user, { CHAN => $args[1] }, $args[2], CRF_DRONE);
869 }
870 else {
871 notice($user, 'Syntax: DRONE <chan> <reason>');
872 }
873 }
874 elsif($cmd =~ /^clear$/i) {
875 my ($cmd, $chan, $clearcmd, $reason) = split(/\s+/, $msg, 4);
876 unless ($chan and $clearcmd) {
877 notice($user, 'Syntax: CLEAR <channel> <MODES|OPS|USERS|BANS> [reason]');
878 return;
879 }
880 if($clearcmd =~ /^modes$/i) {
881 cs_clear_modes($user, { CHAN => $chan }, $reason);
882 }
883 elsif($clearcmd =~ /^ops$/i) {
884 cs_clear_ops($user, { CHAN => $chan }, $reason);
885 }
886 elsif($clearcmd =~ /^users$/i) {
887 cs_clear_users($user, { CHAN => $chan }, $reason);
888 }
889 elsif($clearcmd =~ /^bans?$/i) {
890 cs_clear_bans($user, { CHAN => $chan }, 0, $reason);
891 }
892 elsif($clearcmd =~ /^excepts?$/i) {
893 cs_clear_bans($user, { CHAN => $chan }, 128, $reason);
894 }
895 else {
896 notice($user, "Unknown CLEAR command \002$clearcmd\002",
897 'Syntax: CLEAR <channel> <MODES|OPS|USERS|BANS> [reason]');
898 }
899 }
900 elsif($cmd =~ /^mkick$/i) {
901 my ($cmd, $chan, $reason) = split(/\s+/, $msg, 3);
902 if($chan) {
903 cs_clear_users($user, { CHAN => $chan }, $reason);
904 }
905 else {
906 notice($user, 'Syntax: MKICK <chan> [reason]');
907 }
908 }
909 elsif($cmd =~ /^mdeop$/i) {
910 my ($cmd, $chan, $reason) = split(/\s+/, $msg, 3);
911 if($chan) {
912 cs_clear_ops($user, { CHAN => $chan }, $reason);
913 }
914 else {
915 notice($user, 'Syntax: MDEOP <chan> [reason]');
916 }
917 }
918 elsif($cmd =~ /^welcome$/i) {
919 my $wcmd = splice(@args, 1, 1);
920 if(lc($wcmd) eq 'add') {
921 my ($chan, $wmsg) = (splice(@args, 0, 1), join(' ', @args));
922 unless ($chan and $wmsg) {
923 notice($user, 'Syntax: WELCOME <channel> ADD <message>');
924 return;
925 }
926 cs_welcome_add($user, { CHAN => $chan }, $wmsg);
927 }
928 elsif(lc($wcmd) eq 'del') {
929 if (@args != 2 or !misc::isint($args[1])) {
930 notice($user, 'Syntax: WELCOME <channnel> DEL <number>');
931 return;
932 }
933 cs_welcome_del($user, { CHAN => $args[0] }, $args[1]);
934 }
935 elsif(lc($wcmd) eq 'list') {
936 if (@args != 1) {
937 notice($user, 'Syntax: WELCOME <channel> LIST');
938 return;
939 }
940 cs_welcome_list($user, { CHAN => $args[0] });
941 }
942 else {
943 notice($user, 'Syntax: WELCOME <channel> <ADD|DEL|LIST>');
944 }
945 }
946 elsif($cmd =~ /^alist$/i) {
947 if(@args >= 1) {
948 cs_alist($user, { CHAN => shift @args }, shift @args);
949 } else {
950 notice($user, 'Syntax: ALIST <channel> [mask]');
951 }
952 }
953 elsif($cmd =~ /^unban$/i) {
954 if(@args == 1) {
955 cs_unban($user, { CHAN => shift @args }, $src);
956 }
957 elsif(@args >= 2) {
958 cs_unban($user, { CHAN => shift @args }, @args);
959 } else {
960 notice($user, 'Syntax: UNBAN <channel> [nick]');
961 }
962 }
963 elsif($cmd =~ /^getkey$/i) {
964 if(@args == 1) {
965 cs_getkey($user, { CHAN => $args[0] });
966 } else {
967 notice($user, 'Syntax: GETKEY <channel>');
968 }
969 }
970 elsif($cmd =~ /^auth$/i) {
971 if (@args == 0) {
972 notice($user, 'Syntax: AUTH <channel> <LIST|DELETE> [param]');
973 } else {
974 cs_auth($user, { CHAN => shift @args }, shift @args, @args);
975 }
976 }
977 elsif($cmd =~ /^dice$/i) {
978 notice($user, botserv::get_dice($args[0]));
979 }
980 elsif($cmd =~ /^(q|n)?ban$/i) {
981 my $type = $1;
982 my $chan = shift @args;
983 if(@args >= 1) {
984 cs_ban($user, { CHAN => $chan }, $type, @args)
985 }
986 else {
987 notice($user, 'Syntax: BAN <channel> <nick|mask>');
988 }
989 }
990 elsif($cmd =~ /^banlist$/i) {
991 my $chan = shift @args;
992 if(@args == 0) {
993 cs_banlist($user, { CHAN => $chan });
994 }
995 else {
996 notice($user, 'Syntax: BANLIST <channel>');
997 }
998 }
999 elsif($cmd =~ /^assign$/i) {
1000 my $chan = shift @args;
1001 notice($user, "$csnick ASSIGN is deprecated. Please use $botserv::bsnick ASSIGN");
1002 if(@args == 2) {
1003 botserv::bs_assign($user, { CHAN => shift @args }, shift @args);
1004 }
1005 else {
1006 notice($user, 'Syntax: ASSIGN <#channel> <bot>');
1007 }
1008 }
1009 elsif($cmd =~ /^mode$/i) {
1010 my $chan = shift @args;
1011 if(@args >= 1) {
1012 cs_mode($user, { CHAN => $chan }, @args)
1013 }
1014 else {
1015 notice($user, 'Syntax: MODE <channel> <modes> [parms]');
1016 }
1017 }
1018 elsif($cmd =~ /^copy$/i) {
1019 my $chan = shift @args;
1020 if(@args >= 1) {
1021 cs_copy($user, { CHAN => $chan }, @args)
1022 }
1023 else {
1024 notice($user, 'Syntax: COPY #chan1 [type] #chan2');
1025 }
1026 }
1027 elsif($cmd =~ /^m(?:ode)?lock$/i) {
1028 my $chan = shift @args;
1029 if(@args >= 1) {
1030 cs_mlock($user, { CHAN => $chan }, @args)
1031 }
1032 else {
8f7befb1 1033 notice($user, 'Syntax: MLOCK <channel> <ADD|DEL|SET|RESET> <modes> [parms]');
7b261bb8 1034 }
1035 }
1036 elsif($cmd =~ /^resync$/i) {
1037 if (@args == 0) {
1038 notice($user, 'Syntax: RESYNC <chan1> [chan2 [chan3 [..]]]');
1039 } else {
1040 cs_resync($user, @args);
1041 }
1042 }
30a8b057 1043 elsif($cmd =~ /^JOIN$/i) {
1044 if (@args == 0) {
1045 notice($user, 'Syntax: JOIN <chan1> [chan2 [chan3 [..]]]');
1046 } else {
1047 cs_join($user, @args);
1048 }
1049 }
44b46f5b 1050 elsif($cmd =~ /^topic$/i) {
1051 my $chan = shift @args;
1052 if (@args == 0) {
1053 notice($user, 'Syntax: TOPIC <#channel> <message|NONE>');
1054 } else {
1055 $msg =~ s/^topic #(?:\S+)? //i;
1056 cs_topic($user, { CHAN => $chan }, $msg);
1057 }
1058 }
1059 elsif($cmd =~ /^topicappend$/i) {
1060 my $chan = shift @args;
1061 if (@args == 0) {
1062 notice($user, 'Syntax: TOPICAPPEND <#channel> <message>');
1063 } else {
1064 $msg =~ s/^topicappend #(?:\S+)? //i;
1065 cs_topicappend($user, $chan, $msg);
1066 }
1067 }
1068 elsif($cmd =~ /^topicprepend$/i) {
1069 my $chan = shift @args;
1070 if (@args == 0) {
1071 notice($user, 'Syntax: TOPICPREPEND <#channel> <message>');
1072 } else {
1073 $msg =~ s/^topicprepend #(?:\S+)? //i;
1074 cs_topicprepend($user, $chan, $msg);
01aae2c0 1075 }
1076 }
7b261bb8 1077 else {
1078 notice($user, "Unrecognized command \002$cmd\002.", "For help, type: \002/msg chanserv help\002");
1079 wlog($csnick, LOG_DEBUG(), "$src tried to use $csnick $msg");
1080 }
1081}
1082
1083sub cs_register($$;$$) {
1084 my ($user, $chan, $pass, $desc) = @_;
1085 # $pass is still passed in, but never used!
1086 my $src = get_user_nick($user);
1087 my $cn = $chan->{CHAN};
1088
1089 unless(is_identified($user, $src)) {
1090 notice($user, 'You must register your nickname first.', "Type \002/msg NickServ HELP\002 for information on registering nicknames.");
1091 return;
1092 }
1093
1094 unless(is_in_chan($user, $chan)) {
1095 notice($user, "You are not in \002$cn\002.");
1096 return;
1097 }
1098
5ff51eb2 1099 if(services_conf_chanreg_needs_oper && !adminserv::is_svsop($user)) {
1100 notice($user, "You must be network staff to register a channel\n");
1101 return;
1102 }
7b261bb8 1103 unless(get_op($user, $chan) & ($opmodes{o} | $opmodes{a} | $opmodes{q})) {
1104 # This would be preferred to be a 'opmode_mask' or something
1105 # However that might be misleading due to hop not being enough to register
5ff51eb2 1106 notice($user, "You must have channel operator status to register \002$cn\002.");
7b261bb8 1107 return;
1108 }
1109
1110 my $root = get_root_nick($src);
1111
1112 if($desc) {
1113 my $dlength = length($desc);
1114 if($dlength >= 350) {
1115 notice($user, 'Channel description is too long by '. $dlength-350 .' character(s). Maximum length is 350 characters.');
1116 return;
1117 }
1118 }
1119
1120 if($register->execute($cn, $desc, $root)) {
1121 notice($user, "\002Your channel is now registered. Thank you.\002");
1122 notice($user, ' ', "\002NOTICE:\002 Channel passwords are not used, as a security precaution.")
1123 if $pass;
1124 set_acc($root, $user, $chan, FOUNDER);
8f7befb1 1125 $set_modelock->execute(services_conf_default_channel_mlock, $cn);
7b261bb8 1126 do_modelock($chan);
1127 services::ulog($csnick, LOG_INFO(), "registered $cn", $user, $chan);
1128 botserv::bs_assign($user, $chan, services_conf_default_chanbot) if services_conf_default_chanbot;
1129 } else {
1130 notice($user, 'That channel has already been registered.');
1131 }
1132}
1133
39eca0a2 1134=cut
1135cs_command new SrSv::AgentUI::Simple {
1136 COMMAND => [qw(uop vop hop aop sop cf cofounder cof cfounder)],
1137 SYNTAX => '#chan add/del/list/wipe/clear [nick/mask]',
1138 CALL => \&cs_xop_dispatch,
1139 CMD_TOO => 1,
1140};
1141=cut
1142sub cs_xop_dispatch {
1143 my ($user, $cmd, $chan, $cmd2, @args) = @_;
1144 $cmd = uc $cmd;
1145
1146 if($cmd2 =~ /^add$/i) {
1147 if(@args == 1) {
1148 cs_xop_add($user, $chan, $cmd, $args[0]);
1149 } else {
1150 notice($user, 'Syntax: '.uc $cmd.' <#channel> ADD <nick>');
1151 }
1152 }
1153 elsif($cmd2 =~ /^del(ete)?$/i) {
1154 if(@args == 1) {
1155 cs_xop_del($user, $chan, $cmd, $args[0]);
1156 } else {
1157 notice($user, 'Syntax: '.uc $cmd.' <#channel> DEL <nick>');
1158 }
1159 }
1160 elsif($cmd2 =~ /^list$/i) {
1161 if(@args >= 0) {
1162 cs_xop_list($user, $chan, $cmd, $args[0]);
1163 } else {
1164 notice($user, 'Syntax: '.uc $cmd.' <#channel> LIST [mask]');
1165 }
1166 }
1167 elsif($cmd2 =~ /^(wipe|clear)$/i) {
1168 if(@args == 0) {
1169 cs_xop_wipe($user, $chan, $cmd);
1170 } else {
1171 notice($user, 'Syntax: '.uc $cmd.' <#channel> WIPE');
1172 }
1173 }
1174 else {
1175 notice($user, 'Syntax: '.uc $cmd.' <#channel> <ADD|DEL|LIST|WIPE>');
1176 }
1177}
1178
7b261bb8 1179sub cs_xop_ad_pre($$$$$) {
1180 my ($user, $chan, $nick, $level, $del) = @_;
1181
1182 my $old = get_acc($nick, $chan); $old = 0 unless $old;
1183 my $slevel = get_best_acc($user, $chan);
1184
1185 unless(($del and is_identified($user, $nick)) or adminserv::can_do($user, 'SERVOP')) {
1186 unless($level < $slevel and $old < $slevel) {
1187 notice($user, $err_deny);
1188 return undef;
1189 }
39eca0a2 1190 my $cn = $chan->{CHAN};
1191 my $overrideMsg = "$levels[$level] $cn ".($del ? 'DEL' : 'ADD')." $nick";
1192 can_do($chan, 'ACCCHANGE', $user, { OVERRIDE_MSG => $overrideMsg }) or return undef;
7b261bb8 1193 }
1194
1195 nickserv::chk_registered($user, $nick) or return undef;
1196 if (nr_chk_flag($nick, NRF_NOACC()) and !adminserv::can_do($user, 'SERVOP') and !$del) {
1197 notice($user, "\002$nick\002 is not able to be added to access lists.");
1198 return undef;
1199 }
1200
1201 return $old;
1202}
1203
1204sub cs_xop_list($$$;$) {
1205 my ($user, $chan, $cmd, $mask) = @_;
1206 chk_registered($user, $chan) or return;
1207 my $cn = $chan->{CHAN};
1208 my $level = xop_byname($cmd);
39eca0a2 1209
1210 my $overrideMsg = "$cmd $cn LIST";
1211 can_do($chan, 'ACCLIST', $user, { OVERRIDE_MSG => $overrideMsg }) or return;
7b261bb8 1212
1213 my @reply;
1214 if($mask) {
1215 my ($mnick, $mident, $mhost) = glob2sql(parse_mask($mask));
1216 $mnick = '%' if($mnick eq '');
1217 $mident = '%' if($mident eq '');
1218 $mhost = '%' if($mhost eq '');
1219
1220 $get_acc_list_mask->execute($mnick, $cn, $level, $mnick, $mident, $mhost);
1221 while(my ($n, $a, $t, $lu, $id, $vh) = $get_acc_list_mask->fetchrow_array) {
1222 push @reply, "*) $n ($id\@$vh)" . ($a ? ' Added by: '.$a : '');
1223 push @reply, ' '.($t ? 'Date/time added: '. gmtime2($t).' ' : '').
1224 ($lu ? 'Last used '.time_ago($lu).' ago' : '') if ($t or $lu);
1225 }
1226 $get_acc_list_mask->finish();
1227 } else {
1228 $get_acc_list->execute($cn, $level);
1229 while(my ($n, $a, $t, $lu, $id, $vh) = $get_acc_list->fetchrow_array) {
1230 push @reply, "*) $n ($id\@$vh)" . ($a ? ' Added by: '.$a : '');
1231 push @reply, ' '.($t ? 'Date/time added: '. gmtime2($t).' ' : '').
1232 ($lu ? 'Last used '.time_ago($lu).' ago' : '') if ($t or $lu);
1233 }
1234 $get_acc_list->finish();
1235 }
1236
1237 notice($user, "$levels[$level] list for \002$cn\002:", @reply);
1238
1239 return;
1240}
1241
1242sub cs_xop_wipe($$$) {
1243 my ($user, $chan, $cmd, $nick) = @_;
1244 chk_registered($user, $chan) or return;
1245
1246 my $slevel = get_best_acc($user, $chan);
1247 my $level = xop_byname($cmd);
1248
1249 unless($level < $slevel) {
1250 notice($user, $err_deny);
1251 return;
1252 }
7b261bb8 1253 my $cn = $chan->{CHAN};
39eca0a2 1254 my $overrideMsg = "$cmd $cn WIPE";
1255 my $srcnick = can_do($chan, 'ACCCHANGE', $user, { ACC => $slevel, OVERRIDE_MSG => $overrideMsg }) or return;
7b261bb8 1256
1257 $wipe_acc_list->execute($cn, $level);
1258
1259 my $log_str = "wiped the $cmd list of \002$cn\002.";
1260 my $src = get_user_nick($user);
1261 notice($user, "You have $log_str");
1262 ircd::notice(agent($chan), '%'.$cn, "\002$src\002 has $log_str")
1263 if cr_chk_flag($chan, CRF_VERBOSE);
1264 services::ulog($csnick, LOG_INFO(), $log_str, $user, $chan);
1265
1266 memolog($chan, "\002$srcnick\002 $log_str");
1267}
1268
1269sub cs_xop_add($$$$) {
1270 my ($user, $chan, $cmd, $nick) = @_;
1271
1272 chk_registered($user, $chan) or return;
1273 my $level = xop_byname($cmd);
1274 my $old = cs_xop_ad_pre($user, $chan, $nick, $level, 0);
1275 return unless defined($old);
1276
1277 my $cn = $chan->{CHAN};
1278
1279 if($old == $level) {
1280 notice($user, "\002$nick\002 already has $levels[$level] access to \002$cn\002.");
1281 return;
1282 }
1283
1284 if($old == FOUNDER) {
1285 notice($user, "\002$nick\002 is the founder of \002$cn\002 and cannot be added to access lists.",
1286 "For more information, type: \002/msg chanserv help set founder\002");
1287 return;
1288 }
1289
1290 my $root = get_root_nick($nick);
1291 my $auth = nr_chk_flag($root, NRF_AUTH());
1292 my $src = get_user_nick($user);
1293
1294 if($auth) {
1295 $add_auth->execute($cn, "$src:".($old ? $old : 0 ).":$level:".time(), $root);
1296 del_acc($root, $chan) if $level < $old;
1297 }
1298 else {
1299 set_acc($root, $user, $chan, $level);
1300 }
1301
1302 if($old < 0) {
1303 $del_nick_akick->execute($cn, $root);
1304 my $log_str = "moved $root from the AKICK list to the ${levels[$level]} list of \002$cn\002".
1305 ($auth ? ' (requires authorization)' : '');
1306
1307 my $src = get_user_nick($user);
1308 notice_all_nicks($user, $root, "\002$src\002 $log_str");
1309 ircd::notice(agent($chan), '%'.$cn, "\002$src\002 $log_str")
1310 if cr_chk_flag($chan, CRF_VERBOSE);
1311 services::ulog($csnick, LOG_INFO(), $log_str, $user, $chan);
674d6fee 1312 my $srcnick = can_do($chan, 'ACCLIST', $user);
7b261bb8 1313 memolog($chan, "\002$srcnick\002 $log_str");
1314 } else {
1315 my $log_str = ($old?'moved':'added')." \002$root\002"
1316 . ($old ? " from the ${levels[$old]}" : '') .
1317 " to the ${levels[$level]} list of \002$cn\002" .
1318 ($auth ? ' (requires authorization)' : '');
1319 my $src = get_user_nick($user);
1320 notice_all_nicks($user, $root, "\002$src\002 $log_str");
1321 ircd::notice(agent($chan), '%'.$cn, "\002$src\002 $log_str")
1322 if cr_chk_flag($chan, CRF_VERBOSE);
1323 services::ulog($csnick, LOG_INFO(), $log_str, $user, $chan);
674d6fee 1324 my $srcnick = can_do($chan, 'ACCLIST', $user);
7b261bb8 1325 memolog($chan, "\002$srcnick\002 $log_str");
1326 }
1327}
1328
1329sub cs_xop_del($$$) {
1330 my ($user, $chan, $cmd, $nick) = @_;
1331
1332 chk_registered($user, $chan) or return;
1333 my $level = xop_byname($cmd);
1334 my $old = cs_xop_ad_pre($user, $chan, $nick, $level, 1);
1335 return unless defined($old);
1336
1337 my $cn = $chan->{CHAN};
1338
1339 unless($old == $level) {
1340 notice($user, "\002$nick\002 is not on the ${levels[$level]} list of \002$cn\002.");
1341 return;
1342 }
1343
1344 my $root = get_root_nick($nick);
377b05e2 1345 my $srcnick = can_do($chan, 'ACCLIST', $user);
7b261bb8 1346
1347 del_acc($root, $chan);
1348
1349 my $src = get_user_nick($user);
1350 my $log_str = "removed \002$root\002 ($nick) from the ${levels[$level]} list of \002$cn\002";
1351 notice_all_nicks($user, $root, "\002$src\002 $log_str");
1352 ircd::notice(agent($chan), '%'.$cn, "\002$src\002 $log_str")
1353 if cr_chk_flag($chan, CRF_VERBOSE);
1354 services::ulog($csnick, LOG_INFO(), $log_str, $user, $chan);
7b261bb8 1355 memolog($chan, "\002$srcnick\002 $log_str");
1356}
1357
1358sub cs_count($$) {
39eca0a2 1359 my ($user, $chan) = @_;
1360
7b261bb8 1361 chk_registered($user, $chan) or return;
7b261bb8 1362
1363 my $cn = $chan->{CHAN};
39eca0a2 1364 my $overrideMsg = "COUNT $cn";
1365 if(can_do($chan, 'ACCLIST', $user, { OVERRIDE_MSG => $overrideMsg })) {
1366 } else {
1367 return;
1368 }
1369
7b261bb8 1370 my $reply = '';
1371 for (my $level = $plzero + 1; $level < COFOUNDER + 2; $level++) {
1372 $get_acc_count->execute($cn, $level - 1);
1373 my ($num_recs) = $get_acc_count->fetchrow_array;
1374 $reply = $reply." $plevels[$level]: ".$num_recs;
1375 }
1376 notice($user, "\002$cn Count:\002 ".$reply);
1377}
1378
39eca0a2 1379sub cs_levels_pre($$$;$) {
1380 my($user, $chan, $cmd, $listonly) = @_;
7b261bb8 1381
1382 chk_registered($user, $chan) or return 0;
39eca0a2 1383 my $cn = $chan->{CHAN};
1384 my $overrideMsg = "LEVELS $cn $cmd";
1385 return can_do($chan, ($listonly ? 'LEVELSLIST' : 'LEVELS'), $user, { OVERRIDE_MSG => $overrideMsg });
7b261bb8 1386}
1387
1388sub cs_levels_set($$$;$) {
1389 my ($user, $chan, $perm, $level) = @_;
1390
39eca0a2 1391 cs_levels_pre($user, $chan, "$perm $level") or return;
7b261bb8 1392 my $cn = $chan->{CHAN};
1393
1394 unless(is_level($perm)) {
1395 notice($user, "$perm is not a valid permission.");
1396 return;
1397 }
1398
1399 if(defined($level)) {
1400 $level = xop_byname($level);
1401 unless(defined($level) and $level >= 0) {
1402 notice($user, 'You must specify one of the following levels: '.
1403 'any, uop, vop, hop, aop, sop, cofounder, founder, nobody');
1404 return;
1405 }
1406
1407 $get_level_max->execute($perm);
1408 my ($max) = $get_level_max->fetchrow_array;
1409 $get_level_max->finish();
1410
1411 if($max and $level > $max) {
1412 notice($user, "\002$perm\002 cannot be set to " . $plevels[$level+$plzero] . '.');
1413 return;
1414 }
1415
1416 $add_level->execute($cn, $perm);
1417 $set_level->execute($level, $cn, $perm);
1418
1419 if($level == 8) {
1420 notice($user, "\002$perm\002 is now disabled in \002$cn\002.");
1421 } else {
1422 notice($user, "\002$perm\002 now requires " . $levels[$level] . " access in \002$cn\002.");
1423 }
1424 } else {
1425 $reset_level->execute($perm, $cn);
1426
1427 notice($user, "\002$perm\002 has been reset to default.");
1428 }
1429}
1430
1431sub cs_levels_list($$) {
1432 my ($user, $chan) = @_;
1433
39eca0a2 1434 cs_levels_pre($user, $chan, 'LIST', 1) or return;
7b261bb8 1435 my $cn = $chan->{CHAN};
1436
1437 $get_levels->execute($cn);
1438 my @data;
1439 while(my ($name, $def, $lvl) = $get_levels->fetchrow_array) {
1440 push @data, [$name,
1441 (defined($lvl) ? $plevels[$lvl+$plzero] : $plevels[$def+$plzero]),
1442 (defined($lvl) ? '' : '(default)')];
1443 }
1444
1445 notice($user, columnar { TITLE => "Permission levels for \002$cn\002:",
1446 NOHIGHLIGHT => nr_chk_flag_user($user, NRF_NOHIGHLIGHT) }, @data);
1447}
1448
1449sub cs_levels_clear($$) {
1450 my ($user, $chan) = @_;
1451
39eca0a2 1452 cs_levels_pre($user, $chan, 'CLEAR') or return;
7b261bb8 1453 my $cn = $chan->{CHAN};
1454
1455 $clear_levels->execute($cn);
1456
1457 notice($user, "All permissions have been reset to default.");
1458}
1459
39eca0a2 1460sub cs_akick_pre($$$;$) {
1461 my ($user, $chan, $overrideMsg, $list) = @_;
7b261bb8 1462
1463 chk_registered($user, $chan) or return 0;
1464
39eca0a2 1465 return can_do($chan, ($list ? 'AKICKLIST' : 'AKICK'), $user, { OVERRIDE_MSG => $overrideMsg });
7b261bb8 1466}
1467
1468sub cs_akick_add($$$$) {
1469 my ($user, $chan, $mask, $reason) = @_;
1470 my $cn = $chan->{CHAN};
1471
39eca0a2 1472 my $adder = cs_akick_pre($user, $chan, "ADD $mask $reason") or return;
7b261bb8 1473
1474 my ($nick, $ident, $host) = parse_mask($mask);
1475
1476 if(($ident eq '' or $host eq '') and not ($ident eq '' and $host eq '')) {
1477 notice($user, 'Invalid hostmask.');
1478 return;
1479 }
1480
1481 if($ident eq '') {
1482 $nick = $mask;
1483
1484 unless(valid_nick($nick)) {
1485 $mask = normalize_hostmask($mask);
1486 ($nick, $ident, $host) = parse_mask($mask);
1487 }
1488 }
1489
1490 if ($ident eq '' and $host eq '' and !nickserv::is_registered($nick)) {
1491 notice($user, "\002$nick\002 is not registered");
1492 return;
1493 }
1494
1495 my $rlength = length($reason);
1496 if($rlength >= 350) {
1497 notice($user, 'AKick reason is too long by '. $rlength-350 .' character(s). Maximum length is 350 characters.');
1498 return;
1499 }
1500
1501 my $log_str;
1502 my $src = get_user_nick($user);
1503 if($ident eq '' and $host eq '' and my $old = get_acc($nick, $chan)) {
1504 if ($old == -1) {
1505 notice($user, "\002$nick\002 is already on the AKick list in \002$cn\002");
1506 return;
1507 }
1508 if($old < get_best_acc($user, $chan) or adminserv::can_do($user, 'SERVOP')) {
1509 if ($old == FOUNDER()) {
1510 # This is a fallthrough for the override case.
1511 # It shouldn't happen otherwise.
1512 # I didn't make it part of the previous conditional
1513 # b/c just $err_deny is a bit undescriptive in the override case.
1514 notice($user, "You can't akick the founder!", $err_deny);
1515 return;
1516 }
1517
1518 my $root = get_root_nick($nick);
1519 $add_nick_akick->execute($cn, $src, $reason, $nick); $add_nick_akick->finish();
1520 set_acc($nick, $user, $chan, -1);
1521 $log_str = "moved \002$nick\002 (root: \002$root\002) from the $levels[$old] list".
1522 " to the AKick list of \002$cn\002";
1523 notice_all_nicks($user, $root, "\002$src\002 $log_str");
1524 } else {
1525 notice($user, $err_deny);
1526 return;
1527 }
1528 } else {
1529 if($ident eq '' and $host eq '') {
1530 $add_nick_akick->execute($cn, $src, $reason, $nick); $add_nick_akick->finish();
1531 if (find_auth($cn, $nick)) {
1532 # Don't allow a pending AUTH entry to potentially override an AKick entry
1533 # Believe it or not, it almost happened with #animechat on SCnet.
1534 # This would also end up leaving an orphan entry in the akick table.
1535 $nickserv::del_auth->execute($nick, $cn);
1536 $nickserv::del_auth->finish();
1537 }
1538 set_acc($nick, $user, $chan, -1);
1539 my $root = get_root_nick($nick);
1540 $log_str = "added \002$nick\002 (root: \002$root\002) to the AKick list of \002$cn\002.";
1541 } else {
1542 ($nick, $ident, $host) = glob2sql($nick, $ident, $host);
1543 unless($add_akick->execute($cn, $nick, $ident, $host, $adder, $reason)) {
1544 notice($user, "\002$mask\002 is already on the AKick list of \002$cn\002.");
1545 return;
1546 }
1547 $log_str = "added \002$mask\002 to the AKick list of \002$cn\002.";
1548 }
1549
1550 }
1551 notice($user, "You have $log_str");
1552 ircd::notice(agent($chan), '%'.$cn, "\002$src\002 $log_str")
1553 if cr_chk_flag($chan, CRF_VERBOSE);
1554 services::ulog($csnick, LOG_INFO(), $log_str, $user, $chan);
1555 memolog($chan, "\002$adder\002 $log_str");
1556
1557 akick_allchan($chan);
1558}
1559
1560sub get_akick_by_num($$) {
1561 my ($chan, $num) = @_;
1562 my $cn = $chan->{CHAN};
1563
1564 $get_akick_by_num->execute($cn, $num);
1565 my ($nick, $ident, $host) = $get_akick_by_num->fetchrow_array();
1566 ($nick, $ident, $host) = sql2glob($nick, $ident, $host);
1567 $get_akick_by_num->finish();
1568 if(!$nick) {
1569 return undef;
1570 } elsif($ident eq '' and $host eq '') {
1571 # nick based akicks don't use nicks but nickreg.id
1572 # so we have to get the nickreg.nick back
1573 $nick = nickserv::get_id_nick($nick);
1574 }
1575 return ($nick, $ident, $host);
1576}
1577
1578sub cs_akick_del($$$) {
1579 my ($user, $chan, $mask) = @_;
1580 my $cn = $chan->{CHAN};
1581
39eca0a2 1582 my $adder = cs_akick_pre($user, $chan, "DEL $mask") or return;
7b261bb8 1583
1584 my @masks;
1585 if ($mask =~ /^[0-9\.,-]+$/) {
1586 foreach my $num (makeSeqList($mask)) {
1587 my ($nick, $ident, $host) = get_akick_by_num($chan, $num - 1) or next;
1588 if($ident eq '' and $host eq '') {
1589 push @masks, $nick;
1590 } else {
1591 push @masks, "$nick!$ident\@$host";
1592 }
1593 }
1594 } else {
1595 @masks = ($mask);
1596 }
1597 foreach my $mask (@masks) {
1598 my ($nick, $ident, $host) = parse_mask($mask);
1599
1600 if(($ident eq '' or $host eq '') and not ($ident eq '' and $host eq '')) {
1601 notice($user, 'Invalid hostmask.');
1602 return;
1603 }
1604
1605 if($ident eq '') {
1606 $nick = $mask;
1607
1608 unless(valid_nick($nick)) {
1609 $mask = normalize_hostmask($mask);
1610 ($nick, $ident, $host) = parse_mask($mask);
1611 }
1612 }
1613
1614 if ($ident eq '' and $host eq '' and !nickserv::is_registered($nick)) {
1615 notice($user, "\002$nick\002 is not registered");
1616 return;
1617 }
1618
1619 my ($success, $log_str) = do_akick_del($chan, $mask, $nick, $ident, $host);
1620 my $src = get_user_nick($user);
1621 if($success) {
1622 notice($user, "\002$src\002 $log_str");
1623 services::ulog($csnick, LOG_INFO(), $log_str, $user, $chan);
1624 ircd::notice(agent($chan), '%'.$cn, "\002$src\002 $log_str") if cr_chk_flag($chan, CRF_VERBOSE);
1625 memolog($chan, "\002$adder\002 $log_str");
1626 } else {
1627 notice($user, $log_str);
1628 }
1629 }
1630}
1631
1632sub do_akick_del($$$$$) {
1633 my ($chan, $mask, $nick, $ident, $host) = @_;
1634 my $cn = $chan->{CHAN};
1635
1636 my $log_str;
1637 if($ident eq '' and $host eq '') {
1638 if(get_acc($nick, $chan) == -1) {
1639 del_acc($nick, $chan);
1640 $del_nick_akick->execute($cn, $nick); $del_nick_akick->finish();
1641 my $root = get_root_nick($nick);
1642 return (1, "deleted \002$nick\002 (root: \002$root\002) from the AKick list of \002$cn\002.")
1643 } else {
1644 return (undef, "\002$mask\002 was not on the AKick list of \002$cn\002.");
1645 }
1646 } else {
1647 ($nick, $ident, $host) = glob2sql($nick, $ident, $host);
1648 if($del_akick->execute($cn, $nick, $ident, $host) != 0) {
1649 return (1, "deleted \002$mask\002 from the AKick list of \002$cn\002.");
1650 } else {
1651 return (undef, "\002$mask\002 was not on the AKick list of \002$cn\002.");
1652 }
1653 }
1654}
1655
1656sub cs_akick_list($$) {
1657 my ($user, $chan) = @_;
1658 my $cn = $chan->{CHAN};
1659
39eca0a2 1660 cs_akick_pre($user, $chan, 'LIST', 1) or return;
7b261bb8 1661
1662 my @data;
1663
1664 $get_akick_list->execute($cn);
1665 my $i = 0;
1666 while(my ($nick, $ident, $host, $adder, $reason, $time) = $get_akick_list->fetchrow_array) {
1667 if($ident ne '') {
1668 ($nick, $ident, $host) = sql2glob($nick, $ident, $host);
1669 }
1670
1671 if($ident eq '' and $host eq '') {
1672 $nick = nickserv::get_id_nick($nick);
1673 } else {
1674 $nick = "$nick!$ident\@$host";
1675 }
1676
1677 push @data, ["\002".++$i."\002", $nick, $adder, ($time ? gmtime2($time) : ''), $reason];
1678 }
1679
1680 notice($user, columnar {TITLE => "AKICK list of \002$cn\002:", DOUBLE=>1,
1681 NOHIGHLIGHT => nr_chk_flag_user($user, NRF_NOHIGHLIGHT)}, @data);
1682}
1683
1684sub cs_akick_wipe($$$) {
1685 my ($user, $chan) = @_;
1686 my $cn = $chan->{CHAN};
1687
39eca0a2 1688 my $adder = cs_akick_pre($user, $chan, 'WIPE') or return;
7b261bb8 1689
1690 $drop_akick->execute($cn);
1691 $wipe_acc_list->execute($cn, -1);
1692 my $log_str = "wiped the AKICK list of \002$cn\002.";
1693 my $src = get_user_nick($user);
1694 notice($user, "You have $log_str");
1695 ircd::notice(agent($chan), '%'.$cn, "\002$src\002 $log_str") if cr_chk_flag($chan, CRF_VERBOSE);
1696 services::ulog($csnick, LOG_INFO(), $log_str, $user, $chan);
1697 memolog($chan, "\002$adder\002 $log_str");
1698}
1699
0885aa69 1700sub cs_akick_enforce($$) {
7b261bb8 1701 my ($user, $chan) = @_;
1702 my $cn = $chan->{CHAN};
1703
1704 chk_registered($user, $chan) or return;
1705
39eca0a2 1706 can_do($chan, 'AKickEnforce', $user, { OVERRIDE_MSG => "AKICK $cn ENFORCE" }) or return;
7b261bb8 1707
1708 akick_allchan($chan);
1709}
1710
39eca0a2 1711=cut
1712cs_command new SrSv::AgentUI::Simple {
1713 COMMAND => [qw(info)],
1714 SYNTAX => 'LIST:#chan',
1715 CALL => \&cs_info,
1716 NO_WRAPPER => 1,
1717};
1718=cut
1719sub cs_info($@) {
1720 my ($user, @chanList) = @_;
7b261bb8 1721
39eca0a2 1722 my @reply;
1723 foreach my $cn (@chanList) {
1724 if(ref($cn) eq 'HASH') {
1725 $cn = $cn->{CHAN};
1726 }
1727 elsif($cn =~ /,/) {
1728 push @chanList, split(',', $cn);
1729 next;
1730 }
1731 my $chan = { CHAN => $cn };
1732 unless(__can_do($chan, 'INFO', undef, 0)) {
1733 can_do($chan, 'INFO', $user, { OVERRIDE_MSG => "INFO $cn" })
1734 or next;
1735 }
7b261bb8 1736
39eca0a2 1737 $get_info->execute($cn);
1738 my @result = $get_info->fetchrow_array;
1739 unless(@result) {
1740 push @reply, "The channel \002$cn\002 is not registered.";
1741 next;
1742 }
7b261bb8 1743
44b46f5b 1744 my ($descrip, $regd, $last, $topic, $topicer, $modelock, $founder, $successor, $bot, $bantype,$bantime) = @result;
7b261bb8 1745
39eca0a2 1746 $modelock = modes::sanitize($modelock) unless can_do($chan, 'GETKEY', $user, { NOREPLY => 1 });
7b261bb8 1747
39eca0a2 1748 my @opts;
7b261bb8 1749
39eca0a2 1750 my $topiclock = get_level($chan, 'SETTOPIC');
1751 push @opts, "Topic Lock ($levels[$topiclock])" if $topiclock;
7b261bb8 1752
39eca0a2 1753 if(cr_chk_flag($chan, (CRF_CLOSE | CRF_DRONE))) {
1754 push @reply, "\002$cn\002 is closed and cannot be used: ". get_close($chan);
1755 next;
1756 }
1757
1758 my @extra;
1759 push @extra, 'Will not expire' if cr_chk_flag($chan, CRF_HOLD);
1760 push @extra, 'Channel is frozen and access suspended' if cr_chk_flag($chan, CRF_FREEZE);
1761
1762 push @opts, 'OpGuard' if cr_chk_flag($chan, CRF_OPGUARD);
1763 push @opts, 'BotStay' if cr_chk_flag($chan, CRF_BOTSTAY);
1764 push @opts, 'SplitOps' if cr_chk_flag($chan, CRF_SPLITOPS);
1765 push @opts, 'Verbose' if cr_chk_flag($chan, CRF_VERBOSE);
1766 push @opts, 'NeverOp' if cr_chk_flag($chan, CRF_NEVEROP);
1767 push @opts, 'Ban type '.$bantype if $bantype;
44b46f5b 1768 push @opts, 'Ban time '.$bantime . ' seconds' if $bantype;
39eca0a2 1769 my $opts = join(', ', @opts);
1770
1771 my @data;
1772
1773 push @data, ['Founder:', $founder];
1774 push @data, ['Successor:', $successor] if $successor;
1775 push @data, ['Description:', $descrip] if $descrip;
1776 push @data, ['Mode lock:', $modelock];
1777 push @data, ['Settings:', $opts] if $opts;
1778 push @data, ['ChanBot:', $bot] if $bot and $bot ne '';
1779 #FIXME: memo level
1780 push @data, ['Registered:', gmtime2($regd)],
1781 ['Last opping:', gmtime2($last)],
1782 ['Time now:', gmtime2(time)];
1783
1784 push @reply, columnar {TITLE => "ChanServ info for \002$cn\002:", NOHIGHLIGHT => 1}, @data,
1785 {COLLAPSE => \@extra, BULLET => 1};
1786 }
1787 notice($user, @reply);
7b261bb8 1788}
1789
1790sub cs_set_pre($$$$) {
1791 my ($user, $chan, $set, $parm) = @_;
1792 my $cn = $chan->{CHAN};
1793 my $override = 0;
1794
1795 my %valid_set = (
1796 'founder' => 1, 'successor' => 1, 'unsuccessor' => 1,
1797 #'mlock' => 1, 'modelock' => 1,
1798 'desc' => 1,
1799 'topiclock' => 1, 'greet' => 1, 'opguard' => 1,
1800 'freeze' => 1, 'botstay' => 1, 'verbose' => 1,
1801 'splitops' => 1, 'bantype' => 1, 'dice' => 1,
1802 'welcomeinchan' => 1, 'log' => 1,
1803
1804 'hold' => 1, 'noexpire' => 1, 'no-expire' => 1,
1805
1806 'autovoice' => 1, 'avoice' => 1,
1807 'neverop' => 1, 'noop' => 1,
0b380dbc 1808 'noclones' => 1,
44b46f5b 1809 'bantime' => 1,
7b261bb8 1810 );
1811 my %override_set = (
1812 'hold' => 'SERVOP', 'noexpire' => 'SERVOP', 'no-expire' => 'SERVOP',
5824708b 1813 'freeze' => 'FREEZE', 'botstay' => 'BOT', 'log' => 'LOG',
7b261bb8 1814 );
1815
1816 chk_registered($user, $chan) or return 0;
1817 if($set =~ /m(?:ode)?lock/) {
1818 notice($user, "CS SET MLOCK is deprecated and replaced with CS MLOCK",
1819 "For more information, please /CS HELP MLOCK");
1820 return 0;
1821 }
1822 unless($valid_set{lc $set}) {
1823 notice($user, "$set is not a valid ChanServ setting.");
1824 return 0;
1825 }
1826
1827 if($override_set{lc($set)}) {
1828 if(adminserv::can_do($user, $override_set{lc($set)}) ) {
39eca0a2 1829 if(services_conf_log_overrides) {
1830 my $src = get_user_nick($user);
1831 wlog($csnick, LOG_INFO(), "\002$src\002 used override CS SET $cn $set $parm");
1832 }
7b261bb8 1833 $override = 1;
1834 } else {
1835 notice($user, $err_deny);
1836 return 0;
1837 }
1838 }
1839 else {
674d6fee 1840 can_do($chan, 'SET', $user) or return 0;
7b261bb8 1841 }
1842
1843 return 1;
1844}
1845
1846sub cs_set($$$;$) {
1847 my ($user, $chan, $set, $parm) = @_;
1848 my $cn = $chan->{CHAN};
1849 $set = lc $set;
1850
1851 cs_set_pre($user, $chan, $set, $parm) or return;
1852
1853 if($set =~ /^founder$/i) {
1854 my $override;
1855 unless(get_best_acc($user, $chan) == FOUNDER) {
1856 if(adminserv::can_do($user, 'SERVOP')) {
1857 $override = 1;
1858 } else {
1859 notice($user, $err_deny);
1860 return;
1861 }
1862 }
1863
1864 my $root;
1865 unless($root = get_root_nick($parm)) {
1866 notice($user, "The nick \002$parm\002 is not registered.");
1867 return;
1868 }
1869
1870 $get_founder->execute($cn);
1871 my ($prev) = $get_founder->fetchrow_array;
1872 $get_founder->finish();
1873
1874 if(lc($root) eq lc($prev)) {
1875 notice($user, "\002$parm\002 is already the founder of \002$cn\002.");
1876 return;
1877 }
1878
1879 set_acc($prev, $user, $chan, COFOUNDER);
1880
1881 $set_founder->execute($root, $cn); $set_founder->finish();
1882 set_acc($root, $user, $chan, FOUNDER);
1883
1884 notice($user, ($override ? "The previous founder, \002$prev\002, has" : "You have") . " been moved to the co-founder list of \002$cn\002.");
1885 notice_all_nicks($user, $root, "\002$root\002 has been set as the founder of \002$cn\002.");
1886 services::ulog($csnick, LOG_INFO(), "set founder of \002$cn\002 to \002$root\002", $user, $chan);
1887
1888 $get_successor->execute($cn);
1889 my $suc = $get_successor->fetchrow_array; $get_successor->finish();
1890 if(lc($suc) eq lc($root)) {
1891 $del_successor->execute($cn); $del_successor->finish();
1892 notice($user, "Successor has been removed from \002$cn\002.");
1893 }
1894
1895 return;
1896 }
1897
1898 if($set eq 'successor') {
1899 unless(get_best_acc($user, $chan) == FOUNDER or adminserv::can_do($user, 'SERVOP')) {
1900 notice($user, $err_deny);
1901 return;
1902 }
1903
1904 if(get_acc($parm, $chan) == 7) {
1905 notice($user, "The channel founder may not be the successor.");
1906 return;
1907 }
1908
1909 my $root;
1910 unless($root = get_root_nick($parm)) {
1911 notice($user, "The nick \002$parm\002 is not registered.");
1912 return;
1913 }
1914
1915 $set_successor->execute($root, $cn); $set_successor->finish();
1916
1917 notice($user, "\002$parm\002 is now the successor of \002$cn\002");
1918 services::ulog($csnick, LOG_INFO(), "set successor of \002$cn\002 to \002$root\002", $user, $chan);
1919 return;
1920 }
1921
1922 if($set eq 'unsuccessor') {
1923 unless(get_best_acc($user, $chan) == FOUNDER or adminserv::can_do($user, 'SERVOP')) {
1924 notice($user, $err_deny);
1925 return;
1926 }
1927
1928 $del_successor->execute($cn); $del_successor->finish();
1929
1930 notice($user, "Successor has been removed from \002$cn\002.");
1931 services::ulog($csnick, LOG_INFO(), "removed successor from \002$cn\002", $user, $chan);
1932 return;
1933 }
1934
1935 if($set =~ /m(?:ode)?lock/) {
1936 my $modes = modes::merge($parm, '+r', 1);
1937 $modes = sanitize_mlockable($modes);
1938 $set_modelock->execute($modes, $cn);
1939
1940 notice($user, "Mode lock for \002$cn\002 has been set to: \002$modes\002");
1941 do_modelock($chan);
1942 return;
1943 }
1944
1945 if($set eq 'desc') {
1946 $set_descrip->execute($parm, $cn);
1947
1948 notice($user, "Description of \002$cn\002 has been changed.");
1949 return;
1950 }
1951
1952 if($set eq 'topiclock') {
1953 my $perm = xop_byname($parm);
1954 if($parm =~ /^(?:no|off|false|0)$/i) {
1955 cs_levels_set($user, $chan, 'SETTOPIC');
1956 cs_levels_set($user, $chan, 'TOPIC');
1957 } elsif($perm >= 0 and defined($perm)) {
1958 cs_levels_set($user, $chan, 'SETTOPIC', $parm);
1959 cs_levels_set($user, $chan, 'TOPIC', $parm);
1960 } else {
1961 notice($user, 'Syntax: SET <#chan> TOPICLOCK <off|any|uop|vop|hop|aop|sop|cf|founder>');
1962 }
1963 return;
1964 }
1965
1966 if($set =~ /^bantype$/i) {
1967 unless (misc::isint($parm) and ($parm >= 0 and $parm <= 10)) {
1968 notice($user, 'Invalid bantype');
1969 return;
1970 }
1971
1972 $set_bantype->execute($parm, $cn);
1973
1974 notice($user, "Ban-Type for \002$cn\002 now set to \002$parm\002.");
1975
1976 return;
1977 }
44b46f5b 1978 if($set =~ /^bantime$/i) {
1979 if ( ( my $p = substr($parm, 0, 1) ) != '+' ) {
1980 $parm = '+' . $parm;
1981 }
1982 my $time = $parm;
1983 unless ($time == 0) {
958994a5 1984 $time = parse_time ($parm);
1985 if(!$time) {
44b46f5b 1986 notice ($user, "Invalid bantime. See /msg chanserv help set bantime for examples.");
1987 return;
1988 }
1989 }
1990 $set_bantime->execute($time, $cn);
1991 notice($user, "Ban time for \002$cn\002 now set to \002$time\002 seconds.");
1992 return;
1993 }
7b261bb8 1994 my $val;
1995 if($parm =~ /^(?:no|off|false|0)$/i) { $val = 0; }
1996 elsif($parm =~ /^(?:yes|on|true|1)$/i) { $val = 1; }
1997 else {
1998 notice($user, "Please say \002on\002 or \002off\002.");
1999 return;
2000 }
0b380dbc 2001 if ($set =~ /^(?:noclones)$/i) {
2002 cr_set_flag($chan, CRF_NOCLONES, $val);
2003 if($val) {
2004 notice($user,
2005 "Noclones is now \002ON\002.",
2006 "Clones will be kicked out of \002$cn\002."
2007 );
2008 } else {
2009 notice($user,
2010 "Noclones is now \002OFF\002.",
2011 "People are allowed to bring clones in \002$cn\002."
2012 );
2013 }
2014 }
7b261bb8 2015 if($set =~ /^(?:opguard|secureops)$/i) {
2016 cr_set_flag($chan, CRF_OPGUARD, $val);
2017
2018 if($val) {
2019 notice($user,
2020 "OpGuard is now \002ON\002.",
2021 "Channel status may not be granted by unauthorized users in \002$cn\002."#,
2022 #"Note that you must change the $csnick LEVELS settings for VOICE, HALFOP, OP, and/or ADMIN for this setting to have any effect."
2023 );
2024 } else {
2025 notice($user,
2026 "OpGuard is now \002OFF\002.",
2027 "Channel status may be given freely in \002$cn\002."
2028 );
2029 }
2030
2031 return;
2032 }
2033
2034 if($set =~ /^(?:splitops)$/i) {
2035 cr_set_flag($chan, CRF_SPLITOPS, $val);
2036
2037 if($val) {
2038 notice($user, "SplitOps is now \002ON\002.");
2039 } else {
2040 notice($user, "SplitOps is now \002OFF\002.");
2041 }
2042
2043 return;
2044 }
2045
2046 if($set =~ /^(hold|no-?expire)$/i) {
2047 cr_set_flag($chan, CRF_HOLD, $val);
2048
2049 if($val) {
2050 notice($user, "\002$cn\002 will not expire");
2051 services::ulog($csnick, LOG_INFO(), "has held \002$cn\002", $user, $chan);
2052 } else {
2053 notice($user, "\002$cn\002 is no longer held from expiration");
2054 services::ulog($csnick, LOG_INFO(), "has removed \002$cn\002 from hold", $user, $chan);
2055 }
2056
2057 return;
2058 }
2059
2060 if($set =~ /^freeze$/i) {
2061 cr_set_flag($chan, CRF_FREEZE, $val);
2062
2063 if($val) {
2064 notice($user, "\002$cn\002 is now frozen and access suspended");
2065 services::ulog($csnick, LOG_INFO(), "has frozen \002$cn\002", $user, $chan);
2066 } else {
2067 notice($user, "\002$cn\002 is now unfrozen and access restored");
2068 services::ulog($csnick, LOG_INFO(), "has unfrozen \002$cn\002", $user, $chan);
2069 }
2070
2071 return;
2072 }
2073
2074 if($set =~ /^botstay$/i) {
2075 cr_set_flag($chan, CRF_BOTSTAY, $val);
2076
2077 if($val) {
2078 notice($user, "Bot will now always stay in \002$cn");
2079 botserv::bot_join($chan, undef);
2080 } else {
2081 notice($user, "Bot will now part if less than one user is in \002$cn");
2082 botserv::bot_part_if_needed(undef, $chan, "Botstay turned off");
2083 }
2084
2085 return;
2086 }
2087 if($set =~ /^verbose$/i) {
2088 cr_set_flag($chan, CRF_VERBOSE, $val);
2089
2090 if($val) {
2091 notice($user, "Verbose mode enabled on \002$cn");
2092 }
2093 else {
2094 notice($user, "Verbose mode disabled on \002$cn");
2095 }
2096 return;
2097 }
2098
2099 if($set =~ /^greet$/i) {
2100 if($val) {
2101 notice($user, "$csnick SET $cn GREET ON is deprecated.",
2102 "Please use $csnick LEVELS $cn SET GREET <rank>");
2103 } else {
2104 cs_levels_set($user, $chan, 'GREET', 'nobody');
2105 }
2106
2107 return;
2108 }
2109
2110 if($set =~ /^dice$/i) {
2111 if($val) {
2112 notice($user, "$csnick SET $cn DICE ON is deprecated.",
2113 "Please use $csnick LEVELS $cn SET DICE <rank>");
2114 } else {
2115 cs_levels_set($user, $chan, 'DICE', 'nobody');
2116 }
2117
2118 return;
2119 }
2120
2121 if($set =~ /^welcomeinchan$/i) {
2122 cr_set_flag($chan, CRF_WELCOMEINCHAN(), $val);
2123
2124 if($val) {
2125 notice($user, "WELCOME messages will be put in the channel.");
2126 } else {
2127 notice($user, "WELCOME messages will be sent privately.");
2128 }
2129
2130 return;
2131 }
2132
2133 if($set =~ /^log$/i) {
2134 unless(module::is_loaded('logserv')) {
2135 notice($user, "module logserv is not loaded, logging is not available.");
2136 return;
2137 }
2138
2139 if($val) {
2140 logserv::addchan($user, $cn) and cr_set_flag($chan, CRF_LOG, $val);
2141 }
2142 else {
2143 logserv::delchan($user, $cn) and cr_set_flag($chan, CRF_LOG, $val);
2144 }
2145 return;
2146 }
2147
2148 if($set =~ /^a(?:uto)?voice$/i) {
2149 cr_set_flag($chan, CRF_AUTOVOICE(), $val);
2150
2151 if($val) {
2152 notice($user, "All users w/o access will be autovoiced on join.");
2153 } else {
2154 notice($user, "AUTOVOICE disabled.");
2155 }
2156
2157 return;
2158 }
2159
2160 if($set =~ /^(?:never|no)op$/i) {
2161 cr_set_flag($chan, CRF_NEVEROP(), $val);
2162
2163 if($val) {
2164 notice($user, "Users will not be automatically opped on join.");
2165 } else {
2166 notice($user, "Users with access will now be automatically opped on join.");
2167 }
2168
2169 return;
2170 }
2171}
2172
2173sub cs_why($$@) {
2174 my ($user, $chan, @tnicks) = @_;
2175
2176 chk_registered($user, $chan) or return;
2177
cd61df45 2178 my $cn = $chan->{CHAN};
2f1dc2c1 2179
2180 my ($candoNick, $override) = can_do($chan, 'ACCLIST', $user, { OVERRIDE_MSG => "WHY $cn @tnicks" });
39eca0a2 2181 return unless $candoNick;
7b261bb8 2182
7b261bb8 2183 my @reply;
2184 foreach my $tnick (@tnicks) {
2185 my $tuser = { NICK => $tnick };
2186 unless(get_user_id($tuser)) {
716fd28b 2187 push @reply, "\002$tnick\002: No such user.";
cd61df45 2188 next;
7b261bb8 2189 }
2190
2191 my $has;
2192 if(is_online($tnick)) {
2193 $has = 'has';
2194 } else {
2195 $has = 'had';
2196 }
2197
2198 my $n;
2199 $get_all_acc->execute(get_user_id($tuser), $cn);
2200 while(my ($rnick, $acc) = $get_all_acc->fetchrow_array) {
2201 $n++;
2202 push @reply, "\002$tnick\002 $has $plevels[$acc+$plzero] access to \002$cn\002 due to identification to the nick \002$rnick\002.";
2203 }
2204 $get_all_acc->finish();
2205
2206 unless($n) {
2207 push @reply, "\002$tnick\002 has no access to \002$cn\002.";
2208 }
2209 }
2210 notice($user, @reply);
2211}
2212
2213sub cs_setmodes($$$@) {
2214 my ($user, $cmd, $chan, @args) = @_;
2215 no warnings 'void';
2216 my $agent = $user->{AGENT} or $csnick;
2217 my $src = get_user_nick($user);
2218 my $cn = $chan->{CHAN};
2219 my $self;
2220
2221 if (cr_chk_flag($chan, CRF_FREEZE())) {
2222 notice($user, "\002$cn\002 is frozen and access suspended.");
2223 return;
2224 }
2225
2226 if(scalar(@args) == 0) {
2227 @args = ($src);
2228 $self = 1;
2229 } elsif($args[0] =~ /^#/) {
2230 foreach my $chn ($cn, @args) {
2231 next unless $chn =~ /^#/;
2232 no warnings 'prototype'; # we call ourselves
2233 cs_setmodes($user, $cmd, { CHAN => $chn });
2234 }
2235 return;
2236 } elsif((scalar(@args) == 1) and (lc($args[0]) eq lc($src))) {
2237 $self = 1;
2238 }
2239
2240 # PROTECT is deprecated. remove it in a couple versions.
2241 # It should be called ADMIN under PREFIX_AQ
2242 my @mperms = ('VOICE', 'HALFOP', 'OP', 'ADMIN', 'OWNER');
2243 my @l = ('v', 'h', 'o', 'a', 'q');
2244 my ($level, @modes, $count);
2245
2246 if($cmd =~ /voice$/i) { $level = 0 }
2247 elsif($cmd =~ /h(alf)?op$/i) { $level = 1 }
2248 elsif($cmd =~ /op$/i) { $level = 2 }
2249 elsif($cmd =~ /(protect|admin)$/i) { $level = 3 }
2250 elsif($cmd =~ /owner$/i) { $level = 4 }
2251 my $de = 1 if($cmd =~ s/^de//i);
2252 #$cmd =~ s/^de//i;
2253
2254 my $acc = get_best_acc($user, $chan);
2255
2256 # XXX I'm not sure this is the best way to do it.
674d6fee 2257 unless(
2258 ($de and $self) or ($self and ($level + 2) <= $acc) or
39eca0a2 2259 can_do($chan, $mperms[$level], $user, { ACC => $acc, NOREPLY => 1, OVERRIDE_MSG => "$cmd $cn @args" }) )
674d6fee 2260 {
7b261bb8 2261 notice($user, "$cn: $err_deny");
2262 return;
2263 }
2264
2265 my ($override, $check_override);
2266
2267 foreach my $target (@args) {
2268 my ($tuser);
2269
2270 $tuser = ($self ? $user : { NICK => $target } );
2271
2272 unless(is_in_chan($tuser, $chan)) {
2273 notice($user, "\002$target\002 is not in \002$cn\002.");
2274 next;
2275 }
2b8d9e2d 2276
7b261bb8 2277 my $top = get_op($tuser, $chan);
2278
2279 if($de) {
2280 unless($top & (2**$level)) {
2281 notice($user, "\002$target\002 has no $cmd in \002$cn\002.");
2282 next;
2283 }
2b8d9e2d 2284
7b261bb8 2285 if(!$override and get_best_acc($tuser, $chan) > $acc) {
2286 unless($check_override) {
2287 $override = adminserv::can_do($user, 'SUPER');
2288 $check_override = 1;
2289 }
2290 if($check_override and !$override) {
2291 notice($user, "\002$target\002 outranks you in \002$cn\002.");
2292 next;
2293 }
2294 }
2295 } else {
2296 if($top & (2**$level)) {
2297 if($self) {
2298 notice($user, "You already have $cmd in \002$cn\002.");
2299 } else {
2300 notice($user, "\002$target\002 already has $cmd in \002$cn\002.");
2301 }
2302 next;
2303 }
2304 if (cr_chk_flag($chan, CRF_OPGUARD()) and
2305 !can_keep_op($user, $chan, $tuser, $l[$level]))
2306 {
2307 notice($user, "$target may not hold ops in $cn because OpGuard is enabled. ".
2308 "Please respect the founders wishes.");
2309 next;
2310 }
2311 }
2312
2313 push @modes, [($de ? '-' : '+').$l[$level], $target];
2314 $count++;
2315
2316 }
2317
2318 ircd::setmode2(agent($chan), $cn, @modes) if scalar @modes;
2319 ircd::notice(agent($chan), '%'.$cn, "$src used ".($de ? "de$cmd" : $cmd).' '.join(' ', @args))
2320 if !$self and (lc $user->{AGENT} eq lc $csnick) and cr_chk_flag($chan, CRF_VERBOSE);
2321}
2322
2323sub cs_drop($$) {
2324 my ($user, $chan) = @_;
2325 my $cn = $chan->{CHAN};
2326
2327 chk_registered($user, $chan) or return;
2328
2329 unless(get_best_acc($user, $chan) == FOUNDER or adminserv::can_do($user, 'SERVOP')) {
2330 notice($user, $err_deny);
2331 return;
2332 }
2333
2334 drop($chan);
2335 notice($user, $cn.' has been dropped.');
2336 services::ulog($csnick, LOG_INFO(), "dropped $cn", $user, $chan);
2337
2338 undef($enforcers{lc $cn});
2339 botserv::bot_part_if_needed(undef(), $chan, "Channel dropped.");
2340}
44b46f5b 2341sub cs_tempban($$$;$) {
2342 my ($user, $chan, $target, $reason) = @_;
2343 if(ref($chan) ne 'HASH' || !defined($chan->{CHAN})) {
2344 notice($user, "Invalid tempban command, no channel specified");
2345 return;
2346 }
2347 my @argz = split (" ",$reason);
2348 my $expiry;
2349 $expiry = shift @argz if $argz[0] =~ /\+/;
2350 $reason = join (" ", @argz);
2351 my $srclevel = get_best_acc($user, $chan);
2352 my ($nick, $override) = can_do($chan, 'BAN', $user, { ACC => $srclevel });
2353 return unless $nick;
2354 my $src = get_user_nick($user);
2355 my $cn = $chan->{CHAN};
2356 $reason = "$expiry Requested by $src".($reason?": $reason":'' ) . " ($expiry)";
2357 my @errors = (
2358 ["I'm sorry, $src, I'm afraid I can't do that."],
2359 ["They are not in \002$cn\002."],
2360 [$err_deny],
2361 ["User not found"],
2362 );
2363 my $peace = ({modes::splitmodes(get_modelock($chan))}->{Q}->[0] eq '+');
2364 my @targets = split(/\,/, $target);
2365 foreach $target (@targets) {
2366 if($target =~ /[!@]+/) {
2367 ircd::debug("normalizing hostmask $target");
2368 #$target = normalize_hostmask($target);
2369#=cut
2370 my ($nick, $ident, $host) = parse_mask($target);
2371 $nick = '*' unless length($nick);
2372 $ident = '*' unless length($ident);
2373 $host = '*' unless length($host);
2374 $target = "$nick\!$ident\@$host";
2375#=cut
2376 ircd::debug("normalized hostmask: $target");
2377 tempban ($chan, undef, $target, $reason);
2378 next;
2379 }
2380 my $tuser = { NICK => $target };
2381 my $targetlevel = get_best_acc($tuser, $chan);
2382 if(lc $target eq lc agent($chan) or adminserv::is_service($tuser)) {
2383 push @{$errors[0]}, $target;
2384 next;
2385 }
2386 if(!get_user_id($tuser)) {
2387 push @{$errors[3]}, $target;
2388 next;
2389 }
2390
2391 if( ( ($peace and $targetlevel > 0) or ($srclevel <= $targetlevel) )
2392 and not ($override && check_override($user, 'BAN', "tempban $cn $target")) )
2393 {
2394 push @{$errors[2]}, $target;
2395 next;
2396 }
2397 tempban ($chan, $tuser, undef, $reason);
2398 }
2399 foreach my $errlist (@errors) {
2400 if(@$errlist > 1) {
2401 my $msg = shift @$errlist;
2402
2403 foreach my $e (@$errlist) { $e = "\002$e\002" }
2404
2405 notice($user,
2406 "Cannot tempban ".
2407 enum("or", @$errlist).
2408 ": $msg"
2409 );
2410 }
2411 }
2412}
7b261bb8 2413
2414sub cs_kick($$$;$$) {
2415 my ($user, $chan, $target, $ban, $reason) = @_;
baf0062f 2416
2417 my $cmd = ($ban ? 'KICKBAN' : 'KICK');
2418 my $perm = ($ban ? 'BAN' : 'KICK');
39eca0a2 2419 if(ref($chan) ne 'HASH' || !defined($chan->{CHAN})) {
baf0062f 2420 notice($user, "Invalid $cmd command, no channel specified");
2421 return;
2422 }
2423
7b261bb8 2424 my $srclevel = get_best_acc($user, $chan);
2425
674d6fee 2426 my ($nick, $override) = can_do($chan, ($ban ? 'BAN' : 'KICK'), $user, { ACC => $srclevel });
7b261bb8 2427 return unless $nick;
2428
2429 my $src = get_user_nick($user);
2430 my $cn = $chan->{CHAN};
2431
2432 $reason = "Requested by $src".($reason?": $reason":'');
baf0062f 2433
7b261bb8 2434 my @errors = (
2435 ["I'm sorry, $src, I'm afraid I can't do that."],
2436 ["They are not in \002$cn\002."],
2437 [$err_deny],
2438 ["User not found"],
2439 );
2440 my @notinchan = ();
2441 my $peace = ({modes::splitmodes(get_modelock($chan))}->{Q}->[0] eq '+');
2b8d9e2d 2442
7b261bb8 2443 my @targets = split(/\,/, $target);
2444 foreach $target (@targets) {
2445 my $tuser = { NICK => $target };
2446 my $targetlevel = get_best_acc($tuser, $chan);
2447
2448 if(lc $target eq lc agent($chan) or adminserv::is_service($tuser)) {
2449 push @{$errors[0]}, $target;
2450 next;
2451 }
2b8d9e2d 2452
7b261bb8 2453 if(get_user_id($tuser)) {
2454 unless(is_in_chan($tuser, $chan)) {
2455 if ($ban) {
2456 push @notinchan, $tuser;
2457 } else {
2458 push @{$errors[1]}, $target;
2459 }
2460 next;
2461 }
2462 } else {
2463 push @{$errors[3]}, $target;
2464 next;
2465 }
2466
baf0062f 2467 if( ( ($peace and $targetlevel > 0) or ($srclevel <= $targetlevel) )
39eca0a2 2468 and not ($override && check_override($user, ($ban ? 'BAN' : 'KICK'), "$cmd $cn $target")) )
baf0062f 2469 {
7b261bb8 2470 push @{$errors[2]}, $target;
2471 next;
2472 }
2b8d9e2d 2473
7b261bb8 2474 if($ban) {
93cbe111 2475 kickban($chan, $tuser, undef, $reason, 1);
7b261bb8 2476 } else {
2477 ircd::kick(agent($chan), $cn, $target, $reason) unless adminserv::is_service($user);
2478 }
2479 }
93cbe111 2480 ircd::flushmodes() if($ban);
2481
7b261bb8 2482 foreach my $errlist (@errors) {
2483 if(@$errlist > 1) {
2484 my $msg = shift @$errlist;
2485
2486 foreach my $e (@$errlist) { $e = "\002$e\002" }
2487
2488 notice($user,
baf0062f 2489 "Cannot $cmd ".
7b261bb8 2490 enum("or", @$errlist).
2491 ": $msg"
2492 );
2493 }
2494 }
2495 cs_ban($user, $chan, '', @notinchan) if ($ban and scalar (@notinchan));
2496}
2497
2498sub cs_kickmask($$$;$$) {
2499 my ($user, $chan, $mask, $ban, $reason) = @_;
2500
2501 my $srclevel = get_best_acc($user, $chan);
39eca0a2 2502 my $src = get_user_nick($user);
2503 my $cn = $chan->{CHAN};
7b261bb8 2504
39eca0a2 2505 my $candoOpts = { ACC => $srclevel, OVERRIDE_MSG => 'KICK'.($ban ? 'BAN' : '')."MASK $cn $mask $reason" };
2506 my ($nick, $override) = can_do($chan, ($ban ? 'BAN' : 'KICK'), $user, $candoOpts);
7b261bb8 2507 return unless $nick;
2508
7b261bb8 2509
2510 $reason = "Requested by $src".($reason?": $reason":'');
2511
2512 my $count = kickmask_noacc($chan, $mask, $reason, $ban);
2513 notice($user, ($count ? "Users kicked from \002$cn\002: $count." : "No users in \002$cn\002 matched $mask."))
2514}
2515
2516sub cs_ban($$$@) {
2517 my ($user, $chan, $type, @targets) = @_;
2518 my $cn = $chan->{CHAN};
2519 my $src = get_user_nick($user);
2520
2521 my $srclevel = get_best_acc($user, $chan);
674d6fee 2522 my ($nick, $override) = can_do($chan, 'BAN', $user, { ACC => $srclevel });
7b261bb8 2523 return unless $nick;
2524
2525 my @errors = (
2526 ["I'm sorry, $src, I'm afraid I can't do that."],
2527 ["User not found"],
2528 [$err_deny]
2529 );
2530
2531 my (@bans, @unbans);
2532 foreach my $target (@targets) {
2533 my $tuser;
2534
2535 if(ref($target)) {
2536 $tuser = $target;
2b8d9e2d 2537 }
7b261bb8 2538 elsif($target =~ /\,/) {
2539 push @targets, split(',', $target);
2540 next;
2541 }
2542 elsif($target eq '') {
2543 # Should never happen
2544 # but it could, given the split above
2545 next;
2546 }
2547 elsif($target =~ /^-/) {
2548 $target =~ s/^\-//;
2549 push @unbans, $target;
2550 next;
2551 }
2552=cut
2553 elsif($target =~ /[!@]+/) {
2b8d9e2d 2554 ircd::debug("normalizing hostmask $target");
7b261bb8 2555 #$target = normalize_hostmask($target);
2556#=cut
2557 my ($nick, $ident, $host) = parse_mask($target);
2558 $nick = '*' unless length($nick);
2559 $ident = '*' unless length($ident);
2560 $host = '*' unless length($host);
2561 $target = "$nick\!$ident\@$host";
2562#=cut
2b8d9e2d 2563 ircd::debug("normalized hostmask: $target");
7b261bb8 2564
2565 push @bans, $target;
2566 next;
2567 }
2568=cut
2569 elsif(valid_nick($target)) {
2570 $tuser = { NICK => $target };
2571 }
2572 elsif($target = validate_ban($target)) {
2573 push @bans, $target;
2574 next;
88165864 2575 } else {
2576 notice($user, "Not a valid ban target: $target");
2577 next;
7b261bb8 2578 }
2579 my $targetlevel = get_best_acc($tuser, $chan);
2580
2581 if(lc $target eq lc agent($chan) or adminserv::is_service($tuser)) {
2582 push @{$errors[0]}, get_user_nick($tuser);
2583 next;
2584 }
2585
2586 unless(get_user_id($tuser)) {
2587 push @{$errors[1]}, get_user_nick($tuser);
2588 next;
2589 }
39eca0a2 2590 if( $srclevel <= $targetlevel and not ($override && check_override($user, 'BAN', "BAN $cn $target")) ) {
7b261bb8 2591 push @{$errors[2]}, $target;
2592 next;
2593 }
2594
2595 push @bans, make_banmask($chan, $tuser, $type);
2596 }
2597
2598 foreach my $errlist (@errors) {
2599 if(@$errlist > 1) {
2600 my $msg = shift @$errlist;
2601
2602 foreach my $e (@$errlist) { $e = "\002$e\002" }
2603
2604 notice($user,
2605 "Cannot ban ".
2606 enum("or", @$errlist).
2607 ": $msg"
2608 );
2609 }
2610 }
2611
2612 ircd::ban_list(agent($chan), $cn, +1, 'b', @bans) if (scalar(@bans));
2613 ircd::notice(agent($chan), $cn, "$src used BAN ".join(' ', @bans))
2614 if (lc $user->{AGENT} eq lc $csnick) and (cr_chk_flag($chan, CRF_VERBOSE) and scalar(@bans));
2615 cs_unban($user, $chan, @unbans) if scalar(@unbans);
2616}
2617
2618sub cs_invite($$@) {
2619 my ($user, $chan, @targets) = @_;
2620 my $src = get_user_nick($user);
2621 my $cn = $chan->{CHAN};
2622 my $srclevel = get_best_acc($user, $chan);
2623
2624 my @errors = (
2625 ["They are not online."],
2626 ["They are already in \002$cn\002."],
2627 [$err_deny]
2628 );
2629
2630 my @invited;
2631 foreach my $target (@targets) {
2632 my $tuser;
30a8b057 2633 my $tnick;
2634 if(ref($target)) {
2635 $tuser = $target;
2636 $tnick = get_user_nick($tuser);
2637 } elsif(lc($src) eq lc($target)) {
7b261bb8 2638 $tuser = $user;
30a8b057 2639 $tnick = $src;
2640 } elsif($target =~ /\,/) {
7b261bb8 2641 push @targets, split(',', $target);
2642 next;
30a8b057 2643 } elsif($target eq '') {
7b261bb8 2644 # Should never happen
2645 # but it could, given the split above
2646 next;
30a8b057 2647 } else {
7b261bb8 2648 $tuser = { NICK => $target };
30a8b057 2649 $tnick = $target;
7b261bb8 2650 }
2651
39eca0a2 2652 my $candoOpts = { ACC => $srclevel, NOREPLY => 1, OVERRIDE_MSG => "INVITE $cn $target" };
30a8b057 2653 if(lc($src) eq lc($tnick)) {
39eca0a2 2654 unless(can_do($chan, 'InviteSelf', $user, $candoOpts)) {
30a8b057 2655 push @{$errors[2]}, $tnick;
7b261bb8 2656 next;
2657 }
2658 }
2659 else {
39eca0a2 2660 unless(can_do($chan, 'INVITE', $user, $candoOpts)) {
30a8b057 2661 push @{$errors[2]}, $tnick;
7b261bb8 2662 next;
2663 }
2b8d9e2d 2664
30a8b057 2665 unless(nickserv::is_online($tnick)) {
2666 push @{$errors[0]}, $tnick;
7b261bb8 2667 next;
2668 }
2b8d9e2d 2669
7b261bb8 2670 # invite is annoying, so punish them mercilessly
920a4abc 2671 return if flood_check($user, 2);
7b261bb8 2672 }
2b8d9e2d 2673
7b261bb8 2674 if(is_in_chan($tuser, $chan)) {
30a8b057 2675 push @{$errors[1]}, $tnick;
7b261bb8 2676 next;
2677 }
2b8d9e2d 2678
30a8b057 2679 ircd::invite(agent($chan), $cn, $tnick); push @invited, $tnick;
2680 ircd::notice(agent($chan), $tnick, "\002$src\002 has invited you to \002$cn\002.")
2681 unless(lc($src) eq lc($tnick));
7b261bb8 2682 }
2683
2684 foreach my $errlist (@errors) {
2685 if(@$errlist > 1) {
2686 my $msg = shift @$errlist;
2687
2688 foreach my $e (@$errlist) { $e = "\002$e\002" }
2689
2690 notice($user,
2691 "Cannot invite ".
2692 enum("or", @$errlist).
2693 ": $msg"
2694 );
2695 }
2696 }
2b8d9e2d 2697
7b261bb8 2698 ircd::notice(agent($chan), $cn, "$src used INVITE ".join(' ', @invited))
2699 if (lc $user->{AGENT} eq lc $csnick)and cr_chk_flag($chan, CRF_VERBOSE) and scalar(@invited);
2700}
2701
2702sub cs_close($$$) {
2703 my ($user, $chan, $reason, $type) = @_;
2704 # $type is a flag, either CRF_CLOSE or CRF_DRONE
2705 my $cn = $chan->{CHAN};
2706 my $oper;
2707
5dcf2da9 2708 unless($oper = adminserv::can_do($user, 'SERVOP')) {
7b261bb8 2709 notice($user, $err_deny);
2710 return;
2711 }
2712
2713 my $rlength = length($reason);
2714 if($rlength >= 350) {
2715 notice($user, 'Close reason is too long by '. $rlength-350 .' character(s). Maximum length is 350 characters.');
2716 return;
2717 }
2718
2719 if(is_registered($chan)) {
2720 $drop_acc->execute($cn);
2721 $drop_lvl->execute($cn);
2722 $del_close->execute($cn);
2723 $drop_akick->execute($cn);
2724 $drop_welcome->execute($cn);
2725 $drop_chantext->execute($cn);
2726 $drop_nicktext->execute($cn); # Leftover channel auths
2727
2728 $set_founder->execute($oper, $cn);
2729 }
2730 else {
2731 $register->execute($cn, $reason, $oper);
2732 }
2733 $set_modelock->execute('+rsnt', $cn);
2734 do_modelock($chan);
2735 set_acc($oper, undef, $chan, FOUNDER);
2736
2737 $set_close->execute($cn, $reason, $oper, $type);
2738 cr_set_flag($chan, (CRF_FREEZE | CRF_CLOSE | CRF_DRONE), 0); #unset flags
2739 cr_set_flag($chan, CRF_HOLD, 1); #set flags
2740
2741 my $src = get_user_nick($user);
2742 my $time = gmtime2(time);
2743 my $cmsg = "is closed [$src $time]: $reason";
2744
2745 if ($type == CRF_CLOSE) {
644d7dd3 2746 cr_set_flag($chan, CRF_CLOSE, 1); #set flags
7b261bb8 2747 clear_users($chan, "Channel $cmsg");
2748 ircd::settopic(agent($chan), $cn, $src, time(), "Channel $cmsg")
2749 }
2750 elsif ($type == CRF_DRONE) {
644d7dd3 2751 cr_set_flag($chan, CRF_DRONE, 1); #set flags
7b261bb8 2752 chan_kill($chan, "$cn $cmsg");
2753 }
2754
2755 notice($user, "The channel \002$cn\002 is now closed.");
2756 services::ulog($csnick, LOG_INFO(), "closed $cn with reason: $reason", $user, $chan);
2757}
2758
2759sub cs_clear_pre($$) {
2760 my ($user, $chan) = @_;
2761 my $cn = $chan->{CHAN};
2762
2763 my $srclevel = get_best_acc($user, $chan);
2764
674d6fee 2765 my ($cando, $override) = can_do($chan, 'CLEAR', $user, { ACC => $srclevel });
7b261bb8 2766 return 0 unless($cando);
2767
2768 $get_highrank->execute($cn);
2769 my ($highrank_nick, $highrank_level) = $get_highrank->fetchrow_array();
2770 $get_highrank->finish();
2771
2772 if($highrank_level > $srclevel && !$override) {
2773 notice($user, "$highrank_nick outranks you in $cn (level: $levels[$highrank_level])");
2774 return 0;
2775 }
2776
2777 return 1;
2778}
2779
2780sub cs_clear_users($$;$) {
2781 my ($user, $chan, $reason) = @_;
2782 my $src = get_user_nick($user);
2783
2784 cs_clear_pre($user, $chan) or return;
2785
2786 my $rlength = length($reason);
2787 if($rlength >= 350) {
2788 notice($user, 'Clear reason is too long by '. $rlength-350 .' character(s). Maximum length is 350 characters.');
2789 return;
2790 }
2b8d9e2d 2791
7b261bb8 2792 clear_users($chan, "CLEAR USERS by \002$src\002".($reason?" reason: $reason":''));
2793}
2794
2795sub cs_clear_modes($$;$) {
2796 my ($user, $chan, $reason) = @_;
2797 my $cn = $chan->{CHAN};
2798 my $src = get_user_nick($user);
2799
2800 cs_clear_pre($user, $chan) or return;
2801
2802 my $rlength = length($reason);
2803 if($rlength >= 350) {
2804 notice($user, 'Clear reason is too long by '. $rlength-350 .' character(s). Maximum length is 350 characters.');
2805 return;
2806 }
2807
2808 my $agent = agent($chan);
2809 ircd::notice($agent, $cn, "CLEAR MODES by \002$src\002".($reason?" reason: $reason":''));
2810
2811 $get_chanmodes->execute($cn);
2812 my ($curmodes) = $get_chanmodes->fetchrow_array;
2813 my $ml = get_modelock($chan);
2814
2815 # This method may exceed the 12-mode limit
2816 # But it seems to succeed anyway, even with more than 12.
2817 my ($modes, $parms) = split(/ /, modes::merge(modes::invert($curmodes), $ml, 1). ' * *', 2);
2818 # we split this separately,
2819 # as otherwise it insists on taking the result of the split as a scalar quantity
2820 ircd::setmode($agent, $cn, $modes, $parms);
2821 do_modelock($chan);
2822}
2823
2824sub cs_clear_ops($$;$) {
2825 my ($user, $chan, $reason) = @_;
2826 my $cn = $chan->{CHAN};
2827 my $src = get_user_nick($user);
2828
2829 cs_clear_pre($user, $chan) or return;
2830
2831 my $rlength = length($reason);
2832 if($rlength >= 350) {
2833 notice($user, 'Clear reason is too long by '. $rlength-350 .' character(s). Maximum length is 350 characters.');
2834 return;
2835 }
2836
2837 clear_ops($chan);
2838
2839 ircd::notice(agent($chan), $cn, "CLEAR OPS by \002$src\002".($reason?" reason: $reason":''));
2840 return 1;
2841}
2842
2843sub cs_clear_bans($$;$$) {
2844 my ($user, $chan, $type, $reason) = @_;
2845 my $cn = $chan->{CHAN};
2846 my $src = get_user_nick($user);
2847 $type = 0 unless defined $type;
2848
2849 cs_clear_pre($user, $chan) or return;
2850
2851 my $rlength = length($reason);
2852 if($rlength >= 350) {
2853 notice($user, 'Clear reason is too long by '. $rlength-350 .' character(s). Maximum length is 350 characters.');
2854 return;
2855 }
2856
2857 clear_bans($chan, $type);
2858
2859 ircd::notice(agent($chan), $cn, "CLEAR BANS by \002$src\002".($reason?" reason: $reason":''));
2860}
2861
2862sub cs_welcome_pre($$) {
2863 my ($user, $chan) = @_;
2864
42508b05 2865 return can_do($chan, 'WELCOME', $user);
7b261bb8 2866}
2867
2868sub cs_welcome_add($$$) {
2869 my ($user, $chan, $msg) = @_;
2870 my $src = get_best_acc($user, $chan, 1);
2871 my $cn = $chan->{CHAN};
2872
2873 cs_welcome_pre($user, $chan) or return;
2874
2875 my $mlength = length($msg);
2876 if($mlength >= 350) {
2877 notice($user, 'Welcome Message is too long by '. $mlength-350 .' character(s). Maximum length is 350 characters.');
2878 return;
2879 }
2880
2881 $count_welcome->execute($cn);
2882 my $count = $count_welcome->fetchrow_array;
2883 if ($count >= 5) {
2884 notice($user, 'There is a maximum of five (5) Channel Welcome Messages.');
2885 return;
2886 }
2887
2888 $add_welcome->execute($cn, ++$count, $src, $msg);
2889
2890 notice($user, "Welcome message number $count for \002$cn\002 set to:", " $msg");
2891}
2892
2893sub cs_welcome_list($$) {
2894 my ($user, $chan) = @_;
2895 my $cn = $chan->{CHAN};
2896
2897 cs_welcome_pre($user, $chan) or return;
2898
2899 $list_welcome->execute($cn);
2900
2901 my @data;
2902
2903 while(my ($id, $time, $adder, $msg) = $list_welcome->fetchrow_array) {
2904 push @data, ["$id.", $adder, gmtime2($time), $msg];
2905 }
2906 $list_welcome->finish();
2907
2908 notice($user, columnar {TITLE => "Welcome message list for \002$cn\002:", DOUBLE=>1,
2909 NOHIGHLIGHT => nr_chk_flag_user($user, NRF_NOHIGHLIGHT)}, @data);
2910}
2911
2912sub cs_welcome_del($$$) {
2913 my ($user, $chan, $id) = @_;
2914 my $cn = $chan->{CHAN};
2915
2916 cs_welcome_pre($user, $chan) or return;
2917
2918 if ($del_welcome->execute($cn, $id) == 1) {
2919 notice($user, "Welcome Message \002$id\002 deleted from \002$cn\002");
2920 $consolidate_welcome->execute($cn, $id);
2921 }
2922 else {
2923 notice($user,
2924 "Welcome Message number $id for \002$cn\002 does not exist.");
2925 }
2926}
2927
2928sub cs_alist($$;$) {
2929 my ($user, $chan, $mask) = @_;
2930 my $cn = $chan->{CHAN};
2931
2932 chk_registered($user, $chan) or return;
2933
2934 my $slevel = get_best_acc($user, $chan);
2935
674d6fee 2936 can_do($chan, 'ACCLIST', $user, { ACC => $slevel }) or return;
7b261bb8 2937
2938 my @reply;
2939
2940 if($mask) {
2941 my ($mnick, $mident, $mhost) = glob2sql(parse_mask($mask));
2942 $mnick = '%' if($mnick eq '');
2943 $mident = '%' if($mident eq '');
2944 $mhost = '%' if($mhost eq '');
2945
2946 $get_acc_list2_mask->execute($mnick, $cn, $mnick, $mident, $mhost);
2947 while(my ($nick, $adder, $level, $time, $last_used, $ident, $vhost) = $get_acc_list2_mask->fetchrow_array) {
2948 push @reply, "*) $nick ($ident\@$vhost) Rank: ".$levels[$level] . ($adder ? ' Added by: '.$adder : '');
2949 push @reply, ' '.($time ? 'Date/time added: '. gmtime2($time).' ' : '').
2950 ($last_used ? 'Last used '.time_ago($last_used).' ago' : '') if ($time or $last_used);
2951 }
2952 $get_acc_list2_mask->finish();
2953 } else {
2954 $get_acc_list2->execute($cn);
2955 while(my ($nick, $adder, $level, $time, $last_used, $ident, $vhost) = $get_acc_list2->fetchrow_array) {
2956 push @reply, "*) $nick ($ident\@$vhost) Rank: ".$levels[$level] . ($adder ? ' Added by: '.$adder : '');
2957 push @reply, ' '.($time ? 'Date/time added: '. gmtime2($time).' ' : '').
2958 ($last_used ? 'Last used '.time_ago($last_used).' ago' : '') if ($time or $last_used);
2959 }
2960 $get_acc_list2->finish();
2961 }
2962
2963 notice($user, "Access list for \002$cn\002:", @reply);
2964
2965 return;
2966}
2967
2968sub cs_banlist($$) {
2969 my ($user, $chan) = @_;
2970 my $cn = $chan->{CHAN};
674d6fee 2971 can_do($chan, 'UnbanSelf', $user, { NOREPLY => 1 }) or can_do($chan, 'BAN', $user) or return;
7b261bb8 2972
2973 my $i = 0; my @data;
2974 $list_bans->execute($cn, 0);
2975 while(my ($mask, $setter, $time) = $list_bans->fetchrow_array()) {
2976 push @data, ["\002".++$i."\002", sql2glob($mask), $setter, ($time ? gmtime2($time) : '')];
2977 }
2978
2979 notice($user, columnar {TITLE => "Ban list of \002$cn\002:", DOUBLE=>1,
2980 NOHIGHLIGHT => nr_chk_flag_user($user, NRF_NOHIGHLIGHT)}, @data);
2981}
2982
2983sub cs_unban($$@) {
2984 my ($user, $chan, @parms) = @_;
2985 my $cn = $chan->{CHAN};
2986
114e3f65 2987 my $self;
2988 $self = 1 if ( (scalar(@parms) == 1) and ( lc($parms[0]) eq lc(get_user_nick($user)) ) );
7b261bb8 2989 if ($parms[0] eq '*') {
2990 cs_clear_bans($user, $chan);
2991 return;
2992 }
2993 else {
674d6fee 2994 can_do($chan, ($self ? 'UnbanSelf' : 'UNBAN'), $user) or return;
7b261bb8 2995 }
2996
2997 my (@userlist, @masklist);
2998 foreach my $parm (@parms) {
2999 if(valid_nick($parm)) {
3000 my $tuser = ($self ? $user : { NICK => $parm });
3001 unless(get_user_id($tuser)) {
3002 notice($user, "No such user: \002$parm\002");
3003 next;
3004 }
3005 push @userlist, $tuser;
3006 } elsif($parm =~ /^[0-9\.,-]+$/) {
3007 foreach my $num (makeSeqList($parm)) {
3008 push @masklist, get_ban_num($chan, $num);
3009 }
3010 } else {
3011 push @masklist, $parm;
3012 }
3013 }
3014
3015 if(scalar(@userlist)) {
3016 unban_user($chan, @userlist);
3017 notice($user, "All bans affecting " .
3018 ( $self ? 'you' : enum( 'and', map(get_user_nick($_), @userlist) ) ) .
3019 " on \002$cn\002 have been removed.");
3020 }
3021 if(scalar(@masklist)) {
3022 ircd::ban_list(agent($chan), $cn, -1, 'b', @masklist);
3023 notice($user, "The following bans have been removed: ".join(' ', @masklist))
3024 if scalar(@masklist);
3025 }
3026}
3027
3028sub cs_updown($$@) {
3029 my ($user, $cmd, @chans) = @_;
3030 return cs_updown2($user, $cmd, { CHAN => shift @chans }, @chans)
3031 if (defined($chans[1]) and $chans[1] !~ "^\#" and $chans[0] =~ "^\#");
3032
3033 @chans = get_user_chans($user)
3034 unless (@chans);
3035
3036 if (uc($cmd) eq 'UP') {
3037 foreach my $cn (@chans) {
3038 next unless ($cn =~ /^\#/);
3039 my $chan = { CHAN => $cn };
3040 next if cr_chk_flag($chan, (CRF_DRONE | CRF_CLOSE | CRF_FREEZE), 1);
3041 chanserv::set_modes($user, $chan, chanserv::get_best_acc($user, $chan));
3042 }
3043 }
3044 elsif (uc($cmd) eq 'DOWN') {
3045 foreach my $cn (@chans) {
3046 next unless ($cn =~ /^\#/);
3047 chanserv::unset_modes($user, { CHAN => $cn });
3048 }
3049 }
3050}
3051
3052sub cs_updown2($$$@) {
3053 my ($user, $cmd, $chan, @targets) = @_;
3054 no warnings 'void';
3055 my $agent = $user->{AGENT} or $csnick;
3056 my $cn = $chan->{CHAN};
3057
3058 return unless chk_registered($user, $chan);
3059 if (cr_chk_flag($chan, CRF_FREEZE())) {
3060 notice($user, "\002$cn\002 is frozen and access suspended.");
3061 return;
3062 }
3063
3064 my $acc = get_best_acc($user, $chan);
674d6fee 3065 return unless(can_do($chan, 'UPDOWN', $user, { ACC => $acc }));
7b261bb8 3066
3067 my $updown = ((uc($cmd) eq 'UP') ? 1 : 0);
3068
3069 my ($override, $check_override);
3070 my (@list, $count);
3071 foreach my $target (@targets) {
3072
3073 my $tuser = { NICK => $target };
3074
3075 unless(is_in_chan($tuser, $chan)) {
3076 notice($user, "\002$target\002 is not in \002$cn\002.");
3077 next;
3078 }
3079
3080 if($updown) {
3081 push @list, $target;
3082 chanserv::set_modes($tuser, $chan, chanserv::get_best_acc($tuser, $chan));
3083 }
3084 else {
3085 my $top = get_op($tuser, $chan);
3086 unless($top) {
3087 notice($user, "\002$target\002 is already deopped in \002$cn\002.");
3088 next;
3089 }
3090
3091 if(!$override and get_best_acc($tuser, $chan) > $acc) {
3092 unless($check_override) {
3093 $override = adminserv::can_do($user, 'SUPER');
3094 $check_override = 1;
3095 }
3096 if($check_override and !$override) {
3097 notice($user, "\002$target\002 outranks you in \002$cn\002.");
3098 next;
3099 }
3100 }
3101 push @list, $target;
3102 chanserv::unset_modes($tuser, { CHAN => $cn });
3103 }
3104 $count++;
3105 }
3106
3107 my $src = get_user_nick($user);
3108 ircd::notice(agent($chan), '%'.$cn, "$src used $cmd ".join(' ', @list))
3109 if (lc $user->{AGENT} eq lc $csnick) and cr_chk_flag($chan, CRF_VERBOSE);
3110}
3111
3112sub cs_getkey($$) {
3113 my ($user, $chan) = @_;
3114 my $cn = $chan->{CHAN};
3115
674d6fee 3116 can_do($chan, 'GETKEY', $user) or return;
7b261bb8 3117
3118 $get_chanmodes->execute($cn);
3119 my $modes = $get_chanmodes->fetchrow_array; $get_chanmodes->finish();
3120
3121 if(my $key = modes::get_key($modes)) {
3122 notice($user, "Channel key for \002$cn\002: $key");
3123 }
3124 else {
3125 notice($user, "\002$cn\002 has no channel key.");
3126 }
3127}
3128
3129sub cs_auth($$$@) {
3130 my ($user, $chan, $cmd, @args) = @_;
3131 my $cn = $chan->{CHAN};
3132 $cmd = lc $cmd;
3133
3134 return unless chk_registered($user, $chan);
3135 return unless can_do($chan, 'AccChange', $user);
3136 my $userlevel = get_best_acc($user, $chan);
3137 if($cmd eq 'list') {
3138 my @data;
3139 $list_auth_chan->execute($cn);
3140 while(my ($nick, $data) = $list_auth_chan->fetchrow_array()) {
3141 my ($adder, $old, $level, $time) = split(/:/, $data);
3142 push @data, ["\002$nick\002", $levels[$level], $adder, gmtime2($time)];
3143 }
3144 if ($list_auth_chan->rows()) {
3145 notice($user, columnar {TITLE => "Pending authorizations for \002$cn\002:",
3146 NOHIGHLIGHT => nr_chk_flag_user($user, NRF_NOHIGHLIGHT)}, @data);
3147 }
3148 else {
3149 notice($user, "There are no pending authorizations for \002$cn\002");
3150 }
3151 $list_auth_chan->finish();
3152 }
3153 elsif($cmd eq 'remove' or $cmd eq 'delete' or $cmd eq 'del') {
3154 my ($nick, $adder, $old, $level, $time);
3155 my $parm = shift @args;
3156 if(misc::isint($parm) and ($nick, $adder, $old, $level, $time) = get_auth_num($cn, $parm))
3157 {
3158 }
3159 elsif (($adder, $old, $level, $time) = get_auth_nick($cn, $parm))
3160 {
3161 $nick = $parm;
3162 }
3163 unless ($nick) {
3164 # This should normally be an 'else' as the elsif above should prove false
3165 # For some reason, it doesn't work. the unless ($nick) fixes it.
3166 # It only doesn't work for numbered entries
3167 notice($user, "There is no entry for \002$parm\002 in \002$cn\002's AUTH list");
3168 return;
3169 }
3170 $nickserv::del_auth->execute($nick, $cn); $nickserv::del_auth->finish();
3171 my $log_str = "deleted AUTH entry $cn $nick $levels[$level]";
3172 my $src = get_user_nick($user);
3173 notice($user, "You have $log_str");
3174 ircd::notice(agent($chan), '%'.$cn, "has \002$src\002 has $log_str")
3175 if cr_chk_flag($chan, CRF_VERBOSE);
3176 services::ulog($chanserv::csnick, LOG_INFO(), "has $log_str", $user, $chan);
3177 }
3178 else {
3179 notice($user, "Unknown AUTH command \002$cmd\002");
3180 }
3181}
3182
3183sub cs_mode($$$@) {
3184 my ($user, $chan, $modes_in, @parms_in) = @_;
674d6fee 3185 can_do($chan, 'MODE', $user) or return undef;
7b261bb8 3186 ($modes_in, @parms_in) = validate_chmodes($modes_in, @parms_in);
3187
3188 my %permhash = (
3189 'q' => 'OWNER',
3190 'a' => 'ADMIN',
3191 'o' => 'OP',
3192 'h' => 'HALFOP',
3193 'v' => 'VOICE',
3194 );
3195 my $sign = '+'; my $cn = $chan->{CHAN};
111514a1 3196 my ($modes_out, @parms_out, @bans);
7b261bb8 3197 foreach my $mode (split(//, $modes_in)) {
3198 $sign = $mode if $mode =~ /[+-]/;
3199 if ($permhash{$mode}) {
3200 my $parm = shift @parms_in;
3201 cs_setmodes($user, ($sign eq '-' ? 'de' : '').$permhash{$mode}, $chan, $parm);
3202 }
111514a1 3203 elsif ($mode eq 'b') {
3204 my $parm = shift @parms_in;
3205 if($sign eq '-') {
3206 $parm = '-'.$parm;
3207 }
3208 push @bans, $parm;
3209 }
3210 elsif($mode =~ /[eIlLkjf]/) {
7b261bb8 3211 $modes_out .= $mode;
3212 push @parms_out, shift @parms_in;
3213 } else {
3214 $modes_out .= $mode;
3215 }
3216 }
3217
111514a1 3218 if(scalar(@bans)) {
3219 cs_ban($user, $chan, undef, @bans);
3220 }
7b261bb8 3221 return if $modes_out =~ /^[+-]*$/;
3222 ircd::setmode(agent($chan), $chan->{CHAN}, $modes_out, join(' ', @parms_out));
3223 do_modelock($chan, $modes_out.' '.join(' ', @parms_out));
3224
3225 $modes_out =~ s/^[+-]*([+-].*)$/$1/;
3226 ircd::notice(agent($chan), '%'.$cn, get_user_nick($user).' used MODE '.join(' ', $modes_out, @parms_out))
3227 if (lc $user->{AGENT} eq lc $csnick) and cr_chk_flag($chan, CRF_VERBOSE);
3228}
3229
3230sub cs_copy($$@) {
3231 my ($user, $chan1, @args) = @_;
3232 my $cn1 = $chan1->{CHAN};
3233 my $cn2;
3234 my $type;
3235 if($args[0] =~ /^#/) {
3236 $cn2 = shift @args;
3237 $type = 'all';
3238 }
3239 if($args[0] =~ /(?:acc(?:ess)?|akick|levels|all)/i) {
3240 $type = shift @args;
3241 $cn2 = shift @args unless $cn2;
3242 }
3243 my $rank;
3244 if($type =~ /^acc(?:ess)?/i) {
3245 if($cn2 =~ /^#/) {
3246 $rank = shift @args;
3247 } else {
3248 $rank = $cn2;
3249 $cn2 = shift @args;
3250 }
3251 }
3252 unless(defined $cn2 and defined $type) {
3253 notice($user, 'Unknown COPY command', 'Syntax: COPY #chan1 [type] #chan2');
3254 }
3255 my $chan2 = { CHAN => $cn2 };
3256 if(lc($cn1) eq lc($cn2)) {
3257 notice($user, "You cannot copy a channel onto itself.");
3258 }
3259 unless(is_registered($chan1)) {
3260 notice($user, "Source channel \002$cn1\002 must be registered.");
3261 return;
3262 }
674d6fee 3263 can_do($chan1, 'COPY', $user) or return undef;
7b261bb8 3264 if(lc $type eq 'all') {
3265 if(is_registered($chan2)) {
3266 notice($user, "When copying all channel details, destination channel cannot be registered.");
3267 return;
3268 } elsif(!(get_op($user, $chan2) & ($opmodes{o} | $opmodes{a} | $opmodes{q}))) {
3269 # This would be preferred to be a 'opmode_mask' or something
3270 # However that might be misleading due to hop not being enough to register
3271 notice($user, "You must have channel operator status to register \002$cn2\002.");
3272 return;
3273 } else {
3274 cs_copy_chan_all($user, $chan1, $chan2);
3275 return;
3276 }
3277 } else {
3278 unless(is_registered($chan2)) {
3279 notice($user, "When copying channel lists, destination channel must be registered.");
3280 return;
3281 }
674d6fee 3282 can_do($chan2, 'COPY', $user) or return undef;
7b261bb8 3283 }
3284 if(lc $type eq 'akick') {
3285 cs_copy_chan_akick($user, $chan1, $chan2);
3286 } elsif(lc $type eq 'levels') {
3287 cs_copy_chan_levels($user, $chan1, $chan2);
3288 } elsif($type =~ /^acc(?:ess)?/i) {
3289 cs_copy_chan_acc($user, $chan1, $chan2, xop_byname($rank));
3290 }
3291}
3292
3293sub cs_copy_chan_all($$$) {
3294 my ($user, $chan1, $chan2) = @_;
3295 cs_copy_chan_chanreg($user, $chan1, $chan2);
3296 cs_copy_chan_levels($user, $chan1, $chan2);
3297 cs_copy_chan_acc($user, $chan1, $chan2);
3298 cs_copy_chan_akick($user, $chan1, $chan2);
3299 return;
3300}
3301
3302sub cs_copy_chan_chanreg($$$) {
3303 my ($user, $chan1, $chan2) = @_;
3304 my $cn1 = $chan1->{CHAN};
3305 my $cn2 = $chan2->{CHAN};
3306
3307 copy_chan_chanreg($cn1, $cn2);
3308 botserv::bot_join($chan2) unless (lc(agent($chan2)) eq lc($csnick) );
3309 do_modelock($chan2);
3310 notice($user, "Registration for \002$cn1\002 copied to \002$cn2\002");
3311
3312 my $log_str = "copied the channel registration for \002$cn1\002 to \002$cn2\002";
3313 services::ulog($chanserv::csnick, LOG_INFO(), "$log_str", $user, $chan1);
3314
3315 my $src = get_user_nick($user);
3316 ircd::notice(agent($chan1), '%'.$cn1, "\002$src\002 $log_str")
3317 if cr_chk_flag($chan1, CRF_VERBOSE);
3318 ircd::notice(agent($chan2), '%'.$cn2, "\002$src\002 $log_str")
3319 if cr_chk_flag($chan2, CRF_VERBOSE);
3320}
3321
3322sub cs_copy_chan_acc($$$;$) {
3323 my ($user, $chan1, $chan2, $level) = @_;
3324 my $cn1 = $chan1->{CHAN};
3325 my $cn2 = $chan2->{CHAN};
3326
3327 copy_chan_acc($cn1, $cn2, $level);
3328
3329 unless(cr_chk_flag($chan2, CRF_NEVEROP)) {
3330 $get_chan_users->execute($cn2); my @targets;
3331 while (my ($nick, $uid) = $get_chan_users->fetchrow_array()) {
3332 push @targets, $nick unless nr_chk_flag_user({ NICK => $nick, ID => $uid }, NRF_NEVEROP);
3333 }
3334 cs_updown2($user, 'UP', $chan2, @targets);
3335 }
3336
3337 notice($user, "Access list for \002$cn1\002 ".
3338 ($level ? "(rank: \002".$plevels[$level + $plzero]."\002) " : '').
3339 "copied to \002$cn2\002");
3340
3341 my $log_str = "copied the channel access list for \002$cn1\002 ".
3342 ($level ? "(rank: \002".$plevels[$level + $plzero]."\002) " : '').
3343 "to \002$cn2\002";
3344 services::ulog($chanserv::csnick, LOG_INFO(), "$log_str", $user, $chan1);
3345
3346 my $src = get_user_nick($user);
3347 ircd::notice(agent($chan1), '%'.$cn1, "\002$src\002 $log_str")
3348 if cr_chk_flag($chan1, CRF_VERBOSE);
3349 ircd::notice(agent($chan2), '%'.$cn2, "\002$src\002 $log_str")
3350 if cr_chk_flag($chan2, CRF_VERBOSE);
3351}
3352
3353sub cs_copy_chan_levels($$$) {
3354 my ($user, $chan1, $chan2) = @_;
3355 my $cn1 = $chan1->{CHAN};
3356 my $cn2 = $chan2->{CHAN};
3357
3358 copy_chan_levels($cn1, $cn2);
3359 notice($user, "LEVELS for \002$cn1\002 copied to \002$cn2\002");
3360
3361 my $log_str = "copied the LEVELS list for \002$cn1\002 to \002$cn2\002";
3362 services::ulog($chanserv::csnick, LOG_INFO(), "$log_str", $user, $chan1);
3363
3364 my $src = get_user_nick($user);
3365 ircd::notice(agent($chan1), '%'.$cn1, "\002$src\002 $log_str")
3366 if cr_chk_flag($chan1, CRF_VERBOSE);
3367 ircd::notice(agent($chan2), '%'.$cn2, "\002$src\002 $log_str")
3368 if cr_chk_flag($chan2, CRF_VERBOSE);
3369}
3370
3371sub cs_copy_chan_akick($$$) {
3372 my ($user, $chan1, $chan2) = @_;
3373 my $cn1 = $chan1->{CHAN};
3374 my $cn2 = $chan2->{CHAN};
3375
3376 copy_chan_akick($cn1, $cn2);
3377 notice($user, "Channel AKick list for \002$cn1\002 copied to \002$cn2\002");
3378
3379 my $log_str = "copied the AKick list for \002$cn1\002 to \002$cn2\002";
3380 services::ulog($chanserv::csnick, LOG_INFO(), "$log_str", $user, $chan1);
3381
3382 my $src = get_user_nick($user);
3383 ircd::notice(agent($chan1), '%'.$cn1, "\002$src\002 $log_str")
3384 if cr_chk_flag($chan1, CRF_VERBOSE);
3385 ircd::notice(agent($chan2), '%'.$cn2, "\002$src\002 $log_str")
3386 if cr_chk_flag($chan2, CRF_VERBOSE);
3387}
3388
3389sub cs_mlock($$$@) {
3390 my ($user, $chan, $cmd, @args) = @_;
3391 my $cn = $chan->{CHAN};
3392 # does this need its own privilege now?
674d6fee 3393 can_do($chan, 'SET', $user) or return;
7b261bb8 3394 my $modes;
8f7befb1 3395 if(scalar(@args)) {
7b261bb8 3396 my ($modes_in, @parms_in) = validate_chmodes(shift @args, @args);
3397 $modes = $modes_in.' '.join(' ', @parms_in);
3398 @args = undef;
3399 }
3400
3401 my $cur_modelock = get_modelock($chan);
3402 if(lc $cmd eq 'add') {
3403 $modes = modes::merge($cur_modelock, $modes, 1);
3404 $modes = sanitize_mlockable($modes);
3405 $set_modelock->execute($modes, $cn);
3406 }
3407 elsif(lc $cmd eq 'del') {
3408 $modes =~ s/[+-]//g;
3409 $modes = modes::add($cur_modelock, "-$modes", 1);
3410 $set_modelock->execute($modes, $cn);
3411 }
3412 elsif(lc $cmd eq 'set') {
3413 $modes = modes::merge($modes, "+r", 1);
3414 $set_modelock->execute($modes, $cn);
8f7befb1 3415 }
3416 elsif(lc $cmd eq 'reset') {
3417 $set_modelock->execute(services_conf_default_channel_mlock, $cn);
7b261bb8 3418 } else {
3419 notice($user, "Unknown MLOCK command \"$cmd\"");
392f023e 3420 return;
7b261bb8 3421 }
3422
3423 notice($user, "Mode lock for \002$cn\002 has been set to: \002$modes\002");
3424 do_modelock($chan);
3425
3426=cut
3427 notice($user, columnar {TITLE => "Ban list of \002$cn\002:", DOUBLE=>1,
3428 NOHIGHLIGHT => nr_chk_flag_user($user, NRF_NOHIGHLIGHT)}, @data);
3429=cut
3430}
3431
3432use SrSv::MySQL::Stub {
0885aa69 3433 getChanUsers => ['COLUMN', "SELECT user.nick FROM chanuser JOIN user ON (user.id=chanuser.nickid)
3434 WHERE chanuser.chan=? AND chanuser.joined=1"]
7b261bb8 3435};
3436
3437sub cs_resync($@) {
3438 my ($user, @cns) = @_;
3439 foreach my $cn (@cns) {
3440 my $chan = { CHAN => $cn };
3441 next unless cs_clear_ops($user, $chan, 'Resync');
3442 cs_updown2($user, 'up', $chan, getChanUsers($cn));
0885aa69 3443 if(can_do($chan, 'AKickEnforce', $user, { OVERRIDE_MSG => "AKICK $cn ENFORCE", NOREPLY => 1 })) {
3444 cs_akick_enforce($user, $chan);
3445 }
7b261bb8 3446 }
3447}
3448
30a8b057 3449sub cs_join($@) {
3450 my ($user, @cns) = @_;
3451 my @reply;
3452 my @out_cns;
3453 foreach my $cn (@cns) {
3454 if($cn =~ /,/) {
3455 push @cns, split(',', $cn);
3456 }
3457 elsif($cn eq '') {
3458 next;
3459 }
3460 my $chan = { CHAN => $cn };
3461 my $cando_opts = { NOREPLY => 1 };
64be8cb8 3462 if(check_akick($user, $chan, 1)) {
30a8b057 3463 push @reply, "You are banned from $cn";
3464 next;
64be8cb8 3465 } elsif(!can_do($chan, 'JOIN', $user, $cando_opts)) {
30a8b057 3466 push @reply, "$cn is a private channel.";
3467 next;
3468 }
faaf4d4c 3469 if(is_in_chan($user, $chan)) {
3470 next;
3471 }
99dfe700 3472 if(can_do($chan, 'InviteSelf', $user, $cando_opts)) {
30a8b057 3473 cs_invite($user, $chan, $user);
3474 }
3475 push @out_cns, $cn;
3476
3477 }
faaf4d4c 3478 ircd::svsjoin(get_user_agent($user), get_user_nick($user), @out_cns) if scalar @out_cns;
30a8b057 3479 notice($user, @reply) if scalar @reply;
3480}
3481
c5de8dcc 3482sub cs_topic($$@) {
3483 my ($user, $cn, @args) = @_;
3484 my ($chan, $msg) = ($cn->{CHAN}, join(" ", @args));
3485 can_do($cn, 'SETTOPIC', $user) or return undef;
3486 ircd::settopic(agent($cn), $chan, get_user_nick($user), time, ($msg =~ /^none/i ? "" : $msg));
44b46f5b 3487}
c5de8dcc 3488
7b261bb8 3489### MISCELLANEA ###
3490
3491# these are helpers and do NOT check if $cn1 or $cn2 is reg'd
3492sub copy_chan_acc($$;$) {
3493 my ($cn1, $cn2, $level) = @_;
3494 if($level) {
3495 $copy_acc_rank->execute($cn2, $cn1, $level);
3496 $copy_acc_rank->finish();
3497 } else {
3498 $get_founder->execute($cn2);
3499 my ($founder) = $get_founder->fetchrow_array;
3500 $get_founder->finish();
3501
3502 $copy_acc->execute($cn2, $cn1, $founder);
3503 $copy_acc->finish();
3504 }
3505}
3506
3507sub copy_chan_akick($$;$) {
3508 my ($cn1, $cn2) = @_;
3509 $copy_akick->execute($cn2, $cn1);
3510 $copy_akick->finish();
3511 copy_chan_acc($cn1, $cn2, -1);
3512}
3513
3514sub copy_chan_levels($$) {
3515 my ($cn1, $cn2) = @_;
3516 $copy_levels->execute($cn2, $cn1);
3517 $copy_levels->finish();
3518}
3519
3520sub copy_chan_chanreg($$) {
3521 my ($cn1, $cn2) = @_;
3522 $get_founder->execute($cn1);
3523 my ($founder) = $get_founder->fetchrow_array;
3524 $get_founder->finish();
3525 set_acc($founder, undef, { CHAN => $cn2 }, FOUNDER);
3526 $copy_chanreg->execute($cn2, $cn1);
3527 $copy_chanreg->finish();
3528}
3529
3530sub do_welcome($$) {
3531 my ($user, $chan) = @_;
3532 my $cn = $chan->{CHAN};
3533
3534 $get_welcomes->execute($cn);
3535 if($get_welcomes->rows) {
3536 my @welcomes;
3537 while(my ($msg) = $get_welcomes->fetchrow_array) {
3538 push @welcomes, (cr_chk_flag($chan, CRF_WELCOMEINCHAN) ? '' : "[$cn] " ).$msg;
3539 }
3540 if(cr_chk_flag($chan, CRF_WELCOMEINCHAN)) {
3541 ircd::privmsg(agent($chan), $cn, @welcomes);
3542 } else {
3543 notice($user, @welcomes);
3544 }
3545 }
3546 $get_welcomes->finish();
3547}
3548
3549sub do_greet($$) {
3550 my ($user, $chan) = @_;
3551 my $cn = $chan->{CHAN};
3552
674d6fee 3553 if(can_do($chan, 'GREET', $user)) {
7b261bb8 3554 my $src = get_user_nick($user);
3555 $nickserv::get_greet->execute(get_user_id($user));
3556 my ($greet) = $nickserv::get_greet->fetchrow_array();
3557 $nickserv::get_greet->finish();
3558 ircd::privmsg(agent($chan), $cn, "[\002$src\002] $greet") if $greet;
3559 }
3560}
3561
3562sub chk_registered($$) {
3563 my ($user, $chan) = @_;
3564
3565 unless(is_registered($chan)) {
3566 my $cn = $chan->{CHAN};
3567
3568 notice($user, "The channel \002$cn\002 is not registered.");
3569 return 0;
3570 }
3571
3572 return 1;
3573}
3574
3575sub make_banmask($$;$) {
3576 my ($chan, $tuser, $type) = @_;
3577 my $nick = get_user_nick($tuser);
3578
3579 my ($ident, $vhost) = get_vhost($tuser);
3580 no warnings 'misc';
3581 my ($nick, $ident, $vhost) = make_hostmask(get_bantype($chan), $nick, $ident, $vhost);
3582 if($type eq 'q') {
3583 $type = '~q:';
3584 } elsif($type eq 'n') {
3585 $type = '~n:';
3586 } else {
3587 $type = '';
3588 }
3589 return $type."$nick!$ident\@$vhost";
3590}
3591
db41490c 3592sub kickban($$$$;$$) {
44b46f5b 3593 my ($chan, $user, $mask, $reason, $noflush, $expiry) = @_;
7b261bb8 3594 my $cn = $chan->{CHAN};
44b46f5b 3595 my $nick;
3596 $nick = get_user_nick($user) if ($user);
3597 if (!$user && !$mask) {
3598 ircd::debug ("No user or mask given for /cs kickban!");
3599 return;
3600 }
3601 return 0 if $user && adminserv::is_service($user);
7b261bb8 3602
3603 my $agent = agent($chan);
3604
3605 unless($mask) {
3606 $mask = make_banmask($chan, $user);
3607 }
db41490c 3608
7b261bb8 3609 enforcer_join($chan) if (get_user_count($chan) <= 1);
3610 ircd::setmode($agent, $cn, '+b', $mask);
db41490c 3611 if (!defined($expiry)) {
3612 $get_bantime->execute($cn);
3613 ($expiry) = $get_bantime->fetchrow_array();
3614 $get_bantime->finish();
44b46f5b 3615 }
3616 if ($expiry) {
db41490c 3617 $add_tempban->execute($cn, $mask, $expiry);
44b46f5b 3618 }
93cbe111 3619 ircd::flushmodes() unless $noflush;
44b46f5b 3620 ircd::kick($agent, $cn, $nick, $reason) if ($nick);
7b261bb8 3621 return 1;
3622}
3623
3624sub kickban_multi($$$) {
3625 my ($chan, $users, $reason) = @_;
3626 my $cn = $chan->{CHAN};
3627 my $agent = agent($chan);
2b8d9e2d 3628
7b261bb8 3629 enforcer_join($chan);
3630 ircd::setmode($agent, $cn, '+b', '*!*@*');
3631 ircd::flushmodes();
3632
3633 foreach my $user (@$users) {
3634 next if adminserv::is_ircop($user) or adminserv::is_svsop($user, adminserv::S_HELP());
3635 ircd::kick($agent, $cn, get_user_nick($user), $reason);
3636 }
3637}
3638
3639sub clear_users($$) {
3640 my ($chan, $reason) = @_;
3641 my $cn = $chan->{CHAN};
3642 my $agent = agent($chan);
3643 my $i;
2b8d9e2d 3644
7b261bb8 3645 enforcer_join($chan);
3646 ircd::setmode($agent, $cn, '+b', '*!*@*');
3647 ircd::flushmodes();
3648 $get_chan_users->execute($cn);
3649 while(my ($nick, $uid) = $get_chan_users->fetchrow_array) {
3650 my $user = { NICK => $nick, ID => $uid };
3651 ircd::kick($agent, $cn, $nick, $reason)
3652 unless adminserv::is_ircop($user) or adminserv::is_svsop($user, adminserv::S_HELP());
3653 $i++;
3654 }
3655
3656 return $i;
3657}
3658
3659sub kickmask($$$$) {
3660 my ($chan, $mask, $reason, $ban) = @_;
3661 my $cn = $chan->{CHAN};
3662 my $agent = agent($chan);
3663
3664 my ($nick, $ident, $host) = glob2sql(parse_mask($mask));
3665 $nick = '%' if ($nick eq '');
3666 $ident = '%' if ($ident eq '');
3667 $host = '%' if ($host eq '');
2b8d9e2d 3668
7b261bb8 3669 if ($ban) {
3670 my $banmask = $nick.'!'.$ident.'@'.$host;
3671 $banmask =~ tr/%_/*?/;
3672 ircd::setmode($agent, $cn, '+b', $banmask);
3673 ircd::flushmodes();
3674 }
3675
3676 my $i;
3677 $get_chan_users_mask->execute($cn, $nick, $ident, $host, $host, $host);
3678 while(my ($nick, $uid) = $get_chan_users_mask->fetchrow_array) {
3679 my $user = { NICK => $nick, ID => $uid };
3680 ircd::kick($agent, $cn, $nick, $reason)
3681 unless adminserv::is_service($user);
3682 $i++;
3683 }
3684 $get_chan_users_mask->finish();
3685
3686 return $i;
3687}
3688
3689sub kickmask_noacc($$$$) {
3690 my ($chan, $mask, $reason, $ban) = @_;
3691 my $cn = $chan->{CHAN};
3692 my $agent = agent($chan);
3693
3694 my ($nick, $ident, $host) = glob2sql(parse_mask($mask));
3695 $nick = '%' if ($nick eq '');
3696 $ident = '%' if ($ident eq '');
3697 $host = '%' if ($host eq '');
2b8d9e2d 3698
7b261bb8 3699 if ($ban) {
3700 my $banmask = $nick.'!'.$ident.'@'.$host;
3701 $banmask =~ tr/%_/*?/;
3702 ircd::setmode($agent, $cn, '+b', $banmask);
3703 ircd::flushmodes();
3704 }
3705
3706 my $i;
3707 $get_chan_users_mask_noacc->execute($cn, $nick, $ident, $host, $host, $host);
3708 while(my ($nick, $uid) = $get_chan_users_mask_noacc->fetchrow_array) {
3709 my $user = { NICK => $nick, ID => $uid };
3710 ircd::kick($agent, $cn, $nick, $reason)
3711 unless adminserv::is_service($user);
3712 $i++;
3713 }
3714 $get_chan_users_mask_noacc->finish();
3715
3716 return $i;
3717}
3718
3719sub clear_ops($) {
3720 my ($chan) = @_;
3721 my $cn = $chan->{CHAN};
3722 my @modelist;
3723 my $agent = agent($chan);
3724
3725 $get_chan_users->execute($cn);
3726 while(my ($nick, $uid) = $get_chan_users->fetchrow_array) {
3727 my $user = { NICK => $nick, ID => $uid };
3728 my $opmodes = get_op($user, $chan);
3729 for(my $i; $i < 5; $i++) {
3730 if($opmodes & 2**$i) {
3731 push @modelist, ['-'.$opmodes[$i], $nick];
3732 }
3733 }
3734 }
3735
3736 ircd::setmode2($agent, $cn, @modelist);
3737}
3738
3739sub clear_bans($;$) {
3740 my ($chan, $type) = @_;
3741 my $cn = $chan->{CHAN};
3742 my @args = ();
3743 my $agent = agent($chan);
3744 $type = 0 unless defined $type;
3745 my $mode = ($type == 128 ? 'e' : 'b');
2b8d9e2d 3746
7b261bb8 3747 my @banlist = ();
3748 $get_all_bans->execute($cn, $type);
3749 while(my ($mask) = $get_all_bans->fetchrow_array) {
3750 $mask =~ tr/\%\_/\*\?/;
3751 push @banlist, $mask;
3752 }
3753
3754 ircd::ban_list($agent, $cn, -1, $mode, @banlist);
3755 ircd::flushmodes();
3756}
3757
3758sub unban_user($@) {
3759 my ($chan, @userlist) = @_;
3760 my $cn = $chan->{CHAN};
3761 my $count;
3762 if (defined(&ircd::unban_nick)) {
3763 my @nicklist;
3764 foreach my $tuser (@userlist) {
3765 push @nicklist, get_user_nick($tuser);
3766 }
3767 ircd::unban_nick(agent($chan), $cn, @nicklist);
3768 return scalar(@nicklist);
3769 }
2b8d9e2d 3770
7b261bb8 3771 foreach my $tuser (@userlist) {
3772 my $tuid;
3773 unless($tuid = get_user_id($tuser)) {
3774 next;
3775 }
3776
3777 my (@bans);
3778 # We don't handle extended bans. Yet.
3779 $find_bans_chan_user->execute($cn, $tuid, 0);
3780 while (my ($mask) = $find_bans_chan_user->fetchrow_array) {
3781 $mask =~ tr/\%\_/\*\?/;
3782 push @bans, $mask;
3783 }
3784 $find_bans_chan_user->finish();
3785
3786 ircd::ban_list(agent($chan), $cn, -1, 'b', @bans) if scalar(@bans);
3787 $delete_bans_chan_user->execute($cn, $tuid, 0); $delete_bans_chan_user->finish();
3788 $count++;
3789 }
3790 return $count;
3791}
3792
3793sub chan_kill($$;$) {
3794 my ($chan, $reason, $tusers) = @_;
3795 my $cn = $chan->{CHAN};
3796 my $agent = agent($chan);
3797 my $i;
3798
3799 enforcer_join($chan);
3800 if ($tusers) {
3801 foreach my $tuser (@$tusers) {
3802 $tuser->{ID} = $tuser->{__ID} if defined($tuser->{__ID}); # user_join_multi does this.
3803 nickserv::kline_user($tuser, services_conf_chankilltime, $reason)
3804 unless adminserv::is_ircop($tuser) or adminserv::is_svsop($tuser, adminserv::S_HELP());
3805 $i++;
3806 }
3807 }
3808 else {
3809 $get_chan_users->execute($cn);
3810 while(my ($nick, $uid) = $get_chan_users->fetchrow_array) {
3811 my $tuser = { NICK => $nick, ID => $uid, AGENT => $agent };
3812 nickserv::kline_user($tuser, services_conf_chankilltime, $reason)
3813 unless adminserv::is_ircop($tuser) or adminserv::is_svsop($tuser, adminserv::S_HELP());
3814 $i++;
3815 }
3816 }
3817
3818 return $i;
3819}
3820
3821sub do_nick_akick($$;$) {
3822 my ($tuser, $chan, $root) = @_;
3823 my $cn = $chan->{CHAN};
3824 unless(defined($root)) {
3825 (undef, $root) = get_best_acc($tuser, $chan, 2);
3826 }
3827
3828 $get_nick_akick->execute($cn, $root);
3829 my ($reason) = $get_nick_akick->fetchrow_array(); $get_nick_akick->finish();
3830
3831 return 0 if adminserv::is_svsop($tuser, adminserv::S_HELP());
4eba6964 3832 if(defined($reason) && $reason =~ /\|/) {
3833 ($reason, undef) = split(/ ?\| ?/, $reason, 2);
3834 }
7b261bb8 3835 kickban($chan, $tuser, undef, "User has been banned from ".$cn.($reason?": $reason":''));
3836}
3837
fbd2f314 3838sub check_akick($$;$) {
3839 my ($user, $chan, $check_only) = @_;
3840
64be8cb8 3841 if(adminserv::is_svsop($user, adminserv::S_HELP())) {
3842 return 0;
3843 }
fbd2f314 3844 my ($acc, $root) = get_best_acc($user, $chan, 2);
3845 if ($acc == -1) {
3846 do_nick_akick($user, $chan, $root) unless $check_only;
3847 return 1;
3848 }
3849 my $cn = $chan->{CHAN};
3850 my $uid = get_user_id($user);
64be8cb8 3851 unless($acc) {
fbd2f314 3852 $get_akick->execute($uid, $cn);
3853 if(my @akick = $get_akick->fetchrow_array) {
3854 akickban($cn, @akick) unless $check_only;
3855 return 1;
3856 }
3857 }
64be8cb8 3858 return 0;
fbd2f314 3859}
3860
3861sub do_status($$;$) {
3862 my ($user, $chan, $check_only) = @_;
7b261bb8 3863
3864 return 0 if cr_chk_flag($chan, (CRF_CLOSE | CRF_DRONE));
2b8d9e2d 3865
7b261bb8 3866 my $nick = get_user_nick($user);
7b261bb8 3867
2ffea65e 3868 if(check_akick($user, $chan, $check_only)) {
3869 return 0;
3870 }
7b261bb8 3871 my ($acc, $root) = get_best_acc($user, $chan, 2);
0b380dbc 3872 my ($accnick, $override) = can_do($chan, 'JOIN', $user, { ACC => $acc, NOREPLY => 1 });
3873 unless ($acc > 0 || $override) {
3874 if (clones_exist ($user, $chan)) {
44b46f5b 3875 tempban($chan, $user, undef, "+60s No clones allowed in this channel.");
0b380dbc 3876 }
3877 }
3878
3879 if(!$accnick && !$override) {
fbd2f314 3880 kickban($chan, $user, undef, 'This is a private channel.')
3881 unless $check_only;
7b261bb8 3882 return 0;
3883 }
2b8d9e2d 3884
4d8457e0 3885 if( !$check_only && is_registered($chan) &&
3886 !cr_chk_flag($chan, (CRF_CLOSE | CRF_DRONE)) )
fbd2f314 3887 {
3b477116 3888 my $neverop = (is_neverop_user($user) || cr_chk_flag($chan, CRF_NEVEROP, 1));
3889 my $no_deop = cr_chk_flag($chan, CRF_SPLITOPS, 0);
3890 my $op_anyway = 0;
3891 if($neverop && cr_chk_flag($chan, CRF_AUTOVOICE, 1) && $acc > 2) {
3892 $acc = 2;
3893 $no_deop = 0;
3894 $op_anyway = 1;
3895 }
4d8457e0 3896 set_modes($user, $chan, $acc,
dcba1aa2 3897 # $acc == 3 is +h
6fedd55a 3898 # this probably needs to be configurable for ports
3899 # also Unreal may [optionally] set +q on join.
3b477116 3900 $no_deop,
3901 !$neverop || $op_anyway,
4d8457e0 3902 );
7b261bb8 3903 }
2b8d9e2d 3904
7b261bb8 3905 return 1;
3906}
3907
3908sub akick_alluser($) {
3909 my ($user) = @_;
3910 my $uid = get_user_id($user);
3911
3912 $get_akick_alluser->execute($uid);
3913 while(my @akick = $get_akick_alluser->fetchrow_array) {
3914 akickban(@akick);
3915 }
3916}
3917
3918sub akick_allchan($) {
3919 my ($chan) = @_;
3920 my $cn = $chan->{CHAN};
3921
3922 $get_akick_allchan->execute($cn);
3923 while(my @akick = $get_akick_allchan->fetchrow_array) {
3924 akickban($cn, @akick);
3925 }
3926}
3927
3928sub akickban(@) {
3929 my ($cn, $knick, $bnick, $ident, $host, $reason, $bident) = @_;
3930
3931 my $target = { NICK => $knick };
3932 my $chan = { CHAN => $cn };
3933 return 0 if adminserv::is_svsop($target, adminserv::S_HELP());
3934
3935 if($bident) {
3936 ($bnick, $ident, $host) = make_hostmask(get_bantype($chan), $knick, $bident, $host);
3937 } elsif($host =~ /^(\d{1,3}\.){3}\d{1,3}$/) {
3938 ($bnick, $ident, $host) = make_hostmask(4, $knick, $bident, $host);
3939 } else {
3940 $bnick =~ tr/\%\_/\*\?/;
3941 $ident =~ tr/\%\_/\*\?/;
3942 $host =~ tr/\%\_/\*\?/;
3943 }
3944
4eba6964 3945 if(defined($reason) && $reason =~ /\|/) {
3946 ($reason, undef) = split(/ ?\| ?/, $reason, 2);
3947 }
3948
7b261bb8 3949 return kickban($chan, $target, "$bnick!$ident\@$host", "User has been banned from ".$cn.($reason?": $reason":''));
3950}
3951
3952sub notice_all_nicks($$$) {
3953 my ($user, $nick, $msg) = @_;
3954 my $src = get_user_nick($user);
3955
3956 notice($user, $msg);
3957 foreach my $u (get_nick_user_nicks $nick) {
3958 notice({ NICK => $u, AGENT => $csnick }, $msg) unless lc $src eq lc $u;
3959 }
3960}
3961
3962sub xop_byname($) {
3963 my ($name) = @_;
3964 my $level;
3965
3966 if($name =~ /^uop$/i) { $level=1; }
3967 elsif($name =~ /^vop$/i) { $level=2; }
3968 elsif($name =~ /^hop$/i) { $level=3; }
3969 elsif($name =~ /^aop$/i) { $level=4; }
3970 elsif($name =~ /^sop$/i) { $level=5; }
3971 elsif($name =~ /^co?f(ounder)?$/i) { $level=6; }
3972 elsif($name =~ /^founder$/i) { $level=7; }
3973 elsif($name =~ /^(any|all|user)/i) { $level=0; }
3974 elsif($name =~ /^akick$/i) { $level=-1; }
3975 elsif($name =~ /^(none|disabled?|nobody)$/i) { $level=8; }
3976
3977 return $level;
3978}
3979
3980sub expire {
3981 return if services_conf_noexpire;
3982
3983 $get_expired->execute(time() - (86400 * services_conf_chanexpire));
3984 while(my ($cn, $founder) = $get_expired->fetchrow_array) {
3985 drop({ CHAN => $cn });
3986 wlog($csnick, LOG_INFO(), "\002$cn\002 has expired. Founder: $founder");
3987 }
3988}
3989
3990sub enforcer_join($) {
3991 my ($chan) = @_;
3992 my $cn = $chan->{CHAN};
3993 my $bot = agent($chan);
3994
3995 return if $enforcers{lc $cn};
3996 $enforcers{lc $cn} = lc $bot;
3997
3998 botserv::bot_join($chan);
3999
4000 add_timer("CSEnforce $bot $cn", 60, __PACKAGE__, 'chanserv::enforcer_part');
4001}
4002
4003sub enforcer_part($) {
4004 my ($cookie) = @_;
4005 my ($junk, $bot, $cn) = split(/ /, $cookie);
4006
4007 return unless $enforcers{lc $cn};
4008 undef($enforcers{lc $cn});
4009
4010 botserv::bot_part_if_needed($bot, {CHAN => $cn}, 'Enforcer Leaving');
4011}
4012
4013sub fix_private_join_before_id($) {
4014 my ($user) = @_;
4015
6173575a 4016 my @cns = get_recent_private_chans(get_user_id($user));
4017 foreach my $cn (@cns) {
7b261bb8 4018 my $chan = { CHAN => $cn };
4019 unban_user($chan, $user);
7b261bb8 4020 }
4021
4022 ircd::svsjoin($csnick, get_user_nick($user), @cns) if @cns;
4023}
4024
4025### DATABASE UTILITY FUNCTIONS ###
4026
4027sub get_user_count($) {
4028 my ($chan) = @_;
4029 my $cn = $chan->{CHAN};
2b8d9e2d 4030
7b261bb8 4031 $get_user_count->execute($cn);
2b8d9e2d 4032
7b261bb8 4033 return $get_user_count->fetchrow_array;
4034}
4035
4036sub get_lock($) {
4037 my ($chan) = @_;
4038
4039 $chan = lc $chan;
4040
4041 $chanuser_table++;
4042
4043 if($cur_lock) {
4044 if($cur_lock ne $chan) {
4045 really_release_lock($chan);
4046 $chanuser_table--;
4047 die("Tried to get two locks at the same time: $cur_lock, $chan")
4048 }
4049 $cnt_lock++;
4050 } else {
4051 $cur_lock = $chan;
4052 $get_lock->execute(sql_conf_mysql_db.".chan.$chan");
4053 $get_lock->finish;
4054 }
4055}
4056
4057sub release_lock($) {
4058 my ($chan) = @_;
4059
4060 $chan = lc $chan;
4061
4062 $chanuser_table--;
4063
4064 if($cur_lock and $cur_lock ne $chan) {
4065 really_release_lock($cur_lock);
4066
4067 die("Tried to release the wrong lock");
4068 }
4069
4070 if($cnt_lock) {
4071 $cnt_lock--;
4072 } else {
4073 really_release_lock($chan);
4074 }
4075}
4076
4077sub really_release_lock($) {
4078 my ($chan) = @_;
4079
4080 $cnt_lock = 0;
4081 $release_lock->execute(sql_conf_mysql_db.".chan.$chan");
4082 $release_lock->finish;
4083 undef $cur_lock;
4084}
4085
4086#sub is_free_lock($) {
4087# $is_free_lock->execute($_[0]);
4088# return $is_free_lock->fetchrow_array;
4089#}
4090
4091sub get_modelock($) {
4092 my ($chan) = @_;
4093 my $cn;
4094 if(ref($chan)) {
4095 $cn = $chan->{CHAN}
4096 } else {
4097 $cn = $chan;
4098 }
2b8d9e2d 4099
7b261bb8 4100 $get_modelock->execute($cn);
4101 my ($ml) = $get_modelock->fetchrow_array;
4102 $get_modelock->finish();
4103 return $ml;
4104}
4105
4106sub do_modelock($;$) {
4107 my ($chan, $modes) = @_;
4108 my $cn = $chan->{CHAN};
4109
4110 my $seq = $ircline;
4111
4112 $get_modelock_lock->execute; $get_modelock_lock->finish;
2b8d9e2d 4113
7b261bb8 4114 $get_chanmodes->execute($cn);
4115 my ($omodes) = $get_chanmodes->fetchrow_array;
4116 my $ml = get_modelock($chan);
4117
4118 $ml = do_modelock_fast($cn, $modes, $omodes, $ml);
4119
4120 $unlock_tables->execute; $unlock_tables->finish;
4121
4122 ircd::setmode(agent($chan), $cn, $ml) if($ml);
4123}
4124
4125sub do_modelock_fast($$$$) {
4126 my ($cn, $modes, $omodes, $ml) = @_;
4127 my $nmodes = modes::add($omodes, $modes, 1);
4128 $ml = modes::diff($nmodes, $ml, 1);
4129 $set_chanmodes->execute(modes::add($nmodes, $ml, 1), $cn);
2b8d9e2d 4130
7b261bb8 4131 return $ml;
4132}
4133
4134sub update_modes($$) {
4135 my ($cn, $modes) = @_;
4136
4137 $get_update_modes_lock->execute; $get_update_modes_lock->finish;
4138 $get_chanmodes->execute($cn);
4139 my ($omodes) = $get_chanmodes->fetchrow_array;
4140
4141 $set_chanmodes->execute(modes::add($omodes, $modes, 1), $cn);
4142 $unlock_tables->execute; $unlock_tables->finish;
4143}
4144
4145sub is_level($) {
4146 my ($perm) = @_;
4147
4148 $is_level->execute($perm);
2b8d9e2d 4149
7b261bb8 4150 return $is_level->fetchrow_array;
4151}
4152
4153sub is_neverop($) {
4154 return nr_chk_flag($_[0], NRF_NEVEROP(), 1);
4155}
4156
4157sub is_neverop_user($) {
4158 return nr_chk_flag_user($_[0], NRF_NEVEROP(), 1);
4159}
4160
4161sub is_in_chan($$) {
4162 my ($user, $chan) = @_;
4163 my $cn = $chan->{CHAN};
4164 my $uid = get_user_id($user);
4165
4166 $is_in_chan->execute($uid, $cn);
4167 if($is_in_chan->fetchrow_array) {
4168 return 1;
4169 }
4170
4171 return 0;
4172}
4173
4174sub is_registered($) {
4175 my ($chan) = @_;
4176 my $cn = $chan->{CHAN};
2b8d9e2d 4177
7b261bb8 4178 $is_registered->execute($cn);
4179 if($is_registered->fetchrow_array) {
4180 return 1;
4181 } else {
4182 return 0;
4183 }
4184}
4185
4186sub get_user_chans($) {
4187 my ($user) = @_;
4188 my $uid = get_user_id($user);
4189 my @chans;
2b8d9e2d 4190
7b261bb8 4191 $get_user_chans->execute($uid, $ircline, $ircline+1000);
4192 while(my ($chan) = $get_user_chans->fetchrow_array) {
4193 push @chans, $chan;
4194 }
4195
4196 return (@chans);
4197}
4198
4199sub get_user_chans_recent($) {
4200 my ($user) = @_;
4201 my $uid = get_user_id($user);
4202 my (@curchans, @oldchans);
2b8d9e2d 4203
7b261bb8 4204 $get_user_chans_recent->execute($uid);
4205 while(my ($cn, $joined, $op) = $get_user_chans_recent->fetchrow_array) {
4206 if ($joined) {
4207 push @curchans, make_op_prefix($op).$cn;
4208 }
4209 else {
4210 push @oldchans, $cn;
4211 }
4212 }
4213
4214 return (\@curchans, \@oldchans);
4215}
4216
4217my ($prefixes, $modes);
4218sub make_op_prefix($) {
4219 my ($op) = @_;
4220 return unless $op;
4221
4222 unless(defined($prefixes) and defined($modes)) {
4223 $IRCd_capabilities{PREFIX} =~ /^\((\S+)\)(\S+)$/;
4224 ($modes, $prefixes) = ($1, $2);
4225 $modes = reverse $modes;
4226 $prefixes = reverse $prefixes;
4227 }
4228
4229 my $op_prefix = '';
4230 for(my $i = 0; $i < length($prefixes); $i++) {
4231 $op_prefix = substr($prefixes, $i, 1).$op_prefix if ($op & (2**$i));
4232 }
4233 return $op_prefix;
4234}
4235
4236sub get_op($$) {
4237 my ($user, $chan) = @_;
4238 my $cn = $chan->{CHAN};
4239 my $uid = get_user_id($user);
4240
4241 $get_op->execute($uid, $cn);
4242 my ($op) = $get_op->fetchrow_array;
4243
4244 return $op;
4245}
4246
4247sub get_best_acc($$;$) {
4248 my ($user, $chan, $retnick) = @_;
4249 my $uid = get_user_id($user);
4250 my $cn = $chan->{CHAN};
4251
4252 $get_best_acc->execute($uid, $cn);
4253 my ($bnick, $best) = $get_best_acc->fetchrow_array;
39eca0a2 4254 $get_best_acc->finish();
7b261bb8 4255
4256 if($retnick == 2) {
4257 return ($best, $bnick);
4258 } elsif($retnick == 1) {
4259 return $bnick;
4260 } else {
4261 return $best;
4262 }
4263}
4264
4265sub get_acc($$) {
4266 my ($nick, $chan) = @_;
4267 my $cn = $chan->{CHAN};
4268
4269 return undef
4270 if cr_chk_flag($chan, (CRF_DRONE | CRF_CLOSE | CRF_FREEZE), 1);
4271
4272 $get_acc->execute($cn, $nick);
4273 my ($acc) = $get_acc->fetchrow_array;
4274
4275 return $acc;
4276}
4277
4278sub set_acc($$$$) {
4279 my ($nick, $user, $chan, $level) = @_;
4280 my $cn = $chan->{CHAN};
114e3f65 4281 my $adder;
4282 $adder = get_best_acc($user, $chan, 1) if $user;
7b261bb8 4283
4284 $set_acc1->execute($cn, $level, $nick);
4285 $set_acc2->execute($level, $adder, $cn, $nick);
4286
4287 if ( ( $level > 0 and !is_neverop($nick) and !cr_chk_flag($chan, CRF_NEVEROP) )
4288 or $level < 0)
4289 {
4290 set_modes_allnick($nick, $chan, $level);
4291 }
4292}
4293
4294sub del_acc($$) {
4295 my ($nick, $chan) = @_;
4296 my $cn = $chan->{CHAN};
4297
4298 $del_acc->execute($cn, $nick);
4299
4300 foreach my $user (get_nick_users $nick) {
4301 set_modes($user, $chan, 0, 1) if is_in_chan($user, $chan);
4302 }
4303}
4304
4305sub get_auth_nick($$) {
4306 my ($cn, $nick) = @_;
4307
4308 $get_auth_nick->execute($cn, $nick);
4309 my ($data) = $get_auth_nick->fetchrow_array();
4310 $get_auth_nick->finish();
4311
4312 return split(/:/, $data);
4313}
4314sub get_auth_num($$) {
4315 my ($cn, $num) = @_;
4316
4317 $get_auth_num->execute($cn, $num - 1);
4318 my ($nick, $data) = $get_auth_num->fetchrow_array();
4319 $get_auth_num->finish();
4320
4321 return ($nick, split(/:/, $data));
4322}
4323sub find_auth($$) {
4324 my ($cn, $nick) = @_;
4325
4326 $find_auth->execute($cn, $nick);
4327 my ($ret) = $find_auth->fetchrow_array();
4328 $find_auth->finish();
4329
4330 return $ret;
4331}
4332
4333# Only call this if you've checked the user for NEVEROP already.
4334sub set_modes_allchan($;$) {
4335 my ($user, $neverop) = @_;
4336 my $uid = get_user_id($user);
4337
4338 $get_user_chans->execute($uid, $ircline, $ircline+1000);
4339 while(my ($cn) = $get_user_chans->fetchrow_array) {
4340 my $chan = { CHAN => $cn };
4341 my $acc = get_best_acc($user, $chan);
4342 if($acc > 0) {
4343 set_modes($user, $chan, $acc) unless ($neverop or cr_chk_flag($chan, CRF_NEVEROP));
4344 } elsif($acc < 0) {
4345 do_nick_akick($user, $chan);
4346 }
4347 }
4348}
4349
4350# Only call this if you've checked for NEVEROP already.
4351sub set_modes_allnick($$$) {
4352 my ($nick, $chan, $level) = @_;
4353 my $cn = $chan->{CHAN};
2b8d9e2d 4354
7b261bb8 4355 $get_using_nick_chans->execute($nick, $cn);
4356 while(my ($n) = $get_using_nick_chans->fetchrow_array) {
4357 my $user = { NICK => $n };
4358 my $l = get_best_acc($user, $chan);
4359 if($l > 0) {
4360 set_modes($user, $chan, $level, 1) if($level == $l);
4361 } elsif($l < 0) {
4362 do_nick_akick($user, $chan);
4363 }
4364 }
4365}
4366
4367# If channel has OPGUARD, $doneg is true.
4d8457e0 4368sub set_modes($$$;$$) {
4369 my ($user, $chan, $acc, $doneg, $dopos) = @_;
4370 # can you say eww?
4371 $dopos = 1 unless defined($dopos);
4372 $doneg = 0 unless defined($doneg);
7b261bb8 4373 my $cn = $chan->{CHAN};
4374
2b8d9e2d 4375
7b261bb8 4376 if ($acc < 0) {
4377 # Do akick stuff here.
4378 }
2b8d9e2d 4379
0885aa69 4380 my $dst = ( $acc > 0 ? $ops[$acc] : 0 );
7b261bb8 4381 my $cur = get_op($user, $chan);
4382 my ($pos, $neg);
2b8d9e2d 4383
7b261bb8 4384 if (cr_chk_flag($chan, CRF_FREEZE)) {
4385 set_mode_mask($user, $chan, $cur, undef);
4386 return;
4387 }
4388 if (($acc == 0) and cr_chk_flag($chan, CRF_AUTOVOICE)) {
4389 set_mode_mask($user, $chan, $cur, 1);
4390 return;
4391 }
4392
4393 $pos = $dst ^ ($dst & $cur);
4394 $neg = ($dst ^ $cur) & $cur if $doneg;
4395
4396 if($pos or $neg) {
4d8457e0 4397 set_mode_mask($user, $chan, ($doneg ? $neg : '-'), ($dopos ? $pos : '+'));
7b261bb8 4398 }
4399
4400 if($pos) {
9fe48026 4401 set_lastop($cn);
4402 set_lastused($cn, get_user_id($user));
7b261bb8 4403 }
4404}
4405
4406sub unset_modes($$) {
4407 my ($user, $chan) = @_;
4408
4409 my $mask = get_op($user, $chan);
4410
4411 set_mode_mask($user, $chan, $mask, 0);
4412}
4413
4414sub set_mode_mask($$$$) {
4415 my ($user, $chan, @masks) = @_;
4416 my $nick = get_user_nick($user);
4417 my $cn = $chan->{CHAN};
4418 my (@args, $out);
4419
4420 for(my $sign; $sign < 2; $sign++) {
4421 next if($masks[$sign] == 0);
4422
4423 $out .= '-' if $sign == 0;
4424 $out .= '+' if $sign == 1;
4425
4426 for(my $i; $i < 5; $i++) {
4427 my @l = ('v', 'h', 'o', 'a', 'q');
4428
4429 if($masks[$sign] & 2**$i) {
4430 $out .= $l[$i];
4431 push @args, $nick;
4432 }
4433 }
4434 }
4435
4436 if(@args) {
4437 ircd::setmode(agent($chan), $cn, $out, join(' ', @args));
4438 }
4439}
4440
4441sub get_level($$) {
4442 my ($chan, $perm) = @_;
4443 my $cn = $chan->{CHAN};
4444
4445 $get_level->execute($cn, $perm);
4446 my ($level, $isnotnull) = $get_level->fetchrow_array;
4447 $get_level->finish();
4448
4449 if (wantarray()) {
4450 return ($level, $isnotnull);
4451 }
4452 else {
4453 return $level;
4454 }
4455}
4456
88998b02 4457sub check_override($$;$) {
4458 my ($user, $perm, $logMsg) = @_;
4459 $perm = uc $perm;
7b261bb8 4460
88998b02 4461 #{OVERRIDE::$perm} produces funny package problems, so wrap it in double-quotes.
4462 if(exists($user->{"OVERRIDE::$perm"}) && (my $nick = $user->{"OVERRIDE::$perm"})) {
4463 if(defined($nick)) {
4464 if(services_conf_log_overrides && $logMsg) {
4465 my $src = get_user_nick($user);
4466 wlog($csnick, LOG_INFO(), "\002$src\002 used override $logMsg");
4467 }
4468 return (wantarray ? ($nick, 1) : $nick);
4469 } else {
4470 return;
4471 }
4472 }
7b261bb8 4473 foreach my $o (@override) {
88998b02 4474 my ($operRank, $permHashRef) = @$o;
4475 if($permHashRef->{$perm} and my $nick = adminserv::can_do($user, $operRank)) {
4476 $user->{"OVERRIDE::$perm"} = $nick;
4477 if(services_conf_log_overrides && $logMsg) {
4478 my $src = get_user_nick($user);
4479 wlog($csnick, LOG_INFO(), "\002$src\002 used override $logMsg");
4480 }
7b261bb8 4481 return (wantarray ? ($nick, 1) : $nick);
4482 }
4483 }
88998b02 4484 $user->{"OVERRIDE::$perm"} = undef;
7b261bb8 4485}
4486
674d6fee 4487sub can_do($$$;$) {
4488 my ($chan, $perm, $user, $data) = @_;
4489 $data = {} unless defined $data;
4490 # $data is a hashref/struct
4491 my $noreply = $data->{NOREPLY};
4492 my $acc = $data->{ACC};
4493 my $overrideMsg = $data->{OVERRIDE_MSG};
4494
4495 if(my $nick = __can_do($chan, $perm, $user, $acc)) {
88998b02 4496 # This is becoming increasingly complicated
4497 # and checking if an override was used is becoming tricky.
4498 # We had a case in cs_kick where an oper should be able to override +Q/$peace
4499 # but cannot b/c they have regular access in that channel.
73d9b500 4500 my $override;
4501 if(defined($user)) {
4502 (undef, $override) = check_override($user, $perm);
4503 }
88998b02 4504 return (wantarray ? ($nick, $override) : $nick);
4505 } elsif ( $user and adminserv::is_svsop($user, adminserv::S_HELP()) ) {
674d6fee 4506 #set_lastused($cn, get_user_id($user));
4507 my ($nick, $override) = check_override($user, $perm, $overrideMsg);
88998b02 4508 return (wantarray ? ($nick, $override) : $nick) if $override;
4509 }
674d6fee 4510 if($user and !$noreply) {
4511 my $cn = $chan->{CHAN};
4512 if (cr_chk_flag($chan, (CRF_CLOSE | CRF_DRONE))) {
4513 notice($user, "\002$cn\002 is closed and cannot be used".
4514 ((uc $perm eq 'INFO') ? ': '.get_close($chan) : '.'));
4515 }
4516 elsif(cr_chk_flag($chan, CRF_FREEZE)) {
4517 notice($user, "\002$cn\002 is frozen and access suspended.");
4518 }
4519 else {
4520 notice($user, "$cn: $err_deny");
4521 }
4522 }
88998b02 4523 return 0;
4524}
4525
674d6fee 4526sub __can_do($$$;$) {
4527 my ($chan, $perm, $user, $acc) = @_;
7b261bb8 4528 my $nick;
4529 my $cn = $chan->{CHAN};
4530 $perm = uc $perm;
7b261bb8 4531
4532 my $level;
4533 unless(exists($chan->{"PERM::$perm"})) {
4534 $level = $chan->{"PERM::$perm"} = get_level($chan, $perm);
4535 } else {
4536 $level = $chan->{"PERM::$perm"};
4537 }
4538
4539 unless(defined($acc)) {
674d6fee 4540 unless (defined $user && ref($user) eq 'HASH') {
4541 die "invalid __can_do call";
4542 }
4543 my $chanuser = $user->{lc $cn};
4544 unless (defined($chanuser) && exists($chanuser->{ACC})) {
7b261bb8 4545 ($acc, $nick) = get_best_acc($user, $chan, 2);
674d6fee 4546 ($chanuser->{ACC}, $chanuser->{ACCNICK}) = ($acc, $nick);
7b261bb8 4547 } else {
674d6fee 4548 ($acc, $nick) = ($chanuser->{ACC}, $chanuser->{ACCNICK});
7b261bb8 4549 }
4550 }
4551 $nick = 1 unless $nick;
4552
4553 if($acc >= $level and !cr_chk_flag($chan, (CRF_CLOSE | CRF_FREEZE | CRF_DRONE))) {
674d6fee 4554 set_lastused($cn, get_user_id($user)) if $user;
4555 return (wantarray ? ($nick, 0) : $nick);
7b261bb8 4556 }
4557
4558 if(cr_chk_flag($chan, CRF_FREEZE) and ($perm eq 'JOIN')) {
674d6fee 4559 return (wantarray ? ($nick, 0) : $nick);
7b261bb8 4560 }
4561
4562 return 0;
4563}
4564
4565sub can_keep_op($$$$) {
4566