blob: 7cecd642a37ecc7bc2711ba245569628b229e13b [file] [log] [blame]
// Copyright 2017 The Upspin Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package main
import (
"fmt"
"strings"
dns "google.golang.org/api/dns/v1"
"upspin.io/access"
"upspin.io/cache"
"upspin.io/errors"
"upspin.io/pack"
"upspin.io/path"
"upspin.io/upspin"
"upspin.io/valid"
_ "upspin.io/pack/ee"
_ "upspin.io/transports"
)
// state holds the state for a combined directory and store server that serves
// a file system for interacting with the Cloud DNS service that does DNS
// resolution for the upspin.services domain.
// See the package comment for a full description of the file system.
// A single state struct is shared by all instances of the server (see below).
type state struct {
ep upspin.Endpoint
cfg upspin.Config
accessEntry *upspin.DirEntry
accessBytes []byte
// cache is a cache of recently-packed entries keyed by file path.
// It also acts as a "negative" cache for non-existent entries, in
// which case the cached value is nil.
cache *cache.LRU // [filePath]*entry
dnsSvc *dns.Service
}
// server provides a wrapper around state that provies the upspin.DirServer and
// upspin.StoreServer methods for a particular user.
// A new wrapper is created by each Dial.
type server struct {
user upspin.UserName // Set by Dial.
// state is the state struct shared by all instances of the server.
*state
}
// entry represents a cached DirEntry and its underlying data.
type entry struct {
de *upspin.DirEntry
data []byte
}
type dirServer struct {
*server
}
type storeServer struct {
*server
}
func (s *server) DirServer() upspin.DirServer {
return dirServer{s}
}
func (s *server) StoreServer() upspin.StoreServer {
return storeServer{s}
}
const (
accessRef = upspin.Reference(access.AccessFile)
packing = upspin.EEPack
maxCachedEntries = 1000
)
var accessRefdata = upspin.Refdata{Reference: accessRef}
func newServer(ep upspin.Endpoint, cfg upspin.Config) (*server, error) {
s := &server{
state: &state{
ep: ep,
cfg: cfg,
cache: cache.NewLRU(maxCachedEntries),
},
}
err := s.setupDNSService()
if err != nil {
return nil, err
}
// Allow anyone to write, but only the server user to read.
const accessFile = "read, write: all\n"
s.accessEntry, s.accessBytes, err = s.pack(access.AccessFile, []byte(accessFile))
if err != nil {
return nil, err
}
return s, nil
}
// pack uses packer to pack the named file with the given data and returns the
// resulting DirEntry and ciphertext.
func (s *server) pack(filePath string, data []byte) (*upspin.DirEntry, []byte, error) {
name := path.Join(upspin.PathName(s.cfg.UserName()), filePath)
de := &upspin.DirEntry{
Writer: s.cfg.UserName(),
Name: name,
SignedName: name,
Packing: packing,
Time: upspin.Now(),
Sequence: 1,
}
packer := pack.Lookup(packing)
bp, err := packer.Pack(s.cfg, de)
if err != nil {
return nil, nil, err
}
cipher, err := bp.Pack(data)
if err != nil {
return nil, nil, err
}
bp.SetLocation(upspin.Location{
Endpoint: s.ep,
Reference: upspin.Reference(filePath),
})
if err := bp.Close(); err != nil {
return nil, nil, err
}
// Share with all.
packer.Share(s.cfg, []upspin.PublicKey{upspin.AllUsersKey}, []*[]byte{&de.Packdata})
return de, cipher, nil
}
// packHost packs a file for the user containing the host and IP address.
// It then returns the entry after adding it to the cache.
func (s *server) packHost(name upspin.UserName, ip, host string) (e *entry, err error) {
e = &entry{}
e.de, e.data, err = s.pack(string(name), []byte(fmt.Sprintf("%s\n%s\n", ip, host)))
if err != nil {
return nil, err
}
s.cache.Add(name, e)
return e, nil
}
// lookup returns the entry for the given user. It first consults the cache.
// If the entry is unknown, it invokes lookupName to find the user's host
// information, packs the resulting data, and updates the cache.
func (s *server) lookup(name upspin.UserName) (*entry, error) {
if err := valid.UserName(name); err != nil {
return nil, err
}
ei, ok := s.cache.Get(name)
if ok {
if ei == nil {
return nil, errors.E(name, errors.NotExist)
}
return ei.(*entry), nil
}
ip, host, err := s.lookupName(name)
if errors.Match(errors.E(errors.NotExist), err) {
s.cache.Add(name, nil)
return nil, err
}
if err != nil {
return nil, err
}
return s.packHost(name, ip, host)
}
// These methods implement upspin.Service.
func (s *server) Endpoint() upspin.Endpoint { return s.ep }
func (*server) Close() {}
// These methods implement upspin.Dialer.
func (s storeServer) Dial(cfg upspin.Config, _ upspin.Endpoint) (upspin.Service, error) {
return (&server{
user: cfg.UserName(),
state: s.state,
}).StoreServer(), nil
}
func (s dirServer) Dial(cfg upspin.Config, _ upspin.Endpoint) (upspin.Service, error) {
return (&server{
user: cfg.UserName(),
state: s.state,
}).DirServer(), nil
}
// These methods implement upspin.DirServer.
func (s dirServer) Lookup(name upspin.PathName) (*upspin.DirEntry, error) {
if name == s.accessEntry.Name {
return s.accessEntry, nil
}
p, err := path.Parse(name)
if err != nil {
return nil, err
}
if p.User() != s.cfg.UserName() {
return nil, errors.E(name, errors.NotExist)
}
if p.FilePath() == "" {
return &upspin.DirEntry{
SignedName: name,
Attr: upspin.AttrDirectory,
Packing: upspin.PlainPack,
Time: upspin.Now(),
Name: name,
Writer: s.cfg.UserName(),
}, nil
}
e, err := s.lookup(upspin.UserName(p.FilePath()))
if err != nil {
return nil, errors.E(name, err)
}
return e.de, nil
}
func (s dirServer) Glob(pattern string) ([]*upspin.DirEntry, error) {
// Nobody has list access.
return nil, errors.E(errors.Private)
}
func (s dirServer) Put(de *upspin.DirEntry) (*upspin.DirEntry, error) {
if err := valid.DirEntry(de); err != nil {
return nil, err
}
p, err := path.Parse(de.Name)
if err != nil {
return nil, err
}
if p.User() != s.cfg.UserName() {
return nil, errors.E(de.Name, errors.NotExist)
}
parts := strings.Split(p.FilePath(), "/")
if len(parts) != 2 {
return nil, errors.E(errors.Permission, de.Name, "file names must be of the form user@example.com/ip")
}
user, ip := upspin.UserName(parts[0]), parts[1]
if user != s.user {
return nil, errors.E(errors.Permission, de.Name)
}
host, err := s.updateName(user, ip)
if err != nil {
return nil, err
}
_, err = s.packHost(user, ip, host)
return nil, err
}
func (s dirServer) WhichAccess(name upspin.PathName) (*upspin.DirEntry, error) {
return s.accessEntry, nil
}
// This method implements upspin.StoreServer.
func (s storeServer) Get(ref upspin.Reference) ([]byte, *upspin.Refdata, []upspin.Location, error) {
if ref == accessRef {
return s.accessBytes, &accessRefdata, nil, nil
}
e, err := s.lookup(upspin.UserName(ref))
if err != nil {
return nil, nil, nil, err
}
return e.data, &upspin.Refdata{Reference: ref, Volatile: true}, nil, nil
}
// The DirServer and StoreServer methods below are not implemented.
var errNotImplemented = errors.E(errors.Invalid, "method not implemented")
func (dirServer) Delete(name upspin.PathName) (*upspin.DirEntry, error) {
return nil, errNotImplemented
}
func (dirServer) Watch(_ upspin.PathName, _ int64, _ <-chan struct{}) (<-chan upspin.Event, error) {
return nil, upspin.ErrNotSupported
}
func (storeServer) Put(data []byte) (*upspin.Refdata, error) {
return nil, errNotImplemented
}
func (storeServer) Delete(ref upspin.Reference) error {
return errNotImplemented
}