]> jfr.im git - irc/SurrealServices/srsv.git/blob - branches/erry-devel/modules/serviceslibs/operserv.pm
initial commit of erry's Insp work.
[irc/SurrealServices/srsv.git] / branches / erry-devel / modules / serviceslibs / operserv.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 operserv;
17
18 use strict;
19
20 use SrSv::Timer qw(add_timer);
21
22 use SrSv::IRCd::State qw(get_server_state);
23 use SrSv::IRCd::Validate qw( valid_server valid_nick );
24
25 use SrSv::Time;
26 use SrSv::Text::Format qw(columnar);
27 use SrSv::Errors;
28 use SrSv::Log;
29
30 use SrSv::Conf2Consts qw(main services);
31
32 use SrSv::User qw(get_user_nick get_user_id get_user_agent is_online get_user_info :flood);
33 use SrSv::User::Notice;
34 use SrSv::Help qw( sendhelp );
35
36 use SrSv::NickReg::Flags qw(NRF_NOHIGHLIGHT nr_chk_flag_user);
37
38 use SrSv::MySQL '$dbh';
39
40 use constant {
41 MAX_LIM => 16777215
42 };
43
44 *kill_user = \&nickserv::kill_user;
45
46 our $osnick_default = 'OperServ';
47 our $osnick = $osnick_default;
48
49 my %newstypes = (
50 u => 'User',
51 o => 'Oper'
52 );
53
54 =cut
55 $add_akill, $del_akill, $get_all_akills, $get_expired_akills,
56 $get_akill, $check_akill,
57 =cut
58
59 our (
60 $add_qline, $del_qline, $get_all_qlines, $get_expired_qlines,
61 $get_qline, $check_qline,
62
63 $add_logonnews, $del_logonnews, $list_logonnews, $get_logonnews,
64 $consolidate_logonnews, $count_logonnews, $del_expired_logonnews,
65
66 $add_clone_exceptname, $add_clone_exceptserver, $add_clone_exceptip,
67 $del_clone_exceptname, $del_clone_exceptip,
68 $list_clone_exceptname, $list_clone_exceptserver, $list_clone_exceptip,
69
70 $get_clones_fromhost, $get_clones_fromnick, $get_clones_fromid, $get_clones_fromipv4,
71
72 $get_session_list
73 );
74
75 sub init() {
76 =cut
77 $add_akill = $dbh->prepare("INSERT INTO akill SET setter=?, mask=?, reason=?, time=?, expire=?");
78 $del_akill = $dbh->prepare("DELETE FROM akill WHERE mask=?");
79 $get_all_akills = $dbh->prepare("SELECT setter, mask, reason, time, expire FROM akill ORDER BY time ASC");
80 $get_akill = $dbh->prepare("SELECT setter, mask, reason, time, expire FROM akill WHERE mask=?");
81 $check_akill = $dbh->prepare("SELECT 1 FROM akill WHERE mask=?");
82
83 $get_expired_akills = $dbh->prepare("SELECT setter, mask, reason, time, expire FROM akill WHERE expire < UNIX_TIMESTAMP() AND expire!=0");
84 =cut
85
86 $add_qline = $dbh->prepare("INSERT INTO qline SET setter=?, mask=?, reason=?, time=?, expire=?");
87 $del_qline = $dbh->prepare("DELETE FROM qline WHERE mask=?");
88 $get_all_qlines = $dbh->prepare("SELECT setter, mask, reason, time, expire FROM qline ORDER BY time ASC");
89 $get_qline = $dbh->prepare("SELECT setter, mask, reason, time, expire FROM qline WHERE mask=?");
90 $check_qline = $dbh->prepare("SELECT 1 FROM qline WHERE mask=?");
91
92 $get_expired_qlines = $dbh->prepare("SELECT mask FROM qline WHERE expire < UNIX_TIMESTAMP() AND expire!=0");
93
94 $add_logonnews = $dbh->prepare("INSERT INTO logonnews SET setter=?, expire=?, type=?, id=?, msg=?, time=UNIX_TIMESTAMP()");
95 $del_logonnews = $dbh->prepare("DELETE FROM logonnews WHERE type=? AND id=?");
96 $list_logonnews = $dbh->prepare("SELECT setter, time, expire, id, msg FROM logonnews WHERE type=? ORDER BY id ASC");
97 $get_logonnews = $dbh->prepare("SELECT setter, time, msg FROM logonnews WHERE type=? ORDER BY id ASC");
98 $consolidate_logonnews = $dbh->prepare("UPDATE logonnews SET id=id-1 WHERE type=? AND id>?");
99 $count_logonnews = $dbh->prepare("SELECT COUNT(*) FROM logonnews WHERE type=?");
100 $del_expired_logonnews = $dbh->prepare("DELETE FROM logonnews WHERE expire < UNIX_TIMESTAMP() AND expire!=0");
101
102 $add_clone_exceptname = $dbh->prepare("REPLACE INTO sesexname SET host=?, serv=0, adder=?, lim=?");
103 $add_clone_exceptserver = $dbh->prepare("REPLACE INTO sesexname SET host=?, serv=1, adder=?, lim=?");
104 $add_clone_exceptip = $dbh->prepare("REPLACE INTO sesexip SET ip=INET_ATON(?), mask=?, adder=?, lim=?");
105
106 $del_clone_exceptname = $dbh->prepare("DELETE FROM sesexname WHERE host=?");
107 $del_clone_exceptip = $dbh->prepare("DELETE FROM sesexip WHERE ip=INET_ATON(?)");
108
109 $list_clone_exceptname = $dbh->prepare("SELECT host, adder, lim FROM sesexname WHERE serv=0 ORDER BY host ASC");
110 $list_clone_exceptserver = $dbh->prepare("SELECT host, adder, lim FROM sesexname WHERE serv=1 ORDER BY host ASC");
111 $list_clone_exceptip = $dbh->prepare("SELECT INET_NTOA(ip), mask, adder, lim FROM sesexip ORDER BY ip ASC");
112
113 $get_clones_fromhost = $dbh->prepare("SELECT user.nick, user.id, user.online
114 FROM user JOIN user AS clone ON (user.ip=clone.ip)
115 WHERE clone.host=? GROUP BY id");
116 $get_clones_fromnick = $dbh->prepare("SELECT user.nick, user.id, user.online
117 FROM user JOIN user AS clone ON (user.ip=clone.ip)
118 WHERE clone.nick=? GROUP BY id");
119 $get_clones_fromid = $dbh->prepare("SELECT user.nick, user.id, user.online
120 FROM user JOIN user AS clone ON (user.ip=clone.ip)
121 WHERE clone.id=? GROUP BY id");
122 $get_clones_fromipv4 = $dbh->prepare("SELECT user.nick, user.id, user.online
123 FROM user JOIN user AS clone ON (user.ip=clone.ip)
124 WHERE clone.ip=INET_ATON(?) GROUP BY id");
125
126 $get_session_list = $dbh->prepare("SELECT host, COUNT(*) AS c FROM user WHERE online=1 GROUP BY host HAVING c >= ?");
127 }
128
129 sub dispatch($$$) {
130 my ($src, $dst, $msg) = @_;
131 $msg =~ s/^\s+//;
132 my @args = split(/\s+/, $msg);
133 my $cmd = shift @args;
134
135 my $user = { NICK => $src, AGENT=> $dst };
136
137 services::ulog($osnick, LOG_INFO(), "cmd: [$msg]", $user);
138
139 return if flood_check($user);
140 unless(adminserv::is_svsop($user) or adminserv::is_ircop($user)) {
141 notice($user, $err_deny);
142 ircd::globops($osnick, "\002$src\002 failed access to $osnick $msg");
143 return;
144 }
145
146 if ($cmd =~ /^fjoin$/i) { os_fjoin($user, @args); }
147 elsif ($cmd =~ /^fpart$/i) { os_fpart($user, @args); }
148 elsif ($cmd =~ /^unidentify$/i) { os_unidentify($user, @args); }
149 elsif ($cmd =~ /^qline$/i) {
150 my $cmd2 = shift @args;
151
152 if($cmd2 =~ /^add$/i) {
153 if(@args >= 3 and $args[0] =~ /^\+/) {
154 @args = split(/\s+/, $msg, 5);
155
156 os_qline_add($user, $args[2], $args[3], $args[4]);
157 }
158 elsif(@args >= 2) {
159 @args = split(/\s+/, $msg, 4);
160
161 os_qline_add($user, 0, $args[2], $args[3]);
162 }
163 else {
164 notice($user, 'Syntax: QLINE ADD [+expiry] <mask> <reason>');
165 }
166 }
167 elsif($cmd2 =~ /^del$/i) {
168 if(@args == 1) {
169 os_qline_del($user, $args[0]);
170 }
171 else {
172 notice($user, 'Syntax: QLINE DEL <mask>');
173 }
174 }
175 elsif($cmd2 =~ /^list$/i) {
176 if(@args == 0) {
177 os_qline_list($user);
178 }
179 else {
180 notice($user, 'Syntax: QLINE LIST');
181 }
182 }
183 }
184 elsif ($cmd =~ /^jupe$/i) {
185 if(@args >= 2) {
186 os_jupe($user, shift @args, join(' ', @args));
187 }
188 else {
189 notice($user, 'Syntax: JUPE <server> <reason>');
190 }
191 }
192 elsif ($cmd =~ /^uinfo$/i) { os_uinfo($user, @args); }
193 elsif ($cmd =~ /^ninfo$/i) { os_ninfo($user, @args); }
194 elsif ($cmd =~ /^svsnick$/i) { os_svsnick($user, $args[0], $args[1]); }
195 elsif ($cmd =~ /^gnick$/i) { os_gnick($user, @args); }
196 elsif ($cmd =~ /^help$/i) { sendhelp($user, 'operserv', @args) }
197 elsif ($cmd =~ /^(staff|listadm)$/i) { adminserv::as_staff($user) }
198 elsif ($cmd =~ /^logonnews$/i) {
199 my $cmd2 = shift @args;
200
201 if($cmd2 =~ /^add$/i) {
202 if(@args >= 3 and $args[1] =~ /^\+/) {
203 @args = split(/\s+/, $msg, 5);
204
205 os_logonnews_add($user, $args[2], $args[3], $args[4]);
206 }
207 elsif(@args >= 2) {
208 @args = split(/\s+/, $msg, 4);
209
210 os_logonnews_add($user, $args[2], 0, $args[3]);
211 }
212 else {
213 notice($user, 'Syntax: LOGONNEWS ADD <type> [+expiry] <reason>');
214 }
215 }
216 elsif($cmd2 =~ /^del$/i) {
217 if(@args == 2) {
218 os_logonnews_del($user, $args[0], $args[1]);
219 }
220 else {
221 notice($user, 'Syntax: LOGONNEWS DEL <type> <id>');
222 }
223 }
224 elsif($cmd2 =~ /^list$/i) {
225 if(@args == 1) {
226 os_logonnews_list($user, $args[0]);
227 }
228 else {
229 notice($user, 'Syntax: LOGONNEWS LIST <type>');
230 }
231 }
232 else {
233 notice($user, 'Syntax: LOGONNEWS <LIST|ADD|DEL> <type>');
234 }
235 }
236 elsif($cmd =~ /^except(ion)?$/i) {
237 my $cmd2 = shift @args;
238 if($cmd2 =~ /^server$/i) {
239 my $cmd3 = shift @args;
240 if($cmd3 =~ /^a(dd)?$/) {
241 if(@args == 2) {
242 os_except_server_add($user, $args[0], $args[1]);
243 }
244 else {
245 notice($user, 'Syntax EXCEPT SERVER ADD <hostname> <limit>');
246 }
247 }
248 elsif($cmd =~ /^d(el)?$/) {
249 if(@args == 1) {
250 os_except_server_del($user, $args[0]);
251 }
252 else {
253 notice($user, 'Syntax EXCEPT SERVER DEL <hostname>');
254 }
255 }
256 else {
257 notice($user, 'Syntax EXCEPT SERVER <ADD|DEL>');
258 }
259 }
260 elsif($cmd2 =~ /^h(ostname)?$/i) {
261 my $cmd3 = shift @args;
262 if($cmd3 =~ /^a(dd)?$/) {
263 if(@args == 2) {
264 os_except_hostname_add($user, $args[0], $args[1]);
265 }
266 else {
267 notice($user, 'Syntax EXCEPT HOSTNAME ADD <hostname> <limit>');
268 }
269 }
270 elsif($cmd3 =~ /^d(el)?$/) {
271 if(@args == 1) {
272 os_except_hostname_del($user, $args[0]);
273 }
274 else {
275 notice($user, 'Syntax EXCEPT HOSTNAME DEL <hostname>');
276 }
277 }
278 else {
279 notice($user, 'Syntax EXCEPT HOSTNAME <ADD|DEL>');
280 }
281 }
282 elsif($cmd2 =~ /^i(p)?$/i) {
283 my $cmd3 = shift @args;
284 if($cmd3 =~ /^a(dd)?$/) {
285 if(@args == 2) {
286 os_except_IP_add($user, $args[0], $args[1]);
287 }
288 else {
289 notice($user, 'Syntax EXCEPT IP ADD <IP/mask> <limit>');
290 }
291 }
292 elsif($cmd3 =~ /^d(el)?$/) {
293 if(@args == 1) {
294 os_except_IP_del($user, $args[0]);
295 }
296 else {
297 notice($user, 'Syntax EXCEPT IP DEL <IP>');
298 }
299 }
300 else {
301 notice($user, 'Syntax EXCEPT IP <ADD|DEL>');
302 }
303 }
304 elsif($cmd2 =~ /^l(ist)?$/i) {
305 if(@args == 0) {
306 os_except_list($user);
307 }
308 else {
309 notice($user, 'Syntax EXCEPT LIST');
310 }
311 }
312 else {
313 notice($user, 'Syntax: EXCEPT <SERVER|HOSTNAME|IP|LIST>');
314 }
315 }
316 elsif($cmd =~ /^session$/i) {
317 if(@args == 1) {
318 os_session_list($user, $args[0]);
319 } else {
320 notice($user, 'Syntax SESSION <lim>');
321 }
322 }
323 elsif($cmd =~ /^chankill$/i) {
324 if(@args >= 2) {
325 (undef, @args) = split(/\s+/, $msg, 3);
326 os_chankill($user, @args);
327 } else {
328 notice($user, 'Syntax: CHANKILL <#chan> <reason>');
329 }
330 }
331 elsif ($cmd =~ /^rehash$/i) {
332 if(@args <= 1) {
333 os_rehash($user, @args);
334 }
335 else {
336 notice($user, 'Syntax: REHASH [type]');
337 }
338 }
339 elsif ($cmd =~ /^loners$/i) {
340 os_loners($user, @args);
341 }
342 elsif($cmd =~ /^svskill$/i) {
343 if(@args >= 2) {
344 os_svskill($user, shift @args, join(' ', @args));
345 }
346 else {
347 notice($user, 'Syntax SVSKILL <target> <reason here>');
348 }
349 }
350 elsif($cmd =~ /^kill$/i) {
351 if(@args >= 1) {
352 os_kill($user, shift @args, join(' ', @args));
353 }
354 else {
355 notice($user, 'Syntax KILL <target> <reason here>');
356 }
357 }
358 elsif ($cmd =~ /^clones$/i) {
359 os_clones($user, @args);
360 }
361 elsif ($cmd =~ /^m(ass)?kill$/i) {
362 os_clones($user, 'KILL', @args);
363 }
364 elsif($cmd =~ /^(kline|gline)$/i) {
365 if(@args >= 1) {
366 os_gline($user, 0, @args);
367 }
368 else {
369 notice($user, 'Syntax GLINE <target> [+time] [reason here]');
370 }
371 }
372 elsif($cmd =~ /^(zline|gzline)$/i) {
373 if(@args >= 1) {
374 os_gline($user, 1, @args);
375 }
376 else {
377 notice($user, 'Syntax GZLINE <target> [+time] [reason here]');
378 }
379 }
380
381 else { notice($user, "Unknown command."); }
382 }
383
384 sub os_fjoin($$@) {
385 my ($user, $target, @chans) = @_;
386 if ((!$target or !@chans) or !($chans[0] =~ /^#/)) {
387 notice($user, "Syntax: /OS FJOIN <nick> <#channel1> [#channel2]");
388 }
389 unless (is_online($target)) {
390 notice($user, "\002$target\002 is not online");
391 return;
392 }
393
394 if (!adminserv::can_do($user, 'FJOIN')) {
395 notice($user, "You don't have the right access");
396 return $event::SUCCESS;
397 }
398 ircd::svsjoin($osnick, $target, @chans);
399 }
400
401 sub os_fpart($$@) {
402 my ($user, $target, @params) = @_;
403 if ((!$target or !@params) or !($params[0] =~ /^#/)) {
404 notice($user, "Syntax: /OS FPART <nick> <#channel1> [#channel2] [reason]");
405 }
406 unless (is_online($target)) {
407 notice($user, "\002$target\002 is not online");
408 return;
409 }
410
411 if (!adminserv::can_do($user, 'FJOIN')) {
412 notice($user, "You don't have the right access");
413 return $event::SUCCESS;
414 }
415
416 my ($reason, @chans);
417 while ($params[0] =~ /^#/) {
418 push @chans, shift @params;
419 }
420 $reason = join(' ', @params) if @params;
421
422 ircd::svspart($osnick, $target, $reason, @chans);
423 }
424
425 sub os_qline_add($$$$) {
426 my ($user, $expiry, $mask, $reason) = @_;
427
428 chk_auth($user, 'QLINE') or return;
429
430 $expiry = parse_time($expiry);
431 if($expiry) { $expiry += time() }
432 else { $expiry = 0 }
433
434 $check_qline->execute($mask);
435 if ($check_qline->fetchrow_array) {
436 notice($user, "$mask is already qlined");
437 return $event::SUCCESS;
438 } else {
439 my $src = get_user_nick($user);
440 $add_qline->execute($src, $mask, $reason, time(), $expiry);
441 ircd::sqline($mask, $reason);
442 notice($user, "$mask is now Q:lined");
443 }
444 }
445
446 sub os_qline_del($$) {
447 my($user, $mask) = @_;
448
449 chk_auth($user, 'QLINE') or return;
450
451 $check_qline->execute($mask);
452 if($check_qline->fetchrow_array) {
453 $del_qline->execute($mask);
454 ircd::unsqline($mask);
455 notice($user, "$mask unqlined");
456 } else {
457 notice($user, "$mask is not qlined");
458 }
459 }
460
461 sub os_qline_list($) {
462 my ($user) = @_;
463 my (@reply);
464
465 chk_auth($user, 'QLINE') or return;
466
467 push @reply, 'Q:line list:';
468
469 $get_all_qlines->execute();
470 my $i;
471 while (my ($setter, $mask, $reason, $time, $expiry) = $get_all_qlines->fetchrow_array) {
472 $i++;
473 my $akill_entry1 = " $i) \002$mask\002 $reason";
474 my $akill_entry2 = " set by $setter on ".gmtime2($time).'; ';
475 if($expiry) {
476 my ($weeks, $days, $hours, $minutes, $seconds) = split_time($expiry-time());
477 $akill_entry2 .= "Expires in ".($weeks?"$weeks weeks ":'').
478 ($days?"$days days ":'').
479 ($hours?"$hours hours ":'').
480 ($minutes?"$minutes minutes ":'');
481 }
482 else {
483 $akill_entry2 .= "Does not expire.";
484 }
485 push @reply, $akill_entry1; push @reply, $akill_entry2;
486 }
487 $get_all_qlines->finish();
488 push @reply, ' --';
489
490 notice($user, @reply) if @reply;
491 }
492
493 sub os_jupe($$$) {
494 # introduces fake server to network.
495 my ($user, $server, $reason) = @_;
496
497 unless (adminserv::is_svsop($user, adminserv::S_ROOT())) {
498 notice($user, $err_deny);
499 return $event::SUCCESS;
500 }
501 unless (valid_server($server)) {
502 notice($user, "$server is not a valid servername.");
503 return $event::SUCCESS;
504 }
505 if (get_server_state($server)) {
506 notice($user, "$server is currently connected. You must SQUIT before using JUPE.");
507 return $event::SUCCESS;
508 }
509
510 ircd::jupe_server($server, "Juped by ".get_user_nick($user).": $reason");
511 notice($user, "$server is now juped.");
512 return $event::SUCCESS;
513 }
514
515 sub os_unidentify($$) {
516 my ($user, $tnick) = @_;
517
518 my $tuser = { NICK => $tnick };
519 my $tuid;
520
521 unless ($tuid = get_user_id($tuser)) {
522 notice($user, "\002$tnick\002 is not online");
523 }
524 unless (adminserv::can_do($user, 'SERVOP')) {
525 notice($user, $err_deny);
526 }
527 $nickserv::logout->execute($tuid);
528 notice($user, "$tnick logged out from all nick identifies");
529 }
530
531 sub os_uinfo($@) {
532 my ($user, @targets) = @_;
533
534 my @userlist;
535 foreach my $target (@targets) {
536 if($target =~ /\,/) {
537 push @targets, split(',', $target);
538 next;
539 }
540 my @data;
541 my $tuser = { NICK => $target };
542 my $tuid = get_user_id($tuser);
543 unless ($tuid) {
544 notice($user, "\002$target\002: user not found");
545 next;
546 }
547 push @userlist, $tuser;
548 }
549
550 notice($user, get_uinfo($user, @userlist));
551 return $event::SUCCESS;
552 }
553
554 sub os_ninfo($@) {
555 my ($user, @targetsIn) = @_;
556
557 my @targetsOut;
558 foreach my $target (@targetsIn) {
559 if(not nickserv::is_registered($target)) {
560 notice($user, "\002$target\002: is not registered.");
561 }
562 my @targets = nickserv::get_nick_user_nicks($target);
563 if(scalar(@targets) == 0) {
564 notice($user, "\002$target\002: no user[s] online.");
565 next;
566 }
567 push @targetsOut, @targets;
568 }
569 if(scalar(@targetsOut)) {
570 return os_uinfo($user, @targetsOut);
571 }
572 return $event::SUCCESS;
573 }
574
575 sub os_svsnick($$$) {
576 my ($user, $curnick, $newnick) = @_;
577 my $tuser = { NICK => $curnick };
578
579 if(!adminserv::is_svsop($user, adminserv::S_ROOT())) {
580 notice($user, $err_deny);
581 return $event::SUCCESS;
582 }
583 if ((!$curnick) or (!$newnick)) {
584 notice($user, "Syntax: SVSNICK <curnick> <newnick>");
585 return $event::SUCCESS;
586 }
587 if (!is_online($tuser)) {
588 notice($user, $curnick.' is not online.');
589 return $event::SUCCESS;
590 }
591 if (nickserv::is_online($newnick)) {
592 notice($user, $newnick.' already exists.');
593 return $event::SUCCESS;
594 }
595 nickserv::enforcer_quit($newnick);
596 ircd::svsnick($osnick, $curnick, $newnick);
597 notice($user, $curnick.' changed to '.$newnick);
598 return $event::SUCCESS;
599 }
600
601 sub os_gnick($@) {
602 my ($user, @targets) = @_;
603
604 if(!adminserv::can_do($user, 'QLINE')) {
605 notice($user, $err_deny);
606 return $event::SUCCESS;
607 }
608 if (@targets == 0) {
609 notice($user, "Syntax: GNICK <nick>");
610 return $event::SUCCESS;
611 }
612 foreach my $target (@targets) {
613 if (!is_online($target)) {
614 notice($user, $target.' is not online.');
615 next;
616 }
617 my $newnick = nickserv::collide($target);
618 notice($user, $target.' changed to '.$newnick);
619 }
620 return $event::SUCCESS;
621 }
622
623 sub os_logonnews_pre($$) {
624 my ($user, $type) = @_;
625
626 unless(adminserv::is_svsop($user, adminserv::S_ADMIN())) {
627 notice($user, $err_deny);
628 return undef;
629 }
630
631 return 'u' if($type =~ /^(user)|(u)$/i);
632 return 'o' if($type =~ /^(oper)|(o)$/i);
633 notice($user, 'invalid LOGONNEWS <type>');
634 return undef;
635 }
636
637 sub os_logonnews_add($$$) {
638 my ($user, $type, $expiry, $msg) = @_;
639
640 return unless ($type = os_logonnews_pre($user, $type));
641
642 my $mlength = length($msg);
643 if($mlength >= 350) {
644 notice($user, 'Message is too long by '. $mlength-350 .' character(s). Maximum length is 350 chars');
645 return;
646 }
647
648 if($expiry) {
649 $expiry = parse_time($expiry);
650 }
651 else {
652 $expiry = 0;
653 }
654
655 my $src = get_user_nick($user);
656 $count_logonnews->execute($type);
657 my $count = $count_logonnews->fetchrow_array;
658
659 $add_logonnews->execute($src, $expiry ? time()+$expiry : 0, $type, ++$count, $msg);
660
661 notice($user, "Added new $newstypes{$type} News #\002$count\002");
662 }
663
664 sub os_logonnews_del($$$) {
665 my ($user, $type, $id) = @_;
666
667 return unless ($type = os_logonnews_pre($user, $type));
668
669 my $ret = $del_logonnews->execute($type, $id);
670
671 if ($ret == 1) {
672 notice($user, "News Item $newstypes{$type} News #\002$id\002 deleted");
673 $consolidate_logonnews->execute($type, $id);
674 }
675 else {
676 notice($user, "Delete of $newstypes{$type} News #\002$id\002 failed.",
677 "$newstypes{$type} #\002$id\002 does not exist?");
678 }
679 }
680
681 sub os_logonnews_list($$) {
682 my ($user, $type) = @_;
683
684 return unless ($type = os_logonnews_pre($user, $type));
685
686 my @reply;
687 push @reply, "\002$newstypes{$type}\002 News";
688
689 $list_logonnews->execute($type);
690 push @reply, "There is no $newstypes{$type} News"
691 unless($list_logonnews->rows);
692 while(my ($adder, $time, $expiry, $id, $msg) = $list_logonnews->fetchrow_array) {
693 my ($weeks, $days, $hours, $minutes, $seconds) = split_time($expiry-time());
694 my $expire_string = ($expiry?"Expires in ".($weeks?"$weeks weeks ":'').
695 ($days?"$days days ":'').
696 ($hours?"$hours hours ":'').
697 ($minutes?"$minutes minutes ":'')
698 :'Does not expire');
699 push @reply, "$id\) $msg";
700 push @reply, join(' ', '', 'added: '.gmtime2($time), $expire_string, "added by: $adder");
701 }
702 $list_logonnews->finish();
703 notice($user, @reply);
704 }
705
706 sub os_except_pre($) {
707 my ($user) = @_;
708
709 if (adminserv::is_svsop($user, adminserv::S_ADMIN()) ) {
710 return 1;
711 }
712 else {
713 notice($user, $err_deny);
714 return 0;
715 }
716 }
717
718 sub os_except_hostname_add($$$) {
719 my ($user, $hostname, $limit) = @_;
720
721 os_except_pre($user) or return 0;
722
723 if ($hostname =~ m/\@/ or not $hostname =~ /\./) {
724 notice($user, 'Invalid hostmask.', 'A clone exception hostmask is the HOST portion only, no ident',
725 'and must contain at least one dot \'.\'');
726 return;
727 }
728
729 $limit = MAX_LIM() unless $limit;
730
731 my $src = get_user_nick($user);
732 my $hostmask = $hostname;
733 $hostmask =~ s/\*/\%/g;
734 $add_clone_exceptname->execute($hostmask, $src, $limit);
735 notice($user, "Clone exception for host \002$hostname\002 added.");
736 }
737
738 sub os_except_server_add($$$) {
739 my ($user, $hostname, $limit) = @_;
740
741 os_except_pre($user) or return 0;
742
743 if ($hostname =~ m/\@/ or not $hostname =~ /\./) {
744 notice($user, 'Invalid hostmask.', 'A clone exception servername has no ident',
745 'and must contain at least one dot \'.\'');
746 return;
747 }
748
749 $limit = MAX_LIM() unless $limit;
750
751 my $src = get_user_nick($user);
752 my $hostmask = $hostname;
753 $hostmask =~ s/\*/\%/g;
754 $add_clone_exceptserver->execute($hostmask, $src, $limit);
755 notice($user, "Clone exception for server \002$hostname\002 added.");
756 }
757
758 sub os_except_IP_add($$$$) {
759 my ($user, $IP, $limit) = @_;
760
761 os_except_pre($user) or return 0;
762
763 my $mask;
764 ($IP, $mask) = split(/\//, $IP);
765 $mask = 32 unless $mask;
766 if ($IP =~ m/\@/ or not $IP =~ /\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}/) {
767 notice($user, 'Invalid hostmask.', 'A clone exception IP has no ident',
768 'and must be a valid IP address with 4 octets (example: 1.2.3.4)');
769 return;
770 }
771
772 $limit = MAX_LIM() unless $limit;
773
774 my $src = get_user_nick($user);
775 $add_clone_exceptip->execute($IP, $mask, $src, $limit);
776 notice($user, "IP clone exception \002$IP\/$mask\002 added.");
777 }
778
779 sub os_except_hostname_del($$) {
780 my ($user, $hostname) = @_;
781
782 os_except_pre($user) or return 0;
783
784 my $hostmask = $hostname;
785 $hostmask =~ s/\*/\%/g;
786 my $ret = $del_clone_exceptname->execute($hostmask);
787 ircd::notice($osnick, main_conf_diag, "hostname: $hostname; hostmask: $hostmask");
788
789 if($ret == 1) {
790 notice($user, "\002$hostname\002 successfully deleted from the hostname exception list");
791 }
792 else {
793 notice($user, "Deletion of \002$hostname\002 \037failed\037. \002$hostname\002 entry does not exist?");
794 }
795 }
796
797 sub os_except_server_del($$) {
798 my ($user, $hostname) = @_;
799
800 os_except_pre($user) or return 0;
801
802 my $hostmask = $hostname;
803 $hostmask =~ s/\*/\%/g;
804 my $ret = $del_clone_exceptname->execute($hostmask);
805
806 if($ret == 1) {
807 notice($user, "\002$hostname\002 successfully deleted from the server exception list");
808 }
809 else {
810 notice($user, "Deletion of \002$hostname\002 \037failed\037. \002$hostname\002 entry does not exist?");
811 }
812 }
813
814 sub os_except_IP_del($$$) {
815 my ($user, $IP) = @_;
816
817 os_except_pre($user) or return 0;
818
819 no warnings 'misc';
820 my ($IP, $mask) = split(/\//, $IP);
821 $mask = 32 unless $mask;
822 my $ret = $del_clone_exceptip->execute($IP);
823
824 if($ret == 1) {
825 notice($user, "\002$IP/$mask\002 successfully deleted from the IP exception list");
826 }
827 else {
828 notice($user, "Deletion of \002$IP/$mask\002 \037failed\037. \002$IP/$mask\002 entry does not exist?");
829 }
830 }
831
832 sub os_except_list($) {
833 my ($user) = @_;
834 my @data;
835
836 $list_clone_exceptserver->execute();
837 while(my ($host, $adder, $lim) = $list_clone_exceptserver->fetchrow_array) {
838 $host =~ s/\%/\*/g;
839 push @data, ['Server:', $host, $lim!=MAX_LIM()?$lim:'unlimited', "($adder)"];
840 }
841
842 $list_clone_exceptname->execute();
843 while(my ($host, $adder, $lim) = $list_clone_exceptname->fetchrow_array) {
844 $host =~ s/\%/\*/g;
845 push @data, ['Host:', $host, $lim!=MAX_LIM()?$lim:'unlimited', "($adder)"];
846 }
847
848 $list_clone_exceptip->execute();
849 while(my ($ip, $mask, $adder, $lim) = $list_clone_exceptip->fetchrow_array) {
850 push @data, ['IP:', "$ip/$mask", $lim!=MAX_LIM()?$lim:'unlimited', "($adder)"];
851 }
852
853 notice($user, columnar {TITLE => "Clone exception list:",
854 NOHIGHLIGHT => nr_chk_flag_user($user, NRF_NOHIGHLIGHT)}, @data);
855 }
856
857 sub os_session_list($) {
858 my ($user, $lim) = @_;
859
860 unless($lim > 1) {
861 notice($user, "Please specify a number greater than 1.");
862 return;
863 }
864
865 $get_session_list->execute($lim);
866 my $data = $get_session_list->fetchall_arrayref;
867
868 notice($user, columnar {TITLE => "Hosts with at least $lim sessions:",
869 NOHIGHLIGHT => nr_chk_flag_user($user, NRF_NOHIGHLIGHT)}, @$data);
870 }
871
872 sub os_chankill($$$) {
873 my ($user, $cn, $reason) = @_;
874
875 unless(adminserv::is_svsop($user, adminserv::S_OPER())) {
876 notice($user, $err_deny);
877 return;
878 }
879 my $src = get_user_nick($user);
880
881 chanserv::chan_kill({ CHAN => $cn }, "$reason ($src - ".gmtime2(time()).")");
882 }
883
884 sub os_rehash($;$) {
885 my ($user, $type) = @_;
886
887 unless (adminserv::is_svsop($user, adminserv::S_ROOT())) {
888 notice($user, $err_deny);
889 return $event::SUCCESS;
890 }
891
892 ircd::rehash_all_servers($type);
893 return $event::SUCCESS;
894 }
895
896 sub os_loners($@) {
897 my ($user, @args) = @_;
898 my $cmd = shift @args;
899 my $noid;
900 if ($cmd =~ /(not?id|noidentify)/) {
901 $noid = 1;
902 $cmd = shift @args;
903 }
904 if (defined($args[0]) and $args[0] =~ /(not?id|noidentify)/) {
905 $noid = 1;
906 shift @args;
907 }
908
909 if(!$cmd or $cmd =~ /^list$/i) {
910 my @reply;
911 foreach my $tuser (chanserv::get_users_nochans($noid)) {
912 push @reply, get_user_nick($tuser);
913 }
914 notice($user, "Users in zero channels", @reply);
915 }
916 elsif($cmd =~ /^uinfo$/i) {
917 notice($user, get_uinfo($user, chanserv::get_users_nochans($noid)));
918 }
919 elsif($cmd =~ /^kill$/i) {
920 unless(adminserv::can_do($user, 'KILL')) {
921 notice($user, $err_deny);
922 return;
923 }
924 foreach my $tuser (chanserv::get_users_nochans($noid)) {
925 $tuser->{AGENT} = $osnick;
926 nickserv::kill_user($tuser,
927 "Killed by \002".get_user_nick($user)."\002".
928 (@args ? ": ".join(' ', @args) : '')
929 );
930 }
931 }
932 elsif($cmd =~ /^kline$/i) {
933 unless(adminserv::is_svsop($user, adminserv::S_OPER())) {
934 notice($user, $err_deny);
935 return;
936 }
937 foreach my $tuser (chanserv::get_users_nochans($noid)) {
938 $tuser->{AGENT} = $osnick;
939 nickserv::kline_user($tuser, services_conf_chankilltime,
940 "K:Lined by \002".get_user_nick($user)."\002".
941 (@args ? ": ".join(' ', @args) : '')
942 );
943 }
944 }
945 elsif($cmd =~ /^(msg|message|notice)$/i) {
946 notice($user, "Must have message to send") unless(@args);
947 foreach my $tuser (chanserv::get_users_nochans($noid)) {
948 $tuser->{AGENT} = $osnick;
949 notice($tuser,
950 "Automated message from \002".get_user_nick($user),
951 join(' ', @args)
952 );
953 }
954 }
955 elsif($cmd =~ /^fjoin$/i) {
956 unless(adminserv::can_do($user, 'FJOIN')) {
957 notice($user, $err_deny);
958 return;
959 }
960
961 if ($args[0] !~ /^#/) {
962 notice($user, "\002".$args[0]."\002 is not a valid channel name");
963 return;
964 }
965
966 foreach my $tuser (chanserv::get_users_nochans($noid)) {
967 $tuser->{AGENT} = $osnick;
968 ircd::svsjoin($osnick, get_user_nick($tuser), $args[0]);
969 }
970 }
971 else {
972 notice($user, "Unknown LONERS command: $cmd",
973 'Syntax: OS LONERS [LIST|UINFO|MSG|FJOIN|KILL|KLINE] [NOID] [msg/reason]');
974 }
975 }
976
977 sub os_svskill($$$) {
978 my ($user, $targets, $reason) = @_;
979
980
981 if(!adminserv::is_svsop($user, adminserv::S_ROOT())) {
982 notice($user, $err_deny);
983 return $event::SUCCESS;
984 }
985
986 foreach my $target (split(',', $targets)) {
987 #my $tuser = { NICK => $target };
988 if (!is_online({ NICK => $target })) {
989 notice($user, $target.' is not online.');
990 return $event::SUCCESS;
991 }
992
993 ircd::svskill($osnick, $target, $reason);
994 }
995
996 return $event::SUCCESS;
997 }
998
999 sub os_kill($$$) {
1000 my ($user, $targets, $reason) = @_;
1001
1002
1003 if(!adminserv::can_do($user, 'KILL')) {
1004 notice($user, $err_deny);
1005 return $event::SUCCESS;
1006 }
1007
1008 foreach my $target (split(',', $targets)) {
1009 my $tuser = { NICK => $target, AGENT => $osnick };
1010 if (!get_user_id($tuser)) {
1011 notice($user, $target.' is not online.');
1012 return $event::SUCCESS;
1013 }
1014
1015 nickserv::kill_user($tuser, "Killed by ".get_user_nick($user).($reason ? ': '.$reason : ''));
1016 }
1017
1018 }
1019
1020 sub os_gline($$$@) {
1021 my ($user, $zline, $target, @args) = @_;
1022
1023 my $opernick;
1024 return unless ($opernick = adminserv::is_svsop($user, adminserv::S_OPER));
1025
1026 my $expiry = parse_time(shift @args) if $args[0] =~ /^\+/;
1027 my $reason = join(' ', @args);
1028 $reason =~ s/^\:// if $reason;
1029 my $remove;
1030 if($target =~ /^-/) {
1031 $remove = 1;
1032 $target =~ s/^-//;
1033 }
1034
1035 my ($ident, $host);
1036 if($target =~ /\!/) {
1037 notice($user, "Invalid G:line target \002$target\002");
1038 return;
1039 }
1040 elsif($target =~ /^(\S+)\@(\S+)$/) {
1041 ($ident, $host) = ($1, $2);
1042 } elsif($target =~ /\./) {
1043 ($ident, $host) = ('*', $target);
1044 } elsif(valid_nick($target)) {
1045 my $tuser = { NICK => $target };
1046 unless(get_user_id($tuser)) {
1047 notice($user, "Unknown user \002$target\002");
1048 return;
1049 }
1050 unless($zline) {
1051 (undef, $host) = nickserv::get_host($tuser);
1052 $ident = '*';
1053 } else {
1054 $host = nickserv::get_ip($tuser);
1055 }
1056 } else {
1057 notice($user, "Invalid G:line target \002$target\002");
1058 return;
1059 }
1060 unless($zline) {
1061 if(!$remove) {
1062 ircd::kline($opernick, $ident, $host, $expiry, $reason);
1063 } else {
1064 ircd::unkline($opernick, $ident, $host);
1065 }
1066
1067 } else {
1068 if($ident and $ident !~ /^\**$/) {
1069 notice($user, "You cannot specify an ident in a Z:line");
1070 }
1071 elsif ($host =~ /^(?:\d{1,3}\.){3}(?:\d{1,3})/) {
1072 # all is well, do nothing
1073 }
1074 elsif ($host =~ /^[0-9\/\*\?\.]+$/) {
1075 # This may allow invalid CIDR, not sure.
1076 # We're trusting our opers to not do stupid things.
1077 # THIS MAY BE A SOURCE OF BUGS.
1078
1079 # all is well, do nothing
1080 } else {
1081 notice($user, "Z:lines can only be placed on IPs or IP ranges");
1082 return;
1083 }
1084 if(!$remove) {
1085 ircd::zline($opernick, $host, $expiry, $reason);
1086 } else {
1087 ircd::unzline($opernick, $host);
1088 }
1089 }
1090
1091 return $event::SUCCESS;
1092 }
1093
1094 sub os_clones($@) {
1095 my ($user, @args) = @_;
1096 my $cmd = shift @args;
1097 my $target = shift @args;
1098
1099 if($cmd =~ /^list$/i) {
1100 my @data;
1101 foreach my $tuser (get_clones($target)) {
1102 push @data, [get_user_nick($tuser), (is_online($tuser) ? "\002Online\002" : "\002Offline\002")];
1103 }
1104 notice($user, columnar {TITLE => "Clones matching \002$target\002",
1105 NOHIGHLIGHT => nr_chk_flag_user($user, NRF_NOHIGHLIGHT)}, @data);
1106 }
1107 elsif($cmd =~ /^uinfo$/i) {
1108 notice($user, get_uinfo($user, get_clones($target)));
1109 }
1110 elsif($cmd =~ /^kill$/i) {
1111 unless(adminserv::can_do($user, 'KILL')) {
1112 notice($user, $err_deny);
1113 return;
1114 }
1115 foreach my $tuser (get_clones($target)) {
1116 next unless is_online($tuser);
1117 $tuser->{AGENT} = $osnick;
1118 nickserv::kill_user($tuser,
1119 "Killed by \002".get_user_nick($user)."\002".
1120 (@args ? ": ".join(' ', @args) : '')
1121 );
1122 }
1123 }
1124 elsif($cmd =~ /^kline$/i) {
1125 unless(adminserv::is_svsop($user, adminserv::S_OPER())) {
1126 notice($user, $err_deny);
1127 return;
1128 }
1129 foreach my $tuser (get_clones($target)) {
1130 next unless is_online($tuser);
1131 $tuser->{AGENT} = $osnick;
1132 nickserv::kline_user($tuser, services_conf_chankilltime,
1133 "K:Lined by \002".get_user_nick($user)."\002".
1134 (@args ? ": ".join(' ', @args) : '')
1135 );
1136 }
1137 }
1138 elsif($cmd =~ /^(msg|message|notice)$/i) {
1139 notice($user, "Must have message to send") unless(@args);
1140 foreach my $tuser (get_clones($target)) {
1141 next unless is_online($tuser);
1142 $tuser->{AGENT} = $osnick;
1143 notice($tuser,
1144 "Automated message from \002".get_user_nick($user),
1145 join(' ', @args)
1146 );
1147 }
1148 }
1149 elsif($cmd =~ /^fjoin$/i) {
1150 unless(adminserv::can_do($user, 'FJOIN')) {
1151 notice($user, $err_deny);
1152 return;
1153 }
1154
1155 if ($args[0] !~ /^#/) {
1156 notice($user, "\002".$args[0]."\002 is not a valid channel name");
1157 return;
1158 }
1159
1160 foreach my $tuser (get_clones($target)) {
1161 next unless is_online($tuser);
1162 $tuser->{AGENT} = $osnick;
1163 ircd::svsjoin($osnick, get_user_nick($tuser), $args[0]);
1164 }
1165 }
1166 else {
1167 notice($user, "Unknown CLONES command: $cmd",
1168 'Syntax: OS CLONES [LIST|UINFO|MSG|FJOIN|KILL|KLINE] [msg/reason]');
1169 }
1170 }
1171
1172 ### MISCELLANEA ###
1173
1174 sub do_news($$) {
1175 my ($nick, $type) = @_;
1176
1177 my ($banner, @reply);
1178
1179 if ($type eq 'u') {
1180 $banner = "\002Logon News\002";
1181 }
1182 elsif ($type eq 'o') {
1183 $banner = "\002Oper News\002";
1184 }
1185 $get_logonnews->execute($type);
1186 while(my ($adder, $time, $msg) = $get_logonnews->fetchrow_array) {
1187 my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = gmtime($time);
1188 $year += 1900;
1189 push @reply, "[$banner ".$months[$mon]." $mday $year] $msg";
1190 }
1191 $get_logonnews->finish();
1192 ircd::notice(main_conf_local, $nick, @reply) if scalar(@reply);
1193 }
1194
1195 sub chk_auth($$) {
1196 my ($user, $perm) = @_;
1197
1198 if(adminserv::can_do($user, $perm)) {
1199 return 1;
1200 }
1201
1202 notice($user, $err_deny);
1203 return 0;
1204 }
1205
1206 sub expire(;$) {
1207 add_timer('OperServ Expire', 60, __PACKAGE__, 'operserv::expire');
1208
1209 $get_expired_qlines->execute();
1210 while (my ($mask) = $get_expired_qlines->fetchrow_array() ) {
1211 ircd::unsqline($mask);
1212 $del_qline->execute($mask);
1213 }
1214 $get_expired_qlines->finish();
1215
1216 #don't run this code yet.
1217 =cut
1218 $get_expired_akills->execute();
1219 while (my ($mask) = $get_expired_akills->fetchrow_array() ) {
1220 ($ident, $host) = split('@', $mask);
1221 ircd::unkline($osnick, $ident, $host);
1222 $del_akill->execute($mask);
1223 }
1224 $get_expired_akills->finish();
1225 =cut
1226
1227 $del_expired_logonnews->execute();
1228 }
1229
1230 sub get_uinfo($@) {
1231 my ($user, @userlist) = @_;
1232 my @reply;
1233 foreach my $tuser (@userlist) {
1234 my ($ident, $host, $vhost, $gecos, $server) = get_user_info($tuser);
1235 my $modes = nickserv::get_user_modes($tuser);
1236 my $target = get_user_nick($tuser);
1237
1238 my ($curchans, $oldchans) = chanserv::get_user_chans_recent($tuser);
1239
1240 my @data = (
1241 ["Status:", (nickserv::is_online($tuser) ? "Online" : "Offline")],
1242 ["ID Nicks:", join(', ', nickserv::get_id_nicks($tuser))],
1243 ["Channels:", join(', ', @$curchans)],
1244 ["Recently Parted:", join(', ', @$oldchans)],
1245 ["Flood level:", get_flood_level($tuser)],
1246 ["Hostmask:", "$target\!$ident\@$vhost"],
1247 ["GECOS:", $gecos],
1248 ["Connecting from:", "$host"],
1249 ["Current Server:", $server],
1250 ["Modes:", $modes]
1251 );
1252 if(module::is_loaded('country')) {
1253 push @data, ["Country:", country::get_user_country_long($tuser)];
1254 } elsif(module::is_loaded('geoip')) {
1255 push @data, ["Location:", geoip::stringify_location(geoip::get_user_location($tuser))];
1256 }
1257
1258 push @reply, columnar {TITLE => "User info for \002$target\002:",
1259 NOHIGHLIGHT => nr_chk_flag_user($user, NRF_NOHIGHLIGHT)}, @data;
1260 }
1261 return @reply;
1262 }
1263
1264 sub get_clones($) {
1265 my ($targets) = @_;
1266 my @users;
1267 foreach my $target (split(',', $targets)) {
1268 my $sth; # statement handle. You'll see what I'll do with it next!
1269 if($target =~ /^(?:\d{1,3}\.){3}\d{1,3}$/) {
1270 $sth = $get_clones_fromipv4;
1271 } elsif($target =~ /\./) { # doesn't really work with localhost. oh well.
1272 $sth = $get_clones_fromhost;
1273 } else {
1274 $sth = $get_clones_fromnick;
1275 }
1276
1277 $sth->execute($target);
1278 while(my ($nick, $id, $online) = $sth->fetchrow_array()) {
1279 push @users, { NICK => $nick, ID => $id, ONLINE => $online };
1280 }
1281 $sth->finish();
1282 }
1283 return @users;
1284 }
1285
1286 ## IRC EVENTS ##
1287
1288 1;