Implement admin control through a tunneled RESTful API

pull/71/head
Qian Wang 5 years ago
parent 98a772b6ee
commit 2ce6f380d1

@ -4,25 +4,7 @@ package main
// TODO: rewrite this. Think of another way of admin control
import (
"crypto/aes"
"crypto/cipher"
"crypto/hmac"
"crypto/rand"
"crypto/sha256"
"encoding/base64"
"encoding/binary"
"encoding/json"
"errors"
"fmt"
"log"
"net"
"github.com/cbeuw/Cloak/internal/client"
"github.com/cbeuw/Cloak/internal/client/TLS"
"github.com/cbeuw/Cloak/internal/util"
)
/*
type UserInfo struct {
UID []byte
// ALL of the following fields have to be accessed atomically
@ -53,7 +35,7 @@ func adminPrompt(sta *client.State) error {
4 addNewUser userinfo ok
5 delUser uid ok
6 syncMemFromDB uid ok
7 setSessionsCap uid cap ok
8 setUpRate uid rate ok
9 setDownRate uid rate ok
@ -276,3 +258,4 @@ func (a *administrator) checkAndDecrypt(data []byte) ([]byte, error) {
stream.XORKeyStream(ret, ret)
return ret, nil
}
*/

@ -3,6 +3,7 @@
package main
import (
"encoding/base64"
"flag"
"fmt"
"io"
@ -96,7 +97,7 @@ func main() {
// The proxy port,should be 443
var remotePort string
var config string
isAdmin := new(bool)
var b64AdminUID string
log.SetFlags(log.LstdFlags | log.Lshortfile)
@ -114,8 +115,8 @@ func main() {
flag.StringVar(&remoteHost, "s", "", "remoteHost: IP of your proxy server")
flag.StringVar(&remotePort, "p", "443", "remotePort: proxy port, should be 443")
flag.StringVar(&config, "c", "ckclient.json", "config: path to the configuration file or options seperated with semicolons")
flag.StringVar(&b64AdminUID, "a", "", "adminUID: enter the adminUID to serve the admin api")
askVersion := flag.Bool("v", false, "Print the version number")
isAdmin = flag.Bool("a", false, "Admin mode")
printUsage := flag.Bool("h", false, "Print this message")
flag.Parse()
@ -132,19 +133,6 @@ func main() {
log.Println("Starting standalone mode")
}
if *isAdmin {
sta := client.InitState("", "", "", "", time.Now)
err := sta.ParseConfig(config)
if err != nil {
log.Fatal(err)
}
err = adminPrompt(sta)
if err != nil {
log.Println(err)
}
return
}
sta := client.InitState(localHost, localPort, remoteHost, remotePort, time.Now)
err := sta.ParseConfig(config)
if err != nil {
@ -164,28 +152,40 @@ func main() {
if sta.TicketTimeHint == 0 {
log.Fatal("TicketTimeHint cannot be empty or 0")
}
listeningIP := sta.LocalHost
if net.ParseIP(listeningIP).To4() == nil {
// IPv6 needs square brackets
listeningIP = "[" + listeningIP + "]"
}
listener, err := net.Listen("tcp", listeningIP+":"+sta.LocalPort)
log.Println("Listening for proxy clients on " + listeningIP + ":" + sta.LocalPort)
log.Println("Listening on " + listeningIP + ":" + sta.LocalPort)
if err != nil {
log.Fatal(err)
}
var adminUID []byte
if b64AdminUID != "" {
adminUID, err = base64.StdEncoding.DecodeString(b64AdminUID)
if err != nil {
log.Fatal(err)
}
}
start:
log.Println("Attemtping to start a new session")
// sessionID is usergenerated. There shouldn't be a security concern because the scope of
// sessionID is limited to its UID.
rand.Seed(time.Now().UnixNano())
sessionID := rand.Uint32()
sta.SetSessionID(sessionID)
var UNLIMITED_DOWN int64 = 1e15
var UNLIMITED_UP int64 = 1e15
valve := mux.MakeValve(1e12, 1e12, &UNLIMITED_DOWN, &UNLIMITED_UP)
if adminUID != nil {
sessionID = 0
sta.UID = adminUID
sta.NumConn = 1
}
sta.SetSessionID(sessionID)
var crypto mux.Crypto
switch sta.EncryptionMethod {
case 0x00:
@ -207,7 +207,8 @@ start:
sessionKey := make([]byte, 32)
rand.Read(sessionKey)
sta.SessionKey = sessionKey
sesh := mux.MakeSession(sessionID, valve, mux.MakeObfs(sta.SessionKey, crypto), mux.MakeDeobfs(sta.SessionKey, crypto), util.ReadTLS)
sesh := mux.MakeSession(sessionID, mux.UNLIMITED_VALVE, mux.MakeObfs(sta.SessionKey, crypto), mux.MakeDeobfs(sta.SessionKey, crypto), util.ReadTLS)
var wg sync.WaitGroup
for i := 0; i < sta.NumConn; i++ {

@ -1,12 +1,14 @@
package main
import (
"bytes"
"encoding/base64"
"flag"
"fmt"
"io"
"log"
"net"
"net/http"
"os"
"strings"
"time"
@ -81,6 +83,33 @@ func dispatchConnection(conn net.Conn, sta *server.State) {
return
}
var crypto mux.Crypto
switch encryptionMethod {
case 0x00:
crypto = &mux.Plain{}
case 0x01:
crypto, err = mux.MakeAESGCMCipher(UID)
if err != nil {
log.Println(err)
goWeb(data)
return
}
case 0x02:
crypto, err = mux.MakeCPCipher(UID)
if err != nil {
log.Println(err)
goWeb(data)
return
}
default:
log.Println("Unknown encryption method")
goWeb(data)
return
}
obfs := mux.MakeObfs(sessionKey, crypto)
deobfs := mux.MakeDeobfs(sessionKey, crypto)
finishHandshake := func() error {
reply := server.ComposeReply(ch)
_, err = conn.Write(reply)
@ -101,40 +130,28 @@ func dispatchConnection(conn net.Conn, sta *server.State) {
return nil
}
/*
// adminUID can use the server as normal with unlimited QoS credits. The adminUID is not
// added to the userinfo database. The distinction between going into the admin mode
// and normal proxy mode is that sessionID needs == 0 for admin mode
if bytes.Equal(UID, sta.AdminUID) && sessionID == 0 {
err = finishHandshake()
if err != nil {
log.Println(err)
return
}
c := sta.Userpanel.MakeController(sta.AdminUID)
for {
n, err := conn.Read(data)
if err != nil {
log.Println(err)
return
}
resp, err := c.HandleRequest(data[:n])
if err != nil {
log.Println(err)
}
_, err = conn.Write(resp)
if err != nil {
log.Println(err)
return
}
}
// adminUID can use the server as normal with unlimited QoS credits. The adminUID is not
// added to the userinfo database. The distinction between going into the admin mode
// and normal proxy mode is that sessionID needs == 0 for admin mode
if bytes.Equal(UID, sta.AdminUID) && sessionID == 0 {
err = finishHandshake()
if err != nil {
log.Println(err)
return
}
*/
sesh := mux.MakeSession(0, mux.UNLIMITED_VALVE, obfs, deobfs, util.ReadTLS)
sesh.AddConnection(conn)
//TODO: Router could be nil in cnc mode
err = http.Serve(sesh, sta.LocalAPIRouter)
if err != nil {
log.Println(err)
return
}
}
user, err := sta.Panel.GetUser(UID)
if err != nil {
log.Printf("+1 unauthorised user from %v, uid: %x\n", conn.RemoteAddr(), UID)
log.Printf("+1 unauthorised user from %v, uid: %v\n", conn.RemoteAddr(), base64.StdEncoding.EncodeToString(UID))
goWeb(data)
return
}
@ -145,31 +162,7 @@ func dispatchConnection(conn net.Conn, sta *server.State) {
return
}
var crypto mux.Crypto
switch encryptionMethod {
case 0x00:
crypto = &mux.Plain{}
case 0x01:
crypto, err = mux.MakeAESGCMCipher(UID)
if err != nil {
log.Println(err)
goWeb(data)
return
}
case 0x02:
crypto, err = mux.MakeCPCipher(UID)
if err != nil {
log.Println(err)
goWeb(data)
return
}
default:
log.Println("Unknown encryption method")
goWeb(data)
return
}
sesh, existing, err := user.GetSession(sessionID, mux.MakeObfs(sessionKey, crypto), mux.MakeDeobfs(sessionKey, crypto), util.ReadTLS)
sesh, existing, err := user.GetSession(sessionID, obfs, deobfs, util.ReadTLS)
if err != nil {
user.DelSession(sessionID)
log.Println(err)

@ -2,6 +2,7 @@ module github.com/cbeuw/Cloak
require (
github.com/boltdb/bolt v1.3.1
github.com/gorilla/mux v1.7.3
github.com/juju/ratelimit v1.0.1
github.com/kr/pretty v0.1.0 // indirect
golang.org/x/crypto v0.0.0-20190123085648-057139ce5d2b

@ -1,5 +1,7 @@
github.com/boltdb/bolt v1.3.1 h1:JQmyP4ZBrce+ZQu0dY660FMfatumYDLun9hBCUVIkF4=
github.com/boltdb/bolt v1.3.1/go.mod h1:clJnj/oiGkjum5o1McbSZDSLxVThjynRyGBgiAx27Ps=
github.com/gorilla/mux v1.7.3 h1:gnP5JzjVOuiZD07fKKToCAOjS0yOpj/qPETTXCCS6hw=
github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
github.com/juju/ratelimit v1.0.1 h1:+7AIFJVQ0EQgq/K9+0Krm7m530Du7tIz0METWzN0RgY=
github.com/juju/ratelimit v1.0.1/go.mod h1:qapgC/Gy+xNh9UxzV13HGGl/6UXNN+ct+vwSgWNm/qk=
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=

@ -21,16 +21,19 @@ type Valve struct {
tx *int64
}
func MakeValve(rxRate, txRate int64, rx, tx *int64) *Valve {
func MakeValve(rxRate, txRate int64) *Valve {
var rx, tx int64
v := &Valve{
rx: rx,
tx: tx,
rx: &rx,
tx: &tx,
}
v.SetRxRate(rxRate)
v.SetTxRate(txRate)
return v
}
var UNLIMITED_VALVE = MakeValve(1<<63-1, 1<<63-1)
func (v *Valve) SetRxRate(rate int64) { v.rxtb.Store(ratelimit.NewBucketWithRate(float64(rate), rate)) }
func (v *Valve) SetTxRate(rate int64) { v.txtb.Store(ratelimit.NewBucketWithRate(float64(rate), rate)) }
func (v *Valve) rxWait(n int) { v.rxtb.Load().(*ratelimit.Bucket).Wait(int64(n)) }

@ -35,6 +35,8 @@ type Session struct {
// Switchboard manages all connections to remote
sb *switchboard
addrs atomic.Value
// For accepting new streams
acceptCh chan *Stream
@ -56,6 +58,7 @@ func MakeSession(id uint32, valve *Valve, obfs Obfser, deobfs Deobfser, obfsedRe
acceptCh: make(chan *Stream, acceptBacklog),
die: make(chan struct{}),
}
sesh.addrs.Store([]net.Addr{nil, nil})
sesh.sb = makeSwitchboard(sesh, valve)
go sesh.timeoutAfter(30 * time.Second)
return sesh
@ -63,6 +66,8 @@ func MakeSession(id uint32, valve *Valve, obfs Obfser, deobfs Deobfser, obfsedRe
func (sesh *Session) AddConnection(conn net.Conn) {
sesh.sb.addConn(conn)
addrs := []net.Addr{conn.LocalAddr(), conn.RemoteAddr()}
sesh.addrs.Store(addrs)
}
func (sesh *Session) OpenStream() (*Stream, error) {
@ -174,5 +179,4 @@ func (sesh *Session) timeoutAfter(to time.Duration) {
}
}
// Addr is only for implementing net.Listener
func (sesh *Session) Addr() net.Addr { return nil }
func (sesh *Session) Addr() net.Addr { return sesh.addrs.Load().([]net.Addr)[0] }

@ -2,6 +2,8 @@ package multiplex
import (
"errors"
"io"
"log"
"net"
"time"
@ -29,6 +31,8 @@ type Stream struct {
newFrameCh chan *Frame
// sortedBufCh are order-sorted data ready to be read raw
sortedBufCh chan []byte
feederR *io.PipeReader
feederW *io.PipeWriter
// atomic
nextSendSeq uint32
@ -41,6 +45,7 @@ type Stream struct {
}
func makeStream(id uint32, sesh *Session) *Stream {
r, w := io.Pipe()
stream := &Stream{
id: id,
session: sesh,
@ -48,11 +53,32 @@ func makeStream(id uint32, sesh *Session) *Stream {
sh: []*frameNode{},
newFrameCh: make(chan *Frame, 1024),
sortedBufCh: make(chan []byte, 1024),
feederR: r,
feederW: w,
}
go stream.recvNewFrame()
go stream.feed()
return stream
}
func (stream *Stream) feed() {
for {
select {
case <-stream.die:
return
case data := <-stream.sortedBufCh:
if len(data) == 0 {
stream.passiveClose()
return
}
_, err := stream.feederW.Write(data)
if err != nil {
log.Println(err)
}
}
}
}
func (stream *Stream) Read(buf []byte) (n int, err error) {
if len(buf) == 0 {
select {
@ -65,16 +91,8 @@ func (stream *Stream) Read(buf []byte) (n int, err error) {
select {
case <-stream.die:
return 0, ErrBrokenStream
case data := <-stream.sortedBufCh:
if len(data) == 0 {
stream.passiveClose()
return 0, ErrBrokenStream
}
if len(buf) < len(data) {
return 0, errors.New("buf too small")
}
copy(buf, data)
return len(data), nil
default:
return stream.feederR.Read(buf)
}
}
@ -163,8 +181,8 @@ func (stream *Stream) closeNoDelMap() {
// they are not used
var errNotImplemented = errors.New("Not implemented")
func (stream *Stream) LocalAddr() net.Addr { return nil }
func (stream *Stream) RemoteAddr() net.Addr { return nil }
func (stream *Stream) LocalAddr() net.Addr { return stream.session.addrs.Load().([]net.Addr)[0] }
func (stream *Stream) RemoteAddr() net.Addr { return stream.session.addrs.Load().([]net.Addr)[1] }
// TODO: implement the following
func (stream *Stream) SetDeadline(t time.Time) error { return errNotImplemented }

@ -36,7 +36,7 @@ func (u *ActiveUser) GetSession(sessionID uint32, obfs mux.Obfser, deobfs mux.De
u.sessionsM.Unlock()
return sesh, true, nil
} else {
err := u.panel.manager.authoriseNewSession(u)
err := u.panel.Manager.authoriseNewSession(u)
if err != nil {
u.sessionsM.Unlock()
return nil, false, err

@ -8,6 +8,8 @@ import (
"io/ioutil"
"sync"
"time"
gmux "github.com/gorilla/mux"
)
type rawConfig struct {
@ -36,7 +38,8 @@ type State struct {
usedRandomM sync.RWMutex
usedRandom map[[32]byte]int
Panel *userPanel
Panel *userPanel
LocalAPIRouter *gmux.Router
}
func InitState(bindHost, bindPort string, nowFunc func() time.Time) (*State, error) {
@ -76,6 +79,7 @@ func (sta *State) ParseConfig(conf string) (err error) {
return err
}
sta.Panel = MakeUserPanel(manager)
sta.LocalAPIRouter = manager.Router
}
sta.RedirAddr = preParse.RedirAddr

@ -0,0 +1,159 @@
swagger: '2.0'
info:
description: |
This is the API of Cloak server
version: 1.0.0
title: Cloak Server
contact:
email: cbeuw.andy@gmail.com
license:
name: GPLv3
url: https://www.gnu.org/licenses/gpl-3.0.en.html
# host: petstore.swagger.io
# basePath: /v2
tags:
- name: admin
description: Endpoints used by the host administrators
- name: users
description: Operations related to user controls by admin
# schemes:
# - http
paths:
/admin/users:
get:
tags:
- admin
- users
summary: Show all users
description: Returns an array of all UserInfo
operationId: listAllUsers
produces:
- application/json
responses:
200:
description: successful operation
schema:
type: array
items:
$ref: '#/definitions/UserInfo'
500:
description: internal error
/admin/users/{UID}:
get:
tags:
- admin
- users
summary: Show userinfo by UID
description: Returns a UserInfo object
operationId: getUserInfo
produces:
- application/json
parameters:
- name: UID
in: path
description: UID of the user
required: true
type: string
format: byte
responses:
200:
description: successful operation
schema:
$ref: '#/definitions/UserInfo'
400:
description: bad request
404:
description: User not found
500:
description: internal error
post:
tags:
- admin
- users
summary: Updates the userinfo of the specified user, if the user does not exist, then a new user is created
operationId: writeUserInfo
consumes:
- application/json
produces:
- application/json
parameters:
- name: UID
in: path
description: UID of the user
required: true
type: string
format: byte
- name: UserInfo
in: body
description: New userinfo
required: true
schema:
type: array
items:
$ref: '#/definitions/UserInfo'
responses:
201:
description: successful operation
400:
description: bad request
500:
description: internal error
delete:
tags:
- admin
- users
summary: Deletes a user
operationId: deleteUser
produces:
- application/json
parameters:
- name: UID
in: path
description: UID of the user to be deleted
required: true
type: string
format: byte
responses:
200:
description: successful operation
400:
description: bad request
404:
description: User not found
500:
description: internal error
definitions:
UserInfo:
type: object
properties:
UID:
type: string
format: byte
SessionsCap:
type: integer
format: int32
UpRate:
type: integer
format: int64
DownRate:
type: integer
format: int64
UpCredit:
type: integer
format: int64
DownCredit:
type: integer
format: int64
ExpiryTime:
type: integer
format: int64
externalDocs:
description: Find out more about Swagger
url: http://swagger.io
# Added by API Auto Mocking Plugin
host: virtserver.swaggerhub.com
basePath: /cbeuw/ck-server/1.0.0
schemes:
- https
- http

@ -6,6 +6,7 @@ import (
"time"
"github.com/boltdb/bolt"
gmux "github.com/gorilla/mux"
)
var Uint32 = binary.BigEndian.Uint32
@ -13,8 +14,20 @@ var Uint64 = binary.BigEndian.Uint64
var PutUint32 = binary.BigEndian.PutUint32
var PutUint64 = binary.BigEndian.PutUint64
func i64ToB(value int64) []byte {
oct := make([]byte, 8)
PutUint64(oct, uint64(value))
return oct
}
func i32ToB(value int32) []byte {
nib := make([]byte, 4)
PutUint32(nib, uint32(value))
return nib
}
type localManager struct {
db *bolt.DB
db *bolt.DB
Router *gmux.Router
}
func MakeLocalManager(dbPath string) (*localManager, error) {
@ -22,7 +35,20 @@ func MakeLocalManager(dbPath string) (*localManager, error) {
if err != nil {
return nil, err
}
return &localManager{db}, nil
ret := &localManager{
db: db,
}
ret.Router = ret.registerMux()
return ret, nil
}
func (manager *localManager) registerMux() *gmux.Router {
r := gmux.NewRouter()
r.HandleFunc("/admin/users", manager.listAllUsersHlr).Methods("GET")
r.HandleFunc("/admin/users/{UID}", manager.getUserInfo).Methods("GET")
r.HandleFunc("/admin/users/{UID}", manager.writeUserInfo).Methods("POST")
r.HandleFunc("/admin/users/{UID}", manager.deleteUser).Methods("DELETE")
return r
}
func (manager *localManager) authenticateUser(UID []byte) (int64, int64, error) {
@ -90,12 +116,6 @@ func (manager *localManager) authoriseNewSession(user *ActiveUser) error {
return nil
}
func i64ToB(value int64) []byte {
oct := make([]byte, 8)
PutUint64(oct, uint64(value))
return oct
}
func (manager *localManager) uploadStatus(uploads []statusUpdate) ([]statusResponse, error) {
var responses []statusResponse
err := manager.db.Update(func(tx *bolt.Tx) error {
@ -103,7 +123,12 @@ func (manager *localManager) uploadStatus(uploads []statusUpdate) ([]statusRespo
var resp statusResponse
bucket := tx.Bucket(status.UID)
if bucket == nil {
log.Printf("%x doesn't exist\n", status.UID)
resp = statusResponse{
status.UID,
TERMINATE,
"User no longer exists",
}
responses = append(responses, resp)
continue
}
@ -115,8 +140,14 @@ func (manager *localManager) uploadStatus(uploads []statusUpdate) ([]statusRespo
TERMINATE,
"No upload credit left",
}
responses = append(responses, resp)
continue
}
err := bucket.Put([]byte("UpCredit"), i64ToB(newUp))
if err != nil {
log.Println(err)
continue
}
bucket.Put([]byte("UpCredit"), i64ToB(newUp))
oldDown := int64(Uint64(bucket.Get([]byte("DownCredit"))))
newDown := oldDown - status.downUsage
@ -126,20 +157,24 @@ func (manager *localManager) uploadStatus(uploads []statusUpdate) ([]statusRespo
TERMINATE,
"No download credit left",
}
responses = append(responses, resp)
continue
}
err = bucket.Put([]byte("DownCredit"), i64ToB(newDown))
if err != nil {
log.Println(err)
continue
}
bucket.Put([]byte("DownCredit"), i64ToB(newDown))
expiry := int64(Uint64(bucket.Get([]byte("ExpiryTime"))))
if time.Now().Unix()>expiry{
if time.Now().Unix() > expiry {
resp = statusResponse{
status.UID,
TERMINATE,
"User has expired",
}
}
if resp.UID != nil {
responses = append(responses, resp)
continue
}
}
return nil

@ -0,0 +1,165 @@
package server
import (
"bytes"
"encoding/base64"
"encoding/json"
"github.com/boltdb/bolt"
"net/http"
gmux "github.com/gorilla/mux"
)
type UserInfo struct {
UID []byte
SessionsCap int
UpRate int64
DownRate int64
UpCredit int64
DownCredit int64
ExpiryTime int64
}
func (manager *localManager) listAllUsersHlr(w http.ResponseWriter, r *http.Request) {
var infos []UserInfo
_ = manager.db.View(func(tx *bolt.Tx) error {
err := tx.ForEach(func(UID []byte, bucket *bolt.Bucket) error {
var uinfo UserInfo
uinfo.UID = UID
uinfo.SessionsCap = int(Uint32(bucket.Get([]byte("SessionsCap"))))
uinfo.UpRate = int64(Uint64(bucket.Get([]byte("UpRate"))))
uinfo.DownRate = int64(Uint64(bucket.Get([]byte("DownRate"))))
uinfo.UpCredit = int64(Uint64(bucket.Get([]byte("UpCredit"))))
uinfo.DownCredit = int64(Uint64(bucket.Get([]byte("DownCredit"))))
uinfo.ExpiryTime = int64(Uint64(bucket.Get([]byte("ExpiryTime"))))
infos = append(infos, uinfo)
return nil
})
return err
})
resp, err := json.Marshal(infos)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
_, _ = w.Write(resp)
}
func (manager *localManager) getUserInfo(w http.ResponseWriter, r *http.Request) {
b64UID := gmux.Vars(r)["UID"]
if b64UID == "" {
http.Error(w, "UID cannot be empty", http.StatusBadRequest)
}
UID, err := base64.URLEncoding.DecodeString(b64UID)
if err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
var uinfo UserInfo
err = manager.db.View(func(tx *bolt.Tx) error {
bucket := tx.Bucket([]byte(UID))
if bucket == nil {
return ErrUserNotFound
}
uinfo.UID = UID
uinfo.SessionsCap = int(Uint32(bucket.Get([]byte("SessionsCap"))))
uinfo.UpRate = int64(Uint64(bucket.Get([]byte("UpRate"))))
uinfo.DownRate = int64(Uint64(bucket.Get([]byte("DownRate"))))
uinfo.UpCredit = int64(Uint64(bucket.Get([]byte("UpCredit"))))
uinfo.DownCredit = int64(Uint64(bucket.Get([]byte("DownCredit"))))
uinfo.ExpiryTime = int64(Uint64(bucket.Get([]byte("ExpiryTime"))))
return nil
})
if err == ErrUserNotFound {
http.Error(w, ErrUserNotFound.Error(), http.StatusNotFound)
return
}
resp, err := json.Marshal(uinfo)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
_, _ = w.Write(resp)
}
func (manager *localManager) writeUserInfo(w http.ResponseWriter, r *http.Request) {
b64UID := gmux.Vars(r)["UID"]
if b64UID == "" {
http.Error(w, "UID cannot be empty", http.StatusBadRequest)
return
}
UID, err := base64.URLEncoding.DecodeString(b64UID)
if err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
jsonUinfo := gmux.Vars(r)["UserInfo"]
if jsonUinfo == "" {
http.Error(w, "UserInfo cannot be empty", http.StatusBadRequest)
return
}
var uinfo UserInfo
err = json.Unmarshal([]byte(jsonUinfo), &uinfo)
if err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
if !bytes.Equal(UID, uinfo.UID) {
http.Error(w, "UID mismatch", http.StatusBadRequest)
}
err = manager.db.Update(func(tx *bolt.Tx) error {
bucket, err := tx.CreateBucket(uinfo.UID)
if err != nil {
return err
}
if err = bucket.Put([]byte("SessionsCap"), i32ToB(int32(uinfo.SessionsCap))); err != nil {
return err
}
if err = bucket.Put([]byte("UpRate"), i64ToB(uinfo.UpRate)); err != nil {
return err
}
if err = bucket.Put([]byte("DownRate"), i64ToB(uinfo.DownRate)); err != nil {
return err
}
if err = bucket.Put([]byte("UpCredit"), i64ToB(uinfo.UpCredit)); err != nil {
return err
}
if err = bucket.Put([]byte("DownCredit"), i64ToB(uinfo.DownCredit)); err != nil {
return err
}
if err = bucket.Put([]byte("ExpiryTime"), i64ToB(uinfo.ExpiryTime)); err != nil {
return err
}
return nil
})
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
}
w.WriteHeader(http.StatusCreated)
}
func (manager *localManager) deleteUser(w http.ResponseWriter, r *http.Request) {
b64UID := gmux.Vars(r)["UID"]
if b64UID == "" {
http.Error(w, "UID cannot be empty", http.StatusBadRequest)
return
}
UID, err := base64.URLEncoding.DecodeString(b64UID)
if err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
err = manager.db.Update(func(tx *bolt.Tx) error {
return tx.DeleteBucket(UID)
})
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
}
w.WriteHeader(http.StatusOK)
}

@ -10,7 +10,7 @@ import (
)
type userPanel struct {
manager UserManager
Manager UserManager
activeUsersM sync.RWMutex
activeUsers map[[16]byte]*ActiveUser
@ -20,7 +20,7 @@ type userPanel struct {
func MakeUserPanel(manager UserManager) *userPanel {
ret := &userPanel{
manager: manager,
Manager: manager,
activeUsers: make(map[[16]byte]*ActiveUser),
usageUpdateQueue: make(map[[16]byte]*usagePair),
}
@ -37,13 +37,12 @@ func (panel *userPanel) GetUser(UID []byte) (*ActiveUser, error) {
return user, nil
}
upRate, downRate, err := panel.manager.authenticateUser(UID)
upRate, downRate, err := panel.Manager.authenticateUser(UID)
if err != nil {
panel.activeUsersM.Unlock()
return nil, err
}
var upUsage, downUsage int64
valve := mux.MakeValve(upRate, downRate, &upUsage, &downUsage)
valve := mux.MakeValve(upRate, downRate)
user := &ActiveUser{
panel: panel,
valve: valve,
@ -124,7 +123,7 @@ func (panel *userPanel) commitUpdate() {
statuses = append(statuses, status)
}
responses, err := panel.manager.uploadStatus(statuses)
responses, err := panel.Manager.uploadStatus(statuses)
if err != nil {
log.Println(err)
}

Loading…
Cancel
Save