Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
55 changes: 54 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ QUIRC_M68K_OBJS := $(patsubst src/qr/%.c,$(BUILD)/qr-m68k/%.o,$(QUIRC_SRCS))
QRCODEGEN_HOST_OBJ := $(BUILD)/qr-host/qrcodegen.o
QRCODEGEN_M68K_OBJ := $(BUILD)/qr-m68k/qrcodegen.o

.PHONY: all test cli smoke diff m68k m68k-docker gui gui-docker gui-smoke qr-onhw qr-onhw-docker qr-onhw-smoke arexx-onhw arexx-onhw-docker arexx-onhw-smoke serialtest-m68k serialtest-m68k-docker copperline-smoke pbkdf2-bench asm-bench amissl-bench clean
.PHONY: all test cli smoke diff m68k m68k-docker gui gui-docker gui-smoke qr-onhw qr-onhw-docker qr-onhw-smoke arexx-onhw arexx-onhw-docker arexx-onhw-smoke serialtest-m68k serialtest-m68k-docker copperline-smoke pbkdf2-bench asm-bench amissl-bench flexcat flexcat-docker catalog-strings catalog-strings-docker catalog-onhw-smoke catalog-nolib-onhw catalog-nolib-onhw-docker clean

all: test cli

Expand Down Expand Up @@ -140,6 +140,21 @@ m68k: $(QRCODEGEN_M68K_OBJ) | $(BUILD)
$(M68K_CC) $(M68K_CFLAGS) -Isrc/qr $(CORE_SRCS) $(ASM_SRCS) $(AMIGA_SRCS) $(CLI_SRCS) \
$(QRENC_WRAP) $(QRCODEGEN_M68K_OBJ) -o $(BUILD)/AmiAuth

# Test-only CLI variant (#67): built with a bogus locale.library name, so
# catalog_open()'s OpenLibrary() genuinely fails and the on-target catalog
# test can exercise the LocaleBase==NULL fallback path without deleting the
# real locale.library from a WB clone (which breaks unrelated boot
# components - see tests/gui/catalog-onhw.sh). Never shipped.
CATALOG_NOLIB_DEFS := -DAMIAUTH_LOCALE_LIBNAME='"locale.library.nonexistent"'

catalog-nolib-onhw: $(QRCODEGEN_M68K_OBJ) | $(BUILD)
$(M68K_CC) $(M68K_CFLAGS) $(CATALOG_NOLIB_DEFS) -Isrc/qr $(CORE_SRCS) $(ASM_SRCS) $(AMIGA_SRCS) $(CLI_SRCS) \
$(QRENC_WRAP) $(QRCODEGEN_M68K_OBJ) -o $(BUILD)/AmiAuth-nolib

catalog-nolib-onhw-docker:
$(DOCKER) run --rm --platform linux/amd64 $(DOCKER_USER) -v "$(CURDIR)":/work -w /work \
$(AMIGA_GCC_IMAGE) sh -lc 'PATH=/opt/amiga/bin:$$PATH make catalog-nolib-onhw'

# --- m68k: ReAction GUI binary (Amiga only; needs intuition + ReAction classes) ---
# Includes the QR decoder: qrimage.c (datatypes glue) + our qr.c wrapper + the
# vendored quirc objects (built -w for m68k). QUIRC_FLOAT_TYPE=float: no FPU.
Expand All @@ -159,6 +174,36 @@ gui-docker:
$(DOCKER) run --rm --platform linux/amd64 $(DOCKER_USER) -v "$(CURDIR)":/work -w /work \
$(AMIGA_GCC_IMAGE) sh -lc 'PATH=/opt/amiga/bin:$$PATH make gui GIT_HASH=$(GIT_HASH) GIT_ON_TAG=$(GIT_ON_TAG)'

# --- Localization (#67): FlexCat (github.com/adtools/flexcat) generates
# src/core/catalog_strings.h from locale/AmiAuth.cd. FlexCat itself is a
# host-only build tool (never ships, never touches the m68k cross-compiler -
# see tools/fetch-flexcat.sh) and the generated header is checked into git
# like any other source file, so ordinary `make test`/`cli`/`m68k`/`gui`
# builds need neither FlexCat nor network access. Only re-run `make
# catalog-strings` (regenerating + committing the header) after editing
# locale/AmiAuth.cd. Needs a native (non-cross) C compiler on PATH - macOS's
# clang chokes on FlexCat's own build flags, so use `flexcat-docker` there.
flexcat: $(BUILD)/flexcat

$(BUILD)/flexcat: | $(BUILD)
src=$$(bash tools/fetch-flexcat.sh) && \
cp "$$src" $(BUILD)/flexcat && chmod +x $(BUILD)/flexcat && \
cp "$$(dirname $$src)/../sd/CatComp_h.sd" $(BUILD)/CatComp_h.sd

