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