class ConnectionHandler: """ Infolib.ConnectionHandler ist eine Klasse, die die Grundfunktionalitaet des Infoservers bereitstellt. Sie bindet einen Port, an dem alle Verbindungen eigehen und verteilt die Pakete nach den entsprechenden Kopfinformationen. Sie baut auf allen anderen Klassen in der Infolib auf. Eine Verwendung ausserhalb des Infobook-Projektes wird nicht empfohlen. Eine Dokumentation des Ablaufes finden Sie im GitHub-Wiki unter https://github.org/hansau22/infobook/ """ def __init__(self, port=False): """ Initialisierung und Binden des Ports. Der Port kann via Shell-Argument uebergeben werden, sonst wird versucht, Port 32323 zu binden. @return: None """ self.database = DatabaseHandler("gu.db") self.crypt = EncryptionHandler() self.sid_Pool = Pool(0) self.file_storage = "./files/" self.max_rcv = 2048 self.users = [] # Nutzer, die zum Index Session-ID gehoeren self.ivs = [] # Initialiserungsvektoren self.ctr = [] # Counter self.sesskey = [] # Sessionkeys self.uidstrings = [] # User-ID-Strings #serv_soc = socket.socket(socket.AF_INET, socket.SOCK_STREAM) #file_soc = socket.socket(socket.AF_INET, socket.SOCK_STREAM) serv_soc = socket.socket() if port != False: serv_soc.bind(("", port)) elif len(sys.argv) < 2: serv_soc.bind(("", 32323)) else: serv_soc.bind(("", int(sys.argv[1]))) serv_soc.listen(1) try: while True: komm, addr = serv_soc.accept() data = "" data = komm.recv(self.max_rcv) # Default-Antwort resp = "error - invalid-client-request" # Leere Verbindung if not data: komm.close() continue # Datenpaket ist verschluesslt (= Kein DHEX-Paket) if self.crypt.is_encrypted(data): #body = self.decrypt(data) tmp = split(";", data, 1) body = tmp[1] #print "getting messages :" + body # Wenn nicht entschluesselbar -> Fehler if body == None: komm.send(resp) komm.close() continue body = body.decode("utf-8", "ignore") try: # Kopfdaten und Nutzdaten trennen data = split(";", data, 1) self.header = self.parse_header(data[0]) data = data[1] except IndexError: komm.send(resp) komm.close() continue # Datenpaket encoden #if self.header[0] != "dhex": try: if self.header[0] == "dhex": resp = self.init_dh(data) elif self.header[0] == "auth": resp = self.auth_user(body) elif self.header[0] == "msg": resp = self.recv_msg(body) elif self.header[0] == "getmsg": resp = self.get_msg(body) elif self.header[0] == "gmsg": resp = self.recv_gmsg(body) elif self.header[0] == "getgmsg": resp = self.get_gmsg(body) elif self.header[0] == "reqfile": resp = self.request_file() elif self.header[0] == "regfile": resp = self.register_file(body) elif self.header[0] == "getfile": resp = self.get_globalname(body) elif self.header[0] == "getpic": resp = self.get_profile_pic(body) elif self.header[0] == "adduser": resp = self.add_user(body) elif self.header[0] == "adressbook": resp = self.get_address_book(body) # Antwortpaket senden if self.header[0] == "dhex": komm.send(resp) elif isinstance(resp, list): #for item in resp: #item = (str(item[0]).encode("utf-8", "ignore"), item[1].encode("utf-8", "ignore")) komm.send(self.build_pack(json.dumps(resp))) else: if self.is_error(resp): print "error: " + resp komm.send(resp) else: resp = self.crypt.encode_string(resp) komm.send(self.build_pack(resp)) komm.close() except IndexError: komm.send(resp) komm.close() finally: #for client in clients: # client.close() serv_soc.close() def is_error(self, data): """ Prueft ob eine Antwort eine Fehlermeldung ist @param data: Antwort @type data: str @return: Boolean Ergebnis """ if string.find(data, "error", 0, 4) == -1 : return False else: return True def init_dh(self, data): """ Initialisiert den DH-Schluesselaustausch anhand der Informationen aus der Anfrage des Clients. @param data: Enthaelt den Oeffentlichen Teil vom Partner @type data: str @return: str - Sessionkey """ # DH-Antwort (B) auf die Anfrage (A) ret = self.crypt.init_dh_b(self.sid_Pool.give_next(),data) if ret == False: return "error - DH-initiation-error - DHEX" try: # Alle Felder fuer die neu Initialiserte Session reservieren (befuellen) self.users.append("") self.uidstrings.append("") self.ivs.append(ret[0]) self.ctr.append(ret[1]) self.sesskey.append(ret[2]) #print "sesskey : " + ret[2] return ret[3] except IndexError: return "error - DH-initiation-server-error - DHEX" def decrypt(self, data): """ Entschluesselt ein Datenpaket mit dem Sessionkey, der zur Session-ID gehoert. @param data: Verschluesseltes Paket mit unverschluesselten Kopfinformationen @type data: str @return: str - Unverschluesseltes Paket ohne Kopfinformationen """ try: tmp = split(";", data, 1) # ";" Seperiert Nutz- und Kopfdaten sid = split(":", tmp[0], 2) # Extrahiere Session-ID sid = int(sid[2]) data = self.crypt.decrypt(self.sesskey[sid], self.ctr[sid], tmp[1]) return data except IndexError: return None def encrypt(self, data): """ Verschluesselt die Daten fuer ein Paket. @param data: Daten-String ohne Kopfinformationen @type data: str @return: Verschluesselt Datenpaket ohne Kopfinformationen """ try: sid = self.header[2] #return self.crypt.encrypt(self.sesskey[sid], self.ctr[sid], data) return data except IndexError: return None def parse_header(self, data): """ Verarbeitet Kopfinformationen und wandelt die Informationen in benoetigte Typen @param data: Kopfinformationen @type data: str @return: Array - Kopfinformationen """ try: header = split(":", data, 2) if not header[0] == "dhex": header[2] = int(header[2]) return header except IndexError: pass def build_pack(self, msg): """ Erstellt die Kopfinformationen fuer ein Datenpaket und fuegt die Nachricht an. @param msg: Nachricht ohne Kopfinformationen @type msg: str @return: str - Nachrichtenpaket mit Kopfinformationen """ try: package = "sresp" + ":" + str(self.header[2]) + ";" #enc_msg = self.encrypt(msg) #if enc_msg == None: # return msg package += msg return package except IndexError: return msg def auth_user(self, data): """ Prueft ob eine Nutzer-Passwort Kombination valid ist. @param data: Paket des Clients ohne Kopfinformationen @type data: str @return: str - Digest, der den Nutzer-ID-String enthaelt """ cred = split(":", data, 1) if len(cred) < 2: return "error - not-enough-arguments - AUTH" try: if self.database.auth_user(cred[0], cred[1]) == True: # User-ID String erzeugen dig = self.crypt.get_hash(self.sesskey[self.header[2]] + str(cred[0])) self.uidstrings[self.header[2]] = dig self.users[self.header[2]] = self.database.get_user_id(cred[0]) return dig else: return "error - wrong-credentials - AUTH" except IndexError: return "error - invalid-header - AUTH" def check_uidstring(self, index, string): """ Vergleicht einen Nutzer-ID-String mit dem, der zu dem Nutzer mit der Session-ID gehoert. @param index: Session-ID @type index: int @param string: Nutzer-ID-String @type string: str @return: Boolean - Ergebnis """ try: if self.uidstrings[index] == string: return True return False except IndexError: return False def recv_msg(self, data): """ Traegt eine Nachricht in die Datenbank ein. @param data: Datenpaket des Clients ohne Kopfinformationen @type data: str @return: str - Erfolgs-/Fehlermeldung """ try: sid = self.header[2] tmp = split(":", data, 2) # Nicht alle Felder gegeben if len(tmp) != 3: return "error - not-long-enough - MSG" if self.check_uidstring(sid, tmp[0]): rcv_uid = self.database.get_user_id(tmp[1]) snd_uid = self.users[self.header[2]] print "writing message:" + tmp[2] if not self.database.rcv_message(snd_uid, rcv_uid, tmp[2]): return "error - server-database-error - MSG" return "success - MSG" else: return "error - wrong-uidstring - MSG" except IndexError: return "error - invalid-header - MSG" def get_msg(self, data): """ Gibt dem Client die Nachrichten zurueck. @param data: Letzte MID, die der Client an den Server gibt. @type data: str @return: Array - Nachrichten """ data = split(":", data, 2) try: if not self.check_uidstring(self.header[2], data[0]): # print "wrong uid string :" + data[0] return "error - wrong-credentials - GetMessage" messages = self.database.get_messages_by_last_mid(self.header[2], data[1]) ret_msg = [] for item in messages: username = self.database.get_user_by_id(item[0]) if (username == None) or (item[0] == "False"): username = "******" ret_msg.append((username, item[1])) return ret_msg except IndexError: return "error - internal-database-request-error - MSG" def get_gmsg(self, data): """ Gibt dem Client die Gruppennachrichten zurueck. @param data: Letzte GID, die der Client an den Server gibt. @type data: str @return: Array - Nachrichten """ data = split(":", data, 2) try: if not self.check_uidstring(self.header[2], data[0]): print "wrong uid string" return "error - wrong-credentials - GetGroupMessage" messages = self.database.get_messages_by_last_gid(self.header[2], data[1]) ret_msg = [] for item in messages: groupname = self.database.get_group_by_id(item[0]) if groupname == None: groupname = "Gruppenname unbekannt" ret_msg.append(item[1] + ":" + groupname + ":" + item[2]) return ret_msg except IndexError: return "error - internal-database-request-error - GroupMessage" def recv_gmsg(self, data): """ Traegt eine Broadcast-Nachricht in die Datenbank ein. @param data: Datenpaket des Clients ohne Kopfinformationen @type data: str @return: str - Erfolgs-/Fehlermeldung """ try: sid = self.header[2] tmp = split(":", data, 2) # Nicht alle Felder gegeben if len(tmp) != 3: return "error - not-enough-arguments - GroupMessage" if self.check_uidstring(sid, tmp[0]): rcv_gid = self.database.get_group_id(tmp[1]) snd_uid = self.users[self.header[2]] print "writing group message:" + tmp[2] if not self.database.rcv_brdc_message(snd_uid, rcv_gid, tmp[2]): return "error - server-database-error - GroupMessage" return "success - GroupMessage" else: print "error in uidstring" return "error - wrong-uidstring - GroupMessage" except IndexError: return "error - invalid-header - GroupMessage" def request_file(self): """ Gibt einen Dateistring fuer eine neue Datei zurueck, die ueber FTP hochgeladen werden kann @return: str - Dateistring """ ret_value = self.generate_file_string() if ret_value == None: return "error - storage-full - REQFILE" else: self.database.add_file(ret_value) return ret_value def register_file(self, data): """ Registriert eine Datei (Setzt den Besitzer zu einem Upload und gibt Dateinamen an) @param data: Datenpaket des Clients ohne Kopfinformationen @type data: str @return: str - Erfolgs-/Fehlermeldung """ values = split(":", data, 2) print values try: filestring = values[0] global_name = values[1] del values if not self.database.check_filestring(filestring): return "error - wrong-filestring" if not self.database.register_file(self.users[self.header[2]], global_name, filestring): return "error - server-storage-error" else: return "success - FILE" except IndexError: return "error - invalid-header - FILE" def get_globalname(self, data): """ Sucht eine Datei nach dem Dateinamen und gibt den filestring zuruekc @param data: Datenpaket des Clients ohne Kopfinformationen @type data: str @return: str - filestring / str - error """ filestring = self.database.get_name_by_filestring(data) if not filestring: return "error - file-not-found - FILE" return filestring def generate_file_string(self): """ Generiert einen Datei-String fuer eine neu empfangene Datei Prueft ausserdem, ob eine Datei mit diesem Namen in ./files vorhanden ist @return: str - Dateistring """ for i in range(0, 14): filestring = "" for i in range(0, 10): filestring = filestring + choice(string.ascii_letters) if os.path.exists(self.file_storage + filestring): break else: return filestring return None def get_profile_pic(self, data): """ Gibt dem Nutzer den String zum Profilfoto zurueck @param data: Datenpaket des Clients ohne Kopfinformationen @type data: str @return: str - Dateistring zum Profilbild """ string = self.database.get_profile_pic(data) if not string: return "-" return string def add_user(self, data): """ Fuegt einen neuen Benutzer hinzu @param data: Daten vom Client @type data: str @return: str - Nachricht an den Client """ tmp = split(":", data, 2) if not len(tmp) == 2: return "error - not-enough-arguments - ADDUSER" username = tmp[0] pwhash = tmp[1] if not self.database.check_user(username): return "error - already-present-username - ADDUSER" self.database.add_user(username, pwhash) return "success - ADDUSER" def get_address_book(self, data): """ Gibt das Nutzer-Addressbuch zurueck @param data: uidstring @type data: str @return: Array - [int UID, str Name] """ if not self.check_uidstring(int(self.header[2]), data): return "error - wrong-uidstring - GETADRBOOK" return self.database.get_address_book(self.users[int(self.header[2])])
class DatabaseHandler: """ Infolib.DatabaseHandler ist eine Klasse, die den Datenbankzugriff auf eine sqlite3-Datenbank fuer den Infobook-Server regelt. Eine Verwendung ausserhalb des Infobook-Projektes wird nicht empfohlen. """ def __init__(self, database): """ Initialisierung @param database: Pfad zur Datenbank @type database: str @return: None """ self.db = sqlite3.connect(database) self.db.text_factory = str self.cursor = self.db.cursor() self.init_db() self.mid_Pool = Pool(0, self.get_start_mid()) self.bid_Pool = Pool(0, self.get_start_brdc_mid()) self.fid_Pool = Pool(0, self.get_start_fid()) self.uid_Pool = Pool(0, self.get_start_uid()) def init_db(self): """ Initialisert die Datenbank @return: None """ self.cursor.execute("CREATE TABLE IF NOT EXISTS users(uid INTEGER, username TEXT, password TEXT, profilepic TEXT)") self.cursor.execute("CREATE TABLE IF NOT EXISTS groups(gid INTEGER, member INTEGER, name Text)") self.cursor.execute("CREATE TABLE IF NOT EXISTS messages(mid INTEGER, uidsender INTEGER, uidreceiver INTEGER, content TEXT)") self.cursor.execute("CREATE TABLE IF NOT EXISTS groupmessages(bid INTEGER, uidsender INTEGER, gidreceiver INTEGER, content TEXT)") self.cursor.execute("CREATE TABLE IF NOT EXISTS files(fid INTEGER, localname Text, globalname Text, owner INTEGER)") self.cursor.execute("CREATE TABLE IF NOT EXISTS addressbook(uid INTEGER, addid INTEGER)") self.db.commit() self.cursor.execute("SELECT uid FROM users WHERE uid = 0") if self.cursor.fetchone() == None: self.cursor.execute("INSERT INTO users VALUES(0, 'initial', 'nohash', 'none')") self.cursor.execute("SELECT fid FROM files WHERE fid = 0") if self.cursor.fetchone() == None: self.cursor.execute("INSERT INTO files VALUES(0, 'initial', 'initial', 0)") self.cursor.execute("SELECT mid FROM messages WHERE mid = 0") if self.cursor.fetchone() == None: self.cursor.execute("INSERT INTO messages VALUES(0, 0, 0, 'initial')") self.cursor.execute("SELECT bid FROM groupmessages WHERE bid = 0") if self.cursor.fetchone() == None: self.cursor.execute("INSERT INTO groupmessages VALUES(0, 0, 0, 'initial')") self.cursor.execute("SELECT gid FROM groups WHERE gid = 0") if self.cursor.fetchone() == None: self.cursor.execute("INSERT INTO groups VALUES(0, 0, 'initial')") self.db.commit() def add_user(self, username, pwhash): """ Fuegt einen Nutzer zur Datenbank hinzu @param username: Nutzername @type username: str @param pwhash: SHA256-Verschluesselter Passwort-Hash @type pwhash: str @return: None, False wenn Parameter nicht stimmen """ self.cursor.execute("INSERT INTO users VALUES(?, ?, ?)", (self.uid_Pool.give_next(), username, pwhash)) self.db.commit() def check_user(self, username): """ Prueft ob Nutzer bereits vorhanden ist @param username: Nutzername @type username: str @return: Boolean Erfolg """ self.cursor.execute("SELECT * FROM users WHERE username = ?", [username]) if self.cursor.fetchone() != None: return False return True def auth_user(self, username, pwhash): """ Prueft Nutzer-Passwort Kombination @param username: Nutzername @type username: str @param pwhash: SHA256-Verschluesselter Passwort-Hash @type pwhash: str @return: True, False wenn Parameter nicht stimmen oder Nutzer-Passwort Kombination falsch """ self.cursor.execute("SELECT * FROM users WHERE username=? AND password=?", (str(username), str(pwhash))) if self.cursor.fetchone() != None: return True return False def get_user_id(self, username): """ Gibt die Nutzer-ID eines Benutzers zurueck @param username: Nutzername @type username: str @return: int Nutzer-ID, False bei unbekanntem Nutzer """ self.cursor.execute("SELECT uid FROM users WHERE username=?", [username]) result = self.cursor.fetchone() if result == None: return False return int(result[0]) def get_username_by_id(self, uid): """ Gibt den Nutzernamen nach einer ID zurueck @param uid: Nutzer-ID @type uid: int @return: str - Name """ self.cursor.execute("SELECT username FROM users WHERE uid = ?", [uid]) result = self.cursor.fetchone() if result == None: return False return result[0] def get_group_id(self, name): """ Gibt die Gruppen-ID einer Gruppe zurueck @param name: Gruppenname @type name: str @return: int Gruppen-ID, False bei unbekannter Gruppe """ self.cursor.execute("SELECT gid FROM groups WHERE name=?", [name]) result = self.cursor.fetchone() if result == None: return False return int(result[0]) def get_user_by_id(self, id): """ Gibt den Namen des Nutzers anhand der ID zurueck. @param id: Nutzer-ID @type id: int @return: str Name, None falls Name nicht gefunden """ self.cursor.execute("SELECT username FROM users WHERE uid=?", str(id)) return self.cursor.fetchone() def get_group_by_id(self, gid): """ Gibt den Namen einer Gruppe anhand der ID zurueck. @param gid: Gruppen-ID @type gid: int @return: str Name - None falls Name nicht gefunden """ self.cursor.execute("SELECT name FROM groups WHERE gid=?", gid) return self.cursor.fetchone() def rcv_message(self, uidSender, uidReceiver, data): """ Traegt eine Nachricht in die Datenbank ein @param uidSender: Nutzer-ID des Senders @type uidSender: int @param uidReceiver: Nutzer-ID des Empfaengers @type uidReceiver: int @param data: Nachricht @type data: Nachricht @return: Boolean Erfolg """ data = data.strip() if not isinstance(uidReceiver, list): self.cursor.execute("INSERT INTO messages VALUES(?, ?, ?, ?)", (self.mid_Pool.give_next(), uidSender, uidReceiver, data.encode("utf-8", "ignore"))) else: for item in uidReceiver: self.cursor.execute("INSERT INTO messages VALUES(?, ?, ?, ?)", (self.mid_Pool.give_next(), uidSender, item, data.encode("utf-8", "ignore"))) self.db.commit() return True def get_messages_by_last_mid(self, uidReceiver, last_mid): """ Sendet dem Client die neuen Nachrichten. Alle Nachrichten sind neu, wenn sie eine groessere MID als die uebergebene hat. @param uidReceiver: Empfaenger der Nachrichten @type uidReceiver: str @param last_mid: Letzte bekannte MID @type last_mid: int @return Array - [Sender(str), Nachrichten(str)] """ self.cursor.execute("SELECT uidSender, content FROM messages WHERE MID > ?", [last_mid]) ret_value = [] result = self.cursor.fetchone() while result != None: ret_value.append(result) result = self.cursor.fetchone() return ret_value def get_messages_by_last_group_id(self, uidReceiver, last_gid): """ Sendet dem Client die neuen Gruppennachrichten. Alle Gruppennachrichten sind neu, wenn sie eine groessere GID als die uebergebene hat. @param uidReceiver: Empfaenger der Gruppennachrichten @type uidReceiver: str @param last_gid: Letzte bekannte GID @type last_gid: int @return Array - [Sender(str), Gruppennachrichten(str)] """ self.cursor.execute("SELECT gidreceiver, uidsender, content FROM groupmessages WHERE bid > ?", last_gid) ret_value = [] result = self.cursor.fetchone() while result != None: ret_value.append(result) result = self.cursor.fetchone() return ret_value def rcv_brdc_message(self, uidSender, gidReceiver, data): """ Traegt eine Broadcast-Nachricht in die Datenbank ein @param uidSender: Nutzer-ID des Senders @type uidSender: int @param gidReceiver: Gruppen-ID des Empfaengers @type gidReceiver: int @param data: Nachricht @type data: Nachricht @return: Boolean Erfolg """ self.cursor.execute("INSERT INTO groupmessages VALUES(?, ?, ?, ?)", (self.bid_Pool.give_next(), uidSender, gidReceiver, data)) self.db.commit() return True def add_file(self, local_name): """ Erzeugt einen neuen Dateieintrag @param local_name: Name der Datei auf dem Server @type local_name: str @return: Boolean Erfolg """ self.cursor.execute("INSERT INTO files VALUES(?, ?, ?, ?)", (self.fid_Pool.give_next(), local_name, "", 0)) self.db.commit() return True def register_file(self, owner, globalname, filestring): """ Registriert eine Datei (Setzt Besitzer und Dateinamen) @param owner: Nutzer-ID des Besitzers @type owner: int @param globalname: Dateiname der Datei @type globalname: str @param filestring: Dateistring der Datei @type filestring: str @return: Boolean Erfolg """ self.cursor.execute("UPDATE files SET owner=?, globalname=? WHERE localname=?", (owner, globalname, filestring)) self.db.commit() return True def check_filestring(self, filestring): """ Ueberprueft, ob ein Dateistring existiert @param filestring: Dateistring @type filestring: str @return Boolean Erfolg """ self.cursor.execute("SELECT localname FROM files WHERE localname = ?", [filestring]) if self.cursor.fetchone() != None: return True return False def get_name_by_filestring(self, name): """ Sucht den Dateinamen anhand des Dateistrings @param name: String der Datei @type name: str @return: str Dateiname """ self.cursor.execute("SELECT globalname FROM files WHERE localname = ?", [name]) result = self.cursor.fetchone() if result != None: try: return result[0] except IndexError: return result return False def get_profile_pic(self, uid): """ Gibt den Dateistring zu einem Nutzer zurueck @param uid: Nutzer ID @type uid: int @return: str - Filestring, False falls kein String bekannt """ self.cursor.execute("SELECT profilepic FROM users WHERE uid=?", [uid]) result = self.cursor.fetchone() if result == "None": return False return result def get_start_mid(self): """ Gibt die erste freie Nachrichten-ID zurueck @return: int ID """ self.cursor.execute("SELECT mid FROM messages ORDER BY mid DESC") result = self.cursor.fetchone() if result == None: return 0 return (result[0] + 1) def get_start_brdc_mid(self): """ Gibt die erste freie Broadcast-Nachrichten-ID zurueck @return: int ID """ self.cursor.execute("SELECT bid FROM groupmessages ORDER BY bid DESC") result = self.cursor.fetchone() if result == None: return 0 return (result[0] + 1) def get_start_fid(self): """ Gibt die erste freie Datei-ID @return: int ID """ self.cursor.execute("SELECT fid FROM files ORDER BY fid DESC") result = self.cursor.fetchone() if result == None: return 0 return (result[0] + 1) def get_start_uid(self): """ Gibt die erste freie UID zurueck @return: int ID """ self.cursor.execute("SELECT uid FROM users ORDER BY uid DESC") result = self.cursor.fetchone() if result == None: return 0 return (result[0] + 1) def get_address_book(self, uid): """ User-ID des Senders @param uid: Nutzer-ID @type uid: int @return: Array - [int UID, str Name] """ self.cursor.execute("SELECT addid FROM addressbook WHERE uid = ?", [uid]) ret_value = [] result = self.cursor.fetchone() while result != None: ret_value.append((result[0], self.get_username_by_id(int(result[0])))) result = self.cursor.fetchone() return ret_value