Taru Karttunen avatar Taru Karttunen committed 4794f21

Import

Comments (0)

Files changed (12)

+^noVNC$
+The code is licensed as follows:
+
+vnc.html is derived from noVNC and thus licensed under LGPL-3.
+
+All the Go code and original html templates are licensed under the MIT license,
+which is reproduced below:
+
+Copyright (C) 2012 Taru Karttunen <taruti@taruti.net>
+
+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.
+novncgo:
+	go build
+
+noVNC:
+	git clone https://github.com/kanaka/noVNC.git
+
+update: noVNC
+	hg pull -u
+	cd noVNC && git pull
+
+package main
+
+import (
+	"crypto/rand"
+	"crypto/rsa"
+	"crypto/tls"
+	"crypto/x509"
+	"encoding/asn1"
+	"encoding/pem"
+	"io/ioutil"
+	"log"
+	"math/big"
+	"time"
+)
+
+func EnsureCert(DNS string, ConfigDir string) {
+	cf := ConfigDir + "/novnc." + DNS + ".cert"
+	kf := ConfigDir + "/novnc." + DNS + ".secret"
+	_, err := tls.LoadX509KeyPair(cf, kf)
+	if err != nil {
+		log.Println("Generating certificate ",cf)
+		tmpl := new(x509.Certificate)
+		key, err := rsa.GenerateKey(rand.Reader, 2048)
+		if err != nil {
+			panic(err)
+		}
+		cacert := tmpl
+		cakey  := key
+		
+		tmpl.SerialNumber = big.NewInt(1)
+		tmpl.NotBefore = time.Now()
+		tmpl.NotAfter  = tmpl.NotBefore.AddDate(10,0,0)
+		tmpl.Subject.CommonName = DNS
+		tmpl.Subject.Organization = []string{"novnc"}
+		tmpl.SubjectKeyId = []byte{1, 2, 3, 4}
+		tmpl.BasicConstraintsValid = true
+		tmpl.IsCA = true
+		tmpl.DNSNames = []string{DNS}
+		tmpl.PolicyIdentifiers = []asn1.ObjectIdentifier{[]int{1, 2, 3}}
+		cr, err := x509.CreateCertificate(rand.Reader, tmpl, cacert, &key.PublicKey, cakey)
+		if err != nil {
+			panic(err)
+		}
+		kr := x509.MarshalPKCS1PrivateKey(key)
+		kb := pem.Block{"RSA PRIVATE KEY", nil, kr}
+		cb := pem.Block{"CERTIFICATE", nil, cr}
+		ioutil.WriteFile(kf, pem.EncodeToMemory(&kb), 0400)
+		cm := pem.EncodeToMemory(&cb)
+		ioutil.WriteFile(cf, cm, 0400)
+
+	}
+	return
+}
+package main
+
+import (
+	"bitbucket.org/taruti/pbkdf2.go"
+	"github.com/bmatsuo/csvutil"
+	"encoding/hex"
+	"log"
+	"os"
+	"strings"
+	)
+
+var csvcfg = csvcfgI()
+
+func csvcfgI() *csvutil.Config {
+	c := *csvutil.DefaultConfig
+	c.Sep = ':'
+	return &c
+}
+
+func AuthUser(username, pass string) bool {
+	r,e := os.Open("novnc.go.passwd")
+	if e!=nil {
+		log.Print(e)
+		return false
+	}
+	lines,e := csvutil.NewReader(r,csvcfg).RemainingRows()
+	if e!=nil {
+		log.Print(e)
+		return false
+	}
+	for _,line := range lines {
+		if len(line)>=3 && line[0] == username {
+			var ph pbkdf2.PasswordHash
+			ph.Salt,_ = hex.DecodeString(line[1])
+			ph.Hash,_ = hex.DecodeString(line[2])
+			return pbkdf2.MatchPassword(pass, ph)
+		}
+	}
+	return false
+}
+
+type Server struct {
+	Name, Location string
+	Perms []string
+}
+
+func Servers() []Server {
+	r,e := os.Open("novnc.go.servers")
+	if e!=nil {
+		log.Print(e)
+		return nil
+	}
+	lines,e := csvutil.NewReader(r,csvcfg).RemainingRows()
+	if e!=nil {
+		log.Print(e)
+		return nil
+	}
+	ss := []Server{}
+	for _,line := range lines {
+		ss = append(ss,Server{line[0], line[1]+":"+line[2], strings.Split(line[3]," ")})
+	}
+	return ss
+}
+package main
+
+import (
+	"code.google.com/p/go.net/websocket"
+	"encoding/base64"
+	"flag"
+	"html/template"
+	"io"
+	"log"
+	"net"
+	"net/http"
+	"os"
+	"strings"
+	)
+
+var (
+	hostname,_ = os.Hostname()
+    listen  = flag.String("listen", ":6080", "Location to listen for connections")
+    host    = flag.String("hostname", hostname, "Hostname to use for certificate")
+    fileDir = flag.String("filedir", "export", "Directory for exported files")
+)
+
+//func DebugHandler(w http.ResponseWriter, req *http.Request) {
+//	log.Println(req)
+//	http.DefaultServeMux.ServeHTTP(w,req)
+//}
+
+func main() {
+	flag.Parse()
+	EnsureCert(*host, ".")
+	http.HandleFunc("/file/", ufh)
+	http.HandleFunc("/list", lh)
+ 	http.HandleFunc("/login", loginh)
+ 	http.HandleFunc("/logout", logouth)
+	http.HandleFunc("/upload", uploadh)
+	http.HandleFunc("/websockify/", wsh)
+	http.HandleFunc("/", fh)
+	log.Fatal(http.ListenAndServeTLS(*listen, "novnc."+*host+".cert", "novnc."+*host+".secret", nil))
+}
+
+
+func loginh(w http.ResponseWriter, r *http.Request) {
+	if r.Method != "POST" { return }
+	u := r.FormValue("user")
+	p := r.FormValue("pass")
+	if !AuthUser(u,p) {
+		http.Redirect(w,r,"/",302)
+		return
+	}
+	log.Print("Logging in ",u)
+	SetUser(w,r,u)
+	http.Redirect(w,r,"/list",302)
+}
+
+func logouth(w http.ResponseWriter, r *http.Request) {
+	SetUser(w,r,"")
+	http.Redirect(w,r,"/",302)
+}
+
+type List struct {
+	Servers []Server
+	User string
+}
+
+func lh(w http.ResponseWriter, r *http.Request) {
+ 	user := GetUser(w,r)
+	if user=="" { return }
+	tmpl, _ := template.ParseFiles("tmpl/list.html")
+	tmpl.Execute(w,List{Servers(),user})
+}
+func fh(w http.ResponseWriter, r *http.Request) {
+//	log.Println(r.RequestURI)
+	http.FileServer(http.Dir("static")).ServeHTTP(w,r)
+}
+
+func ufh(w http.ResponseWriter, r *http.Request) {
+ 	user := GetUser(w,r)
+	if user=="" { return }
+	http.StripPrefix("/file",http.FileServer(http.Dir(*fileDir))).ServeHTTP(w,r)
+}
+
+func uploadh(w http.ResponseWriter, r *http.Request) {
+ 	user := GetUser(w,r)
+	if user=="" { return }
+	f,h,e := r.FormFile("file")
+	if e!=nil { return }
+	defer f.Close()
+	dst,e := os.Create(*fileDir+"/"+SafeName(h.Filename))
+	if e!=nil { return }
+	defer dst.Close()
+	io.Copy(dst,f)
+	http.Redirect(w,r,"/list",302)
+}
+
+func SafeName(s string) string {
+	return strings.Replace(s, "/", "_", -1)
+}
+
+func wsh(w http.ResponseWriter, r *http.Request) {
+	log.Println(r.RequestURI)
+ 	user := GetUser(w,r)
+	if user=="" {
+		log.Print("Invalid user in wsh")
+		return 
+	}
+
+	li := strings.LastIndex(r.RequestURI, "/")
+	sname := r.RequestURI[li+1:]
+	var loc string
+	for _,s := range Servers() {
+		log.Print("Looping ",s.Name," searching for ",sname)
+		if s.Name == sname {
+			loc = s.Location
+			goto connect
+		}
+	}
+	log.Print("Invalid server name given")
+	return
+connect:
+	log.Print("Opening vnc connection for ",user," to ",loc)
+	vc,err := net.Dial("tcp",loc)
+	defer vc.Close()
+	if err!=nil {
+		log.Print(err)
+		return
+	}
+	websocket.Handler(func(ws *websocket.Conn) {
+		go func() {
+			sbuf := make([]byte, 32*1024)
+			dbuf := make([]byte, 32*1024)
+			for {
+				n,e := ws.Read(sbuf)
+//				log.Println("<< R",n,e)
+				if e!=nil { return }
+				n,e  = base64.StdEncoding.Decode(dbuf, sbuf[0:n])
+				if e!=nil { return }
+				n,e  = vc.Write(dbuf[0:n])
+				if e!=nil { return }
+			}
+		}()
+		func() {
+			sbuf := make([]byte, 32*1024)
+			dbuf := make([]byte, 64*1024)
+			for {
+				n,e := vc.Read(sbuf)
+//				log.Println(">> R ",n)
+				if e!=nil { return }
+				base64.StdEncoding.Encode(dbuf,sbuf[0:n])
+				n = ((n+2)/3)*4
+				ws.Write(dbuf[0:n])
+//				bws := base64.NewEncoder(base64.StdEncoding, ws)
+//				n,e  = bws.Write(sbuf[0:n])
+//				bws.Close()
+//				log.Println(">> W ",n)
+				if e!=nil { return }
+			}
+			
+		}()
+	}).ServeHTTP(w,r)
+}
+
+package main
+
+import (
+	"code.google.com/p/gorilla/securecookie"
+	"code.google.com/p/gorilla/sessions"
+	"net/http"
+	"time"
+	)
+
+
+var store = sessions.NewCookieStore(
+	securecookie.GenerateRandomKey(32),
+	securecookie.GenerateRandomKey(32))
+
+func GetUser(w http.ResponseWriter, r *http.Request) string {
+	session,_ := store.Get(r,"login")
+	u, _ := session.Values["user"].(string)
+	t, _ := session.Values["time"].(int64)
+	now  := time.Now().Unix()
+	if now-t > 3600 { u = "" }
+	if u=="" {
+		http.Redirect(w,r,"/",302)
+	}
+	return u
+}
+
+func SetUser(w http.ResponseWriter, r *http.Request, user string) {
+	session,_ := store.Get(r,"login")
+	session.Values["user"] = user
+	session.Values["time"] = time.Now().Unix()
+	session.Save(r,w)
+}
+../noVNC/images
+../noVNC/include

