108 lines
3.0 KiB
Go
108 lines
3.0 KiB
Go
// Command ctfetch fetches and dumps a specific leaf entry from a given Static CT log.
|
|
// It can also dump the whole contents of the tile, if the -dumpall flag is specified.
|
|
// (C) Copyright 2026 Pim van Pelt <pim@ipng.ch>
|
|
package main
|
|
|
|
import (
|
|
"flag"
|
|
"fmt"
|
|
"os"
|
|
"strconv"
|
|
"strings"
|
|
|
|
"ctfetch/internal/utils"
|
|
|
|
"filippo.io/sunlight"
|
|
"golang.org/x/mod/sumdb/tlog"
|
|
)
|
|
|
|
func main() {
|
|
dumpAll := flag.Bool("dumpall", false, "dump all entries in the tile")
|
|
flag.Usage = func() {
|
|
fmt.Fprintf(os.Stderr, "Usage: %s [--dumpall] <log-url> <leaf-index>\n", os.Args[0])
|
|
fmt.Fprintf(os.Stderr, "Example: %s https://halloumi2026h1.mon.ct.ipng.ch 457683896\n", os.Args[0])
|
|
fmt.Fprintf(os.Stderr, "\nFlags:\n")
|
|
flag.PrintDefaults()
|
|
}
|
|
flag.Parse()
|
|
|
|
if flag.NArg() != 2 {
|
|
flag.Usage()
|
|
os.Exit(1)
|
|
}
|
|
|
|
logURL := strings.TrimSuffix(flag.Arg(0), "/")
|
|
leafIndex, err := strconv.ParseInt(flag.Arg(1), 10, 64)
|
|
if err != nil {
|
|
fatal("invalid leaf index: %v", err)
|
|
}
|
|
|
|
// Convert leaf index to tile coordinates
|
|
tile := tlog.TileForIndex(sunlight.TileHeight, tlog.StoredHashIndex(0, leafIndex))
|
|
tile.L = -1 // Data tiles are at level -1
|
|
|
|
// Get the tile path (both partial and full versions)
|
|
partialPath := sunlight.TilePath(tile)
|
|
|
|
// For full tile path, we need to remove the .p/W suffix if present
|
|
fullTile := tile
|
|
fullTile.W = sunlight.TileWidth
|
|
fullPath := sunlight.TilePath(fullTile)
|
|
|
|
positionInTile := leafIndex % sunlight.TileWidth
|
|
|
|
fmt.Fprintf(os.Stderr, "Leaf Index: %d\n", leafIndex)
|
|
fmt.Fprintf(os.Stderr, "Position in tile: %d\n", positionInTile)
|
|
fmt.Fprintf(os.Stderr, "Partial tile path: %s\n", partialPath)
|
|
fmt.Fprintf(os.Stderr, "Full tile path: %s\n", fullPath)
|
|
|
|
// Try to fetch the tile (partial first, then full)
|
|
var tileData []byte
|
|
var fetchedPath string
|
|
|
|
// Try partial tile first
|
|
partialURL := logURL + "/" + partialPath
|
|
fmt.Fprintf(os.Stderr, "Trying: %s\n", partialURL)
|
|
tileData, err = utils.FetchURL(partialURL)
|
|
if err == nil {
|
|
fetchedPath = partialPath
|
|
fmt.Fprintf(os.Stderr, "Successfully fetched partial tile\n")
|
|
} else {
|
|
// Fall back to full tile
|
|
fullURL := logURL + "/" + fullPath
|
|
fmt.Fprintf(os.Stderr, "Partial tile failed, trying: %s\n", fullURL)
|
|
tileData, err = utils.FetchURL(fullURL)
|
|
if err != nil {
|
|
fatal("failed to fetch tile: %v", err)
|
|
}
|
|
fetchedPath = fullPath
|
|
fmt.Fprintf(os.Stderr, "Successfully fetched full tile\n")
|
|
}
|
|
|
|
// Decompress if needed
|
|
tileData, err = utils.Decompress(tileData)
|
|
if err != nil {
|
|
fatal("failed to decompress tile: %v", err)
|
|
}
|
|
|
|
fmt.Fprintf(os.Stderr, "Tile size: %d bytes\n", len(tileData))
|
|
fmt.Fprintf(os.Stderr, "Fetched path: %s\n\n", fetchedPath)
|
|
|
|
if *dumpAll {
|
|
// Dump all entries in the tile
|
|
if err := utils.DumpAllEntries(tileData); err != nil {
|
|
fatal("%v", err)
|
|
}
|
|
} else {
|
|
// Dump only the specific entry at the position
|
|
if err := utils.DumpEntryAtPosition(tileData, int(positionInTile), leafIndex); err != nil {
|
|
fatal("%v", err)
|
|
}
|
|
}
|
|
}
|
|
|
|
func fatal(format string, args ...any) {
|
|
fmt.Fprintf(os.Stderr, "Error: "+format+"\n", args...)
|
|
os.Exit(1)
|
|
}
|