import time import binascii from write_db import write_db from server_certification import certification from private_key_gene import private_key_gene import select from BFS_Dir import BFS_Dir import rsa from reg_limit import limit_ip from exe_limit import exe_limit import traceback import logging import multiprocessing import hashlib #事先生成 processor = encode_decode.prpcrypt(key = "qwerasdfzxcvqazx") success = processor.encrypt('success'.encode('utf-8')) fail = processor.encrypt('fail'.encode('utf-8')) limit = processor.encrypt('limit'.encode('utf-8')) nonexist = processor.encrypt('nonexist'.encode('utf-8')) exist = processor.encrypt('exist'.encode('utf-8')) nonexist = processor.encrypt('nonexist'.encode('utf-8')) disconnect = processor.encrypt('disconnect'.encode('utf-8')) sendfile = processor.encrypt('sendfile'.encode('utf-8')) senddir = processor.encrypt('senddir'.encode('utf-8')) intodir = processor.encrypt('intodir'.encode('utf-8')) login = processor.encrypt('login'.encode('utf-8')) signup = processor.encrypt('signup'.encode('utf-8')) FILEINFO_SIZE = struct.calcsize('128s32sI8s') RECV_BUFSIZE = struct.calcsize("32s32s32ssI2048s") RECV_BUFSIZE2 = struct.calcsize("32s32s32ssI65536s")
def server(): #侦听准备 host= ''#input("input the server's IP address: ") port_list = [8090, 8120, 10520, 20520, 30520] port = 8090 recvSock = socket(AF_INET,SOCK_STREAM) recvSock.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1)#设置IP地址 复用 for port in port_list: try: recvSock.bind((host,port)) except Exception as e: print(e, port,'is occupied') else: break else: print('no port is free') time.sleep(5) return fdmap = {recvSock.fileno():recvSock} recvSock.listen(5) recvSock.setblocking(False)#服务器设置非阻塞 timeout = 10 ep = select.epoll() ep.register(recvSock.fileno(), select.EPOLLIN) message = {} private_aes_processor = {} rsa_private_key = {}#存放服务器rsa私钥 rsa_public_key = {}#存放客户端rsa公钥 require = {} ipmap = {} LogMD5 = {} filepointer = {}#客户待下载文件队列(存放所有文件的文件路径及其当前文件读取游标:[filename, offset]) #侦听到请求后等待连接 while(1): #time.sleep(0.2) events = ep.poll(timeout) if not events: continue for fd, event in events: connection = fdmap[fd] #如果活动connection等于recvSock,表示有新连接 if connection == recvSock: new_connection, addr = recvSock.accept() print ("Client connected—> ", addr) ipmap[new_connection.fileno()] = addr[0]#把文件句柄对应的IP地址储存下来 new_connection.setblocking(False) ep.register(new_connection.fileno(), select.EPOLLIN) fdmap[new_connection.fileno()] = new_connection require[new_connection.fileno()] = b'' return_server_rsa_pubkey(new_connection, rsa_private_key) #检测到断连事件 elif event & select.EPOLLHUP: print("client close") ep.unregister(fd) fdmap[fd].close() del fdmap[fd] del message[fd] del private_aes_processor[fd] del ipmap[fd] del LogMD5[fd] #可读事件 elif event & select.EPOLLIN: #发送完目录后等待后续请求,进行常规单次收发处理 Username_e = ''#初始化 #is_disconnected_flag = 0 #print("Waiting for Logging in or Signing up") require[fd] = fdmap[fd].recv(RECV_BUFSIZE2) length = len(require[fd]) if(RECV_BUFSIZE < length < RECV_BUFSIZE2): #print('can not receive enough size of data, the size is :', length) #ep.modify(fd, select.EPOLLIN) left_data_size = RECV_BUFSIZE2 - length while(1): try: fdmap[fd].setblocking(True) recv_data = fdmap[fd].recv(left_data_size) require[fd] += recv_data left_data_size -= len(recv_data) #print(left_data_size, ' ', len(recv_data)) if(left_data_size == 0): break except BlockingIOError as inst: print("EAGAIN_ALLDONE") continue if(len(require[fd]) == RECV_BUFSIZE): Mode_e, Username_e, Code_e, file_signal, filedata_size, filedata_e = struct.unpack("32s32s32ssI2048s",require[fd]) else: Mode_e, Username_e, Code_e, file_signal, filedata_size, filedata_e = struct.unpack("32s32s32ssI65536s",require[fd]) require[fd] = b'' Mode = rsa.decrypt(Mode_e, rsa_private_key[fd]).decode('utf-8')#注意在这里的mode必须用rsa来解密,意味着全程Mode都只能是rsa加密算法 #print("Mode is ",Mode) #Username = rsa.decrypt(Username_e, rsa_private_key[fd]).decode('utf-8') if(Mode == 'disconnect'):#客户端主动断开连接 fdmap[fd].close() del fdmap[fd] del message[fd] if(fd in private_aes_processor): del private_aes_processor[fd] del ipmap[fd] print("Disconnected") #is_disconnected_flag = 1 break if(Mode == 'login'): print("登录模式") Format = "%ds%ds%dx" % (77,5,1966) pub_key_n, pub_key_e = struct.unpack(Format, filedata_e) client_public_key = rsa.PublicKey(int(pub_key_n), int(pub_key_e)) Username = rsa.decrypt(Username_e, rsa_private_key[fd]) Code = rsa.decrypt(Code_e, rsa_private_key[fd]) cert_res = certification(processor.encrypt(Username), processor.encrypt(Code))#储存在数据库的不能是明文,必须是密文;但不能是客户端直接传过来的rsa密文,应该是服务器固定的AES加密密文,否则不能验证 if(cert_res == 'nonexist'): message[fd] = [rsa.encrypt('nonexist'.encode('utf-8'), client_public_key), '', '', '']#这里也是例外,因为没有登陆成功,没有AES密钥生成,只能rsa加密 #fdmap[fd].send(str(message[Username_e]).encode('utf-8')) ep.modify(fd, select.EPOLLOUT) elif(cert_res == 'success'): #验证成功后生成私钥并返回验证结果 rsa_public_key[fd] = client_public_key#只有验证成功才有要必要保存该次登录的客户端rsa公钥 print("The client: %s is logged in" % Username.decode('utf-8'))#利用已保存的服务器rsa私钥解密 private_key, private_iv = private_key_gene(Username_e)#使用rsa加密的用户名生成AESkey几乎没有重复风险 pr_processor = encode_decode.prpcrypt(key = private_key, iv = private_iv)#生成AES解码器 private_aes_processor[fd] = pr_processor#无需储存AESkey和iv,直接储存AES加密器更节省时间和空间。 private_key_rsa = rsa.encrypt(private_key.encode('utf-8'), rsa_public_key[fd]) private_iv_rsa = rsa.encrypt(private_iv.encode('utf-8'), rsa_public_key[fd]) #print(private_key_rsa,private_iv_rsa,type(private_key_rsa)) UserDir = dir_list(Username.decode('utf-8')) UserDir_e = pr_processor.encrypt(UserDir.encode('utf-8'))#为了保证速度,对大型数据只能用AES加密,但因为随着AES密钥一起返回,所以客户端完全可以使用AES解密 message[fd] = [rsa.encrypt('success'.encode('utf-8'), rsa_public_key[fd]), private_key_rsa, private_iv_rsa, '', UserDir_e]#注意,此时success信号只能用rsa加密,因为客户端先判断是否认证成功才获取AES密钥,所以此处决不能使用AES加密 #private_key_iv = struct.pack("16s16s", private_key.encode('utf-8'), private_iv.encode('utf-8')) #发送加密后结果、密钥和盐 #fdmap[fd].send(str(message[Username_e]).encode('utf-8')) #检测用户日志及其MD5存在与否 Log_dir = '/usr/local/' + Username.decode('utf-8') + '/' + Username.decode('utf-8') + '.log' LogMD5_dir = '/usr/local/' + Username.decode('utf-8') + '/' + Username.decode('utf-8') + '_md5.txt' if(not os.path.isfile(Log_dir)): os.makedirs(Log_dir) else:pass if(not os.path.isfile(LogMD5_dir)): os.system('touch ' + LogMD5_dir) with open(LogMD5_dir, 'wb') as fh: m = hashlib.md5() m.update('0'.encode('utf-8')) fh.write(m.hexdigest().encode('utf-8')) else:#若存在MD5应该读取MD5到LogMD5字典中 with open(LogMD5_dir, 'rb') as fh: LogMD5[fd] = fh.read().decode('utf-8') ep.modify(fd, select.EPOLLOUT) print("The result, private key and iv are sent to the client: %s" % Username.decode('utf-8')) continue else: message[fd] = [rsa.encrypt('fail'.encode('utf-8'), client_public_key), '', '', ''] #fdmap[fd].send(str(message[Username_e]).encode('utf-8')) ep.modify(fd, select.EPOLLOUT) elif(Mode == 'signup'):#注册模式 print("注册模式") now = time.time() Format = "%ds%ds%dx" % (77,5,1966) pub_key_n, pub_key_e = struct.unpack(Format, filedata_e) client_public_key = rsa.PublicKey(int(pub_key_n), int(pub_key_e)) if(limit_ip(ipmap[fd], now)):#先判断ip有没有被限制 message[fd] = [rsa.encrypt('limit'.encode('utf-8'), client_public_key), '', '', '']#这时候只有客户端的rsa公钥,只能使用rsa加密,这是个例外 ep.modify(fd, select.EPOLLOUT) print("is limited") else: Username = rsa.decrypt(Username_e, rsa_private_key[fd]) Code = rsa.decrypt(Code_e, rsa_private_key[fd]) print("no limit") sign = write_db(ipmap[fd], processor.encrypt(Username), processor.encrypt(Code))#经过服务器AES加密的用户信息才能写入数据库 if(sign == True): message[fd] = [rsa.encrypt('success'.encode('utf-8'), client_public_key), '', '', ''] ep.modify(fd, select.EPOLLOUT) else: message[fd] = [rsa.encrypt('fail'.encode('utf-8'), client_public_key), '', '', ''] ep.modify(fd, select.EPOLLOUT) elif(Mode == 'sendfile'):#等待接收文件(路径、头信息、数据) Receive_File(ep, fd, fdmap[fd], message[fd], private_aes_processor[fd], rsa_public_key[fd], file_signal, filedata_size, filedata_e) elif(Mode == 'senddir'):#等待接受空文件夹 Receive_Dir(ep, fd, fdmap[fd], message[fd], private_aes_processor[fd], rsa_public_key[fd], filedata_size, filedata_e) elif(Mode == 'intodir'): Return_Into_Dir(ep, fd, fdmap[fd], message[fd], private_aes_processor[fd], rsa_public_key[fd], filedata_size, filedata_e) elif(Mode == 'refresh'): Username = private_aes_processor[fd].decrypt(Username_e) UserDir = dir_list(Username.decode('utf-8')) UserDir_e = private_aes_processor[fd].encrypt(UserDir.encode('utf-8'))#为了保证速度,对大型数据只能用AES加密,但因为随着AES密钥一起返回,所以客户端完全可以使用AES解密 message[fd] = [rsa.encrypt('success'.encode('utf-8'), rsa_public_key[fd]), '', '', '', UserDir_e] ep.modify(fd, select.EPOLLOUT) elif(Mode == 'download'): Format = "%ds%dx" %(filedata_size, 2048 - filedata_size) filename = private_aes_processor[fd].decrypt(struct.unpack(Format, filedata_e)[0]).decode('utf-8')#解密出客户想要下载的文件\目录完整路径 if(not os.path.exists(filename)): message[fd] = [rsa.encrypt('nonexist'.encode('utf-8'), rsa_public_key[fd]), '', '', ''] ep.modify(fd, select.EPOLLOUT) elif(os.path.isfile(filename)):#用户此次下载的只是个文件 file_size = private_aes_processor[fd].encrypt(str(os.stat(filename).st_size).encode('utf-8')) message[fd] = [rsa.encrypt('exist'.encode('utf-8'), rsa_public_key[fd]),'', file_size, '', '', 'download']#引入第六个元素,来供EPOLLOUT来判断是否是下载。 filepointer[fd] = [[filename, 0]]#传输列表中加入这个文件及其初始化游标 ep.modify(fd, select.EPOLLOUT) elif(os.path.isdir(filename)):#用户此次下载的是一个文件夹 filepointer[fd] = [] case_size = 0 filenamelist = eval(file_list(filename))#遍历得到所有子文件的完整目录列表 for file_name in filenamelist: filepointer[fd].append([file_name, 0])#传输列表中加入这个文件及其初始化游标 case_size += int(os.stat(file_name).st_size)#这里只能是文件夹总大小 case_size = private_aes_processor[fd].encrypt(str(case_size).encode('utf-8')) message[fd] = [rsa.encrypt('exist'.encode('utf-8'), rsa_public_key[fd]),'', case_size, '', '', 'download']#引入第六个元素,来供EPOLLOUT来判断是否是下载。 ep.modify(fd, select.EPOLLOUT) elif(Mode == 'execute'): ExecuteFile(ep, fd, fdmap[fd], message[fd], private_aes_processor[fd], rsa_public_key[fd], filedata_size, filedata_e, Username_e) elif(Mode == 'pushing'): CheckPushing(ep, fd, fdmap[fd], message[fd], private_aes_processor[fd], rsa_public_key[fd], filedata_size, filedata_e, Username_e, LogMD5) #检测到可写事件 elif event & select.EPOLLOUT: try: if(len(message[fd]) <= 5): Format = "I%ds" % 8188 send_data = str(message[fd]).encode('utf-8') data_size = len(send_data) #print("data_size is ", data_size) send_stream = struct.pack(Format,data_size, send_data) fdmap[fd].sendall(send_stream) if(len(message[fd]) == 5): print( "Sending the database directory to the client") del message[fd][4] ep.modify(fd, select.EPOLLIN) elif(len(message[fd]) == 6 and message[fd][5] == 'download'): if(not filepointer[fd]):#下载队列已经为空,但是别忘了,message的第五个元素可能是最后一个文件读取的最后一次数据,也可能为空(当最后一次传输的是空文件夹时) message[fd][0] = rsa.encrypt('alldone'.encode('utf-8'), rsa_public_key[fd]) while(1): try: fdmap[fd].setblocking(True) send_data = str(message[fd]).encode('utf-8') data_size = len(send_data) Format = "I%ds" % 70000 send_stream = struct.pack(Format, data_size, send_data) send_size = fdmap[fd].sendall(send_stream) #fdmap[fd].send(str(message[fd]).encode('utf-8'))#客户端完全可以根据message[4]是不是空来判断此次是文件数据还是空文件夹。 except BlockingIOError as inst: print("EAGAIN_ALLDONE") continue else:break print("alldone") del message[fd][5] del message[fd][4] ep.modify(fd, select.EPOLLIN)#把此次connection重新添加到可读事件中 else:#传输队列依旧有文件 filename = filepointer[fd][0][0] if(os.path.isfile(filename)): Download_File(ep, fd, fdmap[fd], message[fd], private_aes_processor[fd], rsa_public_key[fd], filepointer[fd]) while(1): try: fdmap[fd].setblocking(True) send_data = str(message[fd]).encode('utf-8') data_size = len(send_data) Format = "I%ds" % 70000 send_stream = struct.pack(Format, data_size, send_data) send_size = fdmap[fd].sendall(send_stream) except BlockingIOError as inst: print("EAGAIN") fdmap[fd].setblocking(False) continue else: fdmap[fd].setblocking(False) #time.sleep(0.1) break #print("sendsize",send_size) ep.modify(fd, select.EPOLLOUT)#保持可写事件 #此时依然是可写事件EPOLLOUT elif(os.path.isdir(filename)):#此时对应文件夹下的空文件夹,注意这个空文件夹可能是队列最后一个待传输项!要有相应的处理 message[fd][0] = rsa.encrypt('filecase'.encode('utf-8'), rsa_public_key[fd])#提示客户端,这个是空文件夹,需要创建即可。 message[fd][1] = private_aes_processor[fd].encrypt("filename".encode('utf-8')) message[fd][4] = ''#空文件夹不应该有数据,只有路径,这里是客户端判定最后一次alldone时接受的数据到底是文件夹还是文件的判据 #fdmap[fd].sendall(str(message[fd]).encode('utf-8')) del filepointer[fd][0]#移出传输队列 ep.modify(fd, select.EPOLLOUT)#保持可写事件 except BlockingIOError as inst: print("EAGAIN") pass except Exception as e: print("写事件失败") print(e) traceback.print_exc() else: print("nothing", event) ep.unregister(recvSock.fileno()) ep.close() recvSock.close()
#-*- encoding=UTF-8 -*-