openssh最新漏洞修复20160120

infoQ 上看到openssh又爆出了严重漏洞,从5.4到7.1均受影响。

我的版本是5.9,所以在rundeck上新建了一个job,在所有客户端节点上运行下面的脚本

#!/bin/bash
/sbin/ip a|grep 115.182|awk '{print $2}'
/bin/sed -i '$a \ \ \ \ UseRoaming no' /etc/ssh/ssh_config
service ssh restart

这样便修复了,比较简单。

那一串反斜杠完全是强迫症,因为看到这个配置文件的最后几行配置前都留了四个空格,所以我也加上了    =。=

Python核心编程第9章练习题9.4

题目是访问一个文件,每次显示25行,并提示按任意键继续。

下面是我写的代码,测试通过,缺点是需要建一个比较大的列表,在文件非常大的时候占用内存过多。

import os

f = open(os.path.expanduser('~/PycharmProjects/MyPython/test.conf'))
flist = f.readlines()

list = [x for x in range(0, len(flist), 25)]

for i in range(1,len(list)):
    for x in flist[list[i - 1]:list[i]]:
        print x,
    print 'press any key to continue'
    os.system('read -s -n 1')
for x in flist[int(list[-1]):len(flist) - 1]:
    print x,

f.close()

 

下面这个是来自运维生存时间的代码,比上面的要好

import os
fobj = open(os.path.expanduser('~/PycharmProjects/MyPython/test.conf'))

count = 0
for eachline in fobj:
    print eachline,
    count += 1
    if count%25 == 0:
        print  "Press any key to continue"
        os.system('read -s -n 1')
        #print
        continue
fobj.close()

 

用python实现linux下的tail -n功能

#!/usr/bin/python
# -*- #coding:cp936

__author__ = "$Author:$"
__version__ = "$Revision:$"
__date__ = "$Date:$"

import os

while True:
    filename = raw_input('Enter filname:')
    if os.path.exists(filename) == False:
        print 'This file is not exist, please try again'
    else:
        lines = raw_input('Enter the number of rows you want:')
        lines = int(lines)
        block_size = 1024
        block = ''
        nl_count = 0
        start = 0
        newfile = filename + '_last_' + str(lines) + '_lines' + '.txt'
        newfsock = open(newfile, 'w')
        fsock = file(filename, 'rU')
        try:
            fsock.seek(0, 2)
            curpos = fsock.tell()
            while(curpos > 0):
                curpos = curpos - (block_size + len(block));
                if curpos < 0:
                     curpos = 0
                fsock.seek(curpos)
                block = fsock.read()
                nl_count = block.count('\n')
                if nl_count >= lines:
                     break
            for n in range(nl_count-lines+1):
                start = block.find('\n', start)+1
        finally:
            fsock.close()
        newfsock.write(block[start:])
        newfsock.close()
        break

 

python脚本-Gui选取excel文件并读取入库

#!/usr/bin/env python
# -*- coding: utf-8 -*-
import xlrd, tkFileDialog

def insertdb(list):
    #appConn = MySQLdb.connect(host='xx.xx.xx.xx', user='root', passwd='123456', db='xxx_db', port=3306)
    #appConn.set_character_set('utf8')
    #appCur = appConn.cursor()
    #appCur.execute('SET NAMES utf8;')
    #appCur.execute('SET CHARACTER SET utf8;')
    #appCur.execute('SET character_set_connection=utf8;')
    for i in list:
        print i
        #appCur.execute("""xxxxxx""")

def getvalue(filename):
    data = xlrd.open_workbook(filename)
    #sheetname = data.sheet_names()
    sheet = data.sheet_by_index(0)
    rows = sheet.nrows
    cols = sheet.ncols
    #print cols
    valuelist = []
    for row in range(1, rows - 1):
        value = sheet.row_values(row)
        #print value
        valuelist.append(value)
    print valuelist
    insertdb(valuelist)

def guirun():

    initialdir = '/Users/cescwu/Downloads/'
    #initialdir = 'd:/'
    filename = tkFileDialog.askopenfilename(initialdir = initialdir, filetypes=[("Text files","*.xlsx")], message = '请选择需要入库的文件')
    print filename
    getvalue(filename)

if __name__ == '__main__':
    guirun()

