1 | // Copyright 2018 The Go Authors. All rights reserved. |
---|---|
2 | // Use of this source code is governed by a BSD-style |
3 | // license that can be found in the LICENSE file. |
4 | |
5 | // netrcauth uses a .netrc file (or _netrc file on Windows) to implement the |
6 | // GOAUTH protocol described in https://golang.org/issue/26232. |
7 | // It expects the location of the file as the first command-line argument. |
8 | // |
9 | // Example GOAUTH usage: |
10 | // |
11 | // export GOAUTH="netrcauth $HOME/.netrc" |
12 | // |
13 | // See https://www.gnu.org/software/inetutils/manual/html_node/The-_002enetrc-file.html |
14 | // or run 'man 5 netrc' for a description of the .netrc file format. |
15 | package main |
16 | |
17 | import ( |
18 | "fmt" |
19 | "io/ioutil" |
20 | "log" |
21 | "net/http" |
22 | "net/url" |
23 | "os" |
24 | "strings" |
25 | ) |
26 | |
27 | func main() { |
28 | if len(os.Args) < 2 { |
29 | fmt.Fprintf(os.Stderr, "usage: %s NETRCFILE [URL]", os.Args[0]) |
30 | os.Exit(2) |
31 | } |
32 | |
33 | log.SetPrefix("netrcauth: ") |
34 | |
35 | if len(os.Args) != 2 { |
36 | // An explicit URL was passed on the command line, but netrcauth does not |
37 | // have any URL-specific output: it dumps the entire .netrc file at the |
38 | // first call. |
39 | return |
40 | } |
41 | |
42 | path := os.Args[1] |
43 | |
44 | data, err := ioutil.ReadFile(path) |
45 | if err != nil { |
46 | if os.IsNotExist(err) { |
47 | return |
48 | } |
49 | log.Fatalf("failed to read %s: %v\n", path, err) |
50 | } |
51 | |
52 | u := &url.URL{Scheme: "https"} |
53 | lines := parseNetrc(string(data)) |
54 | for _, l := range lines { |
55 | u.Host = l.machine |
56 | fmt.Printf("%s\n\n", u) |
57 | |
58 | req := &http.Request{Header: make(http.Header)} |
59 | req.SetBasicAuth(l.login, l.password) |
60 | req.Header.Write(os.Stdout) |
61 | fmt.Println() |
62 | } |
63 | } |
64 | |
65 | // The following functions were extracted from src/cmd/go/internal/web2/web.go |
66 | // as of https://golang.org/cl/161698. |
67 | |
68 | type netrcLine struct { |
69 | machine string |
70 | login string |
71 | password string |
72 | } |
73 | |
74 | func parseNetrc(data string) []netrcLine { |
75 | // See https://www.gnu.org/software/inetutils/manual/html_node/The-_002enetrc-file.html |
76 | // for documentation on the .netrc format. |
77 | var nrc []netrcLine |
78 | var l netrcLine |
79 | inMacro := false |
80 | for _, line := range strings.Split(data, "\n") { |
81 | if inMacro { |
82 | if line == "" { |
83 | inMacro = false |
84 | } |
85 | continue |
86 | } |
87 | |
88 | f := strings.Fields(line) |
89 | i := 0 |
90 | for ; i < len(f)-1; i += 2 { |
91 | // Reset at each "machine" token. |
92 | // “The auto-login process searches the .netrc file for a machine token |
93 | // that matches […]. Once a match is made, the subsequent .netrc tokens |
94 | // are processed, stopping when the end of file is reached or another |
95 | // machine or a default token is encountered.” |
96 | switch f[i] { |
97 | case "machine": |
98 | l = netrcLine{machine: f[i+1]} |
99 | case "default": |
100 | break |
101 | case "login": |
102 | l.login = f[i+1] |
103 | case "password": |
104 | l.password = f[i+1] |
105 | case "macdef": |
106 | // “A macro is defined with the specified name; its contents begin with |
107 | // the next .netrc line and continue until a null line (consecutive |
108 | // new-line characters) is encountered.” |
109 | inMacro = true |
110 | } |
111 | if l.machine != "" && l.login != "" && l.password != "" { |
112 | nrc = append(nrc, l) |
113 | l = netrcLine{} |
114 | } |
115 | } |
116 | |
117 | if i < len(f) && f[i] == "default" { |
118 | // “There can be only one default token, and it must be after all machine tokens.” |
119 | break |
120 | } |
121 | } |
122 | |
123 | return nrc |
124 | } |
125 |
Members