#!/usr/bin/perl # keitairc # $Id: keitairc,v 1.39 2008-01-13 15:31:09 morimoto Exp $ # $Source: /home/ishikawa/work/keitairc/tmp/keitairc/keitairc,v $ # # Copyright (c) 2003-2008 Jun Morimoto # This program is covered by the GNU General Public License 2 # # Depends: libpoe-component-irc-perl, # liburi-perl, libwww-perl, libappconfig-perl, libproc-daemon-perl, # libhtml-template-perl use lib qw(lib /usr/share/keitairc/lib); use strict; use Encode; use POE; use POE::Filter::HTTPD; use POE::Component::IRC; use POE::Component::Server::TCP; use URI::Escape; use HTML::Template; use HTTP::Response; use Proc::Daemon; use Keitairc::Config; use Keitairc::View; use Keitairc::IrcBuffer; use Keitairc::IrcCallback; use Keitairc::ClientInfo; use Keitairc::SessionManager; use Keitairc::Plugins; our $cf = new Keitairc::Config('2.0b3', @ARGV); our $ib = new Keitairc::IrcBuffer({history => $cf->web_lines()}); our $sm = new Keitairc::SessionManager({default_ttl => $cf->session_ttl()}); our $pl = new Keitairc::Plugins({config => $cf}); # daemonize if($cf->daemonize()){ Proc::Daemon::Init; if(length $cf->pid_dir()){ if (open(PID, '> ' . $cf->pid_dir() . '/keitairc.pid')) { print PID $$, "\n"; close(PID); } } } # create irc component our $irc = POE::Component::IRC->spawn( Alias => 'keitairc_irc', Nick => $cf->irc_nick(), Username => $cf->irc_username(), Ircname => $cf->irc_desc(), Server => $cf->irc_server(), Port => $cf->irc_port(), Password => $cf->irc_password()); # create POE session POE::Session->create( heap => { seen_traffic => 0, disconnect_msg => 1, Config => $cf, Irc => $irc, IrcBuffer => $ib, }, inline_states => { _start => \&Keitairc::IrcCallback::irc_start, autoping => \&Keitairc::IrcCallback::irc_autoping, connect => \&Keitairc::IrcCallback::irc_connect, irc_001 => \&Keitairc::IrcCallback::irc_001, irc_join => \&Keitairc::IrcCallback::irc_join, irc_part => \&Keitairc::IrcCallback::irc_part, irc_public => \&Keitairc::IrcCallback::irc_public, irc_notice => \&Keitairc::IrcCallback::irc_notice, irc_mode => \&Keitairc::IrcCallback::irc_mode, irc_topic => \&Keitairc::IrcCallback::irc_topic, irc_332 => \&Keitairc::IrcCallback::irc_topicraw, irc_352 => \&Keitairc::IrcCallback::irc_whoreply, irc_ctcp_action => \&Keitairc::IrcCallback::irc_ctcp_action, irc_disconnected => \&Keitairc::IrcCallback::irc_reconnect, irc_error => \&Keitairc::IrcCallback::irc_reconnect, irc_socketerr => \&Keitairc::IrcCallback::irc_reconnect, }); # create web server component POE::Component::Server::TCP->new( Alias => 'keitairc', Port => $cf->web_port(), ClientFilter => 'POE::Filter::HTTPD', ClientInput => \&http_request); # fire up main loop $poe_kernel->run(); exit 0; ################################################################ sub http_request{ my ($kernel, $heap, $request) = @_[KERNEL, HEAP, ARG0]; # Filter::HTTPD sometimes generates HTTP::Response objects. # They indicate (and contain the response for) errors that occur # while parsing the client's HTTP request. It's easiest to send # the responses as they are and finish up. if($request->isa('HTTP::Response')){ $heap->{client}->put($request); }elsif(my $response = dispatch($request)){ $heap->{client}->put($response); } $kernel->yield('shutdown'); } ################################################################ sub dispatch{ my $request = shift; my $uri = $request->uri(); my $ci = new Keitairc::ClientInfo($request); ::log_debug("dispatch: $uri"); if($uri eq '/'){ return action_root($request); } if($uri eq '/login'){ return action_login($request); } if($uri eq '/login_icc'){ return action_login_icc($request); } for my $name ($pl->list_action_plugins()){ if($uri =~ m|^/(S[a-zA-Z]{10})/$name/(.*)| || $uri =~ m|^/(S[a-zA-Z]{10})/$name$|){ if($sm->verify({session_id => $1, user_agent => $ci->user_agent()})){ return add_cookie($pl->{plugins}->{$name}->{action_imprementation}($request, $name, $1, $2), $1); } return action_redirect_root($request); } } ::log("dispatch: don't know how to dispatch uri[$uri]"); return action_404($request); } ################################################################ # adds session id cookie to http response object sub add_cookie{ my $response = shift; my $session_id = shift; my ($sec, $min, $hour, $mday, $mon, $year, $wday) = localtime(time + $cf->cookie_ttl()); my $expiration = sprintf('%.3s, %.2d-%.3s-%.4s %.2d:%.2d:%.2d', qw(Sun Mon Tue Wed Thu Fri Sat)[$wday], $mday, qw(Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec)[$mon], $year + 1900, $hour, $min, $sec); my $content = sprintf("sid=%s; expires=%s; \n", $session_id, $expiration); $response->push_header('Set-Cookie', $content); $response; } ################################################################ # 通常ログインのPOST先 # パスワードをチェックして # 間違っていたら / へリンクして終わり # 合っていたらセッションを発行し /{SESSION}/index へ sub action_login{ my $request = shift; my $ci = new Keitairc::ClientInfo($request); my $content = $request->decoded_content(); my ($password) = ($content =~ /^password=(.*)/); ::log_debug("password [$password]"); ::log_debug("web_password [" . $cf->web_password() . "]"); if($cf->web_password() eq $password){ my $s = $sm->add($ci->{header}->{user_agent}, $ci->serial_key()); my $view = new Keitairc::View($cf, $ci, $s->{id}); return $view->redirect("/$s->{id}/index"); } # password mismatch my $view = new Keitairc::View($cf, $ci); return $view->redirect("/"); } ################################################################ sub action_404{ my $request = shift; my $ci = new Keitairc::ClientInfo($request); my $view = new Keitairc::View($cf, $ci); return $view->render('404.html', { action => $request->uri() }); } ################################################################ # かんたんログインのPOST先 # DoCoMoだったらiccが来ているはずなので, icc + user_agent でチェック。 # 合っていたらセッション復帰して /{SESSION}/index へ sub action_login_icc{ my $request = shift; my $ci = new Keitairc::ClientInfo($request); if($ci->is_docomo()){ my $docomo_foma_icc = $ci->docomo_foma_icc(); if(length $docomo_foma_icc){ if(my $s = $sm->verify({serial_key => $docomo_foma_icc, user_agent => $ci->user_agent()})){ ::log_debug("redirect to /$s->{id}/index from docomo_foma_icc"); my $view = new Keitairc::View($cf, $ci, $s->{id}); return $view->redirect("/$s->{id}/index"); } if($docomo_foma_icc eq $cf->docomo_foma_icc()){ my $s = $sm->add($ci->user_agent(), $docomo_foma_icc); ::log_debug("redirect to /$s->{id}/index from docomo_foma_icc"); my $view = new Keitairc::View($cf, $ci, $s->{id}); return $view->redirect("/$s->{id}/index"); } my $view = new Keitairc::View($cf, $ci); return $view->render('login_icc.html', { icc => $docomo_foma_icc }); } } my $view = new Keitairc::View($cf, $ci); return $view->render('root.html', { docomo => $ci->is_docomo() }); } ################################################################ sub action_root{ my $request = shift; my $ci = new Keitairc::ClientInfo($request); if($ci->cookie_available()){ my $session_id = $ci->{cookie}->{sid}; if(length $session_id){ if($sm->verify({session_id => $session_id, user_agent => $ci->user_agent()})){ ::log_debug("redirect to /$session_id/index from cookie"); my $view = new Keitairc::View($cf, $ci, $session_id); return $view->redirect("/$session_id/index"); } } } if($ci->is_ezweb()){ my $subscriber_id = $ci->{header}->{x_up_subno}; if(length $subscriber_id){ if(my $s = $sm->verify({serial_key => $subscriber_id, user_agent => $ci->user_agent()})){ ::log_debug("redirect to /$s->{id}/index from subscriber_id"); my $view = new Keitairc::View($cf, $ci, $s->{id}); return $view->redirect("/$s->{id}/index"); } if($subscriber_id eq $cf->au_subscriber_id()){ my $s = $sm->add($ci->user_agent(), $subscriber_id); ::log_debug("redirect to /$s->{id}/index from au_subscriber_id"); my $view = new Keitairc::View($cf, $ci, $s->{id}); return $view->redirect("/$s->{id}/index"); } } } if($ci->is_softbank()){ my $serial_key = $ci->softbank_serial(); if(length $serial_key){ if(my $s = $sm->verify({serial_key => $serial_key, user_agent => $ci->user_agent()})){ ::log_debug("redirect to /$s->{id}/index from softbank serial_key"); my $view = new Keitairc::View($cf, $ci, $s->{id}); return $view->redirect("/$s->{id}/index"); } } } my $view = new Keitairc::View($cf, $ci); return $view->render('root.html', { docomo => $ci->is_docomo() }); } ################################################################ sub action_redirect_root{ my $request = shift; my $ci = new Keitairc::ClientInfo($request); my $view = new Keitairc::View($cf, $ci); return $view->redirect('/'); } ################################################################ sub send_message{ my $request = shift; my $channel = shift; my $message = $request->content(); $message =~ s/^m=//; $message =~ s/\+/ /g; $message = uri_unescape($message); if(length($message)){ my $jis = $message; my $euc = $message; Encode::from_to($jis, 'shiftjis', 'jis'); Encode::from_to($euc, 'shiftjis', 'euc-jp'); $irc->yield(privmsg => $channel => $jis); my $cid = $ib->name2cid($channel); $ib->add_message($cid, $euc, $cf->irc_nick()); $ib->message_added(1); } } ################################################################ # 入力は euc-jp sub render_line{ local($_); my $in = shift; my $session_id = shift; my $buf; for ((reverse(split("\n", $in)))[0 .. $cf->web_lines()]){ next unless defined; next unless length; $_ = $ib->simple_escape($_); $_ = $ib->colorize($_); for my $name ($pl->list_replace_plugins()){ last if s/$pl->{plugins}->{$name}->{message_replace_regexp}/$pl->{plugins}->{$name}->{message_replace_imprementation}($session_id, $1, $2, $3, $4, $5, $6, $7, $8, $9)/eg; } s/\s+$//; s/\s+/ /g; $buf .= "$_
"; } Encode::from_to($buf, 'euc-jp', 'shiftjis'); $buf; } ################################################################ sub log{ my $m = shift; warn "keitairc: $m\n"; # TODO } sub log_die{ my $m = shift; die "keitairc: $m\n"; # TODO } sub log_debug{ my $m = shift; warn "keitairc: $m\n"; # TODO } __END__