diff --git a/Godeps/Godeps.json b/Godeps/Godeps.json index e01712aa6..5bf8e5242 100644 --- a/Godeps/Godeps.json +++ b/Godeps/Godeps.json @@ -1,110 +1,153 @@ { - "ImportPath": "github.com/arduino/arduino-create-agent", - "GoVersion": "go1.4.2", + "ImportPath": ".", + "GoVersion": "go1.4", "Deps": [ + { + "ImportPath": "github.com/Sirupsen/logrus", + "Comment": "v0.8.7-53-g446d1c1", + "Rev": "446d1c146faa8ed3f4218f056fcd165f6bcfda81" + }, + { + "ImportPath": "github.com/carlescere/scheduler", + "Comment": "0.1-13-g025e9f5", + "Rev": "025e9f58462112b9fb636708374467aef7161289" + }, { "ImportPath": "github.com/facchinm/go-serial", "Rev": "3cfbd2fab741f7c9d238cb3bd17980110c81bd46" }, + { + "ImportPath": "github.com/facchinm/go-serial-native", + "Rev": "6c62fb894d873784830ec370e85ab1f29c2e0324" + }, { "ImportPath": "github.com/facchinm/systray", "Rev": "b32308b0b662e45cde7c5986bc29def5d48bafbb" }, + { + "ImportPath": "github.com/getlantern/filepersist", + "Rev": "e91ecdee50cac4a38975041089abdddcb94c4e97" + }, { "ImportPath": "github.com/getlantern/golog", "Rev": "5245e761fd1c8e9ccba87de802a0f666ed55980f" }, { "ImportPath": "github.com/gin-gonic/gin", - "Comment": "v1.0rc1-128-gfc5e355", - "Rev": "fc5e3557242901e24016d4474168515434871dd5" + "Comment": "v1.0rc1-148-g52fcc5d", + "Rev": "52fcc5dbf6e94df33ad313858fb94b713e9d1b4a" }, { "ImportPath": "github.com/googollee/go-engine.io", - "Rev": "597faf3df88a01780a5881fafbcbccd9d5c3fab0" + "Rev": "a66cf68b24e56df0cacdc6041b93742f983a296a" }, { "ImportPath": "github.com/googollee/go-socket.io", - "Comment": "0.9.x-33-g9c83c02", - "Rev": "9c83c02fa6172eaea8252b6428e3cef3576c9746" + "Comment": "0.9.x-51-g14bb3d0", + "Rev": "14bb3d0368089a74cb52164a7a697cf404bda9c6" }, { "ImportPath": "github.com/gorilla/websocket", - "Rev": "a3ec486e6a7a41858210b0fc5d7b5df593b3c4a3" - }, - { - "ImportPath": "github.com/inconshreveable/go-update", - "Rev": "68f5725818189545231c1fd8694793d45f2fc529" + "Rev": "2dbddebb8266b93c5e6b119efb54e89043186f3f" }, { "ImportPath": "github.com/itsjamie/gin-cors", - "Rev": "bd9551838cd52133960a2f44ab990be71744b663" + "Rev": "9526714e382db4023de25bfdad89cec9b7446092" }, { "ImportPath": "github.com/kardianos/osext", "Rev": "6e7f843663477789fac7c02def0d0909e969b4e5" }, - { - "ImportPath": "github.com/kardianos/service", - "Rev": "1ab12303aaf4cf68a53a5717a31b5acd72c57019" - }, { "ImportPath": "github.com/kr/binarydist", "Rev": "9955b0ab8708602d411341e55fffd7e0700f86bd" }, + { + "ImportPath": "github.com/kr/fs", + "Rev": "2788f0dbd16903de03cb8186e5c7d97b69ad387b" + }, { "ImportPath": "github.com/manucorporat/sse", - "Rev": "c142f0f1baea5cef7f98a8a6c222f6134368c1f5" + "Rev": "fe6ea2c8e398672518ef204bf0fbd9af858d0e15" }, { "ImportPath": "github.com/mattn/go-colorable", - "Rev": "40e4aedc8fabf8c23e040057540867186712faa5" + "Rev": "3dac7b4f76f6e17fb39b768b89e3783d16e237fe" + }, + { + "ImportPath": "github.com/mattn/go-isatty", + "Rev": "56b76bdf51f7708750eac80fa38b952bb9f32639" + }, + { + "ImportPath": "github.com/mattn/go-shellwords", + "Comment": "v1.0.0", + "Rev": "f4e566c536cf69158e808ec28ef4182a37fdc981" }, { "ImportPath": "github.com/miekg/dns", - "Rev": "7864d445e5087e8d761dbefec43f29b92f7650eb" + "Rev": "75e6e86cc601825c5dbcd4e0c209eab180997cd7" }, { "ImportPath": "github.com/oleksandr/bonjour", - "Rev": "415cafc6ab8894ea7b7bcbed933ce2d8c611683c" + "Rev": "7ee314fe2bb898db0072d9af4b540b816fe7c6b5" + }, + { + "ImportPath": "github.com/pivotal-golang/archiver/extractor", + "Rev": "f598990ddfeda2767a09346acf844a7d216c1256" }, { - "ImportPath": "github.com/sanderhahn/gozip/patchzip", - "Rev": "85893825032e0136f506cc9e3f36d42d56305ee9" + "ImportPath": "github.com/pkg/sftp", + "Rev": "2bc00847a6f77f5d32fa46b0ab829f0531d73244" + }, + { + "ImportPath": "github.com/sfreiberg/simplessh", + "Rev": "e2df5a8be6f36be66ed1af2e8767a354788e902b" }, { "ImportPath": "github.com/skratchdot/open-golang/open", "Rev": "c8748311a7528d0ba7330d302adbc5a677ef9c9e" }, { - "ImportPath": "github.com/termie/go-shutil", - "Rev": "bcacb06fecaeec8dc42af03c87c6949f4a05c74c" + "ImportPath": "github.com/vharitonsky/iniflags", + "Comment": "v.1.0-28-g02b57ef", + "Rev": "02b57ef987a5ee59eedc7dcd93315a43f6dcc4a7" }, { - "ImportPath": "github.com/vharitonsky/iniflags", - "Comment": "v.1.0-21-g14f4b95", - "Rev": "14f4b95d09b99e2475e82c37a2f943ef2469a890" + "ImportPath": "github.com/xrash/smetrics", + "Rev": "81a89232431423f9140fa413823ce3045f02f19c" }, { - "ImportPath": "golang.org/x/net/context", - "Rev": "d9558e5c97f85372afee28cf2b6059d7d3818919" + "ImportPath": "golang.org/x/crypto/curve25519", + "Rev": "f18420efc3b4f8e9f3d51f6bd2476e92c46260e9" + }, + { + "ImportPath": "golang.org/x/crypto/ssh", + "Rev": "f18420efc3b4f8e9f3d51f6bd2476e92c46260e9" }, { - "ImportPath": "golang.org/x/net/internal/iana", - "Rev": "d9558e5c97f85372afee28cf2b6059d7d3818919" + "ImportPath": "golang.org/x/net/context", + "Rev": "84ba27dd5b2d8135e9da1395277f2c9333a2ffda" }, { "ImportPath": "golang.org/x/net/ipv4", - "Rev": "d9558e5c97f85372afee28cf2b6059d7d3818919" + "Rev": "84ba27dd5b2d8135e9da1395277f2c9333a2ffda" }, { "ImportPath": "golang.org/x/net/ipv6", - "Rev": "d9558e5c97f85372afee28cf2b6059d7d3818919" + "Rev": "84ba27dd5b2d8135e9da1395277f2c9333a2ffda" + }, + { + "ImportPath": "golang.org/x/sys/unix", + "Rev": "833a04a10549a95dc34458c195cbad61bbb6cb4d" }, { "ImportPath": "gopkg.in/bluesuncorp/validator.v5", - "Comment": "v5.10", - "Rev": "d2748d5ae5e5af5ec3a85e3a59bf0db9d741c82d" + "Comment": "v5.12", + "Rev": "d5acf1dac43705f8bfbb71d878e290e2bed3950b" + }, + { + "ImportPath": "gopkg.in/inconshreveable/go-update.v0", + "Rev": "d8b0b1d421aa1cbf392c05869f8abbc669bb7066" } ] } diff --git a/README.md b/README.md index 1c8921a25..9ca35fcee 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,6 @@ arduino-create-agent ==================== -Version X.XX - Get the latest version of the Agent for all supported platforms: * [Windows](http://downloads.arduino.cc/CreateBridgeStable/ArduinoCreateAgent-1.1-windows-installer.exe) @@ -21,6 +19,16 @@ Please use the current latest version: * [MacOSX dev](http://downloads.arduino.cc/CreateBridge/staging/ArduinoCreateAgent-1.0-osx-installer.dmg) * [Linux x64 dev](http://downloads.arduino.cc/CreateBridge/staging/ArduinoCreateAgent-1.0-linux-x64-installer.run) +## Compiling + +`compile_webidebridge.sh` contains the cross-platform script we use to deploy the agent for all the supported platforms; it needs to be adjusted as per your `go` installation paths and OS. + +You can use `bootstrapPlatforms` function to compile the needed CGO-enabled environment + +Other prerequisites are: +* libappindicator (Linux only) +* [go-selfupdate] (https://github.com/sanbornm/go-selfupdate) if you want to test automatic updates + ## Submitting an issue Please attach the output of the commands running at the debug console if useful. diff --git a/certificates.go b/certificates.go new file mode 100644 index 000000000..bfa4d11ac --- /dev/null +++ b/certificates.go @@ -0,0 +1,332 @@ +// Copyright 2009 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. + +// Generate a self-signed X.509 certificate for a TLS server. Outputs to +// 'cert.pem' and 'key.pem' and will overwrite existing files. + +package main + +import ( + "crypto/ecdsa" + "crypto/elliptic" + "crypto/rand" + "crypto/rsa" + "crypto/x509" + "crypto/x509/pkix" + "encoding/pem" + "fmt" + "io/ioutil" + "math/big" + "net" + "os" + "strings" + "text/template" + "time" + + log "github.com/Sirupsen/logrus" + "github.com/gin-gonic/gin" +) + +var ( + host = "localhost" + validFrom = "" + validFor = 365 * 24 * time.Hour * 2 // 2 years + rsaBits = 2048 +) + +func publicKey(priv interface{}) interface{} { + switch k := priv.(type) { + case *rsa.PrivateKey: + return &k.PublicKey + case *ecdsa.PrivateKey: + return &k.PublicKey + default: + return nil + } +} + +func pemBlockForKey(priv interface{}) *pem.Block { + switch k := priv.(type) { + case *rsa.PrivateKey: + return &pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(k)} + case *ecdsa.PrivateKey: + b, err := x509.MarshalECPrivateKey(k) + if err != nil { + fmt.Fprintf(os.Stderr, "Unable to marshal ECDSA private key: %v", err) + os.Exit(2) + } + return &pem.Block{Type: "EC PRIVATE KEY", Bytes: b} + default: + return nil + } +} + +func generateKey(ecdsaCurve string) (interface{}, error) { + switch ecdsaCurve { + case "": + return rsa.GenerateKey(rand.Reader, rsaBits) + case "P224": + return ecdsa.GenerateKey(elliptic.P224(), rand.Reader) + case "P256": + return ecdsa.GenerateKey(elliptic.P256(), rand.Reader) + case "P384": + return ecdsa.GenerateKey(elliptic.P384(), rand.Reader) + case "P521": + return ecdsa.GenerateKey(elliptic.P521(), rand.Reader) + default: + return nil, fmt.Errorf("Unrecognized elliptic curve: %q", ecdsaCurve) + } +} + +func generateSingleCertificate(isCa bool) (*x509.Certificate, error) { + var notBefore time.Time + var err error + if len(validFrom) == 0 { + notBefore = time.Now() + } else { + notBefore, err = time.Parse("Jan 2 15:04:05 2006", validFrom) + if err != nil { + return nil, fmt.Errorf("Failed to parse creation date: %s\n", err.Error()) + } + } + + notAfter := notBefore.Add(validFor) + + serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128) + serialNumber, err := rand.Int(rand.Reader, serialNumberLimit) + if err != nil { + return nil, fmt.Errorf("failed to generate serial number: %s\n", err.Error()) + } + + template := x509.Certificate{ + SerialNumber: serialNumber, + Subject: pkix.Name{ + Organization: []string{"Arduino LLC US"}, + Country: []string{"US"}, + CommonName: "localhost", + OrganizationalUnit: []string{"IT"}, + }, + NotBefore: notBefore, + NotAfter: notAfter, + + KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature, + ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth, x509.ExtKeyUsageServerAuth}, + BasicConstraintsValid: true, + } + + hosts := strings.Split(host, ",") + for _, h := range hosts { + if ip := net.ParseIP(h); ip != nil { + template.IPAddresses = append(template.IPAddresses, ip) + } else { + template.DNSNames = append(template.DNSNames, h) + } + } + + if isCa { + template.IsCA = true + template.KeyUsage |= x509.KeyUsageCertSign + template.Subject.CommonName = "Arduino" + } + + return &template, nil +} + +func generateCertificates() { + + os.Remove("ca.cert.pem") + os.Remove("ca.key.pem") + os.Remove("cert.pem") + os.Remove("key.pem") + + // Create the key for the certification authority + caKey, err := generateKey("P256") + if err != nil { + log.Error(err.Error()) + os.Exit(1) + } + + keyOut, err := os.OpenFile("ca.key.pem", os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600) + if err != nil { + log.Error(err.Error()) + os.Exit(1) + } + pem.Encode(keyOut, pemBlockForKey(caKey)) + keyOut.Close() + log.Println("written ca.key.pem") + + // Create the certification authority + caTemplate, err := generateSingleCertificate(true) + + if err != nil { + log.Error(err.Error()) + os.Exit(1) + } + + derBytes, err := x509.CreateCertificate(rand.Reader, caTemplate, caTemplate, publicKey(caKey), caKey) + + certOut, err := os.Create("ca.cert.pem") + if err != nil { + log.Error(err.Error()) + os.Exit(1) + } + pem.Encode(certOut, &pem.Block{Type: "CERTIFICATE", Bytes: derBytes}) + certOut.Close() + log.Print("written ca.cert.pem") + + ioutil.WriteFile("ca.cert.cer", derBytes, 0644) + log.Print("written ca.cert.cer") + + // Create the key for the final certificate + key, err := generateKey("P256") + if err != nil { + log.Error(err.Error()) + os.Exit(1) + } + + keyOut, err = os.OpenFile("key.pem", os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600) + if err != nil { + log.Error(err.Error()) + os.Exit(1) + } + pem.Encode(keyOut, pemBlockForKey(key)) + keyOut.Close() + log.Println("written key.pem") + + // Create the final certificate + template, err := generateSingleCertificate(false) + + if err != nil { + log.Error(err.Error()) + os.Exit(1) + } + + derBytes, err = x509.CreateCertificate(rand.Reader, template, caTemplate, publicKey(key), caKey) + + certOut, err = os.Create("cert.pem") + if err != nil { + log.Error(err.Error()) + os.Exit(1) + } + pem.Encode(certOut, &pem.Block{Type: "CERTIFICATE", Bytes: derBytes}) + certOut.Close() + log.Print("written cert.pem") + + ioutil.WriteFile("cert.cer", derBytes, 0644) + log.Print("written cert.cer") + +} + +func certHandler(c *gin.Context) { + if strings.Contains(c.Request.UserAgent(), "Firefox") { + c.Header("content-type", "application/x-x509-ca-cert") + c.File("ca.cert.cer") + return + } + noFirefoxTemplate.Execute(c.Writer, gin.H{ + "url": "http://" + c.Request.Host + c.Request.URL.String(), + }) +} + +const noFirefoxTemplateHTML = ` + + + + + + +
+

Oops, this is not Firefox

+
+ +
+

You need to open this link in Firefox to trust this certificate: {{.host}}{{.url}}

+
+ + +` + +var noFirefoxTemplate = template.Must(template.New("home").Parse(noFirefoxTemplateHTML)) diff --git a/compile_webidebridge.sh b/compile_webidebridge.sh index f9a5c8639..29f9231fc 100755 --- a/compile_webidebridge.sh +++ b/compile_webidebridge.sh @@ -1,3 +1,5 @@ +#!/bin/bash + # git submodule init # git submodule update @@ -29,10 +31,10 @@ createZipEmbeddableFileArduino() mkdir arduino/arduino cp -r arduino/hardware arduino/tools\_$GOOS\_$GOARCH arduino/boards.json arduino/arduino cp config.ini arduino - cp fakecerts/*.pem arduino/ + cp fakecerts/*.pem fakecerts/*.cer arduino/ mv arduino/arduino/tools* arduino/arduino/tools cd arduino - zip -r arduino.zip config.ini *.pem > /dev/null + zip -r arduino.zip config.ini *.pem *.cer > /dev/null cd .. #cat arduino/arduino.zip >> $3 #zip --adjust-sfx $3 diff --git a/config.ini b/config.ini index f0639f2c6..dae4bce0e 100644 --- a/config.ini +++ b/config.ini @@ -7,3 +7,4 @@ v = true # show debug logging appName = CreateBridge updateUrl = http://downloads.arduino.cc/ #updateUrl = http://localhost/ +origins = http://webide.arduino.cc:8080 \ No newline at end of file diff --git a/conn.go b/conn.go index e6981a75c..f8285633a 100644 --- a/conn.go +++ b/conn.go @@ -57,6 +57,9 @@ func uploadHandler(c *gin.Context) { extraInfo.authdata.UserName = c.PostForm("auth_user") extraInfo.authdata.Password = c.PostForm("auth_pass") commandline := c.PostForm("commandline") + if commandline == "undefined" { + commandline = "" + } extraInfo.use_1200bps_touch, _ = strconv.ParseBool(c.PostForm("use_1200bps_touch")) extraInfo.wait_for_upload_port, _ = strconv.ParseBool(c.PostForm("wait_for_upload_port")) extraInfo.networkPort, _ = strconv.ParseBool(c.PostForm("network")) @@ -78,7 +81,11 @@ func uploadHandler(c *gin.Context) { c.String(http.StatusBadRequest, err.Error()) } - go spProgramRW(port, board, board_rewrite, path, commandline, extraInfo) + if board_rewrite != "" { + board = board_rewrite + } + + go spProgramRW(port, board, path, commandline, extraInfo) } } diff --git a/fakecerts/cert.pem b/fakecerts/cert.pem deleted file mode 100644 index 7f648b91c..000000000 --- a/fakecerts/cert.pem +++ /dev/null @@ -1,19 +0,0 @@ ------BEGIN CERTIFICATE----- -MIIDBDCCAewCCQD9LvUUyrKRXzANBgkqhkiG9w0BAQUFADBEMQswCQYDVQQGEwJV -UzEUMBIGA1UEChMLQXJkdWlubyBMTEMxCzAJBgNVBAsTAklUMRIwEAYDVQQDEwls -b2NhbGhvc3QwHhcNMTUwNjExMDcyNTM2WhcNMTYwNjEwMDcyNTM2WjBEMQswCQYD -VQQGEwJVUzEUMBIGA1UEChMLQXJkdWlubyBMTEMxCzAJBgNVBAsTAklUMRIwEAYD -VQQDEwlsb2NhbGhvc3QwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC7 -3UTunwOvcw1hgFDN6bdHxssg/H33faLILjUJIBZwyRCVLc8zcExk7D0YLxnMBaxs -t4DoPfMlAXiJ+GT3fiOaYTKHmGT3Qz85WC6Yngz9A/usDQSSYe9rI4k3oLNA4F+b -6VU5FBj1V3nj79gs3IMEkTMnit+9HZ8PNlJEZSzEuxbrQdIV530H8MvHpUoCkinP -uPur9zGFysIS1euoVybNKmVXcxmy0QHDNfOddH2SBaFD6QriucOIMW6AVHjseL9E -A4IKKum88LLDiKPeu3gr12vNnjydLFGnNg+wlTnXUeeuXgMfNVaTDaIC8NEA+mSQ -M6Ag6CymhWqpe/cqa45hAgMBAAEwDQYJKoZIhvcNAQEFBQADggEBABCXfQZLhYI4 -B0VjgwDDRohTAhsSyaQvFEhGOx2Oo28RhKgY2xRtfjDlm7IzTLga4ApsHqIMf85F -z4bpDDciwPhQDJ6a0ueXJv+xb6RGBnGAbnOiHPfgdd/P2vGfsR7mRaTdXJC4r9PI -C3kzjwTlplaulLbHs5XIXImGvAvRX5sPmkqlBAQs8PVG4I71pKXo1M4kl7uzr96+ -+DKnVz2oACAPCW6zTlT/MlBH4nSeCQfGiE8iWAqODviONOQjFnaTKLw6d1YnbnLp -1gokB8sk1TAM3qjb6giZpe945xISSNUDAVRW+3NIKag+qOURnN+QGi9ac3cPepXb -4Kr4QM+kV+g= ------END CERTIFICATE----- diff --git a/fakecerts/key.pem b/fakecerts/key.pem deleted file mode 100644 index 26c54a281..000000000 --- a/fakecerts/key.pem +++ /dev/null @@ -1,27 +0,0 @@ ------BEGIN RSA PRIVATE KEY----- -MIIEogIBAAKCAQEAu91E7p8Dr3MNYYBQzem3R8bLIPx9932iyC41CSAWcMkQlS3P -M3BMZOw9GC8ZzAWsbLeA6D3zJQF4ifhk934jmmEyh5hk90M/OVgumJ4M/QP7rA0E -kmHvayOJN6CzQOBfm+lVORQY9Vd54+/YLNyDBJEzJ4rfvR2fDzZSRGUsxLsW60HS -Fed9B/DLx6VKApIpz7j7q/cxhcrCEtXrqFcmzSplV3MZstEBwzXznXR9kgWhQ+kK -4rnDiDFugFR47Hi/RAOCCirpvPCyw4ij3rt4K9drzZ48nSxRpzYPsJU511Hnrl4D -HzVWkw2iAvDRAPpkkDOgIOgspoVqqXv3KmuOYQIDAQABAoIBADzL4df7W/z2ldj8 -5qmMvkSbP/d/3uAuJ3TzBzTZzolXeGdeuNRjvkVW8nqWG5ocJ+3TAQOnsL1EGZdE -7J/vkWQPmoOpPNuMRrSvJf08AOYM2PCYEeexjAK3MFvxRLF1K1vQikT7jQww8ABl -CSeTgU8EEnL0jW2tXWFV6g+6Ul+jwfq5IvbUpMsMOPuUEQy85rm04bCw/vUnhZXk -gFSpAp5mKPI6J/v2fkJTjgxi0wURxHKFdH/dFr69k9G7Vv9L8meiZYwA0QsYcmeJ -EAGpZHQXpTCmmmzWM85vz9vg4qUBwF8ypXcWjuqfjAopXvuenyIkfa9paDriRnNM -A3JmUQECgYEA66fdY8cU1cJw5jmhc10mi6lDz0Xswba4kaYnb2fwr7tlkBFzYYVn -GY6f2fNxdx8ZpOCYzembJ67qCXODw4LLle1slgHzOWHQ4RJd2EDw/i9woPHGsT3a -TIk0tX3wOjtJJEXNzQEiqcDqGqrpY3pnm4lFGR4RSE6vIYfPwyO9y7ECgYEAzBUv -hKy19GSFK8tkYa/WcmNMCGwLJB0pEMuT3heDjSEKYNaTh8YcIpeEY3aCWbMKxWdu -O5TIouyNJHtm4Q0umG08ZGekLTZy22it2UJabROvHVHeSnPki9a12Uc3KgB6mBzb -nnHXQ8hR60o0GTPMudVW12aZh9gy+EcGWQEwibECgYAIdQ3M9ugYg9HpcUgKC93G -RVzZo3jLuvMExyHDLcfFhsQuV3H8nf0FPcke2U3JKKSS9jyFp0yaL/zWOf8QlTEZ -QFwVRhykgo4shaw4hpwfgzRXklW/Jqke9g2eNdbZQCdv1dF8+f10eifsrRWTLGFr -g5GnRRz5q1k9qtIZ/r5hAQKBgCegMXLMaiQC5IylWF9TWgsp1piICf3AH7ogHRzx -adycPrygzVJ+l/Xrf4wkdZjfF8dOluvArthbn+gmgcpO2e5Ev4YrTYht2w1ZHPBj -XtVxDf5eaBACwqyYSwTePArOvv8ME2SHbCnAGo/Z/5WpJiYrE0qNpF/pDbSBbe0Y -OwlxAoGAdPEjpeeCpyUcoJMVnIxM7AtsOqomd0lBrRgRq05FYvGMAygoKzz+OBp+ -VgptcGJp+6t5MY9R3asRaFp3rOcXvX5R4wBMfijlzoezMEFZN/+xpm7LN2E0domO -xyku5Kcn9G/KTxCduepOtjqoNYkKYAcrdkmRfZ9C9xvuB2lKjk8= ------END RSA PRIVATE KEY----- diff --git a/hub.go b/hub.go index 421989701..b03ec0a75 100755 --- a/hub.go +++ b/hub.go @@ -57,6 +57,7 @@ func (h *hub) run() { c.send <- []byte("{\"Version\" : \"" + version + "\"} ") c.send <- []byte("{\"Commands\" : [\"list\", \"open [portName] [baud] [bufferAlgorithm (optional)]\", \"send [portName] [cmd]\", \"sendnobuf [portName] [cmd]\", \"close [portName]\", \"bufferalgorithms\", \"baudrates\", \"restart\", \"exit\", \"program [portName] [board:name] [$path/to/filename/without/extension]\", \"programfromurl [portName] [board:name] [urlToHexFile]\"]} ") c.send <- []byte("{\"Hostname\" : \"" + *hostname + "\"} ") + c.send <- []byte("{\"OS\" : \"" + runtime.GOOS + "\"} ") case c := <-h.unregister: delete(h.connections, c) // put close in func cuz it was creating panics and want @@ -216,11 +217,10 @@ func checkCmd(m []byte) { //log.Print("Done with checkCmd") } -var multi_writer = io.MultiWriter(&logger_ws, os.Stderr) - func logAction(sl string) { if strings.HasPrefix(sl, "log on") { *logDump = "on" + multi_writer := io.MultiWriter(&logger_ws, os.Stderr) log.SetOutput(multi_writer) } else if strings.HasPrefix(sl, "log off") { *logDump = "off" diff --git a/info.go b/info.go index 194d46e4c..6fe3df038 100644 --- a/info.go +++ b/info.go @@ -7,7 +7,7 @@ import ( func infoHandler(c *gin.Context) { c.JSON(200, gin.H{ "http": "http://localhost" + port, - "https": "https://localhost" + port, + "https": "https://localhost" + portSSL, "ws": "ws://localhost" + port, "wss": "wss://localhost" + portSSL, }) diff --git a/main.go b/main.go index 99391714d..e04ba5477 100755 --- a/main.go +++ b/main.go @@ -39,10 +39,12 @@ var ( hostname = flag.String("hostname", "unknown-hostname", "Override the hostname we get from the OS") updateUrl = flag.String("updateUrl", "", "") appName = flag.String("appName", "", "") + genCert = flag.Bool("generateCert", false, "") globalToolsMap = make(map[string]string) tempToolsPath = createToolsDir() port string portSSL string + origins = flag.String("origins", "", "Allowed origin list for CORS") ) type NullWriter int @@ -53,7 +55,7 @@ type logWriter struct{} func (u *logWriter) Write(p []byte) (n int, err error) { h.broadcastSys <- p - return 0, nil + return len(p), nil } var logger_ws logWriter @@ -77,6 +79,11 @@ func main() { flag.Parse() + if *genCert == true { + generateCertificates() + os.Exit(0) + } + if *hibernate == false { go func() { @@ -109,6 +116,20 @@ func main() { iniflags.Parse() } + // move CORS to config file compatibility, Vagrant version + if *origins == "" { + log.Println("Patching config.ini for compatibility") + f, err := os.OpenFile(dest+"/"+*configIni, os.O_APPEND|os.O_WRONLY, 0666) + if err != nil { + panic(err) + } + _, err = f.WriteString("\norigins = http://webide.arduino.cc:8080\n") + if err != nil { + panic(err) + } + f.Close() + restart("") + } //log.SetFormatter(&log.JSONFormatter{}) log.SetLevel(log.InfoLevel) @@ -194,8 +215,14 @@ func main() { socketHandler := wsHandler().ServeHTTP + extraOriginStr := "https://create.arduino.cc, http://create.arduino.cc, https://create-dev.arduino.cc, http://create-dev.arduino.cc, http://create-staging.arduino.cc, https://create-staging.arduino.cc" + + for i := 8990; i < 9001; i++ { + extraOriginStr = extraOriginStr + ", http://localhost:" + strconv.Itoa(i) + ", https://localhost:" + strconv.Itoa(i) + } + r.Use(cors.Middleware(cors.Config{ - Origins: "https://create.arduino.cc, http://create.arduino.cc, https://create-dev.arduino.cc, http://create-dev.arduino.cc, http://webide.arduino.cc:8080, http://create-staging.arduino.cc, https://create-staging.arduino.cc, http://localhost:8989, https://localhost:8990", + Origins: *origins + ", " + extraOriginStr, Methods: "GET, PUT, POST, DELETE", RequestHeaders: "Origin, Authorization, Content-Type", ExposedHeaders: "", @@ -204,7 +231,10 @@ func main() { ValidateHeaders: false, })) + r.LoadHTMLFiles("templates/nofirefox.html") + r.GET("/", homeHandler) + r.GET("/certificate.crt", certHandler) r.POST("/upload", uploadHandler) r.GET("/socket.io/", socketHandler) r.POST("/socket.io/", socketHandler) @@ -212,6 +242,11 @@ func main() { r.Handle("WSS", "/socket.io/", socketHandler) r.GET("/info", infoHandler) go func() { + // check if certificates exist; if not, use plain http + if _, err := os.Stat(filepath.Join(dest, "cert.pem")); os.IsNotExist(err) { + return + } + start := 8990 end := 9000 i := start @@ -270,7 +305,7 @@ const homeTemplateHtml = ` var log = document.getElementById('log'); var pause = document.getElementById('myCheck'); var messages = []; - var only_log = false; + var only_log = true; function appendLog(msg) { @@ -361,7 +396,9 @@ body {
- Pause
+ Pause + +
diff --git a/programmer.go b/programmer.go index 167b2e703..e3bf5aa3b 100644 --- a/programmer.go +++ b/programmer.go @@ -7,8 +7,9 @@ import ( "fmt" log "github.com/Sirupsen/logrus" "github.com/facchinm/go-serial" - "github.com/kardianos/osext" "github.com/mattn/go-shellwords" + "github.com/sfreiberg/simplessh" + "github.com/xrash/smetrics" "io" "mime/multipart" "net/http" @@ -41,11 +42,105 @@ type boardExtraInfo struct { authdata basicAuthData } +// Scp uploads sourceFile to remote machine like native scp console app. +func Scp(client *simplessh.Client, sourceFile, targetFile string) error { + + session, err := client.SSHClient.NewSession() + if err != nil { + return err + } + defer session.Close() + + src, srcErr := os.Open(sourceFile) + + if srcErr != nil { + return srcErr + } + + srcStat, statErr := src.Stat() + + if statErr != nil { + return statErr + } + + go func() { + w, _ := session.StdinPipe() + + fmt.Fprintln(w, "C0644", srcStat.Size(), filepath.Base(targetFile)) + + if srcStat.Size() > 0 { + io.Copy(w, src) + fmt.Fprint(w, "\x00") + w.Close() + } else { + fmt.Fprint(w, "\x00") + w.Close() + } + + }() + + if err := session.Run("scp -t " + targetFile); err != nil { + return err + } + + return nil +} + +func spProgramSSHNetwork(portname string, boardname string, filePath string, commandline string, authdata basicAuthData) error { + log.Println("Starting network upload") + log.Println("Board Name: " + boardname) + + if authdata.UserName == "" { + authdata.UserName = "root" + } + + if authdata.Password == "" { + authdata.Password = "arduino" + } + + ssh_client, err := simplessh.ConnectWithPassword(portname+":22", authdata.UserName, authdata.Password) + if err != nil { + log.Println("Error connecting via ssh") + return err + } + defer ssh_client.Close() + + err = Scp(ssh_client, filePath, "/tmp/sketch"+filepath.Ext(filePath)) + if err != nil { + log.Printf("Upload: %s\n", err) + return err + } + + if commandline == "" { + // very special case for Yun (remove once AVR boards.txt is fixed) + commandline = "merge-sketch-with-bootloader.lua /tmp/sketch.hex && /usr/bin/run-avrdude /tmp/sketch.hex" + } + + fmt.Println(commandline) + + ssh_output, err := ssh_client.Exec(commandline) + if err == nil { + log.Printf("Flash: %s\n", ssh_output) + mapD := map[string]string{"ProgrammerStatus": "Busy", "Msg": string(ssh_output)} + mapB, _ := json.Marshal(mapD) + h.broadcastSys <- mapB + } + return err +} + func spProgramNetwork(portname string, boardname string, filePath string, authdata basicAuthData) error { log.Println("Starting network upload") log.Println("Board Name: " + boardname) + if authdata.UserName == "" { + authdata.UserName = "root" + } + + if authdata.Password == "" { + authdata.Password = "arduino" + } + // Prepare a form that you will submit to that URL. _url := "http://" + portname + "/data/upload_sketch_silent" var b bytes.Buffer @@ -110,23 +205,10 @@ func spProgramNetwork(portname string, boardname string, filePath string, authda log.Errorf("bad status: %s", res.Status) err = fmt.Errorf("bad status: %s", res.Status) } - - if err != nil { - log.Printf("Command finished with error: %v ", err) - mapD := map[string]string{"ProgrammerStatus": "Error", "Msg": "Could not program the board", "Output": "", "Err": "Could not program the board"} - mapB, _ := json.Marshal(mapD) - h.broadcastSys <- mapB - } else { - log.Printf("Finished without error. Good stuff.") - mapD := map[string]string{"ProgrammerStatus": "Done", "Flash": "Ok", "Output": ""} - mapB, _ := json.Marshal(mapD) - h.broadcastSys <- mapB - // analyze stdin - } return err } -func spProgramLocal(portname string, boardname string, filePath string, commandline string, extraInfo boardExtraInfo) { +func spProgramLocal(portname string, boardname string, filePath string, commandline string, extraInfo boardExtraInfo) error { var err error if extraInfo.use_1200bps_touch { @@ -135,7 +217,7 @@ func spProgramLocal(portname string, boardname string, filePath string, commandl if err != nil { log.Println("Could not touch the port") - return + return err } log.Printf("Received commandline (unresolved):" + commandline) @@ -152,19 +234,27 @@ func spProgramLocal(portname string, boardname string, filePath string, commandl fmt.Println(runtimeVars) for _, element := range runtimeVars { + + // use string similarity to resolve a runtime var with a "similar" map element + if globalToolsMap[element] == "" { + max_similarity := 0.0 + for i, candidate := range globalToolsMap { + similarity := smetrics.Jaro(element, i) + if similarity > 0.8 && similarity > max_similarity { + max_similarity = similarity + globalToolsMap[element] = candidate + } + } + } + commandline = strings.Replace(commandline, element, globalToolsMap[element], 1) } z, _ := shellwords.Parse(commandline) - spHandlerProgram(z[0], z[1:]) + return spHandlerProgram(z[0], z[1:]) } -func spProgram(portname string, boardname string, filePath string, commandline string, extraInfo boardExtraInfo) { - - spProgramRW(portname, boardname, "", filePath, commandline, extraInfo) -} - -func spProgramRW(portname string, boardname string, boardname_rewrite string, filePath string, commandline string, extraInfo boardExtraInfo) { +func spProgramRW(portname string, boardname string, filePath string, commandline string, extraInfo boardExtraInfo) { compiling = true defer func() { @@ -175,24 +265,32 @@ func spProgramRW(portname string, boardname string, boardname_rewrite string, fi var err error if extraInfo.networkPort { - if boardname_rewrite != "" { - err = spProgramNetwork(portname, boardname_rewrite, filePath, extraInfo.authdata) - } else { - err = spProgramNetwork(portname, boardname, filePath, extraInfo.authdata) - } + err = spProgramNetwork(portname, boardname, filePath, extraInfo.authdata) if err != nil { - mapD := map[string]string{"ProgrammerStatus": "Error", "Msg": "Could not program the board", "Output": "", "Err": err.Error()} - mapB, _ := json.Marshal(mapD) - h.broadcastSys <- mapB + // no http method available, try ssh upload + err = spProgramSSHNetwork(portname, boardname, filePath, commandline, extraInfo.authdata) } } else { - spProgramLocal(portname, boardname, filePath, commandline, extraInfo) + err = spProgramLocal(portname, boardname, filePath, commandline, extraInfo) + } + + if err != nil { + log.Printf("Command finished with error: %v", err) + mapD := map[string]string{"ProgrammerStatus": "Error", "Msg": "Could not program the board"} + mapB, _ := json.Marshal(mapD) + h.broadcastSys <- mapB + } else { + log.Printf("Finished without error. Good stuff") + mapD := map[string]string{"ProgrammerStatus": "Done", "Flash": "Ok"} + mapB, _ := json.Marshal(mapD) + h.broadcastSys <- mapB + // analyze stdin } } var oscmd *exec.Cmd -func spHandlerProgram(flasher string, cmdString []string) { +func spHandlerProgram(flasher string, cmdString []string) error { // if runtime.GOOS == "darwin" { // sh, _ := exec.LookPath("sh") @@ -213,18 +311,18 @@ func spHandlerProgram(flasher string, cmdString []string) { extension = ".exe" } - oscmd = exec.Command(flasher+extension, cmdString...) + oscmd = exec.Command(flasher, cmdString...) tellCommandNotToSpawnShell(oscmd) stdout, err := oscmd.StdoutPipe() if err != nil { - return + return err } stderr, err := oscmd.StderrPipe() if err != nil { - return + return err } multi := io.MultiReader(stderr, stdout) @@ -253,19 +351,7 @@ func spHandlerProgram(flasher string, cmdString []string) { err = oscmd.Wait() - if err != nil { - log.Printf("Command finished with error: %v", err) - mapD := map[string]string{"ProgrammerStatus": "Error", "Msg": "Could not program the board"} - mapB, _ := json.Marshal(mapD) - h.broadcastSys <- mapB - } else { - log.Printf("Finished without error. Good stuff") - mapD := map[string]string{"ProgrammerStatus": "Done", "Flash": "Ok"} - mapB, _ := json.Marshal(mapD) - h.broadcastSys <- mapB - // analyze stdin - - } + return err } func spHandlerProgramKill() { @@ -341,6 +427,11 @@ func touch_port_1200bps(portname string, wait_for_upload_port bool) (string, err initialPortName := portname log.Println("Restarting in bootloader mode") + before_reset_ports, _ := serial.GetPortsList() + log.Println(before_reset_ports) + + var ports []string + mode := &serial.Mode{ BaudRate: 1200, Vmin: 0, @@ -356,220 +447,49 @@ func touch_port_1200bps(portname string, wait_for_upload_port bool) (string, err log.Println(err) } port.Close() - time.Sleep(time.Second / 2.0) timeout := false go func() { - time.Sleep(2 * time.Second) + time.Sleep(10 * time.Second) timeout = true }() - // time.Sleep(time.Second / 4) - // wait for port to reappear + // wait for port to disappear if wait_for_upload_port { - after_reset_ports, _ := serial.GetPortsList() - log.Println(after_reset_ports) - var ports []string for { ports, _ = serial.GetPortsList() log.Println(ports) - time.Sleep(time.Millisecond * 200) - portname = findNewPortName(ports, after_reset_ports) + portname = findNewPortName(ports, before_reset_ports) if portname != "" { break } if timeout { break } + time.Sleep(time.Millisecond * 100) } } - if portname == "" { - portname = initialPortName - } - return portname, nil -} - -func assembleCompilerCommand(boardname string, portname string, filePath string) (bool, string, []string) { - - // get executable (self)path and use it as base for all other paths - execPath, _ := osext.Executable() - - boardFields := strings.Split(boardname, ":") - if len(boardFields) != 3 { - mapD := map[string]string{"Err": "Board need to be specified in core:architecture:name format"} - mapB, _ := json.Marshal(mapD) - h.broadcastSys <- mapB - return false, "", nil - } - tempPath := (filepath.Dir(execPath) + "/" + boardFields[0] + "/hardware/" + boardFields[1] + "/boards.txt") - file, err := os.Open(tempPath) - if err != nil { - mapD := map[string]string{"Err": "Could not find board: " + boardname} - mapB, _ := json.Marshal(mapD) - h.broadcastSys <- mapB - log.Println("Error:", err) - return false, "", nil - } - scanner := bufio.NewScanner(file) - - boardOptions := make(map[string]string) - uploadOptions := make(map[string]string) - - for scanner.Scan() { - // map everything matching with boardname - if strings.Contains(scanner.Text(), boardFields[2]) { - arr := strings.Split(scanner.Text(), "=") - arr[0] = strings.Replace(arr[0], boardFields[2]+".", "", 1) - boardOptions[arr[0]] = arr[1] - } - } - - if len(boardOptions) == 0 { - mapD := map[string]string{"Err": "Board " + boardFields[2] + " is not part of " + boardFields[0] + ":" + boardFields[1]} - mapB, _ := json.Marshal(mapD) - h.broadcastSys <- mapB - return false, "", nil - } - - // filepath need special care; the project_name var is the filename minus its extension (hex or bin) - // if we are going to modify standard IDE files we also could pass ALL filename - filePath = strings.Trim(filePath, "\n") - boardOptions["build.path"] = filepath.Dir(filePath) - boardOptions["build.project_name"] = strings.TrimSuffix(filepath.Base(filePath), filepath.Ext(filepath.Base(filePath))) - - file.Close() - - // get infos about the programmer - tempPath = (filepath.Dir(execPath) + "/" + boardFields[0] + "/hardware/" + boardFields[1] + "/platform.txt") - file, err = os.Open(tempPath) - if err != nil { - mapD := map[string]string{"Err": "Could not find board: " + boardname} - mapB, _ := json.Marshal(mapD) - h.broadcastSys <- mapB - log.Println("Error:", err) - return false, "", nil - } - scanner = bufio.NewScanner(file) - - tool := boardOptions["upload.tool"] - - for scanner.Scan() { - // map everything matching with upload - if strings.Contains(scanner.Text(), tool) { - arr := strings.Split(scanner.Text(), "=") - uploadOptions[arr[0]] = arr[1] - arr[0] = strings.Replace(arr[0], "tools."+tool+".", "", 1) - boardOptions[arr[0]] = arr[1] - // we have a "=" in command line - if len(arr) > 2 { - boardOptions[arr[0]] = arr[1] + "=" + arr[2] + // wait for port to reappear + if wait_for_upload_port { + after_reset_ports, _ := serial.GetPortsList() + log.Println(after_reset_ports) + for { + ports, _ = serial.GetPortsList() + log.Println(ports) + portname = findNewPortName(ports, after_reset_ports) + if portname != "" { + break } - } - } - file.Close() - - // multiple verisons of the same programmer can be handled if "version" is specified - version := uploadOptions["runtime.tools."+tool+".version"] - path := (filepath.Dir(execPath) + "/" + boardFields[0] + "/tools/" + tool + "/" + version) - if err != nil { - mapD := map[string]string{"Err": "Could not find board: " + boardname} - mapB, _ := json.Marshal(mapD) - h.broadcastSys <- mapB - log.Println("Error:", err) - return false, "", nil - } - - boardOptions["runtime.tools."+tool+".path"] = path - - cmdline := boardOptions["upload.pattern"] - // remove cmd.path as it is handled differently - cmdline = strings.Replace(cmdline, "\"{cmd.path}\"", " ", 1) - cmdline = strings.Replace(cmdline, "\"{path}/{cmd}\"", " ", 1) - cmdline = strings.Replace(cmdline, "\"", "", -1) - - initialPortName := portname - - // some boards (eg. Leonardo, Yun) need a special procedure to enter bootloader - if boardOptions["upload.use_1200bps_touch"] == "true" { - // triggers bootloader mode - // the portname could change in this occasion (expecially on Windows) so change portname - // with the port which will reappear - log.Println("Restarting in bootloader mode") - - mode := &serial.Mode{ - BaudRate: 1200, - Vmin: 1, - Vtimeout: 0, - } - port, err := serial.OpenPort(portname, mode) - if err != nil { - log.Println(err) - return false, "", nil - } - //port.SetDTR(false) - port.Close() - time.Sleep(time.Second / 2.0) - - timeout := false - go func() { - time.Sleep(2 * time.Second) - timeout = true - }() - - // time.Sleep(time.Second / 4) - // wait for port to reappear - if boardOptions["upload.wait_for_upload_port"] == "true" { - after_reset_ports, _ := serial.GetPortsList() - log.Println(after_reset_ports) - var ports []string - for { - ports, _ = serial.GetPortsList() - log.Println(ports) - time.Sleep(time.Millisecond * 200) - portname = findNewPortName(ports, after_reset_ports) - if portname != "" { - break - } - if timeout { - break - } + if timeout { + break } + time.Sleep(time.Millisecond * 100) } } if portname == "" { portname = initialPortName } - - boardOptions["serial.port"] = portname - boardOptions["serial.port.file"] = filepath.Base(portname) - - // split the commandline in substrings and recursively replace mapped strings - cmdlineSlice := strings.Split(cmdline, " ") - var winded = true - for index, _ := range cmdlineSlice { - winded = true - for winded != false { - cmdlineSlice[index], winded = formatCmdline(cmdlineSlice[index], boardOptions) - } - } - - tool = (filepath.Dir(execPath) + "/" + boardFields[0] + "/tools/" + tool + "/bin/" + tool) - // the file doesn't exist, we are on windows - if _, err := os.Stat(tool); err != nil { - tool = tool + ".exe" - // convert all "/" to "\" - tool = strings.Replace(tool, "/", "\\", -1) - } - - // remove blanks from cmdlineSlice - var cmdlineSliceOut []string - for _, element := range cmdlineSlice { - if element != "" { - cmdlineSliceOut = append(cmdlineSliceOut, element) - } - } - - return (tool != ""), tool, cmdlineSliceOut + return portname, nil } diff --git a/update.go b/update.go index e2ffea7b6..f6d85b882 100644 --- a/update.go +++ b/update.go @@ -38,11 +38,10 @@ import ( "errors" "fmt" log "github.com/Sirupsen/logrus" - "github.com/inconshreveable/go-update" "github.com/kardianos/osext" "github.com/kr/binarydist" "github.com/pivotal-golang/archiver/extractor" - patch "github.com/sanderhahn/gozip/patchzip" + "gopkg.in/inconshreveable/go-update.v0" "io" "io/ioutil" "math/rand" @@ -69,57 +68,6 @@ func IsZip(path string) bool { return false } -func Zip(path string, dirs []string) (err error) { - if IsZip(path) { - return errors.New(path + " is already a zip file") - } - - f, err := os.OpenFile(path, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0644) - if err != nil { - return - } - defer f.Close() - - startoffset, err := f.Seek(0, os.SEEK_END) - if err != nil { - return - } - - w := patch.NewWriterAt(f, startoffset) - - for _, dir := range dirs { - err = filepath.Walk(dir, func(path string, info os.FileInfo, err error) error { - if err != nil { - return err - } - - fh, err := patch.FileInfoHeader(info) - if err != nil { - return err - } - fh.Name = path - - p, err := w.CreateHeader(fh) - if err != nil { - return err - } - if !info.IsDir() { - content, err := ioutil.ReadFile(path) - if err != nil { - return err - } - _, err = p.Write(content) - if err != nil { - return err - } - } - return err - }) - } - err = w.Close() - return -} - func Unzip(zippath string, destination string) (err error) { r, err := zip.OpenReader(zippath) if err != nil {