]> Sergey Matveev's repositories - meta4ra.git/blob - cmd/meta4-check/main.go
61b50486b1d6795d19920662568a36862aa885de
[meta4ra.git] / cmd / meta4-check / main.go
1 // meta4ra -- Metalink 4.0 utilities
2 // Copyright (C) 2021-2024 Sergey Matveev <stargrave@stargrave.org>
3 //
4 // This program is free software: you can redistribute it and/or modify
5 // it under the terms of the GNU General Public License as published by
6 // the Free Software Foundation, version 3 of the License.
7 //
8 // This program is distributed in the hope that it will be useful,
9 // but WITHOUT ANY WARRANTY; without even the implied warranty of
10 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11 // GNU General Public License for more details.
12 //
13 // You should have received a copy of the GNU General Public License
14 // along with this program.  If not, see <http://www.gnu.org/licenses/>.
15
16 // Metalink 4.0 checker
17 package main
18
19 import (
20         "bufio"
21         "encoding/xml"
22         "flag"
23         "fmt"
24         "io"
25         "io/fs"
26         "log"
27         "os"
28         "path"
29         "strings"
30
31         "go.stargrave.org/meta4ra"
32 )
33
34 func main() {
35         hashes := flag.String("hashes",
36                 strings.Join(meta4ra.HashesDefault, ","), "hash-name:command-s")
37         extractSig := flag.Bool("extract-sig", false, "Extract signature files")
38         metaPath := flag.String("meta4", "file.meta4", "Metalink file")
39         flag.Usage = func() {
40                 fmt.Fprintf(flag.CommandLine.Output(),
41                         "Usage: %s [options] [FILE ...]\n", os.Args[0])
42                 flag.PrintDefaults()
43                 fmt.Fprint(flag.CommandLine.Output(), `
44 If no FILEs are specified, then all <file>s from metalink are searched and
45 verified if they exist. Otherwise only specified FILEs are checked. If you
46 want to skip any <file> verification (for example only to validate the format
47 and -extract-sig, then you can just specify empty ("") FILE.
48 `)
49         }
50         flag.Parse()
51         log.SetFlags(log.Lshortfile)
52
53         data, err := os.ReadFile(*metaPath)
54         if err != nil {
55                 log.Fatalln(err)
56         }
57         var meta meta4ra.Metalink
58         err = xml.Unmarshal(data, &meta)
59         if err != nil {
60                 log.Fatalln(err)
61         }
62
63         toCheck := make(map[string]string)
64         for _, fn := range flag.Args() {
65                 toCheck[path.Base(fn)] = fn
66         }
67
68         bad := false
69         for _, f := range meta.Files {
70                 for _, sig := range f.Signature {
71                         if !*extractSig {
72                                 continue
73                         }
74                         var fn string
75                         switch sig.MediaType {
76                         case meta4ra.SigMediaTypePGP:
77                                 fn = f.Name + ".asc"
78                         case meta4ra.SigMediaTypeSSH:
79                                 fn = f.Name + ".sig"
80                         }
81                         if fn == "" {
82                                 continue
83                         }
84                         if err = os.WriteFile(
85                                 fn,
86                                 []byte(strings.TrimPrefix(sig.Signature, "\n")),
87                                 fs.FileMode(0666),
88                         ); err != nil {
89                                 fmt.Println("Error:", f.Name, "can not save signature:", err)
90                                 bad = true
91                         }
92                 }
93                 hasher := meta4ra.NewHasher(*hashes)
94                 var hashTheir string
95                 var hashName string
96                 for i, name := range hasher.Names {
97                         for _, h := range f.Hashes {
98                                 if h.Type == name {
99                                         hasher.Names = []string{name}
100                                         hasher.Cmds = append(hasher.Cmds[:0], hasher.Cmds[i])
101                                         hasher.Ins = append(hasher.Ins[:0], hasher.Ins[i])
102                                         hasher.Outs = append(hasher.Outs[:0], hasher.Outs[i])
103                                         hashName = name
104                                         hashTheir = h.Hash
105                                         goto HashFound
106                                 }
107                         }
108                 }
109                 log.Fatalln("no common hashes found for:", f.Name)
110         HashFound:
111                 fullPath := toCheck[f.Name]
112                 if !(len(toCheck) == 0 || fullPath != "") {
113                         continue
114                 }
115                 if fullPath == "" {
116                         fullPath = f.Name
117                 }
118                 fd, err := os.Open(fullPath)
119                 if err != nil {
120                         fmt.Println("Error:", f.Name, err)
121                         bad = true
122                         continue
123                 }
124                 hasher.Start()
125                 _, err = io.Copy(hasher, bufio.NewReaderSize(fd, 1<<20))
126                 fd.Close()
127                 sums := hasher.Sums()
128                 if err != nil {
129                         fmt.Println("Error:", f.Name, err)
130                         bad = true
131                         continue
132                 }
133                 hashOur := sums[0].Hash
134                 if hashOur == hashTheir {
135                         fmt.Println(f.Name, hashName, "good")
136                 } else {
137                         fmt.Println(
138                                 "Hash does not match:", f.Name, hashName,
139                                 "our:", hashOur,
140                                 "their:", hashTheir,
141                         )
142                         bad = true
143                         continue
144                 }
145         }
146         if bad {
147                 os.Exit(1)
148         }
149 }