view src/main/kotlin/name/blackcap/passman/PasswordSubcommand.kt @ 26:69526ae8c8de

Allow list --name to be abbreviated -n, since it is so commonly used.
author David Barts <n5jrn@me.com>
date Fri, 05 Jul 2024 10:11:31 -0700
parents ea65ab890f66
children
line wrap: on
line source

package name.blackcap.passman

import java.nio.file.Files
import java.nio.file.Path
import java.nio.file.StandardCopyOption

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")
            return
        }
        if (!args.isEmpty()) {
            throw SubcommandException(message = "unexpected arguments", status = 2)
        }

        // Open databases
        println("Changing database encryption key...")
        val oldPath = Path.of(DB_FILE)
        val oldDb = Database.default
        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. XXX - this closes Database.default, so this subcommand may
        // only be invoked directly from the shell prompt, never in interactive
        // mode.
        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()
                }
            }
        }
    }
}