Mercurial > cgi-bin > hgweb.cgi > PassMan
changeset 15:0fc90892a3ae
Add password subcommand.
author | David Barts <n5jrn@me.com> |
---|---|
date | Fri, 03 Feb 2023 18:48:13 -0800 |
parents | 4dae7a15ee48 |
children | 7a74ae668665 |
files | src/main/kotlin/name/blackcap/passman/Database.kt src/main/kotlin/name/blackcap/passman/Files.kt src/main/kotlin/name/blackcap/passman/HelpSubcommand.kt src/main/kotlin/name/blackcap/passman/PasswordSubcommand.kt src/main/kotlin/name/blackcap/passman/RenameSubcommand.kt |
diffstat | 5 files changed, 94 insertions(+), 27 deletions(-) [+] |
line wrap: on
line diff
--- a/src/main/kotlin/name/blackcap/passman/Database.kt Tue Jan 31 19:07:46 2023 -0800 +++ b/src/main/kotlin/name/blackcap/passman/Database.kt Fri Feb 03 18:48:13 2023 -0800 @@ -149,3 +149,11 @@ } else { setLong(columnIndex, value) } + +fun PreparedStatement.setDateOrNull(parameterIndex: Int, value: Long?) { + if (value == null || value == 0L) { + setNull(parameterIndex, Types.INTEGER) + } else { + setLong(parameterIndex, value) + } +}
--- a/src/main/kotlin/name/blackcap/passman/Files.kt Tue Jan 31 19:07:46 2023 -0800 +++ b/src/main/kotlin/name/blackcap/passman/Files.kt Fri Feb 03 18:48:13 2023 -0800 @@ -1,10 +1,8 @@ package name.blackcap.passman -import java.io.BufferedReader -import java.io.File -import java.io.FileInputStream -import java.io.InputStreamReader import java.nio.charset.StandardCharsets +import java.nio.file.Files +import java.nio.file.Path import java.util.* import kotlin.system.exitProcess @@ -28,27 +26,26 @@ } } -/* joins path name components to java.io.File */ - -fun joinPath(base: String, vararg rest: String) = rest.fold(File(base), ::File) - /* file names */ const val SHORTNAME = "passman" const val MAIN_PACKAGE = "name.blackcap." + SHORTNAME private val HOME = System.getenv("HOME") private val PF_DIR = when (OS.type) { - OS.MAC -> joinPath(HOME, "Library", "Application Support", MAIN_PACKAGE) - OS.WINDOWS -> joinPath(System.getenv("APPDATA"), MAIN_PACKAGE) - else -> joinPath(HOME, "." + SHORTNAME) + OS.MAC -> Path.of(HOME, "Library", "Application Support", MAIN_PACKAGE) + OS.WINDOWS -> Path.of(System.getenv("APPDATA"), MAIN_PACKAGE) + else -> Path.of(HOME, "." + SHORTNAME) } -val PROP_FILE = File(PF_DIR, SHORTNAME + ".properties") -val DB_FILE: String = File(PF_DIR, SHORTNAME + ".db").absolutePath +val PROP_FILE = PF_DIR.resolve( SHORTNAME + ".properties") +val DB_FILE: String = PF_DIR.resolve(SHORTNAME + ".db").toAbsolutePath().toString() +val NEW_DB_FILE: String = PF_DIR.resolve("new.db").toAbsolutePath().toString() -/* make some needed directories */ +/* make some needed directories/files */ -private fun File.makeIfNeeded() = if (exists()) { true } else { mkdirs() } +private fun makeIfNeeded(p: Path) = if (Files.exists(p)) { p } else { Files.createDirectories(p) } + +private fun createIfNeeded(p: Path) = if (Files.exists(p)) { p } else { Files.createFile(p) } /* make some usable objects */ @@ -57,9 +54,9 @@ } val PROPERTIES = Properties(DPROPERTIES).apply { - PF_DIR.makeIfNeeded() - PROP_FILE.createNewFile() - BufferedReader(InputStreamReader(FileInputStream(PROP_FILE), StandardCharsets.UTF_8)).use { + makeIfNeeded(PF_DIR) + createIfNeeded(PROP_FILE) + Files.newBufferedReader(PROP_FILE, StandardCharsets.UTF_8).use { load(it) } }
--- a/src/main/kotlin/name/blackcap/passman/HelpSubcommand.kt Tue Jan 31 19:07:46 2023 -0800 +++ b/src/main/kotlin/name/blackcap/passman/HelpSubcommand.kt Fri Feb 03 18:48:13 2023 -0800 @@ -10,10 +10,11 @@ println("import Import from CSV file.") println("list List records.") println("merge Merge passwords in from another PassMan database.") + println("password Change database encryption key.") println("read Retrieve data from existing record.") println("rename Rename existing record.") println("update Update existing record.") println() - println("Database is $DB_FILE .") + println("Database is ${see(DB_FILE)} .") } }
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/main/kotlin/name/blackcap/passman/PasswordSubcommand.kt Fri Feb 03 18:48:13 2023 -0800 @@ -0,0 +1,69 @@ +package name.blackcap.passman + +import java.nio.file.Files +import java.nio.file.Path +import java.nio.file.StandardCopyOption +import kotlin.system.exitProcess + +class PasswordSubcommand : Subcommand() { + override fun run(args: Array<String>) { + // Parse arguments + if (args.size > 0 && (args[0] == "-h" || args[0].startsWith("--h"))) { + println("usage: passman password") + exitProcess(0) + } + if (!args.isEmpty()) { + die("unexpected arguments", 2) + } + + // Open databases + println("Changing database encryption key...") + val oldPath = Path.of(DB_FILE) + val oldDb = Database.open(fileName = DB_FILE, passwordPrompt = "Old database key: ") + val newPath = Path.of(NEW_DB_FILE) + if (Files.exists(newPath)) { + println("WARNING: deleting ${see(NEW_DB_FILE)}") + Files.delete(newPath) + } + val newDb = Database.open(fileName = NEW_DB_FILE, passwordPrompt = "New database key: ", create = true) + + // Copy old to new + println("WARNING: do not interrupt this process or data may be lost!") + copyRecords(oldDb, newDb) + + // Wrap up + oldDb.connection.close() + newDb.connection.close() + Files.move(newPath, oldPath, StandardCopyOption.REPLACE_EXISTING) + println("Database key changed.") + } + + private fun copyRecords(oldDb: Database, newDb: Database) { + oldDb.connection.prepareStatement("select name, username, password, notes, created, modified, accessed from passwords").use { old -> + val results = old.executeQuery() + while (results.next()) { + newDb.connection.prepareStatement("insert into passwords (id, name, username, password, notes, created, modified, accessed) values (?, ?, ?, ?, ?, ?, ?, ?)").use { new -> + // id and name + val name = results.getDecryptedString(1, oldDb.encryption)!! + new.setLong(1, newDb.makeKey(name)) + new.setEncryptedString(2, name, newDb.encryption) + // username + new.setEncryptedString(3, + results.getDecryptedString(2, oldDb.encryption)!!, newDb.encryption) + // password + val password = results.getDecrypted(3, oldDb.encryption)!! + new.setEncrypted(4, password, newDb.encryption) + password.clear() + // notes + new.setEncryptedString(5, + results.getDecryptedString(4, oldDb.encryption), newDb.encryption) + // created, modified, accessed + new.setDateOrNull(6, results.getLong(5)) + new.setDateOrNull(7, results.getLong(6)) + new.setDateOrNull(8, results.getLong(7)) + new.executeUpdate() + } + } + } + } +} \ No newline at end of file
--- a/src/main/kotlin/name/blackcap/passman/RenameSubcommand.kt Tue Jan 31 19:07:46 2023 -0800 +++ b/src/main/kotlin/name/blackcap/passman/RenameSubcommand.kt Fri Feb 03 18:48:13 2023 -0800 @@ -95,12 +95,4 @@ it.executeUpdate() } } - - private fun PreparedStatement.setDateOrNull(parameterIndex: Int, value: Long?) { - if (value == null || value == 0L) { - setNull(parameterIndex, Types.INTEGER) - } else { - setLong(parameterIndex, value) - } - } }