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