def send_alive_pkg1(self): """ 发送类型一的心跳包 :return: """ pkg = b'\xff' pkg += md5(b'\x03\x01' + self.salt + self.pwd.encode('ascii')) # MD5_A pkg += b'\x00' * 3 pkg += self.auth_info pkg += struct.pack('!H', int(time.time()) % 0xFFFF) pkg += b'\x00' * 3 data, address = self._send_package(pkg, (self.server_ip, 61440)) if data[0] == 0x07: Log( logging.DEBUG, 0, "[DrCOM.send_alive_pkg1]:Successful sending heartbeat package type 1..." ) else: # 当收到的数据包没法识别的时候,标记当前状态已经为掉线状态 Log( logging.ERROR, 40, "[DrCOM.send_alive_pkg1]:Receive unknown packages content: {}". format(data)) self.alive_flag = False
def send_alive_pkg2(self, num, key, cls): """ 发送类型二的心跳包 :return: """ response = 0 pkg = self._make_alive_package(num=num, key=key, cls=cls) data, address = self._send_package(pkg, (self.server_ip, 61440)) if data[0] == 0x07: Log( logging.DEBUG, 0, "[DrCOM.send_alive_pkg2]:Successful sending heartbeat package 2[{}]..." .format(cls)) response = data[16:20] else: # 当收到的数据包没法识别的时候,标记当前状态已经为掉线状态 Log( logging.ERROR, 50, "[DrCOM.send_alive_pkg2]:Receive unknown packages content: {}". format(data)) self.alive_flag = False return response
def __initialise__(self): """ 尝试获取当前主机的主机名称、MAC地址、联网IP地址 :return: """ self.host_name = hostname() if LOCAL_MAC: # 如果没有指定本机MAC,尝试自动获取 self.mac = bytes().fromhex(LOCAL_MAC) else: self.mac = bytes().fromhex(mac()) if LOCAL_IP: # 如果没有指定本机IP,尝试自动获取 self.ip = LOCAL_IP else: self.ip = ipaddress() if not self.host_name or not self.mac or not self.ip: Log(logging.ERROR, 10, "[DrCOM.__init__]:无法获取本机的NIC信息,请直接提交到该项目issues") raise DrCOMException("无法获取本机的NIC信息") self.socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, socket.IPPROTO_UDP) self.socket.settimeout(3) try: self.socket.bind(("", 61440)) except socket.error: Log(logging.ERROR, 10, "[DrCOM.__init__]:无法绑定61440端口,请检查是否有其他进程占据了该端口") raise DrCOMException("无法绑定本机61440端口")
def logout(self): Log(logging.INFO, 0, "[MagicDrCOM.logout]:Sending logout request to DrCOM Server") try: self._client.logout() self._client.interrupt = True Log(logging.INFO, 0, "[MagicDrCOM.logout]:Successful logout to DrCOM Server") except (DrCOMException, TimeoutException) as exc: raise MagicDrCOMException("Failure on logout: " + exc.info)
def _send_package(self, pkg, server): """ 发送数据包, 每次发送都尝试三次,如果发送三次都失败,触发超时异常 :param pkg: :return: """ last_times = ReTryTimes while last_times > 0 and not self.interrupt: last_times = last_times - 1 clean_socket_buffer(self.socket) self.socket.sendto(pkg, server) try: data, address = self.socket.recvfrom(1024) except socket.timeout: Log( logging.WARNING, 0, "[DrCOM._send_package]:Continue to retry times [{}]...". format(last_times)) continue if data and address: return data, address if self.interrupt: exception = TimeoutException( "[DrCOM._send_package]:Failure on sending package...") exception.last_pkg = pkg raise exception
def send_alive_pkg1(self): """ 发送类型一的心跳包 :return: """ pkg = b'\xff' pkg += md5(b'\x03\x01' + self.salt + self.pwd.encode('ascii')) # MD5_A pkg += b'\x00' * 3 pkg += self.auth_info pkg += struct.pack('!H', int(time.time()) % 0xFFFF) pkg += b'\x00' * 3 data, address = self._send_package(pkg, (self.server_ip, 61440)) if data[0] == 0x07: Log( logging.DEBUG, 0, "[DrCOM.send_alive_pkg1]:Successful sending heartbeat package type 1..." ) else: # 当收到的数据包没法识别的时候 exception = DrCOMException( "[DrCOM.send_alive_pkg1]:Receive unknown packages content...") exception.last_pkg = data raise exception
def _login(self): if self._client.usr == "" or self._client.pwd == "": raise MagicDrCOMException( "Please enter your username and password...") Log(logging.INFO, 0, "[MagicDrCOM.login]:Starting login...") try: self._client.prepare() self._client.login() keep_alive_thread = threading.Thread( target=self._client.keep_alive, args=()) keep_alive_thread.start() Log(logging.INFO, 0, "[MagicDrCOM.login]:Successfully login to server...") except (DrCOMException, TimeoutException) as exc: raise MagicDrCOMException("Failure on login: " + exc.info)
def logout(self): """ 登出,仅测试了BISTU版本 登出过程一共会有6个包,分3组,每组2个 第一组同alive_pkg1的发送与确认 第二组似乎是用于告知网关准备登出 第三组会发送登出的详细信息包括用户名等 """ # 第一组 初步判断是为了判断当前网络是否联通 # 发送的数据包的最后两个字节可能有验证功能 self.send_alive_pkg1() # 第二组 登出准备 # 与alive_pkg1的最后两个字节相同 pkg = b'\x01\x03' pkg += b'\x00\x00' pkg += b'\x0a' pkg += b'\x00' * 15 data, address = self._send_package(pkg, (self.server_ip, 61440)) if data[0:2] != b'\x02\x03': Log( logging.ERROR, 70, "[DrCOM.login]:Receive unknown packages content: {}".format( data)) # 第三组 pkg = self._make_logout_package() data, address = self._send_package(pkg, (self.server_ip, 61440)) if data[0] == 0x04: self.login_flag = False else: Log( logging.ERROR, 71, "[DrCOM.logout]:Receive unknown packages content: {}".format( data)) if self.login_flag: exception = DrCOMException("Failure on logout to DrCOM...") exception.last_pkg = pkg raise exception
def login(self): """ 登录方法 :return: """ self._login() if self.relogin_flag: self._set_interval(self.relogin_check, self._daemon) Log(logging.INFO, 0, "[MagicDrCOM.login]:Starting network check daemon thread...")
def login(self): """ 登录到目标服务器方法 :return: """ pkg = self._make_login_package() data, address = self._send_package(pkg, (self.server_ip, 61440)) Log(logging.DEBUG, 0, "[DrCOM.login]:Receive PKG content: {}".format(data)) if data[0] == 0x04: self.auth_info = data[23:39] # 在这里设置当前为登录状态并且也处于在线状态 self.login_flag = True self.alive_flag = True Log(logging.INFO, 0, "[DrCOM.login]:Successfully login to DrCOM Server...") elif data[0] == 0x05: if len(data) > 32: if data[32] == 0x31: Log( logging.ERROR, 31, "[DrCOM.login]:Failure on login because the wrong username..." ) if data[32] == 0x33: Log( logging.ERROR, 32, "[DrCOM.login]:Failure on login because the wrong password..." ) else: Log( logging.ERROR, 30, "[DrCOM.login]:Receive unknown packages content: {}".format( data)) if not self.login_flag: exception = DrCOMException("Failure on login to DrCOM...") exception.last_pkg = pkg raise exception
def relogin(self): self.relogin_times -= 1 if self.relogin_times >= 0: Log( logging.WARNING, 0, "[MagicDrCOM._auto_relogin]:Starting relogin last %d times..." % self.relogin_times) self._client.logout() time.sleep(5) self._client.prepare() self._client.login() else: raise MagicDrCOMException("Maximum time reties...")
def _interval_loop(self, period, callback, args): """ 模拟事件循环,用来循环调用请求网站方法 :param period: 间隔时间 :param callback: 回调方法 :param args: 参数 :return: """ try: while self._client.login_flag: time.sleep(period) callback(*args) except MagicDrCOMException: Log(logging.ERROR, 120, "[MagicDrCOM._auto_relogin]:超出最大重试次数!")
def keep_alive(self): num = 0 key = b'\x00' * 4 while self.alive_flag: try: self.send_alive_pkg1() key = self.send_alive_pkg2(num, key, cls=1) key = self.send_alive_pkg2(num, key, cls=3) except TimeoutException as exc: Log(logging.ERROR, 60, "[DrCOM.keep_alive]:" + exc.info) self.alive_flag = False break num = num + 2 time.sleep(10)
def prepare(self): """ 获取服务器IP和Salt :return: """ random_value = struct.pack( "<H", int(time.time() + random.randint(0xF, 0xFF)) % 0xFFFF) pkg = b'\x01\x02' + random_value + b'\x0a' + b'\x00' * 15 # 尝试目前已知的学校认证服务器地址 for _ in [(SERVER_IP, 61440), ("1.1.1.1", 61440), ("202.1.1.1", 61440)]: data, address = self._send_package(pkg, _) # 未获取合理IP地址则进行下一个服务器地址尝试 Log(logging.DEBUG, 0, "[DrCOM.prepare]:Receive PKG content: {}".format(data)) if data[0:4] == b'\x02\x02' + random_value: self.server_ip = address[0] self.salt = data[4:8] Log( logging.DEBUG, 0, "[DrCOM.prepare]:Server IP: {}, Salt: {}".format( self.server_ip, self.salt)) return else: Log( logging.WARNING, 20, "[DrCOM.prepare]:Receive unknown packages content: {}". format(data)) if not self.server_ip or not self.salt: exception = DrCOMException( "[DrCOM.prepare]:Cannot find any available server...") exception.last_pkg = pkg raise exception
def _daemon(self): """ 判断网络连通性的方法 Host: 114.114.114.114 OpenPort: 53/tcp Service: domain (DNS/TCP) """ try: socket.setdefaulttimeout(1) socket.socket(socket.AF_INET, socket.SOCK_STREAM).connect( ("114.114.114.114", 53)) Log(logging.INFO, 0, "[MagicDrCOM.check]:Successful connect to network...") return True except socket.error: if self.status == DIEOUT: self.relogin()
def __init__(self): print("欢迎使用BISTU专版的第三方Dr.COM客户端") print("本项目目前由@Ryuchen进行开发和维护") print("如有任何问题欢迎在本项目的github页面提交issue") print("[https://github.com/Ryuchen/MagicDrCOM/issues]") self._usr = "" self._pwd = "" # self._login_flag = False self._alive_check = False self._relogin_flag = ReLoginFlag self._relogin_times = ReLoginTimes self._relogin_check = ReLoginCheck try: self._client = DrCOMClient(self._usr, self._pwd) except DrCOMException as exc: Log(logging.ERROR, 10, "[MagicDrCOMClient.__init__]:无法进行初始化:" + exc.info) raise MagicDrCOMException("请检查本机设置之后重试~")
def send_alive_pkg2(self, num, key, cls): """ 发送类型二的心跳包 :return: """ pkg = self._make_alive_package(num=num, key=key, cls=cls) data, address = self._send_package(pkg, (self.server_ip, 61440)) if data[0] == 0x07: Log( logging.DEBUG, 0, "[DrCOM.send_alive_pkg2]:Successful sending heartbeat package 2[{}]..." .format(cls)) response = data[16:20] return response else: # 当收到的数据包没法识别的时候 exception = DrCOMException( "[DrCOM.send_alive_pkg2]:Receive unknown packages content...") exception.last_pkg = data raise exception