Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- import threading
- import socket
- import sys
- import os
- from struct import *
- import ipaddress
- import random
- from pprint import pprint
- from easyzone import easyzone
- from datetime import datetime, timedelta
- # Hard Coded DNS Root Servers
- DNS_root_servers = [
- ['A.ROOT-SERVERS.NET.', '3600000', 'A', '198.41.0.4'],
- ['B.ROOT-SERVERS.NET.', '3600000', 'A', '192.228.79.201'],
- ['C.ROOT-SERVERS.NET.', '3600000', 'A', '192.33.4.12'],
- ['D.ROOT-SERVERS.NET.', '3600000', 'A', '199.7.91.13'],
- ['E.ROOT-SERVERS.NET.', '3600000', 'A', '192.203.230.10'],
- ['F.ROOT-SERVERS.NET.', '3600000', 'A', '192.5.5.241'],
- ['G.ROOT-SERVERS.NET.', '3600000', 'A', '192.112.36.4'],
- ['H.ROOT-SERVERS.NET.', '3600000', 'A', '128.63.2.53'],
- ['I.ROOT-SERVERS.NET.', '3600000', 'A', '192.36.148.17'],
- ['J.ROOT-SERVERS.NET.', '3600000', 'A', '192.58.128.30'],
- ['K.ROOT-SERVERS.NET.', '3600000', 'A', '193.0.14.129'],
- ['L.ROOT-SERVERS.NET.', '3600000', 'A', '199.7.83.42'],
- ['M.ROOT-SERVERS.NET.', '3600000', 'A', '202.12.27.33']
- ]
- # Server Configurations
- DNS_IP = '127.0.0.1'
- Local_DNS_Port = 5353
- DNS_Port = 53
- MAX_BUFFER_SIZE = 4096
- SOCKET_TIMEOUT = 0.3
- # Cache structures
- server_ip_dict = {}
- request_response_dict = {}
- class PrintColors:
- WAITING = '\033[5m'
- HEADER = '\033[95m'
- OKBLUE = '\033[94m'
- OKGREEN = '\033[92m'
- WARNING = '\033[93m'
- FAIL = '\033[91m'
- ENDC = '\033[0m'
- BOLD = '\033[1m'
- UNDERLINE = '\033[4m'
- class DnsWorker(threading.Thread):
- def __init__(self, request, addr, sock, config_path):
- super(DnsWorker, self).__init__()
- self.config_path = config_path
- self.sock = sock
- self.request = request
- self.addr = addr
- def run(self):
- ans, resp = self.find_dns(self.request, 1)
- if ans:
- self.sock.sendto(resp, self.addr)
- print(PrintColors.WAITING + PrintColors.OKGREEN + "Found Given Domain" + PrintColors.ENDC)
- else:
- question = {'total_questions': 1}
- off = self.process_question(question, 12, self.request)
- error_message = self.request
- # Change Flags
- error_message = error_message[:2] + self.generate_error(self.request) + \
- error_message[12:off]
- headers = self.process_request(self.request)
- self.save_response_in_cache(error_message, str(headers['questions'][0]))
- self.sock.sendto(error_message, self.addr)
- print(PrintColors.WAITING + PrintColors.FAIL + "Given Domain Data not Found!" + PrintColors.ENDC)
- def find_dns(self, request, depth):
- headers = self.process_request(request)
- domain_name = headers['questions'][0]['q_name']
- if self.config_contains(domain_name):
- zone_file = self.read_config(domain_name)
- ans, generated_data = self.generate_data_from_zone(zone_file, request, headers)
- print(PrintColors.OKBLUE + PrintColors.BOLD + "Found Domain for {} In Configurations".format(
- domain_name.decode()[:-1]) + PrintColors.ENDC)
- return ans, generated_data
- elif self.check_for_valid_cache(str(headers['questions'][0])):
- print(PrintColors.BOLD + "Found Data From Cache for domain {}".format(
- domain_name.decode()[:-1]) + PrintColors.ENDC)
- res = request_response_dict[str(headers['questions'][0])][0]
- if res[1] & 3:
- return False, self.generate_resonse_from_cache(str(headers['questions'][0]), request)
- else:
- return True, self.generate_resonse_from_cache(str(headers['questions'][0]), request)
- else:
- ans, resp = self.dns_root_search(request, depth)
- if ans:
- resp_headers = self.process_request(resp)
- self.save_response_in_cache(resp, str(headers['questions'][0]), resp_headers['answers'][0]['TTL'])
- return ans, resp
- def check_for_valid_cache(self, dom_name):
- if dom_name in request_response_dict:
- cur_ans = request_response_dict[dom_name]
- entry_date = cur_ans[1]
- cur_date = datetime.now()
- ttl = cur_ans[2]
- if entry_date + timedelta(seconds=ttl) < cur_date:
- del request_response_dict[dom_name]
- return False
- else:
- return True
- else:
- return False
- def generate_resonse_from_cache(self, key, request):
- data = request[:2]
- data += request_response_dict[key][0]
- return data
- def generate_data_from_zone(self, zone_file, request, request_headers):
- data = request[:2]
- data += pack("!B", (request[2] | (1 << 7)))
- data += pack("!B", request[3])
- question = {'total_questions': 1}
- off = self.process_question(question, 12, request)
- q_type = request_headers['questions'][0]['q_type']
- data += pack('!H', 1)
- ttl = 900
- if q_type == 1 and zone_file.root.records('A'):
- data += pack('!H', len(zone_file.root.records('A').items))
- data += pack('!I', 0)
- data += request[12:off]
- for ans in zone_file.root.records('A').items:
- data += pack('!BB', (1 << 7) + (1 << 6), 12)
- data += pack('!H', 1)
- data += pack('!H', 1)
- data += pack("!I", ttl)
- data += pack('!H', 4)
- ans = ans.split('.')
- for i in ans:
- data += pack('!B', int(i))
- elif q_type == 2 and zone_file.root.records('NS'):
- data += pack('!H', len(zone_file.root.records('NS').items))
- data += pack('!I', 0)
- data += request[12:off]
- for aut in zone_file.root.records('NS').items:
- data += pack('!BB', (1 << 7) + (1 << 6), 12)
- data += pack('!H', 2)
- data += pack('!H', 1)
- data += pack("!I", ttl)
- data += pack('!H', len(aut) + 1)
- data += self.build_domain_name(aut).encode()
- elif q_type == 5 and zone_file.root.records('CNAME'):
- data += pack('!H', len(zone_file.root.records('CNAME').items))
- data += pack('!I', 0)
- data += request[12:off]
- for aut in zone_file.root.records('NS').items:
- data += pack('!BB', (1 << 7) + (1 << 6), 12)
- data += pack('!H', 5)
- data += pack('!H', 1)
- data += pack("!I", ttl)
- data += pack('!H', len(aut) + 1)
- data += self.build_domain_name(aut).encode()
- elif q_type == 15 and zone_file.root.records('MX'):
- data += pack('!H', len(zone_file.root.records('MX').items))
- data += pack('!I', 0)
- data += request[12:off]
- for pref, mx in zone_file.root.records('MX').items:
- data += pack('!BB', (1 << 7) + (1 << 6), 12)
- data += pack('!H', 15)
- data += pack('!H', 1)
- data += pack("!I", ttl)
- data += pack('!H', len(mx) + 3)
- data += pack('!H', pref)
- data += self.build_domain_name(mx).encode()
- elif q_type == 16 and zone_file.root.records('TXT'):
- data += pack('!H', len(zone_file.root.records('TXT').items))
- data += pack('!I', 0)
- data += request[12:off]
- for text in zone_file.root.records('TXT').items:
- data += pack('!BB', (1 << 7) + (1 << 6), 12)
- data += pack('!H', 16)
- data += pack('!H', 1)
- data += pack("!I", ttl)
- data += pack('!H', len(text) - 1)
- data += pack('!B', len(text) - 2)
- data += text[1:-1].encode()
- elif q_type == 28 and zone_file.root.records('AAAA'):
- data += pack('!H', len(zone_file.root.records('AAAA').items))
- data += pack('!I', 0)
- data += request[12:off]
- for ans in zone_file.root.records('AAAA').items:
- data += pack('!BB', (1 << 7) + (1 << 6), 12)
- data += pack('!H', 28)
- data += pack('!H', 1)
- data += pack("!I", ttl)
- data += pack('!H', 16)
- ip_v6 = ipaddress.ip_address(ans)
- ip_v6 = ip_v6.exploded.split(':')
- print(ip_v6)
- for bit2 in ip_v6:
- hex_int = int(bit2, 16)
- data += pack('!H', hex_int)
- elif q_type == 6 and zone_file.root.records('SOA'):
- data += pack('!H', len(zone_file.root.records('SOA').items))
- data += pack('!I', 0)
- data += request[12:off]
- for soa in zone_file.root.records('SOA').items:
- soa = soa.split(' ')
- data += pack('!BB', (1 << 7) + (1 << 6), 12)
- data += pack('!H', 6)
- data += pack('!H', 1)
- data += pack("!I", ttl)
- data += pack('!H', 20 + len(soa[0]) + 2 + len(soa[1]))
- data += self.build_domain_name(soa[0]).encode()
- data += self.build_domain_name(soa[1]).encode()
- data += pack('!I', int(soa[2]))
- data += pack('!I', int(soa[3]))
- data += pack('!I', int(soa[4]))
- data += pack('!I', int(soa[5]))
- data += pack('!I', int(soa[6]))
- else:
- question = {'total_questions': 1}
- error_message = request
- # Change Flags
- error_message = error_message[:2] + self.generate_error(request) + \
- error_message[12:off]
- return False, error_message
- return True, data
- def process_request(self, data):
- res = {}
- offset: int = 0
- # Headers
- offset = self.process_header(res, offset, data)
- # Questions
- offset = self.process_question(res, offset, data)
- # Answers
- offset = self.process_answer(res, offset, data)
- # Authority
- offset = self.process_authority(res, offset, data)
- # Additional
- self.process_additional(res, offset, data)
- return res
- def parse_flags(self, flag_bytes):
- flags = {}
- # first byte
- flags['QR'] = (flag_bytes[0] & 0x80) >> 7 # 0x80 = 10000000
- flags['Opcode'] = (flag_bytes[0] & 0x78) >> 3 # 0x78 = 01111000
- flags['AA'] = (flag_bytes[0] & 0x04) >> 2 # 0x04 = 00000100
- flags['TC'] = (flag_bytes[0] & 0x02) >> 1 # 0x02 = 00000010
- flags['RD'] = (flag_bytes[0] & 0x01) # 0x01 = 00000001
- # second byte
- flags['RA'] = (flag_bytes[1] & 0x80) >> 7 # 0x80 = 10000000
- flags['Z'] = (flag_bytes[1] & 0x40) >> 6 # 0x70 = 01000000
- flags['AD'] = (flag_bytes[1] & 0x20) >> 5 # 0x70 = 00100000
- flags['CD'] = (flag_bytes[1] & 0x10) >> 4 # 0x70 = 00010000
- flags['RCODE'] = (flag_bytes[1] & 0x0f) # 0x20 = 00001111
- return flags
- # Parsing Section 1 (Header)
- def process_header(self, res, offset, data):
- # Transaction ID
- transaction_id = unpack('!H', data[offset: offset + 2])[0]
- res['ID'] = transaction_id
- # Flags
- offset += 2
- flags = self.parse_flags(data[offset: offset + 2])
- res['flags'] = flags
- # QDCOUNT
- offset += 2
- qd_count = unpack('!H', data[offset: offset + 2])[0]
- res['total_questions'] = qd_count
- # ANCOUNT
- offset += 2
- an_count = unpack('!H', data[offset: offset + 2])[0]
- res['total_answers'] = an_count
- # NSCOUNT
- offset += 2
- ns_count = unpack('!H', data[offset: offset + 2])[0]
- res['total_authoritys'] = ns_count
- # ARCOUNT
- offset += 2
- ar_count = unpack('!H', data[offset: offset + 2])[0]
- res['total_additional'] = ar_count
- return offset + 2
- # Parsing Section 2 (Question)
- def process_question(self, res, offset, data):
- # QNAME
- res['questions'] = []
- for i in range(res['total_questions']):
- cur_question = {}
- # QNAME
- q_name, offset = self.get_domain_rec(data, offset)
- q_name = q_name
- cur_question['q_name'] = q_name
- # QTYPE
- q_type = unpack('!H', data[offset: offset + 2])[0]
- cur_question['q_type'] = q_type
- # QCLASS
- offset += 2
- q_class = unpack('!H', data[offset: offset + 2])[0]
- cur_question['q_class'] = q_class
- res['questions'].append(cur_question)
- return offset + 2
- # Parsing Section 3 (Answer)
- def process_answer(self, res, offset, data):
- res['answers'] = []
- for i in range(res['total_answers']):
- cur_answer = {}
- # NAME
- name, offset = self.get_domain_rec(data, offset)
- name = name
- cur_answer['name'] = name
- # QTYPE
- q_type = unpack('!H', data[offset: offset + 2])[0]
- cur_answer['type'] = q_type
- # QCLASS
- offset += 2
- q_class = unpack('!H', data[offset: offset + 2])[0]
- cur_answer['class'] = q_class
- offset += 2
- ttl = unpack('!I', data[offset: offset + 4])[0]
- cur_answer['TTL'] = ttl
- offset += 4
- rd_length = unpack('!H', data[offset: offset + 2])[0]
- cur_answer['rd_length'] = rd_length
- offset += 2
- self.read_response_data(data, offset, cur_answer)
- offset += rd_length
- res['answers'].append(cur_answer)
- return offset
- # Parsing Section 4 (Authority)
- def process_authority(self, res, offset, data):
- res['authority'] = []
- for i in range(res['total_authoritys']):
- cur_authority = {}
- # NAME
- name, offset = self.get_domain_rec(data, offset)
- name = name
- cur_authority['name'] = name
- # QTYPE
- q_type = unpack('!H', data[offset: offset + 2])[0]
- cur_authority['type'] = q_type
- # QCLASS
- offset += 2
- q_class = unpack('!H', data[offset: offset + 2])[0]
- cur_authority['class'] = q_class
- offset += 2
- ttl = unpack('!I', data[offset: offset + 4])[0]
- cur_authority['TTL'] = ttl
- offset += 4
- rd_length = unpack('!H', data[offset: offset + 2])[0]
- cur_authority['rd_length'] = rd_length
- offset += 2
- self.read_response_data(data, offset, cur_authority)
- offset += rd_length
- res['authority'].append(cur_authority)
- return offset
- # parsing Section 5 (Additional)
- def process_additional(self, res, offset, data):
- res['additional'] = []
- for i in range(res['total_additional']):
- cur_additional = {}
- # NAME
- name, offset = self.get_domain_rec(data, offset)
- name = name
- cur_additional['name'] = name
- # QTYPE
- q_type = unpack('!H', data[offset: offset + 2])[0]
- cur_additional['type'] = q_type
- # QCLASS
- offset += 2
- q_class = unpack('!H', data[offset: offset + 2])[0]
- cur_additional['class'] = q_class
- offset += 2
- ttl = unpack('!I', data[offset: offset + 4])[0]
- cur_additional['TTL'] = ttl
- offset += 4
- rd_length = unpack('!H', data[offset: offset + 2])[0]
- cur_additional['rd_length'] = rd_length
- offset += 2
- self.read_response_data(data, offset, cur_additional)
- offset += rd_length
- res['additional'].append(cur_additional)
- def read_response_data(self, data, offset, res):
- data_type = res['type']
- data_length: int = res['rd_length']
- if data_type == 1: # type A
- res['data'] = ipaddress.IPv4Address(data[offset:offset + 4])
- elif data_type == 28: # type AAAA
- res['data'] = ipaddress.IPv6Address(data[offset:offset + 16])
- elif data_type == 2: # type NS
- res['data'] = self.get_domain_rec(data, offset)[0]
- elif data_type == 5: # type CNAME
- res['data'] = self.get_domain_rec(data, offset)[0]
- elif data_type == 16: # type TXT
- res['data'] = data[offset:offset + data_length]
- elif data_type == 15: # type MX
- mx = {'preferences': unpack('!H', data[offset: offset + 2])[0],
- 'mx': self.get_domain_rec(data, offset + 2)}
- res['data'] = mx
- elif data_type == 6: # type SOA
- soa = {}
- soa['primary_ns'], offset = self.get_domain_rec(data, offset)
- soa['resp_mx'], offset = self.get_domain_rec(data, offset)
- soa['serial'], soa['refresh'], soa['retry'], soa['expire'], soa['min_ttl'] = \
- unpack('!5I', data[offset: offset + 20])
- res['data'] = soa
- else:
- res['data'] = None
- def get_domain_rec(self, data, offset):
- if not data[offset]:
- return b'', offset + 1
- if (data[offset]) >> 6 == 3:
- name_offset = unpack('!H', data[offset: offset + 2])[0]
- name_offset = name_offset & ((1 << 14) - 1)
- return self.get_domain_rec(data, name_offset)[0], offset + 2
- length = data[offset]
- offset += 1
- label = (data[offset: offset + length]) + b'.'
- rec = self.get_domain_rec(data, offset + length)
- label += rec[0]
- return label, rec[1]
- def dns_root_search(self, request, depth):
- sock_root_dns = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
- sock_root_dns.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
- question = {'total_questions': 1}
- self.process_question(question, 12, request)
- i = 1
- for server in DNS_root_servers:
- print(PrintColors.BOLD + self.count(depth) + "Searching Data In Root Server No {}, {} -> {} , Domain : {}".
- format(i, server[0], server[3], question['questions'][0]['q_name'].decode()[:-1] + PrintColors.ENDC))
- resp = None
- for j in range(3):
- sock_root_dns.settimeout(SOCKET_TIMEOUT)
- try:
- sock_root_dns.sendto(request, (server[3], DNS_Port))
- resp = sock_root_dns.recv(MAX_BUFFER_SIZE)
- break
- except:
- continue
- if resp:
- ans, root_resp = self.find_recursively(resp, sock_root_dns, request, depth, [])
- if ans:
- header = self.process_request(root_resp)
- print(
- PrintColors.BOLD + self.count(
- depth) + 'Found Data From Root Server No {}, {} -> {} , Domain : {}'.
- format(i, server[0], server[3],
- question['questions'][0]['q_name'].decode()[:-1]) + PrintColors.ENDC)
- self.save_response_in_cache(root_resp, str(question['questions'][0]), header['answers'][0]['TTL'])
- return ans, root_resp
- else:
- return False, None
- i += 1
- return False, None
- def find_recursively(self, p_resp, root_socket, request, depth, visited_ip):
- dic = self.process_request(p_resp)
- # n = input()
- if dic['total_answers']:
- for answer in dic['answers']:
- if answer['type'] == 1:
- server_ip_dict[dic['questions'][0]['q_name'].decode()[:-1]] = answer['data']
- return True, p_resp
- # First Check in Additional Section for IPv4
- visited_ns = []
- for additional_record in dic['additional']:
- if additional_record['type'] != 1: # Check if it is IpV4
- continue
- visited_ns += [additional_record['name']]
- server = additional_record['name'].decode()[:-1]
- domain = dic['questions'][0]['q_name'].decode()[:-1]
- ip = additional_record['data']
- code = str(ip) + "#" + domain
- if code in visited_ip:
- continue
- visited_ip += [code]
- print(self.count(depth, False) + "Asking {} for {}".format(server, domain))
- ans, final_resp = self.send_new_request(ip, root_socket, request, depth + 1, visited_ip)
- if ans:
- print(
- self.count(depth, False) + "Server {} found Data for {}".format(server, domain))
- return ans, final_resp
- # After that Find all Name Servers
- for authority_record in dic['authority']:
- if authority_record['data'] in visited_ns:
- continue
- if authority_record['type'] != 2:
- continue
- new_request = self.build_request(authority_record['data'])
- request_domain = dic['questions'][0]['q_name'].decode()[:-1]
- domain_needed = authority_record['data'].decode()[:-1]
- print(self.count(depth, False) + "Asking {} for {}".format(domain_needed,
- request_domain))
- if domain_needed in server_ip_dict:
- code = str(server_ip_dict[domain_needed]) + '#' + dic['questions'][0]['q_name'].decode()[:-1]
- if code in visited_ip:
- continue
- visited_ip += [code]
- print(PrintColors.BOLD + self.count(depth) + "Found IP Address From Cache for domain {} : {}".format(
- domain_needed,
- server_ip_dict[
- domain_needed]) + PrintColors.ENDC)
- ans, final_resp = self.send_new_request(server_ip_dict[domain_needed], root_socket, request, depth,
- visited_ip)
- if ans:
- print(self.count(depth, False) + "Asking {} for {}".format(domain_needed, request_domain))
- return ans, final_resp
- else:
- ans, auth_resp = self.find_dns(new_request, depth + 1)
- if ans:
- resp_headers = self.process_request(auth_resp)
- for answer in resp_headers['answers']:
- if answer['type'] == 1:
- server_ip_dict[domain_needed] = answer['data']
- code = str(answer['data']) + '#' + dic['questions'][0]['q_name'].decode()[:-1]
- if code in visited_ip:
- continue
- visited_ip += [code]
- ans, final_resp = self.send_new_request(answer['data'], root_socket, request, depth + 1,
- visited_ip)
- if ans:
- print(
- self.count(depth, False) + "Asking {} for {}".format(domain_needed, request_domain))
- return ans, final_resp
- return False, None
- def send_new_request(self, ip, root_socket, request, depth, visited_ip):
- ipv4_address = str(ip)
- root_resp = None
- for i in range(3):
- root_socket.settimeout(SOCKET_TIMEOUT)
- try:
- root_socket.sendto(request, (ipv4_address, DNS_Port))
- root_resp = root_socket.recv(MAX_BUFFER_SIZE)
- break
- except:
- pprint("Exception: Lost Data or Server Unreachable, Trying again...")
- # continue
- if root_resp:
- ans, new_resp = self.find_recursively(root_resp, root_socket, request, depth, visited_ip)
- if ans:
- return True, new_resp
- else:
- return False, None
- return False, None
- def build_request(self, domain_name):
- req = b''
- transaction_id = pack('!H', random.getrandbits(16))
- req += transaction_id
- flags = self.request[2:4]
- req += flags
- qd_count = pack('!H', 1)
- req += qd_count
- an_count = pack('!H', 0)
- req += an_count
- ns_count = pack('!H', 0)
- req += ns_count
- ar_count = pack('!H', 0)
- req += ar_count
- domain_name = self.build_domain_name(domain_name.decode())
- req += domain_name.encode()
- q_type = pack('!H', 1)
- req += q_type
- q_class = pack('!H', 1)
- req += q_class
- return req
- def build_domain_name(self, domain_name):
- labels = domain_name.split('.')
- res = ''.join([chr(len(label)) + label for label in labels])
- return res
- def config_contains(self, domain_name):
- config_list = os.listdir(self.config_path)
- return domain_name.decode() + 'conf' in config_list
- def read_config(self, domain_name):
- os.chdir(self.config_path)
- domain_name = domain_name.decode()
- path = domain_name + 'conf'
- z = easyzone.zone_from_file(domain_name[:-1], path)
- os.chdir('..')
- return z
- def generate_error(self, req):
- flag = 0x8183
- bytes = pack("!H", flag)
- que_count = req[4:6]
- bytes += que_count
- ans_count = 0
- bytes += pack("!H", ans_count)
- aut_count = 0
- bytes += pack("!H", aut_count)
- add_count = 0
- bytes += pack("!H", add_count)
- return bytes
- def count(self, depth, print_type=True):
- if print_type:
- return "-" * depth
- else:
- return "|" * depth
- def save_response_in_cache(self, root_resp, question, ttl=300):
- to_save_value = root_resp[2:]
- request_response_dict[question] = [to_save_value, datetime.now(), ttl]
- def run_dns_server(config_path):
- print(PrintColors.HEADER + 'Starting DNS Server on {}:{}'.format(DNS_IP, DNS_Port) + PrintColors.ENDC)
- try:
- sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
- sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
- sock.bind(('127.0.0.1', Local_DNS_Port))
- while True:
- print()
- print("Waiting for Request...")
- request, address = sock.recvfrom(MAX_BUFFER_SIZE)
- print("Received Request")
- DnsWorker(request, address, sock, config_path).run() # Change to start()
- print("Current Cache ")
- pprint(server_ip_dict)
- except socket.error as e:
- print(str(e))
- print("Closing DNS Server!")
- # do not change!
- if __name__ == '__main__':
- configpath = sys.argv[1]
- run_dns_server(configpath)
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement