]> Sergey Matveev's repositories - meta4ra.git/blobdiff - cmd/meta4-check/main.go
Check size before hashing
[meta4ra.git] / cmd / meta4-check / main.go
index 515c3278d8aa223725f82a7c4b80a2faf200fb1e..b807a26993c8b945b477ab63c72ea3ca8b9461ae 100644 (file)
-/*
-meta4a -- Metalink 4.0 checker
-Copyright (C) 2021-2023 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"
        "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 := os.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 = os.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)