#!usr/bin/perl
#作用: 监视服务器的网站目录下access-log文件夹里的日记记录
# 并分析日记里面的ip访问次数, 如果达到预警次数则自动发送email
# 给管理员
#作者: 猪头三 http://www.x86asm.com
#版本: v0.0.1 测试版
#时间: 2014-07-15
#留言: 本人不是Linux系统专家, 因此写这个脚本是纯粹自己用用,
# 如果你有更好的建议和方法请联系我.
#使用方式: 按照提示修好源码之后, 就传到linux服务器并通过crontab -e加入计划任务定时执行即可
#解决其他路径下的模块包含问题
BEGIN
{
push
@
INC
,
"/Users/PigHeadThree/perl5/lib/perl5"
;
}
#use 5.010;
use
strict
;
use
autodie
;
use
Try
::
Tiny
;
use
Tie
::
File
;
use
Fcntl
'O_RDONLY'
;
use
File
::
Spec
::
Functions
qw
(
rel2abs
) ;
use
File
::
Basename
qw
(
dirname
basename
) ;
use
File
::
Spec
;
use
File
::
Copy
qw
(
copy
);
use
MIME
::
Lite
;
use
MIME
::
Base64
;
use
Authen
::
SASL
;
# 重要:如果服务器没有该模块将会导致email无法发送
#=================测试代码==================
#=================全局变量==================
#目录特征, 防止错误删除(表示你执行脚本要放在此目录下面)(可修改)
my
$g_str_App_Flag
=
'pht_monitor'
;
#匹配access-logs的每条记录是否包含google和bing机器人(可修改)
#google bing的ip都是合法ip
my
$g_str_grep_Bot
=
'googlebot|bingbot'
;
#ip访问次数预警(可修改)
my
$g_int_Limit_VisitIp
=
900
;
#服务器名称(可修改)
my
$g_str_ServerName
=
"PHT Server"
;
#自动发送email(可修改)
my
$g_str_email_Pass
=
'xxxxxxx'
;
my
$g_str_eamil_Smtp
=
'smtp.163.com'
;
#=================函数入口点==================
try
{
&
fun_RunningLog
(
"main() start..."
) ;
&
main
() ;
&
fun_RunningLog
(
"main() end."
) ;
}
catch
{
&
fun_RunningLog
(
"===========main() running error==========="
) ;
};
sub
main
()
{
#比如你的服务器存存在一个站点 www.x86asm.com
#那么该服务器内部会有一个专门的文件夹存放该站点的所有文件
#假设为: x86asm
#那么该目录下会存放一个名为x86asm.com的日记文件,一般路径如下
#/home/x86asm/access-logs/x86asm.com
#我们可以通过分析这个日记文件来得到ip的访问次数
my
$str_Site_URL
=
"www.x86asm.com"
;
my
$str_Site_Name
=
"x86asm_"
;
my
$str_Site_LogFilePath
=
"/home/x86asm/access-logs/x86asm.com"
;
fun_CountTargetSite
(
$str_Site_URL
,
$str_Site_Name
,
$str_Site_LogFilePath
) ;
}
#End main()
#=================函数======================
#统计目标网站的ip访问次数
#param_1 : 目标网站域名
#param_2 : 目标网站简称
#param_3 : 目标网站access_log文件路径
sub
fun_CountTargetSite
()
{
#本函数不支持0参数传递
if
(@
_
!=
3
)
{
return
0
;
}
(
my
$str_param_Site_URL
,
my
$str_param_Site_Name
,
my
$str_param_Site_LogFilePath
)
=
@
_
;
my
%
hash_VisitIp_Count
;
&
fun_RunningLog
(
"[$str_param_Site_URL] : start..."
);
&
fun_GetVisitIp
(
$str_param_Site_LogFilePath
,
$g_int_Limit_VisitIp
,
&
fun_GetLocaltime
(
"/"
)
,
\%
hash_VisitIp_Count
) ;
if
(
&
fun_IsNeedUpdate
(
$str_param_Site_Name
.&
fun_GetLocaltime
(
"_"
)
.
".txt"
,
\%
hash_VisitIp_Count
)
eq
1
)
{
#保存最新统计
&
fun_VisitIpSaveToCurrentPathFile
(
$str_param_Site_Name
.&
fun_GetLocaltime
(
"_"
)
.
".txt"
,
\%
hash_VisitIp_Count
);
#发送EMAIL
my
$str_SendEmail_Data
=
&
fun_GetSendEmailToData
(
$str_param_Site_Name
.&
fun_GetLocaltime
(
"_"
)
.
".txt"
) ;
if
(
$str_SendEmail_Data
ne
0
)
{
&
fun_AutoSendEmailToAlarm
(
$g_str_ServerName
.
"[$str_param_Site_URL]"
.
"Alarm Ip Visit"
,
$str_SendEmail_Data
) ;
}
}
else
{
}
&
fun_RunningLog
(
"[$str_param_Site_URL] : end."
);
}
# End fun_CountTargetSite()
#获取本地时间
#param_1 : 时间分隔符
sub
fun_GetLocaltime
()
{
#本函数不支持0参数传递
if
(@
_
!=
1
)
{
return
0
;
}
#月份对照表
my
%
hash_montoname
=
(
"01"
=>
"Jan"
,
"02"
=>
"Feb"
,
"03"
=>
"Mar"
,
"04"
=>
"Apr"
,
"05"
=>
"May"
,
"06"
=>
"Jun"
,
"07"
=>
"Jul"
,
"08"
=>
"Aug"
,
"09"
=>
"Sep"
,
"10"
=>
"Oct"
,
"11"
=>
"Nov"
,
"12"
=>
"Dec"
) ;
#获取本地时间
my
(
$sec
,
$min
,
$hour
,
$mday
,
$mon
,
$year_off
,
$wday
,
$yday
,
$isdat
)
=
localtime
;
(
$mday
,
$mon
,
$year_off
)
=
(
sprintf
(
"%02d"
,
$mday
)
,
sprintf
(
"%02d"
,
$mon
+1
)
,
$year_off
+1900
);
#月份转换为英文描述
$mon
=
$hash_montoname
{
$mon
} ;
return
$mday
.
$_
[
0
]
.
$mon
.
$_
[
0
]
.
$year_off
;
}
# End fun_GetLocaltime()
#用于日记记录的时间格式
sub
fun_GetLocaltimeToLog
()
{
#本函数不支持参数传递
if
(@
_
)
{
return
0
;
}
#月份对照表
my
@
array_montoname
=
qw
(
Jan
Feb
Mar
Apr
May
Jun
Jul
Aug
Sep
Oct
Nov
Dec
) ;
#获取本地时间
my
(
$sec
,
$min
,
$hour
,
$mday
,
$mon
,
$year_off
,
$wday
,
$yday
,
$isdat
)
=
localtime
;
(
$sec
,
$min
,
$hour
,
$mday
,
$year_off
)
=
(
sprintf
(
"%02d"
,
$sec
)
,
sprintf
(
"%02d"
,
$min
)
,
sprintf
(
"%02d"
,
$hour
)
,
sprintf
(
"%02d"
,
$mday
)
,
$year_off
+1900
);
#月份转换为英文描述
$mon
=
$array_montoname
[
$mon
] ;
return
$mday
.
'/'
.
$mon
.
'/'
.
$year_off
.
' '
.
$hour
.
':'
.
$min
.
':'
.
$sec
;
}
# End fun_GetLocaltimeToLog()
#日记记录
#param_1 : 日记内容
sub
fun_RunningLog
()
{
#本函数不支持0个参数传递
if
(@
_
!=
1
)
{
return
0
;
}
(
my
$str_param_Print
)
=
@
_
;
#获取时间
my
$str_LogTime
=
'['
.&
fun_GetLocaltimeToLog
()
.
']'
;
#打开日记文件, 如果没有则创建
my
$str_Log_FileName
=
'run_'
.&
fun_GetLocaltime
(
'_'
)
.
'.txt'
;
$str_Log_FileName
=
File
::
Spec
->
join
(
&
fun_GetCurrentAppPath
()
,
$str_Log_FileName
) ;
unless
(
-
e
$str_Log_FileName
)
{
unless
(
open
(
HANDLE_OPEN
,
">$str_Log_FileName"
))
{
return
0
;
}
}
else
{
unless
(
open
(
HANDLE_OPEN
,
">>$str_Log_FileName"
))
{
return
0
;
}
}
#开始写入
print
HANDLE_OPEN
$str_LogTime
.
' : '
.
$str_param_Print
.
"\n"
;
close
(
HANDLE_OPEN
) ;
return
1
;
}
#End fun_RunningLog()
#获取当前执行目录
sub
fun_GetCurrentAppPath
()
{
#本函数不支持参数传递
if
(@
_
)
{
return
0
;
}
return
dirname
(
rel2abs
(
__FILE__
)) ;
}
# End fun_GetCurrentAppPath()
#复制目标文件到当前目录
#param_1 : 需要复制到目标文件全路径
sub
fun_CopyFileToCurrentAppPath
()
{
my
$str_CopyFileToPath
=
""
;
my
$str_CopyFileName
=
""
;
my
$str_CurrentAppPath
=
""
;
#本函数不支持0个参数传递
if
(@
_
!=
1
)
{
return
0
;
}
else
{
my
$str_param_CopyFilePath
=
$_
[
0
] ;
#判断文件是否存在 -e 表示 文件 -d 表示目录
if
(
-
e
$str_param_CopyFilePath
)
{
#开始复制(先执行旧文件删除)
$str_CurrentAppPath
=
&
fun_GetCurrentAppPath
() ;
$str_CopyFileName
=
basename
(
$str_param_CopyFilePath
) ;
$str_CopyFileToPath
=
File
::
Spec
->
join
(
$str_CurrentAppPath
,
$str_CopyFileName
) ;
if
(
-
e
$str_CopyFileToPath
)
{
#删除旧文件
if
(
$str_CopyFileToPath
=~
/$g_str_App_Flag/i
)
{
&
fun_RunningLog
(
"fun_CopyFileToCurrentAppPath() deleteing $str_CopyFileToPath..."
) ;
unlink
(
$str_CopyFileToPath
);
&
fun_RunningLog
(
"fun_CopyFileToCurrentAppPath() deleted $str_CopyFileToPath."
) ;
}
}
copy
(
$str_param_CopyFilePath
,
$str_CopyFileToPath
) ;
return
$str_CopyFileToPath
;
}
}
return
0
;
}
# End fun_CopyFileToCurrentAppPath()
#正则表达式提取ip地址
sub
fun_GetIpByStr
()
{
#本函数不支持0个参数传递
if
(@
_
!=
1
)
{
return
0
;
}
(
my
$str_param_Target
)
=
@
_
;
if
(
$str_param_Target
=~
/(\d+\.\d+\.\d+\.\d+)/
)
{
return
"$1"
;
#IP地址
}
return
0
;
}
# End fun_GetIpByStr()
#统计目标ip访问次数
#param_1 : 目标访问日记文件路径
#param_2 : IP访问次数报警
#param_3 : 目标时间时间到访问记录 (02/jul/2014)
#param_4 : 哈希表 (地址传递)
sub
fun_GetVisitIp
()
{
#本函数不支持0个参数传递
if
(@
_
!=
4
)
{
return
0
;
}
#获取参数
(
my
$str_param_VisitIp_LogFilePath
,
my
$int_param_VisitIp_Count
,
my
$time_param_Get
,
my
$hash_param_VisitIp
)
=
@
_
;
#获取本地时间
if
(
$time_param_Get
eq
"NO"
)
{
$time_param_Get
=
&
fun_GetLocaltime
(
"/"
) ;
}
#开始统计
if
(
-
e
$str_param_VisitIp_LogFilePath
)
{
my
$str_VisitIp_CopyToPath
=
&
fun_CopyFileToCurrentAppPath
(
$str_param_VisitIp_LogFilePath
) ;
if
(
$str_VisitIp_CopyToPath
eq
0
)
{
#获取日记文件失败
return
0
;
}
#打开日记文件
my
@
array_VisitIp_Load
;
my
$int_VisitIp_FileLineNums
;
if
(
-
e
$str_VisitIp_CopyToPath
)
{
tie
@
array_VisitIp_Load
,
'Tie::File'
,
$str_VisitIp_CopyToPath
,
mode
=>
O_RDONLY
;
#获取行数
$int_VisitIp_FileLineNums
=
@
array_VisitIp_Load
-
1
;
#逆向循环匹配
my
$str_Temp_Line
=
""
;
my
$str_Temp_VisitIp
=
""
;
my
$str_Temp_VisitIp_Count
=
0
;
my
%
hash_Temp_VisitIp
=
() ;
while
(
$int_VisitIp_FileLineNums
>=
0
)
{
$str_Temp_Line
=
$array_VisitIp_Load
[
$int_VisitIp_FileLineNums
] ;
if
(
$str_Temp_Line
=~
/$time_param_Get/i
)
{
if
(
$str_Temp_Line
=~
/$g_str_grep_Bot/i
)
{
}
else
{
#捕获ip地址
$str_Temp_VisitIp
=
&
fun_GetIpByStr
(
$str_Temp_Line
) ;
if
(
$str_Temp_VisitIp
eq
0
)
{
}
else
{
#保存ip地址以及访问次数(临时)
if
(
exists
$hash_Temp_VisitIp
{
$str_Temp_VisitIp
})
{
$hash_Temp_VisitIp
{
$str_Temp_VisitIp
}
+=
1
;
#判断是否大于报警次数,如果大于则保存
if
(
$hash_Temp_VisitIp
{
$str_Temp_VisitIp
}
>=
$int_param_VisitIp_Count
)
{
if
(
exists
$$hash_param_VisitIp
{
$str_Temp_VisitIp
})
{
$$hash_param_VisitIp
{
$str_Temp_VisitIp
}
+=
1
;
}
else
{
$$hash_param_VisitIp
{
$str_Temp_VisitIp
}
=
$int_param_VisitIp_Count
;
}
}
}
else
{
$hash_Temp_VisitIp
{
$str_Temp_VisitIp
}
=
1
;
}
}
}
}
else
{
last
;
}
$int_VisitIp_FileLineNums
-=
1
;
}
#关闭文件
untie
@
array_VisitIp_Load
;
return
1
;
}
}
else
{
&
fun_RunningLog
(
"fun_GetVisitIp() $str_param_VisitIp_LogFilePath no found."
) ;
}
return
0
;
}
# End fun_GetVisitIp()
#把哈希表到内容保存到当前执行目录下
#param_1 : 文件名称
#param_2 : 哈希内容
sub
fun_VisitIpSaveToCurrentPathFile
()
{
#本函数不支持0个参数传递
if
(@
_
!=
2
)
{
return
0
;
}
(
my
$str_param_SaveToFileName
,
my
$hash_param_Conent
)
=
@
_
;
#校验参数
if
((
length
(
$str_param_SaveToFileName
)
==
0
)
or
((
keys
%
$hash_param_Conent
)
==
0
))
{
return
0
;
}
#开始保存
my
$str_SaveToFilePath
=
File
::
Spec
->
join
(
&
fun_GetCurrentAppPath
()
,
$str_param_SaveToFileName
) ;
if
(
-
e
$str_SaveToFilePath
)
{
#删除旧文件
if
(
$str_SaveToFilePath
=~
/$g_str_App_Flag/i
)
{
&
fun_RunningLog
(
"fun_VisitIpSaveToCurrentPathFile() deleteing $str_SaveToFilePath..."
) ;
unlink
(
$str_SaveToFilePath
);
&
fun_RunningLog
(
"fun_VisitIpSaveToCurrentPathFile() deleted $str_SaveToFilePath."
) ;
}
}
if
(
open
(
HANDLE_FILE
,
">$str_SaveToFilePath"
))
{
my
$hash_Keys
;
my
$hash_Values
;
foreach
$hash_Keys
(
sort
keys
%
$hash_param_Conent
)
{
$hash_Values
=
$$hash_param_Conent
{
$hash_Keys
} ;
print
HANDLE_FILE
"$hash_Keys => $hash_Values \n"
;
}
close
(
HANDLE_FILE
) ;
}
else
{
return
0
;
}
}
# End fun_VisitIpSaveToCurrentPathFile()
#判断是否需要更新日记文件(.txt)
#param_1 : 文件名称
#param_2 : 哈希内容
sub
fun_IsNeedUpdate
()
{
#本函数不支持0个参数传递
if
(@
_
!=
2
)
{
return
0
;
}
(
my
$str_param_FileName
,
my
$hash_param_Conent
)
=
@
_
;
#校验参数
if
((
length
(
$str_param_FileName
)
==
0
)
or
(
keys
%
$hash_param_Conent
==
0
))
{
return
0
;
}
my
$bool_IsNeedUpdate
=
0
;
#更新标志位
my
$str_Ip
=
""
;
my
%
hash_Load_Ip
;
my
$str_OpenFilePath
=
File
::
Spec
->
join
(
&
fun_GetCurrentAppPath
()
,
$str_param_FileName
) ;
#打开文件并匹配ip是否有更新
if
(
-
e
$str_OpenFilePath
)
{
if
(
open
(
HANDLE_OPEN
,
$str_OpenFilePath
))
{
while
(
my
$str_Temp_Line
=
<
HANDLE_OPEN
>
)
{
$str_Ip
=
&
fun_GetIpByStr
(
$str_Temp_Line
) ;
if
(
$str_Ip
eq
0
)
{
}
else
{
# 保存之前统计过的ip
$hash_Load_Ip
{
$str_Ip
}
=
1
;
}
}
close
(
HANDLE_OPEN
);
#开始循环匹配
my
$str_Temp_Value
;
while
((
$str_Ip
,
$str_Temp_Value
)
=
each
%
$hash_param_Conent
)
{
#判断ip是否是存在, 如果不存在则表示需要更新, 返回1
if
(
exists
$hash_Load_Ip
{
$str_Ip
})
{
}
else
{
$bool_IsNeedUpdate
=
1
;
last
;
}
}
}
}
else
{
#没有日记记录文件也表示需要更新
$bool_IsNeedUpdate
=
1
;
}
return
$bool_IsNeedUpdate
;
}
# End fun_IsNeedUpdate()
#自动发送email进行报警
#param_1 : 标题
#param_2 : 内容
sub
fun_AutoSendEmailToAlarm
()
{
#本函数不支持0参数传递
if
(@
_
!=
2
)
{
return
0
;
}
(
my
$str_param_Subject
,
my
$str_param_Data
)
=
@
_
;
if
((
length
(
$str_param_Subject
)
==
0
)
or
(
length
(
$str_param_Data
)
==
0
))
{
return
0
}
my
$msg_Send
=
MIME
::
Lite
->
new
(
From
=>
$g_str_email_From
,
To
=>
$g_str_eamil_To
,
Subject
=>
$str_param_Subject
,
Data
=>
$str_param_Data
) ;
#$msg_Send->attr("content-type" => "text/html") ;
$msg_Send
->
send
(
"smtp"
,
$g_str_eamil_Smtp
,
AuthUser
=>
$g_str_email_From
,
AuthPass
=>
$g_str_email_Pass
,
Timeout
=>
30
) ;
# Debug => 1 表示打印发送时的各种状态记录
return
1
;
}
# End fun_AutoSendEmailToAlarm()
#从指定文件获取内容当作email内容进行发送
#param_1 : 目标文件名 (默认只在当前目录下获取)
sub
fun_GetSendEmailToData
()
{
#本函数不支持0参数传递
if
(@
_
!=
1
)
{
return
0
;
}
(
my
$str_param_FileName
)
=
@
_
;
if
(
length
(
$str_param_FileName
)
==
0
)
{
return
0
;
}
#开始打开文件
my
$str_OpenFilePath
=
File
::
Spec
->
join
(
&
fun_GetCurrentAppPath
()
,
$str_param_FileName
) ;
if
(
-
e
$str_OpenFilePath
)
{
my
$str_Content
=
""
;
if
(
open
(
HANDLE_OPEN
,
$str_OpenFilePath
))
{
while
(
my
$str_Temp_Line
=
<
HANDLE_OPEN
>
)
{
chomp
(
$str_Temp_Line
) ;
$str_Content
.=
$str_Temp_Line
.=
"\n"
;
}
close
(
HANDLE_OPEN
);
if
(
length
(
$str_Content
)
!=
0
)
{
return
$str_Content
;
}
}
}
return
0
;
}
# End fun_GetSendEmailToData()