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