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