diff --git a/Dockerfile b/Dockerfile index 991be5804..12064b2f1 100644 --- a/Dockerfile +++ b/Dockerfile @@ -5,6 +5,7 @@ MAINTAINER Minio Inc ENV GOPATH /go ENV PATH $PATH:$GOPATH/bin ENV CGO_ENABLED 0 +ENV MINIO_UPDATE off WORKDIR /go/src/github.com/minio/ diff --git a/Dockerfile.dev b/Dockerfile.dev index 290e5aeb1..541b64848 100644 --- a/Dockerfile.dev +++ b/Dockerfile.dev @@ -5,6 +5,7 @@ MAINTAINER Minio Inc ENV GOPATH /go ENV PATH $PATH:$GOPATH/bin ENV CGO_ENABLED 0 +ENV MINIO_UPDATE off WORKDIR /go/src/github.com/minio/ diff --git a/Dockerfile.release b/Dockerfile.release index 4f064662a..472a3c164 100644 --- a/Dockerfile.release +++ b/Dockerfile.release @@ -4,6 +4,8 @@ MAINTAINER Minio Inc COPY dockerscripts/docker-entrypoint.sh dockerscripts/healthcheck.sh /usr/bin/ +ENV MINIO_UPDATE off + RUN \ apk add --no-cache ca-certificates && \ apk add --no-cache --virtual .build-deps curl && \ diff --git a/cmd/common-main.go b/cmd/common-main.go index 64b02eaa3..f34c9a3b1 100644 --- a/cmd/common-main.go +++ b/cmd/common-main.go @@ -29,10 +29,12 @@ import ( // Check for updates and print a notification message func checkUpdate(mode string) { - // Its OK to ignore any errors during getUpdateInfo() here. - if older, downloadURL, err := getUpdateInfo(1*time.Second, mode); err == nil { - if updateMsg := computeUpdateMessage(downloadURL, older); updateMsg != "" { + // Its OK to ignore any errors during doUpdate() here. + if updateMsg, _, currentReleaseTime, latestReleaseTime, err := getUpdateInfo(2*time.Second, mode); err == nil { + if globalInplaceUpdateDisabled { log.Println(updateMsg) + } else { + log.Println(prepareUpdateMessage("Run `minio update`", latestReleaseTime.Sub(currentReleaseTime))) } } } @@ -106,4 +108,9 @@ func handleCommonEnvVars() { if globalDomainName != "" { globalIsEnvDomainName = true } + + // In place update is true by default if the MINIO_UPDATE is not set + // or is not set to 'off', if MINIO_UPDATE is set to 'off' then + // in-place update is off. + globalInplaceUpdateDisabled = strings.EqualFold(os.Getenv("MINIO_UPDATE"), "off") } diff --git a/cmd/gateway/azure/gateway-azure.go b/cmd/gateway/azure/gateway-azure.go index 25fa6c860..c1cf17c74 100644 --- a/cmd/gateway/azure/gateway-azure.go +++ b/cmd/gateway/azure/gateway-azure.go @@ -71,6 +71,9 @@ ENVIRONMENT VARIABLES: BROWSER: MINIO_BROWSER: To disable web browser access, set this value to "off". + UPDATE: + MINIO_UPDATE: To turn off in-place upgrades, set this value to "off". + EXAMPLES: 1. Start minio gateway server for Azure Blob Storage backend. $ export MINIO_ACCESS_KEY=azureaccountname diff --git a/cmd/gateway/b2/gateway-b2.go b/cmd/gateway/b2/gateway-b2.go index f4a054459..f381a8dd7 100644 --- a/cmd/gateway/b2/gateway-b2.go +++ b/cmd/gateway/b2/gateway-b2.go @@ -23,6 +23,7 @@ import ( "hash" "io" "io/ioutil" + "net/http" "strings" "sync" @@ -63,6 +64,9 @@ ENVIRONMENT VARIABLES: BROWSER: MINIO_BROWSER: To disable web browser access, set this value to "off". + UPDATE: + MINIO_UPDATE: To turn off in-place upgrades, set this value to "off". + EXAMPLES: 1. Start minio gateway server for B2 backend. $ export MINIO_ACCESS_KEY=accountID diff --git a/cmd/gateway/gcs/gateway-gcs.go b/cmd/gateway/gcs/gateway-gcs.go index 81db62320..4b6927e3f 100644 --- a/cmd/gateway/gcs/gateway-gcs.go +++ b/cmd/gateway/gcs/gateway-gcs.go @@ -23,6 +23,7 @@ import ( "fmt" "io" "io/ioutil" + "math" "os" "regexp" @@ -109,6 +110,9 @@ ENVIRONMENT VARIABLES: BROWSER: MINIO_BROWSER: To disable web browser access, set this value to "off". + UPDATE: + MINIO_UPDATE: To turn off in-place upgrades, set this value to "off". + GCS credentials file: GOOGLE_APPLICATION_CREDENTIALS: Path to credentials.json diff --git a/cmd/gateway/s3/gateway-s3.go b/cmd/gateway/s3/gateway-s3.go index 5524676d5..86d186a7c 100644 --- a/cmd/gateway/s3/gateway-s3.go +++ b/cmd/gateway/s3/gateway-s3.go @@ -55,6 +55,9 @@ ENVIRONMENT VARIABLES: BROWSER: MINIO_BROWSER: To disable web browser access, set this value to "off". + UPDATE: + MINIO_UPDATE: To turn off in-place upgrades, set this value to "off". + EXAMPLES: 1. Start minio gateway server for AWS S3 backend. $ export MINIO_ACCESS_KEY=accesskey diff --git a/cmd/gateway/sia/gateway-sia.go b/cmd/gateway/sia/gateway-sia.go index 961ec976f..5cce2ef05 100644 --- a/cmd/gateway/sia/gateway-sia.go +++ b/cmd/gateway/sia/gateway-sia.go @@ -69,6 +69,12 @@ ENVIRONMENT VARIABLES: (Default values in parenthesis) MINIO_ACCESS_KEY: Custom access key (Do not reuse same access keys on all instances) MINIO_SECRET_KEY: Custom secret key (Do not reuse same secret keys on all instances) + BROWSER: + MINIO_BROWSER: To disable web browser access, set this value to "off". + + UPDATE: + MINIO_UPDATE: To turn off in-place upgrades, set this value to "off". + SIA_TEMP_DIR: The name of the local Sia temporary storage directory. (.sia_temp) SIA_API_PASSWORD: API password for Sia daemon. (default is empty) diff --git a/cmd/globals.go b/cmd/globals.go index 439c8e251..575a15ac5 100644 --- a/cmd/globals.go +++ b/cmd/globals.go @@ -89,9 +89,12 @@ var ( // Set to true if credentials were passed from env, default is false. globalIsEnvCreds = false - // This flag is set to 'true' wen MINIO_REGION env is set. + // This flag is set to 'true' when MINIO_REGION env is set. globalIsEnvRegion = false + // This flag is set to 'true' when MINIO_UPDATE env is set to 'off'. Default is false. + globalInplaceUpdateDisabled = false + // This flag is set to 'us-east-1' by default globalServerRegion = globalMinioDefaultRegion diff --git a/cmd/server-main.go b/cmd/server-main.go index 145d82e0f..a69414c58 100644 --- a/cmd/server-main.go +++ b/cmd/server-main.go @@ -62,6 +62,9 @@ ENVIRONMENT VARIABLES: REGION: MINIO_REGION: To set custom region. By default it is "us-east-1". + UPDATE: + MINIO_UPDATE: To turn off in-place upgrades, set this value to "off". + EXAMPLES: 1. Start minio server on "/home/shared" directory. $ {{.HelpName}} /home/shared diff --git a/cmd/update-main.go b/cmd/update-main.go index f02580a63..9e0feba26 100644 --- a/cmd/update-main.go +++ b/cmd/update-main.go @@ -18,6 +18,9 @@ package cmd import ( "bufio" + "crypto" + _ "crypto/sha256" // Needed for sha256 hash verifier. + "encoding/hex" "fmt" "io/ioutil" "net/http" @@ -28,7 +31,9 @@ import ( "time" "github.com/fatih/color" + "github.com/inconshreveable/go-update" "github.com/minio/cli" + "github.com/segmentio/go-prompt" ) // Check for new software updates. @@ -53,18 +58,19 @@ FLAGS: {{end}}{{end}} EXIT STATUS: 0 - You are already running the most recent version. - 1 - New update is available. + 1 - New update was applied successfully. -1 - Error in getting update information. EXAMPLES: - 1. Check if there is a new update available: - $ {{.HelpName}} + 1. Check and update minio: + $ {{.HelpName} `, } const ( minioReleaseTagTimeLayout = "2006-01-02T15-04-05Z" - minioReleaseURL = "https://dl.minio.io/server/minio/release/" + runtime.GOOS + "-" + runtime.GOARCH + "/" + minioOSARCH = runtime.GOOS + "-" + runtime.GOARCH + minioReleaseURL = "https://dl.minio.io/server/minio/release/" + minioOSARCH + "/" ) var ( @@ -335,23 +341,24 @@ func DownloadReleaseData(timeout time.Duration, mode string) (data string, err e // fbe246edbd382902db9a4035df7dce8cb441357d minio.RELEASE.2016-10-07T01-16-39Z // // The second word must be `minio.` appended to a standard release tag. -func parseReleaseData(data string) (releaseTime time.Time, err error) { +func parseReleaseData(data string) (sha256Hex string, releaseTime time.Time, err error) { fields := strings.Fields(data) if len(fields) != 2 { err = fmt.Errorf("Unknown release data `%s`", data) - return releaseTime, err + return sha256Hex, releaseTime, err } + sha256Hex = fields[0] releaseInfo := fields[1] fields = strings.SplitN(releaseInfo, ".", 2) if len(fields) != 2 { err = fmt.Errorf("Unknown release information `%s`", releaseInfo) - return releaseTime, err + return sha256Hex, releaseTime, err } if fields[0] != "minio" { err = fmt.Errorf("Unknown release `%s`", releaseInfo) - return releaseTime, err + return sha256Hex, releaseTime, err } releaseTime, err = releaseTagToReleaseTime(fields[1]) @@ -359,13 +366,13 @@ func parseReleaseData(data string) (releaseTime time.Time, err error) { err = fmt.Errorf("Unknown release tag format. %s", err) } - return releaseTime, err + return sha256Hex, releaseTime, err } -func getLatestReleaseTime(timeout time.Duration, mode string) (releaseTime time.Time, err error) { +func getLatestReleaseTime(timeout time.Duration, mode string) (sha256Hex string, releaseTime time.Time, err error) { data, err := DownloadReleaseData(timeout, mode) if err != nil { - return releaseTime, err + return sha256Hex, releaseTime, err } return parseReleaseData(data) @@ -406,49 +413,105 @@ func getDownloadURL(releaseTag string) (downloadURL string) { return minioReleaseURL + "minio" } -func getUpdateInfo(timeout time.Duration, mode string) (older time.Duration, downloadURL string, err error) { - var currentReleaseTime, latestReleaseTime time.Time +func getUpdateInfo(timeout time.Duration, mode string) (updateMsg string, sha256Hex string, currentReleaseTime, latestReleaseTime time.Time, err error) { currentReleaseTime, err = GetCurrentReleaseTime() if err != nil { - return older, downloadURL, err + return updateMsg, sha256Hex, currentReleaseTime, latestReleaseTime, err } - latestReleaseTime, err = getLatestReleaseTime(timeout, mode) + sha256Hex, latestReleaseTime, err = getLatestReleaseTime(timeout, mode) if err != nil { - return older, downloadURL, err + return updateMsg, sha256Hex, currentReleaseTime, latestReleaseTime, err } + var older time.Duration + var downloadURL string if latestReleaseTime.After(currentReleaseTime) { older = latestReleaseTime.Sub(currentReleaseTime) downloadURL = getDownloadURL(releaseTimeToReleaseTag(latestReleaseTime)) } - return older, downloadURL, nil + return prepareUpdateMessage(downloadURL, older), sha256Hex, currentReleaseTime, latestReleaseTime, nil +} + +func doUpdate(sha256Hex string, latestReleaseTime time.Time, ok bool) (successMsg string, err error) { + if !ok { + successMsg = greenColorSprintf("Minio update to version RELEASE.%s cancelled.", + latestReleaseTime.Format(minioReleaseTagTimeLayout)) + return successMsg, nil + } + var sha256Sum []byte + sha256Sum, err = hex.DecodeString(sha256Hex) + if err != nil { + return successMsg, err + } + + resp, err := http.Get(getDownloadURL(releaseTimeToReleaseTag(latestReleaseTime))) + if err != nil { + return successMsg, err + } + defer resp.Body.Close() + + // FIXME: add support for gpg verification as well. + if err = update.RollbackError(update.Apply(resp.Body, + update.Options{ + Hash: crypto.SHA256, + Checksum: sha256Sum, + }, + )); err != nil { + return successMsg, err + } + + return greenColorSprintf("Minio updated to version RELEASE.%s successfully.", + latestReleaseTime.Format(minioReleaseTagTimeLayout)), nil +} + +func shouldUpdate(quiet bool, sha256Hex string, latestReleaseTime time.Time) (ok bool) { + ok = true + if !quiet { + ok = prompt.Confirm(greenColorSprintf("Update to RELEASE.%s [%s]", latestReleaseTime.Format(minioReleaseTagTimeLayout), "yes")) + } + return ok } -func mainUpdate(ctx *cli.Context) { +var greenColorSprintf = color.New(color.FgGreen, color.Bold).SprintfFunc() + +func mainUpdate(ctx *cli.Context) error { if len(ctx.Args()) != 0 { cli.ShowCommandHelpAndExit(ctx, "update", -1) } + handleCommonEnvVars() + quiet := ctx.Bool("quiet") || ctx.GlobalBool("quiet") if quiet { log.EnableQuiet() } minioMode := "" - older, downloadURL, err := getUpdateInfo(10*time.Second, minioMode) + updateMsg, sha256Hex, _, latestReleaseTime, err := getUpdateInfo(10*time.Second, minioMode) if err != nil { log.Println(err) os.Exit(-1) } - if updateMsg := computeUpdateMessage(downloadURL, older); updateMsg != "" { - log.Println(updateMsg) - os.Exit(1) + // Nothing to update running the latest release. + if updateMsg == "" { + log.Println(greenColorSprintf("You are already running the most recent version of ‘minio’.")) + return nil } - colorSprintf := color.New(color.FgGreen, color.Bold).SprintfFunc() - log.Println(colorSprintf("You are already running the most recent version of ‘minio’.")) - os.Exit(0) + log.Println(updateMsg) + // if the in-place update is disabled then we shouldn't ask the + // user to update the binaries. + if strings.Contains(updateMsg, minioReleaseURL) && !globalInplaceUpdateDisabled { + var successMsg string + successMsg, err = doUpdate(sha256Hex, latestReleaseTime, shouldUpdate(quiet, sha256Hex, latestReleaseTime)) + if err != nil { + log.Println(err) + os.Exit(-1) + } + log.Println(successMsg) + } + return nil } diff --git a/cmd/update-main_test.go b/cmd/update-main_test.go index c725e959f..c76ccb0d3 100644 --- a/cmd/update-main_test.go +++ b/cmd/update-main_test.go @@ -263,20 +263,21 @@ func TestDownloadReleaseData(t *testing.T) { func TestParseReleaseData(t *testing.T) { releaseTime, _ := releaseTagToReleaseTime("RELEASE.2016-10-07T01-16-39Z") testCases := []struct { - data string - expectedResult time.Time - expectedErr error + data string + expectedResult time.Time + expectedSha256hex string + expectedErr error }{ - {"more than two fields", time.Time{}, fmt.Errorf("Unknown release data `more than two fields`")}, - {"more than", time.Time{}, fmt.Errorf("Unknown release information `than`")}, - {"more than.two.fields", time.Time{}, fmt.Errorf("Unknown release `than.two.fields`")}, - {"more minio.RELEASE.fields", time.Time{}, fmt.Errorf(`Unknown release tag format. parsing time "fields" as "2006-01-02T15-04-05Z": cannot parse "fields" as "2006"`)}, - {"more minio.RELEASE.2016-10-07T01-16-39Z", releaseTime, nil}, - {"fbe246edbd382902db9a4035df7dce8cb441357d minio.RELEASE.2016-10-07T01-16-39Z\n", releaseTime, nil}, + {"more than two fields", time.Time{}, "", fmt.Errorf("Unknown release data `more than two fields`")}, + {"more than", time.Time{}, "", fmt.Errorf("Unknown release information `than`")}, + {"more than.two.fields", time.Time{}, "", fmt.Errorf("Unknown release `than.two.fields`")}, + {"more minio.RELEASE.fields", time.Time{}, "", fmt.Errorf(`Unknown release tag format. parsing time "fields" as "2006-01-02T15-04-05Z": cannot parse "fields" as "2006"`)}, + {"more minio.RELEASE.2016-10-07T01-16-39Z", releaseTime, "more", nil}, + {"fbe246edbd382902db9a4035df7dce8cb441357d minio.RELEASE.2016-10-07T01-16-39Z\n", releaseTime, "fbe246edbd382902db9a4035df7dce8cb441357d", nil}, } for i, testCase := range testCases { - result, err := parseReleaseData(testCase.data) + sha256Hex, result, err := parseReleaseData(testCase.data) if testCase.expectedErr == nil { if err != nil { t.Errorf("error case %d: expected: %v, got: %v", i+1, testCase.expectedErr, err) @@ -286,9 +287,13 @@ func TestParseReleaseData(t *testing.T) { } else if testCase.expectedErr.Error() != err.Error() { t.Errorf("error case %d: expected: %v, got: %v", i+1, testCase.expectedErr, err) } - - if !testCase.expectedResult.Equal(result) { - t.Errorf("case %d: result: expected: %v, got: %v", i+1, testCase.expectedResult, result) + if err == nil { + if sha256Hex != testCase.expectedSha256hex { + t.Errorf("case %d: result: expected: %v, got: %v", i+1, testCase.expectedSha256hex, sha256Hex) + } + if !testCase.expectedResult.Equal(result) { + t.Errorf("case %d: result: expected: %v, got: %v", i+1, testCase.expectedResult, result) + } } } } diff --git a/cmd/update-notifier.go b/cmd/update-notifier.go index 16399492d..f164821c6 100644 --- a/cmd/update-notifier.go +++ b/cmd/update-notifier.go @@ -28,9 +28,9 @@ import ( "github.com/fatih/color" ) -// computeUpdateMessage - calculates the update message, only if a +// prepareUpdateMessage - prepares the update message, only if a // newer version is available. -func computeUpdateMessage(downloadURL string, older time.Duration) string { +func prepareUpdateMessage(downloadURL string, older time.Duration) string { if downloadURL == "" || older <= 0 { return "" } diff --git a/cmd/update-notifier_test.go b/cmd/update-notifier_test.go index f6506b8df..f8e543b83 100644 --- a/cmd/update-notifier_test.go +++ b/cmd/update-notifier_test.go @@ -26,7 +26,7 @@ import ( ) // Tests update notifier string builder. -func TestComputeUpdateMessage(t *testing.T) { +func TestPrepareUpdateMessage(t *testing.T) { testCases := []struct { older time.Duration dlURL string @@ -71,7 +71,7 @@ func TestComputeUpdateMessage(t *testing.T) { cyan := color.New(color.FgCyan, color.Bold).SprintFunc() for i, testCase := range testCases { - output := computeUpdateMessage(testCase.dlURL, testCase.older) + output := prepareUpdateMessage(testCase.dlURL, testCase.older) line1 := fmt.Sprintf("%s %s", plainMsg, yellow(testCase.expectedSubStr)) line2 := fmt.Sprintf("Update: %s", cyan(testCase.dlURL)) // Uncomment below to see message appearance: diff --git a/vendor/github.com/howeyc/gopass/LICENSE.txt b/vendor/github.com/howeyc/gopass/LICENSE.txt new file mode 100644 index 000000000..14f74708a --- /dev/null +++ b/vendor/github.com/howeyc/gopass/LICENSE.txt @@ -0,0 +1,15 @@ +ISC License + +Copyright (c) 2012 Chris Howey + +Permission to use, copy, modify, and distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. diff --git a/vendor/github.com/howeyc/gopass/OPENSOLARIS.LICENSE b/vendor/github.com/howeyc/gopass/OPENSOLARIS.LICENSE new file mode 100644 index 000000000..da23621dc --- /dev/null +++ b/vendor/github.com/howeyc/gopass/OPENSOLARIS.LICENSE @@ -0,0 +1,384 @@ +Unless otherwise noted, all files in this distribution are released +under the Common Development and Distribution License (CDDL). +Exceptions are noted within the associated source files. + +-------------------------------------------------------------------- + + +COMMON DEVELOPMENT AND DISTRIBUTION LICENSE Version 1.0 + +1. Definitions. + + 1.1. "Contributor" means each individual or entity that creates + or contributes to the creation of Modifications. + + 1.2. "Contributor Version" means the combination of the Original + Software, prior Modifications used by a Contributor (if any), + and the Modifications made by that particular Contributor. + + 1.3. "Covered Software" means (a) the Original Software, or (b) + Modifications, or (c) the combination of files containing + Original Software with files containing Modifications, in + each case including portions thereof. + + 1.4. "Executable" means the Covered Software in any form other + than Source Code. + + 1.5. "Initial Developer" means the individual or entity that first + makes Original Software available under this License. + + 1.6. "Larger Work" means a work which combines Covered Software or + portions thereof with code not governed by the terms of this + License. + + 1.7. "License" means this document. + + 1.8. "Licensable" means having the right to grant, to the maximum + extent possible, whether at the time of the initial grant or + subsequently acquired, any and all of the rights conveyed + herein. + + 1.9. "Modifications" means the Source Code and Executable form of + any of the following: + + A. Any file that results from an addition to, deletion from or + modification of the contents of a file containing Original + Software or previous Modifications; + + B. Any new file that contains any part of the Original + Software or previous Modifications; or + + C. Any new file that is contributed or otherwise made + available under the terms of this License. + + 1.10. "Original Software" means the Source Code and Executable + form of computer software code that is originally released + under this License. + + 1.11. "Patent Claims" means any patent claim(s), now owned or + hereafter acquired, including without limitation, method, + process, and apparatus claims, in any patent Licensable by + grantor. + + 1.12. "Source Code" means (a) the common form of computer software + code in which modifications are made and (b) associated + documentation included in or with such code. + + 1.13. "You" (or "Your") means an individual or a legal entity + exercising rights under, and complying with all of the terms + of, this License. For legal entities, "You" includes any + entity which controls, is controlled by, or is under common + control with You. For purposes of this definition, + "control" means (a) the power, direct or indirect, to cause + the direction or management of such entity, whether by + contract or otherwise, or (b) ownership of more than fifty + percent (50%) of the outstanding shares or beneficial + ownership of such entity. + +2. License Grants. + + 2.1. The Initial Developer Grant. + + Conditioned upon Your compliance with Section 3.1 below and + subject to third party intellectual property claims, the Initial + Developer hereby grants You a world-wide, royalty-free, + non-exclusive license: + + (a) under intellectual property rights (other than patent or + trademark) Licensable by Initial Developer, to use, + reproduce, modify, display, perform, sublicense and + distribute the Original Software (or portions thereof), + with or without Modifications, and/or as part of a Larger + Work; and + + (b) under Patent Claims infringed by the making, using or + selling of Original Software, to make, have made, use, + practice, sell, and offer for sale, and/or otherwise + dispose of the Original Software (or portions thereof). + + (c) The licenses granted in Sections 2.1(a) and (b) are + effective on the date Initial Developer first distributes + or otherwise makes the Original Software available to a + third party under the terms of this License. + + (d) Notwithstanding Section 2.1(b) above, no patent license is + granted: (1) for code that You delete from the Original + Software, or (2) for infringements caused by: (i) the + modification of the Original Software, or (ii) the + combination of the Original Software with other software + or devices. + + 2.2. Contributor Grant. + + Conditioned upon Your compliance with Section 3.1 below and + subject to third party intellectual property claims, each + Contributor hereby grants You a world-wide, royalty-free, + non-exclusive license: + + (a) under intellectual property rights (other than patent or + trademark) Licensable by Contributor to use, reproduce, + modify, display, perform, sublicense and distribute the + Modifications created by such Contributor (or portions + thereof), either on an unmodified basis, with other + Modifications, as Covered Software and/or as part of a + Larger Work; and + + (b) under Patent Claims infringed by the making, using, or + selling of Modifications made by that Contributor either + alone and/or in combination with its Contributor Version + (or portions of such combination), to make, use, sell, + offer for sale, have made, and/or otherwise dispose of: + (1) Modifications made by that Contributor (or portions + thereof); and (2) the combination of Modifications made by + that Contributor with its Contributor Version (or portions + of such combination). + + (c) The licenses granted in Sections 2.2(a) and 2.2(b) are + effective on the date Contributor first distributes or + otherwise makes the Modifications available to a third + party. + + (d) Notwithstanding Section 2.2(b) above, no patent license is + granted: (1) for any code that Contributor has deleted + from the Contributor Version; (2) for infringements caused + by: (i) third party modifications of Contributor Version, + or (ii) the combination of Modifications made by that + Contributor with other software (except as part of the + Contributor Version) or other devices; or (3) under Patent + Claims infringed by Covered Software in the absence of + Modifications made by that Contributor. + +3. Distribution Obligations. + + 3.1. Availability of Source Code. + + Any Covered Software that You distribute or otherwise make + available in Executable form must also be made available in Source + Code form and that Source Code form must be distributed only under + the terms of this License. You must include a copy of this + License with every copy of the Source Code form of the Covered + Software You distribute or otherwise make available. You must + inform recipients of any such Covered Software in Executable form + as to how they can obtain such Covered Software in Source Code + form in a reasonable manner on or through a medium customarily + used for software exchange. + + 3.2. Modifications. + + The Modifications that You create or to which You contribute are + governed by the terms of this License. You represent that You + believe Your Modifications are Your original creation(s) and/or + You have sufficient rights to grant the rights conveyed by this + License. + + 3.3. Required Notices. + + You must include a notice in each of Your Modifications that + identifies You as the Contributor of the Modification. You may + not remove or alter any copyright, patent or trademark notices + contained within the Covered Software, or any notices of licensing + or any descriptive text giving attribution to any Contributor or + the Initial Developer. + + 3.4. Application of Additional Terms. + + You may not offer or impose any terms on any Covered Software in + Source Code form that alters or restricts the applicable version + of this License or the recipients' rights hereunder. You may + choose to offer, and to charge a fee for, warranty, support, + indemnity or liability obligations to one or more recipients of + Covered Software. However, you may do so only on Your own behalf, + and not on behalf of the Initial Developer or any Contributor. + You must make it absolutely clear that any such warranty, support, + indemnity or liability obligation is offered by You alone, and You + hereby agree to indemnify the Initial Developer and every + Contributor for any liability incurred by the Initial Developer or + such Contributor as a result of warranty, support, indemnity or + liability terms You offer. + + 3.5. Distribution of Executable Versions. + + You may distribute the Executable form of the Covered Software + under the terms of this License or under the terms of a license of + Your choice, which may contain terms different from this License, + provided that You are in compliance with the terms of this License + and that the license for the Executable form does not attempt to + limit or alter the recipient's rights in the Source Code form from + the rights set forth in this License. If You distribute the + Covered Software in Executable form under a different license, You + must make it absolutely clear that any terms which differ from + this License are offered by You alone, not by the Initial + Developer or Contributor. You hereby agree to indemnify the + Initial Developer and every Contributor for any liability incurred + by the Initial Developer or such Contributor as a result of any + such terms You offer. + + 3.6. Larger Works. + + You may create a Larger Work by combining Covered Software with + other code not governed by the terms of this License and + distribute the Larger Work as a single product. In such a case, + You must make sure the requirements of this License are fulfilled + for the Covered Software. + +4. Versions of the License. + + 4.1. New Versions. + + Sun Microsystems, Inc. is the initial license steward and may + publish revised and/or new versions of this License from time to + time. Each version will be given a distinguishing version number. + Except as provided in Section 4.3, no one other than the license + steward has the right to modify this License. + + 4.2. Effect of New Versions. + + You may always continue to use, distribute or otherwise make the + Covered Software available under the terms of the version of the + License under which You originally received the Covered Software. + If the Initial Developer includes a notice in the Original + Software prohibiting it from being distributed or otherwise made + available under any subsequent version of the License, You must + distribute and make the Covered Software available under the terms + of the version of the License under which You originally received + the Covered Software. Otherwise, You may also choose to use, + distribute or otherwise make the Covered Software available under + the terms of any subsequent version of the License published by + the license steward. + + 4.3. Modified Versions. + + When You are an Initial Developer and You want to create a new + license for Your Original Software, You may create and use a + modified version of this License if You: (a) rename the license + and remove any references to the name of the license steward + (except to note that the license differs from this License); and + (b) otherwise make it clear that the license contains terms which + differ from this License. + +5. DISCLAIMER OF WARRANTY. + + COVERED SOFTWARE IS PROVIDED UNDER THIS LICENSE ON AN "AS IS" + BASIS, WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, + INCLUDING, WITHOUT LIMITATION, WARRANTIES THAT THE COVERED + SOFTWARE IS FREE OF DEFECTS, MERCHANTABLE, FIT FOR A PARTICULAR + PURPOSE OR NON-INFRINGING. THE ENTIRE RISK AS TO THE QUALITY AND + PERFORMANCE OF THE COVERED SOFTWARE IS WITH YOU. SHOULD ANY + COVERED SOFTWARE PROVE DEFECTIVE IN ANY RESPECT, YOU (NOT THE + INITIAL DEVELOPER OR ANY OTHER CONTRIBUTOR) ASSUME THE COST OF ANY + NECESSARY SERVICING, REPAIR OR CORRECTION. THIS DISCLAIMER OF + WARRANTY CONSTITUTES AN ESSENTIAL PART OF THIS LICENSE. NO USE OF + ANY COVERED SOFTWARE IS AUTHORIZED HEREUNDER EXCEPT UNDER THIS + DISCLAIMER. + +6. TERMINATION. + + 6.1. This License and the rights granted hereunder will terminate + automatically if You fail to comply with terms herein and fail to + cure such breach within 30 days of becoming aware of the breach. + Provisions which, by their nature, must remain in effect beyond + the termination of this License shall survive. + + 6.2. If You assert a patent infringement claim (excluding + declaratory judgment actions) against Initial Developer or a + Contributor (the Initial Developer or Contributor against whom You + assert such claim is referred to as "Participant") alleging that + the Participant Software (meaning the Contributor Version where + the Participant is a Contributor or the Original Software where + the Participant is the Initial Developer) directly or indirectly + infringes any patent, then any and all rights granted directly or + indirectly to You by such Participant, the Initial Developer (if + the Initial Developer is not the Participant) and all Contributors + under Sections 2.1 and/or 2.2 of this License shall, upon 60 days + notice from Participant terminate prospectively and automatically + at the expiration of such 60 day notice period, unless if within + such 60 day period You withdraw Your claim with respect to the + Participant Software against such Participant either unilaterally + or pursuant to a written agreement with Participant. + + 6.3. In the event of termination under Sections 6.1 or 6.2 above, + all end user licenses that have been validly granted by You or any + distributor hereunder prior to termination (excluding licenses + granted to You by any distributor) shall survive termination. + +7. LIMITATION OF LIABILITY. + + UNDER NO CIRCUMSTANCES AND UNDER NO LEGAL THEORY, WHETHER TORT + (INCLUDING NEGLIGENCE), CONTRACT, OR OTHERWISE, SHALL YOU, THE + INITIAL DEVELOPER, ANY OTHER CONTRIBUTOR, OR ANY DISTRIBUTOR OF + COVERED SOFTWARE, OR ANY SUPPLIER OF ANY OF SUCH PARTIES, BE + LIABLE TO ANY PERSON FOR ANY INDIRECT, SPECIAL, INCIDENTAL, OR + CONSEQUENTIAL DAMAGES OF ANY CHARACTER INCLUDING, WITHOUT + LIMITATION, DAMAGES FOR LOST PROFITS, LOSS OF GOODWILL, WORK + STOPPAGE, COMPUTER FAILURE OR MALFUNCTION, OR ANY AND ALL OTHER + COMMERCIAL DAMAGES OR LOSSES, EVEN IF SUCH PARTY SHALL HAVE BEEN + INFORMED OF THE POSSIBILITY OF SUCH DAMAGES. THIS LIMITATION OF + LIABILITY SHALL NOT APPLY TO LIABILITY FOR DEATH OR PERSONAL + INJURY RESULTING FROM SUCH PARTY'S NEGLIGENCE TO THE EXTENT + APPLICABLE LAW PROHIBITS SUCH LIMITATION. SOME JURISDICTIONS DO + NOT ALLOW THE EXCLUSION OR LIMITATION OF INCIDENTAL OR + CONSEQUENTIAL DAMAGES, SO THIS EXCLUSION AND LIMITATION MAY NOT + APPLY TO YOU. + +8. U.S. GOVERNMENT END USERS. + + The Covered Software is a "commercial item," as that term is + defined in 48 C.F.R. 2.101 (Oct. 1995), consisting of "commercial + computer software" (as that term is defined at 48 + C.F.R. 252.227-7014(a)(1)) and "commercial computer software + documentation" as such terms are used in 48 C.F.R. 12.212 + (Sept. 1995). Consistent with 48 C.F.R. 12.212 and 48 + C.F.R. 227.7202-1 through 227.7202-4 (June 1995), all + U.S. Government End Users acquire Covered Software with only those + rights set forth herein. This U.S. Government Rights clause is in + lieu of, and supersedes, any other FAR, DFAR, or other clause or + provision that addresses Government rights in computer software + under this License. + +9. MISCELLANEOUS. + + This License represents the complete agreement concerning subject + matter hereof. If any provision of this License is held to be + unenforceable, such provision shall be reformed only to the extent + necessary to make it enforceable. This License shall be governed + by the law of the jurisdiction specified in a notice contained + within the Original Software (except to the extent applicable law, + if any, provides otherwise), excluding such jurisdiction's + conflict-of-law provisions. Any litigation relating to this + License shall be subject to the jurisdiction of the courts located + in the jurisdiction and venue specified in a notice contained + within the Original Software, with the losing party responsible + for costs, including, without limitation, court costs and + reasonable attorneys' fees and expenses. The application of the + United Nations Convention on Contracts for the International Sale + of Goods is expressly excluded. Any law or regulation which + provides that the language of a contract shall be construed + against the drafter shall not apply to this License. You agree + that You alone are responsible for compliance with the United + States export administration regulations (and the export control + laws and regulation of any other countries) when You use, + distribute or otherwise make available any Covered Software. + +10. RESPONSIBILITY FOR CLAIMS. + + As between Initial Developer and the Contributors, each party is + responsible for claims and damages arising, directly or + indirectly, out of its utilization of rights under this License + and You agree to work with Initial Developer and Contributors to + distribute such responsibility on an equitable basis. Nothing + herein is intended or shall be deemed to constitute any admission + of liability. + +-------------------------------------------------------------------- + +NOTICE PURSUANT TO SECTION 9 OF THE COMMON DEVELOPMENT AND +DISTRIBUTION LICENSE (CDDL) + +For Covered Software in this distribution, this License shall +be governed by the laws of the State of California (excluding +conflict-of-law provisions). + +Any litigation relating to this License shall be subject to the +jurisdiction of the Federal Courts of the Northern District of +California and the state courts of the State of California, with +venue lying in Santa Clara County, California. diff --git a/vendor/github.com/howeyc/gopass/README.md b/vendor/github.com/howeyc/gopass/README.md new file mode 100644 index 000000000..2d6a4e72c --- /dev/null +++ b/vendor/github.com/howeyc/gopass/README.md @@ -0,0 +1,27 @@ +# getpasswd in Go [![GoDoc](https://godoc.org/github.com/howeyc/gopass?status.svg)](https://godoc.org/github.com/howeyc/gopass) [![Build Status](https://secure.travis-ci.org/howeyc/gopass.png?branch=master)](http://travis-ci.org/howeyc/gopass) + +Retrieve password from user terminal or piped input without echo. + +Verified on BSD, Linux, and Windows. + +Example: +```go +package main + +import "fmt" +import "github.com/howeyc/gopass" + +func main() { + fmt.Printf("Password: ") + + // Silent. For printing *'s use gopass.GetPasswdMasked() + pass, err := gopass.GetPasswd() + if err != nil { + // Handle gopass.ErrInterrupted or getch() read error + } + + // Do something with pass +} +``` + +Caution: Multi-byte characters not supported! diff --git a/vendor/github.com/howeyc/gopass/pass.go b/vendor/github.com/howeyc/gopass/pass.go new file mode 100644 index 000000000..f5bd5a51a --- /dev/null +++ b/vendor/github.com/howeyc/gopass/pass.go @@ -0,0 +1,110 @@ +package gopass + +import ( + "errors" + "fmt" + "io" + "os" +) + +type FdReader interface { + io.Reader + Fd() uintptr +} + +var defaultGetCh = func(r io.Reader) (byte, error) { + buf := make([]byte, 1) + if n, err := r.Read(buf); n == 0 || err != nil { + if err != nil { + return 0, err + } + return 0, io.EOF + } + return buf[0], nil +} + +var ( + maxLength = 512 + ErrInterrupted = errors.New("interrupted") + ErrMaxLengthExceeded = fmt.Errorf("maximum byte limit (%v) exceeded", maxLength) + + // Provide variable so that tests can provide a mock implementation. + getch = defaultGetCh +) + +// getPasswd returns the input read from terminal. +// If prompt is not empty, it will be output as a prompt to the user +// If masked is true, typing will be matched by asterisks on the screen. +// Otherwise, typing will echo nothing. +func getPasswd(prompt string, masked bool, r FdReader, w io.Writer) ([]byte, error) { + var err error + var pass, bs, mask []byte + if masked { + bs = []byte("\b \b") + mask = []byte("*") + } + + if isTerminal(r.Fd()) { + if oldState, err := makeRaw(r.Fd()); err != nil { + return pass, err + } else { + defer func() { + restore(r.Fd(), oldState) + fmt.Fprintln(w) + }() + } + } + + if prompt != "" { + fmt.Fprint(w, prompt) + } + + // Track total bytes read, not just bytes in the password. This ensures any + // errors that might flood the console with nil or -1 bytes infinitely are + // capped. + var counter int + for counter = 0; counter <= maxLength; counter++ { + if v, e := getch(r); e != nil { + err = e + break + } else if v == 127 || v == 8 { + if l := len(pass); l > 0 { + pass = pass[:l-1] + fmt.Fprint(w, string(bs)) + } + } else if v == 13 || v == 10 { + break + } else if v == 3 { + err = ErrInterrupted + break + } else if v != 0 { + pass = append(pass, v) + fmt.Fprint(w, string(mask)) + } + } + + if counter > maxLength { + err = ErrMaxLengthExceeded + } + + return pass, err +} + +// GetPasswd returns the password read from the terminal without echoing input. +// The returned byte array does not include end-of-line characters. +func GetPasswd() ([]byte, error) { + return getPasswd("", false, os.Stdin, os.Stdout) +} + +// GetPasswdMasked returns the password read from the terminal, echoing asterisks. +// The returned byte array does not include end-of-line characters. +func GetPasswdMasked() ([]byte, error) { + return getPasswd("", true, os.Stdin, os.Stdout) +} + +// GetPasswdPrompt prompts the user and returns the password read from the terminal. +// If mask is true, then asterisks are echoed. +// The returned byte array does not include end-of-line characters. +func GetPasswdPrompt(prompt string, mask bool, r FdReader, w io.Writer) ([]byte, error) { + return getPasswd(prompt, mask, r, w) +} diff --git a/vendor/github.com/howeyc/gopass/terminal.go b/vendor/github.com/howeyc/gopass/terminal.go new file mode 100644 index 000000000..083564146 --- /dev/null +++ b/vendor/github.com/howeyc/gopass/terminal.go @@ -0,0 +1,25 @@ +// +build !solaris + +package gopass + +import "golang.org/x/crypto/ssh/terminal" + +type terminalState struct { + state *terminal.State +} + +func isTerminal(fd uintptr) bool { + return terminal.IsTerminal(int(fd)) +} + +func makeRaw(fd uintptr) (*terminalState, error) { + state, err := terminal.MakeRaw(int(fd)) + + return &terminalState{ + state: state, + }, err +} + +func restore(fd uintptr, oldState *terminalState) error { + return terminal.Restore(int(fd), oldState.state) +} diff --git a/vendor/github.com/howeyc/gopass/terminal_solaris.go b/vendor/github.com/howeyc/gopass/terminal_solaris.go new file mode 100644 index 000000000..257e1b4e8 --- /dev/null +++ b/vendor/github.com/howeyc/gopass/terminal_solaris.go @@ -0,0 +1,69 @@ +/* + * CDDL HEADER START + * + * The contents of this file are subject to the terms of the + * Common Development and Distribution License, Version 1.0 only + * (the "License"). You may not use this file except in compliance + * with the License. + * + * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE + * or http://www.opensolaris.org/os/licensing. + * See the License for the specific language governing permissions + * and limitations under the License. + * + * When distributing Covered Code, include this CDDL HEADER in each + * file and include the License file at usr/src/OPENSOLARIS.LICENSE. + * If applicable, add the following below this CDDL HEADER, with the + * fields enclosed by brackets "[]" replaced with your own identifying + * information: Portions Copyright [yyyy] [name of copyright owner] + * + * CDDL HEADER END + */ +// Below is derived from Solaris source, so CDDL license is included. + +package gopass + +import ( + "syscall" + + "golang.org/x/sys/unix" +) + +type terminalState struct { + state *unix.Termios +} + +// isTerminal returns true if there is a terminal attached to the given +// file descriptor. +// Source: http://src.illumos.org/source/xref/illumos-gate/usr/src/lib/libbc/libc/gen/common/isatty.c +func isTerminal(fd uintptr) bool { + var termio unix.Termio + err := unix.IoctlSetTermio(int(fd), unix.TCGETA, &termio) + return err == nil +} + +// makeRaw puts the terminal connected to the given file descriptor into raw +// mode and returns the previous state of the terminal so that it can be +// restored. +// Source: http://src.illumos.org/source/xref/illumos-gate/usr/src/lib/libast/common/uwin/getpass.c +func makeRaw(fd uintptr) (*terminalState, error) { + oldTermiosPtr, err := unix.IoctlGetTermios(int(fd), unix.TCGETS) + if err != nil { + return nil, err + } + oldTermios := *oldTermiosPtr + + newTermios := oldTermios + newTermios.Lflag &^= syscall.ECHO | syscall.ECHOE | syscall.ECHOK | syscall.ECHONL + if err := unix.IoctlSetTermios(int(fd), unix.TCSETS, &newTermios); err != nil { + return nil, err + } + + return &terminalState{ + state: oldTermiosPtr, + }, nil +} + +func restore(fd uintptr, oldState *terminalState) error { + return unix.IoctlSetTermios(int(fd), unix.TCSETS, oldState.state) +} diff --git a/vendor/github.com/inconshreveable/go-update/LICENSE b/vendor/github.com/inconshreveable/go-update/LICENSE new file mode 100644 index 000000000..418a5d1ec --- /dev/null +++ b/vendor/github.com/inconshreveable/go-update/LICENSE @@ -0,0 +1,13 @@ +Copyright 2015 Alan Shreve + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. diff --git a/vendor/github.com/inconshreveable/go-update/README.md b/vendor/github.com/inconshreveable/go-update/README.md new file mode 100644 index 000000000..438ffd43e --- /dev/null +++ b/vendor/github.com/inconshreveable/go-update/README.md @@ -0,0 +1,65 @@ +# go-update: Build self-updating Go programs [![godoc reference](https://godoc.org/github.com/inconshreveable/go-update?status.png)](https://godoc.org/github.com/inconshreveable/go-update) + +Package update provides functionality to implement secure, self-updating Go programs (or other single-file targets) +A program can update itself by replacing its executable file with a new version. + +It provides the flexibility to implement different updating user experiences +like auto-updating, or manual user-initiated updates. It also boasts +advanced features like binary patching and code signing verification. + +Example of updating from a URL: + +```go +import ( + "fmt" + "net/http" + + "github.com/inconshreveable/go-update" +) + +func doUpdate(url string) error { + resp, err := http.Get(url) + if err != nil { + return err + } + defer resp.Body.Close() + err := update.Apply(resp.Body, update.Options{}) + if err != nil { + // error handling + } + return err +} +``` + +## Features + +- Cross platform support (Windows too!) +- Binary patch application +- Checksum verification +- Code signing verification +- Support for updating arbitrary files + +## [equinox.io](https://equinox.io) +[equinox.io](https://equinox.io) is a complete ready-to-go updating solution built on top of go-update that provides: + +- Hosted updates +- Update channels (stable, beta, nightly, ...) +- Dynamically computed binary diffs +- Automatic key generation and code +- Release tooling with proper code signing +- Update/download metrics + +## API Compatibility Promises +The master branch of `go-update` is *not* guaranteed to have a stable API over time. For any production application, you should vendor +your dependency on `go-update` with a tool like git submodules, [gb](http://getgb.io/) or [govendor](https://github.com/kardianos/govendor). + +The `go-update` package makes the following promises about API compatibility: +1. A list of all API-breaking changes will be documented in this README. +1. `go-update` will strive for as few API-breaking changes as possible. + +## API Breaking Changes +- **Sept 3, 2015**: The `Options` struct passed to `Apply` was changed to be passed by value instead of passed by pointer. Old API at `28de026`. +- **Aug 9, 2015**: 2.0 API. Old API at `221d034` or `gopkg.in/inconshreveable/go-update.v0`. + +## License +Apache diff --git a/vendor/github.com/inconshreveable/go-update/apply.go b/vendor/github.com/inconshreveable/go-update/apply.go new file mode 100644 index 000000000..b26dae1c7 --- /dev/null +++ b/vendor/github.com/inconshreveable/go-update/apply.go @@ -0,0 +1,322 @@ +package update + +import ( + "bytes" + "crypto" + "crypto/x509" + "encoding/pem" + "errors" + "fmt" + "io" + "io/ioutil" + "os" + "path/filepath" + + "github.com/inconshreveable/go-update/internal/osext" +) + +var ( + openFile = os.OpenFile +) + +// Apply performs an update of the current executable (or opts.TargetFile, if set) with the contents of the given io.Reader. +// +// Apply performs the following actions to ensure a safe cross-platform update: +// +// 1. If configured, applies the contents of the update io.Reader as a binary patch. +// +// 2. If configured, computes the checksum of the new executable and verifies it matches. +// +// 3. If configured, verifies the signature with a public key. +// +// 4. Creates a new file, /path/to/.target.new with the TargetMode with the contents of the updated file +// +// 5. Renames /path/to/target to /path/to/.target.old +// +// 6. Renames /path/to/.target.new to /path/to/target +// +// 7. If the final rename is successful, deletes /path/to/.target.old, returns no error. On Windows, +// the removal of /path/to/target.old always fails, so instead Apply hides the old file instead. +// +// 8. If the final rename fails, attempts to roll back by renaming /path/to/.target.old +// back to /path/to/target. +// +// If the roll back operation fails, the file system is left in an inconsistent state (betweet steps 5 and 6) where +// there is no new executable file and the old executable file could not be be moved to its original location. In this +// case you should notify the user of the bad news and ask them to recover manually. Applications can determine whether +// the rollback failed by calling RollbackError, see the documentation on that function for additional detail. +func Apply(update io.Reader, opts Options) error { + // validate + verify := false + switch { + case opts.Signature != nil && opts.PublicKey != nil: + // okay + verify = true + case opts.Signature != nil: + return errors.New("no public key to verify signature with") + case opts.PublicKey != nil: + return errors.New("No signature to verify with") + } + + // set defaults + if opts.Hash == 0 { + opts.Hash = crypto.SHA256 + } + if opts.Verifier == nil { + opts.Verifier = NewECDSAVerifier() + } + if opts.TargetMode == 0 { + opts.TargetMode = 0755 + } + + // get target path + var err error + opts.TargetPath, err = opts.getPath() + if err != nil { + return err + } + + var newBytes []byte + if opts.Patcher != nil { + if newBytes, err = opts.applyPatch(update); err != nil { + return err + } + } else { + // no patch to apply, go on through + if newBytes, err = ioutil.ReadAll(update); err != nil { + return err + } + } + + // verify checksum if requested + if opts.Checksum != nil { + if err = opts.verifyChecksum(newBytes); err != nil { + return err + } + } + + if verify { + if err = opts.verifySignature(newBytes); err != nil { + return err + } + } + + // get the directory the executable exists in + updateDir := filepath.Dir(opts.TargetPath) + filename := filepath.Base(opts.TargetPath) + + // Copy the contents of newbinary to a new executable file + newPath := filepath.Join(updateDir, fmt.Sprintf(".%s.new", filename)) + fp, err := openFile(newPath, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, opts.TargetMode) + if err != nil { + return err + } + defer fp.Close() + + _, err = io.Copy(fp, bytes.NewReader(newBytes)) + if err != nil { + return err + } + + // if we don't call fp.Close(), windows won't let us move the new executable + // because the file will still be "in use" + fp.Close() + + // this is where we'll move the executable to so that we can swap in the updated replacement + oldPath := opts.OldSavePath + removeOld := opts.OldSavePath == "" + if removeOld { + oldPath = filepath.Join(updateDir, fmt.Sprintf(".%s.old", filename)) + } + + // delete any existing old exec file - this is necessary on Windows for two reasons: + // 1. after a successful update, Windows can't remove the .old file because the process is still running + // 2. windows rename operations fail if the destination file already exists + _ = os.Remove(oldPath) + + // move the existing executable to a new file in the same directory + err = os.Rename(opts.TargetPath, oldPath) + if err != nil { + return err + } + + // move the new exectuable in to become the new program + err = os.Rename(newPath, opts.TargetPath) + + if err != nil { + // move unsuccessful + // + // The filesystem is now in a bad state. We have successfully + // moved the existing binary to a new location, but we couldn't move the new + // binary to take its place. That means there is no file where the current executable binary + // used to be! + // Try to rollback by restoring the old binary to its original path. + rerr := os.Rename(oldPath, opts.TargetPath) + if rerr != nil { + return &rollbackErr{err, rerr} + } + + return err + } + + // move successful, remove the old binary if needed + if removeOld { + errRemove := os.Remove(oldPath) + + // windows has trouble with removing old binaries, so hide it instead + if errRemove != nil { + _ = hideFile(oldPath) + } + } + + return nil +} + +// RollbackError takes an error value returned by Apply and returns the error, if any, +// that occurred when attempting to roll back from a failed update. Applications should +// always call this function on any non-nil errors returned by Apply. +// +// If no rollback was needed or if the rollback was successful, RollbackError returns nil, +// otherwise it returns the error encountered when trying to roll back. +func RollbackError(err error) error { + if err == nil { + return nil + } + if rerr, ok := err.(*rollbackErr); ok { + return rerr.rollbackErr + } + return nil +} + +type rollbackErr struct { + error // original error + rollbackErr error // error encountered while rolling back +} + +type Options struct { + // TargetPath defines the path to the file to update. + // The emptry string means 'the executable file of the running program'. + TargetPath string + + // Create TargetPath replacement with this file mode. If zero, defaults to 0755. + TargetMode os.FileMode + + // Checksum of the new binary to verify against. If nil, no checksum or signature verification is done. + Checksum []byte + + // Public key to use for signature verification. If nil, no signature verification is done. + PublicKey crypto.PublicKey + + // Signature to verify the updated file. If nil, no signature verification is done. + Signature []byte + + // Pluggable signature verification algorithm. If nil, ECDSA is used. + Verifier Verifier + + // Use this hash function to generate the checksum. If not set, SHA256 is used. + Hash crypto.Hash + + // If nil, treat the update as a complete replacement for the contents of the file at TargetPath. + // If non-nil, treat the update contents as a patch and use this object to apply the patch. + Patcher Patcher + + // Store the old executable file at this path after a successful update. + // The empty string means the old executable file will be removed after the update. + OldSavePath string +} + +// CheckPermissions determines whether the process has the correct permissions to +// perform the requested update. If the update can proceed, it returns nil, otherwise +// it returns the error that would occur if an update were attempted. +func (o *Options) CheckPermissions() error { + // get the directory the file exists in + path, err := o.getPath() + if err != nil { + return err + } + + fileDir := filepath.Dir(path) + fileName := filepath.Base(path) + + // attempt to open a file in the file's directory + newPath := filepath.Join(fileDir, fmt.Sprintf(".%s.new", fileName)) + fp, err := openFile(newPath, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, o.TargetMode) + if err != nil { + return err + } + fp.Close() + + _ = os.Remove(newPath) + return nil +} + +// SetPublicKeyPEM is a convenience method to set the PublicKey property +// used for checking a completed update's signature by parsing a +// Public Key formatted as PEM data. +func (o *Options) SetPublicKeyPEM(pembytes []byte) error { + block, _ := pem.Decode(pembytes) + if block == nil { + return errors.New("couldn't parse PEM data") + } + + pub, err := x509.ParsePKIXPublicKey(block.Bytes) + if err != nil { + return err + } + o.PublicKey = pub + return nil +} + +func (o *Options) getPath() (string, error) { + if o.TargetPath == "" { + return osext.Executable() + } else { + return o.TargetPath, nil + } +} + +func (o *Options) applyPatch(patch io.Reader) ([]byte, error) { + // open the file to patch + old, err := os.Open(o.TargetPath) + if err != nil { + return nil, err + } + defer old.Close() + + // apply the patch + var applied bytes.Buffer + if err = o.Patcher.Patch(old, &applied, patch); err != nil { + return nil, err + } + + return applied.Bytes(), nil +} + +func (o *Options) verifyChecksum(updated []byte) error { + checksum, err := checksumFor(o.Hash, updated) + if err != nil { + return err + } + + if !bytes.Equal(o.Checksum, checksum) { + return fmt.Errorf("Updated file has wrong checksum. Expected: %x, got: %x", o.Checksum, checksum) + } + return nil +} + +func (o *Options) verifySignature(updated []byte) error { + checksum, err := checksumFor(o.Hash, updated) + if err != nil { + return err + } + return o.Verifier.VerifySignature(checksum, o.Signature, o.Hash, o.PublicKey) +} + +func checksumFor(h crypto.Hash, payload []byte) ([]byte, error) { + if !h.Available() { + return nil, errors.New("requested hash function not available") + } + hash := h.New() + hash.Write(payload) // guaranteed not to error + return hash.Sum([]byte{}), nil +} diff --git a/vendor/github.com/inconshreveable/go-update/doc.go b/vendor/github.com/inconshreveable/go-update/doc.go new file mode 100644 index 000000000..468411f95 --- /dev/null +++ b/vendor/github.com/inconshreveable/go-update/doc.go @@ -0,0 +1,172 @@ +/* +Package update provides functionality to implement secure, self-updating Go programs (or other single-file targets). + +For complete updating solutions please see Equinox (https://equinox.io) and go-tuf (https://github.com/flynn/go-tuf). + +Basic Example + +This example shows how to update a program remotely from a URL. + + import ( + "fmt" + "net/http" + + "github.com/inconshreveable/go-update" + ) + + func doUpdate(url string) error { + // request the new file + resp, err := http.Get(url) + if err != nil { + return err + } + defer resp.Body.Close() + err := update.Apply(resp.Body, update.Options{}) + if err != nil { + if rerr := update.RollbackError(err); rerr != nil { + fmt.Println("Failed to rollback from bad update: %v", rerr) + } + } + return err + } + + +Binary Patching + +Go binaries can often be large. It can be advantageous to only ship a binary patch to a client +instead of the complete program text of a new version. + +This example shows how to update a program with a bsdiff binary patch. Other patch formats +may be applied by implementing the Patcher interface. + + import ( + "encoding/hex" + "io" + + "github.com/inconshreveable/go-update" + ) + + func updateWithPatch(patch io.Reader) error { + err := update.Apply(patch, update.Options{ + Patcher: update.NewBSDiffPatcher() + }) + if err != nil { + // error handling + } + return err + } + +Checksum Verification + +Updating executable code on a computer can be a dangerous operation unless you +take the appropriate steps to guarantee the authenticity of the new code. While +checksum verification is important, it should always be combined with signature +verification (next section) to guarantee that the code came from a trusted party. + +go-update validates SHA256 checksums by default, but this is pluggable via the Hash +property on the Options struct. + +This example shows how to guarantee that the newly-updated binary is verified to +have an appropriate checksum (that was otherwise retrived via a secure channel) +specified as a hex string. + + import ( + "crypto" + _ "crypto/sha256" + "encoding/hex" + "io" + + "github.com/inconshreveable/go-update" + ) + + func updateWithChecksum(binary io.Reader, hexChecksum string) error { + checksum, err := hex.DecodeString(hexChecksum) + if err != nil { + return err + } + err = update.Apply(binary, update.Options{ + Hash: crypto.SHA256, // this is the default, you don't need to specify it + Checksum: checksum, + }) + if err != nil { + // error handling + } + return err + } + +Cryptographic Signature Verification + +Cryptographic verification of new code from an update is an extremely important way to guarantee the +security and integrity of your updates. + +Verification is performed by validating the signature of a hash of the new file. This +means nothing changes if you apply your update with a patch. + +This example shows how to add signature verification to your updates. To make all of this work +an application distributor must first create a public/private key pair and embed the public key +into their application. When they issue a new release, the issuer must sign the new executable file +with the private key and distribute the signature along with the update. + + import ( + "crypto" + _ "crypto/sha256" + "encoding/hex" + "io" + + "github.com/inconshreveable/go-update" + ) + + var publicKey = []byte(` + -----BEGIN PUBLIC KEY----- + MFYwEAYHKoZIzj0CAQYFK4EEAAoDQgAEtrVmBxQvheRArXjg2vG1xIprWGuCyESx + MMY8pjmjepSy2kuz+nl9aFLqmr+rDNdYvEBqQaZrYMc6k29gjvoQnQ== + -----END PUBLIC KEY----- + `) + + func verifiedUpdate(binary io.Reader, hexChecksum, hexSignature string) { + checksum, err := hex.DecodeString(hexChecksum) + if err != nil { + return err + } + signature, err := hex.DecodeString(hexSignature) + if err != nil { + return err + } + opts := update.Options{ + Checksum: checksum, + Signature: signature, + Hash: crypto.SHA256, // this is the default, you don't need to specify it + Verifier: update.NewECDSAVerifier(), // this is the default, you don't need to specify it + } + err = opts.SetPublicKeyPEM(publicKey) + if err != nil { + return err + } + err = update.Apply(binary, opts) + if err != nil { + // error handling + } + return err + } + + +Building Single-File Go Binaries + +In order to update a Go application with go-update, you must distributed it as a single executable. +This is often easy, but some applications require static assets (like HTML and CSS asset files or TLS certificates). +In order to update applications like these, you'll want to make sure to embed those asset files into +the distributed binary with a tool like go-bindata (my favorite): https://github.com/jteeuwen/go-bindata + +Non-Goals + +Mechanisms and protocols for determining whether an update should be applied and, if so, which one are +out of scope for this package. Please consult go-tuf (https://github.com/flynn/go-tuf) or Equinox (https://equinox.io) +for more complete solutions. + +go-update only works for self-updating applications that are distributed as a single binary, i.e. +applications that do not have additional assets or dependency files. +Updating application that are distributed as mutliple on-disk files is out of scope, although this +may change in future versions of this library. + +*/ +package update diff --git a/vendor/github.com/inconshreveable/go-update/hide_noop.go b/vendor/github.com/inconshreveable/go-update/hide_noop.go new file mode 100644 index 000000000..370775608 --- /dev/null +++ b/vendor/github.com/inconshreveable/go-update/hide_noop.go @@ -0,0 +1,7 @@ +// +build !windows + +package update + +func hideFile(path string) error { + return nil +} diff --git a/vendor/github.com/inconshreveable/go-update/hide_windows.go b/vendor/github.com/inconshreveable/go-update/hide_windows.go new file mode 100644 index 000000000..c368b9cc4 --- /dev/null +++ b/vendor/github.com/inconshreveable/go-update/hide_windows.go @@ -0,0 +1,19 @@ +package update + +import ( + "syscall" + "unsafe" +) + +func hideFile(path string) error { + kernel32 := syscall.NewLazyDLL("kernel32.dll") + setFileAttributes := kernel32.NewProc("SetFileAttributesW") + + r1, _, err := setFileAttributes.Call(uintptr(unsafe.Pointer(syscall.StringToUTF16Ptr(path))), 2) + + if r1 == 0 { + return err + } else { + return nil + } +} diff --git a/vendor/github.com/inconshreveable/go-update/internal/binarydist/License b/vendor/github.com/inconshreveable/go-update/internal/binarydist/License new file mode 100644 index 000000000..183c3898c --- /dev/null +++ b/vendor/github.com/inconshreveable/go-update/internal/binarydist/License @@ -0,0 +1,22 @@ +Copyright 2012 Keith Rarick + +Permission is hereby granted, free of charge, to any person +obtaining a copy of this software and associated documentation +files (the "Software"), to deal in the Software without +restriction, including without limitation the rights to use, +copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +OTHER DEALINGS IN THE SOFTWARE. diff --git a/vendor/github.com/inconshreveable/go-update/internal/binarydist/Readme.md b/vendor/github.com/inconshreveable/go-update/internal/binarydist/Readme.md new file mode 100644 index 000000000..dadc3683d --- /dev/null +++ b/vendor/github.com/inconshreveable/go-update/internal/binarydist/Readme.md @@ -0,0 +1,7 @@ +# binarydist + +Package binarydist implements binary diff and patch as described on +. It reads and writes files +compatible with the tools there. + +Documentation at . diff --git a/vendor/github.com/inconshreveable/go-update/internal/binarydist/bzip2.go b/vendor/github.com/inconshreveable/go-update/internal/binarydist/bzip2.go new file mode 100644 index 000000000..a2516b81d --- /dev/null +++ b/vendor/github.com/inconshreveable/go-update/internal/binarydist/bzip2.go @@ -0,0 +1,40 @@ +package binarydist + +import ( + "io" + "os/exec" +) + +type bzip2Writer struct { + c *exec.Cmd + w io.WriteCloser +} + +func (w bzip2Writer) Write(b []byte) (int, error) { + return w.w.Write(b) +} + +func (w bzip2Writer) Close() error { + if err := w.w.Close(); err != nil { + return err + } + return w.c.Wait() +} + +// Package compress/bzip2 implements only decompression, +// so we'll fake it by running bzip2 in another process. +func newBzip2Writer(w io.Writer) (wc io.WriteCloser, err error) { + var bw bzip2Writer + bw.c = exec.Command("bzip2", "-c") + bw.c.Stdout = w + + if bw.w, err = bw.c.StdinPipe(); err != nil { + return nil, err + } + + if err = bw.c.Start(); err != nil { + return nil, err + } + + return bw, nil +} diff --git a/vendor/github.com/inconshreveable/go-update/internal/binarydist/diff.go b/vendor/github.com/inconshreveable/go-update/internal/binarydist/diff.go new file mode 100644 index 000000000..1d2d951bb --- /dev/null +++ b/vendor/github.com/inconshreveable/go-update/internal/binarydist/diff.go @@ -0,0 +1,408 @@ +package binarydist + +import ( + "bytes" + "encoding/binary" + "io" + "io/ioutil" +) + +func swap(a []int, i, j int) { a[i], a[j] = a[j], a[i] } + +func split(I, V []int, start, length, h int) { + var i, j, k, x, jj, kk int + + if length < 16 { + for k = start; k < start+length; k += j { + j = 1 + x = V[I[k]+h] + for i = 1; k+i < start+length; i++ { + if V[I[k+i]+h] < x { + x = V[I[k+i]+h] + j = 0 + } + if V[I[k+i]+h] == x { + swap(I, k+i, k+j) + j++ + } + } + for i = 0; i < j; i++ { + V[I[k+i]] = k + j - 1 + } + if j == 1 { + I[k] = -1 + } + } + return + } + + x = V[I[start+length/2]+h] + jj = 0 + kk = 0 + for i = start; i < start+length; i++ { + if V[I[i]+h] < x { + jj++ + } + if V[I[i]+h] == x { + kk++ + } + } + jj += start + kk += jj + + i = start + j = 0 + k = 0 + for i < jj { + if V[I[i]+h] < x { + i++ + } else if V[I[i]+h] == x { + swap(I, i, jj+j) + j++ + } else { + swap(I, i, kk+k) + k++ + } + } + + for jj+j < kk { + if V[I[jj+j]+h] == x { + j++ + } else { + swap(I, jj+j, kk+k) + k++ + } + } + + if jj > start { + split(I, V, start, jj-start, h) + } + + for i = 0; i < kk-jj; i++ { + V[I[jj+i]] = kk - 1 + } + if jj == kk-1 { + I[jj] = -1 + } + + if start+length > kk { + split(I, V, kk, start+length-kk, h) + } +} + +func qsufsort(obuf []byte) []int { + var buckets [256]int + var i, h int + I := make([]int, len(obuf)+1) + V := make([]int, len(obuf)+1) + + for _, c := range obuf { + buckets[c]++ + } + for i = 1; i < 256; i++ { + buckets[i] += buckets[i-1] + } + copy(buckets[1:], buckets[:]) + buckets[0] = 0 + + for i, c := range obuf { + buckets[c]++ + I[buckets[c]] = i + } + + I[0] = len(obuf) + for i, c := range obuf { + V[i] = buckets[c] + } + + V[len(obuf)] = 0 + for i = 1; i < 256; i++ { + if buckets[i] == buckets[i-1]+1 { + I[buckets[i]] = -1 + } + } + I[0] = -1 + + for h = 1; I[0] != -(len(obuf) + 1); h += h { + var n int + for i = 0; i < len(obuf)+1; { + if I[i] < 0 { + n -= I[i] + i -= I[i] + } else { + if n != 0 { + I[i-n] = -n + } + n = V[I[i]] + 1 - i + split(I, V, i, n, h) + i += n + n = 0 + } + } + if n != 0 { + I[i-n] = -n + } + } + + for i = 0; i < len(obuf)+1; i++ { + I[V[i]] = i + } + return I +} + +func matchlen(a, b []byte) (i int) { + for i < len(a) && i < len(b) && a[i] == b[i] { + i++ + } + return i +} + +func search(I []int, obuf, nbuf []byte, st, en int) (pos, n int) { + if en-st < 2 { + x := matchlen(obuf[I[st]:], nbuf) + y := matchlen(obuf[I[en]:], nbuf) + + if x > y { + return I[st], x + } else { + return I[en], y + } + } + + x := st + (en-st)/2 + if bytes.Compare(obuf[I[x]:], nbuf) < 0 { + return search(I, obuf, nbuf, x, en) + } else { + return search(I, obuf, nbuf, st, x) + } + panic("unreached") +} + +// Diff computes the difference between old and new, according to the bsdiff +// algorithm, and writes the result to patch. +func Diff(old, new io.Reader, patch io.Writer) error { + obuf, err := ioutil.ReadAll(old) + if err != nil { + return err + } + + nbuf, err := ioutil.ReadAll(new) + if err != nil { + return err + } + + pbuf, err := diffBytes(obuf, nbuf) + if err != nil { + return err + } + + _, err = patch.Write(pbuf) + return err +} + +func diffBytes(obuf, nbuf []byte) ([]byte, error) { + var patch seekBuffer + err := diff(obuf, nbuf, &patch) + if err != nil { + return nil, err + } + return patch.buf, nil +} + +func diff(obuf, nbuf []byte, patch io.WriteSeeker) error { + var lenf int + I := qsufsort(obuf) + db := make([]byte, len(nbuf)) + eb := make([]byte, len(nbuf)) + var dblen, eblen int + + var hdr header + hdr.Magic = magic + hdr.NewSize = int64(len(nbuf)) + err := binary.Write(patch, signMagLittleEndian{}, &hdr) + if err != nil { + return err + } + + // Compute the differences, writing ctrl as we go + pfbz2, err := newBzip2Writer(patch) + if err != nil { + return err + } + var scan, pos, length int + var lastscan, lastpos, lastoffset int + for scan < len(nbuf) { + var oldscore int + scan += length + for scsc := scan; scan < len(nbuf); scan++ { + pos, length = search(I, obuf, nbuf[scan:], 0, len(obuf)) + + for ; scsc < scan+length; scsc++ { + if scsc+lastoffset < len(obuf) && + obuf[scsc+lastoffset] == nbuf[scsc] { + oldscore++ + } + } + + if (length == oldscore && length != 0) || length > oldscore+8 { + break + } + + if scan+lastoffset < len(obuf) && obuf[scan+lastoffset] == nbuf[scan] { + oldscore-- + } + } + + if length != oldscore || scan == len(nbuf) { + var s, Sf int + lenf = 0 + for i := 0; lastscan+i < scan && lastpos+i < len(obuf); { + if obuf[lastpos+i] == nbuf[lastscan+i] { + s++ + } + i++ + if s*2-i > Sf*2-lenf { + Sf = s + lenf = i + } + } + + lenb := 0 + if scan < len(nbuf) { + var s, Sb int + for i := 1; (scan >= lastscan+i) && (pos >= i); i++ { + if obuf[pos-i] == nbuf[scan-i] { + s++ + } + if s*2-i > Sb*2-lenb { + Sb = s + lenb = i + } + } + } + + if lastscan+lenf > scan-lenb { + overlap := (lastscan + lenf) - (scan - lenb) + s := 0 + Ss := 0 + lens := 0 + for i := 0; i < overlap; i++ { + if nbuf[lastscan+lenf-overlap+i] == obuf[lastpos+lenf-overlap+i] { + s++ + } + if nbuf[scan-lenb+i] == obuf[pos-lenb+i] { + s-- + } + if s > Ss { + Ss = s + lens = i + 1 + } + } + + lenf += lens - overlap + lenb -= lens + } + + for i := 0; i < lenf; i++ { + db[dblen+i] = nbuf[lastscan+i] - obuf[lastpos+i] + } + for i := 0; i < (scan-lenb)-(lastscan+lenf); i++ { + eb[eblen+i] = nbuf[lastscan+lenf+i] + } + + dblen += lenf + eblen += (scan - lenb) - (lastscan + lenf) + + err = binary.Write(pfbz2, signMagLittleEndian{}, int64(lenf)) + if err != nil { + pfbz2.Close() + return err + } + + val := (scan - lenb) - (lastscan + lenf) + err = binary.Write(pfbz2, signMagLittleEndian{}, int64(val)) + if err != nil { + pfbz2.Close() + return err + } + + val = (pos - lenb) - (lastpos + lenf) + err = binary.Write(pfbz2, signMagLittleEndian{}, int64(val)) + if err != nil { + pfbz2.Close() + return err + } + + lastscan = scan - lenb + lastpos = pos - lenb + lastoffset = pos - scan + } + } + err = pfbz2.Close() + if err != nil { + return err + } + + // Compute size of compressed ctrl data + l64, err := patch.Seek(0, 1) + if err != nil { + return err + } + hdr.CtrlLen = int64(l64 - 32) + + // Write compressed diff data + pfbz2, err = newBzip2Writer(patch) + if err != nil { + return err + } + n, err := pfbz2.Write(db[:dblen]) + if err != nil { + pfbz2.Close() + return err + } + if n != dblen { + pfbz2.Close() + return io.ErrShortWrite + } + err = pfbz2.Close() + if err != nil { + return err + } + + // Compute size of compressed diff data + n64, err := patch.Seek(0, 1) + if err != nil { + return err + } + hdr.DiffLen = n64 - l64 + + // Write compressed extra data + pfbz2, err = newBzip2Writer(patch) + if err != nil { + return err + } + n, err = pfbz2.Write(eb[:eblen]) + if err != nil { + pfbz2.Close() + return err + } + if n != eblen { + pfbz2.Close() + return io.ErrShortWrite + } + err = pfbz2.Close() + if err != nil { + return err + } + + // Seek to the beginning, write the header, and close the file + _, err = patch.Seek(0, 0) + if err != nil { + return err + } + err = binary.Write(patch, signMagLittleEndian{}, &hdr) + if err != nil { + return err + } + return nil +} diff --git a/vendor/github.com/inconshreveable/go-update/internal/binarydist/doc.go b/vendor/github.com/inconshreveable/go-update/internal/binarydist/doc.go new file mode 100644 index 000000000..3c92d8750 --- /dev/null +++ b/vendor/github.com/inconshreveable/go-update/internal/binarydist/doc.go @@ -0,0 +1,24 @@ +// Package binarydist implements binary diff and patch as described on +// http://www.daemonology.net/bsdiff/. It reads and writes files +// compatible with the tools there. +package binarydist + +var magic = [8]byte{'B', 'S', 'D', 'I', 'F', 'F', '4', '0'} + +// File format: +// 0 8 "BSDIFF40" +// 8 8 X +// 16 8 Y +// 24 8 sizeof(newfile) +// 32 X bzip2(control block) +// 32+X Y bzip2(diff block) +// 32+X+Y ??? bzip2(extra block) +// with control block a set of triples (x,y,z) meaning "add x bytes +// from oldfile to x bytes from the diff block; copy y bytes from the +// extra block; seek forwards in oldfile by z bytes". +type header struct { + Magic [8]byte + CtrlLen int64 + DiffLen int64 + NewSize int64 +} diff --git a/vendor/github.com/inconshreveable/go-update/internal/binarydist/encoding.go b/vendor/github.com/inconshreveable/go-update/internal/binarydist/encoding.go new file mode 100644 index 000000000..75ba5856a --- /dev/null +++ b/vendor/github.com/inconshreveable/go-update/internal/binarydist/encoding.go @@ -0,0 +1,53 @@ +package binarydist + +// SignMagLittleEndian is the numeric encoding used by the bsdiff tools. +// It implements binary.ByteOrder using a sign-magnitude format +// and little-endian byte order. Only methods Uint64 and String +// have been written; the rest panic. +type signMagLittleEndian struct{} + +func (signMagLittleEndian) Uint16(b []byte) uint16 { panic("unimplemented") } + +func (signMagLittleEndian) PutUint16(b []byte, v uint16) { panic("unimplemented") } + +func (signMagLittleEndian) Uint32(b []byte) uint32 { panic("unimplemented") } + +func (signMagLittleEndian) PutUint32(b []byte, v uint32) { panic("unimplemented") } + +func (signMagLittleEndian) Uint64(b []byte) uint64 { + y := int64(b[0]) | + int64(b[1])<<8 | + int64(b[2])<<16 | + int64(b[3])<<24 | + int64(b[4])<<32 | + int64(b[5])<<40 | + int64(b[6])<<48 | + int64(b[7]&0x7f)<<56 + + if b[7]&0x80 != 0 { + y = -y + } + return uint64(y) +} + +func (signMagLittleEndian) PutUint64(b []byte, v uint64) { + x := int64(v) + neg := x < 0 + if neg { + x = -x + } + + b[0] = byte(x) + b[1] = byte(x >> 8) + b[2] = byte(x >> 16) + b[3] = byte(x >> 24) + b[4] = byte(x >> 32) + b[5] = byte(x >> 40) + b[6] = byte(x >> 48) + b[7] = byte(x >> 56) + if neg { + b[7] |= 0x80 + } +} + +func (signMagLittleEndian) String() string { return "signMagLittleEndian" } diff --git a/vendor/github.com/inconshreveable/go-update/internal/binarydist/patch.go b/vendor/github.com/inconshreveable/go-update/internal/binarydist/patch.go new file mode 100644 index 000000000..eb0322578 --- /dev/null +++ b/vendor/github.com/inconshreveable/go-update/internal/binarydist/patch.go @@ -0,0 +1,109 @@ +package binarydist + +import ( + "bytes" + "compress/bzip2" + "encoding/binary" + "errors" + "io" + "io/ioutil" +) + +var ErrCorrupt = errors.New("corrupt patch") + +// Patch applies patch to old, according to the bspatch algorithm, +// and writes the result to new. +func Patch(old io.Reader, new io.Writer, patch io.Reader) error { + var hdr header + err := binary.Read(patch, signMagLittleEndian{}, &hdr) + if err != nil { + return err + } + if hdr.Magic != magic { + return ErrCorrupt + } + if hdr.CtrlLen < 0 || hdr.DiffLen < 0 || hdr.NewSize < 0 { + return ErrCorrupt + } + + ctrlbuf := make([]byte, hdr.CtrlLen) + _, err = io.ReadFull(patch, ctrlbuf) + if err != nil { + return err + } + cpfbz2 := bzip2.NewReader(bytes.NewReader(ctrlbuf)) + + diffbuf := make([]byte, hdr.DiffLen) + _, err = io.ReadFull(patch, diffbuf) + if err != nil { + return err + } + dpfbz2 := bzip2.NewReader(bytes.NewReader(diffbuf)) + + // The entire rest of the file is the extra block. + epfbz2 := bzip2.NewReader(patch) + + obuf, err := ioutil.ReadAll(old) + if err != nil { + return err + } + + nbuf := make([]byte, hdr.NewSize) + + var oldpos, newpos int64 + for newpos < hdr.NewSize { + var ctrl struct{ Add, Copy, Seek int64 } + err = binary.Read(cpfbz2, signMagLittleEndian{}, &ctrl) + if err != nil { + return err + } + + // Sanity-check + if newpos+ctrl.Add > hdr.NewSize { + return ErrCorrupt + } + + // Read diff string + _, err = io.ReadFull(dpfbz2, nbuf[newpos:newpos+ctrl.Add]) + if err != nil { + return ErrCorrupt + } + + // Add old data to diff string + for i := int64(0); i < ctrl.Add; i++ { + if oldpos+i >= 0 && oldpos+i < int64(len(obuf)) { + nbuf[newpos+i] += obuf[oldpos+i] + } + } + + // Adjust pointers + newpos += ctrl.Add + oldpos += ctrl.Add + + // Sanity-check + if newpos+ctrl.Copy > hdr.NewSize { + return ErrCorrupt + } + + // Read extra string + _, err = io.ReadFull(epfbz2, nbuf[newpos:newpos+ctrl.Copy]) + if err != nil { + return ErrCorrupt + } + + // Adjust pointers + newpos += ctrl.Copy + oldpos += ctrl.Seek + } + + // Write the new file + for len(nbuf) > 0 { + n, err := new.Write(nbuf) + if err != nil { + return err + } + nbuf = nbuf[n:] + } + + return nil +} diff --git a/vendor/github.com/inconshreveable/go-update/internal/binarydist/seek.go b/vendor/github.com/inconshreveable/go-update/internal/binarydist/seek.go new file mode 100644 index 000000000..96c03461e --- /dev/null +++ b/vendor/github.com/inconshreveable/go-update/internal/binarydist/seek.go @@ -0,0 +1,43 @@ +package binarydist + +import ( + "errors" +) + +type seekBuffer struct { + buf []byte + pos int +} + +func (b *seekBuffer) Write(p []byte) (n int, err error) { + n = copy(b.buf[b.pos:], p) + if n == len(p) { + b.pos += n + return n, nil + } + b.buf = append(b.buf, p[n:]...) + b.pos += len(p) + return len(p), nil +} + +func (b *seekBuffer) Seek(offset int64, whence int) (ret int64, err error) { + var abs int64 + switch whence { + case 0: + abs = offset + case 1: + abs = int64(b.pos) + offset + case 2: + abs = int64(len(b.buf)) + offset + default: + return 0, errors.New("binarydist: invalid whence") + } + if abs < 0 { + return 0, errors.New("binarydist: negative position") + } + if abs >= 1<<31 { + return 0, errors.New("binarydist: position out of range") + } + b.pos = int(abs) + return abs, nil +} diff --git a/vendor/github.com/inconshreveable/go-update/internal/osext/LICENSE b/vendor/github.com/inconshreveable/go-update/internal/osext/LICENSE new file mode 100644 index 000000000..744875676 --- /dev/null +++ b/vendor/github.com/inconshreveable/go-update/internal/osext/LICENSE @@ -0,0 +1,27 @@ +Copyright (c) 2012 The Go Authors. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of Google Inc. nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/vendor/github.com/inconshreveable/go-update/internal/osext/README.md b/vendor/github.com/inconshreveable/go-update/internal/osext/README.md new file mode 100644 index 000000000..61350baba --- /dev/null +++ b/vendor/github.com/inconshreveable/go-update/internal/osext/README.md @@ -0,0 +1,16 @@ +### Extensions to the "os" package. + +## Find the current Executable and ExecutableFolder. + +There is sometimes utility in finding the current executable file +that is running. This can be used for upgrading the current executable +or finding resources located relative to the executable file. Both +working directory and the os.Args[0] value are arbitrary and cannot +be relied on; os.Args[0] can be "faked". + +Multi-platform and supports: + * Linux + * OS X + * Windows + * Plan 9 + * BSDs. diff --git a/vendor/github.com/inconshreveable/go-update/internal/osext/osext.go b/vendor/github.com/inconshreveable/go-update/internal/osext/osext.go new file mode 100644 index 000000000..7bef46f03 --- /dev/null +++ b/vendor/github.com/inconshreveable/go-update/internal/osext/osext.go @@ -0,0 +1,27 @@ +// Copyright 2012 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Extensions to the standard "os" package. +package osext + +import "path/filepath" + +// Executable returns an absolute path that can be used to +// re-invoke the current program. +// It may not be valid after the current program exits. +func Executable() (string, error) { + p, err := executable() + return filepath.Clean(p), err +} + +// Returns same path as Executable, returns just the folder +// path. Excludes the executable name and any trailing slash. +func ExecutableFolder() (string, error) { + p, err := Executable() + if err != nil { + return "", err + } + + return filepath.Dir(p), nil +} diff --git a/vendor/github.com/inconshreveable/go-update/internal/osext/osext_plan9.go b/vendor/github.com/inconshreveable/go-update/internal/osext/osext_plan9.go new file mode 100644 index 000000000..655750c54 --- /dev/null +++ b/vendor/github.com/inconshreveable/go-update/internal/osext/osext_plan9.go @@ -0,0 +1,20 @@ +// Copyright 2012 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package osext + +import ( + "os" + "strconv" + "syscall" +) + +func executable() (string, error) { + f, err := os.Open("/proc/" + strconv.Itoa(os.Getpid()) + "/text") + if err != nil { + return "", err + } + defer f.Close() + return syscall.Fd2path(int(f.Fd())) +} diff --git a/vendor/github.com/inconshreveable/go-update/internal/osext/osext_procfs.go b/vendor/github.com/inconshreveable/go-update/internal/osext/osext_procfs.go new file mode 100644 index 000000000..b2598bc77 --- /dev/null +++ b/vendor/github.com/inconshreveable/go-update/internal/osext/osext_procfs.go @@ -0,0 +1,36 @@ +// Copyright 2012 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// +build linux netbsd openbsd solaris dragonfly + +package osext + +import ( + "errors" + "fmt" + "os" + "runtime" + "strings" +) + +func executable() (string, error) { + switch runtime.GOOS { + case "linux": + const deletedTag = " (deleted)" + execpath, err := os.Readlink("/proc/self/exe") + if err != nil { + return execpath, err + } + execpath = strings.TrimSuffix(execpath, deletedTag) + execpath = strings.TrimPrefix(execpath, deletedTag) + return execpath, nil + case "netbsd": + return os.Readlink("/proc/curproc/exe") + case "openbsd", "dragonfly": + return os.Readlink("/proc/curproc/file") + case "solaris": + return os.Readlink(fmt.Sprintf("/proc/%d/path/a.out", os.Getpid())) + } + return "", errors.New("ExecPath not implemented for " + runtime.GOOS) +} diff --git a/vendor/github.com/inconshreveable/go-update/internal/osext/osext_sysctl.go b/vendor/github.com/inconshreveable/go-update/internal/osext/osext_sysctl.go new file mode 100644 index 000000000..b66cac878 --- /dev/null +++ b/vendor/github.com/inconshreveable/go-update/internal/osext/osext_sysctl.go @@ -0,0 +1,79 @@ +// Copyright 2012 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// +build darwin freebsd + +package osext + +import ( + "os" + "path/filepath" + "runtime" + "syscall" + "unsafe" +) + +var initCwd, initCwdErr = os.Getwd() + +func executable() (string, error) { + var mib [4]int32 + switch runtime.GOOS { + case "freebsd": + mib = [4]int32{1 /* CTL_KERN */, 14 /* KERN_PROC */, 12 /* KERN_PROC_PATHNAME */, -1} + case "darwin": + mib = [4]int32{1 /* CTL_KERN */, 38 /* KERN_PROCARGS */, int32(os.Getpid()), -1} + } + + n := uintptr(0) + // Get length. + _, _, errNum := syscall.Syscall6(syscall.SYS___SYSCTL, uintptr(unsafe.Pointer(&mib[0])), 4, 0, uintptr(unsafe.Pointer(&n)), 0, 0) + if errNum != 0 { + return "", errNum + } + if n == 0 { // This shouldn't happen. + return "", nil + } + buf := make([]byte, n) + _, _, errNum = syscall.Syscall6(syscall.SYS___SYSCTL, uintptr(unsafe.Pointer(&mib[0])), 4, uintptr(unsafe.Pointer(&buf[0])), uintptr(unsafe.Pointer(&n)), 0, 0) + if errNum != 0 { + return "", errNum + } + if n == 0 { // This shouldn't happen. + return "", nil + } + for i, v := range buf { + if v == 0 { + buf = buf[:i] + break + } + } + var err error + execPath := string(buf) + // execPath will not be empty due to above checks. + // Try to get the absolute path if the execPath is not rooted. + if execPath[0] != '/' { + execPath, err = getAbs(execPath) + if err != nil { + return execPath, err + } + } + // For darwin KERN_PROCARGS may return the path to a symlink rather than the + // actual executable. + if runtime.GOOS == "darwin" { + if execPath, err = filepath.EvalSymlinks(execPath); err != nil { + return execPath, err + } + } + return execPath, nil +} + +func getAbs(execPath string) (string, error) { + if initCwdErr != nil { + return execPath, initCwdErr + } + // The execPath may begin with a "../" or a "./" so clean it first. + // Join the two paths, trailing and starting slashes undetermined, so use + // the generic Join function. + return filepath.Join(initCwd, filepath.Clean(execPath)), nil +} diff --git a/vendor/github.com/inconshreveable/go-update/internal/osext/osext_windows.go b/vendor/github.com/inconshreveable/go-update/internal/osext/osext_windows.go new file mode 100644 index 000000000..72d282cf8 --- /dev/null +++ b/vendor/github.com/inconshreveable/go-update/internal/osext/osext_windows.go @@ -0,0 +1,34 @@ +// Copyright 2012 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package osext + +import ( + "syscall" + "unicode/utf16" + "unsafe" +) + +var ( + kernel = syscall.MustLoadDLL("kernel32.dll") + getModuleFileNameProc = kernel.MustFindProc("GetModuleFileNameW") +) + +// GetModuleFileName() with hModule = NULL +func executable() (exePath string, err error) { + return getModuleFileName() +} + +func getModuleFileName() (string, error) { + var n uint32 + b := make([]uint16, syscall.MAX_PATH) + size := uint32(len(b)) + + r0, _, e1 := getModuleFileNameProc.Call(0, uintptr(unsafe.Pointer(&b[0])), uintptr(size)) + n = uint32(r0) + if n == 0 { + return "", e1 + } + return string(utf16.Decode(b[0:n])), nil +} diff --git a/vendor/github.com/inconshreveable/go-update/patcher.go b/vendor/github.com/inconshreveable/go-update/patcher.go new file mode 100644 index 000000000..1da89cb85 --- /dev/null +++ b/vendor/github.com/inconshreveable/go-update/patcher.go @@ -0,0 +1,24 @@ +package update + +import ( + "io" + + "github.com/inconshreveable/go-update/internal/binarydist" +) + +// Patcher defines an interface for applying binary patches to an old item to get an updated item. +type Patcher interface { + Patch(old io.Reader, new io.Writer, patch io.Reader) error +} + +type patchFn func(io.Reader, io.Writer, io.Reader) error + +func (fn patchFn) Patch(old io.Reader, new io.Writer, patch io.Reader) error { + return fn(old, new, patch) +} + +// NewBSDifferPatcher returns a new Patcher that applies binary patches using +// the bsdiff algorithm. See http://www.daemonology.net/bsdiff/ +func NewBSDiffPatcher() Patcher { + return patchFn(binarydist.Patch) +} diff --git a/vendor/github.com/inconshreveable/go-update/verifier.go b/vendor/github.com/inconshreveable/go-update/verifier.go new file mode 100644 index 000000000..af1fc5764 --- /dev/null +++ b/vendor/github.com/inconshreveable/go-update/verifier.go @@ -0,0 +1,74 @@ +package update + +import ( + "crypto" + "crypto/dsa" + "crypto/ecdsa" + "crypto/rsa" + "encoding/asn1" + "errors" + "math/big" +) + +// Verifier defines an interface for verfiying an update's signature with a public key. +type Verifier interface { + VerifySignature(checksum, signature []byte, h crypto.Hash, publicKey crypto.PublicKey) error +} + +type verifyFn func([]byte, []byte, crypto.Hash, crypto.PublicKey) error + +func (fn verifyFn) VerifySignature(checksum []byte, signature []byte, hash crypto.Hash, publicKey crypto.PublicKey) error { + return fn(checksum, signature, hash, publicKey) +} + +// NewRSAVerifier returns a Verifier that uses the RSA algorithm to verify updates. +func NewRSAVerifier() Verifier { + return verifyFn(func(checksum, signature []byte, hash crypto.Hash, publicKey crypto.PublicKey) error { + key, ok := publicKey.(*rsa.PublicKey) + if !ok { + return errors.New("not a valid RSA public key") + } + return rsa.VerifyPKCS1v15(key, hash, checksum, signature) + }) +} + +type rsDER struct { + R *big.Int + S *big.Int +} + +// NewECDSAVerifier returns a Verifier that uses the ECDSA algorithm to verify updates. +func NewECDSAVerifier() Verifier { + return verifyFn(func(checksum, signature []byte, hash crypto.Hash, publicKey crypto.PublicKey) error { + key, ok := publicKey.(*ecdsa.PublicKey) + if !ok { + return errors.New("not a valid ECDSA public key") + } + var rs rsDER + if _, err := asn1.Unmarshal(signature, &rs); err != nil { + return err + } + if !ecdsa.Verify(key, checksum, rs.R, rs.S) { + return errors.New("failed to verify ecsda signature") + } + return nil + }) +} + +// NewDSAVerifier returns a Verifier that uses the DSA algorithm to verify updates. +func NewDSAVerifier() Verifier { + return verifyFn(func(checksum, signature []byte, hash crypto.Hash, publicKey crypto.PublicKey) error { + key, ok := publicKey.(*dsa.PublicKey) + if !ok { + return errors.New("not a valid DSA public key") + } + var rs rsDER + if _, err := asn1.Unmarshal(signature, &rs); err != nil { + return err + } + if !dsa.Verify(key, checksum, rs.R, rs.S) { + return errors.New("failed to verify ecsda signature") + } + return nil + }) +} diff --git a/vendor/github.com/segmentio/go-prompt/History.md b/vendor/github.com/segmentio/go-prompt/History.md new file mode 100644 index 000000000..ef79ef1b2 --- /dev/null +++ b/vendor/github.com/segmentio/go-prompt/History.md @@ -0,0 +1,13 @@ + +v1.2.0 / 2014-12-10 +=================== + + * remove appending of ? to Confirm() + +v1.1.0 / 2014-10-22 +================== + + * add passwords example + * add password docs + * Merge pull request #2 from nrmitchi/add/gopass + * Adding convenience wrappers around howeyc/gopass diff --git a/vendor/github.com/segmentio/go-prompt/Readme.md b/vendor/github.com/segmentio/go-prompt/Readme.md new file mode 100644 index 000000000..af14a050a --- /dev/null +++ b/vendor/github.com/segmentio/go-prompt/Readme.md @@ -0,0 +1,33 @@ + +# go-prompt + + Terminal prompts for Go. + + View the [docs](http://godoc.org/pkg/github.com/segmentio/go-prompt). + +## Example + +```go +package main + +import "github.com/segmentio/go-prompt" + +var langs = []string{ + "c", + "c++", + "lua", + "go", + "js", + "ruby", + "python", +} + +func main() { + i := prompt.Choose("What's your favorite language?", langs) + println("picked: " + langs[i]) +} +``` + +## License + + MIT \ No newline at end of file diff --git a/vendor/github.com/segmentio/go-prompt/prompt.go b/vendor/github.com/segmentio/go-prompt/prompt.go new file mode 100644 index 000000000..412736e24 --- /dev/null +++ b/vendor/github.com/segmentio/go-prompt/prompt.go @@ -0,0 +1,94 @@ +package prompt + +import "github.com/howeyc/gopass" +import "strings" +import "strconv" +import "fmt" + +// String prompt. +func String(prompt string, args ...interface{}) string { + var s string + fmt.Printf(prompt+": ", args...) + fmt.Scanln(&s) + return s +} + +// String prompt (required). +func StringRequired(prompt string, args ...interface{}) (s string) { + for strings.Trim(s, " ") == "" { + s = String(prompt, args...) + } + return s +} + +// Confirm continues prompting until the input is boolean-ish. +func Confirm(prompt string, args ...interface{}) bool { + for { + switch String(prompt, args...) { + case "Yes", "yes", "y", "Y": + return true + case "No", "no", "n", "N": + return false + } + } +} + +// Choose prompts for a single selection from `list`, returning in the index. +func Choose(prompt string, list []string) int { + fmt.Println() + for i, val := range list { + fmt.Printf(" %d) %s\n", i+1, val) + } + + fmt.Println() + i := -1 + + for { + s := String(prompt) + + // index + n, err := strconv.Atoi(s) + if err == nil { + if n > 0 && n <= len(list) { + i = n - 1 + break + } else { + continue + } + } + + // value + i = indexOf(s, list) + if i != -1 { + break + } + } + + return i +} + +// Password prompt. +func Password(prompt string, args ...interface{}) string { + fmt.Printf(prompt+": ", args...) + password, _ := gopass.GetPasswd() + s := string(password[0:]) + return s +} + +// Password prompt with mask. +func PasswordMasked(prompt string, args ...interface{}) string { + fmt.Printf(prompt+": ", args...) + password, _ := gopass.GetPasswdMasked() + s := string(password[0:]) + return s +} + +// index of `s` in `list`. +func indexOf(s string, list []string) int { + for i, val := range list { + if val == s { + return i + } + } + return -1 +} diff --git a/vendor/golang.org/x/crypto/ssh/terminal/terminal.go b/vendor/golang.org/x/crypto/ssh/terminal/terminal.go new file mode 100644 index 000000000..18379a935 --- /dev/null +++ b/vendor/golang.org/x/crypto/ssh/terminal/terminal.go @@ -0,0 +1,951 @@ +// Copyright 2011 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package terminal + +import ( + "bytes" + "io" + "sync" + "unicode/utf8" +) + +// EscapeCodes contains escape sequences that can be written to the terminal in +// order to achieve different styles of text. +type EscapeCodes struct { + // Foreground colors + Black, Red, Green, Yellow, Blue, Magenta, Cyan, White []byte + + // Reset all attributes + Reset []byte +} + +var vt100EscapeCodes = EscapeCodes{ + Black: []byte{keyEscape, '[', '3', '0', 'm'}, + Red: []byte{keyEscape, '[', '3', '1', 'm'}, + Green: []byte{keyEscape, '[', '3', '2', 'm'}, + Yellow: []byte{keyEscape, '[', '3', '3', 'm'}, + Blue: []byte{keyEscape, '[', '3', '4', 'm'}, + Magenta: []byte{keyEscape, '[', '3', '5', 'm'}, + Cyan: []byte{keyEscape, '[', '3', '6', 'm'}, + White: []byte{keyEscape, '[', '3', '7', 'm'}, + + Reset: []byte{keyEscape, '[', '0', 'm'}, +} + +// Terminal contains the state for running a VT100 terminal that is capable of +// reading lines of input. +type Terminal struct { + // AutoCompleteCallback, if non-null, is called for each keypress with + // the full input line and the current position of the cursor (in + // bytes, as an index into |line|). If it returns ok=false, the key + // press is processed normally. Otherwise it returns a replacement line + // and the new cursor position. + AutoCompleteCallback func(line string, pos int, key rune) (newLine string, newPos int, ok bool) + + // Escape contains a pointer to the escape codes for this terminal. + // It's always a valid pointer, although the escape codes themselves + // may be empty if the terminal doesn't support them. + Escape *EscapeCodes + + // lock protects the terminal and the state in this object from + // concurrent processing of a key press and a Write() call. + lock sync.Mutex + + c io.ReadWriter + prompt []rune + + // line is the current line being entered. + line []rune + // pos is the logical position of the cursor in line + pos int + // echo is true if local echo is enabled + echo bool + // pasteActive is true iff there is a bracketed paste operation in + // progress. + pasteActive bool + + // cursorX contains the current X value of the cursor where the left + // edge is 0. cursorY contains the row number where the first row of + // the current line is 0. + cursorX, cursorY int + // maxLine is the greatest value of cursorY so far. + maxLine int + + termWidth, termHeight int + + // outBuf contains the terminal data to be sent. + outBuf []byte + // remainder contains the remainder of any partial key sequences after + // a read. It aliases into inBuf. + remainder []byte + inBuf [256]byte + + // history contains previously entered commands so that they can be + // accessed with the up and down keys. + history stRingBuffer + // historyIndex stores the currently accessed history entry, where zero + // means the immediately previous entry. + historyIndex int + // When navigating up and down the history it's possible to return to + // the incomplete, initial line. That value is stored in + // historyPending. + historyPending string +} + +// NewTerminal runs a VT100 terminal on the given ReadWriter. If the ReadWriter is +// a local terminal, that terminal must first have been put into raw mode. +// prompt is a string that is written at the start of each input line (i.e. +// "> "). +func NewTerminal(c io.ReadWriter, prompt string) *Terminal { + return &Terminal{ + Escape: &vt100EscapeCodes, + c: c, + prompt: []rune(prompt), + termWidth: 80, + termHeight: 24, + echo: true, + historyIndex: -1, + } +} + +const ( + keyCtrlD = 4 + keyCtrlU = 21 + keyEnter = '\r' + keyEscape = 27 + keyBackspace = 127 + keyUnknown = 0xd800 /* UTF-16 surrogate area */ + iota + keyUp + keyDown + keyLeft + keyRight + keyAltLeft + keyAltRight + keyHome + keyEnd + keyDeleteWord + keyDeleteLine + keyClearScreen + keyPasteStart + keyPasteEnd +) + +var ( + crlf = []byte{'\r', '\n'} + pasteStart = []byte{keyEscape, '[', '2', '0', '0', '~'} + pasteEnd = []byte{keyEscape, '[', '2', '0', '1', '~'} +) + +// bytesToKey tries to parse a key sequence from b. If successful, it returns +// the key and the remainder of the input. Otherwise it returns utf8.RuneError. +func bytesToKey(b []byte, pasteActive bool) (rune, []byte) { + if len(b) == 0 { + return utf8.RuneError, nil + } + + if !pasteActive { + switch b[0] { + case 1: // ^A + return keyHome, b[1:] + case 5: // ^E + return keyEnd, b[1:] + case 8: // ^H + return keyBackspace, b[1:] + case 11: // ^K + return keyDeleteLine, b[1:] + case 12: // ^L + return keyClearScreen, b[1:] + case 23: // ^W + return keyDeleteWord, b[1:] + } + } + + if b[0] != keyEscape { + if !utf8.FullRune(b) { + return utf8.RuneError, b + } + r, l := utf8.DecodeRune(b) + return r, b[l:] + } + + if !pasteActive && len(b) >= 3 && b[0] == keyEscape && b[1] == '[' { + switch b[2] { + case 'A': + return keyUp, b[3:] + case 'B': + return keyDown, b[3:] + case 'C': + return keyRight, b[3:] + case 'D': + return keyLeft, b[3:] + case 'H': + return keyHome, b[3:] + case 'F': + return keyEnd, b[3:] + } + } + + if !pasteActive && len(b) >= 6 && b[0] == keyEscape && b[1] == '[' && b[2] == '1' && b[3] == ';' && b[4] == '3' { + switch b[5] { + case 'C': + return keyAltRight, b[6:] + case 'D': + return keyAltLeft, b[6:] + } + } + + if !pasteActive && len(b) >= 6 && bytes.Equal(b[:6], pasteStart) { + return keyPasteStart, b[6:] + } + + if pasteActive && len(b) >= 6 && bytes.Equal(b[:6], pasteEnd) { + return keyPasteEnd, b[6:] + } + + // If we get here then we have a key that we don't recognise, or a + // partial sequence. It's not clear how one should find the end of a + // sequence without knowing them all, but it seems that [a-zA-Z~] only + // appears at the end of a sequence. + for i, c := range b[0:] { + if c >= 'a' && c <= 'z' || c >= 'A' && c <= 'Z' || c == '~' { + return keyUnknown, b[i+1:] + } + } + + return utf8.RuneError, b +} + +// queue appends data to the end of t.outBuf +func (t *Terminal) queue(data []rune) { + t.outBuf = append(t.outBuf, []byte(string(data))...) +} + +var eraseUnderCursor = []rune{' ', keyEscape, '[', 'D'} +var space = []rune{' '} + +func isPrintable(key rune) bool { + isInSurrogateArea := key >= 0xd800 && key <= 0xdbff + return key >= 32 && !isInSurrogateArea +} + +// moveCursorToPos appends data to t.outBuf which will move the cursor to the +// given, logical position in the text. +func (t *Terminal) moveCursorToPos(pos int) { + if !t.echo { + return + } + + x := visualLength(t.prompt) + pos + y := x / t.termWidth + x = x % t.termWidth + + up := 0 + if y < t.cursorY { + up = t.cursorY - y + } + + down := 0 + if y > t.cursorY { + down = y - t.cursorY + } + + left := 0 + if x < t.cursorX { + left = t.cursorX - x + } + + right := 0 + if x > t.cursorX { + right = x - t.cursorX + } + + t.cursorX = x + t.cursorY = y + t.move(up, down, left, right) +} + +func (t *Terminal) move(up, down, left, right int) { + movement := make([]rune, 3*(up+down+left+right)) + m := movement + for i := 0; i < up; i++ { + m[0] = keyEscape + m[1] = '[' + m[2] = 'A' + m = m[3:] + } + for i := 0; i < down; i++ { + m[0] = keyEscape + m[1] = '[' + m[2] = 'B' + m = m[3:] + } + for i := 0; i < left; i++ { + m[0] = keyEscape + m[1] = '[' + m[2] = 'D' + m = m[3:] + } + for i := 0; i < right; i++ { + m[0] = keyEscape + m[1] = '[' + m[2] = 'C' + m = m[3:] + } + + t.queue(movement) +} + +func (t *Terminal) clearLineToRight() { + op := []rune{keyEscape, '[', 'K'} + t.queue(op) +} + +const maxLineLength = 4096 + +func (t *Terminal) setLine(newLine []rune, newPos int) { + if t.echo { + t.moveCursorToPos(0) + t.writeLine(newLine) + for i := len(newLine); i < len(t.line); i++ { + t.writeLine(space) + } + t.moveCursorToPos(newPos) + } + t.line = newLine + t.pos = newPos +} + +func (t *Terminal) advanceCursor(places int) { + t.cursorX += places + t.cursorY += t.cursorX / t.termWidth + if t.cursorY > t.maxLine { + t.maxLine = t.cursorY + } + t.cursorX = t.cursorX % t.termWidth + + if places > 0 && t.cursorX == 0 { + // Normally terminals will advance the current position + // when writing a character. But that doesn't happen + // for the last character in a line. However, when + // writing a character (except a new line) that causes + // a line wrap, the position will be advanced two + // places. + // + // So, if we are stopping at the end of a line, we + // need to write a newline so that our cursor can be + // advanced to the next line. + t.outBuf = append(t.outBuf, '\r', '\n') + } +} + +func (t *Terminal) eraseNPreviousChars(n int) { + if n == 0 { + return + } + + if t.pos < n { + n = t.pos + } + t.pos -= n + t.moveCursorToPos(t.pos) + + copy(t.line[t.pos:], t.line[n+t.pos:]) + t.line = t.line[:len(t.line)-n] + if t.echo { + t.writeLine(t.line[t.pos:]) + for i := 0; i < n; i++ { + t.queue(space) + } + t.advanceCursor(n) + t.moveCursorToPos(t.pos) + } +} + +// countToLeftWord returns then number of characters from the cursor to the +// start of the previous word. +func (t *Terminal) countToLeftWord() int { + if t.pos == 0 { + return 0 + } + + pos := t.pos - 1 + for pos > 0 { + if t.line[pos] != ' ' { + break + } + pos-- + } + for pos > 0 { + if t.line[pos] == ' ' { + pos++ + break + } + pos-- + } + + return t.pos - pos +} + +// countToRightWord returns then number of characters from the cursor to the +// start of the next word. +func (t *Terminal) countToRightWord() int { + pos := t.pos + for pos < len(t.line) { + if t.line[pos] == ' ' { + break + } + pos++ + } + for pos < len(t.line) { + if t.line[pos] != ' ' { + break + } + pos++ + } + return pos - t.pos +} + +// visualLength returns the number of visible glyphs in s. +func visualLength(runes []rune) int { + inEscapeSeq := false + length := 0 + + for _, r := range runes { + switch { + case inEscapeSeq: + if (r >= 'a' && r <= 'z') || (r >= 'A' && r <= 'Z') { + inEscapeSeq = false + } + case r == '\x1b': + inEscapeSeq = true + default: + length++ + } + } + + return length +} + +// handleKey processes the given key and, optionally, returns a line of text +// that the user has entered. +func (t *Terminal) handleKey(key rune) (line string, ok bool) { + if t.pasteActive && key != keyEnter { + t.addKeyToLine(key) + return + } + + switch key { + case keyBackspace: + if t.pos == 0 { + return + } + t.eraseNPreviousChars(1) + case keyAltLeft: + // move left by a word. + t.pos -= t.countToLeftWord() + t.moveCursorToPos(t.pos) + case keyAltRight: + // move right by a word. + t.pos += t.countToRightWord() + t.moveCursorToPos(t.pos) + case keyLeft: + if t.pos == 0 { + return + } + t.pos-- + t.moveCursorToPos(t.pos) + case keyRight: + if t.pos == len(t.line) { + return + } + t.pos++ + t.moveCursorToPos(t.pos) + case keyHome: + if t.pos == 0 { + return + } + t.pos = 0 + t.moveCursorToPos(t.pos) + case keyEnd: + if t.pos == len(t.line) { + return + } + t.pos = len(t.line) + t.moveCursorToPos(t.pos) + case keyUp: + entry, ok := t.history.NthPreviousEntry(t.historyIndex + 1) + if !ok { + return "", false + } + if t.historyIndex == -1 { + t.historyPending = string(t.line) + } + t.historyIndex++ + runes := []rune(entry) + t.setLine(runes, len(runes)) + case keyDown: + switch t.historyIndex { + case -1: + return + case 0: + runes := []rune(t.historyPending) + t.setLine(runes, len(runes)) + t.historyIndex-- + default: + entry, ok := t.history.NthPreviousEntry(t.historyIndex - 1) + if ok { + t.historyIndex-- + runes := []rune(entry) + t.setLine(runes, len(runes)) + } + } + case keyEnter: + t.moveCursorToPos(len(t.line)) + t.queue([]rune("\r\n")) + line = string(t.line) + ok = true + t.line = t.line[:0] + t.pos = 0 + t.cursorX = 0 + t.cursorY = 0 + t.maxLine = 0 + case keyDeleteWord: + // Delete zero or more spaces and then one or more characters. + t.eraseNPreviousChars(t.countToLeftWord()) + case keyDeleteLine: + // Delete everything from the current cursor position to the + // end of line. + for i := t.pos; i < len(t.line); i++ { + t.queue(space) + t.advanceCursor(1) + } + t.line = t.line[:t.pos] + t.moveCursorToPos(t.pos) + case keyCtrlD: + // Erase the character under the current position. + // The EOF case when the line is empty is handled in + // readLine(). + if t.pos < len(t.line) { + t.pos++ + t.eraseNPreviousChars(1) + } + case keyCtrlU: + t.eraseNPreviousChars(t.pos) + case keyClearScreen: + // Erases the screen and moves the cursor to the home position. + t.queue([]rune("\x1b[2J\x1b[H")) + t.queue(t.prompt) + t.cursorX, t.cursorY = 0, 0 + t.advanceCursor(visualLength(t.prompt)) + t.setLine(t.line, t.pos) + default: + if t.AutoCompleteCallback != nil { + prefix := string(t.line[:t.pos]) + suffix := string(t.line[t.pos:]) + + t.lock.Unlock() + newLine, newPos, completeOk := t.AutoCompleteCallback(prefix+suffix, len(prefix), key) + t.lock.Lock() + + if completeOk { + t.setLine([]rune(newLine), utf8.RuneCount([]byte(newLine)[:newPos])) + return + } + } + if !isPrintable(key) { + return + } + if len(t.line) == maxLineLength { + return + } + t.addKeyToLine(key) + } + return +} + +// addKeyToLine inserts the given key at the current position in the current +// line. +func (t *Terminal) addKeyToLine(key rune) { + if len(t.line) == cap(t.line) { + newLine := make([]rune, len(t.line), 2*(1+len(t.line))) + copy(newLine, t.line) + t.line = newLine + } + t.line = t.line[:len(t.line)+1] + copy(t.line[t.pos+1:], t.line[t.pos:]) + t.line[t.pos] = key + if t.echo { + t.writeLine(t.line[t.pos:]) + } + t.pos++ + t.moveCursorToPos(t.pos) +} + +func (t *Terminal) writeLine(line []rune) { + for len(line) != 0 { + remainingOnLine := t.termWidth - t.cursorX + todo := len(line) + if todo > remainingOnLine { + todo = remainingOnLine + } + t.queue(line[:todo]) + t.advanceCursor(visualLength(line[:todo])) + line = line[todo:] + } +} + +// writeWithCRLF writes buf to w but replaces all occurrences of \n with \r\n. +func writeWithCRLF(w io.Writer, buf []byte) (n int, err error) { + for len(buf) > 0 { + i := bytes.IndexByte(buf, '\n') + todo := len(buf) + if i >= 0 { + todo = i + } + + var nn int + nn, err = w.Write(buf[:todo]) + n += nn + if err != nil { + return n, err + } + buf = buf[todo:] + + if i >= 0 { + if _, err = w.Write(crlf); err != nil { + return n, err + } + n += 1 + buf = buf[1:] + } + } + + return n, nil +} + +func (t *Terminal) Write(buf []byte) (n int, err error) { + t.lock.Lock() + defer t.lock.Unlock() + + if t.cursorX == 0 && t.cursorY == 0 { + // This is the easy case: there's nothing on the screen that we + // have to move out of the way. + return writeWithCRLF(t.c, buf) + } + + // We have a prompt and possibly user input on the screen. We + // have to clear it first. + t.move(0 /* up */, 0 /* down */, t.cursorX /* left */, 0 /* right */) + t.cursorX = 0 + t.clearLineToRight() + + for t.cursorY > 0 { + t.move(1 /* up */, 0, 0, 0) + t.cursorY-- + t.clearLineToRight() + } + + if _, err = t.c.Write(t.outBuf); err != nil { + return + } + t.outBuf = t.outBuf[:0] + + if n, err = writeWithCRLF(t.c, buf); err != nil { + return + } + + t.writeLine(t.prompt) + if t.echo { + t.writeLine(t.line) + } + + t.moveCursorToPos(t.pos) + + if _, err = t.c.Write(t.outBuf); err != nil { + return + } + t.outBuf = t.outBuf[:0] + return +} + +// ReadPassword temporarily changes the prompt and reads a password, without +// echo, from the terminal. +func (t *Terminal) ReadPassword(prompt string) (line string, err error) { + t.lock.Lock() + defer t.lock.Unlock() + + oldPrompt := t.prompt + t.prompt = []rune(prompt) + t.echo = false + + line, err = t.readLine() + + t.prompt = oldPrompt + t.echo = true + + return +} + +// ReadLine returns a line of input from the terminal. +func (t *Terminal) ReadLine() (line string, err error) { + t.lock.Lock() + defer t.lock.Unlock() + + return t.readLine() +} + +func (t *Terminal) readLine() (line string, err error) { + // t.lock must be held at this point + + if t.cursorX == 0 && t.cursorY == 0 { + t.writeLine(t.prompt) + t.c.Write(t.outBuf) + t.outBuf = t.outBuf[:0] + } + + lineIsPasted := t.pasteActive + + for { + rest := t.remainder + lineOk := false + for !lineOk { + var key rune + key, rest = bytesToKey(rest, t.pasteActive) + if key == utf8.RuneError { + break + } + if !t.pasteActive { + if key == keyCtrlD { + if len(t.line) == 0 { + return "", io.EOF + } + } + if key == keyPasteStart { + t.pasteActive = true + if len(t.line) == 0 { + lineIsPasted = true + } + continue + } + } else if key == keyPasteEnd { + t.pasteActive = false + continue + } + if !t.pasteActive { + lineIsPasted = false + } + line, lineOk = t.handleKey(key) + } + if len(rest) > 0 { + n := copy(t.inBuf[:], rest) + t.remainder = t.inBuf[:n] + } else { + t.remainder = nil + } + t.c.Write(t.outBuf) + t.outBuf = t.outBuf[:0] + if lineOk { + if t.echo { + t.historyIndex = -1 + t.history.Add(line) + } + if lineIsPasted { + err = ErrPasteIndicator + } + return + } + + // t.remainder is a slice at the beginning of t.inBuf + // containing a partial key sequence + readBuf := t.inBuf[len(t.remainder):] + var n int + + t.lock.Unlock() + n, err = t.c.Read(readBuf) + t.lock.Lock() + + if err != nil { + return + } + + t.remainder = t.inBuf[:n+len(t.remainder)] + } +} + +// SetPrompt sets the prompt to be used when reading subsequent lines. +func (t *Terminal) SetPrompt(prompt string) { + t.lock.Lock() + defer t.lock.Unlock() + + t.prompt = []rune(prompt) +} + +func (t *Terminal) clearAndRepaintLinePlusNPrevious(numPrevLines int) { + // Move cursor to column zero at the start of the line. + t.move(t.cursorY, 0, t.cursorX, 0) + t.cursorX, t.cursorY = 0, 0 + t.clearLineToRight() + for t.cursorY < numPrevLines { + // Move down a line + t.move(0, 1, 0, 0) + t.cursorY++ + t.clearLineToRight() + } + // Move back to beginning. + t.move(t.cursorY, 0, 0, 0) + t.cursorX, t.cursorY = 0, 0 + + t.queue(t.prompt) + t.advanceCursor(visualLength(t.prompt)) + t.writeLine(t.line) + t.moveCursorToPos(t.pos) +} + +func (t *Terminal) SetSize(width, height int) error { + t.lock.Lock() + defer t.lock.Unlock() + + if width == 0 { + width = 1 + } + + oldWidth := t.termWidth + t.termWidth, t.termHeight = width, height + + switch { + case width == oldWidth: + // If the width didn't change then nothing else needs to be + // done. + return nil + case len(t.line) == 0 && t.cursorX == 0 && t.cursorY == 0: + // If there is nothing on current line and no prompt printed, + // just do nothing + return nil + case width < oldWidth: + // Some terminals (e.g. xterm) will truncate lines that were + // too long when shinking. Others, (e.g. gnome-terminal) will + // attempt to wrap them. For the former, repainting t.maxLine + // works great, but that behaviour goes badly wrong in the case + // of the latter because they have doubled every full line. + + // We assume that we are working on a terminal that wraps lines + // and adjust the cursor position based on every previous line + // wrapping and turning into two. This causes the prompt on + // xterms to move upwards, which isn't great, but it avoids a + // huge mess with gnome-terminal. + if t.cursorX >= t.termWidth { + t.cursorX = t.termWidth - 1 + } + t.cursorY *= 2 + t.clearAndRepaintLinePlusNPrevious(t.maxLine * 2) + case width > oldWidth: + // If the terminal expands then our position calculations will + // be wrong in the future because we think the cursor is + // |t.pos| chars into the string, but there will be a gap at + // the end of any wrapped line. + // + // But the position will actually be correct until we move, so + // we can move back to the beginning and repaint everything. + t.clearAndRepaintLinePlusNPrevious(t.maxLine) + } + + _, err := t.c.Write(t.outBuf) + t.outBuf = t.outBuf[:0] + return err +} + +type pasteIndicatorError struct{} + +func (pasteIndicatorError) Error() string { + return "terminal: ErrPasteIndicator not correctly handled" +} + +// ErrPasteIndicator may be returned from ReadLine as the error, in addition +// to valid line data. It indicates that bracketed paste mode is enabled and +// that the returned line consists only of pasted data. Programs may wish to +// interpret pasted data more literally than typed data. +var ErrPasteIndicator = pasteIndicatorError{} + +// SetBracketedPasteMode requests that the terminal bracket paste operations +// with markers. Not all terminals support this but, if it is supported, then +// enabling this mode will stop any autocomplete callback from running due to +// pastes. Additionally, any lines that are completely pasted will be returned +// from ReadLine with the error set to ErrPasteIndicator. +func (t *Terminal) SetBracketedPasteMode(on bool) { + if on { + io.WriteString(t.c, "\x1b[?2004h") + } else { + io.WriteString(t.c, "\x1b[?2004l") + } +} + +// stRingBuffer is a ring buffer of strings. +type stRingBuffer struct { + // entries contains max elements. + entries []string + max int + // head contains the index of the element most recently added to the ring. + head int + // size contains the number of elements in the ring. + size int +} + +func (s *stRingBuffer) Add(a string) { + if s.entries == nil { + const defaultNumEntries = 100 + s.entries = make([]string, defaultNumEntries) + s.max = defaultNumEntries + } + + s.head = (s.head + 1) % s.max + s.entries[s.head] = a + if s.size < s.max { + s.size++ + } +} + +// NthPreviousEntry returns the value passed to the nth previous call to Add. +// If n is zero then the immediately prior value is returned, if one, then the +// next most recent, and so on. If such an element doesn't exist then ok is +// false. +func (s *stRingBuffer) NthPreviousEntry(n int) (value string, ok bool) { + if n >= s.size { + return "", false + } + index := s.head - n + if index < 0 { + index += s.max + } + return s.entries[index], true +} + +// readPasswordLine reads from reader until it finds \n or io.EOF. +// The slice returned does not include the \n. +// readPasswordLine also ignores any \r it finds. +func readPasswordLine(reader io.Reader) ([]byte, error) { + var buf [1]byte + var ret []byte + + for { + n, err := reader.Read(buf[:]) + if n > 0 { + switch buf[0] { + case '\n': + return ret, nil + case '\r': + // remove \r from passwords on Windows + default: + ret = append(ret, buf[0]) + } + continue + } + if err != nil { + if err == io.EOF && len(ret) > 0 { + return ret, nil + } + return ret, err + } + } +} diff --git a/vendor/golang.org/x/crypto/ssh/terminal/util.go b/vendor/golang.org/x/crypto/ssh/terminal/util.go new file mode 100644 index 000000000..d01919614 --- /dev/null +++ b/vendor/golang.org/x/crypto/ssh/terminal/util.go @@ -0,0 +1,119 @@ +// Copyright 2011 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// +build darwin dragonfly freebsd linux,!appengine netbsd openbsd + +// Package terminal provides support functions for dealing with terminals, as +// commonly found on UNIX systems. +// +// Putting a terminal into raw mode is the most common requirement: +// +// oldState, err := terminal.MakeRaw(0) +// if err != nil { +// panic(err) +// } +// defer terminal.Restore(0, oldState) +package terminal // import "golang.org/x/crypto/ssh/terminal" + +import ( + "syscall" + "unsafe" +) + +// State contains the state of a terminal. +type State struct { + termios syscall.Termios +} + +// IsTerminal returns true if the given file descriptor is a terminal. +func IsTerminal(fd int) bool { + var termios syscall.Termios + _, _, err := syscall.Syscall6(syscall.SYS_IOCTL, uintptr(fd), ioctlReadTermios, uintptr(unsafe.Pointer(&termios)), 0, 0, 0) + return err == 0 +} + +// MakeRaw put the terminal connected to the given file descriptor into raw +// mode and returns the previous state of the terminal so that it can be +// restored. +func MakeRaw(fd int) (*State, error) { + var oldState State + if _, _, err := syscall.Syscall6(syscall.SYS_IOCTL, uintptr(fd), ioctlReadTermios, uintptr(unsafe.Pointer(&oldState.termios)), 0, 0, 0); err != 0 { + return nil, err + } + + newState := oldState.termios + // This attempts to replicate the behaviour documented for cfmakeraw in + // the termios(3) manpage. + newState.Iflag &^= syscall.IGNBRK | syscall.BRKINT | syscall.PARMRK | syscall.ISTRIP | syscall.INLCR | syscall.IGNCR | syscall.ICRNL | syscall.IXON + newState.Oflag &^= syscall.OPOST + newState.Lflag &^= syscall.ECHO | syscall.ECHONL | syscall.ICANON | syscall.ISIG | syscall.IEXTEN + newState.Cflag &^= syscall.CSIZE | syscall.PARENB + newState.Cflag |= syscall.CS8 + if _, _, err := syscall.Syscall6(syscall.SYS_IOCTL, uintptr(fd), ioctlWriteTermios, uintptr(unsafe.Pointer(&newState)), 0, 0, 0); err != 0 { + return nil, err + } + + return &oldState, nil +} + +// GetState returns the current state of a terminal which may be useful to +// restore the terminal after a signal. +func GetState(fd int) (*State, error) { + var oldState State + if _, _, err := syscall.Syscall6(syscall.SYS_IOCTL, uintptr(fd), ioctlReadTermios, uintptr(unsafe.Pointer(&oldState.termios)), 0, 0, 0); err != 0 { + return nil, err + } + + return &oldState, nil +} + +// Restore restores the terminal connected to the given file descriptor to a +// previous state. +func Restore(fd int, state *State) error { + if _, _, err := syscall.Syscall6(syscall.SYS_IOCTL, uintptr(fd), ioctlWriteTermios, uintptr(unsafe.Pointer(&state.termios)), 0, 0, 0); err != 0 { + return err + } + return nil +} + +// GetSize returns the dimensions of the given terminal. +func GetSize(fd int) (width, height int, err error) { + var dimensions [4]uint16 + + if _, _, err := syscall.Syscall6(syscall.SYS_IOCTL, uintptr(fd), uintptr(syscall.TIOCGWINSZ), uintptr(unsafe.Pointer(&dimensions)), 0, 0, 0); err != 0 { + return -1, -1, err + } + return int(dimensions[1]), int(dimensions[0]), nil +} + +// passwordReader is an io.Reader that reads from a specific file descriptor. +type passwordReader int + +func (r passwordReader) Read(buf []byte) (int, error) { + return syscall.Read(int(r), buf) +} + +// ReadPassword reads a line of input from a terminal without local echo. This +// is commonly used for inputting passwords and other sensitive data. The slice +// returned does not include the \n. +func ReadPassword(fd int) ([]byte, error) { + var oldState syscall.Termios + if _, _, err := syscall.Syscall6(syscall.SYS_IOCTL, uintptr(fd), ioctlReadTermios, uintptr(unsafe.Pointer(&oldState)), 0, 0, 0); err != 0 { + return nil, err + } + + newState := oldState + newState.Lflag &^= syscall.ECHO + newState.Lflag |= syscall.ICANON | syscall.ISIG + newState.Iflag |= syscall.ICRNL + if _, _, err := syscall.Syscall6(syscall.SYS_IOCTL, uintptr(fd), ioctlWriteTermios, uintptr(unsafe.Pointer(&newState)), 0, 0, 0); err != 0 { + return nil, err + } + + defer func() { + syscall.Syscall6(syscall.SYS_IOCTL, uintptr(fd), ioctlWriteTermios, uintptr(unsafe.Pointer(&oldState)), 0, 0, 0) + }() + + return readPasswordLine(passwordReader(fd)) +} diff --git a/vendor/golang.org/x/crypto/ssh/terminal/util_bsd.go b/vendor/golang.org/x/crypto/ssh/terminal/util_bsd.go new file mode 100644 index 000000000..9c1ffd145 --- /dev/null +++ b/vendor/golang.org/x/crypto/ssh/terminal/util_bsd.go @@ -0,0 +1,12 @@ +// Copyright 2013 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// +build darwin dragonfly freebsd netbsd openbsd + +package terminal + +import "syscall" + +const ioctlReadTermios = syscall.TIOCGETA +const ioctlWriteTermios = syscall.TIOCSETA diff --git a/vendor/golang.org/x/crypto/ssh/terminal/util_linux.go b/vendor/golang.org/x/crypto/ssh/terminal/util_linux.go new file mode 100644 index 000000000..5883b22d7 --- /dev/null +++ b/vendor/golang.org/x/crypto/ssh/terminal/util_linux.go @@ -0,0 +1,11 @@ +// Copyright 2013 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package terminal + +// These constants are declared here, rather than importing +// them from the syscall package as some syscall packages, even +// on linux, for example gccgo, do not declare them. +const ioctlReadTermios = 0x5401 // syscall.TCGETS +const ioctlWriteTermios = 0x5402 // syscall.TCSETS diff --git a/vendor/golang.org/x/crypto/ssh/terminal/util_plan9.go b/vendor/golang.org/x/crypto/ssh/terminal/util_plan9.go new file mode 100644 index 000000000..799f049f0 --- /dev/null +++ b/vendor/golang.org/x/crypto/ssh/terminal/util_plan9.go @@ -0,0 +1,58 @@ +// Copyright 2016 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package terminal provides support functions for dealing with terminals, as +// commonly found on UNIX systems. +// +// Putting a terminal into raw mode is the most common requirement: +// +// oldState, err := terminal.MakeRaw(0) +// if err != nil { +// panic(err) +// } +// defer terminal.Restore(0, oldState) +package terminal + +import ( + "fmt" + "runtime" +) + +type State struct{} + +// IsTerminal returns true if the given file descriptor is a terminal. +func IsTerminal(fd int) bool { + return false +} + +// MakeRaw put the terminal connected to the given file descriptor into raw +// mode and returns the previous state of the terminal so that it can be +// restored. +func MakeRaw(fd int) (*State, error) { + return nil, fmt.Errorf("terminal: MakeRaw not implemented on %s/%s", runtime.GOOS, runtime.GOARCH) +} + +// GetState returns the current state of a terminal which may be useful to +// restore the terminal after a signal. +func GetState(fd int) (*State, error) { + return nil, fmt.Errorf("terminal: GetState not implemented on %s/%s", runtime.GOOS, runtime.GOARCH) +} + +// Restore restores the terminal connected to the given file descriptor to a +// previous state. +func Restore(fd int, state *State) error { + return fmt.Errorf("terminal: Restore not implemented on %s/%s", runtime.GOOS, runtime.GOARCH) +} + +// GetSize returns the dimensions of the given terminal. +func GetSize(fd int) (width, height int, err error) { + return 0, 0, fmt.Errorf("terminal: GetSize not implemented on %s/%s", runtime.GOOS, runtime.GOARCH) +} + +// ReadPassword reads a line of input from a terminal without local echo. This +// is commonly used for inputting passwords and other sensitive data. The slice +// returned does not include the \n. +func ReadPassword(fd int) ([]byte, error) { + return nil, fmt.Errorf("terminal: ReadPassword not implemented on %s/%s", runtime.GOOS, runtime.GOARCH) +} diff --git a/vendor/golang.org/x/crypto/ssh/terminal/util_solaris.go b/vendor/golang.org/x/crypto/ssh/terminal/util_solaris.go new file mode 100644 index 000000000..a2e1b57dc --- /dev/null +++ b/vendor/golang.org/x/crypto/ssh/terminal/util_solaris.go @@ -0,0 +1,128 @@ +// Copyright 2015 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// +build solaris + +package terminal // import "golang.org/x/crypto/ssh/terminal" + +import ( + "golang.org/x/sys/unix" + "io" + "syscall" +) + +// State contains the state of a terminal. +type State struct { + state *unix.Termios +} + +// IsTerminal returns true if the given file descriptor is a terminal. +func IsTerminal(fd int) bool { + _, err := unix.IoctlGetTermio(fd, unix.TCGETA) + return err == nil +} + +// ReadPassword reads a line of input from a terminal without local echo. This +// is commonly used for inputting passwords and other sensitive data. The slice +// returned does not include the \n. +func ReadPassword(fd int) ([]byte, error) { + // see also: http://src.illumos.org/source/xref/illumos-gate/usr/src/lib/libast/common/uwin/getpass.c + val, err := unix.IoctlGetTermios(fd, unix.TCGETS) + if err != nil { + return nil, err + } + oldState := *val + + newState := oldState + newState.Lflag &^= syscall.ECHO + newState.Lflag |= syscall.ICANON | syscall.ISIG + newState.Iflag |= syscall.ICRNL + err = unix.IoctlSetTermios(fd, unix.TCSETS, &newState) + if err != nil { + return nil, err + } + + defer unix.IoctlSetTermios(fd, unix.TCSETS, &oldState) + + var buf [16]byte + var ret []byte + for { + n, err := syscall.Read(fd, buf[:]) + if err != nil { + return nil, err + } + if n == 0 { + if len(ret) == 0 { + return nil, io.EOF + } + break + } + if buf[n-1] == '\n' { + n-- + } + ret = append(ret, buf[:n]...) + if n < len(buf) { + break + } + } + + return ret, nil +} + +// MakeRaw puts the terminal connected to the given file descriptor into raw +// mode and returns the previous state of the terminal so that it can be +// restored. +// see http://cr.illumos.org/~webrev/andy_js/1060/ +func MakeRaw(fd int) (*State, error) { + oldTermiosPtr, err := unix.IoctlGetTermios(fd, unix.TCGETS) + if err != nil { + return nil, err + } + oldTermios := *oldTermiosPtr + + newTermios := oldTermios + newTermios.Iflag &^= syscall.IGNBRK | syscall.BRKINT | syscall.PARMRK | syscall.ISTRIP | syscall.INLCR | syscall.IGNCR | syscall.ICRNL | syscall.IXON + newTermios.Oflag &^= syscall.OPOST + newTermios.Lflag &^= syscall.ECHO | syscall.ECHONL | syscall.ICANON | syscall.ISIG | syscall.IEXTEN + newTermios.Cflag &^= syscall.CSIZE | syscall.PARENB + newTermios.Cflag |= syscall.CS8 + newTermios.Cc[unix.VMIN] = 1 + newTermios.Cc[unix.VTIME] = 0 + + if err := unix.IoctlSetTermios(fd, unix.TCSETS, &newTermios); err != nil { + return nil, err + } + + return &State{ + state: oldTermiosPtr, + }, nil +} + +// Restore restores the terminal connected to the given file descriptor to a +// previous state. +func Restore(fd int, oldState *State) error { + return unix.IoctlSetTermios(fd, unix.TCSETS, oldState.state) +} + +// GetState returns the current state of a terminal which may be useful to +// restore the terminal after a signal. +func GetState(fd int) (*State, error) { + oldTermiosPtr, err := unix.IoctlGetTermios(fd, unix.TCGETS) + if err != nil { + return nil, err + } + + return &State{ + state: oldTermiosPtr, + }, nil +} + +// GetSize returns the dimensions of the given terminal. +func GetSize(fd int) (width, height int, err error) { + ws, err := unix.IoctlGetWinsize(fd, unix.TIOCGWINSZ) + if err != nil { + return 0, 0, err + } + return int(ws.Col), int(ws.Row), nil +} diff --git a/vendor/golang.org/x/crypto/ssh/terminal/util_windows.go b/vendor/golang.org/x/crypto/ssh/terminal/util_windows.go new file mode 100644 index 000000000..e0a1f36ce --- /dev/null +++ b/vendor/golang.org/x/crypto/ssh/terminal/util_windows.go @@ -0,0 +1,155 @@ +// Copyright 2011 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// +build windows + +// Package terminal provides support functions for dealing with terminals, as +// commonly found on UNIX systems. +// +// Putting a terminal into raw mode is the most common requirement: +// +// oldState, err := terminal.MakeRaw(0) +// if err != nil { +// panic(err) +// } +// defer terminal.Restore(0, oldState) +package terminal + +import ( + "syscall" + "unsafe" +) + +const ( + enableLineInput = 2 + enableEchoInput = 4 + enableProcessedInput = 1 + enableWindowInput = 8 + enableMouseInput = 16 + enableInsertMode = 32 + enableQuickEditMode = 64 + enableExtendedFlags = 128 + enableAutoPosition = 256 + enableProcessedOutput = 1 + enableWrapAtEolOutput = 2 +) + +var kernel32 = syscall.NewLazyDLL("kernel32.dll") + +var ( + procGetConsoleMode = kernel32.NewProc("GetConsoleMode") + procSetConsoleMode = kernel32.NewProc("SetConsoleMode") + procGetConsoleScreenBufferInfo = kernel32.NewProc("GetConsoleScreenBufferInfo") +) + +type ( + short int16 + word uint16 + + coord struct { + x short + y short + } + smallRect struct { + left short + top short + right short + bottom short + } + consoleScreenBufferInfo struct { + size coord + cursorPosition coord + attributes word + window smallRect + maximumWindowSize coord + } +) + +type State struct { + mode uint32 +} + +// IsTerminal returns true if the given file descriptor is a terminal. +func IsTerminal(fd int) bool { + var st uint32 + r, _, e := syscall.Syscall(procGetConsoleMode.Addr(), 2, uintptr(fd), uintptr(unsafe.Pointer(&st)), 0) + return r != 0 && e == 0 +} + +// MakeRaw put the terminal connected to the given file descriptor into raw +// mode and returns the previous state of the terminal so that it can be +// restored. +func MakeRaw(fd int) (*State, error) { + var st uint32 + _, _, e := syscall.Syscall(procGetConsoleMode.Addr(), 2, uintptr(fd), uintptr(unsafe.Pointer(&st)), 0) + if e != 0 { + return nil, error(e) + } + raw := st &^ (enableEchoInput | enableProcessedInput | enableLineInput | enableProcessedOutput) + _, _, e = syscall.Syscall(procSetConsoleMode.Addr(), 2, uintptr(fd), uintptr(raw), 0) + if e != 0 { + return nil, error(e) + } + return &State{st}, nil +} + +// GetState returns the current state of a terminal which may be useful to +// restore the terminal after a signal. +func GetState(fd int) (*State, error) { + var st uint32 + _, _, e := syscall.Syscall(procGetConsoleMode.Addr(), 2, uintptr(fd), uintptr(unsafe.Pointer(&st)), 0) + if e != 0 { + return nil, error(e) + } + return &State{st}, nil +} + +// Restore restores the terminal connected to the given file descriptor to a +// previous state. +func Restore(fd int, state *State) error { + _, _, err := syscall.Syscall(procSetConsoleMode.Addr(), 2, uintptr(fd), uintptr(state.mode), 0) + return err +} + +// GetSize returns the dimensions of the given terminal. +func GetSize(fd int) (width, height int, err error) { + var info consoleScreenBufferInfo + _, _, e := syscall.Syscall(procGetConsoleScreenBufferInfo.Addr(), 2, uintptr(fd), uintptr(unsafe.Pointer(&info)), 0) + if e != 0 { + return 0, 0, error(e) + } + return int(info.size.x), int(info.size.y), nil +} + +// passwordReader is an io.Reader that reads from a specific Windows HANDLE. +type passwordReader int + +func (r passwordReader) Read(buf []byte) (int, error) { + return syscall.Read(syscall.Handle(r), buf) +} + +// ReadPassword reads a line of input from a terminal without local echo. This +// is commonly used for inputting passwords and other sensitive data. The slice +// returned does not include the \n. +func ReadPassword(fd int) ([]byte, error) { + var st uint32 + _, _, e := syscall.Syscall(procGetConsoleMode.Addr(), 2, uintptr(fd), uintptr(unsafe.Pointer(&st)), 0) + if e != 0 { + return nil, error(e) + } + old := st + + st &^= (enableEchoInput) + st |= (enableProcessedInput | enableLineInput | enableProcessedOutput) + _, _, e = syscall.Syscall(procSetConsoleMode.Addr(), 2, uintptr(fd), uintptr(st), 0) + if e != 0 { + return nil, error(e) + } + + defer func() { + syscall.Syscall(procSetConsoleMode.Addr(), 2, uintptr(fd), uintptr(old), 0) + }() + + return readPasswordLine(passwordReader(fd)) +} diff --git a/vendor/vendor.json b/vendor/vendor.json index 7d829aecc..b4e436f9d 100644 --- a/vendor/vendor.json +++ b/vendor/vendor.json @@ -255,6 +255,30 @@ "revision": "7e3c02b30806fa5779d3bdfc152ce4c6f40e7b38", "revisionTime": "2016-01-19T13:13:26-08:00" }, + { + "checksumSHA1": "K6exl2ouL7d8cR2i378EzZOdRVI=", + "path": "github.com/howeyc/gopass", + "revision": "bf9dde6d0d2c004a008c27aaee91170c786f6db8", + "revisionTime": "2017-01-09T16:22:49Z" + }, + { + "checksumSHA1": "YjZiQ5N552dJ7GpJ2P4TTRu4PKI=", + "path": "github.com/inconshreveable/go-update", + "revision": "8152e7eb6ccf8679a64582a66b78519688d156ad", + "revisionTime": "2016-01-12T19:33:35Z" + }, + { + "checksumSHA1": "LeFW+ad/zlj30+Z0O4wwnXrVUSQ=", + "path": "github.com/inconshreveable/go-update/internal/binarydist", + "revision": "8152e7eb6ccf8679a64582a66b78519688d156ad", + "revisionTime": "2016-01-12T19:33:35Z" + }, + { + "checksumSHA1": "H63+DW/S2nASpHk59G1wEUZD1qk=", + "path": "github.com/inconshreveable/go-update/internal/osext", + "revision": "8152e7eb6ccf8679a64582a66b78519688d156ad", + "revisionTime": "2016-01-12T19:33:35Z" + }, { "path": "github.com/klauspost/cpuid", "revision": "349c675778172472f5e8f3a3e0fe187e302e5a10", @@ -462,7 +486,13 @@ "checksumSHA1": "iqUXcP3VA+G1/gVLRpQpBUt/BuA=", "path": "github.com/satori/uuid", "revision": "5bf94b69c6b68ee1b541973bb8e1144db23a194b", - "revisionTime": "2017-03-21T23:07:31Z" + "revisionTime": "2017-03-21T23:07:31Z" + }, + { + "checksumSHA1": "llmzhtIUy63V3Pl65RuEn18ck5g=", + "path": "github.com/segmentio/go-prompt", + "revision": "f0d19b6901ade831d5a3204edc0d6a7d6457fbb2", + "revisionTime": "2016-10-17T23:32:05Z" }, { "checksumSHA1": "u0hXGADM3JDza8YjgiyNJpAJk8g=",