diff --git a/debian/changelog b/debian/changelog
index 6352e51..5af9cdf 100644
--- a/debian/changelog
+++ b/debian/changelog
@@ -1,3 +1,9 @@
+nss-passwords (0.3+git20210609.1.07e1eec-1) UNRELEASED; urgency=low
+
+  * New upstream snapshot.
+
+ -- Debian Janitor <janitor@jelmer.uk>  Tue, 05 Apr 2022 00:03:26 -0000
+
 nss-passwords (0.3-2) unstable; urgency=medium
 
   [ Stéphane Glondu ]
diff --git a/json_types.atd b/json_types.atd
index f76a601..f1070b1 100644
--- a/json_types.atd
+++ b/json_types.atd
@@ -2,8 +2,16 @@ type login = {
   hostname : string;
   encryptedUsername : string;
   encryptedPassword : string;
-}
+} <ocaml field_prefix="i">
 
 type logins = {
   logins : login list;
 }
+
+type output_login = {
+  hostname : string;
+  username : string;
+  password : string;
+}
+
+type output = output_login list
diff --git a/main.ml b/main.ml
index 8736f02..7a2e3c8 100644
--- a/main.ml
+++ b/main.ml
@@ -30,6 +30,8 @@
  *
  * ***** END LICENSE BLOCK ***** *)
 
+open Json_types_j
+
 (** C interface *)
 
 exception NSS_init_failed
@@ -56,17 +58,37 @@ external ttyname : Unix.file_descr -> string = "caml_ttyname"
 let dir = ref ""
 let pinentry = ref "pinentry"
 let queries = ref []
+let json_output = ref false
 
 let spec =  Arg.align [
   "-d", Arg.Set_string dir, "profile directory (default: Firefox default profile)";
   "-p", Arg.Set_string pinentry, "pinentry program to use (default: pinentry)";
+  "-j", Arg.Set json_output, " output result in JSON";
 ]
-let usage_msg = "nss-passwords [-d <dir>] [-p <pinentry>] query [...]"
+let usage_msg = "\
+nss-passwords [-d <dir>] [-p <pinentry>] query [...]\n\
+A query is either hostname:string, username:string, or string (which\n\
+translates to hostname:string)."
 
 exception Found of string
 
