From cc4d55288db63122a4231b5dccf4db3918cd6765 Mon Sep 17 00:00:00 2001 From: Craig Everett Date: Fri, 24 Nov 2017 19:58:06 +0900 Subject: [PATCH] wip --- zx | 188 +++++++++++++++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 165 insertions(+), 23 deletions(-) diff --git a/zx b/zx index bfc7072..3bd8b25 100755 --- a/zx +++ b/zx @@ -668,7 +668,7 @@ package(TargetDir) -> [] -> ok = log(info, "Need to generate key"), KeyID = prompt_keygen(), - ok = generate_rsa(KeyID), + {ok, _, _} = generate_rsa(KeyID), package(KeyID, TargetDir); [KeyName] -> KeyID = {Realm, KeyName}, @@ -1144,11 +1144,14 @@ prompt_keygen() -> case {valid_lower0_9(Realm), valid_label(KeyName)} of {true, true} -> {Realm, KeyName}; - {false, _} -> + {false, true} -> ok = io:format("Bad realm name ~tp. Try again.~n", [Realm]), prompt_keygen(); {true, false} -> ok = io:format("Bad key name ~tp. Try again.~n", [KeyName]), + prompt_keygen(); + {false, false} -> + ok = io:format("NUTS! Both the key and realm names are illegal. Try again.~n"), prompt_keygen() end. @@ -1161,8 +1164,8 @@ create_keypair() -> ok = file:set_cwd(zomp_dir()), KeyID = prompt_keygen(), case generate_rsa(KeyID) of - ok -> halt(0); - Error -> error_exit("create_keypair/0 failed with ~tp", [Error], ?FILE, ?LINE) + {ok, _, _} -> halt(0); + Error -> error_exit("create_keypair/0 failed with ~tp", [Error], ?FILE, ?LINE) end. @@ -1198,7 +1201,7 @@ generate_rsa({Realm, KeyName}) -> ok = log(info, "~ts and ~ts agree", [KeyFile, PubFile]), ok = log(info, "Wrote private key to: ~ts.", [KeyFile]), ok = log(info, "Wrote public key to: ~ts.", [PubFile]), - ok; + {ok, KeyFile, PubFile}; false -> ok = lists:foreach(fun file:delete/1, [PemFile, KeyFile, PubFile]), ok = log(error, "Something has gone wrong."), @@ -1400,11 +1403,12 @@ dialyze() -> %%% Create Realm & Sysop create_realm() -> - RealmMessage = - "~n Enter a name for your new realm.~n" + Instructions = + "~n" + " Enter a name for your new realm.~n" " Valid names can contain only lower-case letters, numbers and the underscore.~n" " Valid names must begin with a lower-case letter.~n", - ok = io:format(RealmMessage), + ok = io:format(Instructions), Realm = get_input(), case valid_lower0_9(Realm) of true -> @@ -1422,32 +1426,144 @@ create_realm() -> end. create_realm(Realm) -> - UserNameMessage = - "~n Enter a username for the realm sysop.~n" + HostInstructions = + "~n" + " Enter the prime (permanent) server's publicly accessible hostname or~n" + " address. If prime is identified by an address and not a name, enter it~n" + " as either a normal IPv4 dot-notation or IPv6 colon-notation.~n" + " Note that address ranges will not be checked for validity.~n", + ok = io:format(HostInstructions), + HostString = get_input(), + Host = + case inet:parse_address(HostString) of + {ok, Address} -> Address; + {error, einval} -> HostString + end, + PortInstructions = + " Enter the port number the host will be listening on.~n" + " The port can be any number from 1 to 65535.~n" + " [Press enter for the default: 11311]~n", + ok = io:format(PortInstructions), + case get_input() of + "" -> + create_realm(Realm, {HostString, 11311}); + S -> + try + case list_to_integer(S) of + Port when 16#ffff >= Port, Port > 0 -> + create_realm(Realm, {Host, Port}); + Illegal -> + Whoops = "~p is out of bounds (1~65535). Try again...", + ok = io:format(Whoops, [Illegal]), + create_realm(Realm) + end + catch error:badarg -> + Error = "~tp is not a port number. Try again...", + ok = io:format(Error, [S]), + create_realm(Realm) + end + end. + +create_realm(Realm, Prime) -> + Instructions = + "~n" + " Enter a username for the realm sysop.~n" " Valid names can contain only lower-case letters, numbers and the underscore.~n" " Valid names must begin with a lower-case letter.~n", - ok = io:format(UserNameMessage), + ok = io:format(Instructions), UserName = get_input(), case valid_lower0_9(UserName) of true -> - create_realm(Realm, UserName); + create_realm(Realm, Prime, UserName); false -> ok = io:format("Bad username ~tp. Try again.~n", [UserName]), - create_realm(Realm) + create_realm(Realm, Prime) end. +create_realm(Realm, Prime, UserName) -> + Instructions = + "~n" + " Enter an email address for the realm sysop.~n" + " Valid email address rules apply though the checking done here is quite~n" + " minimal. Check the address you enter carefully. The only people who will~n" + " suffer from an invalid address are your users.~n", + ok = io:format(Instructions), + Email = get_input(), + [User, Host] = string:lexemes(Email, "@"), + case {valid_lower0_9(User), valid_label(Host)} of + {true, true} -> + create_realm(Realm, Prime, UserName, Email); + {false, true} -> + Message = "The user part of the email address seems invalid. Try again.~n", + ok = io:format(Message), + create_realm(Realm, Prime, UserName); + {true, false} -> + Message = "The host part of the email address seems invalid. Try again.~n", + ok = io:format(Message), + create_realm(Realm, Prime, UserName); + {false, false} -> + Message = "This email address seems like its totally bonkers. Try again.~n", + ok = io:format(Message), + create_realm(Realm, Prime, UserName) + end. - -create_realm(Realm, UserName, UserRecord, Prime, RealmKey, PackageKey) -> +create_realm(Realm, Prime, UserName, Email) -> + Instructions = + "~n" + " Enter the real name (or whatever name people will recognize) for the sysop.~n" + " There are no rules for this one. Any valid UTF-8 printables are legal.~n", + ok = io:format(Instructions), + RealName = get_input(), + 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], + 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 ~tp to the local directory", [From]) + end + end, + Drop = + fun(File) -> + ok = file:delete(File), + log(info, "Deleting ~tp", [File]) + end, + DangerousKeys = [PackageKey, SysopKey], + ok = lists:foreach(Copy, AllKeys), + ok = lists:foreach(Drop, DangerousKeys), + Message = + "~n" + " All of the keys generated have been moved to the current directory.~n" + " MAKE AND SECURELY STORE COPIES OF THESE KEYS.~n" + " The private package key and private sysop login key have been deleted from the~n" + " zomp key directory. These should only exist on your local system, not a prime.~n" + " realm server (particularly if other services are run on that machine).~n" + " Your package and sysop keys will need to be copied to the ~~/.zomp/keys/~p/~n" + " directory on your personal or dev machine.", + ok = io:format(Message, [Realm]), + Timestamp = calendar:now_to_universal_time(erlang:timestamp()), + UserRecord = {{UserName, Realm}, [SysopPub], Email, RealName, 1, Timestamp}, RealmMeta = [{realm, Realm}, - {prime, Prime}, - {realm_keys, [RealmKey], - {package_keys, [PacakageKey] {revision, 0}, - {serial, 0}, - {mirrors, []}], - ok = log(info, "Seriously, we would be creating a realm now."), + {prime, Prime}, + {private, []}, + {mirrors, []}, + {sysops, [UserRecord]}, + {realm_keys, [RealmPub]}, + {package_keys, [PackagePub]}], + ok = log(info, "Would be writing ~tp", [RealmMeta]), halt(0). @@ -1961,7 +2077,33 @@ hurr() -> io:format("That isn't an option.~n"). -%%% Directory Management +%%% Directory & File Management + +%-spec move_file(From, To) -> Result +% when From :: file:filename(), +% To :: file:filename(), +% Result :: ok +% | {error, Reason}, +% Reason :: bad_source +% | destination_exists +% | file:posix(). +%%% @private +%%% Utility function to safely copy a file From one path To another without clobbering the +%%% destination in the event it already exists. Both the source and the destination must be +%%% complete filenames, not directories. +% +%move_file(From, To) -> +% case {filelib:is_regular(From), filelib:is_file(To)} of +% {false, false} -> +% case file:copy(From, To) of +% {ok, _} -> file:delete(From); +% Error -> Error +% end; +% {true, _} -> +% {error, bad_source}; +% {false, true} -> +% {error, destination_exists} +% end. -spec ensure_zomp_home() -> ok. @@ -2248,4 +2390,4 @@ log(Level, Format, Args) -> warning -> "[WARNING]"; error -> "[ERROR]" end, - io:format("~s ~p: " ++ Format ++ "~n", [Tag, self() | Args]). + io:format("~p ~s: " ++ Format ++ "~n", [self(), Tag | Args]).