From 11d5f3f767b1629b8c6170dd859aedd889079376 Mon Sep 17 00:00:00 2001 From: Craig Everett Date: Tue, 12 Dec 2017 08:31:25 +0900 Subject: [PATCH] wip --- zx | 208 +++++++++++++++++++++++++++++++++++++++++++------------------ 1 file changed, 148 insertions(+), 60 deletions(-) diff --git a/zx b/zx index c6e31fc..ddd3501 100755 --- a/zx +++ b/zx @@ -348,7 +348,7 @@ ensure_installed(PackageID = {Realm, Name, Version}) -> end. --spec ensure_installed(Realm, Name, Version) -> Result +-spec ensure_installed(Realm, Name, Version) -> Result | no_return() when Realm :: realm(), Name :: name(), Version :: version(), @@ -707,13 +707,10 @@ valid_package({Realm, Name}) -> {false, false} -> ok = log(error, "Invalid realm ~tp and package ~tp", [Realm, Name]), halt(1) - end; -valid_package(Bad) -> - ok = log(error, "Invalid package() value: ~160tp", [Bad]), - halt(1). + end. --spec string_to_package(string()) -> ok | no_return(). +-spec string_to_package(string()) -> package() | no_return(). %% @private %% Convert a string to a package() type if possible. If not then halt the system. @@ -2306,35 +2303,31 @@ create_realm(ZompConf, Realm, ExAddress, ExPort, InPort, UserName, Email) -> " There are no rules for this one. Any valid UTF-8 printables are legal.~n", ok = io:format(Instructions), RealName = get_input(), + create_realm(ZompConf, Realm, ExAddress, ExPort, InPort, UserName, Email, RealName). + + +-spec create_realm(ZompConf, Realm, ExAddress, ExPort, InPort, UserName, Email, RealName) -> + no_return() + when ZompConf :: [{Key :: atom(), Value :: term()}], + Realm :: realm(), + ExAddress :: inet:hostname() | inet:ip_address(), + ExPort :: inet:port_number(), + InPort :: inet:port_number(), + UserName :: string(), + Email :: string(), + RealName :: string(). + +create_realm(ZompConf, Realm, ExAddress, ExPort, InPort, UserName, Email, RealName) -> ok = io:format("~nGenerating keys. This might take a while, so settle in...~n"), {ok, RealmKey, RealmPub} = generate_rsa({Realm, Realm ++ ".1.realm"}), {ok, PackageKey, PackagePub} = generate_rsa({Realm, Realm ++ ".1.package"}), {ok, SysopKey, SysopPub} = generate_rsa({Realm, UserName ++ ".1"}), - AllKeys = [RealmKey, RealmPub, PackageKey, PackagePub, SysopKey, SysopPub], - DangerousKeys = [PackageKey, SysopKey], - Copy = - fun(From) -> - To = filename:basename(From), - case filelib:is_file(To) of - true -> - M = "Whoops! Keyfile local destination ~tp exists! Aborting", - ok = log(error, M, [To]), - ok = log(info, "Undoing all changes..."), - ok = lists:foreach(fun file:delete/1, AllKeys), - halt(0); - false -> - {ok, _} = file:copy(From, To), - log(info, "Copying to local directory: ~ts", [From]) - end - end, - Drop = - fun(File) -> - ok = file:delete(File), - log(info, "Deleting ~ts", [File]) - end, - ok = lists:foreach(Copy, AllKeys), - ok = lists:foreach(Drop, DangerousKeys), + ok = log(info, "Generated 16k RSA pair ~ts ~ts", [RealmKey, RealmPub]), + ok = log(info, "Generated 16k RSA pair ~ts ~ts", [PackageKey, PackagePub]), + ok = log(info, "Generated 16k RSA pair ~ts ~ts", [SysopKey, SysopPub]), + Timestamp = calendar:now_to_universal_time(erlang:timestamp()), + {ok, RealmPubData} = file:read_file(RealmPub), RealmPubRecord = {{Realm, filename:basename(RealmPub, ".pub.der")}, @@ -2349,25 +2342,12 @@ create_realm(ZompConf, Realm, ExAddress, ExPort, InPort, UserName, Email) -> {realm, Realm}, crypto:hash(sha512, PackagePubData), Timestamp}, - Message = - "~n" - " All of the keys generated have been moved to the current directory.~n" - "~n" - " MAKE AND SECURELY STORE COPIES OF THESE KEYS.~n" - "~n" - " The private package and sysop login keys have been deleted from the " - "key directory. These should only exist on your local system, not a prime " - "realm server (particularly if other services are run on that machine).~n" - " The package and sysop keys will need to be copied to the ~~/.zomp/keys/~s/~n" - " directory on your personal or dev machine.~n", - ok = io:format(Message, [Realm]), UserRecord = {{Realm, UserName}, [filename:basename(SysopPub, ".pub.der")], Email, RealName}, - RealmFile = filename:join(zomp_dir(), Realm ++ ".realm"), - RealmMeta = + RealmSettings = [{realm, Realm}, {revision, 0}, {prime, {ExAddress, ExPort}}, @@ -2376,23 +2356,86 @@ create_realm(ZompConf, Realm, ExAddress, ExPort, InPort, UserName, Email) -> {sysops, [UserRecord]}, {realm_keys, [RealmPubRecord]}, {package_keys, [PackagePubRecord]}], - Realms = - case lists:keyfind(managed, 1, ZompConf) of - {managed, M} -> [Realm | M]; - false -> [Realm] - end, - ZompFile = filename:join(zomp_dir(), "zomp.conf"), - Update = fun({K, V}, ZC) -> lists:keystore(K, 1, ZC, {K, V}) end, - NewConf = - [{managed, Realms}, + ZompSettings = + [{managed, [Realm]}, {external_address, ExAddress}, {external_port, ExPort}, {internal_port, InPort}], - NewZompConf = lists:foldl(Update, ZompConf, NewConf), - ok = write_terms(RealmFile, RealmMeta), - ok = write_terms(ZompFile, NewZompConf), - ok = log(info, "Realm ~ts created.", [Realm]), - create_realmfile(Realm). + + RealmFN = Realm ++ ".realm", + RealmConf = filename:join(zomp_dir(), RealmFN), + ok = write_terms(RealmConf, RealmSettings), + {ok, CWD} = file:get_cwd(), + {ok, TempDir} = mktemp_dir("zomp"), + ok = file:set_cwd(TempDir), + KeyDir = filename:join("key", Realm), + ok = filelib:ensure_dir(KeyDir), + ok = file:make_dir(KeyDir), + KeyCopy = + fun(K) -> + {ok, _} = file:copy(K, filename:join(KeyDir, filename:basename(K))), + ok + end, + TarOpts = [compressed, {cwd, TempDir}], + + ok = write_terms(RealmFN, RealmSettings), + ok = KeyCopy(PackagePub), + ok = KeyCopy(RealmPub), + PublicZRF = filename:join(CWD, Realm ++ ".zrf"), + Files = filelib:wildcard("**"), + ok = erl_tar:create(PublicZRF, [RealmFN, "key"], TarOpts), + + ok = KeyCopy(SysopPub), + ok = write_terms("zomp.conf", ZompSettings), + PrimeZRF = filename:join(CWD, Realm ++ ".zpf"), + ok = erl_tar:create(PrimeZRF, [RealmFN, "zomp.conf", "key"], TarOpts), + + ok = file:set_cwd(zomp_dir()), + KeyBundle = filename:join(CWD, Realm ++ ".zkf"), + ok = erl_tar:create(KeyBundle, [KeyDir], [compressed]), + + ok = file:set_cwd(CWD), + ok = rm_rf(TempDir), + + Message = + "============================================================================~n" + "DONE!~n" + "~n" + "The realm ~ts has been created and is accessible from the current system.~n" + "Three configuration bundles have been created in the current directory:~n" + "~n" + " 1. ~ts ~n" + "This is the PRIVATE realm file you will need to install on the realm's prime~n" + "node. It includes the your (the sysop's) public key.~n" + "~n" + " 2. ~ts ~n" + "This file is the PUBLIC realm file other zomp nodes and zx users will need to~n" + "access the realm. It does not include your (the sysop's) public key.~n" + "~n" + " 3. ~ts ~n" + "This is the bundle of ALL KEYS that are defined in this realm at the moment.~n" + "~n" + "Now you need to make copies of these three files and back them up.~n" + "~n" + "On the PRIME NODE you need to run `zx add realm ~ts` and follow the prompts~n" + "to cause it to begin serving that realm as prime. (Node restart required.)~n" + "~n" + "On all zx CLIENTS that want to access your new realm and on all subordinate~n" + "MIRROR NODES the command `zx add realm ~ts` will need to be run.~n" + "The method of public realm file distribution (~ts) is up to you.~n" + "~n" + "~n" + "Public & Private key installation (if you need to recover them or perform~n" + "sysop functions from another computer) is `zx add keybundle ~ts`.~n" + "============================================================================~n", + Substitutions = + [Realm, + PrimeZRF, PublicZRF, KeyBundle, + PrimeZRF, + PublicZRF, PublicZRF, + KeyBundle], + ok = io:format(Message, Substitutions), + halt(0). -spec create_realmfile(realm()) -> no_return(). @@ -2451,7 +2494,7 @@ install(PackageID) -> {TgzFile, TgzData} = lists:keyfind(TgzFile, 1, Files), {"zomp.meta", MetaBin} = lists:keyfind("zomp.meta", 1, Files), Meta = binary_to_term(MetaBin), - {KeyID, Signature} = maps:get(sig, 1, Meta), + {KeyID, Signature} = maps:get(sig, Meta), {ok, PubKey} = loadkey(public, KeyID), ok = ensure_package_dirs(PackageID), PackageDir = filename:join("lib", PackageString), @@ -2558,6 +2601,24 @@ receive_zrp(Socket, PackageID) -> end. +-spec mktemp_dir(Prefix) -> Result + when Prefix :: string(), + Result :: {ok, TempDir :: file:filename()} + | {error, Reason :: file:posix()}. + +mktemp_dir(Prefix) -> + Rand = integer_to_list(binary:decode_unsigned(crypto:strong_rand_bytes(8)), 36), + TempPath = filename:basedir(user_cache, Prefix), + TempDir = filename:join(TempPath, Rand), + Result1 = filelib:ensure_dir(TempDir), + Result2 = file:make_dir(TempDir), + case {Result1, Result2} of + {ok, ok} -> {ok, TempDir}; + {ok, Error} -> Error; + {Error, _} -> Error + end. + + %%% Utility functions -spec read_meta() -> package_meta() | no_return(). @@ -2674,6 +2735,33 @@ installed(PackageID) -> filelib:is_dir(PackageDir). +-spec rm_rf(file:filename()) -> ok | {error, file:posix()}. +%% @private +%% Recursively remove files and directories, equivalent to `rm -rf' on unix. + +rm_rf(Path) -> + case filelib:is_dir(Path) of + true -> + Pattern = filename:join(Path, "**"), + Contents = lists:reverse(lists:sort(filelib:wildcard(Pattern))), + ok = lists:foreach(fun rm/1, Contents), + file:del_dir(Path); + false -> + file:delete(Path) + end. + + +-spec rm(file:filename()) -> ok | {error, file:posix()}. +%% @private +%% An omnibus delete helper. + +rm(Path) -> + case filelib:is_dir(Path) of + true -> file:del_dir(Path); + false -> file:delete(Path) + end. + + %%% Input argument mangling @@ -3225,4 +3313,4 @@ log(Level, Format, Args) -> warning -> "[WARNING]"; error -> "[ERROR]" end, - io:format("~p ~s: " ++ Format ++ "~n", [self(), Tag | Args]). + io:format("~s ~p: " ++ Format ++ "~n", [Tag, self() | Args]).