# HG changeset patch # User David Barts # Date 1663991992 25200 # Node ID 698c4a3d758dd9211c83d9c8f3b94bc3de10e314 # Parent f245b9a53495825963d9af780bda35d045c5e891 Some code clean-up. diff -r f245b9a53495 -r 698c4a3d758d src/main/kotlin/name/blackcap/passman/CreateSubcommand.kt --- a/src/main/kotlin/name/blackcap/passman/CreateSubcommand.kt Tue Sep 20 21:54:32 2022 -0700 +++ b/src/main/kotlin/name/blackcap/passman/CreateSubcommand.kt Fri Sep 23 20:59:52 2022 -0700 @@ -4,38 +4,46 @@ import kotlin.system.exitProcess class CreateSubcommand(): Subcommand() { + private companion object { + const val GENERATE = "generate" + const val HELP = "help" + const val LENGTH = "length" + const val SYMBOLS = "symbols" + const val VERBOSE = "verbose" + } private lateinit var commandLine: CommandLine override fun run(args: Array) { val options = Options().apply { - addOption("g", "generate", false, "Use password generator.") - addOption("l", "length", true, "Length of generated password.") - addOption("s", "symbols", false, "Use symbol characters in generated password.") - addOption("v", "verbose", false, "Print the generated password.") + addOption("g", GENERATE, false, "Use password generator.") + addOption("h", HELP, false, "Print this help message.") + addOption("l", LENGTH, true, "Length of generated password.") + addOption("s", SYMBOLS, false, "Use symbol characters in generated password.") + addOption("v", VERBOSE, false, "Print the generated password.") } try { commandLine = DefaultParser().parse(options, args) } catch (e: ParseException) { die(e.message ?: "syntax error", 2) } - if (commandLine.hasOption("help")) { - HelpFormatter().printHelp("$SHORTNAME createJv", options) + if (commandLine.hasOption(HELP)) { + HelpFormatter().printHelp("$SHORTNAME create", options) exitProcess(0) } checkArguments() val db = Database.open() - val entry = if (commandLine.hasOption("generate")) { - val rawLength = commandLine.getOptionValue("length") + val entry = if (commandLine.hasOption(GENERATE)) { + val rawLength = commandLine.getOptionValue(LENGTH) val length = try { rawLength?.toInt() ?: DEFAULT_GENERATED_LENGTH } catch (e: NumberFormatException) { die("${see(rawLength)} - invalid length") -1 /* will never happen */ } - val symbols = commandLine.hasOption("symbols") - val verbose = commandLine.hasOption("verbose") - Entry.withGeneratedPassword(length, symbols, verbose) + Entry.withGeneratedPassword(length, + commandLine.hasOption(SYMBOLS), + commandLine.hasOption(VERBOSE)) } else { Entry.withPromptedPassword() } @@ -52,7 +60,7 @@ } try { - if (entry.notes.isBlank()) { + if (entry.notes.isNullOrBlank()) { db.connection.prepareStatement("insert into passwords (id, name, username, password, created) values (?, ?, ?, ?, ?)") .use { it.setLong(1, id) @@ -81,10 +89,10 @@ private fun checkArguments(): Unit { var bad = false - if (!commandLine.hasOption("generate")) { - for (option in listOf("length", "symbols", "verbose")) { + if (!commandLine.hasOption(GENERATE)) { + for (option in listOf(LENGTH, SYMBOLS, VERBOSE)) { if (commandLine.hasOption(option)) { - error("--$option requires --generate") + error("--$option requires --$GENERATE") bad = true } } diff -r f245b9a53495 -r 698c4a3d758d src/main/kotlin/name/blackcap/passman/Database.kt --- a/src/main/kotlin/name/blackcap/passman/Database.kt Tue Sep 20 21:54:32 2022 -0700 +++ b/src/main/kotlin/name/blackcap/passman/Database.kt Fri Sep 23 20:59:52 2022 -0700 @@ -114,14 +114,24 @@ fun makeKey(name: String): Long = Hashing.hash(encryption.encryptFromString0(name.lowercase())) } -public fun ResultSet.getDecryptedString(columnIndex: Int, encryption: Encryption) = - encryption.decryptToString(getBytes(columnIndex)) +public fun ResultSet.getDecryptedString(columnIndex: Int, encryption: Encryption): String? { + return encryption.decryptToString(getBytes(columnIndex) ?: return null) +} -public fun ResultSet.getDecrypted(columnIndex: Int, encryption: Encryption) = - encryption.decrypt(getBytes(columnIndex)) +public fun ResultSet.getDecrypted(columnIndex: Int, encryption: Encryption): CharArray? { + return encryption.decrypt(getBytes(columnIndex) ?: return null) +} -public fun PreparedStatement.setEncryptedString(columnIndex: Int, value: String, encryption: Encryption) = - setBytes(columnIndex, encryption.encryptFromString(value)) +public fun PreparedStatement.setEncryptedString(columnIndex: Int, value: String?, encryption: Encryption) = + if (value == null) { + setNull(columnIndex, Types.BLOB) + } else { + setBytes(columnIndex, encryption.encryptFromString(value)) + } -public fun PreparedStatement.setEncrypted(columnIndex: Int, value: CharArray, encryption: Encryption) = - setBytes(columnIndex, encryption.encrypt(value)) +public fun PreparedStatement.setEncrypted(columnIndex: Int, value: CharArray?, encryption: Encryption) = + if (value == null) { + setNull(columnIndex, Types.BLOB) + } else { + setBytes(columnIndex, encryption.encrypt(value)) + } diff -r f245b9a53495 -r 698c4a3d758d src/main/kotlin/name/blackcap/passman/DeleteSubcommand.kt --- a/src/main/kotlin/name/blackcap/passman/DeleteSubcommand.kt Tue Sep 20 21:54:32 2022 -0700 +++ b/src/main/kotlin/name/blackcap/passman/DeleteSubcommand.kt Fri Sep 23 20:59:52 2022 -0700 @@ -20,4 +20,4 @@ } exitProcess(if (errors > 0) 1 else 0) } -} \ No newline at end of file +} diff -r f245b9a53495 -r 698c4a3d758d src/main/kotlin/name/blackcap/passman/Encryption.kt --- a/src/main/kotlin/name/blackcap/passman/Encryption.kt Tue Sep 20 21:54:32 2022 -0700 +++ b/src/main/kotlin/name/blackcap/passman/Encryption.kt Fri Sep 23 20:59:52 2022 -0700 @@ -94,4 +94,4 @@ fun CharBuffer.zero() { clear() array().clear() -} \ No newline at end of file +} diff -r f245b9a53495 -r 698c4a3d758d src/main/kotlin/name/blackcap/passman/Entry.kt --- a/src/main/kotlin/name/blackcap/passman/Entry.kt Tue Sep 20 21:54:32 2022 -0700 +++ b/src/main/kotlin/name/blackcap/passman/Entry.kt Fri Sep 23 20:59:52 2022 -0700 @@ -4,7 +4,7 @@ import kotlin.reflect.KProperty import kotlin.reflect.full.declaredMemberProperties -class Entry(val name: String, val username: String, val password: CharArray, val notes: String, +class Entry(val name: String, val username: String, val password: CharArray, val notes: String?, val created: Date? = null, val modified: Date? = null, val accessed: Date? = null) { companion object { @@ -59,7 +59,7 @@ fun printLong(redactPassword: String? = null) { print(redactPassword) - println("Notes: $notes") + println("Notes: ${notes ?: "(none)"}") printDate("Created", created) printDate("Modified", modified) printDate("Accessed", accessed) @@ -77,5 +77,4 @@ println(ISO8601.format(date)) } } - -} \ No newline at end of file +} diff -r f245b9a53495 -r 698c4a3d758d src/main/kotlin/name/blackcap/passman/Generate.kt --- a/src/main/kotlin/name/blackcap/passman/Generate.kt Tue Sep 20 21:54:32 2022 -0700 +++ b/src/main/kotlin/name/blackcap/passman/Generate.kt Fri Sep 23 20:59:52 2022 -0700 @@ -44,12 +44,12 @@ } /* scramble them */ - for (i in 0 until length) { - val j = randomizer.nextInt(length) - if (i != j) { - val temp = generated[i] - generated[i] = generated[j] - generated[j] = temp + for (j in 0 until length) { + val k = randomizer.nextInt(length) + if (j != k) { + val temp = generated[j] + generated[j] = generated[k] + generated[k] = temp } } diff -r f245b9a53495 -r 698c4a3d758d src/main/kotlin/name/blackcap/passman/Hashing.kt --- a/src/main/kotlin/name/blackcap/passman/Hashing.kt Tue Sep 20 21:54:32 2022 -0700 +++ b/src/main/kotlin/name/blackcap/passman/Hashing.kt Fri Sep 23 20:59:52 2022 -0700 @@ -31,4 +31,4 @@ order(ByteOrder.LITTLE_ENDIAN) getLong(hash.size - 8) } -} \ No newline at end of file +} diff -r f245b9a53495 -r 698c4a3d758d src/main/kotlin/name/blackcap/passman/HelpSubcommand.kt --- a/src/main/kotlin/name/blackcap/passman/HelpSubcommand.kt Tue Sep 20 21:54:32 2022 -0700 +++ b/src/main/kotlin/name/blackcap/passman/HelpSubcommand.kt Fri Sep 23 20:59:52 2022 -0700 @@ -12,4 +12,4 @@ println("list List records.") println("merge Merge passwords in from another PassMan database.") } -} \ No newline at end of file +} diff -r f245b9a53495 -r 698c4a3d758d src/main/kotlin/name/blackcap/passman/ListSubcommand.kt --- a/src/main/kotlin/name/blackcap/passman/ListSubcommand.kt Tue Sep 20 21:54:32 2022 -0700 +++ b/src/main/kotlin/name/blackcap/passman/ListSubcommand.kt Fri Sep 23 20:59:52 2022 -0700 @@ -63,6 +63,9 @@ HelpFormatter().printHelp("$SHORTNAME list", options) exitProcess(0) } + if (commandLine.args.isNotEmpty()) { + error("unexpected trailing arguments") + } STRING_OPTIONS.forEach { commandLine.getOptionValues(it.name)?.forEach { value -> @@ -122,8 +125,8 @@ var count = 0; while (results.next()) { val entry = Entry( - name = results.getDecryptedString(1, db.encryption), - username = results.getDecryptedString(2, db.encryption), + name = results.getDecryptedString(1, db.encryption)!!, + username = results.getDecryptedString(2, db.encryption)!!, password = REDACTED, notes = results.getDecryptedString(3, db.encryption), created = results.getDate(4), diff -r f245b9a53495 -r 698c4a3d758d src/main/kotlin/name/blackcap/passman/Main.kt --- a/src/main/kotlin/name/blackcap/passman/Main.kt Tue Sep 20 21:54:32 2022 -0700 +++ b/src/main/kotlin/name/blackcap/passman/Main.kt Fri Sep 23 20:59:52 2022 -0700 @@ -32,8 +32,6 @@ .lowercase() .replaceFirstChar { it.titlecase(Locale.getDefault()) } Class.forName("$MAIN_PACKAGE.${shortName}Subcommand") as? Class - /* val ret = Class.forName("$MAIN_PACKAGE.$shortName") - if (ret.isInstance(Subcommand::class.java)) { ret as Class } else { null } */ } catch (e: ClassNotFoundException) { null } diff -r f245b9a53495 -r 698c4a3d758d src/main/kotlin/name/blackcap/passman/MergeSubcommand.kt --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/main/kotlin/name/blackcap/passman/MergeSubcommand.kt Fri Sep 23 20:59:52 2022 -0700 @@ -0,0 +1,7 @@ +package name.blackcap.passman + +class MergeSubcommand(): Subcommand() { + override fun run(args: Array) { + error("not yet implemented") + } +} diff -r f245b9a53495 -r 698c4a3d758d src/main/kotlin/name/blackcap/passman/ReadSubcommand.kt --- a/src/main/kotlin/name/blackcap/passman/ReadSubcommand.kt Tue Sep 20 21:54:32 2022 -0700 +++ b/src/main/kotlin/name/blackcap/passman/ReadSubcommand.kt Fri Sep 23 20:59:52 2022 -0700 @@ -4,25 +4,31 @@ import kotlin.system.exitProcess class ReadSubcommand(): Subcommand() { + private companion object { + const val CLIPBOARD = "clipboard" + const val HELP = "help" + const val LONG = "long" + const val ALT_SB = "\u001b[?1049h" + const val NORM_SB = "\u001b[?1049l" + const val CLEAR = "\u001b[H\u001b[2J" + } private lateinit var commandLine: CommandLine override fun run(args: Array) { val options = Options().apply { - addOption("c", "clipboard", false, "Copy username and password into clipboard.") - addOption("h", "help", false, "Print this help message.") - addOption("l", "long", false, "Long format listing.") + addOption("c", CLIPBOARD, false, "Copy username and password into clipboard.") + addOption("h", HELP, false, "Print this help message.") + addOption("l", LONG, false, "Long format listing.") } try { commandLine = DefaultParser().parse(options, args) } catch (e: ParseException) { die(e.message ?: "syntax error", 2) } - if (commandLine.hasOption("help")) { + if (commandLine.hasOption(HELP)) { HelpFormatter().printHelp("$SHORTNAME read", options) exitProcess(0) } - val clipboard = commandLine.hasOption("clipboard") - val long = commandLine.hasOption("long") if (commandLine.args.isEmpty()) { error("expecting site name") } @@ -40,25 +46,28 @@ die("no record matches ${see(nameIn)}") } val entry = Entry( - name = result.getDecryptedString(1, db.encryption), - username = result.getDecryptedString(2, db.encryption), - password = result.getDecrypted(3, db.encryption), + name = result.getDecryptedString(1, db.encryption)!!, + username = result.getDecryptedString(2, db.encryption)!!, + password = result.getDecrypted(3, db.encryption)!!, notes = result.getDecryptedString(4, db.encryption), created = result.getDate(5), modified = result.getDate(6), accessed = result.getDate(7) ) try { - val redaction = if (clipboard) { "(in clipboard)" } else { null } - if (long) { + print(ALT_SB + CLEAR) + val redaction = if (commandLine.hasOption(CLIPBOARD)) { "(in clipboard)" } else { null } + if (commandLine.hasOption(LONG)) { entry.printLong(redaction) } else { entry.print(redaction) } - if (clipboard) { + if (commandLine.hasOption(CLIPBOARD)) { writeToClipboard(entry.password) } + name.blackcap.passman.readLine("Press ENTER to continue: ") } finally { + print(CLEAR + NORM_SB) entry.password.clear() } } diff -r f245b9a53495 -r 698c4a3d758d src/main/kotlin/name/blackcap/passman/UpdateSubcommand.kt --- a/src/main/kotlin/name/blackcap/passman/UpdateSubcommand.kt Tue Sep 20 21:54:32 2022 -0700 +++ b/src/main/kotlin/name/blackcap/passman/UpdateSubcommand.kt Fri Sep 23 20:59:52 2022 -0700 @@ -12,13 +12,15 @@ private lateinit var nameIn: String private var id by Delegates.notNull() private var length by Delegates.notNull() - private var generate by Delegates.notNull() - private var allowSymbols by Delegates.notNull() - private var verbose by Delegates.notNull() private val fields = StringBuilder() private val fieldValues = mutableListOf() private companion object { + const val GENERATE = "generate" + const val HELP = "help" + const val LENGTH = "length" + const val SYMBOLS = "symbols" + const val VERBOSE = "verbose" const val NULL_SPECIFIED = "." val NULLABLE_FIELDS = setOf("notes") val SENSITIVE_FIELDS = setOf("password") @@ -27,23 +29,27 @@ override fun run(args: Array) { parseArguments(args) checkDatabase() - update() + try { + update() + } finally { + cleanUp() + } } private fun parseArguments(args: Array) { val options = Options().apply { - addOption("g", "generate", false, "Use password generator.") - addOption("h", "help", false, "Print this help message.") - addOption("l", "length", true, "Length of generated password.") - addOption("s", "symbols", false, "Use symbol characters in generated password.") - addOption("v", "verbose", false, "Print the generated password.") + addOption("g", GENERATE, false, "Use password generator.") + addOption("h", HELP, false, "Print this help message.") + addOption("l", LENGTH, true, "Length of generated password.") + addOption("s", SYMBOLS, false, "Use symbol characters in generated password.") + addOption("v", VERBOSE, false, "Print the generated password.") } try { commandLine = DefaultParser().parse(options, args) } catch (e: ParseException) { die(e.message ?: "syntax error", 2) } - if (commandLine.hasOption("help")) { + if (commandLine.hasOption(HELP)) { HelpFormatter().printHelp("$SHORTNAME update", options) exitProcess(0) } @@ -51,25 +57,22 @@ db = Database.open() nameIn = commandLine.args[0] id = db.makeKey(nameIn) - length = commandLine.getOptionValue("length").let { rawLength -> + length = commandLine.getOptionValue(LENGTH)?.let { rawLength -> try { - rawLength?.toInt() ?: DEFAULT_GENERATED_LENGTH + rawLength.toInt() } catch (e: NumberFormatException) { die("${see(rawLength)} - invalid length") -1 /* will never happen */ } - } - generate = commandLine.hasOption("generate") - allowSymbols = commandLine.hasOption("symbols") - verbose = commandLine.hasOption("verbose") + } ?: DEFAULT_GENERATED_LENGTH } private fun checkArguments(): Unit { var bad = false - if (!commandLine.hasOption("generate")) { - for (option in listOf("length", "symbols", "verbose")) { + if (!commandLine.hasOption(GENERATE)) { + for (option in listOf(LENGTH, SYMBOLS, VERBOSE)) { if (commandLine.hasOption(option)) { - error("--$option requires --generate") + error("--$option requires --$GENERATE") bad = true } } @@ -99,7 +102,7 @@ private fun update(): Unit { updateOne("username") - if (generate) { + if (commandLine.hasOption(GENERATE)) { generatePassword() } else { updateOne("password") @@ -127,6 +130,10 @@ } } + private fun cleanUp(): Unit { + fieldValues.forEach { if (it is CharArray) { it.clear() } } + } + private fun updateOne(name: String): Unit { val prompt = name.replaceFirstChar { it.titlecase(Locale.getDefault()) } + ": " val value: Any? = if (name in SENSITIVE_FIELDS) { @@ -162,8 +169,8 @@ } private fun generatePassword(): Unit { - val newPassword = generate(length, allowSymbols) - if (verbose) { + val newPassword = generate(length, commandLine.hasOption(SYMBOLS)) + if (commandLine.hasOption(VERBOSE)) { printPassword(newPassword) } addOne("password", newPassword)