flexcat-docker:
$(DOCKER) run --rm --platform linux/amd64 $(DOCKER_USER) -v "$(CURDIR)":/work -w /work \
$(AMIGA_GCC_IMAGE) sh -lc 'make flexcat'

catalog-strings: $(BUILD)/flexcat
$(BUILD)/flexcat locale/AmiAuth.cd src/core/catalog_strings.h=$(BUILD)/CatComp_h.sd

# $(BUILD)/flexcat is a Linux ELF binary (built inside the container) - won't
# run directly on a macOS host, so regenerating the header locally on macOS
# needs the whole step done inside the container, not just the build half.
catalog-strings-docker:
$(DOCKER) run --rm --platform linux/amd64 $(DOCKER_USER) -v "$(CURDIR)":/work -w /work \
$(AMIGA_GCC_IMAGE) sh -lc 'make catalog-strings'

# --- Headless GUI smoke test: boot WB 3.2 under Copperline, render AmiAuthGUI --
# Boots an A1200/OS 3.2 under native Copperline, auto-launches AmiAuthGUI, and
# asserts the ReAction window rendered (screenshot in build/gui-smoke/). No VNC
Expand Down Expand Up @@ -240,6 +285,14 @@ arexx-onhw-docker:
arexx-onhw-smoke:
sh tests/gui/arexx-onhw.sh

# --- Headless on-target catalog test (#67): boot WB 3.2, run AmiAuth CLI
# commands whose output is routed through MSG()/catalog_get(), with
# locale.library both present (no matching catalog - the common case) and
# entirely absent, asserting the correct English default either way. See
# tests/gui/catalog-onhw.sh for what this does/doesn't cover.
catalog-onhw-smoke:
sh tests/gui/catalog-onhw.sh