主要用到python读取excel文件的xlrd插件,还有tkFileDialog模块,入库的步骤省略了。

另外,可以使用pyinstaller将python脚本打包为exe文件。

下载:https://github.com/pyinstaller/pyinstaller/releases/download/3.0/PyInstaller-3.0.tar.gz

解压后安装模块

python setup.py install

将脚本放到解压目录下,运行python pyinstaller.py -F scripts.py即可。

Nginx根据查询字符串屏蔽访问

发现有人在刷网站的注册,IP比较分散,网站注册环节是有验证码,但刷注册的这个验证码一直就没变过

36.251.148.6 - - [12/Oct/2015:13:50:38 +0800] "GET /member/reg.html?step=1&mobile=13872972880&checkcode=v5bb HTTP/1.1" 200 
101.71.32.12 - - [12/Oct/2015:13:50:38 +0800] "GET /member/reg.html?step=1&mobile=13971050735&checkcode=v5bb HTTP/1.1" 200
171.88.126.51 - - [12/Oct/2015:13:50:38 +0800] "GET /member/reg.html?step=1&mobile=15588291414&checkcode=v5bb HTTP/1.1" 200
222.216.31.24 - - [12/Oct/2015:13:50:38 +0800] "GET /member/reg.html?step=1&mobile=13977125525&checkcode=v5bb HTTP/1.1" 200
115.212.189.106 - - [12/Oct/2015:13:50:38 +0800] "GET /member/reg.html?step=1&mobile=13625885599&checkcode=v5bb HTTP/1.1" 200
115.212.189.106 - - [12/Oct/2015:13:50:38 +0800] "GET /member/reg.html?step=1&mobile=13625885599&checkcode=v5bb HTTP/1.1" 200
222.216.31.24 - - [12/Oct/2015:13:50:38 +0800] "GET /member/reg.html?step=1&mobile=15607818598&checkcode=v5bb HTTP/1.1" 200

虽说这样刷是注册不了的,但却增加了后端服务器的压力,干脆封掉,在nginx的server中添加如下内容,$query_string是nginx的内建变量,类似的还有$request_uri和$uri

if  ($query_string  ~* "step=1&mobile=(\d){11}&checkcode=v5bb") {
            return 403;
}

这样配置之后,nginx便直接给恶意访问者返回403,不用经过后端服务器处理了

 

 

 

Iptables学习笔记

Linux防火墙主要包括两个部分:一部分为netfilter是内核过滤的基础架构,一部分为iptables是用户工具,用来编辑具体的过滤规则提供给内核netfilter。当一个数据包通过时,查看包头信息并决定对该包的处理方式。可以选择丢弃该数据包、接受该数据包或是其他更复杂的处理方式。


规则,表,链的概念
  1. 规则(rules)其实就是网络管理员预定义的条件,规则一般的定义为“如果数据包头符合这样的条件,就这样处理这个数据包”。规则存储在内核空间的信息包过滤表中,这些规则分别指定了源地址、目的地址、传输协议(如TCP、UDP、ICMP)和服务类型(如HTTP、FTP和SMTP)等。当数据包与规则匹配时,iptables就根据规则所定义的方法来处理这些数据包,如放行(accept)、拒绝(reject)和丢弃(drop)等。配置防火墙的主要工作就是添加、修改和删除这些规则。
  2. 链(chains)是数据包传播的路径,每一条链其实就是众多规则中的一个检查清单,每一条链中可以有一条或数条规则。当一个数据包到达一个链时,iptables就会从链中第一条规则开始检查,看该数据包是否满足规则所定义的条件。如果满足,系统就会根据该条规则所定义的方法处理该数据包;否则iptables将继续检查下一条规则,如果该数据包不符合链中任一条规则,iptables就会根据该链预先定义的默认策略来处理数据包。
  3. 表(tables)提供特定的功能,iptables内置了4个表,即raw表、filter表、nat表和mangle表,分别用于实现包过滤,网络地址转换和包重构的功能。20131023184415343
 
