1
0

3 Incheckningar 2ebbefc9f6 ... 3a8d9b2846

Upphovsman SHA1 Meddelande Datum
  Alexey Edelev 3a8d9b2846 Add setup scenario and fix annoying bugs 2 år sedan
  Alexey Edelev 4afce80e73 Various fixes 2 år sedan
  Alexey Edelev 2ebbefc9f6 Various fixes 2 år sedan
15 ändrade filer med 331 tillägg och 947 borttagningar
  1. 2 0
      .gitignore
  2. 19 17
      auth/authenticator.go
  3. 4 4
      build.sh
  4. 0 804
      common/gostfix.pb.go
  5. 2 2
      common/gostfix.proto
  6. 71 39
      config/config.go
  7. 38 16
      config/main.ini.default
  8. 30 4
      db/db.go
  9. 9 7
      go.mod
  10. 50 31
      go.sum
  11. 3 2
      main.go
  12. 0 2
      scanner/mailscanner.go
  13. 88 0
      setup/setup.go
  14. 1 1
      views
  15. 14 18
      web/auth.go

+ 2 - 0
.gitignore

@@ -25,3 +25,5 @@ _testmain.go
 *.prof
 
 data/
+
+common/gostfix.pb.go

+ 19 - 17
auth/authenticator.go

@@ -210,28 +210,30 @@ func (a *Authenticator) checkToken(user, token string) error {
 				Expire int64
 			}
 		}{}
-
+		
 		err = cur.Decode(&result)