static/index.html

+<!DOCTYPE html>
+<html>
+ <head>
+  <title>noVNC.go: Login</title>
+  <link rel="stylesheet" type="text/css" href="shared.css"/>
+ </head>
+ <body class="center">
+   <h1>noVNC.go: Login</h1>
+   <form method="POST" action="/login">
+	 <input id="user" name="user" type="text" size="32" autofocus/>
+	 <input id="pass" name="pass" type="password" size="32"/>
+	 <input type="submit" value="Login"/>
+   </form>
+ </body>
+</html>
+
+<!DOCTYPE html>
+<html>
+    <!-- 
+    noVNC Example: Automatically connect on page load.
+    Copyright (C) 2011 Joel Martin
+    Licensed under LGPL-3 (see LICENSE.txt)
+
+    Connect parameters are provided in query string:
+        http://example.com/?host=HOST&port=PORT&encrypt=1&true_color=1
+    -->
+    <head>
+        <title>noVNC</title>
+        <meta http-equiv="X-UA-Compatible" content="chrome=1">
+        <link rel="stylesheet" href="include/base.css" title="plain">
+        <!--
+        <script type='text/javascript' 
+            src='http://getfirebug.com/releases/lite/1.2/firebug-lite-compressed.js'></script>
+        -->
+        <script src="include/vnc.js"></script>
+    </head>
+
+    <body style="margin: 0px;">
+        <div id="noVNC_screen">
+            <div id="noVNC_status_bar" class="noVNC_status_bar" style="margin-top: 0px;">
+                <table border=0 width="100%"><tr>
+                    <td><div id="noVNC_status">Loading</div></td>
+                    <td width="1%"><div id="noVNC_buttons">
+                        <input type=button value="Back"
+							   onclick="location.href='/list';return false;">
+                    </div></td>
+                    <td width="1%"><div id="noVNC_buttons">
+                        <input type=button value="Send CtrlAltDel" id="sendCtrlAltDelButton">
+                    </div></td>
+                </tr></table>
+            </div>
+            <canvas id="noVNC_canvas" width="640px" height="20px">
+                Canvas not supported.
+            </canvas>
+        </div>
+
+        <script>
+        /*jslint white: false */
+        /*global window, $, Util, RFB, */
+        "use strict";
+
+        var rfb;
+
+        function passwordRequired(rfb) {
+            var msg;
+            msg = '<form onsubmit="return setPassword();"';
+            msg += '  style="margin-bottom: 0px">';
+            msg += 'Password Required: ';
+            msg += '<input type=password size=10 id="password_input" class="noVNC_status">';
+            msg += '<\/form>';
+            $D('noVNC_status_bar').setAttribute("class", "noVNC_status_warn");
+            $D('noVNC_status').innerHTML = msg;
+        }
+        function setPassword() {
+            rfb.sendPassword($D('password_input').value);
+            return false;
+        }
+        function sendCtrlAltDel() {
+            rfb.sendCtrlAltDel();
+            return false;
+        }
+        function updateState(rfb, state, oldstate, msg) {
+            var s, sb, cad, level;
+            s = $D('noVNC_status');
+            sb = $D('noVNC_status_bar');
+            cad = $D('sendCtrlAltDelButton');
+            switch (state) {
+                case 'failed':       level = "error";  break;
+                case 'fatal':        level = "error";  break;
+                case 'normal':       level = "normal"; break;
+                case 'disconnected': level = "normal"; break;
+                case 'loaded':       level = "normal"; break;
+                default:             level = "warn";   break;
+            }
+
+            if (state === "normal") { cad.disabled = false; }
+            else                    { cad.disabled = true; }
+
+            if (typeof(msg) !== 'undefined') {
+                sb.setAttribute("class", "noVNC_status_" + level);
+                s.innerHTML = msg;
+            }
+        }
+
+        window.onload = function () {
+            var host, port, password, path;
+
+            $D('sendCtrlAltDelButton').style.display = "inline";
+            $D('sendCtrlAltDelButton').onclick = sendCtrlAltDel;
+
+            document.title = unescape(WebUtil.getQueryVar('title', 'noVNC'));
+            host = WebUtil.getQueryVar('host', window.location.hostname);
+            port = WebUtil.getQueryVar('port', window.location.port);
+            password = WebUtil.getQueryVar('password', '');
+            path = "websockify/" + WebUtil.getQueryVar('path', '');
+            if ((!host) || (!port)) {
+                updateState('failed',
+                    "Must specify host and port in URL");
+                return;
+            }
+
+            rfb = new RFB({'target':       $D('noVNC_canvas'),
+                           'encrypt':      WebUtil.getQueryVar('encrypt',
+                                    (window.location.protocol === "https:")),
+                           'true_color':   WebUtil.getQueryVar('true_color', true),
+                           'local_cursor': WebUtil.getQueryVar('cursor', true),
+                           'shared':       WebUtil.getQueryVar('shared', true),
+                           'view_only':    WebUtil.getQueryVar('view_only', false),
+                           'updateState':  updateState,
+                           'onPasswordRequired':  passwordRequired});
+            rfb.connect(host, port, password, path);
+        };
+        </script>
+
+    </body>
+</html>
+
+<!DOCTYPE html>
+<html>
+ <head>
+  <title>noVNC.go</title>
+  <link rel="stylesheet" type="text/css" href="shared.css"/>
+ </head>
+ <body class="center">
+   <h2>noVNC.go</h2>
+   <h2><a href="/logout">Logout {{.User}}</a></h2>
+   <h2>VNC Servers</h2>
+   <ul>
+   {{range .Servers}}
+   <li><a href="/vnc.html?path={{.Name}}"/>{{.Name}}</a></li>
+   {{end}}
+   </ul>
+   <h2><a href="/file">List Files</a></h2>
+   <h2>Upload</h2>
+   <form enctype="multipart/form-data" action="/upload",
+		 method="POST">
+	 File: <input name="file" type="file"/>
+	 <input type="submit" value="Upload"/>
+   </form>
+ </body>
+</html>
+
Tip: Filter by directory path e.g. /media app.js to search for public/media/app.js.
Tip: Use camelCasing e.g. ProjME to search for ProjectModifiedEvent.java.
Tip: Filter by extension type e.g. /repo .js to search for all .js files in the /repo directory.
Tip: Separate your search with spaces e.g. /ssh pom.xml to search for src/ssh/pom.xml.
Tip: Use ↑ and ↓ arrow keys to navigate and return to view the file.
Tip: You can also navigate files with Ctrl+j (next) and Ctrl+k (previous) and view the file with Ctrl+o.
Tip: You can also navigate files with Alt+j (next) and Alt+k (previous) and view the file with Alt+o.