(1)RAW表
只使用在PREROUTING链和OUTPUT链上,因为优先级最高,从而可以对收到的数据包在连接跟踪前进行处理。一但用户使用了RAW表,在 某个链上,RAW表处理完后,将跳过NAT表和 ip_conntrack处理,即不再做地址转换和数据包的链接跟踪处理了. (2)filter表 主要用于过滤数据包,该表根据系统管理员预定义的一组规则过滤符合条件的数据包。对于防火墙而言,主要利用在filter表中指定的规则来实现对数据包的过滤。Filter表是默认的表,如果没有指定哪个表,iptables 就默认使用filter表来执行所有命令,filter表包含了INPUT链(处理进入的数据包),RORWARD链(处理转发的数据包),OUTPUT链(处理本地生成的数据包)在filter表中只能允许对数据包进行接受,丢弃的操作,而无法对数据包进行更改 (3)nat表 主要用于网络地址转换NAT,该表可以实现一对一,一对多,多对多等NAT 工作,iptables就是使用该表实现共享上网的,NAT表包含了PREROUTING链(修改即将到来的数据包),POSTROUTING链(修改即将出去的数据包),OUTPUT链(修改路由之前本地生成的数据包) (4)mangle表 主要用于对指定数据包进行更改,在内核版本2.4.18 后的linux版本中该表包含的链为:INPUT链(处理进入的数据包),RORWARD链(处理转发的数据包),OUTPUT链(处理本地生成的数据包)POSTROUTING链(修改即将出去的数据包),PREROUTING链(修改即将到来的数据包)   链根据规则判断数据包的处理方式,如果链中的某个规则说丢弃该包,该数据包将立即被丢弃;如果链中规则说接受,则数据包继续向后传输。每个链中包含有具体的规则,每个规则具体明确说明:什么数据包头信息做怎样的处理,如果你的数据包没有匹配第一个规则,则继续对比下一条规则。最后如果没有规则与你的数据包匹配,内核将读取链的默认规则。出于安全的考虑,默认规则一般会被设置为丢弃(DROP)。
 
  1.数据包过来时内核先查看目标地址:这一步被称为路由。
  2.如果该数据是发往本地的,则继续向下传递至INPUT链,当INPUT链允许该数据包,数据包进入本机等待程序接受数据。
  3.否则,如果内核未开启数据转发功能,被转发的数据包将被直接丢弃,如果内核开启了数据转发功能,该数据包将传递给FORWARD链以转发数据,数据进入目标网络接口(网卡接口);此时如果 FORWARD链允许数据包通过,该数据包继续向后传递。
  4.最后在本机的一个程序发送网络数据包时,数据包会立刻进入OUPUT链,根据具体规则决定允许或拒绝发送出去。
 

 
iptables基本语法
iptables [-t table] command [match] [-j target/jump]
-t用来指定规则表,内建的规则表有三个,分别是:nat,mangle,raw,filter,默认为filter表

常用命令

命令 -A, --append
范例 iptables -A INPUT ...
说明 新增规则(追加方式)到某个规则链(这里是INPUT规则链)中,该规则将会成为规则链中的最后一条规则。

命令 -D, --delete
范例 iptables -D INPUT --dport 80 -j DROP
    iptables -D INPUT 1
说明 从某个规则链中删除一条规则,可以输入完整规则,或直接指定规则编号加以删除。

命令 -R, --replace
范例 iptables -R INPUT 1 -s 192.168.0.1 -j DROP
说明 取代现行规则,规则被取代后并不会改变顺序。(1是位置)

命令 -I, --insert
范例 iptables -I INPUT 1 --dport 80 -j ACCEPT
说明 插入一条规则,原本该位置(这里是位置1)上的规则将会往后移动一个顺位。

命令 -L, --list
范例 iptables -L INPUT
说明 列出某规则链中的所有规则。

命令 -F, --flush
范例 iptables -F INPUT
说明 删除某规则链(这里是INPUT规则链)中的所有规则。

命令 -Z, --zero
范例 iptables -Z INPUT
说明 将封包计数器归零。封包计数器是用来计算同一封包出现次数,是过滤阻断式攻击不可或缺的工具。

命令 -N, --new-chain
范例 iptables -N allowed
说明 定义新的规则链。

命令 -X, --delete-chain
范例 iptables -X allowed
说明 删除某个规则链。

命令 -P, --policy
范例 iptables -P INPUT DROP
说明 定义过滤政策。 也就是未符合过滤条件之封包,预设的处理方式。

