2 # -*- mode: perl; coding: utf-8 -*-
5 # Copyright (c) 2003-2010 Jun Morimoto <morimoto@mrmt.net>
6 # This program is covered by the GNU General Public License 2
8 # Depends: libpoe-component-irc-perl,
9 # liburi-perl, libwww-perl, libappconfig-perl, libproc-daemon-perl,
10 # libhtml-template-perl
12 # 00location_receiver plugin use XML::Simple, so if you want to use it
13 # Depends: libxml-simple-perl
17 use POE::Filter::HTTPD;
18 use POE::Component::IRC;
19 use POE::Component::Server::TCP;
26 use lib ("$FindBin::Bin/lib", '/usr/share/keitairc/lib');
29 use Keitairc::IrcBuffer;
30 use Keitairc::IrcCallback;
31 use Keitairc::ClientInfo;
32 use Keitairc::SessionManager;
33 use Keitairc::Plugins;
38 our $cf = new Keitairc::Config({version => '2.1a1', argv => \@ARGV});
39 our $ib = new Keitairc::IrcBuffer({history => $cf->web_lines()});
40 our $sm = new Keitairc::SessionManager({default_ttl => $cf->session_ttl()});
41 our $pl = new Keitairc::Plugins({config => $cf});
48 if(length $cf->pid_dir()){
49 if (open(PID, '> ' . $cf->pid_dir() . '/' . $cf->pid_file())) {
54 $poe_kernel->has_forked if ($poe_kernel->can('has_forked'));
57 # create irc component
58 our $irc = POE::Component::IRC->spawn(
59 Alias => 'keitairc_irc',
60 Nick => $cf->irc_nick(),
61 Username => $cf->irc_username(),
62 Ircname => $cf->irc_desc(),
63 Server => $cf->irc_server(),
64 Port => $cf->irc_port(),
65 Password => $cf->irc_password());
77 _start => \&Keitairc::IrcCallback::irc_start,
78 autoping => \&Keitairc::IrcCallback::irc_autoping,
79 connect => \&Keitairc::IrcCallback::irc_connect,
80 irc_registered => \&Keitairc::IrcCallback::irc_registered,
81 irc_001 => \&Keitairc::IrcCallback::irc_001,
82 irc_join => \&Keitairc::IrcCallback::irc_join,
83 irc_part => \&Keitairc::IrcCallback::irc_part,
84 irc_quit => \&Keitairc::IrcCallback::irc_quit,
85 irc_public => \&Keitairc::IrcCallback::irc_public,
86 irc_notice => \&Keitairc::IrcCallback::irc_notice,
87 irc_mode => \&Keitairc::IrcCallback::irc_mode,
88 irc_nick => \&Keitairc::IrcCallback::irc_nick,
89 irc_msg => \&Keitairc::IrcCallback::irc_msg,
90 irc_topic => \&Keitairc::IrcCallback::irc_topic,
91 irc_332 => \&Keitairc::IrcCallback::irc_topicraw,
92 irc_352 => \&Keitairc::IrcCallback::irc_whoreply,
93 irc_ctcp_action => \&Keitairc::IrcCallback::irc_ctcp_action,
94 irc_disconnected => \&Keitairc::IrcCallback::irc_reconnect,
95 irc_error => \&Keitairc::IrcCallback::irc_reconnect,
96 irc_socketerr => \&Keitairc::IrcCallback::irc_reconnect,
99 # create web server component
100 POE::Component::Server::TCP->new(
102 Port => $cf->web_listen_port(),
103 ClientFilter => 'POE::Filter::HTTPD',
104 ClientInput => \&http_request);
110 ################################################################
112 my ($kernel, $heap, $request) = @_[KERNEL, HEAP, ARG0];
114 # Filter::HTTPD sometimes generates HTTP::Response objects.
115 # They indicate (and contain the response for) errors that occur
116 # while parsing the client's HTTP request. It's easiest to send
117 # the responses as they are and finish up.
118 if($request->isa('HTTP::Response')){
119 $heap->{client}->put($request);
120 }elsif(my $response = dispatch($request)){
121 $heap->{client}->put($response);
124 $kernel->yield('shutdown');
127 ################################################################
130 my $uri = $request->uri();
131 my $ci = new Keitairc::ClientInfo($request);
133 Keitairc::Log::log_debug("dispatch: $uri");
136 # chop off $cf->web_root()
137 my $root = $cf->web_root();
142 return action_root($request);
145 if($uri eq '/login'){
146 return action_login($request);
149 if($uri eq '/login_icc'){
150 return action_login_icc($request);
153 if($uri eq '/login_imodeid?guid=ON'){
154 return action_login_imodeid($request);
157 for my $name ($pl->list_action_plugins()){
158 if($uri =~ m|^/(S[a-zA-Z]{10})/$name/(.*)| ||
159 $uri =~ m|^/(S[a-zA-Z]{10})/$name$|){
160 if($sm->verify({session_id => $1, user_agent => $ci->user_agent()})){
161 return add_cookie($pl->{plugins}->{$name}->{action_imprementation}($request, $name, $1, $2), $1);
163 if ($ci->is_webkit() && $cf->webkit_newui()) {
164 return action_error($request, 401);
166 return action_redirect_root($request);
171 return action_public($request, $uri) || action_error($request, 404);
174 ################################################################
175 # adds session id cookie to http response object
177 my $response = shift;
178 my $session_id = shift;
180 my ($sec, $min, $hour, $mday, $mon, $year, $wday) = localtime(time + $cf->cookie_ttl());
182 sprintf('%.3s, %.2d-%.3s-%.4s %.2d:%.2d:%.2d',
183 qw(Sun Mon Tue Wed Thu Fri Sat)[$wday],
185 qw(Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec)[$mon],
190 my $content = sprintf("sid=%s; expires=%s; path=%s; \n", $session_id, $expiration, $cf->web_root());
191 $response->push_header('Set-Cookie', $content);
195 ################################################################
198 # 間違っていたら / へリンクして終わり
199 # 合っていたらセッションを発行し /{SESSION}/index へ
202 my $ci = new Keitairc::ClientInfo($request);
203 my $content = $request->decoded_content();
204 my ($password) = ($content =~ /^password=(.*)/);
206 Keitairc::Log::log_debug("password [$password]");
207 Keitairc::Log::log_debug("web_password [" . $cf->web_password() . "]");
209 if($cf->web_password() eq $password){
210 my $s = $sm->add($ci->user_agent(), $ci->serial_key());
211 my $view = new Keitairc::View($cf, $ci, $s->{id});
212 if ($ci->is_webkit() && $cf->webkit_newui()) {
213 return add_cookie($view->redirect('/'), $s->{id});
215 return $view->redirect("/$s->{id}/index");
220 my $view = new Keitairc::View($cf, $ci);
221 return $view->redirect('/');
224 ################################################################
227 my $error_code = shift;
228 my $ci = new Keitairc::ClientInfo($request);
229 my $view = new Keitairc::View($cf, $ci);
230 return $view->render('error.html', { action => $request->uri(),
231 _http_status_code => $error_code,
232 _http_status_message => status_message($error_code) });
235 ################################################################
238 my $uri = shift; # such as '/favicon.ico'
239 my $ci = new Keitairc::ClientInfo($request);
240 my $view = new Keitairc::View($cf, $ci);
241 return $view->public($uri);
244 ################################################################
246 # DoCoMoだったらiccが来ているはずなので, icc + user_agent でチェック。
247 # 合っていたらセッション復帰して /{SESSION}/index へ
248 sub action_login_icc{
250 my $ci = new Keitairc::ClientInfo($request);
251 if($ci->is_docomo()){
252 my $docomo_foma_icc = $ci->docomo_foma_icc();
253 if(length $docomo_foma_icc){
254 if(my $s = $sm->verify({serial_key => $docomo_foma_icc,
255 user_agent => $ci->user_agent()})){
256 Keitairc::Log::log_debug("redirect to /$s->{id}/index from docomo_foma_icc");
257 my $view = new Keitairc::View($cf, $ci, $s->{id});
258 return $view->redirect("/$s->{id}/index");
261 if($docomo_foma_icc eq $cf->docomo_foma_icc()){
262 my $s = $sm->add($ci->user_agent(), $docomo_foma_icc);
263 Keitairc::Log::log_debug("redirect to /$s->{id}/index from docomo_foma_icc");
264 my $view = new Keitairc::View($cf, $ci, $s->{id});
265 return $view->redirect("/$s->{id}/index");
268 my $view = new Keitairc::View($cf, $ci);
269 return $view->render('login_icc.html', { icc => $docomo_foma_icc });
273 my $view = new Keitairc::View($cf, $ci);
274 return $view->render('root.html', {
275 docomo_foma_icc => $cf->docomo_foma_icc(),
276 docomo_imodeid => $cf->docomo_imodeid(),
280 ################################################################
282 # DoCoMoだったらiモードIDが来ているはずなので, iモードID + user_agent でチェック。
283 # 合っていたらセッション復帰して /{SESSION}/index へ
284 sub action_login_imodeid{
286 my $ci = new Keitairc::ClientInfo($request);
287 if($ci->is_docomo()){
288 my $docomo_imodeid = $ci->docomo_imodeid();
289 if(length $docomo_imodeid){
290 if(my $s = $sm->verify({serial_key => $docomo_imodeid,
291 user_agent => $ci->user_agent()})){
292 Keitairc::Log::log_debug("redirect to /$s->{id}/index from docomo_imodeid");
293 my $view = new Keitairc::View($cf, $ci, $s->{id});
294 return $view->redirect("/$s->{id}/index");
297 if($docomo_imodeid eq $cf->docomo_imodeid()){
298 my $s = $sm->add($ci->user_agent(), $docomo_imodeid);
299 Keitairc::Log::log_debug("redirect to /$s->{id}/index from docomo_imodeid");
300 my $view = new Keitairc::View($cf, $ci, $s->{id});
301 return $view->redirect("/$s->{id}/index");
304 my $view = new Keitairc::View($cf, $ci);
305 return $view->render('login_imodeid.html', { imodeid => $docomo_imodeid });
309 my $view = new Keitairc::View($cf, $ci);
310 return $view->render('root.html', {
311 docomo_foma_icc => $cf->docomo_foma_icc(),
312 docomo_imodeid => $cf->docomo_imodeid(),
316 ################################################################
319 my $ci = new Keitairc::ClientInfo($request);
321 if($ci->cookie_available()){
322 my $session_id = $ci->{cookie}->{sid};
323 if(defined($session_id) && length($session_id)){
324 if($sm->verify({session_id => $session_id,
325 user_agent => $ci->user_agent()})){
326 Keitairc::Log::log_debug("redirect to /$session_id/index from cookie");
327 my $view = new Keitairc::View($cf, $ci, $session_id);
328 if ($ci->is_webkit() && $cf->webkit_newui()) {
329 return add_cookie($view->render('root_home.html', {sid => $session_id}), $session_id);
331 return $view->redirect("/$session_id/index");
338 my $subscriber_id = $ci->au_subscriber_id();
339 if(length $subscriber_id){
340 if(my $s = $sm->verify({serial_key => $subscriber_id,
341 user_agent => $ci->user_agent()})){
342 Keitairc::Log::log_debug("redirect to /$s->{id}/index from subscriber_id");
343 my $view = new Keitairc::View($cf, $ci, $s->{id});
344 return $view->redirect("/$s->{id}/index");
347 if($subscriber_id eq $cf->au_subscriber_id()){
348 my $s = $sm->add($ci->user_agent(), $subscriber_id);
349 Keitairc::Log::log_debug("redirect to /$s->{id}/index from au_subscriber_id");
350 my $view = new Keitairc::View($cf, $ci, $s->{id});
351 return $view->redirect("/$s->{id}/index");
356 if($ci->is_softbank()){
357 my $serial_key = $ci->softbank_serial();
358 if(length $serial_key){
359 if(my $s = $sm->verify({serial_key => $serial_key,
360 user_agent => $ci->user_agent()})){
361 Keitairc::Log::log_debug("redirect to /$s->{id}/index from softbank serial_key");
362 my $view = new Keitairc::View($cf, $ci, $s->{id});
363 return $view->redirect("/$s->{id}/index");
365 if($serial_key eq $cf->softbank_serial_key()){
366 my $s = $sm->add($ci->user_agent(), $serial_key);
367 Keitairc::Log::log_debug("redirect to /$s->{id}/index from softbank_serial_key");
368 my $view = new Keitairc::View($cf, $ci, $s->{id});
369 return $view->redirect("/$s->{id}/index");
374 if($ci->is_emobile()){
375 my $userid = $ci->emobile_userid();
377 if(my $s = $sm->verify({serial_key => $userid,
378 user_agent => $ci->user_agent()})){
379 Keitairc::Log::log_debug("redirect to /$s->{id}/index from userid");
380 my $view = new Keitairc::View($cf, $ci, $s->{id});
381 return $view->redirect("/$s->{id}/index");
384 if($userid eq $cf->emobile_userid()){
385 my $s = $sm->add($ci->user_agent(), $userid);
386 Keitairc::Log::log_debug("redirect to /$s->{id}/index from emobile_userid");
387 my $view = new Keitairc::View($cf, $ci, $s->{id});
388 return $view->redirect("/$s->{id}/index");
393 my $view = new Keitairc::View($cf, $ci);
394 return $view->render('root.html', {
395 docomo_foma_icc => $cf->docomo_foma_icc(),
396 docomo_imodeid => $cf->docomo_imodeid(),
400 ################################################################
401 sub action_redirect_root{
403 my $ci = new Keitairc::ClientInfo($request);
404 my $view = new Keitairc::View($cf, $ci);
405 return $view->redirect('/');
408 ################################################################
411 my $ci = new Keitairc::ClientInfo($request);
414 my $message = $request->content();
416 if(length($message)){
417 ($message, $timestamp) = split(/&/, $message);
419 $timestamp =~ s/^stamp=//g;
422 $message =~ s/\+/ /g;
423 $message = uri_unescape($message);
425 if($ci->is_webkit() && !$cf->webkit_newui()){
426 $message = fix_webkit_escape($message);
429 if ($cf->webkit_newui()) {
430 # ajax で投げ込んでるので utf8 できます
431 $message = Encode::decode('utf8', $message);
433 $message = Encode::decode($cf->web_charset(), $message);
435 return ($message, $timestamp);
442 my ($message, $timestamp) = parse_message($request);
444 if(length($message) && length($channel)){
445 if($ib->update_timestamp($timestamp)){
446 my $enc_message = Encode::encode($cf->irc_charset(), $message);
447 my $enc_channel = Encode::encode($cf->irc_charset(), $channel);
448 $irc->yield(privmsg => $enc_channel => $enc_message);
449 my $cid = $ib->name2cid($channel);
450 $ib->add_message($cid, $message, $cf->irc_nick());
458 my ($message, $timestamp) = parse_message($request);
460 if(length($message)){
461 if($message =~ s|^/||) {
462 my ($params, $trailing) = split(/ :/, $message, 2);
463 my @postcmd = split(/ /, $params);
464 push @postcmd, $trailing if defined $trailing;
465 # This parser may be incomplete.
466 if($postcmd[0] =~ /join/i) {
467 if($postcmd[1] =~ /^\w/) {
468 $ib->join($postcmd[1]);
471 } elsif($postcmd[0] =~ /part/i) {
472 if($postcmd[1] =~ /^\w/) {
473 $ib->part($ib->name2cid($postcmd[1]));
477 $irc->yield(map { Encode::encode($cf->irc_charset(), $_) } @postcmd);
482 ################################################################
483 # posted string from Webkit browser
484 # contains escaped utf-8 in the form %uXXXX
485 # and may contains escaped Shift-JIS (web_charset) in the form \xXX
486 # when operated from Safari/Mac OS X
487 sub fix_webkit_escape{
488 # charset: $cf->irc_charset()
490 $in =~ s/\\x([0-9A-F]{2})/pack('C',hex($1))/egi;
491 #my $pi = Encode::decode('utf8', $in);
492 $in =~ s/%u([0-9A-F]{4})/pack('U',hex($1))/egi;