]> jfr.im git - irc/SurrealServices/srsv.git/blame - branches/0.5.0/modules/serviceslibs/chanserv.pm
Fixed some more stuff.. No more getuuid for normal users!
[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
143our $csnick_default = 'ChanServ';
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 });
2270 return unless $nick;
2271
2272 my $src = get_user_nick($user);
2273 my $cn = $chan->{CHAN};
2274
2275 $reason = "Requested by $src".($reason?": $reason":'');
2276
2277 my @errors = (
2278 ["I'm sorry, $src, I'm afraid I can't do that."],
2279 ["They are not in \002$cn\002."],
2280 [$err_deny],
2281 ["User not found"],
2282 );
2283 my @notinchan = ();
2284 my $peace = ({modes::splitmodes(get_modelock($chan))}->{Q}->[0] eq '+');
2285
2286 my @targets = split(/\,/, $target);
2287 foreach $target (@targets) {
7b3a5814 2288 my $tuser = { NICK => $target };
2289 get_user_id ($tuser);
aecfa1fd 2290 my $targetlevel = get_best_acc($tuser, $chan);
2291
2292 if(lc $target eq lc agent($chan) or adminserv::is_service($tuser)) {
2293 push @{$errors[0]}, $target;
2294 next;
2295 }
2296
2297 if(get_user_id($tuser)) {
2298 unless(is_in_chan($tuser, $chan)) {
2299 if ($ban) {
2300 push @notinchan, $tuser;
2301 } else {
2302 push @{$errors[1]}, $target;
2303 }
2304 next;
2305 }
2306 } else {
2307 push @{$errors[3]}, $target;
2308 next;
2309 }
2310
2311 if( ( ($peace and $targetlevel > 0) or ($srclevel <= $targetlevel) )
2312 and not ($override && check_override($user, ($ban ? 'BAN' : 'KICK'), "$cmd $cn $target")) )
2313 {
2314 push @{$errors[2]}, $target;
2315 next;
2316 }
2317
2318 if($ban) {
2319 kickban($chan, $tuser, undef, $reason, 1);
2320 } else {
5e682044 2321 ircd::kick(agent($chan), $cn, $tuser, $reason) unless adminserv::is_service($user);
aecfa1fd 2322 }
2323 }
2324 ircd::flushmodes() if($ban);
2325
2326 foreach my $errlist (@errors) {
2327 if(@$errlist > 1) {
2328 my $msg = shift @$errlist;
2329
2330 foreach my $e (@$errlist) { $e = "\002$e\002" }
2331
2332 notice($user,
2333 "Cannot $cmd ".
2334 enum("or", @$errlist).
2335 ": $msg"
2336 );
2337 }
2338 }
2339 cs_ban($user, $chan, '', @notinchan) if ($ban and scalar (@notinchan));
2340}
2341
2342sub cs_kickmask($$$;$$) {
2343 my ($user, $chan, $mask, $ban, $reason) = @_;
2344
2345 my $srclevel = get_best_acc($user, $chan);
2346 my $src = get_user_nick($user);
2347 my $cn = $chan->{CHAN};
2348
2349 my $candoOpts = { ACC => $srclevel, OVERRIDE_MSG => 'KICK'.($ban ? 'BAN' : '')."MASK $cn $mask $reason" };
2350 my ($nick, $override) = can_do($chan, ($ban ? 'BAN' : 'KICK'), $user, $candoOpts);
2351 return unless $nick;
2352
2353
2354 $reason = "Requested by $src".($reason?": $reason":'');
2355
2356 my $count = kickmask_noacc($chan, $mask, $reason, $ban);
2357 notice($user, ($count ? "Users kicked from \002$cn\002: $count." : "No users in \002$cn\002 matched $mask."))
2358}
2359
2360sub cs_ban($$$@) {
2361 my ($user, $chan, $type, @targets) = @_;
2362 my $cn = $chan->{CHAN};
2363 my $src = get_user_nick($user);
2364
2365 my $srclevel = get_best_acc($user, $chan);
2366 my ($nick, $override) = can_do($chan, 'BAN', $user, { ACC => $srclevel });
2367 return unless $nick;
2368
2369 my @errors = (
2370 ["I'm sorry, $src, I'm afraid I can't do that."],
2371 ["User not found"],
2372 [$err_deny]
2373 );
2374
2375 my (@bans, @unbans);
2376 foreach my $target (@targets) {
2377 my $tuser;
2378
2379 if(ref($target)) {
2380 $tuser = $target;
2381 }
2382 elsif($target =~ /\,/) {
2383 push @targets, split(',', $target);
2384 next;
2385 }
2386 elsif($target eq '') {
2387 # Should never happen
2388 # but it could, given the split above
2389 next;
2390 }
2391 elsif($target =~ /^-/) {
2392 $target =~ s/^\-//;
2393 push @unbans, $target;
2394 next;
2395 }
2396=cut
2397 elsif($target =~ /[!@]+/) {
2398 ircd::debug("normalizing hostmask $target");
2399 #$target = normalize_hostmask($target);
2400#=cut
2401 my ($nick, $ident, $host) = parse_mask($target);
2402 $nick = '*' unless length($nick);
2403 $ident = '*' unless length($ident);
2404 $host = '*' unless length($host);
2405 $target = "$nick\!$ident\@$host";
2406#=cut
2407 ircd::debug("normalized hostmask: $target");
2408
2409 push @bans, $target;
2410 next;
2411 }
2412=cut
2413 elsif(valid_nick($target)) {
2414 $tuser = { NICK => $target };
2415 }
2416 elsif($target = validate_ban($target)) {
2417 push @bans, $target;
2418 next;
2419 } else {
2420 notice($user, "Not a valid ban target: $target");
2421 next;
2422 }
2423 my $targetlevel = get_best_acc($tuser, $chan);
2424
2425 if(lc $target eq lc agent($chan) or adminserv::is_service($tuser)) {
2426 push @{$errors[0]}, get_user_nick($tuser);
2427 next;
2428 }
2429
2430 unless(get_user_id($tuser)) {
2431 push @{$errors[1]}, get_user_nick($tuser);
2432 next;
2433 }
2434 if( $srclevel <= $targetlevel and not ($override && check_override($user, 'BAN', "BAN $cn $target")) ) {
2435 push @{$errors[2]}, $target;
2436 next;
2437 }
2438
2439 push @bans, make_banmask($chan, $tuser, $type);
2440 }
2441
2442 foreach my $errlist (@errors) {
2443 if(@$errlist > 1) {
2444 my $msg = shift @$errlist;
2445
2446 foreach my $e (@$errlist) { $e = "\002$e\002" }
2447
2448 notice($user,
2449 "Cannot ban ".
2450 enum("or", @$errlist).
2451 ": $msg"
2452 );
2453 }
2454 }
2455
2456 ircd::ban_list(agent($chan), $cn, +1, 'b', @bans) if (scalar(@bans));
2457 ircd::notice(agent($chan), $cn, "$src used BAN ".join(' ', @bans))
2458 if (lc $user->{AGENT} eq lc $csnick) and (cr_chk_flag($chan, CRF_VERBOSE) and scalar(@bans));
2459 cs_unban($user, $chan, @unbans) if scalar(@unbans);
2460}
2461
2462sub cs_invite($$@) {
2463 my ($user, $chan, @targets) = @_;
2464 my $src = get_user_nick($user);
2465 my $cn = $chan->{CHAN};
2466 my $srclevel = get_best_acc($user, $chan);
2467
2468 my @errors = (
2469 ["They are not online."],
2470 ["They are already in \002$cn\002."],
2471 [$err_deny]
2472 );
2473
2474 my @invited;
2475 foreach my $target (@targets) {
2476 my $tuser;
2477 my $tnick;
2478 if(ref($target)) {
2479 $tuser = $target;
2480 $tnick = get_user_nick($tuser);
2481 } elsif(lc($src) eq lc($target)) {
2482 $tuser = $user;
2483 $tnick = $src;
2484 } elsif($target =~ /\,/) {
2485 push @targets, split(',', $target);
2486 next;
2487 } elsif($target eq '') {
2488 # Should never happen
2489 # but it could, given the split above
2490 next;
2491 } else {
2492 $tuser = { NICK => $target };
2493 $tnick = $target;
2494 }
2495
2496 my $candoOpts = { ACC => $srclevel, NOREPLY => 1, OVERRIDE_MSG => "INVITE $cn $target" };
2497 if(lc($src) eq lc($tnick)) {
2498 unless(can_do($chan, 'InviteSelf', $user, $candoOpts)) {
2499 push @{$errors[2]}, $tnick;
2500 next;
2501 }
2502 }
2503 else {
2504 unless(can_do($chan, 'INVITE', $user, $candoOpts)) {
2505 push @{$errors[2]}, $tnick;
2506 next;
2507 }
2508
2509 unless(nickserv::is_online($tnick)) {
2510 push @{$errors[0]}, $tnick;
2511 next;
2512 }
2513
2514 # invite is annoying, so punish them mercilessly
2515 return if flood_check($user, 2);
2516 }
2517
2518 if(is_in_chan($tuser, $chan)) {
2519 push @{$errors[1]}, $tnick;
2520 next;
2521 }
2522
026939ee 2523 ircd::invite(agent($chan), $cn, $tuser); push @invited, $tnick;
2524 ircd::notice(agent($chan), $tuser, "\002$src\002 has invited you to \002$cn\002.")
aecfa1fd 2525 unless(lc($src) eq lc($tnick));
2526 }
2527
2528 foreach my $errlist (@errors) {
2529 if(@$errlist > 1) {
2530 my $msg = shift @$errlist;
2531
2532 foreach my $e (@$errlist) { $e = "\002$e\002" }
2533
2534 notice($user,
2535 "Cannot invite ".
2536 enum("or", @$errlist).
2537 ": $msg"
2538 );
2539 }
2540 }
2541
2542 ircd::notice(agent($chan), $cn, "$src used INVITE ".join(' ', @invited))
2543 if (lc $user->{AGENT} eq lc $csnick)and cr_chk_flag($chan, CRF_VERBOSE) and scalar(@invited);
2544}
2545
2546sub cs_close($$$) {
2547 my ($user, $chan, $reason, $type) = @_;
2548 # $type is a flag, either CRF_CLOSE or CRF_DRONE
2549 my $cn = $chan->{CHAN};
2550 my $oper;
2551
2552 unless($oper = adminserv::can_do($user, 'SERVOP')) {
2553 notice($user, $err_deny);
2554 return;
2555 }
2556
2557 my $rlength = length($reason);
2558 if($rlength >= 350) {
2559 notice($user, 'Close reason is too long by '. $rlength-350 .' character(s). Maximum length is 350 characters.');
2560 return;
2561 }
2562
2563 if(is_registered($chan)) {
2564 $drop_acc->execute($cn);
2565 $drop_lvl->execute($cn);
2566 $del_close->execute($cn);
2567 $drop_akick->execute($cn);
2568 $drop_welcome->execute($cn);
2569 $drop_chantext->execute($cn);
2570 $drop_nicktext->execute($cn); # Leftover channel auths
2571
2572 $set_founder->execute($oper, $cn);
2573 }
2574 else {
2575 $register->execute($cn, $reason, $oper);
2576 }
2577 $set_modelock->execute('+rsnt', $cn);
2578 do_modelock($chan);
2579 set_acc($oper, undef, $chan, FOUNDER);
2580
2581 $set_close->execute($cn, $reason, $oper, $type);
2582 cr_set_flag($chan, (CRF_FREEZE | CRF_CLOSE | CRF_DRONE), 0); #unset flags
2583 cr_set_flag($chan, CRF_HOLD, 1); #set flags
5e682044 2584 cr_set_flag($chan, $type, 1); #set flags
aecfa1fd 2585 my $src = get_user_nick($user);
2586 my $time = gmtime2(time);
2587 my $cmsg = "is closed [$src $time]: $reason";
2588
2589 if ($type == CRF_CLOSE) {
2590 cr_set_flag($chan, CRF_CLOSE, 1); #set flags
2591 clear_users($chan, "Channel $cmsg");
2592 ircd::settopic(agent($chan), $cn, $src, time(), "Channel $cmsg")
2593 }
2594 elsif ($type == CRF_DRONE) {
2595 cr_set_flag($chan, CRF_DRONE, 1); #set flags
2596 chan_kill($chan, "$cn $cmsg");
2597 }
2598
2599 notice($user, "The channel \002$cn\002 is now closed.");
2600 services::ulog($csnick, LOG_INFO(), "closed $cn with reason: $reason", $user, $chan);
2601}
2602
2603sub cs_clear_pre($$) {
2604 my ($user, $chan) = @_;
2605 my $cn = $chan->{CHAN};
2606
2607 my $srclevel = get_best_acc($user, $chan);
2608
2609 my ($cando, $override) = can_do($chan, 'CLEAR', $user, { ACC => $srclevel });
2610 return 0 unless($cando);
2611
2612 $get_highrank->execute($cn);
2613 my ($highrank_nick, $highrank_level) = $get_highrank->fetchrow_array();
2614 $get_highrank->finish();
2615
2616 if($highrank_level > $srclevel && !$override) {
2617 notice($user, "$highrank_nick outranks you in $cn (level: $levels[$highrank_level])");
2618 return 0;
2619 }
2620
2621 return 1;
2622}
2623
2624sub cs_clear_users($$;$) {
2625 my ($user, $chan, $reason) = @_;
2626 my $src = get_user_nick($user);
2627
2628 cs_clear_pre($user, $chan) or return;
2629
2630 my $rlength = length($reason);
2631 if($rlength >= 350) {
2632 notice($user, 'Clear reason is too long by '. $rlength-350 .' character(s). Maximum length is 350 characters.');
2633 return;
2634 }
2635
2636 clear_users($chan, "CLEAR USERS by \002$src\002".($reason?" reason: $reason":''));
2637}
2638
2639sub cs_clear_modes($$;$) {
2640 my ($user, $chan, $reason) = @_;
2641 my $cn = $chan->{CHAN};
2642 my $src = get_user_nick($user);
2643
2644 cs_clear_pre($user, $chan) or return;
2645
2646 my $rlength = length($reason);
2647 if($rlength >= 350) {
2648 notice($user, 'Clear reason is too long by '. $rlength-350 .' character(s). Maximum length is 350 characters.');
2649 return;
2650 }
2651
2652 my $agent = agent($chan);
2653 ircd::notice($agent, $cn, "CLEAR MODES by \002$src\002".($reason?" reason: $reason":''));
2654
2655 $get_chanmodes->execute($cn);
2656 my ($curmodes) = $get_chanmodes->fetchrow_array;
2657 my $ml = get_modelock($chan);
2658
2659 # This method may exceed the 12-mode limit
2660 # But it seems to succeed anyway, even with more than 12.
2661 my ($modes, $parms) = split(/ /, modes::merge(modes::invert($curmodes), $ml, 1). ' * *', 2);
2662 # we split this separately,
2663 # as otherwise it insists on taking the result of the split as a scalar quantity
2664 ircd::setmode($agent, $cn, $modes, $parms);
2665 do_modelock($chan);
2666}
2667
2668sub cs_clear_ops($$;$) {
2669 my ($user, $chan, $reason) = @_;
2670 my $cn = $chan->{CHAN};
2671 my $src = get_user_nick($user);
2672
2673 cs_clear_pre($user, $chan) or return;
2674
2675 my $rlength = length($reason);
2676 if($rlength >= 350) {
2677 notice($user, 'Clear reason is too long by '. $rlength-350 .' character(s). Maximum length is 350 characters.');
2678 return;
2679 }
2680
2681 clear_ops($chan);
2682
2683 ircd::notice(agent($chan), $cn, "CLEAR OPS by \002$src\002".($reason?" reason: $reason":''));
2684 return 1;
2685}
2686
2687sub cs_clear_bans($$;$$) {
2688 my ($user, $chan, $type, $reason) = @_;
2689 my $cn = $chan->{CHAN};
2690 my $src = get_user_nick($user);
2691 $type = 0 unless defined $type;
2692
2693 cs_clear_pre($user, $chan) or return;
2694
2695 my $rlength = length($reason);
2696 if($rlength >= 350) {
2697 notice($user, 'Clear reason is too long by '. $rlength-350 .' character(s). Maximum length is 350 characters.');
2698 return;
2699 }
2700
2701 clear_bans($chan, $type);
2702
2703 ircd::notice(agent($chan), $cn, "CLEAR BANS by \002$src\002".($reason?" reason: $reason":''));
2704}
2705
2706sub cs_welcome_pre($$) {
2707 my ($user, $chan) = @_;
2708
2709 return can_do($chan, 'WELCOME', $user);
2710}
2711
2712sub cs_welcome_add($$$) {
2713 my ($user, $chan, $msg) = @_;
2714 my $src = get_best_acc($user, $chan, 1);
2715 my $cn = $chan->{CHAN};
2716
2717 cs_welcome_pre($user, $chan) or return;
2718
2719 my $mlength = length($msg);
2720 if($mlength >= 350) {
2721 notice($user, 'Welcome Message is too long by '. $mlength-350 .' character(s). Maximum length is 350 characters.');
2722 return;
2723 }
2724
2725 $count_welcome->execute($cn);
2726 my $count = $count_welcome->fetchrow_array;
2727 if ($count >= 5) {
2728 notice($user, 'There is a maximum of five (5) Channel Welcome Messages.');
2729 return;
2730 }
2731
2732 $add_welcome->execute($cn, ++$count, $src, $msg);
2733
2734 notice($user, "Welcome message number $count for \002$cn\002 set to:", " $msg");
2735}
2736
2737sub cs_welcome_list($$) {
2738 my ($user, $chan) = @_;
2739 my $cn = $chan->{CHAN};
2740
2741 cs_welcome_pre($user, $chan) or return;
2742
2743 $list_welcome->execute($cn);
2744
2745 my @data;
2746
2747 while(my ($id, $time, $adder, $msg) = $list_welcome->fetchrow_array) {
2748 push @data, ["$id.", $adder, gmtime2($time), $msg];
2749 }
2750 $list_welcome->finish();
2751
2752 notice($user, columnar {TITLE => "Welcome message list for \002$cn\002:", DOUBLE=>1,
2753 NOHIGHLIGHT => nr_chk_flag_user($user, NRF_NOHIGHLIGHT)}, @data);
2754}
2755
2756sub cs_welcome_del($$$) {
2757 my ($user, $chan, $id) = @_;
2758 my $cn = $chan->{CHAN};
2759
2760 cs_welcome_pre($user, $chan) or return;
2761
2762 if ($del_welcome->execute($cn, $id) == 1) {
2763 notice($user, "Welcome Message \002$id\002 deleted from \002$cn\002");
2764 $consolidate_welcome->execute($cn, $id);
2765 }
2766 else {
2767 notice($user,
2768 "Welcome Message number $id for \002$cn\002 does not exist.");
2769 }
2770}
2771
2772sub cs_alist($$;$) {
2773 my ($user, $chan, $mask) = @_;
2774 my $cn = $chan->{CHAN};
2775
2776 chk_registered($user, $chan) or return;
2777
2778 my $slevel = get_best_acc($user, $chan);
2779
2780 can_do($chan, 'ACCLIST', $user, { ACC => $slevel }) or return;
2781
2782 my @reply;
2783
2784 if($mask) {
2785 my ($mnick, $mident, $mhost) = glob2sql(parse_mask($mask));
2786 $mnick = '%' if($mnick eq '');
2787 $mident = '%' if($mident eq '');
2788 $mhost = '%' if($mhost eq '');
2789
2790 $get_acc_list2_mask->execute($mnick, $cn, $mnick, $mident, $mhost);
2791 while(my ($nick, $adder, $level, $time, $last_used, $ident, $vhost) = $get_acc_list2_mask->fetchrow_array) {
2792 push @reply, "*) $nick ($ident\@$vhost) Rank: ".$levels[$level] . ($adder ? ' Added by: '.$adder : '');
2793 push @reply, ' '.($time ? 'Date/time added: '. gmtime2($time).' ' : '').
2794 ($last_used ? 'Last used '.time_ago($last_used).' ago' : '') if ($time or $last_used);
2795 }
2796 $get_acc_list2_mask->finish();
2797 } else {
2798 $get_acc_list2->execute($cn);
2799 while(my ($nick, $adder, $level, $time, $last_used, $ident, $vhost) = $get_acc_list2->fetchrow_array) {
2800 push @reply, "*) $nick ($ident\@$vhost) Rank: ".$levels[$level] . ($adder ? ' Added by: '.$adder : '');
2801 push @reply, ' '.($time ? 'Date/time added: '. gmtime2($time).' ' : '').
2802 ($last_used ? 'Last used '.time_ago($last_used).' ago' : '') if ($time or $last_used);
2803 }
2804 $get_acc_list2->finish();
2805 }
2806
2807 notice($user, "Access list for \002$cn\002:", @reply);
2808
2809 return;
2810}
2811
2812sub cs_banlist($$) {
2813 my ($user, $chan) = @_;
2814 my $cn = $chan->{CHAN};
2815 can_do($chan, 'UnbanSelf', $user, { NOREPLY => 1 }) or can_do($chan, 'BAN', $user) or return;
2816
2817 my $i = 0; my @data;
2818 $list_bans->execute($cn, 0);
2819 while(my ($mask, $setter, $time) = $list_bans->fetchrow_array()) {
2820 push @data, ["\002".++$i."\002", sql2glob($mask), $setter, ($time ? gmtime2($time) : '')];
2821 }
2822
2823 notice($user, columnar {TITLE => "Ban list of \002$cn\002:", DOUBLE=>1,
2824 NOHIGHLIGHT => nr_chk_flag_user($user, NRF_NOHIGHLIGHT)}, @data);
2825}
2826
2827sub cs_unban($$@) {
2828 my ($user, $chan, @parms) = @_;
2829 my $cn = $chan->{CHAN};
2830
2831 my $self;
2832 $self = 1 if ( (scalar(@parms) == 1) and ( lc($parms[0]) eq lc(get_user_nick($user)) ) );
2833 if ($parms[0] eq '*') {
2834 cs_clear_bans($user, $chan);
2835 return;
2836 }
2837 else {
2838 can_do($chan, ($self ? 'UnbanSelf' : 'UNBAN'), $user) or return;
2839 }
2840
2841 my (@userlist, @masklist);
2842 foreach my $parm (@parms) {
2843 if(valid_nick($parm)) {
2844 my $tuser = ($self ? $user : { NICK => $parm });
2845 unless(get_user_id($tuser)) {
2846 notice($user, "No such user: \002$parm\002");
2847 next;
2848 }
2849 push @userlist, $tuser;
2850 } elsif($parm =~ /^[0-9\.,-]+$/) {
2851 foreach my $num (makeSeqList($parm)) {
2852 push @masklist, get_ban_num($chan, $num);
2853 }
2854 } else {
2855 push @masklist, $parm;
2856 }
2857 }
2858
2859 if(scalar(@userlist)) {
2860 unban_user($chan, @userlist);
2861 notice($user, "All bans affecting " .
2862 ( $self ? 'you' : enum( 'and', map(get_user_nick($_), @userlist) ) ) .
2863 " on \002$cn\002 have been removed.");
2864 }
2865 if(scalar(@masklist)) {
2866 ircd::ban_list(agent($chan), $cn, -1, 'b', @masklist);
2867 notice($user, "The following bans have been removed: ".join(' ', @masklist))
2868 if scalar(@masklist);
2869 }
2870}
2871
2872sub cs_updown($$@) {
2873 my ($user, $cmd, @chans) = @_;
2874 return cs_updown2($user, $cmd, { CHAN => shift @chans }, @chans)
2875 if (defined($chans[1]) and $chans[1] !~ "^\#" and $chans[0] =~ "^\#");
2876
2877 @chans = get_user_chans($user)
2878 unless (@chans);
2879
2880 if (uc($cmd) eq 'UP') {
2881 foreach my $cn (@chans) {
2882 next unless ($cn =~ /^\#/);
2883 my $chan = { CHAN => $cn };
2884 next if cr_chk_flag($chan, (CRF_DRONE | CRF_CLOSE | CRF_FREEZE), 1);
2885 chanserv::set_modes($user, $chan, chanserv::get_best_acc($user, $chan));
2886 }
2887 }
2888 elsif (uc($cmd) eq 'DOWN') {
2889 foreach my $cn (@chans) {
2890 next unless ($cn =~ /^\#/);
2891 chanserv::unset_modes($user, { CHAN => $cn });
2892 }
2893 }
2894}
2895
2896sub cs_updown2($$$@) {
2897 my ($user, $cmd, $chan, @targets) = @_;
2898 no warnings 'void';
2899 my $agent = $user->{AGENT} or $csnick;
2900 my $cn = $chan->{CHAN};
2901
2902 return unless chk_registered($user, $chan);
2903 if (cr_chk_flag($chan, CRF_FREEZE())) {
2904 notice($user, "\002$cn\002 is frozen and access suspended.");
2905 return;
2906 }
2907
2908 my $acc = get_best_acc($user, $chan);
2909 return unless(can_do($chan, 'UPDOWN', $user, { ACC => $acc }));
2910
2911 my $updown = ((uc($cmd) eq 'UP') ? 1 : 0);
2912
2913 my ($override, $check_override);
2914 my (@list, $count);
2915 foreach my $target (@targets) {
2916
2917 my $tuser = { NICK => $target };
2918
2919 unless(is_in_chan($tuser, $chan)) {
2920 notice($user, "\002$target\002 is not in \002$cn\002.");
2921 next;
2922 }
2923
2924 if($updown) {
2925 push @list, $target;
2926 chanserv::set_modes($tuser, $chan, chanserv::get_best_acc($tuser, $chan));
2927 }
2928 else {
2929 my $top = get_op($tuser, $chan);
2930 unless($top) {
2931 notice($user, "\002$target\002 is already deopped in \002$cn\002.");
2932 next;
2933 }
2934
2935 if(!$override and get_best_acc($tuser, $chan) > $acc) {
2936 unless($check_override) {
2937 $override = adminserv::can_do($user, 'SUPER');
2938 $check_override = 1;
2939 }
2940 if($check_override and !$override) {
2941 notice($user, "\002$target\002 outranks you in \002$cn\002.");
2942 next;
2943 }
2944 }
2945 push @list, $target;
2946 chanserv::unset_modes($tuser, { CHAN => $cn });
2947 }
2948 $count++;
2949 }
2950
2951 my $src = get_user_nick($user);
2952 ircd::notice(agent($chan), '%'.$cn, "$src used $cmd ".join(' ', @list))
2953 if (lc $user->{AGENT} eq lc $csnick) and cr_chk_flag($chan, CRF_VERBOSE);
2954}
2955
2956sub cs_getkey($$) {
2957 my ($user, $chan) = @_;
2958 my $cn = $chan->{CHAN};
2959
2960 can_do($chan, 'GETKEY', $user) or return;
2961
2962 $get_chanmodes->execute($cn);
2963 my $modes = $get_chanmodes->fetchrow_array; $get_chanmodes->finish();
2964
2965 if(my $key = modes::get_key($modes)) {
2966 notice($user, "Channel key for \002$cn\002: $key");
2967 }
2968 else {
2969 notice($user, "\002$cn\002 has no channel key.");
2970 }
2971}
2972
2973sub cs_auth($$$@) {
2974 my ($user, $chan, $cmd, @args) = @_;
2975 my $cn = $chan->{CHAN};
2976 $cmd = lc $cmd;
2977
2978 return unless chk_registered($user, $chan);
2979 return unless can_do($chan, 'AccChange', $user);
2980 my $userlevel = get_best_acc($user, $chan);
2981 if($cmd eq 'list') {
2982 my @data;
2983 $list_auth_chan->execute($cn);
2984 while(my ($nick, $data) = $list_auth_chan->fetchrow_array()) {
2985 my ($adder, $old, $level, $time) = split(/:/, $data);
2986 push @data, ["\002$nick\002", $levels[$level], $adder, gmtime2($time)];
2987 }
2988 if ($list_auth_chan->rows()) {
2989 notice($user, columnar {TITLE => "Pending authorizations for \002$cn\002:",
2990 NOHIGHLIGHT => nr_chk_flag_user($user, NRF_NOHIGHLIGHT)}, @data);
2991 }
2992 else {
2993 notice($user, "There are no pending authorizations for \002$cn\002");
2994 }
2995 $list_auth_chan->finish();
2996 }
2997 elsif($cmd eq 'remove' or $cmd eq 'delete' or $cmd eq 'del') {
2998 my ($nick, $adder, $old, $level, $time);
2999 my $parm = shift @args;
3000 if(misc::isint($parm) and ($nick, $adder, $old, $level, $time) = get_auth_num($cn, $parm))
3001 {
3002 }
3003 elsif (($adder, $old, $level, $time) = get_auth_nick($cn, $parm))
3004 {
3005 $nick = $parm;
3006 }
3007 unless ($nick) {
3008 # This should normally be an 'else' as the elsif above should prove false
3009 # For some reason, it doesn't work. the unless ($nick) fixes it.
3010 # It only doesn't work for numbered entries
3011 notice($user, "There is no entry for \002$parm\002 in \002$cn\002's AUTH list");
3012 return;
3013 }
3014 $nickserv::del_auth->execute($nick, $cn); $nickserv::del_auth->finish();
3015 my $log_str = "deleted AUTH entry $cn $nick $levels[$level]";
3016 my $src = get_user_nick($user);
3017 notice($user, "You have $log_str");
3018 ircd::notice(agent($chan), '%'.$cn, "has \002$src\002 has $log_str")
3019 if cr_chk_flag($chan, CRF_VERBOSE);
3020 services::ulog($chanserv::csnick, LOG_INFO(), "has $log_str", $user, $chan);
3021 }
3022 else {
3023 notice($user, "Unknown AUTH command \002$cmd\002");
3024 }
3025}
3026
3027sub cs_mode($$$@) {
3028 my ($user, $chan, $modes_in, @parms_in) = @_;
3029 can_do($chan, 'MODE', $user) or return undef;
3030 ($modes_in, @parms_in) = validate_chmodes($modes_in, @parms_in);
3031
3032 my %permhash = (
3033 'q' => 'OWNER',
3034 'a' => 'ADMIN',
3035 'o' => 'OP',
3036 'h' => 'HALFOP',
3037 'v' => 'VOICE',
3038 );
3039 my $sign = '+'; my $cn = $chan->{CHAN};
3040 my ($modes_out, @parms_out, @bans);
3041 foreach my $mode (split(//, $modes_in)) {
3042 $sign = $mode if $mode =~ /[+-]/;
3043 if ($permhash{$mode}) {
3044 my $parm = shift @parms_in;
3045 cs_setmodes($user, ($sign eq '-' ? 'de' : '').$permhash{$mode}, $chan, $parm);
3046 }
3047 elsif ($mode eq 'b') {
3048 my $parm = shift @parms_in;
3049 if($sign eq '-') {
3050 $parm = '-'.$parm;
3051 }
3052 push @bans, $parm;
3053 }
3054 elsif($mode =~ /[eIlLkjf]/) {
3055 $modes_out .= $mode;
3056 push @parms_out, shift @parms_in;
3057 } else {
3058 $modes_out .= $mode;
3059 }
3060 }
3061
3062 if(scalar(@bans)) {
3063 cs_ban($user, $chan, undef, @bans);
3064 }
3065 return if $modes_out =~ /^[+-]*$/;
3066 ircd::setmode(agent($chan), $chan->{CHAN}, $modes_out, join(' ', @parms_out));
3067 do_modelock($chan, $modes_out.' '.join(' ', @parms_out));
3068
3069 $modes_out =~ s/^[+-]*([+-].*)$/$1/;
3070 ircd::notice(agent($chan), '%'.$cn, get_user_nick($user).' used MODE '.join(' ', $modes_out, @parms_out))
3071 if (lc $user->{AGENT} eq lc $csnick) and cr_chk_flag($chan, CRF_VERBOSE);
3072}
3073
3074sub cs_copy($$@) {
3075 my ($user, $chan1, @args) = @_;
3076 my $cn1 = $chan1->{CHAN};
3077 my $cn2;
3078 my $type;
3079 if($args[0] =~ /^#/) {
3080 $cn2 = shift @args;
3081 $type = 'all';
3082 }
3083 if($args[0] =~ /(?:acc(?:ess)?|akick|levels|all)/i) {
3084 $type = shift @args;
3085 $cn2 = shift @args unless $cn2;
3086 }
3087 my $rank;
3088 if($type =~ /^acc(?:ess)?/i) {
3089 if($cn2 =~ /^#/) {
3090 $rank = shift @args;
3091 } else {
3092 $rank = $cn2;
3093 $cn2 = shift @args;
3094 }
3095 }
3096 unless(defined $cn2 and defined $type) {
3097 notice($user, 'Unknown COPY command', 'Syntax: COPY #chan1 [type] #chan2');
3098 }
3099 my $chan2 = { CHAN => $cn2 };
3100 if(lc($cn1) eq lc($cn2)) {
3101 notice($user, "You cannot copy a channel onto itself.");
3102 }
3103 unless(is_registered($chan1)) {
3104 notice($user, "Source channel \002$cn1\002 must be registered.");
3105 return;
3106 }
3107 can_do($chan1, 'COPY', $user) or return undef;
3108 if(lc $type eq 'all') {
3109 if(is_registered($chan2)) {
3110 notice($user, "When copying all channel details, destination channel cannot be registered.");
3111 return;
3112 } elsif(!(get_op($user, $chan2) & ($opmodes{o} | $opmodes{a} | $opmodes{q}))) {
3113 # This would be preferred to be a 'opmode_mask' or something
3114 # However that might be misleading due to hop not being enough to register
3115 notice($user, "You must have channel operator status to register \002$cn2\002.");
3116 return;
3117 } else {
3118 cs_copy_chan_all($user, $chan1, $chan2);
3119 return;
3120 }
3121 } else {
3122 unless(is_registered($chan2)) {
3123 notice($user, "When copying channel lists, destination channel must be registered.");
3124 return;
3125 }
3126 can_do($chan2, 'COPY', $user) or return undef;
3127 }
3128 if(lc $type eq 'akick') {
3129 cs_copy_chan_akick($user, $chan1, $chan2);
3130 } elsif(lc $type eq 'levels') {
3131 cs_copy_chan_levels($user, $chan1, $chan2);
3132 } elsif($type =~ /^acc(?:ess)?/i) {
3133 cs_copy_chan_acc($user, $chan1, $chan2, xop_byname($rank));
3134 }
3135}
3136
3137sub cs_copy_chan_all($$$) {
3138 my ($user, $chan1, $chan2) = @_;
3139 cs_copy_chan_chanreg($user, $chan1, $chan2);
3140 cs_copy_chan_levels($user, $chan1, $chan2);
3141 cs_copy_chan_acc($user, $chan1, $chan2);
3142 cs_copy_chan_akick($user, $chan1, $chan2);
3143 return;
3144}
3145
3146sub cs_copy_chan_chanreg($$$) {
3147 my ($user, $chan1, $chan2) = @_;
3148 my $cn1 = $chan1->{CHAN};
3149 my $cn2 = $chan2->{CHAN};
3150
3151 copy_chan_chanreg($cn1, $cn2);
3152 botserv::bot_join($chan2) unless (lc(agent($chan2)) eq lc($csnick) );
3153 do_modelock($chan2);
3154 notice($user, "Registration for \002$cn1\002 copied to \002$cn2\002");
3155
3156 my $log_str = "copied the channel registration for \002$cn1\002 to \002$cn2\002";
3157 services::ulog($chanserv::csnick, LOG_INFO(), "$log_str", $user, $chan1);
3158
3159 my $src = get_user_nick($user);
3160 ircd::notice(agent($chan1), '%'.$cn1, "\002$src\002 $log_str")
3161 if cr_chk_flag($chan1, CRF_VERBOSE);
3162 ircd::notice(agent($chan2), '%'.$cn2, "\002$src\002 $log_str")
3163 if cr_chk_flag($chan2, CRF_VERBOSE);
3164}
3165
3166sub cs_copy_chan_acc($$$;$) {
3167 my ($user, $chan1, $chan2, $level) = @_;
3168 my $cn1 = $chan1->{CHAN};
3169 my $cn2 = $chan2->{CHAN};
3170
3171 copy_chan_acc($cn1, $cn2, $level);
3172
3173 unless(cr_chk_flag($chan2, CRF_NEVEROP)) {
3174 $get_chan_users->execute($cn2); my @targets;
3175 while (my ($nick, $uid) = $get_chan_users->fetchrow_array()) {
3176 push @targets, $nick unless nr_chk_flag_user({ NICK => $nick, ID => $uid }, NRF_NEVEROP);
3177 }
3178 cs_updown2($user, 'UP', $chan2, @targets);
3179 }
3180
3181 notice($user, "Access list for \002$cn1\002 ".
3182 ($level ? "(rank: \002".$plevels[$level + $plzero]."\002) " : '').
3183 "copied to \002$cn2\002");
3184
3185 my $log_str = "copied the channel access list for \002$cn1\002 ".
3186 ($level ? "(rank: \002".$plevels[$level + $plzero]."\002) " : '').
3187 "to \002$cn2\002";
3188 services::ulog($chanserv::csnick, LOG_INFO(), "$log_str", $user, $chan1);
3189
3190 my $src = get_user_nick($user);
3191 ircd::notice(agent($chan1), '%'.$cn1, "\002$src\002 $log_str")
3192 if cr_chk_flag($chan1, CRF_VERBOSE);
3193 ircd::notice(agent($chan2), '%'.$cn2, "\002$src\002 $log_str")
3194 if cr_chk_flag($chan2, CRF_VERBOSE);
3195}
3196
3197sub cs_copy_chan_levels($$$) {
3198 my ($user, $chan1, $chan2) = @_;
3199 my $cn1 = $chan1->{CHAN};
3200 my $cn2 = $chan2->{CHAN};
3201
3202 copy_chan_levels($cn1, $cn2);
3203 notice($user, "LEVELS for \002$cn1\002 copied to \002$cn2\002");
3204
3205 my $log_str = "copied the LEVELS list for \002$cn1\002 to \002$cn2\002";
3206 services::ulog($chanserv::csnick, LOG_INFO(), "$log_str", $user, $chan1);
3207
3208 my $src = get_user_nick($user);
3209 ircd::notice(agent($chan1), '%'.$cn1, "\002$src\002 $log_str")
3210 if cr_chk_flag($chan1, CRF_VERBOSE);
3211 ircd::notice(agent($chan2), '%'.$cn2, "\002$src\002 $log_str")
3212 if cr_chk_flag($chan2, CRF_VERBOSE);
3213}
3214
3215sub cs_copy_chan_akick($$$) {
3216 my ($user, $chan1, $chan2) = @_;
3217 my $cn1 = $chan1->{CHAN};
3218 my $cn2 = $chan2->{CHAN};
3219
3220 copy_chan_akick($cn1, $cn2);
3221 notice($user, "Channel AKick list for \002$cn1\002 copied to \002$cn2\002");
3222
3223 my $log_str = "copied the AKick list for \002$cn1\002 to \002$cn2\002";
3224 services::ulog($chanserv::csnick, LOG_INFO(), "$log_str", $user, $chan1);
3225
3226 my $src = get_user_nick($user);
3227 ircd::notice(agent($chan1), '%'.$cn1, "\002$src\002 $log_str")
3228 if cr_chk_flag($chan1, CRF_VERBOSE);
3229 ircd::notice(agent($chan2), '%'.$cn2, "\002$src\002 $log_str")
3230 if cr_chk_flag($chan2, CRF_VERBOSE);
3231}
3232
3233sub cs_mlock($$$@) {
3234 my ($user, $chan, $cmd, @args) = @_;
3235 my $cn = $chan->{CHAN};
3236 # does this need its own privilege now?
3237 can_do($chan, 'SET', $user) or return;
3238 my $modes;
3239 if(scalar(@args)) {
3240 my ($modes_in, @parms_in) = validate_chmodes(shift @args, @args);
3241 $modes = $modes_in.' '.join(' ', @parms_in);
3242 @args = undef;
3243 }
3244
3245 my $cur_modelock = get_modelock($chan);
3246 if(lc $cmd eq 'add') {
3247 $modes = modes::merge($cur_modelock, $modes, 1);
3248 $modes = sanitize_mlockable($modes);
3249 $set_modelock->execute($modes, $cn);
3250 }
3251 elsif(lc $cmd eq 'del') {
3252 $modes =~ s/[+-]//g;
3253 $modes = modes::add($cur_modelock, "-$modes", 1);
3254 $set_modelock->execute($modes, $cn);
3255 }
3256 elsif(lc $cmd eq 'set') {
3257 $modes = modes::merge($modes, "+r", 1);
3258 $set_modelock->execute($modes, $cn);
3259 }
3260 elsif(lc $cmd eq 'reset') {
3261 $set_modelock->execute(services_conf_default_channel_mlock, $cn);
3262 } else {
3263 notice($user, "Unknown MLOCK command \"$cmd\"");
3264 return;
3265 }
3266
3267 notice($user, "Mode lock for \002$cn\002 has been set to: \002$modes\002");
3268 do_modelock($chan);
3269
3270=cut
3271 notice($user, columnar {TITLE => "Ban list of \002$cn\002:", DOUBLE=>1,
3272 NOHIGHLIGHT => nr_chk_flag_user($user, NRF_NOHIGHLIGHT)}, @data);
3273=cut
3274}
3275
3276use SrSv::MySQL::Stub {
3277 getChanUsers => ['COLUMN', "SELECT user.nick FROM chanuser JOIN user ON (user.id=chanuser.nickid)
3278 WHERE chanuser.chan=? AND chanuser.joined=1"]
3279};
3280
3281sub cs_resync($@) {
3282 my ($user, @cns) = @_;
3283 foreach my $cn (@cns) {
3284 my $chan = { CHAN => $cn };
3285 next unless cs_clear_ops($user, $chan, 'Resync');
3286 cs_updown2($user, 'up', $chan, getChanUsers($cn));
3287 if(can_do($chan, 'AKickEnforce', $user, { OVERRIDE_MSG => "AKICK $cn ENFORCE", NOREPLY => 1 })) {
3288 cs_akick_enforce($user, $chan);
3289 }
3290 }
3291}
3292
3293sub cs_join($@) {
3294 my ($user, @cns) = @_;
3295 my @reply;
3296 my @out_cns;
3297 foreach my $cn (@cns) {
3298 if($cn =~ /,/) {
3299 push @cns, split(',', $cn);
3300 }
3301 elsif($cn eq '') {
3302 next;
3303 }
3304 my $chan = { CHAN => $cn };
3305 my $cando_opts = { NOREPLY => 1 };
3306 if(check_akick($user, $chan, 1)) {
3307 push @reply, "You are banned from $cn";
3308 next;
3309 } elsif(!can_do($chan, 'JOIN', $user, $cando_opts)) {
3310 push @reply, "$cn is a private channel.";
3311 next;
3312 }
3313 if(is_in_chan($user, $chan)) {
3314 next;
3315 }
3316 if(can_do($chan, 'InviteSelf', $user, $cando_opts)) {
3317 cs_invite($user, $chan, $user);
3318 }
3319 push @out_cns, $cn;
3320
3321 }
88eba747 3322 ircd::svsjoin(get_user_agent($user), $user, @out_cns) if scalar @out_cns;
aecfa1fd 3323 notice($user, @reply) if scalar @reply;
3324}
3325
3326sub cs_topic($$@) {
3327 my ($user, $cn, @args) = @_;
3328 my ($chan, $msg) = ($cn->{CHAN}, join(" ", @args));
3329 can_do($cn, 'SETTOPIC', $user) or return undef;
3330 ircd::settopic(agent($cn), $chan, get_user_nick($user), time, ($msg =~ /^none/i ? "" : $msg));
3331}
3332
3333### MISCELLANEA ###
3334
3335# these are helpers and do NOT check if $cn1 or $cn2 is reg'd
3336sub copy_chan_acc($$;$) {
3337 my ($cn1, $cn2, $level) = @_;
3338 if($level) {
3339 $copy_acc_rank->execute($cn2, $cn1, $level);
3340 $copy_acc_rank->finish();
3341 } else {
3342 $get_founder->execute($cn2);
3343 my ($founder) = $get_founder->fetchrow_array;
3344 $get_founder->finish();
3345
3346 $copy_acc->execute($cn2, $cn1, $founder);
3347 $copy_acc->finish();
3348 }
3349}
3350
3351sub copy_chan_akick($$;$) {
3352 my ($cn1, $cn2) = @_;
3353 $copy_akick->execute($cn2, $cn1);
3354 $copy_akick->finish();
3355 copy_chan_acc($cn1, $cn2, -1);
3356}
3357
3358sub copy_chan_levels($$) {
3359 my ($cn1, $cn2) = @_;
3360 $copy_levels->execute($cn2, $cn1);
3361 $copy_levels->finish();
3362}
3363
3364sub copy_chan_chanreg($$) {
3365 my ($cn1, $cn2) = @_;
3366 $get_founder->execute($cn1);
3367 my ($founder) = $get_founder->fetchrow_array;
3368 $get_founder->finish();
3369 set_acc($founder, undef, { CHAN => $cn2 }, FOUNDER);
3370 $copy_chanreg->execute($cn2, $cn1);
3371 $copy_chanreg->finish();
3372}
3373
3374sub do_welcome($$) {
3375 my ($user, $chan) = @_;
3376 my $cn = $chan->{CHAN};
3377
3378 $get_welcomes->execute($cn);
3379 if($get_welcomes->rows) {
3380 my @welcomes;
3381 while(my ($msg) = $get_welcomes->fetchrow_array) {
3382 push @welcomes, (cr_chk_flag($chan, CRF_WELCOMEINCHAN) ? '' : "[$cn] " ).$msg;
3383 }
3384 if(cr_chk_flag($chan, CRF_WELCOMEINCHAN)) {
3385 ircd::privmsg(agent($chan), $cn, @welcomes);
3386 } else {
3387 notice($user, @welcomes);
3388 }
3389 }
3390 $get_welcomes->finish();
3391}
3392
3393sub do_greet($$) {
3394 my ($user, $chan) = @_;
3395 my $cn = $chan->{CHAN};
3396
3397 if(can_do($chan, 'GREET', $user)) {
3398 my $src = get_user_nick($user);
3399 $nickserv::get_greet->execute(get_user_id($user));
3400 my ($greet) = $nickserv::get_greet->fetchrow_array();
3401 $nickserv::get_greet->finish();
3402 ircd::privmsg(agent($chan), $cn, "[\002$src\002] $greet") if $greet;
3403 }
3404}
3405
3406sub chk_registered($$) {
3407 my ($user, $chan) = @_;
3408
3409 unless(is_registered($chan)) {
3410 my $cn = $chan->{CHAN};
3411
3412 notice($user, "The channel \002$cn\002 is not registered.");
3413 return 0;
3414 }
3415
3416 return 1;
3417}
3418
3419sub make_banmask($$;$) {
3420 my ($chan, $tuser, $type) = @_;
3421 my $nick = get_user_nick($tuser);
3422
3423 my ($ident, $vhost) = get_vhost($tuser);
3424 no warnings 'misc';
3425 my ($nick, $ident, $vhost) = make_hostmask(get_bantype($chan), $nick, $ident, $vhost);
3426 if($type eq 'q') {
3427 $type = '~q:';
3428 } elsif($type eq 'n') {
3429 $type = '~n:';
3430 } else {
3431 $type = '';
3432 }
3433 return $type."$nick!$ident\@$vhost";
3434}
3435
3436sub kickban($$$$;$) {
3437 my ($chan, $user, $mask, $reason, $noflush) = @_;
3438 my $cn = $chan->{CHAN};
3439 my $nick = get_user_nick($user);
3440
3441 return 0 if adminserv::is_service($user);
3442
3443 my $agent = agent($chan);
3444
3445 unless($mask) {
3446 $mask = make_banmask($chan, $user);
3447 }
3448
3449 enforcer_join($chan) if (get_user_count($chan) <= 1);
3450 ircd::setmode($agent, $cn, '+b', $mask);
3451 ircd::flushmodes() unless $noflush;
5e682044 3452 ircd::kick($agent, $cn, $user, $reason);
aecfa1fd 3453 return 1;
3454}
3455
3456sub kickban_multi($$$) {
3457 my ($chan, $users, $reason) = @_;
3458 my $cn = $chan->{CHAN};
3459 my $agent = agent($chan);
3460
3461 enforcer_join($chan);
3462 ircd::setmode($agent, $cn, '+b', '*!*@*');
3463 ircd::flushmodes();
3464
3465 foreach my $user (@$users) {
3466 next if adminserv::is_ircop($user) or adminserv::is_svsop($user, adminserv::S_HELP());
5e682044 3467 ircd::kick($agent, $cn, $user, $reason);
aecfa1fd 3468 }
3469}
3470
3471sub clear_users($$) {
3472 my ($chan, $reason) = @_;
3473 my $cn = $chan->{CHAN};
3474 my $agent = agent($chan);
3475 my $i;
3476
3477 enforcer_join($chan);
3478 ircd::setmode($agent, $cn, '+b', '*!*@*');
3479 ircd::flushmodes();
3480 $get_chan_users->execute($cn);
3481 while(my ($nick, $uid) = $get_chan_users->fetchrow_array) {
3482 my $user = { NICK => $nick, ID => $uid };
5e682044 3483 ircd::kick($agent, $cn, $user, $reason)
aecfa1fd 3484 unless adminserv::is_ircop($user) or adminserv::is_svsop($user, adminserv::S_HELP());
3485 $i++;
3486 }
3487
3488 return $i;
3489}
3490
3491sub kickmask($$$$) {
3492 my ($chan, $mask, $reason, $ban) = @_;
3493 my $cn = $chan->{CHAN};
3494 my $agent = agent($chan);
3495
3496 my ($nick, $ident, $host) = glob2sql(parse_mask($mask));
3497 $nick = '%' if ($nick eq '');
3498 $ident = '%' if ($ident eq '');
3499 $host = '%' if ($host eq '');
3500
3501 if ($ban) {
3502 my $banmask = $nick.'!'.$ident.'@'.$host;
3503 $banmask =~ tr/%_/*?/;
3504 ircd::setmode($agent, $cn, '+b', $banmask);
3505 ircd::flushmodes();
3506 }
3507
3508 my $i;
3509 $get_chan_users_mask->execute($cn, $nick, $ident, $host, $host, $host);
3510 while(my ($nick, $uid) = $get_chan_users_mask->fetchrow_array) {
3511 my $user = { NICK => $nick, ID => $uid };
5e682044 3512 ircd::kick($agent, $cn, $user, $reason)
aecfa1fd 3513 unless adminserv::is_service($user);
3514 $i++;
3515 }
3516 $get_chan_users_mask->finish();
3517
3518 return $i;
3519}
3520
3521sub kickmask_noacc($$$$) {
3522 my ($chan, $mask, $reason, $ban) = @_;
3523 my $cn = $chan->{CHAN};
3524 my $agent = agent($chan);
3525
3526 my ($nick, $ident, $host) = glob2sql(parse_mask($mask));
3527 $nick = '%' if ($nick eq '');
3528 $ident = '%' if ($ident eq '');
3529 $host = '%' if ($host eq '');
3530
3531 if ($ban) {
3532 my $banmask = $nick.'!'.$ident.'@'.$host;
3533 $banmask =~ tr/%_/*?/;
3534 ircd::setmode($agent, $cn, '+b', $banmask);
3535 ircd::flushmodes();
3536 }
3537
3538 my $i;
3539 $get_chan_users_mask_noacc->execute($cn, $nick, $ident, $host, $host, $host);
3540 while(my ($nick, $uid) = $get_chan_users_mask_noacc->fetchrow_array) {
3541 my $user = { NICK => $nick, ID => $uid };
5e682044 3542 ircd::kick($agent, $cn, $user, $reason)
aecfa1fd 3543 unless adminserv::is_service($user);
3544 $i++;
3545 }
3546 $get_chan_users_mask_noacc->finish();
3547
3548 return $i;
3549}
3550
3551sub clear_ops($) {
3552 my ($chan) = @_;
3553 my $cn = $chan->{CHAN};
3554 my @modelist;
3555 my $agent = agent($chan);
3556
3557 $get_chan_users->execute($cn);
3558 while(my ($nick, $uid) = $get_chan_users->fetchrow_array) {
5e682044 3559 my $user = { NICK => $nick };
3560 get_user_id ($user);
aecfa1fd 3561 my $opmodes = get_op($user, $chan);
026939ee 3562 print "OPMODES $opmodes\n";
aecfa1fd 3563 for(my $i; $i < 5; $i++) {
3564 if($opmodes & 2**$i) {
5e682044 3565 push @modelist, ['-'.$opmodes[$i], $user];
aecfa1fd 3566 }
3567 }
3568 }
3569
3570 ircd::setmode2($agent, $cn, @modelist);
3571}
3572
3573sub clear_bans($;$) {
3574 my ($chan, $type) = @_;
3575 my $cn = $chan->{CHAN};
3576 my @args = ();
3577 my $agent = agent($chan);
3578 $type = 0 unless defined $type;
3579 my $mode = ($type == 128 ? 'e' : 'b');
3580
3581 my @banlist = ();
3582 $get_all_bans->execute($cn, $type);
3583 while(my ($mask) = $get_all_bans->fetchrow_array) {
3584 $mask =~ tr/\%\_/\*\?/;
3585 push @banlist, $mask;
3586 }
3587
3588 ircd::ban_list($agent, $cn, -1, $mode, @banlist);
3589 ircd::flushmodes();
3590}
3591
3592sub unban_user($@) {
3593 my ($chan, @userlist) = @_;
3594 my $cn = $chan->{CHAN};
3595 my $count;
5e682044 3596 if (defined(&ircd::unban_users)) {
3597 ircd::unban_users(@userlist);
aecfa1fd 3598 }
3599
3600 foreach my $tuser (@userlist) {
3601 my $tuid;
3602 unless($tuid = get_user_id($tuser)) {
3603 next;
3604 }
aecfa1fd 3605 my (@bans);
3606 # We don't handle extended bans. Yet.
3607 $find_bans_chan_user->execute($cn, $tuid, 0);
3608 while (my ($mask) = $find_bans_chan_user->fetchrow_array) {
3609 $mask =~ tr/\%\_/\*\?/;
3610 push @bans, $mask;
3611 }
3612 $find_bans_chan_user->finish();
3613
3614 ircd::ban_list(agent($chan), $cn, -1, 'b', @bans) if scalar(@bans);
3615 $delete_bans_chan_user->execute($cn, $tuid, 0); $delete_bans_chan_user->finish();
3616 $count++;
3617 }
3618 return $count;
3619}
3620
3621sub chan_kill($$;$) {
3622 my ($chan, $reason, $tusers) = @_;
3623 my $cn = $chan->{CHAN};
3624 my $agent = agent($chan);
3625 my $i;
3626
3627 enforcer_join($chan);
3628 if ($tusers) {
3629 foreach my $tuser (@$tusers) {
3630 $tuser->{ID} = $tuser->{__ID} if defined($tuser->{__ID}); # user_join_multi does this.
3631 nickserv::kline_user($tuser, services_conf_chankilltime, $reason)
3632 unless adminserv::is_ircop($tuser) or adminserv::is_svsop($tuser, adminserv::S_HELP());
3633 $i++;
3634 }
3635 }
3636 else {
3637 $get_chan_users->execute($cn);
3638 while(my ($nick, $uid) = $get_chan_users->fetchrow_array) {
3639 my $tuser = { NICK => $nick, ID => $uid, AGENT => $agent };
3640 nickserv::kline_user($tuser, services_conf_chankilltime, $reason)
3641 unless adminserv::is_ircop($tuser) or adminserv::is_svsop($tuser, adminserv::S_HELP());
3642 $i++;
3643 }
3644 }
3645
3646 return $i;
3647}
3648
3649sub do_nick_akick($$;$) {
3650 my ($tuser, $chan, $root) = @_;
3651 my $cn = $chan->{CHAN};
3652 unless(defined($root)) {
3653 (undef, $root) = get_best_acc($tuser, $chan, 2);
3654 }
aecfa1fd 3655 $get_nick_akick->execute($cn, $root);
3656 my ($reason) = $get_nick_akick->fetchrow_array(); $get_nick_akick->finish();
aecfa1fd 3657 return 0 if adminserv::is_svsop($tuser, adminserv::S_HELP());
3658 if(defined($reason) && $reason =~ /\|/) {
3659 ($reason, undef) = split(/ ?\| ?/, $reason, 2);
3660 }
3661 kickban($chan, $tuser, undef, "User has been banned from ".$cn.($reason?": $reason":''));
3662}
3663
3664sub check_akick($$;$) {
3665 my ($user, $chan, $check_only) = @_;
aecfa1fd 3666 if(adminserv::is_svsop($user, adminserv::S_HELP())) {
3667 return 0;
3668 }
3669 my ($acc, $root) = get_best_acc($user, $chan, 2);
3670 if ($acc == -1) {
3671 do_nick_akick($user, $chan, $root) unless $check_only;
3672 return 1;
3673 }
3674 my $cn = $chan->{CHAN};
3675 my $uid = get_user_id($user);
3676 unless($acc) {
3677 $get_akick->execute($uid, $cn);
3678 if(my @akick = $get_akick->fetchrow_array) {
3679 akickban($cn, @akick) unless $check_only;
3680 return 1;
3681 }
3682 }
3683 return 0;
3684}
3685
3686sub do_status($$;$) {
3687 my ($user, $chan, $check_only) = @_;
3688
3689 return 0 if cr_chk_flag($chan, (CRF_CLOSE | CRF_DRONE));
3690
3691 my $nick = get_user_nick($user);
3692
3693 if(check_akick($user, $chan, $check_only)) {
3694 return 0;
3695 }
3696 my ($acc, $root) = get_best_acc($user, $chan, 2);
3697 if(!can_do($chan, 'JOIN', $user, { ACC => $acc, NOREPLY => 1 })) {
026939ee 3698 if (!is_agent ($user->{NICK})) {
3699 kickban($chan, $user, undef, 'This is a private channel.')
3700 unless $check_only;
3701 }
aecfa1fd 3702 return 0;
3703 }
3704
3705 if( !$check_only && is_registered($chan) &&
3706 !cr_chk_flag($chan, (CRF_CLOSE | CRF_DRONE)) )
3707 {
3708 my $neverop = (is_neverop_user($user) || cr_chk_flag($chan, CRF_NEVEROP, 1));
3709 my $no_deop = cr_chk_flag($chan, CRF_SPLITOPS, 0);
3710 my $op_anyway = 0;
3711 if($neverop && cr_chk_flag($chan, CRF_AUTOVOICE, 1) && $acc > 2) {
3712 $acc = 2;
3713 $no_deop = 0;
3714 $op_anyway = 1;
3715 }
3716 set_modes($user, $chan, $acc,
3717 # $acc == 3 is +h
3718 # this probably needs to be configurable for ports
3719 # also Unreal may [optionally] set +q on join.
3720 $no_deop,
3721 !$neverop || $op_anyway,
3722 );
3723 }
3724
3725 return 1;
3726}
3727
3728sub akick_alluser($) {
3729 my ($user) = @_;
3730 my $uid = get_user_id($user);
3731
3732 $get_akick_alluser->execute($uid);
3733 while(my @akick = $get_akick_alluser->fetchrow_array) {
3734 akickban(@akick);
3735 }
3736}
3737
3738sub akick_allchan($) {
3739 my ($chan) = @_;
3740 my $cn = $chan->{CHAN};
3741
3742 $get_akick_allchan->execute($cn);
3743 while(my @akick = $get_akick_allchan->fetchrow_array) {
3744 akickban($cn, @akick);
3745 }
3746}
3747
3748sub akickban(@) {
3749 my ($cn, $knick, $bnick, $ident, $host, $reason, $bident) = @_;
aecfa1fd 3750 my $target = { NICK => $knick };
3751 my $chan = { CHAN => $cn };
3752 return 0 if adminserv::is_svsop($target, adminserv::S_HELP());
3753
3754 if($bident) {
3755 ($bnick, $ident, $host) = make_hostmask(get_bantype($chan), $knick, $bident, $host);
3756 } elsif($host =~ /^(\d{1,3}\.){3}\d{1,3}$/) {
3757 ($bnick, $ident, $host) = make_hostmask(4, $knick, $bident, $host);
3758 } else {
3759 $bnick =~ tr/\%\_/\*\?/;
3760 $ident =~ tr/\%\_/\*\?/;
3761 $host =~ tr/\%\_/\*\?/;
3762 }
5e682044 3763 if(defined($reason) && $reason =~ /\|/) {
3764 ($reason, undef) = split(/ ?\| ?/, $reason, 2);
3765 }
aecfa1fd 3766
3767 if(defined($reason) && $reason =~ /\|/) {
3768 ($reason, undef) = split(/ ?\| ?/, $reason, 2);
3769 }
3770
3771 return kickban($chan, $target, "$bnick!$ident\@$host", "User has been banned from ".$cn.($reason?": $reason":''));
3772}
3773
3774sub notice_all_nicks($$$) {
3775 my ($user, $nick, $msg) = @_;
3776 my $src = get_user_nick($user);
3777
3778 notice($user, $msg);
3779 foreach my $u (get_nick_user_nicks $nick) {
92c29160 3780 notice({ NICK => $u, AGENT => $csUser }, $msg) unless lc $src eq lc $u;
aecfa1fd 3781 }
3782}
3783
3784sub xop_byname($) {
3785 my ($name) = @_;
3786 my $level;
3787
3788 if($name =~ /^uop$/i) { $level=1; }
3789 elsif($name =~ /^vop$/i) { $level=2; }
3790 elsif($name =~ /^hop$/i) { $level=3; }
3791 elsif($name =~ /^aop$/i) { $level=4; }
3792 elsif($name =~ /^sop$/i) { $level=5; }
3793 elsif($name =~ /^co?f(ounder)?$/i) { $level=6; }
3794 elsif($name =~ /^founder$/i) { $level=7; }
3795 elsif($name =~ /^(any|all|user)/i) { $level=0; }
3796 elsif($name =~ /^akick$/i) { $level=-1; }
3797 elsif($name =~ /^(none|disabled?|nobody)$/i) { $level=8; }
3798
3799 return $level;
3800}
3801
3802sub expire {
3803 return if services_conf_noexpire;
3804
3805 $get_expired->execute(time() - (86400 * services_conf_chanexpire));
3806 while(my ($cn, $founder) = $get_expired->fetchrow_array) {
3807 drop({ CHAN => $cn });
3808 wlog($csnick, LOG_INFO(), "\002$cn\002 has expired. Founder: $founder");
3809 }
3810}
3811
3812sub enforcer_join($) {
3813 my ($chan) = @_;
3814 my $cn = $chan->{CHAN};
3815 my $bot = agent($chan);
3816
3817 return if $enforcers{lc $cn};
3818 $enforcers{lc $cn} = lc $bot;
3819
3820 botserv::bot_join($chan);
3821
3822 add_timer("CSEnforce $bot $cn", 60, __PACKAGE__, 'chanserv::enforcer_part');
3823}
3824
3825sub enforcer_part($) {
3826 my ($cookie) = @_;
3827 my ($junk, $bot, $cn) = split(/ /, $cookie);
3828
3829 return unless $enforcers{lc $cn};
3830 undef($enforcers{lc $cn});
3831
3832 botserv::bot_part_if_needed($bot, {CHAN => $cn}, 'Enforcer Leaving');
3833}
3834
3835sub fix_private_join_before_id($) {
3836 my ($user) = @_;
3837
3838 my @cns = get_recent_private_chans(get_user_id($user));
3839 foreach my $cn (@cns) {
3840 my $chan = { CHAN => $cn };
3841 unban_user($chan, $user);
3842 }
3843
5e682044 3844 ircd::svsjoin($csUser, $user, @cns) if @cns;
aecfa1fd 3845}
3846
3847### DATABASE UTILITY FUNCTIONS ###
3848
3849sub get_user_count($) {
3850 my ($chan) = @_;
3851 my $cn = $chan->{CHAN};
3852
3853 $get_user_count->execute($cn);
3854
3855 return $get_user_count->fetchrow_array;
3856}
3857
3858sub get_lock($) {
3859 my ($chan) = @_;
3860
3861 $chan = lc $chan;
3862
3863 $chanuser_table++;
3864
3865 if($cur_lock) {
3866 if($cur_lock ne $chan) {
3867 really_release_lock($chan);
3868 $chanuser_table--;
3869 die("Tried to get two locks at the same time: $cur_lock, $chan")
3870 }
3871 $cnt_lock++;
3872 } else {
3873 $cur_lock = $chan;
3874 $get_lock->execute(sql_conf_mysql_db.".chan.$chan");
3875 $get_lock->finish;
3876 }
3877}
3878
3879sub release_lock($) {
3880 my ($chan) = @_;
3881
3882 $chan = lc $chan;
3883
3884 $chanuser_table--;
3885
3886 if($cur_lock and $cur_lock ne $chan) {
3887 really_release_lock($cur_lock);
3888
3889 die("Tried to release the wrong lock");
3890 }
3891
3892 if($cnt_lock) {
3893 $cnt_lock--;
3894 } else {
3895 really_release_lock($chan);
3896 }
3897}
3898
3899sub really_release_lock($) {
3900 my ($chan) = @_;
3901
3902 $cnt_lock = 0;
3903 $release_lock->execute(sql_conf_mysql_db.".chan.$chan");
3904 $release_lock->finish;
3905 undef $cur_lock;
3906}
3907
3908#sub is_free_lock($) {
3909# $is_free_lock->execute($_[0]);
3910# return $is_free_lock->fetchrow_array;
3911#}
3912
3913sub get_modelock($) {
3914 my ($chan) = @_;
3915 my $cn;
3916 if(ref($chan)) {
3917 $cn = $chan->{CHAN}
3918 } else {
3919 $cn = $chan;
3920 }
3921
3922 $get_modelock->execute($cn);
3923 my ($ml) = $get_modelock->fetchrow_array;
3924 $get_modelock->finish();
3925 return $ml;
3926}
3927
3928sub do_modelock($;$) {
3929 my ($chan, $modes) = @_;
3930 my $cn = $chan->{CHAN};
3931
3932 my $seq = $ircline;
3933
3934 $get_modelock_lock->execute; $get_modelock_lock->finish;
3935
3936 $get_chanmodes->execute($cn);
3937 my ($omodes) = $get_chanmodes->fetchrow_array;
3938 my $ml = get_modelock($chan);
3939
3940 $ml = do_modelock_fast($cn, $modes, $omodes, $ml);
3941
3942 $unlock_tables->execute; $unlock_tables->finish;
3943
3944 ircd::setmode(agent($chan), $cn, $ml) if($ml);
3945}
3946
3947sub do_modelock_fast($$$$) {
3948 my ($cn, $modes, $omodes, $ml) = @_;
3949 my $nmodes = modes::add($omodes, $modes, 1);
3950 $ml = modes::diff($nmodes, $ml, 1);
3951 $set_chanmodes->execute(modes::add($nmodes, $ml, 1), $cn);
3952
3953 return $ml;
3954}
3955
3956sub update_modes($$) {
3957 my ($cn, $modes) = @_;
3958
3959 $get_update_modes_lock->execute; $get_update_modes_lock->finish;
3960 $get_chanmodes->execute($cn);
3961 my ($omodes) = $get_chanmodes->fetchrow_array;
3962
3963 $set_chanmodes->execute(modes::add($omodes, $modes, 1), $cn);
3964 $unlock_tables->execute; $unlock_tables->finish;
3965}
3966
3967sub is_level($) {
3968 my ($perm) = @_;
3969
3970 $is_level->execute($perm);
3971
3972 return $is_level->fetchrow_array;
3973}
3974
3975sub is_neverop($) {
3976 return nr_chk_flag($_[0], NRF_NEVEROP(), 1);
3977}
3978
3979sub is_neverop_user($) {
3980 return nr_chk_flag_user($_[0], NRF_NEVEROP(), 1);
3981}
3982
3983sub is_in_chan($$) {
3984 my ($user, $chan) = @_;
3985 my $cn = $chan->{CHAN};
3986 my $uid = get_user_id($user);
3987
3988 $is_in_chan->execute($uid, $cn);
3989 if($is_in_chan->fetchrow_array) {
3990 return 1;
3991 }
3992
3993 return 0;
3994}
3995
3996sub is_registered($) {
3997 my ($chan) = @_;
3998 my $cn = $chan->{CHAN};
3999
4000 $is_registered->execute($cn);
4001 if($is_registered->fetchrow_array) {
4002 return 1;
4003 } else {
4004 return 0;
4005 }
4006}
4007
4008sub get_user_chans($) {
4009 my ($user) = @_;
4010 my $uid = get_user_id($user);
4011 my @chans;
4012
4013 $get_user_chans->execute($uid, $ircline, $ircline+1000);
4014 while(my ($chan) = $get_user_chans->fetchrow_array) {
4015 push @chans, $chan;
4016 }
4017
4018 return (@chans);
4019}
4020
4021sub get_user_chans_recent($) {
4022 my ($user) = @_;
4023 my $uid = get_user_id($user);
4024 my (@curchans, @oldchans);
4025
4026 $get_user_chans_recent->execute($uid);
4027 while(my ($cn, $joined, $op) = $get_user_chans_recent->fetchrow_array) {
4028 if ($joined) {
4029 push @curchans, make_op_prefix($op).$cn;
4030 }
4031 else {
4032 push @oldchans, $cn;
4033 }
4034 }
4035
4036 return (\@curchans, \@oldchans);
4037}
4038
4039my ($prefixes, $modes);
4040sub make_op_prefix($) {
4041 my ($op) = @_;
4042 return unless $op;
4043
4044 unless(defined($prefixes) and defined($modes)) {
4045 $IRCd_capabilities{PREFIX} =~ /^\((\S+)\)(\S+)$/;
4046 ($modes, $prefixes) = ($1, $2);
4047 $modes = reverse $modes;
4048 $prefixes = reverse $prefixes;
4049 }
4050
4051 my $op_prefix = '';
4052 for(my $i = 0; $i < length($prefixes); $i++) {
4053 $op_prefix = substr($prefixes, $i, 1).$op_prefix if ($op & (2**$i));
4054 }
4055 return $op_prefix;
4056}
4057
4058sub get_op($$) {
4059 my ($user, $chan) = @_;
4060 my $cn = $chan->{CHAN};
4061 my $uid = get_user_id($user);
4062
4063 $get_op->execute($uid, $cn);
4064 my ($op) = $get_op->fetchrow_array;
4065
4066 return $op;
4067}
4068
4069sub get_best_acc($$;$) {
4070 my ($user, $chan, $retnick) = @_;
4071 my $uid = get_user_id($user);
4072 my $cn = $chan->{CHAN};
4073
4074 $get_best_acc->execute($uid, $cn);
4075 my ($bnick, $best) = $get_best_acc->fetchrow_array;
4076 $get_best_acc->finish();
4077
4078 if($retnick == 2) {
4079 return ($best, $bnick);
4080 } elsif($retnick == 1) {
4081 return $bnick;
4082 } else {
4083 return $best;
4084 }
4085}
4086
4087sub get_acc($$) {
4088 my ($nick, $chan) = @_;
4089 my $cn = $chan->{CHAN};
4090
4091 return undef
4092 if cr_chk_flag($chan, (CRF_DRONE | CRF_CLOSE | CRF_FREEZE), 1);
4093
4094 $get_acc->execute($cn, $nick);
4095 my ($acc) = $get_acc->fetchrow_array;
4096
4097 return $acc;
4098}
4099
4100sub set_acc($$$$) {
4101 my ($nick, $user, $chan, $level) = @_;
4102 my $cn = $chan->{CHAN};
4103 my $adder;
4104 $adder = get_best_acc($user, $chan, 1) if $user;
4105
4106 $set_acc1->execute($cn, $level, $nick);
4107 $set_acc2->execute($level, $adder, $cn, $nick);
4108
4109 if ( ( $level > 0 and !is_neverop($nick) and !cr_chk_flag($chan, CRF_NEVEROP) )
4110 or $level < 0)
4111 {
4112 set_modes_allnick($nick, $chan, $level);
4113 }
4114}
4115
4116sub del_acc($$) {
4117 my ($nick, $chan) = @_;
4118 my $cn = $chan->{CHAN};
4119
4120 $del_acc->execute($cn, $nick);
4121
4122 foreach my $user (get_nick_users $nick) {
4123 set_modes($user, $chan, 0, 1) if is_in_chan($user, $chan);
4124 }
4125}
4126
4127sub get_auth_nick($$) {
4128 my ($cn, $nick) = @_;
4129
4130 $get_auth_nick->execute($cn, $nick);
4131 my ($data) = $get_auth_nick->fetchrow_array();
4132 $get_auth_nick->finish();
4133
4134 return split(/:/, $data);
4135}
4136sub get_auth_num($$) {
4137 my ($cn, $num) = @_;
4138
4139 $get_auth_num->execute($cn, $num - 1);
4140 my ($nick, $data) = $get_auth_num->fetchrow_array();
4141 $get_auth_num->finish();
4142
4143 return ($nick, split(/:/, $data));
4144}
4145sub find_auth($$) {
4146 my ($cn, $nick) = @_;
4147
4148 $find_auth->execute($cn, $nick);
4149 my ($ret) = $find_auth->fetchrow_array();
4150 $find_auth->finish();
4151
4152 return $ret;
4153}
4154
4155# Only call this if you've checked the user for NEVEROP already.
4156sub set_modes_allchan($;$) {
4157 my ($user, $neverop) = @_;
4158 my $uid = get_user_id($user);
4159
4160 $get_user_chans->execute($uid, $ircline, $ircline+1000);
4161 while(my ($cn) = $get_user_chans->fetchrow_array) {
4162 my $chan = { CHAN => $cn };
4163 my $acc = get_best_acc($user, $chan);
4164 if($acc > 0) {
4165 set_modes($user, $chan, $acc) unless ($neverop or cr_chk_flag($chan, CRF_NEVEROP));
4166 } elsif($acc < 0) {
4167 do_nick_akick($user, $chan);
4168 }
4169 }
4170}
4171
4172# Only call this if you've checked for NEVEROP already.
4173sub set_modes_allnick($$$) {
4174 my ($nick, $chan, $level) = @_;
4175 my $cn = $chan->{CHAN};
4176
4177 $get_using_nick_chans->execute($nick, $cn);
4178 while(my ($n) = $get_using_nick_chans->fetchrow_array) {
4179 my $user = { NICK => $n };
4180 my $l = get_best_acc($user, $chan);
4181 if($l > 0) {
4182 set_modes($user, $chan, $level, 1) if($level == $l);
4183 } elsif($l < 0) {
4184 do_nick_akick($user, $chan);
4185 }
4186 }
4187}
4188
4189# If channel has OPGUARD, $doneg is true.
4190sub set_modes($$$;$$) {
4191 my ($user, $chan, $acc, $doneg, $dopos) = @_;
4192 # can you say eww?
4193 $dopos = 1 unless defined($dopos);
4194 $doneg = 0 unless defined($doneg);
4195 my $cn = $chan->{CHAN};
4196
aecfa1fd 4197 if ($acc < 0) {
4198 # Do akick stuff here.
4199 }
4200
4201 my $dst = ( $acc > 0 ? $ops[$acc] : 0 );
4202 my $cur = get_op($user, $chan);
4203 my ($pos, $neg);
4204
4205 if (cr_chk_flag($chan, CRF_FREEZE)) {
4206 set_mode_mask($user, $chan, $cur, undef);
4207 return;
4208 }
4209 if (($acc == 0) and cr_chk_flag($chan, CRF_AUTOVOICE)) {
4210 set_mode_mask($user, $chan, $cur, 1);
4211 return;
4212 }
4213
4214 $pos = $dst ^ ($dst & $cur);
4215 $neg = ($dst ^ $cur) & $cur if $doneg;
4216
4217 if($pos or $neg) {
4218 set_mode_mask($user, $chan, ($doneg ? $neg : '-'), ($dopos ? $pos : '+'));
4219 }
4220
4221 if($pos) {
4222 set_lastop($cn);
4223 set_lastused($cn, get_user_id($user));
4224 }
4225}
4226
4227sub unset_modes($$) {
4228 my ($user, $chan) = @_;
4229
4230 my $mask = get_op($user, $chan);
4231
4232 set_mode_mask($user, $chan, $mask, 0);
4233}
4234
4235sub set_mode_mask($$$$) {
4236 my ($user, $chan, @masks) = @_;
4237 my $nick = get_user_nick($user);
4238 my $cn = $chan->{CHAN};
4239 my (@args, $out);
4240
4241 for(my $sign; $sign < 2; $sign++) {
4242 next if($masks[$sign] == 0);
4243
4244 $out .= '-' if $sign == 0;
4245 $out .= '+' if $sign == 1;
4246
4247 for(my $i; $i < 5; $i++) {
4248 my @l = ('v', 'h', 'o', 'a', 'q');
026939ee 4249 if ($IRCd_capabilities{"FOUNDER"} eq "") {
4250 $l[4] = 'o';
4251 }
4252 if ($IRCd_capabilities{"ADMIN"} eq "") {
4253 $l[3] = 'o';
4254 }
1eb006d9 4255 if ($IRCd_capabilities{"HALFOP"} eq "") {
4256 $l[2] = "v";
4257 }
aecfa1fd 4258 if($masks[$sign] & 2**$i) {
4259 $out .= $l[$i];
5e682044 4260 my $user_ = { NICK => $nick, AGENT => $csnick};
4261 get_user_id ($user_);
4262 push @args, $user_;
aecfa1fd 4263 }
4264 }
4265 }
4266
4267 if(@args) {
5e682044 4268 ircd::setmode_many(agent($chan), $cn, $out, @args);
aecfa1fd 4269 }
4270}
4271
4272sub get_level($$) {
4273 my ($chan, $perm) = @_;
4274 my $cn = $chan->{CHAN};
4275
4276 $get_level->execute($cn, $perm);
4277 my ($level, $isnotnull) = $get_level->fetchrow_array;
4278 $get_level->finish();
4279
4280 if (wantarray()) {
4281 return ($level, $isnotnull);
4282 }
4283 else {
4284 return $level;
4285 }
4286}
4287
4288sub check_override($$;$) {
4289 my ($user, $perm, $logMsg) = @_;
4290 $perm = uc $perm;
4291
4292 #{OVERRIDE::$perm} produces funny package problems, so wrap it in double-quotes.
4293 if(exists($user->{"OVERRIDE::$perm"}) && (my $nick = $user->{"OVERRIDE::$perm"})) {
4294 if(defined($nick)) {
4295 if(services_conf_log_overrides && $logMsg) {
4296 my $src = get_user_nick($user);
4297 wlog($csnick, LOG_INFO(), "\002$src\002 used override $logMsg");
4298 }
4299 return (wantarray ? ($nick, 1) : $nick);
4300 } else {
4301 return;
4302 }
4303 }
4304 foreach my $o (@override) {
4305 my ($operRank, $permHashRef) = @$o;
4306 if($permHashRef->{$perm} and my $nick = adminserv::can_do($user, $operRank)) {
4307 $user->{"OVERRIDE::$perm"} = $nick;
4308 if(services_conf_log_overrides && $logMsg) {
4309 my $src = get_user_nick($user);
4310 wlog($csnick, LOG_INFO(), "\002$src\002 used override $logMsg");
4311 }
4312 return (wantarray ? ($nick, 1) : $nick);
4313 }
4314 }
4315 $user->{"OVERRIDE::$perm"} = undef;
4316}
4317
4318sub can_do($$$;$) {
4319 my ($chan, $perm, $user, $data) = @_;
4320 $data = {} unless defined $data;
4321 # $data is a hashref/struct
4322 my $noreply = $data->{NOREPLY};
4323 my $acc = $data->{ACC};
4324 my $overrideMsg = $data->{OVERRIDE_MSG};
08e4295b 4325
aecfa1fd 4326 if(my $nick = __can_do($chan, $perm, $user, $acc)) {
4327 # This is becoming increasingly complicated
4328 # and checking if an override was used is becoming tricky.
4329 # We had a case in cs_kick where an oper should be able to override +Q/$peace
4330 # but cannot b/c they have regular access in that channel.
4331 my $override;
4332 if(defined($user)) {
4333 (undef, $override) = check_override($user, $perm);
4334 }
4335 return (wantarray ? ($nick, $override) : $nick);
4336 } elsif ( $user and adminserv::is_svsop($user, adminserv::S_HELP()) ) {
4337 #set_lastused($cn, get_user_id($user));
4338 my ($nick, $override) = check_override($user, $perm, $overrideMsg);
4339 return (wantarray ? ($nick, $override) : $nick) if $override;
4340 }
4341 if($user and !$noreply) {
4342 my $cn = $chan->{CHAN};
4343 if (cr_chk_flag($chan, (CRF_CLOSE | CRF_DRONE))) {
4344 notice($user, "\002$cn\002 is closed and cannot be used".
4345 ((uc $perm eq 'INFO') ? ': '.get_close($chan) : '.'));
4346 }
4347 elsif(cr_chk_flag($chan, CRF_FREEZE)) {
4348 notice($user, "\002$cn\002 is frozen and access suspended.");
4349 }
4350 else {
4351 notice($user, "$cn: $err_deny");
4352 }
4353 }
4354 return 0;
4355}
4356
4357sub __can_do($$$;$) {
4358 my ($chan, $perm, $user, $acc) = @_;
4359 my $nick;
4360 my $cn = $chan->{CHAN};
4361 $perm = uc $perm;
08e4295b 4362
aecfa1fd 4363 my $level;
4364 unless(exists($chan->{"PERM::$perm"})) {
4365 $level = $chan->{"PERM::$perm"} = get_level($chan, $perm);
4366 } else {
4367 $level = $chan->{"PERM::$perm"};
4368 }
4369
4370 unless(defined($acc)) {
4371 unless (defined $user && ref($user) eq 'HASH') {
4372 die "invalid __can_do call";
4373 }
4374 my $chanuser = $user->{lc $cn};
4375 unless (defined($chanuser) && exists($chanuser->{ACC})) {
4376 ($acc, $nick) = get_best_acc($user, $chan, 2);
4377 ($chanuser->{ACC}, $chanuser->{ACCNICK}) = ($acc, $nick);
4378 } else {
4379 ($acc, $nick) = ($chanuser->{ACC}, $chanuser->{ACCNICK});
4380 }
4381 }
4382 $nick = 1 unless $nick;
08e4295b 4383
aecfa1fd 4384 if($acc >= $level and !cr_chk_flag($chan, (CRF_CLOSE | CRF_FREEZE | CRF_DRONE))) {
4385 set_lastused($cn, get_user_id($user)) if $user;
4386 return (wantarray ? ($nick, 0) : $nick);
4387 }
4388
4389 if(cr_chk_flag($chan, CRF_FREEZE) and ($perm eq 'JOIN')) {
4390 return (wantarray ? ($nick, 0) : $nick);
4391 }
4392
4393 return 0;
4394}
4395
4396sub can_keep_op($$$$) {
4397