命令 -E, --rename-chain
范例 iptables -E allowed disallowed

常用匹配参数

-p, --protocol                    (指定协议)
例如: iptables -A INPUT -p tcp           (指定协议)      -p all   所有协议,  -p !tcp 去除tcp外的所有协议。
说明: 比对通讯协议类型是否相符,可以使用 ! 运算子进行反向比对,例如:-p ! tcp ,意思是指除 tcp 以外的其它类型,包含udp、icmp ...等。如果要比对所有类型,则可以使用 all 关键词,例如:-p all。

-s, --src, --source                  (指定源地址,指定源端口--sport)
例如: iptables -A INPUT -s 192.168.1.1
说明: 用来比对封包的来源 IP,可以比对单机或网络,比对网络时请用数字来表示屏蔽,
例如:-s 192.168.0.0/24,比对 IP 时可以使用 ! 运算子进行反向比对,例如:-s ! 192.168.0.0/24。

-d, --dst, --destination           (指定目的地址,指定目的端口--dport)
例如: iptables -A INPUT -d 192.168.1.1
说明: 用来比对封包的目的地 IP,设定方式同上。

-i, --in-interface                      (指定入口网卡)      -i  eth+   所有网卡
例如: iptables -A INPUT -i eth0
说明: 用来比对封包是从哪片网卡进入,可以使用通配字符 + 来做大范围比对,
例如:-i eth+ 表示所有的 ethernet 网卡,也以使用 ! 运算子进行反向比对,例如:-i ! eth0。

-o, --out-interface                   (指定出口网卡)
例如: iptables -A FORWARD -o eth0
说明: 用来比对封包要从哪片网卡送出,设定方式同上。

--sport, --source-port              (源端口)
例如: iptables -A INPUT -p tcp --sport 22
说明: 用来比对封包的来源端口号,可以比对单一端口号,或是一个范围,
例如:--sport 22:80,表示从 22 到 80 端口之间都算是符合件,如果要比对不连续的多个端口号,则必须使用 --multiport 参数,详见后文。比对端口号号时,可以使用 ! 运算子进行反向比对。

--dport, --destination-port     (目的端口)
例如: iptables -A INPUT -p tcp --dport 22
说明: 用来比对封包的目的端口号,设定方式同上。

--tcp-flags                                  (只过滤TCP中的一些包,比如SYN包,ACK包,FIN包,RST包等等)
例如: iptables -p tcp --tcp-flags SYN,FIN,ACK SYN
说明:  比对 TCP 封包的状态旗号,参数分为两个部分,第一个部分列举出想比对的旗号,
第二部分则列举前述旗号中哪些有被设,未被列举的旗号必须是空的。
TCP 状态旗号包括:SYN(同步)、ACK(应答)、FIN(结束)、RST(重设)、URG(紧急)PSH(强迫推送) 等均可使用于参数中,除此之外还可以使用关键词 ALL 和 NONE 进行比对。比对旗号时,可以使用 ! 运算子行反向比对。

-m multiport --source-port
例如: iptables -A INPUT -p tcp -m multiport --source-port 22,53,80,110
说明: 用来比对不连续的多个来源端口号,一次最多可以比对15个端口号,可以使用 ! 运算子进行反向比对。

-m multiport --destination-port
例如: iptables -A INPUT -p tcp -m multiport --destination-port 22,53,80,110
说明: 用来比对不连续的多个目的地端口号,设定方式同上。

-m multiport --port
例如: iptables -A INPUT -p tcp -m multiport --port 22,53,80,110
说明: 这个参数比较特殊,用来比对来源端口号和目的端口号相同的封包,设定方式同上。
注意:在本例如:中,如果来源端口号为 80目的地端口号号为 110,这种封包并不算符合条件。

--icmp-type
例如: iptables -A INPUT -p icmp --icmp-type 8
说明: 用来比对 ICMP 的类型编号,可以使用代码或数字编号来进行比对。
请打 iptables -p icmp --help 来查看有哪些代码可用。

