1package commands 2 3import ( 4 "errors" 5 "fmt" 6 "io" 7 "io/ioutil" 8 "os" 9 "path/filepath" 10 "sort" 11 "strings" 12 13 "github.com/jesseduffield/horcrux/pkg/multiplexing" 14 "github.com/jesseduffield/horcrux/pkg/shamir" 15) 16 17func GetHorcruxPathsInDir(dir string) ([]string, error) { 18 files, err := ioutil.ReadDir(dir) 19 if err != nil { 20 return nil, err 21 } 22 23 paths := []string{} 24 for _, file := range files { 25 if filepath.Ext(file.Name()) == ".horcrux" { 26 paths = append(paths, file.Name()) 27 } 28 } 29 30 return paths, nil 31} 32 33type byIndex []Horcrux 34 35func (h byIndex) Len() int { 36 return len(h) 37} 38 39func (h byIndex) Swap(i, j int) { 40 h[i], h[j] = h[j], h[i] 41} 42 43func (h byIndex) Less(i, j int) bool { 44 return h[i].GetHeader().Index < h[j].GetHeader().Index 45} 46 47func GetHorcruxes(paths []string) ([]Horcrux, error) { 48 horcruxes := []Horcrux{} 49 50 for _, path := range paths { 51 currentHorcrux, err := NewHorcrux(path) 52 if err != nil { 53 return nil, err 54 } 55 for _, horcrux := range horcruxes { 56 if horcrux.GetHeader().Index == currentHorcrux.GetHeader().Index && horcrux.GetHeader().OriginalFilename == currentHorcrux.GetHeader().OriginalFilename { 57 // we've already obtained this horcrux so we'll skip this instance 58 continue 59 } 60 } 61 62 horcruxes = append(horcruxes, *currentHorcrux) 63 } 64 65 sort.Sort(byIndex(horcruxes)) 66 67 return horcruxes, nil 68} 69 70func ValidateHorcruxes(horcruxes []Horcrux) error { 71 if len(horcruxes) == 0 { 72 return errors.New("No horcruxes supplied") 73 } 74 75 if len(horcruxes) < horcruxes[0].GetHeader().Threshold { 76 return fmt.Errorf( 77 "You do not have all the required horcruxes. There are %d required to resurrect the original file. You only have %d", 78 horcruxes[0].GetHeader().Threshold, 79 len(horcruxes), 80 ) 81 } 82 83 for _, horcrux := range horcruxes { 84 if !strings.HasSuffix(horcrux.GetPath(), ".horcrux") { 85 return fmt.Errorf("%s is not a horcrux file (requires .horcrux extension)", horcrux.GetPath()) 86 } 87 if horcrux.GetHeader().OriginalFilename != horcruxes[0].GetHeader().OriginalFilename || horcrux.GetHeader().Timestamp != horcruxes[0].GetHeader().Timestamp { 88 return errors.New("All horcruxes in the given directory must have the same original filename and timestamp.") 89 } 90 } 91 92 return nil 93} 94 95func Bind(paths []string, dstPath string, overwrite bool) error { 96 horcruxes, err := GetHorcruxes(paths) 97 if err != nil { 98 return err 99 } 100 101 if err := ValidateHorcruxes(horcruxes); err != nil { 102 return err 103 } 104 105 firstHorcrux := horcruxes[0] 106 107 // if dstPath is empty we use the original filename 108 if dstPath == "" { 109 cwd, err := os.Getwd() 110 if err != nil { 111 return err 112 } 113 dstPath = filepath.Join(cwd, firstHorcrux.GetHeader().OriginalFilename) 114 } 115 116 if fileExists(dstPath) && !overwrite { 117 return os.ErrExist 118 } 119 120 keyFragments := make([][]byte, len(horcruxes)) 121 for i := range keyFragments { 122 keyFragments[i] = horcruxes[i].GetHeader().KeyFragment 123 } 124 125 key, err := shamir.Combine(keyFragments) 126 if err != nil { 127 return err 128 } 129 130 var fileReader io.Reader 131 if firstHorcrux.GetHeader().Total == firstHorcrux.GetHeader().Threshold { 132 horcruxFiles := make([]*os.File, len(horcruxes)) 133 for i, horcrux := range horcruxes { 134 horcruxFiles[i] = horcrux.GetFile() 135 } 136 137 fileReader = &multiplexing.Multiplexer{Readers: horcruxFiles} 138 } else { 139 fileReader = firstHorcrux.GetFile() // arbitrarily read from the first horcrux: they all contain the same contents 140 } 141 142 reader := cryptoReader(fileReader, key) 143 144 _ = os.Truncate(dstPath, 0) 145 146 newFile, err := os.OpenFile(dstPath, os.O_WRONLY|os.O_CREATE, 0644) 147 if err != nil { 148 return err 149 } 150 defer newFile.Close() 151 152 _, err = io.Copy(newFile, reader) 153 if err != nil { 154 return err 155 } 156 157 return err 158} 159