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