-m limit --limit
例如: iptables -A INPUT -m limit --limit 3/hour
说明: 用来比对某段时间内封包的平均流量,上面的例子是用来比对:每小时平均流量是否超过一次 3 个封包。 
除了每小时平均次外,也可以每秒钟、每分钟或每天平均一次,默认值为每小时平均一次,参数如后: /second、 /minute、/day。 
除了进行封数量的比对外,设定这个参数也会在条件达成时,暂停封包的比对动作,以避免因骇客使用洪水攻击法,导致服务被阻断。

--limit-burst
例如: iptables -A INPUT -m limit --limit-burst 5
说明: 用来比对瞬间大量封包的数量,上面的例子是用来比对一次同时涌入的封包是否超过 5 个(这是默认值),超过此上限的封将被直接丢弃。使用效果同上。

-m mac --mac-source
例如: iptables -A INPUT -m mac --mac-source 00:00:00:00:00:01
说明: 用来比对封包来源网络接口的硬件地址,这个参数不能用在 OUTPUT 和 Postrouting规则炼上,这是因为封包要送出到网后,才能由网卡驱动程序透过 ARP 通讯协议查出目的地的 MAC 地址,所以 iptables 在进行封包比对时,并不知道封包会送到个网络接口去。

-m owner --uid-owner
例如: iptables -A OUTPUT -m owner --uid-owner 500
说明: 用来比对来自本机的封包,是否为某特定使用者所产生的,这样可以避免服务器使用 root 或其它身分将敏感数据传送出,可以降低系统被骇的损失。可惜这个功能无法比对出来自其它主机的封包。

-m owner --gid-owner
例如: iptables -A OUTPUT -m owner --gid-owner 0
说明: 用来比对来自本机的封包,是否为某特定使用者群组所产生的,使用时机同上。

-m owner --pid-owner
例如: iptables -A OUTPUT -m owner --pid-owner 78
说明: 用来比对来自本机的封包,是否为某特定行程所产生的,使用时机同上。

-m owner --sid-owner
例如: iptables -A OUTPUT -m owner --sid-owner 100
说明: 用来比对来自本机的封包,是否为某特定联机(Session ID)的响应封包,使用时机同上。

-m state --state
例如: iptables -A INPUT -m state --state RELATED,ESTABLISHED
说明: 用来比对联机状态,联机状态共有四种:INVALID、ESTABLISHED、NEW 和 RELATED。INVALID 表示该封包的联机编号(Session ID)无法辨识或编号不正确。ESTABLISHED 表示该封包属于某个已经建立的联机。

-j参数的常用处理动作

-j参数用来指定要进行的处理动作,常用的处理动作包括:ACCEPT、REJECT、DROP、REDIRECT、MASQUERADE、LOG、DNAT、SNAT、MIRROR、QUEUE、RETURN、MARK,分别说明如下:
ACCEPT        将封包放行,进行完此处理动作后,将不再比对其它规则,直接跳往下一个规则链(natostrouting)。

REJECT        拦阻该封包,并传送封包通知对方,可以传送的封包有几个选择:ICMP port-unreachable、ICMP echo-reply 或是 tcp-reset(这个封包会要求对方关闭联机),进行完此处理动作后,将不再比对其它规则,直接中断过滤程序。 
例如:iptables -A FORWARD -p TCP --dport 22 -j REJECT --reject-with tcp-reset

DROP          丢弃封包不予处理,进行完此处理动作后,将不再比对其它规则,直接中断过滤程序。

REDIRECT      将封包重新导向到另一个端口(PNAT),进行完此处理动作后,将会继续比对其它规则。 这个功能可以用来实作通透式porxy 或用来保护 web 服务器。
例如:iptables -t nat -A PREROUTING -p tcp --dport 80 -j REDIRECT --to-ports 8080

MASQUERADE    改写封包来源 IP 为防火墙 NIC IP,可以指定 port 对应的范围,进行完此处理动作后,直接跳往下一个规则(mangleostrouting)。这个功能与 SNAT 略有不同,当进行 IP 伪装时,不需指定要伪装成哪个 IP,IP 会从网卡直接读,当使用拨接连线时,IP 通常是由 ISP 公司的 DHCP 服务器指派的,这个时候 MASQUERADE 特别有用。
例如:iptables -t nat -A POSTROUTING -p TCP -j MASQUERADE --to-ports 1024-31000

