├── 2fa ├── .gitignore ├── Makefile └── main.go ├── jp ├── .gitignore └── jp.go ├── publishr ├── public │ └── .empty ├── .gitignore ├── Makefile ├── cmd_secret.go ├── README.md ├── util.go ├── cmd_init.go ├── main.go └── cmd_serve.go ├── cidr ├── .gitignore ├── Makefile └── cidr-match.go ├── infod ├── .gitignore ├── Makefile ├── README.md └── main.go ├── hash-ids ├── .gitignore ├── Makefile └── main.go ├── sub-command ├── .gitignore ├── Makefile ├── cmd_foo.go ├── cmd_bar.go └── go-subcommand.go ├── class_factory ├── .gitignore ├── Makefile ├── lazer_impl.go ├── dropkick_impl.go ├── fireball_impl.go └── main.go ├── plugins ├── .gitignore ├── README.md ├── ssh │ └── main.go ├── http │ └── main.go ├── Makefile └── driver.go ├── local-ip ├── Makefile └── local-ip.go ├── executable-source └── executable-source.go ├── README.md └── router └── main.go /2fa/.gitignore: -------------------------------------------------------------------------------- 1 | 2fa 2 | -------------------------------------------------------------------------------- /jp/.gitignore: -------------------------------------------------------------------------------- 1 | jp 2 | -------------------------------------------------------------------------------- /publishr/public/.empty: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /cidr/.gitignore: -------------------------------------------------------------------------------- 1 | cidr 2 | -------------------------------------------------------------------------------- /infod/.gitignore: -------------------------------------------------------------------------------- 1 | infod 2 | -------------------------------------------------------------------------------- /hash-ids/.gitignore: -------------------------------------------------------------------------------- 1 | hash-ids 2 | -------------------------------------------------------------------------------- /sub-command/.gitignore: -------------------------------------------------------------------------------- 1 | sub-command 2 | -------------------------------------------------------------------------------- /class_factory/.gitignore: -------------------------------------------------------------------------------- 1 | class_factory 2 | -------------------------------------------------------------------------------- /plugins/.gitignore: -------------------------------------------------------------------------------- 1 | */*.so 2 | plugins 3 | -------------------------------------------------------------------------------- /publishr/.gitignore: -------------------------------------------------------------------------------- 1 | publishr 2 | public/* 3 | -------------------------------------------------------------------------------- /cidr/Makefile: -------------------------------------------------------------------------------- 1 | 2 | cidr: $(wildcard *.go) 3 | go build . 4 | 5 | clean: 6 | go clean 7 | -------------------------------------------------------------------------------- /local-ip/Makefile: -------------------------------------------------------------------------------- 1 | 2 | local-ip: $(wildcard *.go) 3 | go build 4 | 5 | clean: 6 | go clean 7 | -------------------------------------------------------------------------------- /sub-command/Makefile: -------------------------------------------------------------------------------- 1 | 2 | sub-command: $(wildcard *.go) 3 | go build 4 | 5 | clean: 6 | go clean 7 | -------------------------------------------------------------------------------- /infod/Makefile: -------------------------------------------------------------------------------- 1 | 2 | default: server 3 | 4 | clean: 5 | go clean 6 | 7 | fmt: 8 | go fmt *.go 9 | 10 | server: main.go 11 | go build . 12 | -------------------------------------------------------------------------------- /2fa/Makefile: -------------------------------------------------------------------------------- 1 | # 2 | # Utility to make the 2fa example. 3 | # 4 | 5 | default: 2fa 6 | 7 | clean: 8 | go clean 9 | 10 | libs: 11 | go get ./... 12 | 13 | 14 | 2fa: libs main.go 15 | go build . 16 | -------------------------------------------------------------------------------- /hash-ids/Makefile: -------------------------------------------------------------------------------- 1 | # 2 | # Utility to test the hasher. 3 | # 4 | 5 | default: hash 6 | 7 | 8 | clean: 9 | go clean 10 | 11 | libs: 12 | go get ./... 13 | 14 | fmt: 15 | go fmt *.go 16 | 17 | hash: libs main.go 18 | go build . 19 | -------------------------------------------------------------------------------- /class_factory/Makefile: -------------------------------------------------------------------------------- 1 | 2 | 3 | # 4 | # Compile the driver. 5 | # 6 | main: $(wildcard *.go) 7 | go build . 8 | 9 | # 10 | # Format all our code. 11 | # 12 | fmt: 13 | find . -name '*.go' -print | xargs -n 1 go fmt 14 | 15 | 16 | clean: 17 | rm -f class_factory || true 18 | -------------------------------------------------------------------------------- /class_factory/lazer_impl.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | ) 6 | 7 | type Lazer struct{} 8 | 9 | func (s *Lazer) Execute() { 10 | fmt.Println("LAZER") 11 | } 12 | 13 | func init() { 14 | Register("Lazer", func() Ability { 15 | return &Lazer{} 16 | }) 17 | } 18 | -------------------------------------------------------------------------------- /executable-source/executable-source.go: -------------------------------------------------------------------------------- 1 | //usr/bin/env go run $0 $@; exit 2 | // 3 | // If this source-file is marked as executable 4 | // then the source may be executed! 5 | // 6 | 7 | package main 8 | 9 | import "fmt" 10 | 11 | func main() { 12 | fmt.Println("hello world") 13 | } 14 | -------------------------------------------------------------------------------- /plugins/README.md: -------------------------------------------------------------------------------- 1 | plugins/ 2 | ------- 3 | 4 | This directory contains some simple code for testing the plugin 5 | interface included in golang 1.8 beta1, or higher. 6 | 7 | The basic idea is that we can externalise code into shared libraries 8 | (read "plugins"), which a driver can load dynamically. 9 | -------------------------------------------------------------------------------- /class_factory/dropkick_impl.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | ) 6 | 7 | type DropKick struct{} 8 | 9 | func (s *DropKick) Execute() { 10 | fmt.Println("DropKick EXECUTED") 11 | } 12 | 13 | func init() { 14 | Register("Dropkick", func() Ability { 15 | return &DropKick{} 16 | }) 17 | } 18 | -------------------------------------------------------------------------------- /class_factory/fireball_impl.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | ) 6 | 7 | type Fireball struct{} 8 | 9 | func (s *Fireball) Execute() { 10 | fmt.Println("FIREBALL EXECUTED") 11 | } 12 | 13 | func init() { 14 | Register("Fireball", func() Ability { 15 | return &Fireball{} 16 | }) 17 | } 18 | -------------------------------------------------------------------------------- /plugins/ssh/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "fmt" 4 | 5 | // 6 | // This is the input to the RUN_TEST method. 7 | // 8 | var INPUT string 9 | 10 | // 11 | // This function is called by our driver. 12 | // 13 | func RUN_TEST() { 14 | fmt.Printf("I'm a SSH-plugin called with: %s\n", INPUT) 15 | } 16 | 17 | // 18 | // What kind of protocols will this plugin handle? 19 | func HANDLES() string { 20 | return "ssh" 21 | } 22 | -------------------------------------------------------------------------------- /plugins/http/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "fmt" 4 | 5 | // 6 | // This is the input to the RUN_TEST method. 7 | // 8 | var INPUT string 9 | 10 | // 11 | // This function is called by our driver. 12 | // 13 | func RUN_TEST() { 14 | fmt.Printf("I'm a HTTP-plugin called with: %s\n", INPUT) 15 | } 16 | 17 | // 18 | // What kind of protocols will this plugin handle? 19 | func HANDLES() string { 20 | return "http" 21 | } 22 | -------------------------------------------------------------------------------- /plugins/Makefile: -------------------------------------------------------------------------------- 1 | # 2 | # Compile our driver, and each plugin-subdirectory. 3 | # 4 | 5 | 6 | # 7 | # Compile the driver. 8 | # 9 | main: libs 10 | go build . 11 | 12 | # 13 | # Compile each plugin; treating each subdirectory as a new one. 14 | # 15 | libs: 16 | for i in */; do \ 17 | cd $$i ; go build -buildmode=plugin ; cd .. ;\ 18 | done 19 | 20 | # 21 | # Format all our code. 22 | # 23 | fmt: 24 | find . -name '*.go' -print | xargs -n 1 go fmt 25 | 26 | 27 | clean: 28 | rm -f */*.so || true 29 | rm -f plugins || true 30 | -------------------------------------------------------------------------------- /publishr/Makefile: -------------------------------------------------------------------------------- 1 | # 2 | # Utility to make the 2fa example. 3 | # 4 | 5 | default: server 6 | 7 | clean: 8 | go clean 9 | 10 | fmt: 11 | go fmt *.go 12 | 13 | libs: 14 | test -d $$GOPATH/github.com/dgryski/dgoogauth || go get github.com/dgryski/dgoogauth 15 | test -d $$GOPATH/github.com/gorilla/mux || go get github.com/gorilla/mux 16 | test -d $$GOPATH/github.com/rakyll/magicmime || go get github.com/rakyll/magicmime 17 | test -d $$GOPATH/github.com/speps/go-hashids || go get github.com/speps/go-hashids 18 | 19 | 20 | server: libs main.go 21 | go build . 22 | -------------------------------------------------------------------------------- /hash-ids/main.go: -------------------------------------------------------------------------------- 1 | /** 2 | * See if we can hash integers, such that 3 | * we could create unique "short identifiers" 4 | * for uploaded files. 5 | */ 6 | 7 | package main 8 | 9 | import ( 10 | "fmt" 11 | "github.com/speps/go-hashids" 12 | ) 13 | 14 | func main() { 15 | hd := hashids.NewData() 16 | hd.Salt = "I hope this is secure" 17 | hd.MinLength = 1 18 | h := hashids.NewWithData(hd) 19 | 20 | for i := 0; i < 10000; i++ { 21 | numbers := []int{99} 22 | numbers[0] = i 23 | 24 | e, _ := h.Encode(numbers) 25 | fmt.Printf("%d -> %s\n", i, e) 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /local-ip/local-ip.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "fmt" 4 | import "net" 5 | 6 | // GetLocalIP returns the non loopback local IP of the host 7 | func GetLocalIP() string { 8 | addrs, err := net.InterfaceAddrs() 9 | if err != nil { 10 | return "" 11 | } 12 | for _, address := range addrs { 13 | // check the address type and if it is not a loopback the display it 14 | if ipnet, ok := address.(*net.IPNet); ok && !ipnet.IP.IsLoopback() { 15 | if ipnet.IP.To4() != nil { 16 | return ipnet.IP.String() 17 | } 18 | } 19 | } 20 | return "" 21 | } 22 | 23 | func main() { 24 | fmt.Printf("%s\n", GetLocalIP()) 25 | } 26 | -------------------------------------------------------------------------------- /sub-command/cmd_foo.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "fmt" 4 | 5 | type cmd_foo struct{} 6 | 7 | // 8 | // Implementation for "foo" 9 | // 10 | func (r cmd_foo) name() string { 11 | return "foo" 12 | } 13 | func (r cmd_foo) help(extended bool) string { 14 | short := "run sub-command foo" 15 | if extended { 16 | fmt.Printf("%s\n\n", short) 17 | fmt.Printf("Extra Options:\n\n\tNone\n\n") 18 | } 19 | 20 | return short 21 | } 22 | func (r cmd_foo) execute(args ...string) int { 23 | fmt.Println("I am foo") 24 | fmt.Println("I received ", len(args), " arguments") 25 | 26 | for _, ent := range args { 27 | fmt.Println("Argument: ", ent) 28 | } 29 | 30 | return 0 31 | } 32 | 33 | func init() { 34 | CMDS = append(CMDS, cmd_foo{}) 35 | } 36 | -------------------------------------------------------------------------------- /sub-command/cmd_bar.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "fmt" 4 | 5 | // 6 | // types for each sub-command 7 | // 8 | type cmd_bar struct{} 9 | 10 | // 11 | // Implementation for "bar" 12 | // 13 | func (r cmd_bar) name() string { 14 | return "bar" 15 | } 16 | func (r cmd_bar) help(extended bool) string { 17 | short := "run sub-command bar" 18 | if extended { 19 | fmt.Printf("%s\n\n", short) 20 | fmt.Printf("Extra Options:\n\n\tNone\n\n") 21 | } 22 | 23 | return short 24 | } 25 | func (r cmd_bar) execute(args ...string) int { 26 | fmt.Println("I am bar") 27 | fmt.Println("I received ", len(args), " arguments") 28 | 29 | for _, ent := range args { 30 | fmt.Println("Argument: ", ent) 31 | } 32 | 33 | return 0 34 | } 35 | 36 | func init() { 37 | CMDS = append(CMDS, cmd_bar{}) 38 | } 39 | -------------------------------------------------------------------------------- /cidr/cidr-match.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "net" 6 | "os" 7 | "path" 8 | "regexp" 9 | ) 10 | 11 | func test_range(rng string, ip string) bool { 12 | match, _ := regexp.MatchString("/", rng) 13 | 14 | if match { 15 | _, cidrnet, err := net.ParseCIDR(rng) 16 | if err != nil { 17 | panic(err) 18 | } 19 | myaddr := net.ParseIP(ip) 20 | if cidrnet.Contains(myaddr) { 21 | fmt.Printf("Range %s contains IP %s.\n", rng, ip) 22 | return true 23 | } 24 | } else { 25 | if ip == rng { 26 | fmt.Printf("Literal match: %s == %s\n", ip, rng) 27 | return true 28 | } 29 | } 30 | 31 | fmt.Printf("Failed to match IP %s\n", ip) 32 | return false 33 | } 34 | 35 | func main() { 36 | 37 | if len(os.Args) >= 3 { 38 | test_range(os.Args[1], os.Args[2]) 39 | } else { 40 | fmt.Printf("Usage %s ip-range|ip1 ip2\n", path.Base(os.Args[0])) 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /class_factory/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "sync" 6 | ) 7 | 8 | type Ability interface { 9 | Execute() 10 | } 11 | 12 | var abilities = struct { 13 | m map[string]AbilityCtor 14 | sync.RWMutex 15 | }{m: make(map[string]AbilityCtor)} 16 | 17 | type AbilityCtor func() Ability 18 | 19 | func Register(id string, newfunc AbilityCtor) { 20 | abilities.Lock() 21 | abilities.m[id] = newfunc 22 | abilities.Unlock() 23 | } 24 | 25 | func DumpAbilities() { 26 | for k, _ := range abilities.m { 27 | fmt.Println("\t" + k) 28 | } 29 | } 30 | 31 | func GetAbility(id string) (a Ability) { 32 | abilities.RLock() 33 | ctor, ok := abilities.m[id] 34 | abilities.RUnlock() 35 | if ok { 36 | a = ctor() 37 | } 38 | return 39 | } 40 | 41 | func main() { 42 | fmt.Println("Abilities:") 43 | DumpAbilities() 44 | 45 | if fireball := GetAbility("Fireball"); fireball != nil { 46 | fireball.Execute() 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Go Experiments 2 | ============== 3 | 4 | A small repository of code that contains my learning-experiments using the golang language/libraries. 5 | 6 | Highlights include: 7 | 8 | * 2fa 9 | * Authentication using TOTP. 10 | * This is usually used to add two-factor support, in addition to username/password. 11 | * class_factory 12 | * A simple class-factory example. 13 | * sub-command 14 | * Writing binaries that allow subcommands. 15 | * local-ip 16 | * Getting the local IP address of your system. 17 | * cidr-match 18 | * Testing if IPs are within ranges. 19 | * hash-ids 20 | * Test the hashing-library I use for generating "shortlinks". 21 | * plugins 22 | * Simple example code of invoking code dynamically via the new golang plugin interface. 23 | * publishr 24 | * File upload/publishing server, [documented on my blog](http://blog.steve.org.uk/all_about_sharing_files_easily.html) 25 | * infod 26 | * File introspection and meta-data sharing. 27 | -------------------------------------------------------------------------------- /router/main.go: -------------------------------------------------------------------------------- 1 | // Simple HTTP server with regex-based router 2 | 3 | package main 4 | 5 | import ( 6 | "fmt" 7 | "net/http" 8 | "regexp" 9 | ) 10 | 11 | type Route struct { 12 | Regex *regexp.Regexp 13 | Handler func(w http.ResponseWriter, r *http.Request) 14 | } 15 | 16 | func homeHandler(w http.ResponseWriter, r *http.Request) { 17 | fmt.Fprintf(w, "HOME") 18 | } 19 | 20 | func hotelHandler(w http.ResponseWriter, r *http.Request) { 21 | fmt.Fprintf(w, "HOTEL %s", r.URL.Path) 22 | } 23 | 24 | func main() { 25 | routes := []Route{ 26 | {regexp.MustCompile(`^/$`), homeHandler}, 27 | {regexp.MustCompile(`^/hotels/(\d+)$`), hotelHandler}, 28 | } 29 | http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { 30 | for _, route := range routes { 31 | matches := route.Regex.FindStringSubmatch(r.URL.Path) 32 | if len(matches) >= 1 { 33 | route.Handler(w, r) 34 | return 35 | } 36 | } 37 | http.NotFound(w, r) 38 | }) 39 | fmt.Println("listening on port 8080") 40 | http.ListenAndServe(":8080", nil) 41 | } 42 | -------------------------------------------------------------------------------- /jp/jp.go: -------------------------------------------------------------------------------- 1 | // 2 | // pretty-printer for JSON. 3 | // 4 | 5 | package main 6 | 7 | import "encoding/json" 8 | import "io/ioutil" 9 | import "os" 10 | import "io" 11 | 12 | // 13 | // Return either a handle to STDIN, or the file on the command-line. 14 | // 15 | func openStdinOrFile() io.Reader { 16 | var err error 17 | r := os.Stdin 18 | if len(os.Args) > 1 { 19 | r, err = os.Open(os.Args[1]) 20 | if err != nil { 21 | panic(err) 22 | } 23 | } 24 | return r 25 | } 26 | 27 | // 28 | // Entry point. 29 | // 30 | func main() { 31 | 32 | // 33 | // Read the complete contents of the named file, or from STDIN. 34 | // 35 | input := openStdinOrFile() 36 | byt, err := ioutil.ReadAll(input) 37 | if err != nil { 38 | panic(err) 39 | } 40 | 41 | // 42 | // Unpack the JSON. 43 | // 44 | var dat map[string]interface{} 45 | 46 | if err := json.Unmarshal(byt, &dat); err != nil { 47 | panic(err) 48 | } 49 | 50 | // 51 | // Pretty-Print it. 52 | // 53 | b, err := json.MarshalIndent(dat, "", " ") 54 | if err != nil { 55 | panic(err) 56 | } 57 | 58 | // 59 | // Write it to STDOUT 60 | // 61 | b2 := append(b, '\n') 62 | os.Stdout.Write(b2) 63 | 64 | } 65 | -------------------------------------------------------------------------------- /infod/README.md: -------------------------------------------------------------------------------- 1 | infod 2 | ----- 3 | 4 | Simple server that presents information about the local system 5 | over HTTP, in a simple to consume fashion. 6 | 7 | 8 | Usage 9 | ----- 10 | 11 | Compile the server: 12 | 13 | ~$ make 14 | 15 | Launch it: 16 | 17 | ~$ ./infod 18 | 19 | Now query via curl: 20 | 21 | ~$ curl http://localhost:8000/ 22 | {"FQDN":"shelob.(none)", 23 | "Interfaces":["lo","eth0","vpn","teredo"], 24 | "IPv4":["192.168.10.64","10.0.0.200"], 25 | "IPv6":["fe80::a62:66ff:fe28:6cbf","2001:0:53aa:64c:2cbd:3bde:cafe:beef","fe80::ffff:ffff:ffff"] 26 | } 27 | 28 | 29 | This dump showed all the information. Perhaps you only cared about the hostname: 30 | 31 | ~$ curl http://localhost:8000/FQDN 32 | 33 | Or just the interfaces? 34 | 35 | ~$ curl http://localhost:8000/Interfaces 36 | 37 | You can also replace "`_`" with "`/`" in your requests: 38 | 39 | ~ $ curl http://shelob.home:8000/LSB/Version 40 | "7.9 41 | ~ $ curl http://shelob.home:8000/LSB/Codename 42 | "wheezy" 43 | 44 | The intention is obviously that every key can be queried individually, and dynamically. 45 | 46 | 47 | 48 | Patches 49 | ------- 50 | 51 | Add in things like "free", "`dpkg --list | grep ^ii | awk '{print $2}'`" and I'll accept them. 52 | 53 | Steve 54 | -- 55 | -------------------------------------------------------------------------------- /2fa/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bufio" 5 | "crypto/rand" 6 | "encoding/base32" 7 | "fmt" 8 | "github.com/dgryski/dgoogauth" 9 | "os" 10 | "strings" 11 | ) 12 | 13 | func main() { 14 | 15 | // Get random secret 16 | sec := make([]byte, 6) 17 | _, err := rand.Read(sec) 18 | if err != nil { 19 | fmt.Printf("Error creating random secret key: %s", err) 20 | } 21 | 22 | // Encode secret to base32 string 23 | secret := base32.StdEncoding.EncodeToString(sec) 24 | 25 | // Give the user instructions 26 | fmt.Printf("Please enter token for secret: %s\n", secret) 27 | fmt.Printf("Hint - you can probably run: oathtool --totp -b %s\n", secret) 28 | 29 | // Read users' input, and strip newlines, etc. 30 | reader := bufio.NewReader(os.Stdin) 31 | token, _ := reader.ReadString('\n') 32 | token = strings.TrimSpace(token) 33 | 34 | // Configure the authentication checker. 35 | otpc := &dgoogauth.OTPConfig{ 36 | Secret: secret, 37 | WindowSize: 3, 38 | HotpCounter: 0, 39 | } 40 | 41 | // Validate the submitted token 42 | val, err := otpc.Authenticate(token) 43 | if err != nil { 44 | fmt.Printf("Error authenticating token: %s\n", err) 45 | } 46 | 47 | // Did it work? 48 | if val { 49 | fmt.Printf("Access granted - token was correct.\n") 50 | } else { 51 | fmt.Printf("Access denied - token was invalid.\n") 52 | } 53 | 54 | } 55 | -------------------------------------------------------------------------------- /publishr/cmd_secret.go: -------------------------------------------------------------------------------- 1 | /** 2 | * Implemetnation for 'publishr secret'. 3 | * 4 | * This shows the randomly-generated secret. 5 | */ 6 | package main 7 | 8 | import ( 9 | "fmt" 10 | "os" 11 | ) 12 | 13 | // 14 | // types for each sub-command 15 | // 16 | type cmd_secret struct{} 17 | 18 | // 19 | // Implementation for "secret" 20 | // 21 | func (r cmd_secret) name() string { 22 | return "secret" 23 | } 24 | 25 | func (r cmd_secret) help(extended bool) string { 26 | short := "Show our authentication-secret." 27 | if extended { 28 | fmt.Printf("%s\n", short) 29 | fmt.Printf("Extra Options:\n\tNone\n") 30 | } 31 | 32 | return short 33 | } 34 | 35 | /** 36 | * Read ~/.publishr.json and show the secret 37 | */ 38 | func (r cmd_secret) execute(args ...string) int { 39 | 40 | path := os.Getenv("HOME") + "/.publishr.json" 41 | if Exists(path) { 42 | 43 | state, err := LoadState() 44 | if err != nil { 45 | fmt.Printf("Error loading state from %s\n", path) 46 | } else { 47 | 48 | fmt.Printf("Configure your authenticator-client with the secret %s\n", state.Secret) 49 | fmt.Printf("For example:\n") 50 | fmt.Printf("\toathtool --totp -b %s\n", state.Secret) 51 | } 52 | } else { 53 | fmt.Printf("Not initialized - Please run 'publishr init'\n") 54 | } 55 | return 0 56 | } 57 | 58 | func init() { 59 | CMDS = append(CMDS, cmd_secret{}) 60 | } 61 | -------------------------------------------------------------------------------- /publishr/README.md: -------------------------------------------------------------------------------- 1 | 2 | Simple Files Server 3 | ------------------- 4 | 5 | This program allows files to be uploaded, via HTTP-POST, and 6 | later read back. It can be used to store files on the move. 7 | 8 | You can upload a file like so: 9 | 10 | curl --header X-Auth:123456 -X POST -F "file=@/etc/motd" http://localhost:8081/upload 11 | 12 | Then get it back again like so: 13 | 14 | curl -v http://localhost:8081/get/vO 15 | 16 | (A succesful upload will show you the download-URL.) 17 | 18 | 19 | Authentication 20 | -------------- 21 | 22 | Uploads are protected by the "X-Auth" header, which contains a TOTP 23 | value based upon a shared secret. 24 | 25 | To generate the secret for the server, and view it, run this: 26 | 27 | publishr init 28 | publishr secret 29 | 30 | It is assumed you can import the secret into a google authenticator, 31 | or use a tool to generate a good response. 32 | 33 | 34 | Building & Deploying 35 | -------------------- 36 | 37 | There are a few dependencies which must be installed, and chances are you'll need the `libmagic-dev` package to install one of them: 38 | 39 | apt-get install libmagic-dev 40 | 41 | If you're running beneath `$GOPATH/src` you can build via: 42 | 43 | go get -d ./... 44 | go build . 45 | 46 | Otherwise you should use the provided `Makefile` to fetch the dependencies explicitly and build: 47 | 48 | make 49 | 50 | As for deployment? It is assumed you'll be hosting this behind a reverse proxy. 51 | -------------------------------------------------------------------------------- /publishr/util.go: -------------------------------------------------------------------------------- 1 | /** 2 | * Utility functions which we require, but didn't otherwise 3 | * have an obvious home. 4 | */ 5 | 6 | package main 7 | 8 | import ( 9 | "encoding/json" 10 | "io/ioutil" 11 | "os" 12 | "sync" 13 | ) 14 | 15 | var mutex = &sync.Mutex{} 16 | 17 | /** 18 | * This is the state our server itself keeps: 19 | * 20 | * Secret - Used for TOTP authentication. 21 | * 22 | * Count - The number of files uploaded. 23 | */ 24 | type PublishrState struct { 25 | Secret string `json:"secret"` 26 | Count int `json:"count"` 27 | } 28 | 29 | /** 30 | * Report whether the named file or directory exists. 31 | */ 32 | func Exists(name string) bool { 33 | if _, err := os.Stat(name); err != nil { 34 | if os.IsNotExist(err) { 35 | return false 36 | } 37 | } 38 | return true 39 | } 40 | 41 | /** 42 | * Save state 43 | */ 44 | func SaveState(state PublishrState) { 45 | 46 | mutex.Lock() 47 | state_pth := os.Getenv("HOME") + "/.publishr.json" 48 | state_json, _ := json.Marshal(state) 49 | f, _ := os.Create(state_pth) 50 | defer f.Close() 51 | f.WriteString(string(state_json)) 52 | 53 | mutex.Unlock() 54 | } 55 | 56 | /** 57 | * Load state 58 | */ 59 | func LoadState() (PublishrState, error) { 60 | mutex.Lock() 61 | 62 | state_pth := os.Getenv("HOME") + "/.publishr.json" 63 | state_cnt, _ := ioutil.ReadFile(state_pth) 64 | 65 | var state PublishrState 66 | 67 | if err := json.Unmarshal(state_cnt, &state); err != nil { 68 | mutex.Unlock() 69 | return state, err 70 | } 71 | mutex.Unlock() 72 | return state, nil 73 | 74 | } 75 | -------------------------------------------------------------------------------- /publishr/cmd_init.go: -------------------------------------------------------------------------------- 1 | /** 2 | * Implementation for 'publishr init'. 3 | * 4 | * Generate a random secret for TOTP-authentication. 5 | */ 6 | 7 | package main 8 | 9 | import ( 10 | "crypto/rand" 11 | "encoding/base32" 12 | "flag" 13 | "fmt" 14 | "os" 15 | ) 16 | 17 | // 18 | // types for each sub-command 19 | // 20 | type cmd_init struct{} 21 | 22 | // 23 | // Implementation for "init" 24 | // 25 | func (r cmd_init) name() string { 26 | return "init" 27 | } 28 | 29 | func (r cmd_init) help(extended bool) string { 30 | short := "Initialize our secure secret and state." 31 | if extended { 32 | fmt.Printf("%s\n", short) 33 | fmt.Printf("\nExtra Options:\n") 34 | fmt.Printf(" --force For overwriting any existing secret and state.\n") 35 | } 36 | 37 | return short 38 | } 39 | 40 | /** 41 | * Write ~/.publishr.json with a secret for TOTP. 42 | */ 43 | func (r cmd_init) execute(args ...string) int { 44 | 45 | f1 := flag.NewFlagSet("f1", flag.ContinueOnError) 46 | force := f1.Bool("force", false, "Force overwriting existing details.") 47 | f1.Parse(args) 48 | 49 | path := os.Getenv("HOME") + "/.publishr.json" 50 | if !Exists(path) || *force { 51 | 52 | sec := make([]byte, 6) 53 | _, err := rand.Read(sec) 54 | if err != nil { 55 | fmt.Printf("Error creating random secret key: %s", err) 56 | } 57 | 58 | secret := base32.StdEncoding.EncodeToString(sec) 59 | 60 | state := PublishrState{Secret: secret, Count: 0} 61 | 62 | SaveState(state) 63 | 64 | } else { 65 | fmt.Printf("Already initialized, and running without --force\n") 66 | } 67 | 68 | path = os.Getenv("HOME") + "/public" 69 | if !Exists(path) { 70 | os.Mkdir(path, 0755) 71 | } 72 | 73 | return 0 74 | } 75 | 76 | func init() { 77 | CMDS = append(CMDS, cmd_init{}) 78 | } 79 | -------------------------------------------------------------------------------- /plugins/driver.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "path/filepath" 6 | "plugin" 7 | ) 8 | 9 | // 10 | // Given the path to a plugin, and a string argument, invoke the plugin 11 | // method. 12 | // 13 | func run_plugin(path string, arg string) { 14 | p, err := plugin.Open(path) 15 | if err != nil { 16 | panic(err) 17 | } 18 | v, err := p.Lookup("INPUT") 19 | if err != nil { 20 | panic(err) 21 | } 22 | f, err := p.Lookup("RUN_TEST") 23 | if err != nil { 24 | panic(err) 25 | } 26 | *v.(*string) = arg 27 | f.(func())() // call the plugin 28 | 29 | } 30 | 31 | // 32 | // Call the `HANDLES` method of the specified plugin. 33 | // 34 | func plugin_handles(path string) string { 35 | p, err := plugin.Open(path) 36 | if err != nil { 37 | panic(err) 38 | } 39 | f, err := p.Lookup("HANDLES") 40 | if err != nil { 41 | panic(err) 42 | } 43 | x := f.(func() string)() 44 | return (x) 45 | } 46 | 47 | func main() { 48 | 49 | // 50 | // The plugin association array. 51 | // 52 | plugins := make(map[string]string) 53 | 54 | // 55 | // Find each plugin. 56 | // 57 | files, _ := filepath.Glob("*/*.so") 58 | 59 | // 60 | // Each plugin will handle a specific test-type 61 | // 62 | // Invoke each one so we can store a list of known-types. 63 | // 64 | for _, f := range files { 65 | plugins[f] = plugin_handles(f) 66 | } 67 | 68 | // 69 | // Show the plugins we've loaded, as well as what type of 70 | // test is accepted 71 | // 72 | fmt.Println("Plugin Dump") 73 | for plugin_path, plugin_type := range plugins { 74 | fmt.Printf("\tPlugin: %s - Handles: %s\n", 75 | plugin_path, plugin_type) 76 | } 77 | 78 | // 79 | // Invoke each plugin. 80 | // 81 | fmt.Println("Calling Plugins") 82 | for plugin_path, _ := range plugins { 83 | run_plugin(plugin_path, "Hello World!") 84 | } 85 | 86 | } 87 | -------------------------------------------------------------------------------- /publishr/main.go: -------------------------------------------------------------------------------- 1 | // 2 | // This is the entry-point for the publishr binary, which is 3 | // responsible for routing control to one of three sub-commands: 4 | // 5 | // publishr init 6 | // 7 | // publishr serve 8 | // 9 | // publishr secret 10 | // 11 | // 12 | // Steve 13 | // -- 14 | // 15 | 16 | package main 17 | 18 | import ( 19 | "fmt" 20 | "os" 21 | "path" 22 | ) 23 | 24 | // 25 | // Here's a basic interface for sub-commands. 26 | // 27 | type subcommand interface { 28 | // Show one-line help 29 | help(bool) string 30 | 31 | // Get the public-facing name of the command. 32 | name() string 33 | 34 | // Execute the command, with an array of arguments. 35 | execute(...string) int 36 | } 37 | 38 | // 39 | // Global array of registered sub-commands 40 | // 41 | var CMDS = []subcommand{} 42 | 43 | // 44 | // Entry-point. 45 | // 46 | func main() { 47 | 48 | // 49 | // If we have at least one argument 50 | // 51 | if len(os.Args) >= 2 { 52 | 53 | // 54 | // Get the sub-command 55 | // 56 | sc := os.Args[1] 57 | 58 | // 59 | // And execute it, if we found the matching class. 60 | // 61 | for _, ent := range CMDS { 62 | if sc == ent.name() { 63 | os.Exit(ent.execute(os.Args[2:]...)) 64 | } 65 | } 66 | 67 | } 68 | 69 | // 70 | // Are we looking for help? 71 | // 72 | if (len(os.Args) == 3) && (os.Args[1] == "help") { 73 | sc := os.Args[2] 74 | 75 | // 76 | // And execute it, if we found the matching class. 77 | // 78 | for _, ent := range CMDS { 79 | if sc == ent.name() { 80 | ent.help(true) 81 | os.Exit(0) 82 | } 83 | } 84 | } 85 | 86 | // 87 | // Otherwise show the commands and their help 88 | // 89 | fmt.Printf("Usage: %s subcommand [options]\n\nSubcommands include:\n\n", path.Base(os.Args[0])) 90 | 91 | fmt.Printf("\t% 8s - %s\n", "help", "Show Extended help for the named subcommand") 92 | 93 | for _, ent := range CMDS { 94 | fmt.Printf("\t% 8s - %s\n", ent.name(), ent.help(false)) 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /sub-command/go-subcommand.go: -------------------------------------------------------------------------------- 1 | // 2 | // This is a simple program which is designed to be a base for 3 | // a go-binary which handles several different sub-commands. 4 | // 5 | // For example we might have a script which supports: 6 | // 7 | // foo login username $password 8 | // 9 | // foo logout 10 | // 11 | // foo list_load_balancers 12 | // 13 | // This stub program allows the simple addition of sub-commands, via 14 | // the implementation of an interface for each. 15 | // 16 | // It is work-in-progress and very simple because go is still a little 17 | // weird to me! 18 | // 19 | // Steve 20 | // -- 21 | // 22 | 23 | package main 24 | 25 | import "fmt" 26 | import "path" 27 | import "os" 28 | 29 | // 30 | // Here's a basic interface for sub-commands. 31 | // 32 | type subcommand interface { 33 | // Show one-line help 34 | help(bool) string 35 | 36 | // Get the public-facing name of the command. 37 | name() string 38 | 39 | // Execute the command, with an array of arguments. 40 | execute(...string) int 41 | } 42 | 43 | // 44 | // Gloal array of registered sub-commands 45 | // 46 | var CMDS = []subcommand{} 47 | 48 | // 49 | // Entry-point. 50 | // 51 | func main() { 52 | 53 | // 54 | // If we have at least one argument 55 | // 56 | if len(os.Args) >= 2 { 57 | 58 | // 59 | // Get the sub-command 60 | // 61 | sc := os.Args[1] 62 | 63 | // 64 | // And execute it, if we found the matching class. 65 | // 66 | for _, ent := range CMDS { 67 | if sc == ent.name() { 68 | os.Exit(ent.execute(os.Args[2:]...)) 69 | } 70 | } 71 | 72 | } 73 | 74 | // 75 | // Are we looking for extended help? 76 | // 77 | if (len(os.Args) == 3) && (os.Args[1] == "help") { 78 | sc := os.Args[2] 79 | 80 | // 81 | // And execute it, if we found the matching class. 82 | // 83 | for _, ent := range CMDS { 84 | if sc == ent.name() { 85 | ent.help(true) 86 | os.Exit(0) 87 | } 88 | } 89 | } 90 | 91 | // 92 | // Otherwise show the commands and their help 93 | // 94 | fmt.Printf("Usage: %s subcommand [options]\n\nSubcommands include:\n\n", path.Base(os.Args[0])) 95 | fmt.Printf("\t% 8s - %s\n", "help", "Show extended help for the named subcommand") 96 | 97 | for _, ent := range CMDS { 98 | fmt.Printf("\t% 8s - %s\n", ent.name(), ent.help(false)) 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /infod/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "io" 7 | "log" 8 | "net" 9 | "net/http" 10 | "os/exec" 11 | "reflect" 12 | "strings" 13 | "sync" 14 | "time" 15 | ) 16 | 17 | /** 18 | * The information structure we return to callers. 19 | * 20 | * Note this is deliberately "flat" and "fat". 21 | */ 22 | type Information struct { 23 | ARCH string 24 | FQDN string 25 | LSB_Codename string 26 | LSB_Release string 27 | LSB_Version string 28 | Interfaces []string 29 | IPv4 []string 30 | IPv6 []string 31 | } 32 | 33 | /** 34 | * The global information set - and a mutex to protect the same. 35 | */ 36 | var info = Information{} 37 | var mutex = &sync.Mutex{} 38 | 39 | /** 40 | * Run a command, and return the output without any newlines. 41 | */ 42 | func runCommand(cmd string, args ...string) string { 43 | 44 | out, err := exec.Command(cmd, args...).Output() 45 | if err != nil { 46 | log.Panic(err) 47 | return "" 48 | } 49 | return strings.Trim(string(out), "\r\n") 50 | } 51 | 52 | /** 53 | * Populate the structure. 54 | * 55 | * This is called once at startup-time, then on a time aftewards. 56 | * By default this timer will update the global variable every 60 seconds. 57 | */ 58 | func updateInformation() { 59 | 60 | /** 61 | * Some fields in our structure are arrays. 62 | * 63 | * Create them. 64 | */ 65 | interfaces := make([]string, 0) 66 | ipv4 := make([]string, 0) 67 | ipv6 := make([]string, 0) 68 | 69 | /** 70 | * Get network interfaces 71 | */ 72 | ifaces, _ := net.Interfaces() 73 | for _, i := range ifaces { 74 | interfaces = append(interfaces, i.Name) 75 | } 76 | 77 | /** 78 | * Get IPv4 & IPv6 addresses. 79 | */ 80 | addrs, _ := net.InterfaceAddrs() 81 | for _, address := range addrs { 82 | 83 | if ipnet, ok := address.(*net.IPNet); ok && !ipnet.IP.IsLoopback() { 84 | 85 | if ipnet.IP.To4() != nil { 86 | ipv4 = append(ipv4, ipnet.IP.String()) 87 | } else { 88 | ipv6 = append(ipv6, ipnet.IP.String()) 89 | } 90 | } 91 | } 92 | 93 | /** 94 | * Update the global information - protecting access with a mutex. 95 | */ 96 | mutex.Lock() 97 | 98 | info = Information{ 99 | ARCH: runCommand("arch"), 100 | FQDN: runCommand("/bin/hostname", "--fqdn"), 101 | LSB_Codename: runCommand("/usr/bin/lsb_release", "--short", "--codename"), 102 | LSB_Release: runCommand("/usr/bin/lsb_release", "--short", "--id"), 103 | LSB_Version: runCommand("/usr/bin/lsb_release", "--short", "--release"), 104 | Interfaces: interfaces, 105 | IPv4: ipv4, 106 | IPv6: ipv6} 107 | 108 | mutex.Unlock() 109 | 110 | } 111 | 112 | /** 113 | * Our HTTP-handler. 114 | */ 115 | func handler(res http.ResponseWriter, req *http.Request) { 116 | 117 | res.Header().Set("Content-Type", "text/plain") 118 | 119 | /* Get the path */ 120 | key := req.URL.Path[1:] 121 | 122 | /** 123 | * This allows: 124 | * http://example.com:800/LSB/Release -> LSB_Release 125 | */ 126 | key = strings.Replace(key, "/", "_", -1) 127 | 128 | if key == "" { 129 | mutex.Lock() 130 | jsn, err := json.Marshal(info) 131 | if err == nil { 132 | io.WriteString(res, string(jsn)) 133 | } else { 134 | res.WriteHeader(http.StatusInternalServerError) 135 | fmt.Fprintf(res, "Failed encode to JSON") 136 | } 137 | mutex.Unlock() 138 | return 139 | } 140 | 141 | mutex.Lock() 142 | 143 | /* Perform the reflection */ 144 | r := reflect.ValueOf(&info) 145 | 146 | /* Get the field. */ 147 | v := reflect.Indirect(r).FieldByName(key) 148 | 149 | /* If it isn't invalid */ 150 | if v.Kind().String() != "invalid" { 151 | 152 | /* Get the value in JSON */ 153 | j, err := json.Marshal(v.Interface()) 154 | 155 | if err == nil { 156 | 157 | /* Success */ 158 | fmt.Fprintf(res, "%s", string(j)) 159 | mutex.Unlock() 160 | return 161 | } 162 | } 163 | mutex.Unlock() 164 | 165 | res.WriteHeader(http.StatusInternalServerError) 166 | fmt.Fprintf(res, "Failed to lookup value of %s", key) 167 | } 168 | 169 | func main() { 170 | 171 | /** 172 | * Ensure the information is populated. 173 | */ 174 | updateInformation() 175 | 176 | /** 177 | * Update the information every 60 seconds. 178 | */ 179 | ticker := time.NewTicker(time.Second * 60) 180 | go func() { 181 | for _ = range ticker.C { 182 | updateInformation() 183 | fmt.Println("Information updated") 184 | } 185 | }() 186 | 187 | http.HandleFunc("/", handler) 188 | http.ListenAndServe(":8000", nil) 189 | } 190 | -------------------------------------------------------------------------------- /publishr/cmd_serve.go: -------------------------------------------------------------------------------- 1 | /** 2 | * Implementation for 'publishr serve'. 3 | * 4 | * This starts a HTTP-server which accepts uploads, 5 | * and serves downloads. 6 | */ 7 | 8 | package main 9 | 10 | import ( 11 | "encoding/json" 12 | "errors" 13 | "flag" 14 | "fmt" 15 | "github.com/dgryski/dgoogauth" 16 | "github.com/gorilla/mux" 17 | "github.com/rakyll/magicmime" 18 | "github.com/speps/go-hashids" 19 | "io" 20 | "io/ioutil" 21 | "mime/multipart" 22 | "net" 23 | "net/http" 24 | "os" 25 | "path/filepath" 26 | "strings" 27 | "time" 28 | ) 29 | 30 | /** 31 | * types for each sub-command 32 | */ 33 | type cmd_serve struct{} 34 | 35 | /** 36 | * Implementation for "serve" 37 | */ 38 | func (r cmd_serve) name() string { 39 | return "serve" 40 | } 41 | 42 | /** 43 | * Server. 44 | */ 45 | func (r cmd_serve) help(extended bool) string { 46 | short := "Launch our HTTP-server." 47 | if extended { 48 | fmt.Printf("%s\n", short) 49 | fmt.Printf("Extra Options:\n") 50 | fmt.Printf(" --port=N Specify the port to listen upon. (8081)\n") 51 | fmt.Printf(" --host=N Specify the IP to listen upon. (127.0.0.1)\n") 52 | fmt.Printf("\n") 53 | } 54 | 55 | return short 56 | } 57 | 58 | /** 59 | * Meta-data structure for every uploaded-file. 60 | * 61 | * MIME -> The MIME-type of the uploaded file. 62 | * 63 | * IP -> The remote host that performed the upload. 64 | * 65 | * AT -> The date/time of the upload. 66 | * 67 | */ 68 | type UploadMetaData struct { 69 | MIME string `json:"MIME"` 70 | IP string `json:"IP"` 71 | AT string `json:"AT"` 72 | } 73 | 74 | /** 75 | * Get the next short-ID. 76 | * 77 | * Do that by loading the state, increasing the count, and saving it. 78 | */ 79 | func NextShortID() string { 80 | 81 | state, _ := LoadState() 82 | 83 | /** 84 | * Increase the count, and hash it. 85 | */ 86 | state.Count += 1 87 | 88 | hd := hashids.NewData() 89 | hd.Salt = "I hope this is secure" 90 | hd.MinLength = 1 91 | h := hashids.NewWithData(hd) 92 | 93 | numbers := []int{99} 94 | numbers[0] = state.Count 95 | hash, _ := h.Encode(numbers) 96 | 97 | /** 98 | * Write out the body 99 | */ 100 | SaveState(state) 101 | return hash 102 | } 103 | 104 | /** 105 | * Get the remote IP-address, taking account of X-Forwarded-For. 106 | */ 107 | func getRemoteIP(r *http.Request) string { 108 | 109 | hdrForwardedFor := r.Header.Get("X-Forwarded-For") 110 | if hdrForwardedFor != "" { 111 | parts := strings.Split(hdrForwardedFor, ",") 112 | // TODO: should return first non-local address 113 | return parts[0] 114 | } 115 | 116 | // Fall-back 117 | ip, _, _ := net.SplitHostPort(r.RemoteAddr) 118 | return (ip) 119 | } 120 | 121 | /** 122 | * Called via GET /get/XXXXXX 123 | */ 124 | func GetHandler(res http.ResponseWriter, req *http.Request) { 125 | var ( 126 | status int 127 | err error 128 | ) 129 | defer func() { 130 | if nil != err { 131 | http.Error(res, err.Error(), status) 132 | } 133 | }() 134 | 135 | vars := mux.Vars(req) 136 | fname := vars["id"] 137 | 138 | // Remove any suffix that might be present. 139 | extension := filepath.Ext(fname) 140 | fname = fname[0 : len(fname)-len(extension)] 141 | 142 | fname = "./public/" + fname 143 | 144 | if !Exists(fname) || !Exists(fname+".meta") { 145 | http.NotFound(res, req) 146 | return 147 | } 148 | 149 | file, _ := ioutil.ReadFile(fname + ".meta") 150 | 151 | var md UploadMetaData 152 | 153 | if err := json.Unmarshal(file, &md); err != nil { 154 | status = 500 155 | err = errors.New("Loading JSON failed") 156 | return 157 | } 158 | 159 | /** 160 | * Serve the file, with the correct MIME-type 161 | */ 162 | res.Header().Set("Content-Type", md.MIME) 163 | http.ServeFile(res, req, fname) 164 | } 165 | 166 | /** 167 | * Upload a file to ./public/ - with a short-name. 168 | * 169 | * Each file will also have a ".meta" file created, to contain 170 | * some content. 171 | */ 172 | func UploadHandler(res http.ResponseWriter, req *http.Request) { 173 | var ( 174 | status int 175 | err error 176 | ) 177 | defer func() { 178 | if nil != err { 179 | http.Error(res, err.Error(), status) 180 | } 181 | }() 182 | 183 | /** 184 | * Get the authentication-header. If missing we abort. 185 | */ 186 | auth := string(req.Header.Get("X-Auth")) 187 | if len(auth) < 1 { 188 | status = 401 189 | err = errors.New("Missing X-Auth header") 190 | return 191 | } 192 | auth = strings.TrimSpace(auth) 193 | 194 | /** 195 | * Load the secret. 196 | */ 197 | state, err := LoadState() 198 | if err != nil { 199 | status = 500 200 | err = errors.New("Loading state failed") 201 | return 202 | } 203 | 204 | /** 205 | * Test the token. 206 | */ 207 | otpc := &dgoogauth.OTPConfig{ 208 | Secret: state.Secret, 209 | WindowSize: 3, 210 | HotpCounter: 0, 211 | } 212 | 213 | val, err := otpc.Authenticate(auth) 214 | if err != nil { 215 | status = 401 216 | err = errors.New("Failed to use X-Auth header") 217 | return 218 | } 219 | 220 | /** 221 | * If it failed then we're done. 222 | */ 223 | if !val { 224 | status = 401 225 | err = errors.New("Invalid X-Auth header") 226 | return 227 | } 228 | 229 | /** 230 | ** At ths point we know we have an authorized submitter. 231 | **/ 232 | 233 | /** 234 | * Parse the incoming request 235 | */ 236 | const _24K = (1 << 20) * 24 237 | if err = req.ParseMultipartForm(_24K); nil != err { 238 | status = http.StatusInternalServerError 239 | return 240 | } 241 | 242 | /** 243 | * Get the short-ID 244 | */ 245 | sn := NextShortID() 246 | 247 | for _, fheaders := range req.MultipartForm.File { 248 | for _, hdr := range fheaders { 249 | // open uploaded 250 | var infile multipart.File 251 | if infile, err = hdr.Open(); nil != err { 252 | status = http.StatusInternalServerError 253 | return 254 | } 255 | // open destination 256 | var outfile *os.File 257 | if outfile, err = os.Create("./public/" + sn); nil != err { 258 | status = http.StatusInternalServerError 259 | return 260 | } 261 | // 32K buffer copy 262 | if _, err = io.Copy(outfile, infile); nil != err { 263 | status = http.StatusInternalServerError 264 | return 265 | } 266 | 267 | // Get the MIME-type of the uploaded file. 268 | err = magicmime.Open(magicmime.MAGIC_MIME_TYPE | magicmime.MAGIC_SYMLINK | magicmime.MAGIC_ERROR) 269 | if err != nil { 270 | status = http.StatusInternalServerError 271 | return 272 | } 273 | defer magicmime.Close() 274 | mimetype, _ := magicmime.TypeByFile("./public/" + sn) 275 | 276 | // 277 | // Write out the meta-data - which is a structure 278 | // containing the following members. 279 | // 280 | md := &UploadMetaData{MIME: mimetype, IP: getRemoteIP(req), AT: time.Now().Format(time.RFC850)} 281 | data_json, _ := json.Marshal(md) 282 | 283 | var meta *os.File 284 | defer meta.Close() 285 | if meta, err = os.Create("./public/" + sn + ".meta"); nil != err { 286 | status = http.StatusInternalServerError 287 | return 288 | } 289 | meta.WriteString(string(data_json)) //mimetype) 290 | 291 | // 292 | // Write out the redirection - using the host 293 | // scheme, and the end-point of the new upload. 294 | // 295 | hostname := req.Host 296 | scheme := "http" 297 | 298 | if strings.HasPrefix(req.Proto, "HTTPS") { 299 | scheme = "https" 300 | } 301 | if req.Header.Get("X-Forwarded-Proto") == "https" { 302 | scheme = "https" 303 | } 304 | 305 | res.Write([]byte(scheme + "://" + hostname + "/get/" + sn + "\n")) 306 | } 307 | } 308 | } 309 | 310 | /** 311 | * Called via GET /robots.txt 312 | */ 313 | func RobotsHandler(res http.ResponseWriter, req *http.Request) { 314 | fmt.Fprintf(res, "User-agent: *\nDisallow: /") 315 | } 316 | 317 | /** 318 | * Fallback handler, returns 404 for all requests. 319 | */ 320 | func MissingHandler(res http.ResponseWriter, req *http.Request) { 321 | res.WriteHeader(http.StatusNotFound) 322 | fmt.Fprintf(res, "publishr - 404 - content is not hosted here.") 323 | } 324 | 325 | /** 326 | * Launch our HTTP-server. 327 | */ 328 | func (r cmd_serve) execute(args ...string) int { 329 | 330 | f1 := flag.NewFlagSet("f1", flag.ContinueOnError) 331 | port := f1.String("port", "8081", "The port to bind to.") 332 | host := f1.String("host", "127.0.0.1", "The host to listen upon.") 333 | f1.Parse(args) 334 | 335 | /* Create a router */ 336 | router := mux.NewRouter() 337 | 338 | /* Get a previous upload */ 339 | router.HandleFunc("/get/{id}", GetHandler).Methods("GET") 340 | 341 | /* Post a new one */ 342 | router.HandleFunc("/upload", UploadHandler).Methods("POST") 343 | 344 | /* Robots.txt handler */ 345 | router.HandleFunc("/robots.txt", RobotsHandler).Methods("GET") 346 | 347 | /* Error-Handler - Return a 404 on all requests */ 348 | router.PathPrefix("/").HandlerFunc(MissingHandler) 349 | 350 | /* Load the routers beneath the server root */ 351 | http.Handle("/", router) 352 | 353 | /* Build up the bind-string */ 354 | bind := *host + ":" + *port 355 | 356 | /* Launch the server */ 357 | fmt.Printf("Launching the server on http://%s\n", bind) 358 | err := http.ListenAndServe(fmt.Sprintf("%s:%s", *host, *port), nil) 359 | if err != nil { 360 | panic(err) 361 | } 362 | return 0 363 | } 364 | 365 | func init() { 366 | CMDS = append(CMDS, cmd_serve{}) 367 | } 368 | --------------------------------------------------------------------------------