-
-		ok = err == nil && result.Token.Expire >= time.Now().Unix()
+		
+		ok = err == nil && (config.ConfigInstance().WebSessionExpireTime <= 0 || result.Token.Expire >= time.Now().Unix())
 	}
 
 	if ok {
-		opts := options.Update().SetArrayFilters(options.ArrayFilters{
-			Registry: bson.DefaultRegistry,
-			Filters: bson.A{
-				bson.M{"element.token": "b3a612c1-a56c-4465-8071-4250ec5de79d"},
-			}})
-		a.tokensCollection.UpdateOne(context.Background(),
-			bson.M{
-				"user": user,
-			},
-			bson.M{
-				"$set": bson.M{
-					"token.$[element].expire": time.Now().Add(time.Hour * 24).Unix(),
+		if config.ConfigInstance().WebSessionExpireTime > 0 {
+			opts := options.Update().SetArrayFilters(options.ArrayFilters{
+				Registry: bson.DefaultRegistry,
+				Filters: bson.A{
+					bson.M{"element.token": token},
+				}})
+			a.tokensCollection.UpdateOne(context.Background(),
+				bson.M{
+					"user": user,
 				},
-			},
-			opts)
+				bson.M{
+					"$set": bson.M{
+						"token.$[element].expire": time.Now().Add(config.ConfigInstance().WebSessionExpireTime).Unix(),
+					},
+				},
+				opts)
+		}
 		return nil
 	}
 

+ 4 - 4
build.sh

@@ -8,14 +8,14 @@ go install github.com/amsokol/protoc-gen-gotag
 
 # mkdir -p $RPC_PATH
 rm -f $RPC_PATH/*.pb.go
-protoc -I$RPC_PATH --go_out=plugins=grpc:$RPC_PATH $RPC_PATH/gostfix.proto
+protoc -I$RPC_PATH --go_out=plugins=grpc:$PWD $RPC_PATH/gostfix.proto
 
 protoc -I$RPC_PATH --gotag_out=xxx="bson+\"-\"",output_path=$RPC_PATH:. $RPC_PATH/gostfix.proto
 
 #echo "Installing data"
-rm -rf data
-mkdir data
-cp -a main.ini data/
+#rm -rf data
+#mkdir data
+#cp -a main.ini data/
 #cp -a main.cf data/
 #cp -a vmailbox.db data/
 cp -a web/assets data/

+ 0 - 804
common/gostfix.pb.go

@@ -1,804 +0,0 @@
-// Code generated by protoc-gen-go. DO NOT EDIT.
-// versions:
-// 	protoc-gen-go v1.25.0
-// 	protoc        v3.6.1
-// source: gostfix.proto
-
-package common
-
-import (
-	proto "github.com/golang/protobuf/proto"
-	protoreflect "google.golang.org/protobuf/reflect/protoreflect"
-	protoimpl "google.golang.org/protobuf/runtime/protoimpl"
-	reflect "reflect"
-	sync "sync"
-)
-
-const (
-	// Verify that this generated code is sufficiently up-to-date.
-	_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
-	// Verify that runtime/protoimpl is sufficiently up-to-date.
-	_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
-)
-
-// This is a compile-time assertion that a sufficiently up-to-date version
-// of the legacy proto package is being used.
-const _ = proto.ProtoPackageIsVersion4
-
-type MailBody struct {
-	state         protoimpl.MessageState
-	sizeCache     protoimpl.SizeCache
-	unknownFields protoimpl.UnknownFields
-
-	PlainText   string              `protobuf:"bytes,1,opt,name=plainText,proto3" json:"plainText,omitempty"`
-	RichText    string              `protobuf:"bytes,2,opt,name=richText,proto3" json:"richText,omitempty"`
-	Attachments []*AttachmentHeader `protobuf:"bytes,3,rep,name=attachments,proto3" json:"attachments,omitempty"`
-}
-
-func (x *MailBody) Reset() {
-	*x = MailBody{}
-	if protoimpl.UnsafeEnabled {
-		mi := &file_gostfix_proto_msgTypes[0]
-		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
-		ms.StoreMessageInfo(mi)
-	}
-}
-
-func (x *MailBody) String() string {
-	return protoimpl.X.MessageStringOf(x)
-}
-
-func (*MailBody) ProtoMessage() {}
-
-func (x *MailBody) ProtoReflect() protoreflect.Message {
-	mi := &file_gostfix_proto_msgTypes[0]
-	if protoimpl.UnsafeEnabled && x != nil {
-		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
-		if ms.LoadMessageInfo() == nil {
-			ms.StoreMessageInfo(mi)
-		}
-		return ms
-	}
-	return mi.MessageOf(x)
-}
-
-// Deprecated: Use MailBody.ProtoReflect.Descriptor instead.
-func (*MailBody) Descriptor() ([]byte, []int) {
-	return file_gostfix_proto_rawDescGZIP(), []int{0}
-}
-
-func (x *MailBody) GetPlainText() string {
-	if x != nil {
-		return x.PlainText
-	}
-	return ""
-}
-
-func (x *MailBody) GetRichText() string {
-	if x != nil {
-		return x.RichText
-	}
-	return ""
-}
-
-func (x *MailBody) GetAttachments() []*AttachmentHeader {
-	if x != nil {
-		return x.Attachments
-	}
-	return nil
-}
-
-type MailHeader struct {
-	state         protoimpl.MessageState
-	sizeCache     protoimpl.SizeCache
-	unknownFields protoimpl.UnknownFields
-
-	From    string `protobuf:"bytes,1,opt,name=from,proto3" json:"from,omitempty"`
-	To      string `protobuf:"bytes,2,opt,name=to,proto3" json:"to,omitempty"`
-	Cc      string `protobuf:"bytes,3,opt,name=cc,proto3" json:"cc,omitempty"`
-	Bcc     string `protobuf:"bytes,4,opt,name=bcc,proto3" json:"bcc,omitempty"`
-	Date    int64  `protobuf:"zigzag64,5,opt,name=date,proto3" json:"date,omitempty"`
-	Subject string `protobuf:"bytes,6,opt,name=subject,proto3" json:"subject,omitempty"`
-}
-
-func (x *MailHeader) Reset() {
-	*x = MailHeader{}
-	if protoimpl.UnsafeEnabled {
-		mi := &file_gostfix_proto_msgTypes[1]
-		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
-		ms.StoreMessageInfo(mi)
-	}
-}
-
-func (x *MailHeader) String() string {
-	return protoimpl.X.MessageStringOf(x)
-}
-
-func (*MailHeader) ProtoMessage() {}
-
-func (x *MailHeader) ProtoReflect() protoreflect.Message {
-	mi := &file_gostfix_proto_msgTypes[1]
-	if protoimpl.UnsafeEnabled && x != nil {
-		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
-		if ms.LoadMessageInfo() == nil {
-			ms.StoreMessageInfo(mi)
-		}
-		return ms
-	}
-	return mi.MessageOf(x)
-}
-
-// Deprecated: Use MailHeader.ProtoReflect.Descriptor instead.
-func (*MailHeader) Descriptor() ([]byte, []int) {
-	return file_gostfix_proto_rawDescGZIP(), []int{1}
-}
-
-func (x *MailHeader) GetFrom() string {
-	if x != nil {
-		return x.From
-	}
-	return ""
-}
-
-func (x *MailHeader) GetTo() string {
-	if x != nil {
-		return x.To
-	}
-	return ""
-}
-
-func (x *MailHeader) GetCc() string {
-	if x != nil {
-		return x.Cc
-	}
-	return ""
-}
-
-func (x *MailHeader) GetBcc() string {
-	if x != nil {
-		return x.Bcc
-	}
-	return ""
-}
-
-func (x *MailHeader) GetDate() int64 {
-	if x != nil {
-		return x.Date
-	}
-	return 0
-}
-
-func (x *MailHeader) GetSubject() string {
-	if x != nil {
-		return x.Subject
-	}
-	return ""
-}
-
-type Mail struct {
-	state         protoimpl.MessageState
-	sizeCache     protoimpl.SizeCache
-	unknownFields protoimpl.UnknownFields
-
-	Header *MailHeader `protobuf:"bytes,1,opt,name=header,proto3" json:"header,omitempty"`
-	Body   *MailBody   `protobuf:"bytes,2,opt,name=body,proto3" json:"body,omitempty"`
-}
-
-func (x *Mail) Reset() {
-	*x = Mail{}
-	if protoimpl.UnsafeEnabled {
-		mi := &file_gostfix_proto_msgTypes[2]
-		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
-		ms.StoreMessageInfo(mi)
-	}
-}
-
-func (x *Mail) String() string {
-	return protoimpl.X.MessageStringOf(x)
-}
-
-func (*Mail) ProtoMessage() {}
-
-func (x *Mail) ProtoReflect() protoreflect.Message {
-	mi := &file_gostfix_proto_msgTypes[2]
-	if protoimpl.UnsafeEnabled && x != nil {
-		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
-		if ms.LoadMessageInfo() == nil {
-			ms.StoreMessageInfo(mi)
-		}
-		return ms
-	}
-	return mi.MessageOf(x)
-}
-
-// Deprecated: Use Mail.ProtoReflect.Descriptor instead.
-func (*Mail) Descriptor() ([]byte, []int) {
-	return file_gostfix_proto_rawDescGZIP(), []int{2}
-}
-
-func (x *Mail) GetHeader() *MailHeader {
-	if x != nil {
-		return x.Header
-	}
-	return nil
-}
-
-func (x *Mail) GetBody() *MailBody {
-	if x != nil {
-		return x.Body
-	}
-	return nil
-}
-
-type Attachment struct {
-	state         protoimpl.MessageState
-	sizeCache     protoimpl.SizeCache
-	unknownFields protoimpl.UnknownFields
-
-	Header *AttachmentHeader `protobuf:"bytes,1,opt,name=header,proto3" json:"header,omitempty"`
-	Data   []byte            `protobuf:"bytes,2,opt,name=data,proto3" json:"data,omitempty"`
-}
-
-func (x *Attachment) Reset() {
-	*x = Attachment{}
-	if protoimpl.UnsafeEnabled {
-		mi := &file_gostfix_proto_msgTypes[3]
-		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
-		ms.StoreMessageInfo(mi)
-	}
-}
-
-func (x *Attachment) String() string {
-	return protoimpl.X.MessageStringOf(x)
-}
-
-func (*Attachment) ProtoMessage() {}
-
-func (x *Attachment) ProtoReflect() protoreflect.Message {
-	mi := &file_gostfix_proto_msgTypes[3]
-	if protoimpl.UnsafeEnabled && x != nil {
-		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
-		if ms.LoadMessageInfo() == nil {
-			ms.StoreMessageInfo(mi)
-		}
-		return ms
-	}
-	return mi.MessageOf(x)
-}
-
-// Deprecated: Use Attachment.ProtoReflect.Descriptor instead.
-func (*Attachment) Descriptor() ([]byte, []int) {
-	return file_gostfix_proto_rawDescGZIP(), []int{3}
-}
-
-func (x *Attachment) GetHeader() *AttachmentHeader {
-	if x != nil {
-		return x.Header
-	}
-	return nil
-}
-
-func (x *Attachment) GetData() []byte {
-	if x != nil {
-		return x.Data
-	}
-	return nil
-}
-
-type AttachmentHeader struct {
-	state         protoimpl.MessageState
-	sizeCache     protoimpl.SizeCache
-	unknownFields protoimpl.UnknownFields
-
-	Id          string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"`
-	FileName    string `protobuf:"bytes,2,opt,name=fileName,proto3" json:"fileName,omitempty"`
-	ContentType string `protobuf:"bytes,3,opt,name=contentType,proto3" json:"contentType,omitempty"`
-}
-
-func (x *AttachmentHeader) Reset() {
-	*x = AttachmentHeader{}
-	if protoimpl.UnsafeEnabled {
-		mi := &file_gostfix_proto_msgTypes[4]
-		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
-		ms.StoreMessageInfo(mi)
-	}
-}
-
-func (x *AttachmentHeader) String() string {
-	return protoimpl.X.MessageStringOf(x)
-}
-
-func (*AttachmentHeader) ProtoMessage() {}
-
-func (x *AttachmentHeader) ProtoReflect() protoreflect.Message {
-	mi := &file_gostfix_proto_msgTypes[4]
-	if protoimpl.UnsafeEnabled && x != nil {
-		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
-		if ms.LoadMessageInfo() == nil {
-			ms.StoreMessageInfo(mi)
-		}
-		return ms
-	}
-	return mi.MessageOf(x)
-}
-
-// Deprecated: Use AttachmentHeader.ProtoReflect.Descriptor instead.
-func (*AttachmentHeader) Descriptor() ([]byte, []int) {
-	return file_gostfix_proto_rawDescGZIP(), []int{4}
-}
-
-func (x *AttachmentHeader) GetId() string {
-	if x != nil {
-		return x.Id
-	}
-	return ""
-}
-
-func (x *AttachmentHeader) GetFileName() string {
-	if x != nil {
-		return x.FileName
-	}
-	return ""
-}
-
-func (x *AttachmentHeader) GetContentType() string {
-	if x != nil {
-		return x.ContentType
-	}
-	return ""
-}
-
-type UserInfo struct {
-	state         protoimpl.MessageState
-	sizeCache     protoimpl.SizeCache
-	unknownFields protoimpl.UnknownFields
-
-	User     string `protobuf:"bytes,1,opt,name=user,proto3" json:"user,omitempty"`
-	FullName string `protobuf:"bytes,2,opt,name=fullName,proto3" json:"fullName,omitempty"`
-}
-
-func (x *UserInfo) Reset() {
-	*x = UserInfo{}
-	if protoimpl.UnsafeEnabled {
-		mi := &file_gostfix_proto_msgTypes[5]
-		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
-		ms.StoreMessageInfo(mi)
-	}
-}
-
-func (x *UserInfo) String() string {
-	return protoimpl.X.MessageStringOf(x)
-}
-
-func (*UserInfo) ProtoMessage() {}
-
-func (x *UserInfo) ProtoReflect() protoreflect.Message {
-	mi := &file_gostfix_proto_msgTypes[5]
-	if protoimpl.UnsafeEnabled && x != nil {
-		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
-		if ms.LoadMessageInfo() == nil {
-			ms.StoreMessageInfo(mi)
-		}
-		return ms
-	}
-	return mi.MessageOf(x)
-}
-
-// Deprecated: Use UserInfo.ProtoReflect.Descriptor instead.
-func (*UserInfo) Descriptor() ([]byte, []int) {
-	return file_gostfix_proto_rawDescGZIP(), []int{5}
-}
-
-func (x *UserInfo) GetUser() string {
-	if x != nil {
-		return x.User
-	}
-	return ""
-}
-
-func (x *UserInfo) GetFullName() string {
-	if x != nil {
-		return x.FullName
-	}
-	return ""
-}
-
-type Frame struct {
-	state         protoimpl.MessageState
-	sizeCache     protoimpl.SizeCache
-	unknownFields protoimpl.UnknownFields
-
-	Skip  int32 `protobuf:"zigzag32,1,opt,name=skip,proto3" json:"skip,omitempty"`
-	Limit int32 `protobuf:"zigzag32,2,opt,name=limit,proto3" json:"limit,omitempty"`
-}
-
-func (x *Frame) Reset() {
-	*x = Frame{}
-	if protoimpl.UnsafeEnabled {
-		mi := &file_gostfix_proto_msgTypes[6]
-		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
-		ms.StoreMessageInfo(mi)
-	}
-}
-
-func (x *Frame) String() string {
-	return protoimpl.X.MessageStringOf(x)
-}
-
-func (*Frame) ProtoMessage() {}
-
-func (x *Frame) ProtoReflect() protoreflect.Message {
-	mi := &file_gostfix_proto_msgTypes[6]
-	if protoimpl.UnsafeEnabled && x != nil {
-		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
-		if ms.LoadMessageInfo() == nil {
-			ms.StoreMessageInfo(mi)
-		}
-		return ms
-	}
-	return mi.MessageOf(x)
-}
-
-// Deprecated: Use Frame.ProtoReflect.Descriptor instead.
-func (*Frame) Descriptor() ([]byte, []int) {
-	return file_gostfix_proto_rawDescGZIP(), []int{6}
-}
-
-func (x *Frame) GetSkip() int32 {
-	if x != nil {
-		return x.Skip
-	}
-	return 0
-}
-
-func (x *Frame) GetLimit() int32 {
-	if x != nil {
-		return x.Limit
-	}
-	return 0
-}
-
-type Folder struct {
-	state         protoimpl.MessageState
-	sizeCache     protoimpl.SizeCache
-	unknownFields protoimpl.UnknownFields
-
-	Name   string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"`
-	Custom bool   `protobuf:"varint,2,opt,name=custom,proto3" json:"custom,omitempty"`
-}
-
-func (x *Folder) Reset() {
-	*x = Folder{}
-	if protoimpl.UnsafeEnabled {
-		mi := &file_gostfix_proto_msgTypes[7]
-		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
-		ms.StoreMessageInfo(mi)
-	}
-}
-
-func (x *Folder) String() string {
-	return protoimpl.X.MessageStringOf(x)
-}
-
-func (*Folder) ProtoMessage() {}
-
-func (x *Folder) ProtoReflect() protoreflect.Message {
-	mi := &file_gostfix_proto_msgTypes[7]
-	if protoimpl.UnsafeEnabled && x != nil {
-		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
-		if ms.LoadMessageInfo() == nil {
-			ms.StoreMessageInfo(mi)
-		}
-		return ms
-	}
-	return mi.MessageOf(x)
-}
-
-// Deprecated: Use Folder.ProtoReflect.Descriptor instead.
-func (*Folder) Descriptor() ([]byte, []int) {
-	return file_gostfix_proto_rawDescGZIP(), []int{7}
-}
-
-func (x *Folder) GetName() string {
-	if x != nil {
-		return x.Name
-	}
-	return ""
-}
-
-func (x *Folder) GetCustom() bool {
-	if x != nil {
-		return x.Custom
-	}
-	return false
-}
-
-type FolderStat struct {
-	state         protoimpl.MessageState
-	sizeCache     protoimpl.SizeCache
-	unknownFields protoimpl.UnknownFields
-
-	Folder string `protobuf:"bytes,1,opt,name=folder,proto3" json:"folder,omitempty"`
-	Total  uint32 `protobuf:"varint,2,opt,name=total,proto3" json:"total,omitempty"`
-	Unread uint32 `protobuf:"varint,3,opt,name=unread,proto3" json:"unread,omitempty"`
-}
-
-func (x *FolderStat) Reset() {
-	*x = FolderStat{}
-	if protoimpl.UnsafeEnabled {
-		mi := &file_gostfix_proto_msgTypes[8]
-		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
-		ms.StoreMessageInfo(mi)
-	}
-}
-
-func (x *FolderStat) String() string {
-	return protoimpl.X.MessageStringOf(x)
-}
-
-func (*FolderStat) ProtoMessage() {}
-
-func (x *FolderStat) ProtoReflect() protoreflect.Message {
-	mi := &file_gostfix_proto_msgTypes[8]
-	if protoimpl.UnsafeEnabled && x != nil {
-		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
-		if ms.LoadMessageInfo() == nil {
-			ms.StoreMessageInfo(mi)
-		}
-		return ms
-	}
-	return mi.MessageOf(x)
-}
-
-// Deprecated: Use FolderStat.ProtoReflect.Descriptor instead.
-func (*FolderStat) Descriptor() ([]byte, []int) {
-	return file_gostfix_proto_rawDescGZIP(), []int{8}
-}
-
-func (x *FolderStat) GetFolder() string {
-	if x != nil {
-		return x.Folder
-	}
-	return ""
-}
-
-func (x *FolderStat) GetTotal() uint32 {
-	if x != nil {
-		return x.Total
-	}
-	return 0
-}
-
-func (x *FolderStat) GetUnread() uint32 {
-	if x != nil {
-		return x.Unread
-	}
-	return 0
-}
-
-var File_gostfix_proto protoreflect.FileDescriptor
-
-var file_gostfix_proto_rawDesc = []byte{
-	0x0a, 0x0d, 0x67, 0x6f, 0x73, 0x74, 0x66, 0x69, 0x78, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12,
-	0x06, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x22, 0x80, 0x01, 0x0a, 0x08, 0x4d, 0x61, 0x69, 0x6c,
-	0x42, 0x6f, 0x64, 0x79, 0x12, 0x1c, 0x0a, 0x09, 0x70, 0x6c, 0x61, 0x69, 0x6e, 0x54, 0x65, 0x78,
-	0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x70, 0x6c, 0x61, 0x69, 0x6e, 0x54, 0x65,
-	0x78, 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x72, 0x69, 0x63, 0x68, 0x54, 0x65, 0x78, 0x74, 0x18, 0x02,
-	0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x72, 0x69, 0x63, 0x68, 0x54, 0x65, 0x78, 0x74, 0x12, 0x3a,
-	0x0a, 0x0b, 0x61, 0x74, 0x74, 0x61, 0x63, 0x68, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x18, 0x03, 0x20,
-	0x03, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x41, 0x74, 0x74,
-	0x61, 0x63, 0x68, 0x6d, 0x65, 0x6e, 0x74, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x52, 0x0b, 0x61,
-	0x74, 0x74, 0x61, 0x63, 0x68, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x22, 0x80, 0x01, 0x0a, 0x0a, 0x4d,
-	0x61, 0x69, 0x6c, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x12, 0x12, 0x0a, 0x04, 0x66, 0x72, 0x6f,
-	0x6d, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x66, 0x72, 0x6f, 0x6d, 0x12, 0x0e, 0x0a,
-	0x02, 0x74, 0x6f, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x74, 0x6f, 0x12, 0x0e, 0x0a,
-	0x02, 0x63, 0x63, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x63, 0x63, 0x12, 0x10, 0x0a,
-	0x03, 0x62, 0x63, 0x63, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x62, 0x63, 0x63, 0x12,
-	0x12, 0x0a, 0x04, 0x64, 0x61, 0x74, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x12, 0x52, 0x04, 0x64,
-	0x61, 0x74, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x18, 0x06,
-	0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x22, 0x58, 0x0a,
-	0x04, 0x4d, 0x61, 0x69, 0x6c, 0x12, 0x2a, 0x0a, 0x06, 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x18,
-	0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x4d,
-	0x61, 0x69, 0x6c, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x52, 0x06, 0x68, 0x65, 0x61, 0x64, 0x65,
-	0x72, 0x12, 0x24, 0x0a, 0x04, 0x62, 0x6f, 0x64, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32,
-	0x10, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x4d, 0x61, 0x69, 0x6c, 0x42, 0x6f, 0x64,
-	0x79, 0x52, 0x04, 0x62, 0x6f, 0x64, 0x79, 0x22, 0x52, 0x0a, 0x0a, 0x41, 0x74, 0x74, 0x61, 0x63,
-	0x68, 0x6d, 0x65, 0x6e, 0x74, 0x12, 0x30, 0x0a, 0x06, 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x18,
-	0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x41,
-	0x74, 0x74, 0x61, 0x63, 0x68, 0x6d, 0x65, 0x6e, 0x74, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x52,
-	0x06, 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x12, 0x12, 0x0a, 0x04, 0x64, 0x61, 0x74, 0x61, 0x18,
-	0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x04, 0x64, 0x61, 0x74, 0x61, 0x22, 0x60, 0x0a, 0x10, 0x41,
-	0x74, 0x74, 0x61, 0x63, 0x68, 0x6d, 0x65, 0x6e, 0x74, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x12,
-	0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12,
-	0x1a, 0x0a, 0x08, 0x66, 0x69, 0x6c, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28,
-	0x09, 0x52, 0x08, 0x66, 0x69, 0x6c, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x20, 0x0a, 0x0b, 0x63,
-	0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x54, 0x79, 0x70, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09,
-	0x52, 0x0b, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x54, 0x79, 0x70, 0x65, 0x22, 0x3a, 0x0a,
-	0x08, 0x55, 0x73, 0x65, 0x72, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x12, 0x0a, 0x04, 0x75, 0x73, 0x65,
-	0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x75, 0x73, 0x65, 0x72, 0x12, 0x1a, 0x0a,
-	0x08, 0x66, 0x75, 0x6c, 0x6c, 0x4e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52,
-	0x08, 0x66, 0x75, 0x6c, 0x6c, 0x4e, 0x61, 0x6d, 0x65, 0x22, 0x31, 0x0a, 0x05, 0x46, 0x72, 0x61,
-	0x6d, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x73, 0x6b, 0x69, 0x70, 0x18, 0x01, 0x20, 0x01, 0x28, 0x11,
-	0x52, 0x04, 0x73, 0x6b, 0x69, 0x70, 0x12, 0x14, 0x0a, 0x05, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x18,
-	0x02, 0x20, 0x01, 0x28, 0x11, 0x52, 0x05, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x22, 0x34, 0x0a, 0x06,
-	0x46, 0x6f, 0x6c, 0x64, 0x65, 0x72, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01,
-	0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x63, 0x75,
-	0x73, 0x74, 0x6f, 0x6d, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x06, 0x63, 0x75, 0x73, 0x74,
-	0x6f, 0x6d, 0x22, 0x52, 0x0a, 0x0a, 0x46, 0x6f, 0x6c, 0x64, 0x65, 0x72, 0x53, 0x74, 0x61, 0x74,
-	0x12, 0x16, 0x0a, 0x06, 0x66, 0x6f, 0x6c, 0x64, 0x65, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09,
-	0x52, 0x06, 0x66, 0x6f, 0x6c, 0x64, 0x65, 0x72, 0x12, 0x14, 0x0a, 0x05, 0x74, 0x6f, 0x74, 0x61,
-	0x6c, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x05, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x12, 0x16,
-	0x0a, 0x06, 0x75, 0x6e, 0x72, 0x65, 0x61, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x06,
-	0x75, 0x6e, 0x72, 0x65, 0x61, 0x64, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
-}
-
-var (
-	file_gostfix_proto_rawDescOnce sync.Once
-	file_gostfix_proto_rawDescData = file_gostfix_proto_rawDesc
-)
-
-func file_gostfix_proto_rawDescGZIP() []byte {
-	file_gostfix_proto_rawDescOnce.Do(func() {
-		file_gostfix_proto_rawDescData = protoimpl.X.CompressGZIP(file_gostfix_proto_rawDescData)
-	})
-	return file_gostfix_proto_rawDescData
-}
-
-var file_gostfix_proto_msgTypes = make([]protoimpl.MessageInfo, 9)
-var file_gostfix_proto_goTypes = []interface{}{
-	(*MailBody)(nil),         // 0: common.MailBody
-	(*MailHeader)(nil),       // 1: common.MailHeader
-	(*Mail)(nil),             // 2: common.Mail
-	(*Attachment)(nil),       // 3: common.Attachment
-	(*AttachmentHeader)(nil), // 4: common.AttachmentHeader
-	(*UserInfo)(nil),         // 5: common.UserInfo
-	(*Frame)(nil),            // 6: common.Frame
-	(*Folder)(nil),           // 7: common.Folder
-	(*FolderStat)(nil),       // 8: common.FolderStat
-}
-var file_gostfix_proto_depIdxs = []int32{
-	4, // 0: common.MailBody.attachments:type_name -> common.AttachmentHeader
-	1, // 1: common.Mail.header:type_name -> common.MailHeader
-	0, // 2: common.Mail.body:type_name -> common.MailBody
-	4, // 3: common.Attachment.header:type_name -> common.AttachmentHeader
-	4, // [4:4] is the sub-list for method output_type
-	4, // [4:4] is the sub-list for method input_type
-	4, // [4:4] is the sub-list for extension type_name
-	4, // [4:4] is the sub-list for extension extendee
-	0, // [0:4] is the sub-list for field type_name
-}
-
-func init() { file_gostfix_proto_init() }
-func file_gostfix_proto_init() {
-	if File_gostfix_proto != nil {
-		return
-	}
-	if !protoimpl.UnsafeEnabled {
-		file_gostfix_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
-			switch v := v.(*MailBody); i {
-			case 0:
-				return &v.state
-			case 1:
-				return &v.sizeCache
-			case 2:
-				return &v.unknownFields
-			default:
-				return nil
-			}
-		}
-		file_gostfix_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} {
-			switch v := v.(*MailHeader); i {
-			case 0:
-				return &v.state
-			case 1:
-				return &v.sizeCache
-			case 2:
-				return &v.unknownFields
-			default:
-				return nil
-			}
-		}
-		file_gostfix_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} {
-			switch v := v.(*Mail); i {
-			case 0:
-				return &v.state
-			case 1:
-				return &v.sizeCache
-			case 2:
-				return &v.unknownFields
-			default:
-				return nil
-			}
-		}
-		file_gostfix_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} {
-			switch v := v.(*Attachment); i {
-			case 0:
-				return &v.state
-			case 1:
-				return &v.sizeCache
-			case 2:
-				return &v.unknownFields
-			default:
-				return nil
-			}
-		}
-		file_gostfix_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} {
-			switch v := v.(*AttachmentHeader); i {
-			case 0:
-				return &v.state
-			case 1:
-				return &v.sizeCache
-			case 2:
-				return &v.unknownFields
-			default:
-				return nil
-			}
-		}
-		file_gostfix_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} {
-			switch v := v.(*UserInfo); i {
-			case 0:
-				return &v.state
-			case 1:
-				return &v.sizeCache
-			case 2:
-				return &v.unknownFields
-			default:
-				return nil
-			}
-		}
-		file_gostfix_proto_msgTypes[6].Exporter = func(v interface{}, i int) interface{} {
-			switch v := v.(*Frame); i {
-			case 0:
-				return &v.state
-			case 1:
-				return &v.sizeCache
-			case 2:
-				return &v.unknownFields
-			default:
-				return nil
-			}
-		}
-		file_gostfix_proto_msgTypes[7].Exporter = func(v interface{}, i int) interface{} {
-			switch v := v.(*Folder); i {
-			case 0:
-				return &v.state
-			case 1:
-				return &v.sizeCache
-			case 2:
-				return &v.unknownFields
-			default:
-				return nil
-			}
-		}
-		file_gostfix_proto_msgTypes[8].Exporter = func(v interface{}, i int) interface{} {
-			switch v := v.(*FolderStat); i {
-			case 0:
-				return &v.state
-			case 1:
-				return &v.sizeCache
-			case 2:
-				return &v.unknownFields
-			default:
-				return nil
-			}
-		}
-	}
-	type x struct{}
-	out := protoimpl.TypeBuilder{
-		File: protoimpl.DescBuilder{
-			GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
-			RawDescriptor: file_gostfix_proto_rawDesc,
-			NumEnums:      0,
-			NumMessages:   9,
-			NumExtensions: 0,
-			NumServices:   0,
-		},
-		GoTypes:           file_gostfix_proto_goTypes,
-		DependencyIndexes: file_gostfix_proto_depIdxs,
-		MessageInfos:      file_gostfix_proto_msgTypes,
-	}.Build()
-	File_gostfix_proto = out.File
-	file_gostfix_proto_rawDesc = nil
-	file_gostfix_proto_goTypes = nil
-	file_gostfix_proto_depIdxs = nil
-}

+ 2 - 2
common/gostfix.proto

@@ -1,5 +1,5 @@
 syntax = "proto3";
-
+option go_package = "./common";
 package common;
 
 message MailBody {
@@ -52,4 +52,4 @@ message FolderStat {
 	string folder = 1;
 	uint32 total = 2;
 	uint32 unread = 3;
-}
+}

+ 71 - 39
config/config.go

@@ -29,6 +29,7 @@ import (
 	"log"
 	"strings"
 	"sync"
+	"time"
 
 	utils "git.semlanik.org/semlanik/gostfix/utils"
 	ini "gopkg.in/go-ini/ini.v1"
@@ -37,16 +38,27 @@ import (
 const configPath = "data/main.ini"
 
 const (
-	KeyWebPort             = "web_port"
-	KeySASLPort            = "sasl_port"
-	KeyPostfixConfig       = "postfix_config"
-	KeyMongoAddress        = "mongo_address"
-	KeyMongoUser           = "mongo_user"
-	KeyMongoPassword       = "mongo_password"
-	KeyAttachmentsPath     = "attachments_path"
-	KeyAttachmentsUser     = "attachments_user"
-	KeyAttachmentsPassword = "attachments_password"
-	KeyRegistrationEnabled = "registration_enabled"
+	KeyWebPort              = "web_port"
+	KeySASLPort             = "sasl_port"
+	KeyPostfixConfig        = "postfix_config"
+	KeyMongoAddress         = "mongo_address"
+	KeyMongoUser            = "mongo_user"
+	KeyMongoPassword        = "mongo_password"
+	KeyAttachmentsPath      = "attachments_path"
+	KeyAttachmentsUser      = "attachments_user"
+	KeyAttachmentsPassword  = "attachments_password"
+	KeyRegistrationEnabled  = "registration_enabled"
+)
+
+const (
+	SetupSection            = "intial_setup"
+	SetupKeyEnabled         = "enabled"
+	SetupKeyPassword        = "password"
+)
+
+const (
+	WebSection              = "web"
+	WebKeySessionExpireTime = "session_expire_time"
 )
 
 const (
@@ -73,27 +85,44 @@ func ConfigInstance() *GostfixConfig {
 }
 
 type gostfixConfig struct {
-	WebPort             string
-	SASLPort            string
-	MyDomain            string
-	VMailboxMaps        string
-	VMailboxBase        string
-	VMailboxDomains     []string
-	MongoUser           string
-	MongoPassword       string
-	MongoAddress        string
-	AttachmentsPath     string
-	RegistrationEnabled bool
+	WebPort              string
+	SASLPort             string
+	MyDomain             string
+	VMailboxMaps         string
+	VMailboxBase         string
+	VMailboxDomains      []string
+	MongoUser            string
+	MongoPassword        string
+	MongoAddress         string
+	AttachmentsPath      string
+	RegistrationEnabled  bool
+	WebSessionExpireTime time.Duration
+	SetupEnabled         bool
+	SetupPassword        string
 }
 
 func newConfig() (config *gostfixConfig, err error) {
-
 	cfg, err := ini.Load(configPath)
 	if err != nil {
 		log.Fatalf("Unable to load %s\n", configPath)
 		return
 	}
 
+	webPort := cfg.Section("").Key(KeyWebPort).String()
+	if webPort == "" {
+		log.Printf("Web server port is not specified in configuration file, use default 65200")
+		webPort = "65200"
+	}
+	
+	initialSetup, _ := cfg.Section(SetupSection).Key(SetupKeyEnabled).Bool()
+	initialPassword := cfg.Section(SetupSection).Key(SetupKeyPassword).String()
+	if initialSetup {
+		if len(initialPassword) < 8 {
+			log.Fatalf("Initial setup requires a temporary master password in the configuration file. The minimum lenght is 8 symbols.")
+			return
+		}
+	}
+
 	postfixConfigPath := cfg.Section("").Key(KeyPostfixConfig).String()
 	if !utils.FileExists(postfixConfigPath) {
 		log.Fatalf("Unable to find postfix config %s\n", postfixConfigPath)
@@ -165,30 +194,33 @@ func newConfig() (config *gostfixConfig, err error) {
 
 	registrationEnabled := cfg.Section("").Key(KeyRegistrationEnabled).String()
 
-	webPort := cfg.Section("").Key(KeyWebPort).String()
-	if webPort == "" {
-		log.Printf("Web server port is not specified in configuration file, use default 65200")
-		webPort = "65200"
-	}
-
 	saslPort := cfg.Section("").Key(KeySASLPort).String()
 	if saslPort == "" {
 		log.Printf("SASL server port is not specified in configuration file, use default 65201")
 		saslPort = "65201"
 	}
 
+	webSessionExpireTime, err := time.ParseDuration(cfg.Section(WebSection).Key(WebKeySessionExpireTime).String())
+	if err != nil {
+		webSessionExpireTime = time.Hour * 24
+		log.Printf("Unable to read web session expire time. 24h by default.");
+	}
+
 	config = &gostfixConfig{
-		WebPort:             webPort,
-		SASLPort:            saslPort,
-		MyDomain:            myDomain,
-		VMailboxBase:        baseDir,
-		VMailboxMaps:        mapsList[1] + ".db",
-		VMailboxDomains:     validDomains,
-		MongoUser:           mongoUser,
-		MongoPassword:       mongoPassword,
-		MongoAddress:        mongoAddress,
-		AttachmentsPath:     attachmentsPath,
-		RegistrationEnabled: registrationEnabled == "true",
+		WebPort:              webPort,
+		SASLPort:             saslPort,
+		MyDomain:             myDomain,
+		VMailboxBase:         baseDir,
+		VMailboxMaps:         mapsList[1] + ".db",
+		VMailboxDomains:      validDomains,
+		MongoUser:            mongoUser,
+		MongoPassword:        mongoPassword,
+		MongoAddress:         mongoAddress,
+		AttachmentsPath:      attachmentsPath,
+		RegistrationEnabled:  registrationEnabled == "true",
+		WebSessionExpireTime: webSessionExpireTime * 1000,
+		SetupEnabled:         initialSetup,
+		SetupPassword:        initialPassword,
 	}
 	return
 }

+ 38 - 16
config/main.ini.default

@@ -1,28 +1,50 @@
-# Port used by web interface server
-web_port = 65200
+; Web server port
+; Default: 65200
+;
+web_port=65200
 
-# Port used by SASL authentication server
-sasl_port = 65201
+; SASL authentication server port
+; Default: 65201
+;
+sasl_port=65201
 
-# Path to postfix service configuration.
-# Usualy placed in /etc/postfix/main.cf
-# If not set cause critical error.
+; Enables or disable registration functionality in web interface
+;
+registration_enabled=true
+
+; Path to postfix service configuration.
+; Usualy is located in /etc/postfix/main.cf
+; If not set cause critical error.
+;
 postfix_config = /etc/postfix/main.cf
 
-# Address of mongo database to store service data in following
-# format:
-#     host:[port]
-# By default is localhost:27017.
-mongo_address = localhost:27017
+; Address of mongo database to store service data in following
+; format:
+;     host:[port]
+; Default: localhost:27017
+;
+;mongo_address = localhost:27017
 
-# User name to access mongo database. Empty by default.
+; User name to access mongo database. Empty by default.
+;
 mongo_user =
 
-# Password to access mongo database. Empty by default.
+; Password to access mongo database. Empty by default.
+;
 mongo_password =
 
-# Path to attachments storage. By dafault "./attachments".
+; Path to attachments storage. By dafault "./attachments".
+;
 attachments_path = attachments
 
-# Enables registration functionality, disabled by default
+; Enables registration functionality, disabled by default
+;
 registration_enabled = false
+
+[web]
+; Duration while the user web session is valid. If user tryes to access the web
+; interface after the session is expired, a token that is used to access the web
+; interface is invalidated and removed from database.
+; Default: 24h
+;
+;session_expire_time=1m

+ 30 - 4
db/db.go

@@ -106,6 +106,30 @@ func NewStorage() (s *Storage, err error) {
 		Options: options.Index().SetUnique(true),
 	}
 
+	collections, err := db.ListCollectionNames(context.Background(), bson.D{})
+
+	if err != nil {
+		log.Fatal(err)
+	}
+
+	createAllEmails := true
+	for _, collection := range collections {
+		if collection == "allEmails" {
+			createAllEmails = false
+		}
+		log.Println(collection)
+	}
+
+	if createAllEmails {
+		unwindStage := bson.D{{"$unwind", bson.D{{"path", "$email"}}}}
+		groupStage := bson.D{{"$group", bson.D{{"_id", "null"}, {"emails", bson.D{{"$addToSet", "$email"}}}}}}
+		pipeline := mongo.Pipeline{unwindStage, groupStage}
+		err = db.CreateView(context.Background(), "allEmails", "emails", pipeline)
+		if err != nil {
+			log.Fatal(err)
+		}
+	}
+
 	s = &Storage{
 		db:                  db,
 		usersCollection:     db.Collection("users"),
@@ -557,16 +581,18 @@ func (s *Storage) ReadEmailMaps() (map[string]string, error) {
 	mailPath := config.ConfigInstance().VMailboxBase
 
 	mapsFile := config.ConfigInstance().VMailboxMaps
-	if !utils.FileExists(mapsFile) {
-		return nil, errors.New("Could not read virtual mailbox maps")
-	}
 
 	db, err := berkeleydb.NewDB()
 	if err != nil {
 		log.Fatal(err)
 	}
 
-	err = db.Open(config.ConfigInstance().VMailboxMaps, berkeleydb.DbHash, berkeleydb.DbRdOnly)
+	if !utils.FileExists(config.ConfigInstance().VMailboxMaps) {
+		err = db.Open(config.ConfigInstance().VMailboxMaps, berkeleydb.DbHash, berkeleydb.DbCreate)
+	} else {
+		err = db.Open(config.ConfigInstance().VMailboxMaps, berkeleydb.DbHash, berkeleydb.DbRdOnly)
+	}
+
 	if err != nil {
 		return nil, errors.New("Unable to open virtual mailbox maps " + mapsFile + " " + err.Error())
 	}

+ 9 - 7
go.mod

@@ -5,19 +5,21 @@ go 1.14
 require (
 	github.com/fsnotify/fsnotify v1.4.9
 	github.com/golang/protobuf v1.5.2
-	github.com/google/uuid v1.1.1
+	github.com/google/uuid v1.1.2
 	github.com/gorilla/sessions v1.2.0
 	github.com/gorilla/websocket v1.4.2
 	github.com/jhillyerd/enmime v0.8.1
 	github.com/jsimonetti/berkeleydb v0.0.0-20170815141343-5cde5eaaf78c // indirect
-	github.com/pkg/profile v1.5.0
+	github.com/pkg/profile v1.6.0
 	github.com/semlanik/berkeleydb v0.0.0-20200324082802-7b28da5446c0
 	github.com/smartystreets/goconvey v1.6.4 // indirect
-	github.com/stretchr/testify v1.6.1 // indirect
-	go.mongodb.org/mongo-driver v1.3.5
-	golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586
-	golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9
-	google.golang.org/protobuf v1.26.0
+	github.com/stretchr/testify v1.7.0 // indirect
+	go.mongodb.org/mongo-driver v1.5.1
+	golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9
+	golang.org/x/net v0.0.0-20220421235706-1d1ef9303861 // indirect
+	golang.org/x/sys v0.0.0-20220422013727-9388b58f7150
+	golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect
+	google.golang.org/protobuf v1.28.0 // indirect
 	gopkg.in/go-ini/ini.v1 v1.57.0
 	gopkg.in/ini.v1 v1.57.0 // indirect
 )

+ 50 - 31
go.sum

@@ -1,4 +1,6 @@
 github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
+github.com/aws/aws-sdk-go v1.34.28 h1:sscPpn/Ns3i0F4HPEWAVcwdIRaZZCuL7llJ2/60yPIk=
+github.com/aws/aws-sdk-go v1.34.28/go.mod h1:H7NKnBqNVzoTJpGfLrQkkD+ytBA93eiDYi/+8rV9s48=
 github.com/cention-sany/utf7 v0.0.0-20170124080048-26cad61bd60a h1:MISbI8sU/PSK/ztvmWKFcI7UGb5/HQT7B+i3a2myKgI=
 github.com/cention-sany/utf7 v0.0.0-20170124080048-26cad61bd60a/go.mod h1:2GxOXOlEPAMFPfp014mK1SWq8G8BN8o7/dfYqJrVGn8=
 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
@@ -6,6 +8,7 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c
 github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4=
 github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
+github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
 github.com/go-stack/stack v1.8.0 h1:5SgMzNM5HxrEjV0ww2lTmX6E2Izsfxas4+YHWRs3Lsk=
 github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
 github.com/go-test/deep v1.0.2 h1:onZX1rnHT3Wv6cqNgYyFOOlgVKJrksuCMCRvJStbMYw=
@@ -36,17 +39,17 @@ github.com/gobuffalo/packr/v2 v2.2.0/go.mod h1:CaAwI0GPIAv+5wKLtv8Afwl+Cm78K/I/V
 github.com/gobuffalo/syncx v0.0.0-20190224160051-33c29581e754/go.mod h1:HhnNqWY95UYwwW3uSASeV7vtgYkT2t16hJgV3AEPUpw=
 github.com/gogs/chardet v0.0.0-20150115103509-2404f7772561 h1:aBzukfDxQlCTVS0NBUjI5YA3iVeaZ9Tb5PxNrrIP1xs=
 github.com/gogs/chardet v0.0.0-20150115103509-2404f7772561/go.mod h1:Pcatq5tYkCW2Q6yrR2VRHlbHpZ/R4/7qyL1TCF7vl14=
+github.com/golang/protobuf v1.5.0 h1:LUVKkCeviFUMKqHa4tXIIij/lbhnMbP7Fn5wKdKkRh4=
 github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
 github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw=
 github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
 github.com/golang/snappy v0.0.1 h1:Qgr9rKW7uDUkrbSmQeiDsGa8SjGyCOGtuasMWwvp2P4=
 github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
-github.com/google/go-cmp v0.2.0 h1:+dTQ8DZQJz0Mb/HjFlkptS1FeQ4cWSnN941F8aEG4SQ=
-github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
+github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
 github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU=
 github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
-github.com/google/uuid v1.1.1 h1:Gkbcsh/GbpXz7lPftLA3P6TYMwjCLYm83jiFQZF/3gY=
-github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
+github.com/google/uuid v1.1.2 h1:EVhdT+1Kseyi1/pUmXKaFxYsDNy9RQYkMWRH68J/W7Y=
+github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
 github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8=
 github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
 github.com/gorilla/securecookie v1.1.1 h1:miw7JPhV+b/lAHSXz4qd/nN9jRiAFV5FwjeKyCS8BvQ=
@@ -60,6 +63,10 @@ github.com/jaytaylor/html2text v0.0.0-20190408195923-01ec452cbe43 h1:jTkyeF7NZ5o
 github.com/jaytaylor/html2text v0.0.0-20190408195923-01ec452cbe43/go.mod h1:CVKlgaMiht+LXvHG173ujK6JUhZXKb2u/BQtjPDIvyk=
 github.com/jhillyerd/enmime v0.8.1 h1:Kz4xj3sJJ4Ju8e+w/7v9H4Matv5ijPgv7UkhPf+C15I=
 github.com/jhillyerd/enmime v0.8.1/go.mod h1:MBHs3ugk03NGjMM6PuRynlKf+HA5eSillZ+TRCm73AE=
+github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg=
+github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo=
+github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8=
+github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U=
 github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg=
 github.com/jsimonetti/berkeleydb v0.0.0-20170815141343-5cde5eaaf78c h1:/5ja7kk4aMhLJiRTtOhbPYLb9Y95VlqFiRTAoIhrHI4=
 github.com/jsimonetti/berkeleydb v0.0.0-20170815141343-5cde5eaaf78c/go.mod h1:MzZzsYRTOJXLM04/edhWq3Y89ktkIWH0OcenhMCSHfs=
@@ -83,12 +90,13 @@ github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzp
 github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe/go.mod h1:wL8QJuTMNUDYhXwkmfOly8iTdp5TEcJFWZD2D7SIkUc=
 github.com/olekukonko/tablewriter v0.0.1 h1:b3iUnf1v+ppJiOfNX4yxxqfWKMQPZR5yoh8urCTFX88=
 github.com/olekukonko/tablewriter v0.0.1/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo=
-github.com/pelletier/go-toml v1.4.0/go.mod h1:PN7xzY2wHTK0K9p34ErDQMlFxa51Fk0OUruD3k1mMwo=
+github.com/pelletier/go-toml v1.7.0/go.mod h1:vwGMzjaWMwyfHwgIBhI2YUM4fB6nL6lVAvS1LBMMhTE=
 github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
-github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
 github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
-github.com/pkg/profile v1.5.0 h1:042Buzk+NhDI+DeSAA62RwJL8VAuZUMQZUjCsRz1Mug=
-github.com/pkg/profile v1.5.0/go.mod h1:qBsxPvzyUincmltOk6iyRVxHYg4adc0OFOv72ZdLa18=
+github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
+github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
+github.com/pkg/profile v1.6.0 h1:hUDfIISABYI59DyeB3OTay/HxSRwTQ8rB/H83k6r5dM=
+github.com/pkg/profile v1.6.0/go.mod h1:qBsxPvzyUincmltOk6iyRVxHYg4adc0OFOv72ZdLa18=
 github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
 github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
 github.com/rogpeppe/go-internal v1.1.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
@@ -112,59 +120,70 @@ github.com/ssor/bom v0.0.0-20170718123548-6386211fdfcf/go.mod h1:RJID2RhlZKId02n
 github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
 github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
 github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
-github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q=
 github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
-github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0=
 github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
+github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
+github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
 github.com/tidwall/pretty v1.0.0 h1:HsD+QiTn7sK6flMKIvNmpqz1qrpP3Ps6jOKIKMooyg4=
 github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk=
-github.com/xdg/scram v0.0.0-20180814205039-7eeb5667e42c h1:u40Z8hqBAAQyv+vATcGgV0YCnDjqSL7/q/JyPhhJSPk=
-github.com/xdg/scram v0.0.0-20180814205039-7eeb5667e42c/go.mod h1:lB8K/P019DLNhemzwFU4jHLhdvlE6uDZjXFejJXr49I=
-github.com/xdg/stringprep v0.0.0-20180714160509-73f8eece6fdc h1:n+nNi93yXLkJvKwXNP9d55HC7lGK4H/SRcwB5IaUZLo=
-github.com/xdg/stringprep v0.0.0-20180714160509-73f8eece6fdc/go.mod h1:Jhud4/sHMO4oL310DaZAKk9ZaJ08SJfe+sJh0HrGL1Y=
-go.mongodb.org/mongo-driver v1.3.5 h1:S0ZOruh4YGHjD7JoN7mIsTrNjnQbOjrmgrx6l6pZN7I=
-go.mongodb.org/mongo-driver v1.3.5/go.mod h1:Ual6Gkco7ZGQw8wE1t4tLnvBsf6yVSM60qW6TgOeJ5c=
+github.com/xdg-go/pbkdf2 v1.0.0 h1:Su7DPu48wXMwC3bs7MCNG+z4FhcyEuz5dlvchbq0B0c=
+github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI=
+github.com/xdg-go/scram v1.0.2 h1:akYIkZ28e6A96dkWNJQu3nmCzH3YfwMPQExUYDaRv7w=
+github.com/xdg-go/scram v1.0.2/go.mod h1:1WAq6h33pAW+iRreB34OORO2Nf7qel3VV3fjBj+hCSs=
+github.com/xdg-go/stringprep v1.0.2 h1:6iq84/ryjjeRmMJwxutI51F2GIPlP5BfTvXHeYjyhBc=
+github.com/xdg-go/stringprep v1.0.2/go.mod h1:8F9zXuvzgwmyT5DUm4GUfZGDdT3W+LCvS6+da4O5kxM=
+github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d h1:splanxYIlg+5LfHAM6xpdFEAYOk8iySO56hMFq6uLyA=
+github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d/go.mod h1:rHwXgn7JulP+udvsHwJoVG1YGAP6VLg4y9I5dyZdqmA=
+go.mongodb.org/mongo-driver v1.5.1 h1:9nOVLGDfOaZ9R0tBumx/BcuqkbFpyTCU2r/Po7A2azI=
+go.mongodb.org/mongo-driver v1.5.1/go.mod h1:gRXCHX4Jo7J0IJ1oDQyUxF7jfy19UfxniMS4xxMmUqw=
 golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
 golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
 golang.org/x/crypto v0.0.0-20190422162423-af44ce270edf/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE=
-golang.org/x/crypto v0.0.0-20190530122614-20be4c3c3ed5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
-golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586 h1:7KByu05hhLed2MO29w7p1XfZvZ13m8mub3shuVftRs0=
-golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
+golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
+golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 h1:psW17arqaxU48Z5kZ0CQnkZWQJsqcURM6tKiBApRjXI=
+golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
 golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
 golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
-golang.org/x/net v0.0.0-20190724013045-ca1201d0de80 h1:Ao/3l156eZf2AW5wK8a7/smtodRU+gha3+BeqJ69lRk=
 golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20220421235706-1d1ef9303861 h1:yssD99+7tqHWO5Gwh81phT+67hg+KttniBr6UnEXOY8=
+golang.org/x/net v0.0.0-20220421235706-1d1ef9303861/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
 golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20190412183630-56d357773e84/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
-golang.org/x/sync v0.0.0-20190423024810-112230192c58 h1:8gQV6CLnAEikrhgkHFbMAEhagSSnXWGV915qUMm9mrU=
 golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e h1:vcxGaoTs7kV8m5Np9uUNQin4BrLOthgV7252N8V+FwY=
+golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20190412213103-97732733099d h1:+R4KGOnez64A81RvjARKc4UT5/tI9ujCIVX+P5KiHuI=
 golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20190419153524-e8e3143a4f4a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20190531175056-4c3a928424d2/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9 h1:L2auWcuQIvxz9xSEqzESnV/QN/gNRXNApHi3fYwl2w0=
 golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
+golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220422013727-9388b58f7150 h1:xHms4gcpe1YE7A3yIllJXP16CMAGuqwO2lX1mTyyRRc=
+golang.org/x/sys v0.0.0-20220422013727-9388b58f7150/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
 golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
-golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
 golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
-golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k=
-golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
+golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
+golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk=
+golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
 golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
 golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
 golang.org/x/tools v0.0.0-20190329151228-23e29df326fe/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
 golang.org/x/tools v0.0.0-20190416151739-9c9e1878f421/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
 golang.org/x/tools v0.0.0-20190420181800-aa740d480789/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
 golang.org/x/tools v0.0.0-20190531172133-b3315ee88b7d/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
-golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
 golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
+golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
-google.golang.org/protobuf v1.26.0 h1:bxAC2xTBsZGibn2RTntX0oH50xLsqy1OxA9tTL3p/lk=
 google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
+google.golang.org/protobuf v1.28.0 h1:w43yiav+6bVFTBQFZX0r7ipe9JQ1QsbMgHwbBziscLw=
+google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
 gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
 gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
 gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
@@ -173,7 +192,7 @@ gopkg.in/go-ini/ini.v1 v1.57.0 h1:e9cP3G5H9zMIwvQnUDbnD7k5SFuSbrPWdSAV8VHKxX8=
 gopkg.in/go-ini/ini.v1 v1.57.0/go.mod h1:M74/hG4RTwbkZyTEZ9iQwM4v6dFD4u6QBjoqT/pM8Kg=
 gopkg.in/ini.v1 v1.57.0 h1:9unxIsFcTt4I55uWluz+UmL95q4kdJ0buvQ1ZIqVQww=
 gopkg.in/ini.v1 v1.57.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
-gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
-gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10=
+gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
 gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
 gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

+ 3 - 2
main.go

@@ -31,7 +31,7 @@ import (
 	sasl "git.semlanik.org/semlanik/gostfix/sasl"
 	scanner "git.semlanik.org/semlanik/gostfix/scanner"
 	web "git.semlanik.org/semlanik/gostfix/web"
-	profile "github.com/pkg/profile"
+	"github.com/pkg/profile"
 )
 
 type GofixEngine struct {
@@ -43,12 +43,13 @@ type GofixEngine struct {
 func NewGofixEngine() (e *GofixEngine) {
 	mailScanner := scanner.NewMailScanner()
 	saslService, err := sasl.NewSaslServer()
+	webServer := web.NewServer(mailScanner)
 	if err != nil {
 		log.Fatalf("Unable to intialize sasl server %s\n", err)
 	}
 	e = &GofixEngine{
 		scanner: mailScanner,
-		web:     web.NewServer(mailScanner),
+		web:     webServer,
 		sasl:    saslService,
 	}
 	return

+ 0 - 2
scanner/mailscanner.go

@@ -54,9 +54,7 @@ func NewMailScanner() (ms *MailScanner) {
 		log.Fatal(err)
 		return
 	}
-
 	storage, err := db.NewStorage()
-
 	if err != nil {
 		log.Fatal(err)
 		return

+ 88 - 0
setup/setup.go

@@ -0,0 +1,88 @@
+/*
+ * MIT License
+ *
+ * Copyright (c) 2020 Alexey Edelev <semlanik@gmail.com>
+ *
+ * This file is part of gostfix project https://git.semlanik.org/semlanik/gostfix
+ *
+ * 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.
+ */
+
+package setup
+
+import (
+	config "git.semlanik.org/semlanik/gostfix/config"
+)
+
+type Setup struct {
+	sessionStore      *sessions.CookieStore
+}
+
+func NewSetup() *Setup {
+	s := &Setup{
+		sessionStore:      sessions.NewCookieStore(make([]byte, 32)),
+	}
+	return s
+}
+
+func (s *Setup) Run() {
+	if !config.ConfigInstance().SetupEnabled {
+		return
+	}
+	http.Handle("/", s)
+	log.Fatal(http.ListenAndServe(":"+config.ConfigInstance().WebPort, nil))
+}
+
+func (s *Setup) ServeHTTP(w http.ResponseWriter, r *http.Request) {
+	urlParts := strings.Split(r.URL.Path, "/")[1:]
+	if len(urlParts) == 0 || urlParts[0] == "" {
+		// TODO: Welcome page with password
+		return
+	}
+	
+	if !checkPassword(r) {
+		http.Redirect(w, r, "/", http.StatusTemporaryRedirect)
+		return
+	}
+	
+	switch urlParts[1] {
+	case "config":
+		// TODO: Configure values
+	case "save":
+		// TODO: Confirm storing new config and reload server
+	case "recovery":
+		// TODO: Recovery mode, where user can resolve system inconsistency
+	case "admin":
+		// TODO: Admin panel handling
+	}
+}
+
+func (s *Server) checkPassword(r *http.Request) bool {
+	switch r.Method {
+	case "GET":
+		session, err := s.sessionStore.Get(r, CookieSessionToken)
+		if err != nil {
+			log.Printf("Unable to read user session %s\n", err)
+			return false
+		}
+		setupPassword, _ = session.Values["setupPassword"].(string)
+	case "POST":
+		setupPassword := r.FormValue("setupPassword")
+	}
+	return masterPassword == config.ConfigInstance().SetupPassword;
+}

+ 1 - 1
views

@@ -1 +1 @@
-db.createView("allEmails", "emails", [{$unwind:"$email"},{$group: {_id: null, emails: {$addToSet: "$email"}}}]
+db.createView("allEmails", "emails", [{$unwind:"$email"},{$group: {_id: null, emails: {$addToSet: "$email"}}}])

+ 14 - 18
web/auth.go

@@ -37,10 +37,6 @@ import (
 )
 
 func (s *Server) handleRegister(w http.ResponseWriter, r *http.Request) {
-	// if session, err := s.sessionStore.Get(r, CookieSessionToken); err == nil && session.Values["user"] != nil && session.Values["token"] != nil {
-	// 	http.Redirect(w, r, "/m0", http.StatusTemporaryRedirect)
-	// 	return
-	// }
 	if !config.ConfigInstance().RegistrationEnabled {
 		s.error(http.StatusNotImplemented, "Registration is disabled on this server", w)
 		return
@@ -92,20 +88,6 @@ func (s *Server) handleLogin(w http.ResponseWriter, r *http.Request) {
 			http.Redirect(w, r, "/m/0", http.StatusTemporaryRedirect)
 			return
 		}
-
-		var signupTemplate template.HTML
-		if config.ConfigInstance().RegistrationEnabled {
-			signupTemplate = template.HTML(s.templater.ExecuteSignup(""))
-		} else {
-			signupTemplate = ""
-		}
-
-		//Otherwise make sure user logged out and show login page
-		s.logout(w, r)
-		fmt.Fprint(w, s.templater.ExecuteLogin(&struct {
-			Version string
-			Signup  template.HTML
-		}{common.Version, signupTemplate}))
 	case "POST":
 		//Check passed in form login/password pair first
 		user := r.FormValue("user")
@@ -116,6 +98,20 @@ func (s *Server) handleLogin(w http.ResponseWriter, r *http.Request) {
 			return
 		}
 	}
+
+	var signupTemplate template.HTML
+	if config.ConfigInstance().RegistrationEnabled {
+		signupTemplate = template.HTML(s.templater.ExecuteSignup(""))
+	} else {
+		signupTemplate = ""
+	}
+
+	//Otherwise make sure user logged out and show login page
+	s.logout(w, r)
+	fmt.Fprint(w, s.templater.ExecuteLogin(&struct {
+		Version string
+		Signup  template.HTML
+	}{common.Version, signupTemplate}))
 }
 
 func (s *Server) handleLogout(w http.ResponseWriter, r *http.Request) {