+let chop_prefix ~prefix x =
+  let n = String.length x and nprefix = String.length prefix in
+  if n >= nprefix && String.sub x 0 nprefix = prefix then
+    Some (String.sub x nprefix (n - nprefix))
+  else
+    None
+
+let parse_query x =
+  match chop_prefix ~prefix:"hostname:" x with
+  | Some x -> `Hostname x
+  | None ->
+     match chop_prefix ~prefix:"username:" x with
+     | Some x -> `Username x
+     | None -> `Hostname x
+
 let () =
-  Arg.parse spec (fun x -> queries := x :: !queries) usage_msg;
+  Arg.parse spec (fun x -> queries := parse_query x :: !queries) usage_msg;
   if !queries = [] then begin
     Arg.usage spec usage_msg;
     exit 1
@@ -147,38 +169,64 @@ let quote_query buf x =
 
 let results = ref []
 
-let process_row = function
-  | [| hostname; encryptedUsername; encryptedPassword |] ->
-    let username = do_decrypt ~callback ~data:encryptedUsername in
-    let password = do_decrypt ~callback ~data:encryptedPassword in
-    results := (hostname, username, password) :: !results
-  | _ -> assert false
-
-let exec db query =
+let exec_hostname db query =
+  let cb = function
+    | [| hostname; encryptedUsername; encryptedPassword |] ->
+       let username = do_decrypt ~callback ~data:encryptedUsername in
+       let password = do_decrypt ~callback ~data:encryptedPassword in
+       results := {hostname; username; password} :: !results
+    | _ -> assert false
+  in
   let buf = Buffer.create (2 * String.length query + 128) in
   Printf.bprintf buf
     "SELECT hostname, encryptedUsername, encryptedPassword FROM moz_logins WHERE hostname LIKE %a ESCAPE 'x';"
     quote_query query;
-  let r = Sqlite3.exec_not_null_no_headers ~cb:process_row db (Buffer.contents buf) in
+  let r = Sqlite3.exec_not_null_no_headers ~cb db (Buffer.contents buf) in
   assert (r = Sqlite3.Rc.OK)
 
+let exec_username db query =
+  let rex = Str.regexp (".*" ^ Str.quote query ^ ".*") in
+  let cb = function
+    | [| hostname; encryptedUsername; encryptedPassword |] ->
+       let username = do_decrypt ~callback ~data:encryptedUsername in
+       if Str.string_match rex username 0 then (
+         let password = do_decrypt ~callback ~data:encryptedPassword in
+         results := {hostname; username; password} :: !results
+       )
+    | _ -> assert false
+  in
+  let sql = "SELECT hostname, encryptedUsername, encryptedPassword FROM moz_logins;" in
+  let r = Sqlite3.exec_not_null_no_headers ~cb db sql in
+  assert (r = Sqlite3.Rc.OK)
+
+let exec db = function
+  | `Hostname x -> exec_hostname db x
+  | `Username x -> exec_username db x
+
 let exec_sqlite () =
   let db = Sqlite3.db_open (FilePath.concat !dir "signons.sqlite") in
   List.iter (exec db) !queries;
   let r = Sqlite3.db_close db in
   assert (r = true)
 
-open Json_types_j
-
 let json_process logins query =
-  let rex = Str.regexp (".*" ^ Str.quote query ^ ".*") in
+  let string_match =
+    match query with
+    | `Hostname x ->
+       let rex = Str.regexp (".*" ^ Str.quote x ^ ".*") in
+       fun hostname _ -> Str.string_match rex hostname 0
+    | `Username x ->
+       let rex = Str.regexp (".*" ^ Str.quote x ^ ".*") in
+       fun _ username -> Str.string_match rex username 0
+  in
   List.iter
     (fun l ->
-     if Str.string_match rex l.hostname 0 then (
-       let username = do_decrypt ~callback ~data:l.encryptedUsername in
-       let password = do_decrypt ~callback ~data:l.encryptedPassword in
-       results := (l.hostname, username, password) :: !results
-     )
+      let hostname = l.ihostname in
+      let username = do_decrypt ~callback ~data:l.iencryptedUsername in
+      if string_match hostname username then (
+        let password = do_decrypt ~callback ~data:l.iencryptedPassword in
+        results := {hostname; username; password} :: !results
+      )
     ) logins
 
 let exec_json () =
@@ -189,6 +237,25 @@ let exec_json () =
   close_in ic;
   List.iter (json_process logins.logins) !queries
 
+let print_as_table results =
+  let (a, b, c) =
+    List.fold_left
+      (fun (a, b, c) o ->
+        let a = max a (String.length o.hostname) in
+        let b = max b (String.length o.username) in
+        let c = max c (String.length o.password) in
+        (a, b, c))
+      (0, 0, 0)
+      results
+  in
+  List.iter
+    (fun o ->
+      Printf.printf "| %-*s | %-*s | %-*s |\n" a o.hostname b o.username c o.password
+    ) results
+
+let print_as_json results =
+  print_endline (string_of_output results)
+
 let () =
   try
     (if Sys.file_exists (FilePath.concat !dir "logins.json")
@@ -196,18 +263,7 @@ let () =
      else exec_sqlite ()
     );
     let results = List.sort compare !results in
-    let (a, b, c) = List.fold_left
-      (fun (a, b, c) (x, y, z) ->
-        let a = max a (String.length x) in
-        let b = max b (String.length y) in
-        let c = max c (String.length z) in
-        (a, b, c))
-      (0, 0, 0)
-      results
-    in
-    List.iter (fun (x, y, z) ->
-      Printf.printf "| %-*s | %-*s | %-*s |\n" a x b y c z
-    ) results
+    (if !json_output then print_as_json else print_as_table) results
   with
     | NSS_decrypt_failed(_, _, Some e) ->
       Printf.eprintf "Error while decrypting: %s\n" (Printexc.to_string e);
diff --git a/nss_stubs.c b/nss_stubs.c
index 7494939..13bf344 100644
--- a/nss_stubs.c
+++ b/nss_stubs.c
@@ -98,7 +98,7 @@ CAMLprim value caml_nss_init(value path) {
 CAMLprim value caml_do_decrypt(value callback, value data) {
   CAMLparam2(callback, data);
   CAMLlocal3(res, exn, cb_data);
-  char *dataString = String_val(data);
+  const char *dataString = String_val(data);
   int strLen = caml_string_length(data);
   SECItem *decoded = NSSBase64_DecodeBuffer(NULL, NULL, dataString, strLen);
   SECStatus rv;
@@ -125,7 +125,7 @@ CAMLprim value caml_do_decrypt(value callback, value data) {
   SECITEM_ZfreeItem(decoded, PR_TRUE);
   if (rv == SECSuccess) {
     res = caml_alloc_string(result.len);
-    memcpy(String_val(res), result.data, result.len);
+    memcpy(Bytes_val(res), result.data, result.len);
     SECITEM_ZfreeItem(&result, PR_FALSE);
     CAMLreturn(res);
   }