4 $max_room_number = 3; //最大ルーム数
\r
5 $spilt_size = 1024 * 512; //分割するサイズ
\r
6 $reset_password_diff = 1000 * 60 * 60; //ルームパスワードをリセットする間隔
\r
7 $gc_time_interval = 1000 * 60 * 60; //ゴミ掃除を行う間隔
\r
8 $invaild_token_message = "トークンが一致しませんでした";
\r
9 $block_message = "メッセージの送信に失敗しました"; //ブロック時のメッセージ
\r
10 $not_match_password = "パスワードが一致しませんでした"; //パスワードが一致しない場合に表示されるメッセージ
\r
11 $password_setted_message = "パスワードを設定しました"; //パスワードが設定されたときに表示されるメッセージ
\r
12 $password_resetted_message = "パスワードをリセットしました"; //パスワードが再設定されたときに表示されるメッセージ
\r
13 $failed_set_password_message = "パスワードの設定に失敗しました"; //パスワードが再設定されたときに表示されるメッセージ
\r
14 $ip_ban_list_file_name = "ipbanlist.txt"; //アクセスを禁止するIPが記録されているファイル
\r
15 $room_configure_file_name = "roomlist.txt"; //ルームの設定が記録されているファイル
\r
16 $port = process.env.port || 3000; //ポート
\r
17 $username = "admin"; //管理者用のページにアクセスできるユーザ名
\r
18 $password = "admin"; //管理者用のページにアクセスするのに必要なパスワード
\r
19 $token_length = 32; //トークンの長さ
\r
20 $redisHost = "localhost"; //redisサーバのアドレス
\r
21 $redisPort = 6379; //redisサーバのポート
\r
22 $redisPassword = ""; //redisサーバのパスワード
\r
23 $system_name = "system"; //システム発言を表す名前
\r
24 $log_directory = "log"; //ログファイルを置くフォルダー
\r
25 $log_file_name = "logfile%d.txt"; //ログファイル名(%dはそのままにしておくこと)
\r
26 $splited_log_file_name = "logfile%d_%s.txt" //分割後のファイル名(%dと%sはそのままにしておくこと)
\r
27 $pastlogfile_pattern = "logfile%d(_+.*)?\.txt"; //過去ログと判定する正規表現
\r
28 $secret = "5514EA2B-C9B2-4D65-8D81-1F33A180A0C2"; //cookie用秘密鍵
\r
30 * Module dependencies.
\r
34 var express = require("express");
\r
36 var app = express();
\r
38 var http = require("http");
\r
40 var util = require("util");
\r
42 var lazy = require("./lazy.js");
\r
44 var fs = require("fs");
\r
46 var cookie = require("express/node_modules/cookie");
\r
48 var connectUtils = require("express/node_modules/connect/lib/utils");
\r
50 var RedisStore = require("connect-redis")(express);
\r
51 var sessionStore = new RedisStore({host:$redisHost,port:$redisPort,pass:$redisPassword});
\r
53 var async = require("async");
\r
55 var path = require("path");
\r
59 app.configure(function(){
\r
60 app.disabled("view cache");
\r
61 app.set("view options", { layout: false })
\r
62 app.set("views", __dirname + "/public");
\r
63 app.set("view engine", "ejs");
\r
64 app.use(express.bodyParser());
\r
65 app.use(express.methodOverride());
\r
66 app.use(express.cookieParser($secret));
\r
67 app.use(express.session({
\r
69 cookie: { httpOnly: false }
\r
71 app.use(app.router);
\r
72 app.use(express.static(__dirname + "/public"));
\r
75 app.configure('development', function(){
\r
76 app.use(express.errorHandler({ dumpExceptions: true, showStack: true }));
\r
79 app.configure('production', function(){
\r
80 app.use(express.errorHandler());
\r
83 function SessionInfomation(token,admin)
\r
90 app.get("/chat", function(req, res){
\r
91 var auth_string = getRandomString($token_length);
\r
92 req.session.items = new SessionInfomation(auth_string,false);
\r
94 var room_number = 0;
\r
95 if(typeof(req.query.rno) != "undefined")
\r
96 room_number = req.query.rno;
\r
97 res.render("chat",{rno:room_number,token:auth_string});
\r
100 app.all("/log/*",express.basicAuth(function (user, pass) {
\r
101 return user === $username && pass === $password;
\r
104 app.get("/log/*",function (req, res) {
\r
105 res.sendfile(__dirname + req.url);
\r
108 app.all("/admin_chat",express.basicAuth(function (user, pass) {
\r
109 return user === $username && pass === $password;
\r
112 app.get("/admin_chat", function(req, res){
\r
113 var auth_string = getRandomString($token_length);
\r
114 req.session.items = new SessionInfomation(auth_string,true);
\r
116 var room_number = 0;
\r
117 if(typeof(req.query.rno) != "undefined")
\r
118 room_number = req.query.rno;
\r
119 res.render("chat",{rno:room_number,token:auth_string});
\r
122 app.all("/admin",express.basicAuth(function (user, pass) {
\r
123 return user === $username && pass === $password;
\r
126 app.get("/admin", function(req, res){
\r
127 renderAdmin(req,res);
\r
130 app.post("/admin",function(req,res){
\r
131 if(req.session.items.token != req.body.token)
\r
133 res.send($invaild_token_message);
\r
136 if(typeof(req.body.erase) != "undefined")
\r
138 removeLog(req.body.file,function(){
\r
139 renderAdmin(req,res);
\r
142 if(typeof(req.body.registor) != "undefined")
\r
144 ipbanlist.Update(req.body.newbanlist,function(){
\r
145 renderAdmin(req,res);
\r
148 if(typeof(req.body.updateroom) != "undefined")
\r
150 $rooms.Update(req.body.newroomlist,function(){
\r
151 renderAdmin(req,res);
\r
156 function renderAdmin(req,res)
\r
158 var auth_string = getRandomString($token_length);
\r
159 req.session.items = {token:auth_string};
\r
160 var iplist = ipbanlist.GetText();
\r
162 fs.readdir($log_directory,function(err,list){
\r
163 res.render("admin", {
\r
165 log_directory:$log_directory,
\r
168 roomlist:$rooms.GetString()
\r
173 function getRandomString(length)
\r
175 var RandomString = "";
\r
176 var BaseString ="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!#$%&'()=~|-^\@[;:],./\`{+*}<>?_";
\r
177 for(var i=0; i<length; i++) {
\r
178 RandomString += BaseString.charAt( Math.floor( Math.random() * BaseString.length));
\r
180 return RandomString
\r
183 function removeLog(files,callback)
\r
185 if(typeof(files) == "undefined")
\r
187 if(typeof(callback) == "function")
\r
193 function(item,callback){
\r
194 fs.unlink($log_directory + "/" + item,callback);
\r
196 function(err,results){
\r
197 if(typeof(callback) == "function")
\r
202 var server = http.createServer(app).listen($port);
\r
203 console.log("Express server listening on port %d in %s mode", $port, app.settings.env);
\r
209 var io = require("socket.io").listen(server);
\r
210 io.configure('production', function(){
\r
211 io.enable('browser client minification'); // minified されたクライアントファイルを送信する
\r
212 io.enable('browser client etag'); // バージョンによって etag によるキャッシングを有効にする
\r
213 io.set('log level', 1); // ログレベルを設定(デフォルトより下げている)
\r
215 var clients = new Array();
\r
217 var ipbanlist = new IpBanCollecion();
\r
218 var $rooms = new RoomInfomationCollection();
\r
220 createLogDirectory();
\r
222 for(var i = 0; i < $max_room_number; i++)
\r
225 .of(GetNameFromRoomNumber(i))
\r
226 .authorization(ParseAuthorization)
\r
227 .on("connection", function (socket) {
\r
228 var ip = GetClientIPAdress(socket);
\r
230 console.log("connected from %s",ip);
\r
232 var rno = GetRoomNumberFromName(socket.namespace.name);
\r
233 var roomconfig = {};
\r
234 $rooms.Get(rno).AddRom(ip);
\r
235 if($rooms.Get(rno).IsVolatile() == false)
\r
237 if($rooms.Get(rno).IsFixedPassword())
\r
238 roomconfig.type = 2;
\r
239 else if($rooms.Get(rno).IsHiddenLogFromRom())
\r
240 roomconfig.type = 3;
\r
242 roomconfig.type = 1;
\r
243 roomconfig.IsOwned = !$rooms.Get(rno).IsFirstAuth();
\r
245 roomconfig.type = 0;
\r
247 roomconfig.admin = socket.handshake.admin;
\r
248 socket.json.emit("send roominfo",roomconfig);
\r
250 var romcount = $rooms.Get(rno).GetRomCount();
\r
251 socket.json.emit("send romcount",romcount);
\r
252 socket.json.broadcast.emit("send romcount",romcount);
\r
254 socket.on("get pastLogList", function (msg) {
\r
255 ParseGetPastLogList(socket,msg);
\r
257 socket.on("get pastLog", function (msg) {
\r
258 ParseGetPastLog(socket,msg);
\r
260 socket.on("join",function(msg){
\r
261 ParseJoin(socket,msg);
\r
263 socket.on("quit",function(msg){
\r
264 ParseQuit(socket,msg);
\r
266 socket.on("set password",function(msg){
\r
267 ParseSetPassword(socket,msg);
\r
269 socket.on("send msg", function (msg) {
\r
270 ParseSendMsg(socket,msg);
\r
272 socket.on("disconnect", function (msg) {
\r
273 ParseDisconnect(socket,msg);
\r
278 function createLogDirectory()
\r
280 fs.exists($log_directory,function(exists){
\r
281 if(exists == false)
\r
282 fs.mkdirSync($log_directory);
\r
286 function ParseAuthorization(handshakeData, callback)
\r
288 if(handshakeData.headers.cookie) {
\r
289 var signedCookie = cookie.parse(handshakeData.headers.cookie);
\r
290 var sessionID = connectUtils.parseSignedCookies(signedCookie, $secret)["connect.sid"];
\r
291 sessionStore.get(sessionID, function (err, session) {
\r
293 if (ipbanlist.IsBaned(handshakeData.address.address))
\r
294 result = "failed get from session store";
\r
297 else if(handshakeData.query.token != session.items.token)
\r
298 result = "invaild token";
\r
299 if(typeof(session) != "undefined" && result == null)
\r
301 handshakeData.admin = session.items.admin;
\r
302 handshakeData.sessionID = sessionID;
\r
304 callback(result,result == null && !err);
\r
307 return callback("failed get cookie", false);
\r
311 function ParseDisconnect(socket,msg)
\r
313 var ip = GetClientIPAdress(socket);
\r
314 var rno = GetRoomNumberFromName(socket.namespace.name);
\r
315 $rooms.Get(rno).RemoveRom(ip);
\r
317 var romcount = $rooms.Get(rno).GetRomCount();
\r
318 socket.json.emit("send romcount",romcount);
\r
319 socket.json.broadcast.emit("send romcount",romcount);
\r
321 //sessionStore.destroy(socket.handshake.sessionID);
\r
323 console.log("disconnected");
\r
326 function ParseSetPassword(socket,msg)
\r
328 var rno = GetRoomNumberFromName(socket.namespace.name);
\r
333 if($rooms.Get(rno).IsVolatile() == false && $rooms.Get(rno).SetPassword(msg.owner,msg.password))
\r
334 newMeg.message = $password_setted_message;
\r
336 newMeg.message = $failed_set_password_message;
\r
337 ParseSendMsg(socket,newMeg);
\r
340 function ParseJoin(socket,msg)
\r
342 var ip = GetClientIPAdress(socket);
\r
344 if(ipbanlist.IsBlockedToWrite(ip))
\r
346 socket.emit("error",$block_message);
\r
350 var rno = GetRoomNumberFromName(socket.namespace.name);
\r
352 $rooms.Get(rno).RemoveRom(ip);
\r
354 var romcount = $rooms.Get(rno).GetRomCount();
\r
355 socket.json.emit("send romcount",romcount);
\r
356 socket.json.broadcast.emit("send romcount",romcount);
\r
358 if($rooms.Get(rno).IsVolatile() == false)
\r
360 if($rooms.Get(rno).IsTimeout() ||
\r
361 $rooms.Get(rno).IsFirstAuth())
\r
363 $rooms.Get(rno).Reset(msg.name);
\r
364 ParseGetPastLog(socket,util.format($log_file_name,rno));
\r
366 else if($rooms.Get(rno).Auth(msg.name,msg.password))
\r
368 ParseGetPastLog(socket,util.format($log_file_name,rno));
\r
372 socket.emit("error",$not_match_password);
\r
379 message:util.format("/enteredby %s %s %s",msg.name,msg.color,msg.mailto),
\r
381 ParseSendMsg(socket,newMeg);
\r
384 function ParseQuit(socket,msg)
\r
386 var ip = GetClientIPAdress(socket);
\r
388 if(ipbanlist.IsBlockedToWrite(ip))
\r
390 socket.emit("error",$block_message);
\r
394 var rno = GetRoomNumberFromName(socket.namespace.name);
\r
398 message:$password_resetted_message,
\r
401 $rooms.Get(rno).AddRom(ip);
\r
403 var romcount = $rooms.Get(rno).GetRomCount();
\r
404 socket.json.emit("send romcount",romcount);
\r
405 socket.json.broadcast.emit("send romcount",romcount);
\r
407 if($rooms.Get(rno).IsVolatile() == false)
\r
409 if($rooms.Get(rno).IsOwner(msg.name))
\r
411 $rooms.Get(rno).Reset(null);
\r
412 ParseSendMsg(socket,newMeg);
\r
414 if(!$rooms.Get(rno).IsFirstAuth() &&
\r
415 !$rooms.Get(rno).IsAuthed(msg.name))
\r
418 $rooms.Get(rno).RemoveAuth(msg.name);
\r
421 newMeg.message = util.format("/quitedby %s",msg.name);
\r
422 ParseSendMsg(socket,newMeg);
\r
427 function ParseSendMsg(socket,msg)
\r
429 var ip = GetClientIPAdress(socket);
\r
431 if(ip in ipbanlist)
\r
433 socket.emit("error",$block_message);
\r
437 var rno = GetRoomNumberFromName(socket.namespace.name);
\r
439 if(msg.name != $system_name &&
\r
440 $rooms.Get(rno).IsVolatile() == false &&
\r
441 !$rooms.Get(rno).IsAuthed(msg.name) &&
\r
442 !$rooms.Get(rno).IsOwner(rno,msg.name))
\r
447 var date = new Date();
\r
449 var repacked_msg = CreateMessage(msg.name,date,msg.message);
\r
451 if(socket.handshake.admin)
\r
452 repacked_msg.ip = ip;
\r
454 socket.json.emit("req msg", repacked_msg);
\r
456 socket.json.broadcast.emit("req msg", repacked_msg);
\r
458 var path = $log_directory + "/" + util.format($log_file_name,rno);
\r
459 var log = new ChatLog(path);
\r
460 log.Save(repacked_msg,ip,rno);
\r
463 function GetNameFromRoomNumber(number)
\r
465 return "/" + number;
\r
468 function GetRoomNumberFromName(name)
\r
470 if(name.charAt(0) == "/")
\r
471 return parseInt(name.substr(1));
\r
472 throw "GetRoomNumberFromName error";
\r
475 function ParseGetPastLogList(socket,msg)
\r
477 var list = fs.readdir($log_directory,function(err,files){
\r
479 var rno = GetRoomNumberFromName(socket.namespace.name);
\r
480 var pattern = $pastlogfile_pattern.replace("%d",rno);
\r
481 for(var i = 0; i < files.length; i++)
\r
483 var logname = files[i];
\r
484 if(logname.match(pattern))
\r
485 text += files[i] + "\n";
\r
487 socket.emit("req pastloglist",text);
\r
491 function ParseGetPastLog(socket,file)
\r
495 var path = $log_directory + "/" + file;
\r
496 var log = new ChatLog(path);
\r
497 log.ToArray(socket.handshake.admin,function(array){
\r
498 socket.json.emit("req pastlog",array);
\r
502 function ChatLog(path)
\r
504 this.ToArray = function(hasIp,callback)
\r
506 var state = fs.stat(path,function(err,state){
\r
509 var array = new Array();
\r
510 var stream = fs.createReadStream(path);
\r
513 .forEach(function(line){
\r
514 var msg = CreateMessageFromText(line.toString());
\r
525 this.Save = function(msg,ip,rno){
\r
526 var text = GetTextFromMessage(msg,ip);
\r
528 SplitLog(rno,function(){
\r
529 WritePastLog(path,text);
\r
533 function GetTextFromMessage(msg,ip)
\r
535 var text = msg.name + "<>" +
\r
543 function SplitLog(rno,callback)
\r
545 var state = fs.stat(path,function(err,state){
\r
546 if(err && typeof(callback) == "function")
\r
551 if(state.size > $spilt_size)
\r
553 var date = new Date();
\r
554 var dateString = ""+date.getFullYear()+date.getMonth()+date.getDate()+date.getHours()+date.getMinutes()+date.getSeconds();
\r
556 var newpath = $log_directory + "/" +
\r
557 util.format($splited_log_file_name,rno,dateString);
\r
558 fs.rename(path,newpath,callback);
\r
560 if(typeof(callback) == "function")
\r
566 function WritePastLog(path,text)
\r
569 function(callback){
\r
570 fs.open(path,"a",callback);
\r
572 function(fd,callback){
\r
573 var buf = new Buffer(text);
\r
574 fs.write(fd,buf,0,Buffer.byteLength(text),null,function(){
\r
585 function GetClientIPAdress(socket)
\r
587 return socket.handshake.headers["x-forwarded-for"] || socket.handshake.address.address;
\r
591 function CreateMessage(name,date,message)
\r
593 var result = {name:name,
\r
599 function CreateMessageFromText(text)
\r
601 var data = text.split("<>");
\r
602 var msg = {name:data[0],
\r
609 //RoomInfomationCollecionクラス
\r
610 function RoomInfomationCollection()
\r
612 var collection = {};
\r
613 this.Get = function(rno){
\r
614 return collection[rno];
\r
616 this.IsContains = function(rno){
\r
617 return rno in collection;
\r
619 this.GetString = function(){
\r
621 for(var rno in collection)
\r
623 if($rooms.Get(rno).IsVolatile())
\r
625 var pass = collection[rno].password;
\r
628 var hiddenlog = collection[rno].hiddenlog;
\r
629 retval += rno + ":" + pass + ":" + hiddenlog + "\r\n";
\r
633 this.GetKeys = function(){
\r
635 for(var rno in collection)
\r
641 this.Update = function(text,callfunc){
\r
643 function(callback){
\r
644 fs.open($room_configure_file_name,"w",callback);
\r
646 function(fd,callback){
\r
647 var buf = new Buffer(text);
\r
648 fs.write(fd,buf,0,Buffer.byteLength(text),null,function(){
\r
652 function(fd,callback){
\r
653 fs.close(fd,function(){
\r
654 GetRoomList(callfunc);
\r
659 function GetRoomList(callback){
\r
661 fs.exists($room_configure_file_name,function(exists){
\r
662 if(exists == false)
\r
664 if(typeof(callback) == "function")
\r
668 var stream = fs.createReadStream($room_configure_file_name);
\r
671 .forEach(function(line){
\r
672 var token = line.toString().replace(/(\r|\n|\r\n)/gm, "").split(":");
\r
673 if(token.length == 1)
\r
675 Add(token[0],null,false);
\r
677 else if(token.length == 2)
\r
679 var rno = token[0];
\r
680 var pass = token[1];
\r
683 Add(rno, pass,false);
\r
685 else if(token.length == 3)
\r
687 var rno = token[0];
\r
688 var pass = token[1];
\r
691 var hiddenlog = false;
\r
692 if(token[2] == "true")
\r
694 Add(rno, pass,hiddenlog);
\r
698 if(typeof(callback) == "function")
\r
705 for(var i = 0; i < $max_room_number; i++)
\r
708 function Add(rno,pass,hiddenlogflag){
\r
709 collection[rno] = new RoomInfomation(pass,hiddenlogflag);
\r
711 collection[rno].owner = $system_name;
\r
713 var $gc_interval_id = setInterval(function(){
\r
714 for(var rno in this.rom_list)
\r
715 collection[rno].GCRomList();
\r
716 },$gc_time_interval);
\r
720 //RoomInfomationクラス
\r
721 function RoomInfomation(pass,hiddenlogflag)
\r
723 this.password = pass;
\r
724 this.rom_list = {};
\r
725 this.authed_list = {};
\r
728 this.hiddenlog = hiddenlogflag;
\r
729 this.IsVolatile = function(){
\r
730 return this.owner == null &&
\r
731 this.password == null &&
\r
732 this.time == null &&
\r
733 this.hiddenlog == null;
\r
735 this.GetRomCount = function(){
\r
737 for(var key in this.rom_list)
\r
741 this.AddRom = function(ip){
\r
742 var date = new Date();
\r
743 this.rom_list[ip] = {time:date.getTime()};
\r
745 this.RemoveRom = function(ip){
\r
746 delete this.rom_list[ip];
\r
748 this.Reset = function(owner){
\r
749 var date = new Date();
\r
750 var time = date.getTime();
\r
751 this.password = null;
\r
752 this.authed_list = {};
\r
753 this.owner = owner;
\r
756 this.IsFirstAuth = function(){
\r
757 return this.owner == null;
\r
759 this.IsAuthed = function(name){
\r
760 return name == this.owner ||
\r
761 name in this.authed_list;
\r
763 this.IsHiddenLogFromRom = function(){
\r
764 return this.hiddenlog;
\r
766 this.IsFixedPassword = function(){
\r
767 return this.owner == $system_name;
\r
769 this.IsOwner = function(name){
\r
770 return this.owner == name;
\r
772 this.IsTimeout = function(){
\r
773 var date = new Date();
\r
774 var current_time = date.getTime();
\r
775 return !this.IsFixedPassword() &&
\r
776 current_time - this.time >= $reset_password_diff;
\r
778 this.RemoveAuth = function(name)
\r
780 delete this.authed_list[name];
\r
782 this.Auth = function(name,password){
\r
783 if(this.password != password)
\r
785 var date = new Date();
\r
786 var time = date.getTime();
\r
788 this.authed_list[name] = "";
\r
791 this.SetPassword = function(owner,password){
\r
792 if(owner == this.owner &&
\r
793 !this.IsFixedPassword() &&
\r
794 !this.IsHiddenLogFromRom())
\r
796 var date = new Date();
\r
797 this.time = date.getTime();
\r
798 this.password = password;
\r
803 this.GCRomList = function(){
\r
804 var date = new Date();
\r
805 var current_time = date.getTime();
\r
806 for(var ip in this.rom_list)
\r
808 if(current_time - this.rom_list[ip].time >= $gc_time_interval)
\r
809 delete this.rom_list[ip];
\r
815 function IpBanCollecion()
\r
817 var collection = {};
\r
818 this.IsBaned = function(ip){
\r
819 return collection[ip] == "r";
\r
821 this.IsBlockedToWrite = function(ip){
\r
822 return ip in collection;
\r
824 this.GetText = function(){
\r
826 for(var key in collection)
\r
828 if(collection[key] == "")
\r
829 text += key + "\r\n";
\r
831 text += key + ":" + collection[key] + "\r\n";
\r
835 this.Update = function(text,callfunc){
\r
837 function(callback){
\r
838 fs.open($ip_ban_list_file_name,"w",callback);
\r
840 function(fd,callback){
\r
841 var buf = new Buffer(text);
\r
842 fs.write(fd,buf,0,Buffer.byteLength(text),null,function(){
\r
846 function(fd,callback){
\r
847 fs.close(fd,function(){
\r
848 GetIpBanList(callfunc);
\r
853 function GetIpBanList(callback)
\r
856 fs.exists($ip_ban_list_file_name,function(exists){
\r
857 if(exists == false)
\r
859 if(typeof(callback) == "function")
\r
863 var stream = fs.createReadStream($ip_ban_list_file_name);
\r
866 .forEach(function(line){
\r
867 var token = line.toString().replace(/(\r|\n|\r\n)/gm, "").split(":");
\r
869 if(token.length == 1)
\r
870 collection[ip] = "";
\r
872 collection[ip] = token[1];
\r
875 if(typeof(callback) == "function")
\r