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