# Measure PBKDF2 throughput on a stock 68000 (informs the KDF policy). Dev-only:
# needs a Kickstart ROM (timer.device EClock isn't available under AROS).
pbkdf2-bench:
Expand Down
81 changes: 81 additions & 0 deletions locale/AmiAuth.cd
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
; AmiAuth.cd - catalog description (source of truth for all user-visible
; strings). Generated header: `make catalog-strings` (locale/AmiAuth.cd via
; FlexCat's CatComp_h.sd template -> src/core/catalog_strings.h).
;
; This is Phase 1 (#67): a representative slice, not the full string set -
; see userdocs/Localization.md for the migration plan and translator workflow.
;
; Do NOT add: CLI subcommand keywords, ReadArgs template field names, or
; ARexx port command keywords - those are the scripting protocol, not
; user-facing prose, and must stay identical across every locale.
;
; Do NOT hardcode "AmiAuth" (or any other proper noun) into a translatable
; string - pass it as a %s argument instead (see MSG_CLI_ALREADY_EXISTS/
; MSG_CLI_USAGE_RUNAS below). Translators should never need to copy a
; literal brand name verbatim into every language.
;
; Do NOT add a wrapper template with no real content of its own. The plain
; "AmiAuth: %s\n" prefix wrapping vault_err()'s own (not yet migrated -
; Phase 2) error text has nothing left to translate once the brand name is
; an argument rather than literal text - id 0 is deliberately left unused
; rather than assigned to that, to leave room for it once vault_err()'s
; strings actually get migrated.
;
#language english
#version 0
;
; --- CLI (src/cli/main.c) ---------------------------------------------
;
MSG_CLI_ALREADY_EXISTS (1//)
%s: %s already exists\n
;
MSG_CLI_ADDED (2//)
Added %s:%s\n
;
MSG_CLI_REMOVED (3//)
Removed '%s'\n
;
MSG_CLI_USAGE_RUNAS (4//)
Run as '%s <COMMAND> ...' ('%s ?' shows the arg template):\n
;
; --- GUI (src/gui/main.c): account list column headers -----------------
;
MSG_GUI_COL_ACCOUNT (5//)
Account
;
MSG_GUI_COL_CODE (6//)
Code
;
MSG_GUI_COL_LEFT (7//)
Left
;
; --- GUI: requester text -------------------------------------------------
;
MSG_GUI_CANNOT_OPEN_VAULT (8//)
Cannot open the vault at\n%s
;
MSG_GUI_BAD_URI (9//)
That is not a valid otpauth:// URI.
;
MSG_GUI_BAD_SECRET (10//)
That is not an otpauth:// URI or a Base32 secret.
;
MSG_GUI_NO_QR (11//)
No otpauth QR code was found in that image.
;
MSG_GUI_SAVE_FAILED (12//)
Could not save the vault.
;
MSG_GUI_REKEY_DONE (13//)
Vault re-keyed for this machine.
;
MSG_GUI_REKEY_FAILED (14//)
Re-key failed; the vault is unchanged.
;
; --- GUI: common requester button labels ----------------------------------
;
MSG_GUI_OK (15//)
OK
;
MSG_GUI_CANCEL (16//)
Cancel
1 change: 1 addition & 0 deletions mkdocs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ nav:
- Under the hood:
- Vault and Passphrases: Vault-and-Passphrases.md
- Settings Reference: Settings-Reference.md
- Localization: Localization.md
- Security Model: Security-Model.md
- Help:
- Troubleshooting and FAQ: Troubleshooting-and-FAQ.md
Expand Down
74 changes: 74 additions & 0 deletions src/amiga/catalog.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
/* catalog.c — locale.library catalog backend (#67). See catalog.h. AmigaOS
* only; the host stub is in src/core/catalog.c.
*
* Opened once at startup (CLI main(); GUI open_libs()), closed once at exit
* — persistent app-lifetime scope, unlike the existing transient
* OpenLibrary/CloseLibrary pairs elsewhere in this codebase that only want
* loc_GMTOffset for a moment (src/cli/main.c's cli_utc_offset(),
* src/gui/main.c's GMT-offset probe). Absence of locale.library, or of an
* installed AmiAuth.catalog for the user's language, is not an error - same
* optional-feature pattern as every other library in this codebase; the
* caller always gets English (the built-in default) either way.
*
* OC_BuiltInLanguage note: when it matches the system's current locale
* language (the common case for most users - AmiAuth's built-ins are
* English, most installs are English), OpenCatalog() intentionally skips
* loading any on-disk catalog at all and GetCatalogStr() just returns the
* default - this is documented AmigaOS behaviour, not a bug (confirmed
* on-target: a deliberately mismatched OC_BuiltInLanguage does load the
* catalog and GetCatalogStr() correctly returns the translated string with
* substitution parameters intact - see tests/gui/catalog-onhw.sh). */
#ifdef __amigaos__

#include <exec/types.h>
#include <proto/exec.h>
#include <proto/locale.h>
#include <libraries/locale.h>

#include "catalog.h"

/* <proto/locale.h>'s inline stubs reference this exact global by name and
* type (struct LocaleBase *, not struct Library *) - same convention as
* RexxSysBase/GfxBase elsewhere in this project; must not be static. */
struct LocaleBase *LocaleBase = NULL;
static struct Catalog *g_catalog = NULL;

/* Overridable at build time (-DAMIAUTH_LOCALE_LIBNAME='"nonexistent"') so an
* on-target test can force a genuine OpenLibrary() failure and exercise the
* LocaleBase==NULL fallback below, without touching the real locale.library
* a WB clone's own boot (IPrefs etc.) still needs - see
* `make catalog-nolib-onhw` / tests/gui/catalog-onhw.sh. Never set for a
* real build; the Makefile only ever defines it for that one test binary. */
#ifndef AMIAUTH_LOCALE_LIBNAME
#define AMIAUTH_LOCALE_LIBNAME "locale.library"
#endif

void catalog_open(void)
{
LocaleBase = (struct LocaleBase *)OpenLibrary((STRPTR)AMIAUTH_LOCALE_LIBNAME, 38); /* OS 2.1+ */
if (LocaleBase)
g_catalog = OpenCatalog(NULL, (STRPTR)"AmiAuth.catalog",
OC_BuiltInLanguage, (ULONG)(STRPTR)"english",
TAG_DONE);
}

void catalog_close(void)
{
if (LocaleBase) {
if (g_catalog) CloseCatalog(g_catalog);
CloseLibrary((struct Library *)LocaleBase);
}
g_catalog = NULL;
LocaleBase = NULL;
}

const char *catalog_get(long id, const char *def)
{
/* GetCatalogStr() is documented safe with a NULL catalog (just returns
* def) - but not with a NULL LocaleBase, since that means the library
* itself never opened and there is no function to call at all. */
if (!LocaleBase) return def;
return (const char *)GetCatalogStr(g_catalog, id, (STRPTR)def);
}

#endif /* __amigaos__ */
28 changes: 18 additions & 10 deletions src/cli/main.c
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
#include "pbkdf2.h"
#include "prefs.h"
#include "qrencode.h" /* qr_encode_uri/qr_render_ascii (#45) */
#include "catalog.h" /* locale.library message catalogs (#67) */

#if defined(__unix__) || defined(__APPLE__) || defined(__linux__)
# include <unistd.h>
Expand Down Expand Up @@ -551,7 +552,7 @@ static int cmd_init(const char *path, int always_unlocked, long iterations,
exists = fopen(path, "rb");
if (exists) {
fclose(exists);
fprintf(stderr, "AmiAuth: %s already exists\n", path);
fprintf(stderr, MSG(MSG_CLI_ALREADY_EXISTS), "AmiAuth", path);
return 2;
}

Expand Down Expand Up @@ -677,7 +678,7 @@ static int cmd_add(const char *path, const char *uri)

{
const otp_account *added = &v.accounts[v.count - 1];
if (added->issuer[0]) printf("Added %s:%s\n", added->issuer, added->label);
if (added->issuer[0]) printf(MSG(MSG_CLI_ADDED), added->issuer, added->label);
else printf("Added %s\n", added->label);
}
vault_lock(&v);
Expand Down Expand Up @@ -730,7 +731,7 @@ static int cmd_add_secret(const char *path, const char *secret,
memset(&acct, 0, sizeof acct);
if (rc != VAULT_OK) { vault_lock(&v); fprintf(stderr, "AmiAuth: %s\n", vault_err(rc)); return 2; }

printf("Added %s:%s\n", issuer, label);
printf(MSG(MSG_CLI_ADDED), issuer, label);
vault_lock(&v);
return 0;
}
Expand Down Expand Up @@ -840,7 +841,7 @@ static int cmd_remove(const char *path, const char *account)
if (rc == VAULT_OK) rc = save_vault(&v, path);
if (rc != VAULT_OK) { vault_lock(&v); fprintf(stderr, "AmiAuth: %s\n", vault_err(rc)); return 2; }

printf("Removed '%s'\n", account);
printf(MSG(MSG_CLI_REMOVED), account);
vault_lock(&v);
return 0;
}
Expand All @@ -850,8 +851,9 @@ static int usage(void)
const char *p = g_prog;
fprintf(stderr,
"AmiAuth - TOTP/HOTP authenticator for AmigaOS\n"
"\n"
"Run as '%s <COMMAND> ...' ('%s ?' shows the arg template):\n"
"\n");
fprintf(stderr, MSG(MSG_CLI_USAGE_RUNAS), p, p);
fprintf(stderr,
" CODE <secret> [digits] [period] Print a code (no vault)\n"
" INIT [OPEN] Create a vault\n"
" ADD \"<otpauth://...>\" Import an account from a URI\n"
Expand All @@ -869,8 +871,7 @@ static int usage(void)
" OFFSET <seconds> Set + save a UTC offset\n"
" NUDGE <+/-seconds> Adjust + save the current offset\n"
" HELP\n"
"\n",
p, p);
"\n");
#ifdef AMIAUTH_AMIGA
fprintf(stderr,
"Options: ISSUER/K LABEL/K STEAM/S (bare-secret ADD) VAULT <path> OPEN/S\n"
Expand Down Expand Up @@ -965,9 +966,10 @@ int main(int argc, char **argv)

if (argc > 0 && argv[0] && argv[0][0]) g_prog = argv[0];
crypto_select_init();
catalog_open();
memset(opt, 0, sizeof opt);
rda = ReadArgs((STRPTR)TMPL, opt, NULL);
if (!rda) { PrintFault(IoErr(), (STRPTR)g_prog); return 20; } /* RETURN_FAIL */
if (!rda) { PrintFault(IoErr(), (STRPTR)g_prog); catalog_close(); return 20; } /* RETURN_FAIL */

memset(&a, 0, sizeof a);
a.command = (const char *)opt[P_COMMAND];
Expand All @@ -984,6 +986,7 @@ int main(int argc, char **argv)

rc = dispatch(&a);
FreeArgs(rda);
catalog_close();
return rc;
}

Expand All @@ -997,6 +1000,7 @@ int main(int argc, char **argv)
int npos = 0, i;

if (argc > 0 && argv[0] && argv[0][0]) g_prog = argv[0];
catalog_open();
memset(&a, 0, sizeof a);
a.iterations = -1;

Expand Down Expand Up @@ -1032,7 +1036,11 @@ int main(int argc, char **argv)
a.value = npos > 1 ? pos[1] : NULL; /* the command's positional argument */
a.digits = npos > 2 ? pos[2] : NULL; /* CODE only */
a.period = npos > 3 ? pos[3] : NULL; /* CODE only */
return dispatch(&a);
{
int rc = dispatch(&a);
catalog_close();
return rc;
}
}

#endif
16 changes: 16 additions & 0 deletions src/core/catalog.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
/* catalog.c — host stub. The real locale.library backend is in
* src/amiga/catalog.c; exactly one is compiled per platform. */
#include "catalog.h"

#ifndef __amigaos__

void catalog_open(void) {}
void catalog_close(void) {}

const char *catalog_get(long id, const char *def)
{
(void)id;
return def;
}

#endif /* !__amigaos__ */
Loading