LOG           将封包相关讯息纪录在 /var/log 中,详细位置请查阅 /etc/syslog.conf 组态档,进行完此处理动作后,将会继续比对其规则。
例如:iptables -A INPUT -p tcp -j LOG --log-prefix "INPUT packets"

SNAT          改写封包来源 IP 为某特定 IP 或 IP 范围,可以指定 port 对应的范围,进行完此处理动作后,将直接跳往下一个规则(mangleostrouting)。
例如:iptables -t nat -A POSTROUTING -p tcp-o eth0 -j SNAT --to-source 194.236.50.155-194.236.50.160:1024-32000

DNAT          改写封包目的地 IP 为某特定 IP 或 IP 范围,可以指定 port 对应的范围,进行完此处理动作后,将会直接跳往下一个规炼(filter:input 或 filter:forward)。
例如:iptables -t nat -A PREROUTING -p tcp -d 15.45.23.67 --dport 80 -j DNAT --to-destination 192.168.1.1-192.168.1.10:80-100

MIRROR        镜射封包,也就是将来源 IP 与目的地 IP 对调后,将封包送回,进行完此处理动作后,将会中断过滤程序。

QUEUE         中断过滤程序,将封包放入队列,交给其它程序处理。透过自行开发的处理程序,可以进行其它应用,
例如:计算联机费......等。

RETURN        结束在目前规则炼中的过滤程序,返回主规则炼继续过滤,如果把自订规则炼看成是一个子程序,那么这个动作,就相当提早结束子程序并返回到主程序中。

MARK          将封包标上某个代号,以便提供作为后续过滤的条件判断依据,进行完此处理动作后,将会继续比对其它规则。
例如:iptables -t mangle -A PREROUTING -p tcp --dport 22 -j MARK --set-mark 2

拓展模块

--mac-source	按来源MAC地址匹配
例如:# iptables -t filter -A FORWARD -m --mac-source 00:02:b2:03:a5:f6 -j DROP
拒绝转发来自该MAC地址的数据包

-m multiport [--prots, --sports,--dports] 	按多端口或连续端口匹配
20:  表示20以后的所有端口。20:100  表示20到100的端口。:20 表示20之前的所有端口 
例如:# iptables -A INPUT -p tcp -m multiport --dports 21,20,25,53,80 -j ACCEPT 【多端口匹配】
     # iptables -A INPUT -p tcp --dport 20: -j ACCEPT
     # iptables -A INPUT -p tcp --sport 20:80 -j ACCEPT
     # iptables -A INPUT -p tcp --sport :80 -j ACCEPT

-m limit		按数据包速率和状态匹配
例如: -m limit --limit 50/s -j ACCEPT

-m state		按状态匹配
例如: -m state --state INVALID,RELATED -j ACCEPT

-m connlimit	限制链接数
例如:# iptables -I FORWARD -p tcp -m connlimit --connlimit-above 9 -j DROP        //表示限制链接数最大为9个

-m random		模拟随机丢包率
例如:# iptables -A FORWARD -p icmp -m statistic --mode random --probability 0.31  -j REJECT   //表示31%的丢包率
     # -m random --average 5 -j DROP 表示模拟丢掉5%比例的包

参考: http://my.oschina.net/u/945017/blog/107791   http://blog.163.com/xychenbaihu@yeah/blog/static/132229655201212705752493/   http://wiki.ubuntu.org.cn/IptablesHowTo    http://blog.csdn.net/reyleon/article/details/12976341

 

Mysql主从监控python脚本

脚本通过监控mysql slave status中的Slave_IO_Running,Slave_SQL_Running,Seconds_Behind_Master三个状态参数来判断replication的健康状况,通过邮件发送报警信息 –2015.7.24优化– 实际发现每天都有几次Seconds_Behind_Master的值大于0引发报警的情况,所以将脚本修改为如果连续两次Seconds_Behind_Master大于0才报警,如下:

#!/usr/bin/python
# -*- coding: cp936 -*-

import time
import datetime
import string
import MySQLdb
import smtplib
from email.Header import Header
from email.MIMEText import MIMEText
import urllib


