-/*
-meta4a -- Metalink 4.0 checker
-Copyright (C) 2021 Sergey Matveev <stargrave@stargrave.org>
-
-This program is free software: you can redistribute it and/or modify
-it under the terms of the GNU General Public License as published by
-the Free Software Foundation, version 3 of the License.
-
-This program is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-GNU General Public License for more details.
-
-You should have received a copy of the GNU General Public License
-along with this program. If not, see <http://www.gnu.org/licenses/>.
-*/
+// meta4ra -- Metalink 4.0 utilities
+// Copyright (C) 2021-2024 Sergey Matveev <stargrave@stargrave.org>
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, version 3 of the License.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see <http://www.gnu.org/licenses/>.
// Metalink 4.0 checker
package main
import (
"bufio"
- "crypto/sha256"
- "crypto/sha512"
- "encoding/hex"
"encoding/xml"
"flag"
"fmt"
- "hash"
"io"
"io/fs"
- "io/ioutil"
"log"
"os"
+ "path"
+ "strings"
"go.stargrave.org/meta4ra"
)
func main() {
+ hashes := flag.String("hashes",
+ strings.Join(meta4ra.HashesDefault, ","), "hash-name:command-s")
extractSig := flag.Bool("extract-sig", false, "Extract signature files")
- log.SetFlags(log.Lshortfile)
+ metaPath := flag.String("meta4", "file.meta4", "Metalink file")
+ flag.Usage = func() {
+ fmt.Fprintf(flag.CommandLine.Output(),
+ "Usage: %s [options] [FILE ...]\n", os.Args[0])
+ flag.PrintDefaults()
+ fmt.Fprint(flag.CommandLine.Output(), `
+If no FILEs are specified, then all <file>s from metalink are searched and
+verified if they exist. Otherwise only specified FILEs are checked. If you
+want to skip any <file> verification (for example only to validate the format
+and -extract-sig, then you can just specify empty ("") FILE.
+`)
+ }
flag.Parse()
- sha256Hasher := sha256.New()
- sha512Hasher := sha512.New()
+ log.SetFlags(log.Lshortfile)
+
+ data, err := os.ReadFile(*metaPath)
+ if err != nil {
+ log.Fatalln(err)
+ }
+ var meta meta4ra.Metalink
+ err = xml.Unmarshal(data, &meta)
+ if err != nil {
+ log.Fatalln(err)
+ }
+
+ toCheck := make(map[string]string)
+ for _, fn := range flag.Args() {
+ toCheck[path.Base(fn)] = fn
+ }
+
bad := false
- for _, metaPath := range flag.Args() {
- data, err := ioutil.ReadFile(metaPath)
- if err != nil {
- log.Fatalln(err)
- }
- var meta meta4ra.Metalink
- err = xml.Unmarshal(data, &meta)
- if err != nil {
- log.Fatalln(err)
- }
- for _, f := range meta.Files {
- if f.Signature != nil && *extractSig {
- if err = ioutil.WriteFile(
- f.Name+".asc",
- []byte(f.Signature.Signature),
- fs.FileMode(0666),
- ); err != nil {
- fmt.Println("Error:", f.Name, "can not save signature:", err)
- bad = true
- }
- }
- var sha256Digest string
- var sha512Digest string
- fd, err := os.Open(f.Name)
- if err != nil {
+ for _, f := range meta.Files {
+ for _, sig := range f.Signature {
+ if !*extractSig {
continue
}
- for _, h := range f.Hashes {
- switch h.Type {
- case meta4ra.HashSHA256:
- sha256Digest = h.Hash
- case meta4ra.HashSHA512:
- sha512Digest = h.Hash
- }
+ var fn string
+ switch sig.MediaType {
+ case meta4ra.SigMediaTypePGP:
+ fn = f.Name + ".asc"
+ case meta4ra.SigMediaTypeSSH:
+ fn = f.Name + ".sig"
}
- var digestTheir string
- var digestName string
- var hasher hash.Hash
- if sha512Digest != "" {
- digestName = meta4ra.HashSHA512
- digestTheir = sha512Digest
- hasher = sha512Hasher
- } else if sha256Digest != "" {
- digestName = meta4ra.HashSHA256
- digestTheir = sha256Digest
- hasher = sha256Hasher
- } else {
- fd.Close()
- fmt.Println("Error:", f.Name, "no satisfiable hash algorithm found")
- bad = true
+ if fn == "" {
continue
}
- hasher.Reset()
- _, err = io.Copy(hasher, bufio.NewReader(fd))
- fd.Close()
- if err != nil {
- fmt.Println("Error:", f.Name, err)
+ if err = os.WriteFile(
+ fn,
+ []byte(strings.TrimPrefix(sig.Signature, "\n")),
+ fs.FileMode(0666),
+ ); err != nil {
+ fmt.Println("Error:", f.Name, "can not save signature:", err)
bad = true
- continue
}
- digestOur := hex.EncodeToString(hasher.Sum(nil))
- if digestOur == digestTheir {
- fmt.Println(f.Name, digestName, "good")
- } else {
- fmt.Println(
- "Hash does not match:", f.Name, digestName,
- "our:", digestOur,
- "their:", digestTheir,
- )
- bad = true
- continue
+ }
+
+ fullPath := toCheck[f.Name]
+ if !(len(toCheck) == 0 || fullPath != "") {
+ continue
+ }
+ if fullPath == "" {
+ fullPath = f.Name
+ }
+ s, err := os.Stat(fullPath)
+ if err != nil {
+ fmt.Println(err)
+ bad = true
+ continue
+ }
+ if uint64(s.Size()) != f.Size {
+ fmt.Println("size mismatch",
+ f.Name, "our:", s.Size(), "their:", f.Size)
+ bad = true
+ continue
+ }
+
+ hasher := meta4ra.NewHasher(*hashes)
+ var hashTheir string
+ var hashName string
+ for i, name := range hasher.Names {
+ for _, h := range f.Hashes {
+ if h.Type == name {
+ hasher.Names = []string{name}
+ hasher.Cmds = append(hasher.Cmds[:0], hasher.Cmds[i])
+ hasher.Ins = append(hasher.Ins[:0], hasher.Ins[i])
+ hasher.Outs = append(hasher.Outs[:0], hasher.Outs[i])
+ hashName = name
+ hashTheir = h.Hash
+ goto HashFound
+ }
}
}
+ fmt.Println("no common hashes found for:", f.Name)
+ bad = true
+ continue
+
+ HashFound:
+ fd, err := os.Open(fullPath)
+ if err != nil {
+ fmt.Println("Error:", f.Name, err)
+ bad = true
+ continue
+ }
+ hasher.Start()
+ _, err = io.Copy(hasher, bufio.NewReaderSize(fd, 1<<20))
+ fd.Close()
+ sums := hasher.Sums()
+ if err != nil {
+ fmt.Println("Error:", f.Name, err)
+ bad = true
+ continue
+ }
+ hashOur := sums[0].Hash
+ if hashOur == hashTheir {
+ fmt.Println(f.Name, hashName, "good")
+ } else {
+ fmt.Println(
+ "hash mismatch:", f.Name, hashName,
+ "our:", hashOur,
+ "their:", hashTheir,
+ )
+ bad = true
+ continue
+ }
}
if bad {
os.Exit(1)