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