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