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