def sendMail(ip, ioStatus, sqlStatus, lastErrNo, lastError, masterLogFile, masterLogFilePos, relayLogFile, relayLogFilePos, relayMasterLogFile, secondsBehindMaster):

    mailto_list = ["abc@xyz.com"]
    mail_host = "mail.xyz.com"
    mail_user = "mon@xyz.com"
    mail_pass = "passwd"
    mail_from = "<mon@xyz.com>"
    mail_postfix = "xyz.com"
    body = ip + " mysql slave status:\n\n IO Thread Status: " + ioStatus + "\n SQL Thread Status: " + sqlStatus + "\n LastErrorNo: " + str(lastErrNo)
    body = body + "\n LastError: " + str(lastError) + "\n Master Log File: " + str(masterLogFile) + "\n Read Master Log File Pos: " + str(masterLogFilePos)
    body = body + "\n Relay Log File: " + relayLogFile + "\n Relay Log File Pos: " + str(relayLogFilePos) +  "\n Relay Master Log File: " + relayMasterLogFile
    body = body + "\n Seconds_Behind_Master: " + str(secondsBehindMaster)

    mail_subject = "(" +ip + ") MYSQL REPLICATION ERROR!!!"
    me = mail_from+"<"+mail_user+">"
    msg = MIMEText(body,_subtype='plain',_charset='gb2312')
    msg['Subject'] = mail_subject
    msg['From'] = mail_from
    msg['To'] = ";".join(mailto_list)
    try:
        s = smtplib.SMTP()
        s.connect(mail_host)
        s.login(mail_user,mail_pass)
        s.sendmail(me, mailto_list, msg.as_string())
        s.close()
        return True
    except Exception, e:
        print str(e)
        return False

def check(ip, username, pwd):
    conn = MySQLdb.connect(host=ip, user=username, passwd=pwd,use_unicode=True,charset='utf8')
    cur = conn.cursor()
    cur.execute("show slave status")
    conn.commit()
    rows = cur.fetchall()

    ioStatus = ""
    sqlStatus = ""
    lastErrNo = 0
    secondsBehindMaster = 0

    logfile = "/var/log/mysql_db_replication_mon_" + str(ip) + ".log"
    if os.path.isfile(logfile):
        #print logfile + ' is exists.'
        cmd = "sed -n '$p' " + logfile + "|awk -F ':' '{print $6}'"
        secondsBehindMaster_last = int(os.popen(cmd).read())
    else:
        print logfile + ' is not exists, I created it.'
        initf = open(logfile,'w')
        initf.close()
        secondsBehindMaster_last = 0

    if len(rows) > 0:
        ioStatus = rows[0][10]
        sqlStatus = rows[0][11]
        lastErrNo = rows[0][18]
        secondsBehindMaster = rows[0][32]
        today = (datetime.date.today().isoformat())
        timestamp = datetime.datetime.strftime(datetime.datetime.today(),"%y%m%d%H%M%S")

    if "Yes" == ioStatus and "Yes" == sqlStatus:
        print >> open(logfile,'a+'), timestamp, ip + ": ioStatus:" + ioStatus + "; sqlStatus:" + sqlStatus + "; lastErrNo:" + str(lastErrNo) + "; Seconds_Behind_Master:" + str(secondsBehindMaster)
        open(logfile,'a+').close()
        if 0 == secondsBehindMaster or 0 == secondsBehindMaster_last:
            cur.close()
            conn.close()
            return

    if "No" == ioStatus and lastErrNo == 23:
        cur.execute("stop slave")
        conn.commit()
        cur.execute("start slave")
        conn.commit()
    cur.close()
    conn.close()
    lastError=rows[0][19]
    masterLogFile=rows[0][5]
    masterLogFilePos=rows[0][6]
    relayLogFile=rows[0][7]
    relayLogFilePos=rows[0][8]
    relayMasterLogFile=rows[0][9]

    try:
        sendMail(ip, ioStatus, sqlStatus, lastErrNo, lastError, masterLogFile, masterLogFilePos, relayLogFile, relayLogFilePos, relayMasterLogFile, secondsBehindMaster)
    except Exception, e:
        print str(e)
        pass

def main():
    SLAVE_ARRAY = ["192.168.0.1", "192.168.0.2", "192.168.0.3"]
    for slave in SLAVE_ARRAY:
        check(slave, "repuser", "reppasswd")

if __name__ == "__main__":
    main()