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