2011-01-28 07:07:18 +00:00
|
|
|
/*
|
|
|
|
Copyright 2011 Google Inc.
|
|
|
|
|
|
|
|
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.
|
|
|
|
*/
|
|
|
|
|
2013-07-07 23:09:17 +00:00
|
|
|
// Package auth implements Camlistore authentication.
|
2010-11-15 03:52:52 +00:00
|
|
|
package auth
|
2010-07-26 03:34:04 +00:00
|
|
|
|
|
|
|
import (
|
2013-12-12 11:13:44 +00:00
|
|
|
"crypto/rand"
|
2013-01-26 19:23:39 +00:00
|
|
|
"errors"
|
2010-07-26 03:34:04 +00:00
|
|
|
"fmt"
|
Update from r60 to [almost] Go 1.
A lot is still broken, but most stuff at least compiles now.
The directory tree has been rearranged now too. Go libraries are now
under "pkg". Fully qualified, they are e.g. "camlistore.org/pkg/jsonsign".
The go tool cannot yet fetch from arbitrary domains, but discussion is
happening now on which mechanism to use to allow that.
For now, put the camlistore root under $GOPATH/src. Typically $GOPATH
is $HOME, so Camlistore should be at $HOME/src/camlistore.org.
Then you can:
$ go build ./server/camlistored
... etc
The build.pl script is currently disabled. It'll be resurrected at
some point, but with a very different role (helping create a fake
GOPATH and running the go build command, if things are installed at
the wrong place, and/or running fileembed generators).
Many things are certainly broken.
Many things are disabled. (MySQL, all indexing, etc).
Many things need to be moved into
camlistore.org/third_party/{code.google.com,github.com} and updated
from their r60 to Go 1 versions, where applicable.
The GoMySQL stuff should be updated to use database/sql and the ziutek
library implementing database/sql/driver.
Help wanted.
Change-Id: If71217dc5c8f0e70dbe46e9504ca5131c6eeacde
2012-02-19 05:53:06 +00:00
|
|
|
"net/http"
|
2011-06-09 23:09:21 +00:00
|
|
|
"os"
|
2010-07-26 03:34:04 +00:00
|
|
|
"strings"
|
2013-12-12 11:13:44 +00:00
|
|
|
"sync"
|
2012-03-28 00:57:13 +00:00
|
|
|
|
2013-10-31 05:00:17 +00:00
|
|
|
"camlistore.org/pkg/httputil"
|
2010-07-26 03:34:04 +00:00
|
|
|
)
|
|
|
|
|
2013-01-02 22:52:35 +00:00
|
|
|
// Operation represents a bitmask of operations. See the OpX constants.
|
|
|
|
type Operation int
|
|
|
|
|
|
|
|
const (
|
|
|
|
OpUpload Operation = 1 << iota
|
|
|
|
OpStat
|
|
|
|
OpGet
|
|
|
|
OpEnumerate
|
|
|
|
OpRemove
|
2013-01-04 22:09:55 +00:00
|
|
|
OpSign
|
2013-01-15 14:01:57 +00:00
|
|
|
OpDiscovery
|
|
|
|
OpRead = OpEnumerate | OpStat | OpGet | OpDiscovery
|
2013-01-02 22:52:35 +00:00
|
|
|
OpRW = OpUpload | OpEnumerate | OpStat | OpGet // Not Remove
|
2013-01-15 14:01:57 +00:00
|
|
|
OpVivify = OpUpload | OpStat | OpGet | OpDiscovery
|
|
|
|
OpAll = OpUpload | OpEnumerate | OpStat | OpRemove | OpGet | OpSign | OpDiscovery
|
2013-01-02 22:52:35 +00:00
|
|
|
)
|
|
|
|
|
2011-11-16 10:41:38 +00:00
|
|
|
var (
|
2011-11-28 17:45:08 +00:00
|
|
|
mode AuthMode // the auth logic depending on the choosen auth mechanism
|
2011-11-16 10:41:38 +00:00
|
|
|
)
|
2010-07-26 03:34:04 +00:00
|
|
|
|
2013-01-11 07:03:46 +00:00
|
|
|
// An AuthMode is the interface implemented by diffent authentication
|
|
|
|
// schemes.
|
2011-11-16 10:41:38 +00:00
|
|
|
type AuthMode interface {
|
2013-01-02 22:52:35 +00:00
|
|
|
// AllowedAccess returns a bitmask of all operations
|
|
|
|
// this user/request is allowed to do.
|
|
|
|
AllowedAccess(req *http.Request) Operation
|
2011-12-02 10:35:28 +00:00
|
|
|
// AddAuthHeader inserts in req the credentials needed
|
2012-11-08 23:40:13 +00:00
|
|
|
// for a client to authenticate.
|
2011-12-02 10:35:28 +00:00
|
|
|
AddAuthHeader(req *http.Request)
|
2011-03-05 08:03:53 +00:00
|
|
|
}
|
|
|
|
|
2013-01-11 07:03:46 +00:00
|
|
|
// UnauthorizedSender may be implemented by AuthModes which want to
|
|
|
|
// handle sending unauthorized.
|
|
|
|
type UnauthorizedSender interface {
|
|
|
|
// SendUnauthorized sends an unauthorized response,
|
|
|
|
// and returns whether it handled it.
|
|
|
|
SendUnauthorized(http.ResponseWriter, *http.Request) (handled bool)
|
|
|
|
}
|
|
|
|
|
Update from r60 to [almost] Go 1.
A lot is still broken, but most stuff at least compiles now.
The directory tree has been rearranged now too. Go libraries are now
under "pkg". Fully qualified, they are e.g. "camlistore.org/pkg/jsonsign".
The go tool cannot yet fetch from arbitrary domains, but discussion is
happening now on which mechanism to use to allow that.
For now, put the camlistore root under $GOPATH/src. Typically $GOPATH
is $HOME, so Camlistore should be at $HOME/src/camlistore.org.
Then you can:
$ go build ./server/camlistored
... etc
The build.pl script is currently disabled. It'll be resurrected at
some point, but with a very different role (helping create a fake
GOPATH and running the go build command, if things are installed at
the wrong place, and/or running fileembed generators).
Many things are certainly broken.
Many things are disabled. (MySQL, all indexing, etc).
Many things need to be moved into
camlistore.org/third_party/{code.google.com,github.com} and updated
from their r60 to Go 1 versions, where applicable.
The GoMySQL stuff should be updated to use database/sql and the ziutek
library implementing database/sql/driver.
Help wanted.
Change-Id: If71217dc5c8f0e70dbe46e9504ca5131c6eeacde
2012-02-19 05:53:06 +00:00
|
|
|
func FromEnv() (AuthMode, error) {
|
2011-11-16 10:41:38 +00:00
|
|
|
return FromConfig(os.Getenv("CAMLI_AUTH"))
|
|
|
|
}
|
|
|
|
|
2013-01-11 07:03:46 +00:00
|
|
|
// An AuthConfigParser parses a registered authentication type's option
|
|
|
|
// and returns an AuthMode.
|
|
|
|
type AuthConfigParser func(arg string) (AuthMode, error)
|
|
|
|
|
|
|
|
var authConstructor = map[string]AuthConfigParser{
|
|
|
|
"none": newNoneAuth,
|
|
|
|
"localhost": newLocalhostAuth,
|
|
|
|
"userpass": newUserPassAuth,
|
2013-08-31 20:44:44 +00:00
|
|
|
"devauth": newDevAuth,
|
2013-01-11 07:03:46 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// RegisterAuth registers a new authentication scheme.
|
|
|
|
func RegisterAuth(name string, ctor AuthConfigParser) {
|
|
|
|
if _, dup := authConstructor[name]; dup {
|
|
|
|
panic("Dup registration of auth mode " + name)
|
|
|
|
}
|
|
|
|
authConstructor[name] = ctor
|
|
|
|
}
|
|
|
|
|
|
|
|
func newNoneAuth(string) (AuthMode, error) {
|
|
|
|
return None{}, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func newLocalhostAuth(string) (AuthMode, error) {
|
2013-08-31 20:44:44 +00:00
|
|
|
return Localhost{}, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func newDevAuth(pw string) (AuthMode, error) {
|
|
|
|
// the vivify mode password is automatically set to "vivi" + Password
|
|
|
|
return &DevAuth{pw, "vivi" + pw}, nil
|
2013-01-11 07:03:46 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func newUserPassAuth(arg string) (AuthMode, error) {
|
|
|
|
pieces := strings.Split(arg, ":")
|
|
|
|
if len(pieces) < 2 {
|
|
|
|
return nil, fmt.Errorf("Wrong userpass auth string; needs to be \"userpass:user:password\"")
|
|
|
|
}
|
|
|
|
username := pieces[0]
|
|
|
|
password := pieces[1]
|
|
|
|
mode := &UserPass{Username: username, Password: password}
|
|
|
|
for _, opt := range pieces[2:] {
|
|
|
|
switch {
|
|
|
|
case opt == "+localhost":
|
|
|
|
mode.OrLocalhost = true
|
|
|
|
case strings.HasPrefix(opt, "vivify="):
|
|
|
|
// optional vivify mode password: "userpass:joe:ponies:vivify=rainbowdash"
|
|
|
|
mode.VivifyPass = strings.Replace(opt, "vivify=", "", -1)
|
|
|
|
default:
|
|
|
|
return nil, fmt.Errorf("Unknown userpass option %q", opt)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return mode, nil
|
|
|
|
}
|
|
|
|
|
2013-01-26 19:23:39 +00:00
|
|
|
// ErrNoAuth is returned when there is no configured authentication.
|
|
|
|
var ErrNoAuth = errors.New("auth: no configured authentication")
|
|
|
|
|
2011-11-28 17:45:08 +00:00
|
|
|
// FromConfig parses authConfig and accordingly sets up the AuthMode
|
|
|
|
// that will be used for all upcoming authentication exchanges. The
|
|
|
|
// supported modes are UserPass and DevAuth. UserPass requires an authConfig
|
2013-08-31 20:44:44 +00:00
|
|
|
// of the kind "userpass:joe:ponies".
|
2013-01-26 19:23:39 +00:00
|
|
|
//
|
|
|
|
// If the input string is empty, the error will be ErrNoAuth.
|
Update from r60 to [almost] Go 1.
A lot is still broken, but most stuff at least compiles now.
The directory tree has been rearranged now too. Go libraries are now
under "pkg". Fully qualified, they are e.g. "camlistore.org/pkg/jsonsign".
The go tool cannot yet fetch from arbitrary domains, but discussion is
happening now on which mechanism to use to allow that.
For now, put the camlistore root under $GOPATH/src. Typically $GOPATH
is $HOME, so Camlistore should be at $HOME/src/camlistore.org.
Then you can:
$ go build ./server/camlistored
... etc
The build.pl script is currently disabled. It'll be resurrected at
some point, but with a very different role (helping create a fake
GOPATH and running the go build command, if things are installed at
the wrong place, and/or running fileembed generators).
Many things are certainly broken.
Many things are disabled. (MySQL, all indexing, etc).
Many things need to be moved into
camlistore.org/third_party/{code.google.com,github.com} and updated
from their r60 to Go 1 versions, where applicable.
The GoMySQL stuff should be updated to use database/sql and the ziutek
library implementing database/sql/driver.
Help wanted.
Change-Id: If71217dc5c8f0e70dbe46e9504ca5131c6eeacde
2012-02-19 05:53:06 +00:00
|
|
|
func FromConfig(authConfig string) (AuthMode, error) {
|
2013-01-26 19:23:39 +00:00
|
|
|
if authConfig == "" {
|
|
|
|
return nil, ErrNoAuth
|
|
|
|
}
|
2013-01-11 07:03:46 +00:00
|
|
|
pieces := strings.SplitN(authConfig, ":", 2)
|
2011-11-16 10:41:38 +00:00
|
|
|
if len(pieces) < 1 {
|
|
|
|
return nil, fmt.Errorf("Invalid auth string: %q", authConfig)
|
2011-06-15 09:44:38 +00:00
|
|
|
}
|
2011-11-28 03:23:23 +00:00
|
|
|
authType := pieces[0]
|
|
|
|
|
2013-01-11 07:03:46 +00:00
|
|
|
if fn, ok := authConstructor[authType]; ok {
|
|
|
|
arg := ""
|
|
|
|
if len(pieces) == 2 {
|
|
|
|
arg = pieces[1]
|
2011-11-16 10:41:38 +00:00
|
|
|
}
|
2013-01-11 07:03:46 +00:00
|
|
|
return fn(arg)
|
2011-11-16 10:41:38 +00:00
|
|
|
}
|
2013-01-11 07:03:46 +00:00
|
|
|
return nil, fmt.Errorf("Unknown auth type: %q", authType)
|
|
|
|
}
|
|
|
|
|
|
|
|
// SetMode sets the authentication mode for future requests.
|
|
|
|
func SetMode(m AuthMode) {
|
|
|
|
mode = m
|
2011-06-15 09:44:38 +00:00
|
|
|
}
|
|
|
|
|
2011-11-16 10:41:38 +00:00
|
|
|
// UserPass is used when the auth string provided in the config
|
|
|
|
// is of the kind "userpass:username:pass"
|
2013-01-02 22:52:35 +00:00
|
|
|
// Possible options appended to the config string are
|
|
|
|
// "+localhost" and "vivify=pass", where pass will be the
|
|
|
|
// alternative password which only allows the vivify operation.
|
2011-11-16 10:41:38 +00:00
|
|
|
type UserPass struct {
|
|
|
|
Username, Password string
|
2012-04-08 02:02:31 +00:00
|
|
|
OrLocalhost bool // if true, allow localhost ident auth too
|
2013-01-02 22:52:35 +00:00
|
|
|
// Alternative password used (only) for the vivify operation.
|
|
|
|
// It is checked when uploading, but Password takes precedence.
|
|
|
|
VivifyPass string
|
2011-11-16 10:41:38 +00:00
|
|
|
}
|
|
|
|
|
2013-01-02 22:52:35 +00:00
|
|
|
func (up *UserPass) AllowedAccess(req *http.Request) Operation {
|
2013-10-31 05:00:17 +00:00
|
|
|
user, pass, err := httputil.BasicAuth(req)
|
2013-08-21 03:02:16 +00:00
|
|
|
if err == nil {
|
|
|
|
if user == up.Username {
|
|
|
|
if pass == up.Password {
|
|
|
|
return OpAll
|
|
|
|
}
|
|
|
|
if pass == up.VivifyPass {
|
|
|
|
return OpVivify
|
|
|
|
}
|
2013-01-02 22:52:35 +00:00
|
|
|
}
|
2010-07-26 03:34:04 +00:00
|
|
|
}
|
2013-08-19 04:07:10 +00:00
|
|
|
|
2013-12-12 11:13:44 +00:00
|
|
|
if websocketTokenMatches(req) {
|
|
|
|
return OpAll
|
|
|
|
}
|
2013-10-31 05:00:17 +00:00
|
|
|
if up.OrLocalhost && httputil.IsLocalhost(req) {
|
2013-08-19 04:07:10 +00:00
|
|
|
return OpAll
|
|
|
|
}
|
|
|
|
|
2013-01-02 22:52:35 +00:00
|
|
|
return 0
|
2011-11-16 10:41:38 +00:00
|
|
|
}
|
|
|
|
|
2011-12-02 10:35:28 +00:00
|
|
|
func (up *UserPass) AddAuthHeader(req *http.Request) {
|
|
|
|
req.SetBasicAuth(up.Username, up.Password)
|
|
|
|
}
|
|
|
|
|
2012-04-12 23:54:25 +00:00
|
|
|
type None struct{}
|
|
|
|
|
2013-01-02 22:52:35 +00:00
|
|
|
func (None) AllowedAccess(req *http.Request) Operation {
|
|
|
|
return OpAll
|
2012-04-12 23:54:25 +00:00
|
|
|
}
|
|
|
|
|
2013-01-02 22:52:35 +00:00
|
|
|
func (None) AddAuthHeader(req *http.Request) {
|
|
|
|
// Nothing.
|
2012-04-12 23:54:25 +00:00
|
|
|
}
|
|
|
|
|
2013-01-02 22:52:35 +00:00
|
|
|
type Localhost struct {
|
|
|
|
None
|
2012-04-12 23:54:25 +00:00
|
|
|
}
|
|
|
|
|
2013-08-31 20:44:44 +00:00
|
|
|
func (Localhost) AllowedAccess(req *http.Request) (out Operation) {
|
2013-10-31 05:00:17 +00:00
|
|
|
if httputil.IsLocalhost(req) {
|
2013-01-02 22:52:35 +00:00
|
|
|
return OpAll
|
|
|
|
}
|
|
|
|
return 0
|
2012-04-12 23:54:25 +00:00
|
|
|
}
|
|
|
|
|
2013-08-31 20:44:44 +00:00
|
|
|
// DevAuth is used for development. It has one password and one vivify password, but
|
|
|
|
// also accepts all passwords from localhost. Usernames are ignored.
|
2011-11-16 10:41:38 +00:00
|
|
|
type DevAuth struct {
|
|
|
|
Password string
|
2013-01-02 22:52:35 +00:00
|
|
|
// Password for the vivify mode, automatically set to "vivi" + Password
|
|
|
|
VivifyPass string
|
|
|
|
}
|
|
|
|
|
|
|
|
func (da *DevAuth) AllowedAccess(req *http.Request) Operation {
|
2013-10-31 05:00:17 +00:00
|
|
|
_, pass, err := httputil.BasicAuth(req)
|
2013-08-21 03:02:16 +00:00
|
|
|
if err == nil {
|
|
|
|
if pass == da.Password {
|
|
|
|
return OpAll
|
|
|
|
}
|
|
|
|
if pass == da.VivifyPass {
|
|
|
|
return OpVivify
|
|
|
|
}
|
2013-01-02 22:52:35 +00:00
|
|
|
}
|
2013-08-19 04:07:10 +00:00
|
|
|
|
2013-12-12 11:13:44 +00:00
|
|
|
if websocketTokenMatches(req) {
|
|
|
|
return OpAll
|
|
|
|
}
|
|
|
|
|
2013-08-19 04:07:10 +00:00
|
|
|
// See if the local TCP port is owned by the same non-root user as this
|
|
|
|
// server. This check performed last as it may require reading from the
|
|
|
|
// kernel or exec'ing a program.
|
2013-10-31 05:00:17 +00:00
|
|
|
if httputil.IsLocalhost(req) {
|
2013-08-19 04:07:10 +00:00
|
|
|
return OpAll
|
|
|
|
}
|
|
|
|
|
2013-01-02 22:52:35 +00:00
|
|
|
return 0
|
|
|
|
}
|
|
|
|
|
|
|
|
func (da *DevAuth) AddAuthHeader(req *http.Request) {
|
|
|
|
req.SetBasicAuth("", da.Password)
|
2011-11-16 10:41:38 +00:00
|
|
|
}
|
|
|
|
|
2013-01-02 22:52:35 +00:00
|
|
|
func IsLocalhost(req *http.Request) bool {
|
2013-10-31 05:00:17 +00:00
|
|
|
return httputil.IsLocalhost(req)
|
2012-04-15 18:09:51 +00:00
|
|
|
}
|
|
|
|
|
2013-01-02 22:52:35 +00:00
|
|
|
// TODO(mpl): if/when we ever need it:
|
|
|
|
// func AllowedWithAuth(am AuthMode, req *http.Request, op Operation) bool
|
2012-03-28 00:57:13 +00:00
|
|
|
|
2013-01-02 22:52:35 +00:00
|
|
|
// Allowed returns whether the given request
|
|
|
|
// has access to perform all the operations in op.
|
|
|
|
func Allowed(req *http.Request, op Operation) bool {
|
|
|
|
if op|OpUpload != 0 {
|
|
|
|
// upload (at least from camput) requires stat and get too
|
|
|
|
op = op | OpVivify
|
2010-07-26 03:34:04 +00:00
|
|
|
}
|
2013-01-02 22:52:35 +00:00
|
|
|
return mode.AllowedAccess(req)&op == op
|
2011-11-16 10:41:38 +00:00
|
|
|
}
|
|
|
|
|
2013-12-12 11:13:44 +00:00
|
|
|
func websocketTokenMatches(req *http.Request) bool {
|
|
|
|
return req.Method == "GET" &&
|
|
|
|
req.Header.Get("Upgrade") == "websocket" &&
|
|
|
|
req.FormValue("authtoken") == ProcessRandom()
|
|
|
|
}
|
|
|
|
|
2011-11-16 10:41:38 +00:00
|
|
|
func TriedAuthorization(req *http.Request) bool {
|
|
|
|
// Currently a simple test just using HTTP basic auth
|
|
|
|
// (presumably over https); may expand.
|
|
|
|
return req.Header.Get("Authorization") != ""
|
|
|
|
}
|
|
|
|
|
2013-01-11 07:03:46 +00:00
|
|
|
func SendUnauthorized(rw http.ResponseWriter, req *http.Request) {
|
|
|
|
if us, ok := mode.(UnauthorizedSender); ok {
|
|
|
|
if us.SendUnauthorized(rw, req) {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
2011-11-16 10:41:38 +00:00
|
|
|
realm := "camlistored"
|
2011-11-28 03:23:23 +00:00
|
|
|
if devAuth, ok := mode.(*DevAuth); ok {
|
|
|
|
realm = "Any username, password is: " + devAuth.Password
|
2011-11-16 10:41:38 +00:00
|
|
|
}
|
2013-01-11 07:03:46 +00:00
|
|
|
rw.Header().Set("WWW-Authenticate", fmt.Sprintf("Basic realm=%q", realm))
|
|
|
|
rw.WriteHeader(http.StatusUnauthorized)
|
|
|
|
fmt.Fprintf(rw, "<html><body><h1>Unauthorized</h1>")
|
2010-07-26 03:34:04 +00:00
|
|
|
}
|
|
|
|
|
2011-12-11 00:56:35 +00:00
|
|
|
type Handler struct {
|
|
|
|
http.Handler
|
|
|
|
}
|
|
|
|
|
2013-01-02 22:52:35 +00:00
|
|
|
// ServeHTTP serves only if this request and auth mode are allowed all Operations.
|
2011-12-11 00:56:35 +00:00
|
|
|
func (h Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
2013-01-02 22:52:35 +00:00
|
|
|
h.serveHTTPForOp(w, r, OpAll)
|
|
|
|
}
|
|
|
|
|
|
|
|
// serveHTTPForOp serves only if op is allowed for this request and auth mode.
|
|
|
|
func (h Handler) serveHTTPForOp(w http.ResponseWriter, r *http.Request, op Operation) {
|
|
|
|
if Allowed(r, op) {
|
2011-12-11 00:56:35 +00:00
|
|
|
h.Handler.ServeHTTP(w, r)
|
|
|
|
} else {
|
2013-01-11 07:03:46 +00:00
|
|
|
SendUnauthorized(w, r)
|
2011-12-11 00:56:35 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2010-07-26 03:34:04 +00:00
|
|
|
// requireAuth wraps a function with another function that enforces
|
2013-01-02 22:52:35 +00:00
|
|
|
// HTTP Basic Auth and checks if the operations in op are all permitted.
|
2013-01-11 07:03:46 +00:00
|
|
|
func RequireAuth(handler func(http.ResponseWriter, *http.Request), op Operation) func(http.ResponseWriter, *http.Request) {
|
|
|
|
return func(rw http.ResponseWriter, req *http.Request) {
|
2013-01-02 22:52:35 +00:00
|
|
|
if Allowed(req, op) {
|
2013-01-11 07:03:46 +00:00
|
|
|
handler(rw, req)
|
2011-06-15 09:44:38 +00:00
|
|
|
} else {
|
2013-01-11 07:03:46 +00:00
|
|
|
SendUnauthorized(rw, req)
|
2010-07-26 03:34:04 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2013-12-12 11:13:44 +00:00
|
|
|
|
|
|
|
var (
|
|
|
|
processRand string
|
|
|
|
processRandOnce sync.Once
|
|
|
|
)
|
|
|
|
|
|
|
|
func ProcessRandom() string {
|
|
|
|
processRandOnce.Do(genProcessRand)
|
|
|
|
return processRand
|
|
|
|
}
|
|
|
|
|
|
|
|
func genProcessRand() {
|
|
|
|
buf := make([]byte, 20)
|
|
|
|
if n, err := rand.Read(buf); err != nil || n != len(buf) {
|
|
|
|
panic("failed to get random: " + err.Error())
|
|
|
|
}
|
|
|
|
processRand = fmt.Sprintf("%x", buf)
|
|
|
|
}
|