From df8c5d7f26f17ed62d0f7cfc464d8a9eddefc4bc Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Pascal=20Eng=C3=A9libert?= <tuxmain@zettascript.org>
Date: Sat, 7 Sep 2019 14:25:22 +0200
Subject: [PATCH] Fixes, client config

---
 README.md |  4 +--
 client.py | 73 +++++++++++++++++++++++++++++++++++++++++++++----------
 server.py | 16 +++++++++---
 utils.py  |  9 ++++---
 4 files changed, 81 insertions(+), 21 deletions(-)

diff --git a/README.md b/README.md
index e39036a..4afa5b3 100644
--- a/README.md
+++ b/README.md
@@ -32,10 +32,10 @@ Install the following packages: `libleveldb-dev` `libsodium-dev` `python3` `pyth
 
 ## How to use it
 
-Create config & data dir: (change `~/.gmixer-g1`, so you can run different 
+Create config & data dir: (change `~/.config/gmixer-g1`, so you can run different 
 servers with different configs) (note that each server must have different dir)
 
-    python3 server.py -i -d ~/.gmixer-g1
+    python3 server.py -i -d ~/.config/gmixer-g1
 
 Edit config and set your values. If you are not using any proxy, `bind_` and `public_` addresses should be the same.
 If `salt` or `password` are empty, they will be asked at runtime. For security reasons, it is not a good idea to write them in commandline.
diff --git a/client.py b/client.py
index 702503a..b72594f 100644
--- a/client.py
+++ b/client.py
@@ -22,14 +22,55 @@ Sources:
 https://www.ietf.org/rfc/rfc3092.txt <- Very important RFC, please read it
 """
 
-import sys, os, asyncio, getpass, random, time, secrets, socket
+import sys, os, asyncio, getpass, random, time, secrets, socket, json
 import ubjson
 import plyvel
 import libnacl.sign
 from duniterpy.key import SigningKey, PublicKey
 import utils
 
-DIR = "~/.gmixer"
+DIR = "~/.config/gmixer-client"
+BMA_HOSTS = ["g1.duniter.fr 443", "g1.duniter.org 443", "g1.presles.fr 443", "g1.cgeek.fr 443", "ts.g1.librelois.fr 443"]
+PEER_SIG_AGE_MAX = 604800 # max age of a peer document signature
+IDTY_SIG_AGE_MAX = 2592000 # max age of a idty document signature
+CURRENCY = "g1"
+
+# Read json config file
+def read_config(cdir, conf_overwrite={}):
+	if not os.path.isfile(cdir+"/config.json"):
+		configfile = open(cdir+"/config.json", "w")
+		configfile.write("{}")
+		configfile.close()
+	
+	with open(cdir+"/config.json", "r") as configfile:
+		try:
+			conf = json.load(configfile)
+		except json.JSONDecodeError:
+			utils.logprint("Config: bad JSON => abort", utils.LOG_ERROR)
+			exit(1)
+	
+	conf.setdefault("currency", CURRENCY)
+	conf.setdefault("server", {})
+	conf["server"].setdefault("peer_sig_age_max", PEER_SIG_AGE_MAX)
+	conf["server"].setdefault("idty_sig_age_max", IDTY_SIG_AGE_MAX)
+	conf.setdefault("client", {})
+	conf["client"].setdefault("bma_hosts", BMA_HOSTS)
+	conf["client"].setdefault("proxy", None)
+	conf["client"].setdefault("proxy_onion_only", False)
+	conf.setdefault("idty", {})
+	conf["idty"].setdefault("needed", True)
+	
+	for key in conf_overwrite:
+		c = conf
+		k = key.split(".")
+		for i in k[:len(k)-1]:
+			c = conf[i]
+		c[k[len(k)-1]] = conf_overwrite[key]
+	
+	with open(cdir+"/config.json", "w") as configfile:
+		json.dump(conf, configfile, indent=1)
+	
+	return conf
 
 class Confirmation():
 	def __init__(self, client_pubkey, node_pubkey, raw):
@@ -63,7 +104,8 @@ class Confirmation():
 			"out_base": self.out_base
 		}
 
-def get_peers(host, proxy=None, proxy_onion_only=False):
+def get_peers(conf, host, proxy=None, proxy_onion_only=False):
+	
 	header, content = utils.sdata(host, "GET", "/peers/info", proxy=proxy, proxy_onion_only=proxy_onion_only)
 	
 	try:
@@ -74,7 +116,7 @@ def get_peers(host, proxy=None, proxy_onion_only=False):
 		print("Error: bad UBJSON")
 		return
 	
-	peers = [utils.Peer(data["info"]), *[utils.Peer(p) for p in data["peers"]]]
+	peers = [utils.Peer(conf, data["info"]), *[utils.Peer(conf, p["raw"]) for p in data["peers"]]]
 	#peers = [utils.Peer(p) for p in data["peers"]]
 	print([p.pubkey for p in peers])
 	
@@ -239,7 +281,7 @@ async def mix(db_txs, amount, base, sender, path, host, proxy=None, proxy_onion_
 			if not send_tx:
 				print("Remind: no-tx mode")
 			
-			if input("OK? [yn]: ").lower() == "y":
+			if input("OK? [yN]: ").lower() == "y":
 				message = {
 					"sender": sender.pubkey,
 					"path": path,
@@ -252,7 +294,10 @@ async def mix(db_txs, amount, base, sender, path, host, proxy=None, proxy_onion_
 				
 				if send_tx:
 					try:
-						await utils.send_transaction(sender, path[0], amount, utils.gen_comment(comment_seeds[0]))
+						authfile = "/tmp/gmixer-client-authfile-"+secrets.token_urlsafe(8)
+						sender.save_seedhex_file(authfile)
+						#await utils.send_transaction(sender, path[0], amount, utils.gen_comment(comment_seeds[0]))
+						utils.send_transaction(authfile, path[0], amount, utils.gen_comment(comment_seeds[0]))
 						
 						message["sent"] = True
 						db_txs.put(comment_seeds[0][1], PublicKey(sender.pubkey).encrypt_seal(ubjson.dumpb(message)))
@@ -262,10 +307,10 @@ async def mix(db_txs, amount, base, sender, path, host, proxy=None, proxy_onion_
 						print("Error when sending tx: " + str(e))
 				return
 
-async def main(db_txs, host, receiver, amount=1000, layers=3, proxy=None, proxy_onion_only=False, send_tx=True):
+async def main(conf, db_txs, host, receiver, amount=1000, layers=3, proxy=None, proxy_onion_only=False, send_tx=True):
 	if amount < 100:
 		print("!! Warning !!\nYou are going to send less than 1.00, all this money will be destroyed by Duniter.\nPlease always send 1.00 or more.")
-		if input("Do it anyway? [yn]: ").lower() != "y":
+		if input("Do it anyway? [yN]: ").lower() != "y":
 			return
 	
 	print("IDs of the expeditor account:")
@@ -273,10 +318,10 @@ async def main(db_txs, host, receiver, amount=1000, layers=3, proxy=None, proxy_
 	password = getpass.getpass("Psw: ")
 	keys = SigningKey.from_credentials(salt, password) # sender
 	print(keys.pubkey)
-	if input("Is that the right pubkey? [yn]: ").lower() != "y":
+	if input("Is that the right pubkey? [yN]: ").lower() != "y":
 		return
 	
-	peers = get_peers(host, proxy, proxy_onion_only)
+	peers = get_peers(conf, host, proxy, proxy_onion_only)
 	if peers == None:
 		print("Error getting peer list")
 		exit(1)
@@ -287,7 +332,7 @@ async def main(db_txs, host, receiver, amount=1000, layers=3, proxy=None, proxy_
 			exit(1)
 		for peer in path:
 			print(peer)
-		if input("OK? [yn]: ").lower() == "y":
+		if input("OK? [yN]: ").lower() == "y":
 			break
 	
 	await mix(db_txs, amount, 0, keys, path, host1, proxy, proxy_onion_only, send_tx)
@@ -303,7 +348,7 @@ Options:
  -h <host> <port>  host for sync peer list
  -p <host> <port>  SOCKS5 proxy
  --onion           use proxy only when connecting to .onion
- -d <path>         config directory (default ~/.gmixer)
+ -d <path>         config directory (default ~/.config/gmixer-client)
  --no-tx           Do not send transaction (for debug)
 
 Example:
@@ -317,6 +362,8 @@ python3 client.py -h svetsae7j3usrycn.onion 10951 -r 78ZwwgpgdH5uLZLbThUQH7LKwPg
 		DIR = DIR[:len(DIR)-1] # Remove last slash
 	os.makedirs(DIR, exist_ok=True)
 	
+	conf = read_config(DIR)
+	
 	receiver = utils.getargv("-r", "")
 	if receiver == "":
 		print("Error: No receiver set (try option --help)")
@@ -336,4 +383,4 @@ python3 client.py -h svetsae7j3usrycn.onion 10951 -r 78ZwwgpgdH5uLZLbThUQH7LKwPg
 	
 	db_txs = plyvel.DB(DIR+"/client_db_txs", create_if_missing=True)
 	
-	asyncio.get_event_loop().run_until_complete(main(db_txs, (host, port), receiver, amount, layers, proxy, proxy_onion_only, send_tx))
+	asyncio.get_event_loop().run_until_complete(main(conf, db_txs, (host, port), receiver, amount, layers, proxy, proxy_onion_only, send_tx))
diff --git a/server.py b/server.py
index f4e6e43..0324834 100644
--- a/server.py
+++ b/server.py
@@ -38,7 +38,7 @@ in_  = something that a node sent to me
 out_ = something I'm sending to a node
 """
 
-DIR = "~/.gmixer"
+DIR = "~/.config/gmixer"
 BIND_HOST = socket.gethostname()
 try:
 	BIND_HOST = socket.gethostbyname(BIND_HOST)
@@ -716,7 +716,8 @@ class ClientThread(Thread):
 					random.shuffle(txs)
 					for tx in txs:
 						try:
-							await utils.send_transaction(self.keys, tx.receiver_pubkey, tx.out_amount, utils.gen_comment(tx.out_seeds))
+							#await utils.send_transaction(self.keys, tx.receiver_pubkey, tx.out_amount, utils.gen_comment(tx.out_seeds))
+							utils.send_transaction(DIR+"/authfile", tx.receiver_pubkey, tx.out_amount, utils.gen_comment(tx.out_seeds))
 							tx.tx_sent = True
 							tx.export_ubjson(self.db_txs)
 						except socket.timeout:
@@ -881,6 +882,9 @@ def main():
 	keys = SigningKey.from_credentials(salt, password)
 	utils.logprint("Pubkey: "+keys.pubkey, utils.LOG_INFO)
 	
+	# Generate authfile
+	keys.save_seedhex_file(DIR+"/authfile")
+	
 	# Generate peer info
 	local_peer = utils.Peer.generate(conf, keys, {})
 	
@@ -904,6 +908,12 @@ def main():
 	serverThread.join()
 	clientThread.join()
 	
+	# Wipe authfile
+	f = open(DIR+"/authfile", "w")
+	f.write(secrets.token_hex(32))
+	f.close()
+	os.remove(DIR+"/authfile")
+	
 	# Save
 	utils.save_peers(db_peers, peers)
 	db_peers.close()
@@ -1033,7 +1043,7 @@ Options:\n\
  --help     Display help\n\
 \n\
  -d <path>  Change config & data dir\n\
-  default: ~/.gmixer\n\
+  default: ~/.config/gmixer\n\
  -v         Verbose\n\
  -P         Auto set public address (overwrites config)\n\
  -g         (with -i or -I only) Generate identity signature\n\
diff --git a/utils.py b/utils.py
index 17f5088..91b68cd 100644
--- a/utils.py
+++ b/utils.py
@@ -17,7 +17,7 @@
 	along with ÄžMixer-py.  If not, see <https://www.gnu.org/licenses/>.
 """
 
-import sys, os, re, socket, time, secrets, hashlib, base64, asyncio
+import sys, os, re, socket, time, secrets, hashlib, base64, asyncio, subprocess
 import socks
 import ubjson
 import libnacl.sign
@@ -275,8 +275,11 @@ async def check_idty(bma_endpoints:list, pubkey:str):
 		await client.close()
 	return "identities" in result and len(result["identities"]) > 0 and result["identities"][0]["pubkey"] == pubkey and result["identities"][0]["membershipExpiresIn"] > 0
 
-async def send_transaction(sender_keys:SigningKey, receiver_pubkey:str, amount:int, comment:str):
+#async def send_transaction(sender_keys:SigningKey, receiver_pubkey:str, amount:int, comment:str):
 	#sender_amount = silkaj.money.get_amount_from_pubkey(sender_keys.pubkey)[0]
 	#assert sender_amount >= amount, "not enough money"
 	
-	await silkaj.tx.handle_intermediaries_transactions(sender_keys, sender_keys.pubkey, amount, [receiver_pubkey], comment)
+	#await silkaj.tx.handle_intermediaries_transactions(sender_keys, sender_keys.pubkey, amount, [receiver_pubkey], comment)
+
+def send_transaction(authfile:str, receiver_pubkey:str, amount:int, comment:str):
+	subprocess.Popen(["silkaj", "--auth-file", "--file", authfile, "tx", "--output", receiver_pubkey, "--amount", str(amount/100), "--comment", comment, "-y"])
-- 
GitLab