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