summaryrefslogtreecommitdiffstats
path: root/plugin/rpcplugin
diff options
context:
space:
mode:
authorChristopher Speller <crspeller@gmail.com>2018-06-25 12:33:13 -0700
committerGitHub <noreply@github.com>2018-06-25 12:33:13 -0700
commit1e5c432e1029601a664454388ae366ef69618d62 (patch)
treecb9e8bfb66640ac3b29c934bb2c3202d25aeb368 /plugin/rpcplugin
parentecefa6cdd1e7376046bbec82c1b47f7756fea646 (diff)
downloadchat-1e5c432e1029601a664454388ae366ef69618d62.tar.gz
chat-1e5c432e1029601a664454388ae366ef69618d62.tar.bz2
chat-1e5c432e1029601a664454388ae366ef69618d62.zip
MM-10702 Moving plugins to use hashicorp go-plugin. (#8978)
* Moving plugins to use hashicorp go-plugin. * Tweaks from feedback.
Diffstat (limited to 'plugin/rpcplugin')
-rw-r--r--plugin/rpcplugin/api.go718
-rw-r--r--plugin/rpcplugin/api_test.go300
-rw-r--r--plugin/rpcplugin/hooks.go398
-rw-r--r--plugin/rpcplugin/hooks_test.go237
-rw-r--r--plugin/rpcplugin/http.go91
-rw-r--r--plugin/rpcplugin/http_test.go61
-rw-r--r--plugin/rpcplugin/io.go63
-rw-r--r--plugin/rpcplugin/ipc.go31
-rw-r--r--plugin/rpcplugin/ipc_test.go63
-rw-r--r--plugin/rpcplugin/main.go47
-rw-r--r--plugin/rpcplugin/main_test.go63
-rw-r--r--plugin/rpcplugin/muxer.go264
-rw-r--r--plugin/rpcplugin/muxer_test.go197
-rw-r--r--plugin/rpcplugin/process.go26
-rw-r--r--plugin/rpcplugin/process_test.go60
-rw-r--r--plugin/rpcplugin/process_unix.go48
-rw-r--r--plugin/rpcplugin/process_windows.go648
-rw-r--r--plugin/rpcplugin/rpcplugintest/rpcplugintest.go26
-rw-r--r--plugin/rpcplugin/rpcplugintest/supervisor.go312
-rw-r--r--plugin/rpcplugin/sandbox/main_test.go18
-rw-r--r--plugin/rpcplugin/sandbox/sandbox.go34
-rw-r--r--plugin/rpcplugin/sandbox/sandbox_linux.go488
-rw-r--r--plugin/rpcplugin/sandbox/sandbox_linux_test.go159
-rw-r--r--plugin/rpcplugin/sandbox/sandbox_other.go22
-rw-r--r--plugin/rpcplugin/sandbox/sandbox_test.go25
-rw-r--r--plugin/rpcplugin/sandbox/seccomp_linux.go178
-rw-r--r--plugin/rpcplugin/sandbox/seccomp_linux_amd64.go301
-rw-r--r--plugin/rpcplugin/sandbox/seccomp_linux_other.go10
-rw-r--r--plugin/rpcplugin/sandbox/seccomp_linux_test.go210
-rw-r--r--plugin/rpcplugin/sandbox/supervisor.go33
-rw-r--r--plugin/rpcplugin/sandbox/supervisor_test.go18
-rw-r--r--plugin/rpcplugin/supervisor.go176
-rw-r--r--plugin/rpcplugin/supervisor_test.go14
33 files changed, 0 insertions, 5339 deletions
diff --git a/plugin/rpcplugin/api.go b/plugin/rpcplugin/api.go
deleted file mode 100644
index c81bbb7c5..000000000
--- a/plugin/rpcplugin/api.go
+++ /dev/null
@@ -1,718 +0,0 @@
-// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-package rpcplugin
-
-import (
- "encoding/gob"
- "encoding/json"
- "io"
- "net/http"
- "net/rpc"
-
- "github.com/mattermost/mattermost-server/model"
- "github.com/mattermost/mattermost-server/plugin"
-)
-
-type LocalAPI struct {
- api plugin.API
- muxer *Muxer
-}
-
-func (api *LocalAPI) LoadPluginConfiguration(args struct{}, reply *[]byte) error {
- var config interface{}
- if err := api.api.LoadPluginConfiguration(&config); err != nil {
- return err
- }
- b, err := json.Marshal(config)
- if err != nil {
- return err
- }
- *reply = b
- return nil
-}
-
-func (api *LocalAPI) RegisterCommand(args *model.Command, reply *APITeamReply) error {
- return api.api.RegisterCommand(args)
-}
-
-func (api *LocalAPI) UnregisterCommand(args *APIUnregisterCommandArgs, reply *APITeamReply) error {
- return api.api.UnregisterCommand(args.TeamId, args.Trigger)
-}
-
-type APIErrorReply struct {
- Error *model.AppError
-}
-
-type APITeamReply struct {
- Team *model.Team
- Error *model.AppError
-}
-
-func (api *LocalAPI) CreateTeam(args *model.Team, reply *APITeamReply) error {
- team, err := api.api.CreateTeam(args)
- *reply = APITeamReply{
- Team: team,
- Error: err,
- }
- return nil
-}
-
-func (api *LocalAPI) DeleteTeam(args string, reply *APIErrorReply) error {
- *reply = APIErrorReply{
- Error: api.api.DeleteTeam(args),
- }
- return nil
-}
-
-func (api *LocalAPI) GetTeam(args string, reply *APITeamReply) error {
- team, err := api.api.GetTeam(args)
- *reply = APITeamReply{
- Team: team,
- Error: err,
- }
- return nil
-}
-
-func (api *LocalAPI) GetTeamByName(args string, reply *APITeamReply) error {
- team, err := api.api.GetTeamByName(args)
- *reply = APITeamReply{
- Team: team,
- Error: err,
- }
- return nil
-}
-
-func (api *LocalAPI) UpdateTeam(args *model.Team, reply *APITeamReply) error {
- team, err := api.api.UpdateTeam(args)
- *reply = APITeamReply{
- Team: team,
- Error: err,
- }
- return nil
-}
-
-type APIUserReply struct {
- User *model.User
- Error *model.AppError
-}
-
-func (api *LocalAPI) CreateUser(args *model.User, reply *APIUserReply) error {
- user, err := api.api.CreateUser(args)
- *reply = APIUserReply{
- User: user,
- Error: err,
- }
- return nil
-}
-
-func (api *LocalAPI) DeleteUser(args string, reply *APIErrorReply) error {
- *reply = APIErrorReply{
- Error: api.api.DeleteUser(args),
- }
- return nil
-}
-
-func (api *LocalAPI) GetUser(args string, reply *APIUserReply) error {
- user, err := api.api.GetUser(args)
- *reply = APIUserReply{
- User: user,
- Error: err,
- }
- return nil
-}
-
-func (api *LocalAPI) GetUserByEmail(args string, reply *APIUserReply) error {
- user, err := api.api.GetUserByEmail(args)
- *reply = APIUserReply{
- User: user,
- Error: err,
- }
- return nil
-}
-
-func (api *LocalAPI) GetUserByUsername(args string, reply *APIUserReply) error {
- user, err := api.api.GetUserByUsername(args)
- *reply = APIUserReply{
- User: user,
- Error: err,
- }
- return nil
-}
-
-func (api *LocalAPI) UpdateUser(args *model.User, reply *APIUserReply) error {
- user, err := api.api.UpdateUser(args)
- *reply = APIUserReply{
- User: user,
- Error: err,
- }
- return nil
-}
-
-type APIGetChannelByNameArgs struct {
- Name string
- TeamId string
-}
-
-type APIGetDirectChannelArgs struct {
- UserId1 string
- UserId2 string
-}
-
-type APIGetGroupChannelArgs struct {
- UserIds []string
-}
-
-type APIAddChannelMemberArgs struct {
- ChannelId string
- UserId string
-}
-
-type APIGetChannelMemberArgs struct {
- ChannelId string
- UserId string
-}
-
-type APIUpdateChannelMemberRolesArgs struct {
- ChannelId string
- UserId string
- NewRoles string
-}
-
-type APIUpdateChannelMemberNotificationsArgs struct {
- ChannelId string
- UserId string
- Notifications map[string]string
-}
-
-type APIDeleteChannelMemberArgs struct {
- ChannelId string
- UserId string
-}
-
-type APIChannelReply struct {
- Channel *model.Channel
- Error *model.AppError
-}
-
-type APIChannelMemberReply struct {
- ChannelMember *model.ChannelMember
- Error *model.AppError
-}
-
-func (api *LocalAPI) CreateChannel(args *model.Channel, reply *APIChannelReply) error {
- channel, err := api.api.CreateChannel(args)
- *reply = APIChannelReply{
- Channel: channel,
- Error: err,
- }
- return nil
-}
-
-func (api *LocalAPI) DeleteChannel(args string, reply *APIErrorReply) error {
- *reply = APIErrorReply{
- Error: api.api.DeleteChannel(args),
- }
- return nil
-}
-
-func (api *LocalAPI) GetChannel(args string, reply *APIChannelReply) error {
- channel, err := api.api.GetChannel(args)
- *reply = APIChannelReply{
- Channel: channel,
- Error: err,
- }
- return nil
-}
-
-func (api *LocalAPI) GetChannelByName(args *APIGetChannelByNameArgs, reply *APIChannelReply) error {
- channel, err := api.api.GetChannelByName(args.Name, args.TeamId)
- *reply = APIChannelReply{
- Channel: channel,
- Error: err,
- }
- return nil
-}
-
-func (api *LocalAPI) GetDirectChannel(args *APIGetDirectChannelArgs, reply *APIChannelReply) error {
- channel, err := api.api.GetDirectChannel(args.UserId1, args.UserId2)
- *reply = APIChannelReply{
- Channel: channel,
- Error: err,
- }
- return nil
-}
-
-func (api *LocalAPI) GetGroupChannel(args *APIGetGroupChannelArgs, reply *APIChannelReply) error {
- channel, err := api.api.GetGroupChannel(args.UserIds)
- *reply = APIChannelReply{
- Channel: channel,
- Error: err,
- }
- return nil
-}
-
-func (api *LocalAPI) UpdateChannel(args *model.Channel, reply *APIChannelReply) error {
- channel, err := api.api.UpdateChannel(args)
- *reply = APIChannelReply{
- Channel: channel,
- Error: err,
- }
- return nil
-}
-
-func (api *LocalAPI) AddChannelMember(args *APIAddChannelMemberArgs, reply *APIChannelMemberReply) error {
- member, err := api.api.AddChannelMember(args.ChannelId, args.UserId)
- *reply = APIChannelMemberReply{
- ChannelMember: member,
- Error: err,
- }
- return nil
-}
-
-func (api *LocalAPI) GetChannelMember(args *APIGetChannelMemberArgs, reply *APIChannelMemberReply) error {
- member, err := api.api.GetChannelMember(args.ChannelId, args.UserId)
- *reply = APIChannelMemberReply{
- ChannelMember: member,
- Error: err,
- }
- return nil
-}
-
-func (api *LocalAPI) UpdateChannelMemberRoles(args *APIUpdateChannelMemberRolesArgs, reply *APIChannelMemberReply) error {
- member, err := api.api.UpdateChannelMemberRoles(args.ChannelId, args.UserId, args.NewRoles)
- *reply = APIChannelMemberReply{
- ChannelMember: member,
- Error: err,
- }
- return nil
-}
-
-func (api *LocalAPI) UpdateChannelMemberNotifications(args *APIUpdateChannelMemberNotificationsArgs, reply *APIChannelMemberReply) error {
- member, err := api.api.UpdateChannelMemberNotifications(args.ChannelId, args.UserId, args.Notifications)
- *reply = APIChannelMemberReply{
- ChannelMember: member,
- Error: err,
- }
- return nil
-}
-
-func (api *LocalAPI) DeleteChannelMember(args *APIDeleteChannelMemberArgs, reply *APIErrorReply) error {
- err := api.api.DeleteChannelMember(args.ChannelId, args.UserId)
- *reply = APIErrorReply{
- Error: err,
- }
- return nil
-}
-
-type APIPostReply struct {
- Post *model.Post
- Error *model.AppError
-}
-
-func (api *LocalAPI) CreatePost(args *model.Post, reply *APIPostReply) error {
- post, err := api.api.CreatePost(args)
- *reply = APIPostReply{
- Post: post,
- Error: err,
- }
- return nil
-}
-
-func (api *LocalAPI) DeletePost(args string, reply *APIErrorReply) error {
- *reply = APIErrorReply{
- Error: api.api.DeletePost(args),
- }
- return nil
-}
-
-func (api *LocalAPI) GetPost(args string, reply *APIPostReply) error {
- post, err := api.api.GetPost(args)
- *reply = APIPostReply{
- Post: post,
- Error: err,
- }
- return nil
-}
-
-func (api *LocalAPI) UpdatePost(args *model.Post, reply *APIPostReply) error {
- post, err := api.api.UpdatePost(args)
- *reply = APIPostReply{
- Post: post,
- Error: err,
- }
- return nil
-}
-
-type APIKeyValueStoreReply struct {
- Value []byte
- Error *model.AppError
-}
-
-type APIKeyValueStoreSetArgs struct {
- Key string
- Value []byte
-}
-
-func (api *LocalAPI) KeyValueStoreSet(args *APIKeyValueStoreSetArgs, reply *APIErrorReply) error {
- err := api.api.KeyValueStore().Set(args.Key, args.Value)
- *reply = APIErrorReply{
- Error: err,
- }
- return nil
-}
-
-func (api *LocalAPI) KeyValueStoreGet(args string, reply *APIKeyValueStoreReply) error {
- v, err := api.api.KeyValueStore().Get(args)
- *reply = APIKeyValueStoreReply{
- Value: v,
- Error: err,
- }
- return nil
-}
-
-func (api *LocalAPI) KeyValueStoreDelete(args string, reply *APIErrorReply) error {
- err := api.api.KeyValueStore().Delete(args)
- *reply = APIErrorReply{
- Error: err,
- }
- return nil
-}
-
-func ServeAPI(api plugin.API, conn io.ReadWriteCloser, muxer *Muxer) {
- server := rpc.NewServer()
- server.Register(&LocalAPI{
- api: api,
- muxer: muxer,
- })
- server.ServeConn(conn)
-}
-
-type RemoteAPI struct {
- client *rpc.Client
- muxer *Muxer
- keyValueStore *RemoteKeyValueStore
-}
-
-type RemoteKeyValueStore struct {
- api *RemoteAPI
-}
-
-var _ plugin.API = (*RemoteAPI)(nil)
-var _ plugin.KeyValueStore = (*RemoteKeyValueStore)(nil)
-
-func (api *RemoteAPI) LoadPluginConfiguration(dest interface{}) error {
- var config []byte
- if err := api.client.Call("LocalAPI.LoadPluginConfiguration", struct{}{}, &config); err != nil {
- return err
- }
- return json.Unmarshal(config, dest)
-}
-
-func (api *RemoteAPI) RegisterCommand(command *model.Command) error {
- return api.client.Call("LocalAPI.RegisterCommand", command, nil)
-}
-
-type APIUnregisterCommandArgs struct {
- TeamId string
- Trigger string
-}
-
-func (api *RemoteAPI) UnregisterCommand(teamId, trigger string) error {
- return api.client.Call("LocalAPI.UnregisterCommand", &APIUnregisterCommandArgs{
- TeamId: teamId,
- Trigger: trigger,
- }, nil)
-}
-
-func (api *RemoteAPI) CreateUser(user *model.User) (*model.User, *model.AppError) {
- var reply APIUserReply
- if err := api.client.Call("LocalAPI.CreateUser", user, &reply); err != nil {
- return nil, model.NewAppError("RemoteAPI.CreateUser", "plugin.rpcplugin.invocation.error", nil, "err="+err.Error(), http.StatusInternalServerError)
- }
- return reply.User, reply.Error
-}
-
-func (api *RemoteAPI) DeleteUser(userId string) *model.AppError {
- var reply APIErrorReply
- if err := api.client.Call("LocalAPI.DeleteUser", userId, &reply); err != nil {
- return model.NewAppError("RemoteAPI.DeleteUser", "plugin.rpcplugin.invocation.error", nil, "err="+err.Error(), http.StatusInternalServerError)
- }
- return reply.Error
-}
-
-func (api *RemoteAPI) GetUser(userId string) (*model.User, *model.AppError) {
- var reply APIUserReply
- if err := api.client.Call("LocalAPI.GetUser", userId, &reply); err != nil {
- return nil, model.NewAppError("RemoteAPI.GetUser", "plugin.rpcplugin.invocation.error", nil, "err="+err.Error(), http.StatusInternalServerError)
- }
- return reply.User, reply.Error
-}
-
-func (api *RemoteAPI) GetUserByEmail(email string) (*model.User, *model.AppError) {
- var reply APIUserReply
- if err := api.client.Call("LocalAPI.GetUserByEmail", email, &reply); err != nil {
- return nil, model.NewAppError("RemoteAPI.GetUserByEmail", "plugin.rpcplugin.invocation.error", nil, "err="+err.Error(), http.StatusInternalServerError)
- }
- return reply.User, reply.Error
-}
-
-func (api *RemoteAPI) GetUserByUsername(name string) (*model.User, *model.AppError) {
- var reply APIUserReply
- if err := api.client.Call("LocalAPI.GetUserByUsername", name, &reply); err != nil {
- return nil, model.NewAppError("RemoteAPI.GetUserByUsername", "plugin.rpcplugin.invocation.error", nil, "err="+err.Error(), http.StatusInternalServerError)
- }
- return reply.User, reply.Error
-}
-
-func (api *RemoteAPI) UpdateUser(user *model.User) (*model.User, *model.AppError) {
- var reply APIUserReply
- if err := api.client.Call("LocalAPI.UpdateUser", user, &reply); err != nil {
- return nil, model.NewAppError("RemoteAPI.UpdateUser", "plugin.rpcplugin.invocation.error", nil, "err="+err.Error(), http.StatusInternalServerError)
- }
- return reply.User, reply.Error
-}
-
-func (api *RemoteAPI) CreateTeam(team *model.Team) (*model.Team, *model.AppError) {
- var reply APITeamReply
- if err := api.client.Call("LocalAPI.CreateTeam", team, &reply); err != nil {
- return nil, model.NewAppError("RemoteAPI.CreateTeam", "plugin.rpcplugin.invocation.error", nil, "err="+err.Error(), http.StatusInternalServerError)
- }
- return reply.Team, reply.Error
-}
-
-func (api *RemoteAPI) DeleteTeam(teamId string) *model.AppError {
- var reply APIErrorReply
- if err := api.client.Call("LocalAPI.DeleteTeam", teamId, &reply); err != nil {
- return model.NewAppError("RemoteAPI.DeleteTeam", "plugin.rpcplugin.invocation.error", nil, "err="+err.Error(), http.StatusInternalServerError)
- }
- return reply.Error
-}
-
-func (api *RemoteAPI) GetTeam(teamId string) (*model.Team, *model.AppError) {
- var reply APITeamReply
- if err := api.client.Call("LocalAPI.GetTeam", teamId, &reply); err != nil {
- return nil, model.NewAppError("RemoteAPI.GetTeam", "plugin.rpcplugin.invocation.error", nil, "err="+err.Error(), http.StatusInternalServerError)
- }
- return reply.Team, reply.Error
-}
-
-func (api *RemoteAPI) GetTeamByName(name string) (*model.Team, *model.AppError) {
- var reply APITeamReply
- if err := api.client.Call("LocalAPI.GetTeamByName", name, &reply); err != nil {
- return nil, model.NewAppError("RemoteAPI.GetTeamByName", "plugin.rpcplugin.invocation.error", nil, "err="+err.Error(), http.StatusInternalServerError)
- }
- return reply.Team, reply.Error
-}
-
-func (api *RemoteAPI) UpdateTeam(team *model.Team) (*model.Team, *model.AppError) {
- var reply APITeamReply
- if err := api.client.Call("LocalAPI.UpdateTeam", team, &reply); err != nil {
- return nil, model.NewAppError("RemoteAPI.UpdateTeam", "plugin.rpcplugin.invocation.error", nil, "err="+err.Error(), http.StatusInternalServerError)
- }
- return reply.Team, reply.Error
-}
-
-func (api *RemoteAPI) CreateChannel(channel *model.Channel) (*model.Channel, *model.AppError) {
- var reply APIChannelReply
- if err := api.client.Call("LocalAPI.CreateChannel", channel, &reply); err != nil {
- return nil, model.NewAppError("RemoteAPI.CreateChannel", "plugin.rpcplugin.invocation.error", nil, "err="+err.Error(), http.StatusInternalServerError)
- }
- return reply.Channel, reply.Error
-}
-
-func (api *RemoteAPI) DeleteChannel(channelId string) *model.AppError {
- var reply APIErrorReply
- if err := api.client.Call("LocalAPI.DeleteChannel", channelId, &reply); err != nil {
- return model.NewAppError("RemoteAPI.DeleteChannel", "plugin.rpcplugin.invocation.error", nil, "err="+err.Error(), http.StatusInternalServerError)
- }
- return reply.Error
-}
-
-func (api *RemoteAPI) GetChannel(channelId string) (*model.Channel, *model.AppError) {
- var reply APIChannelReply
- if err := api.client.Call("LocalAPI.GetChannel", channelId, &reply); err != nil {
- return nil, model.NewAppError("RemoteAPI.GetChannel", "plugin.rpcplugin.invocation.error", nil, "err="+err.Error(), http.StatusInternalServerError)
- }
- return reply.Channel, reply.Error
-}
-
-func (api *RemoteAPI) GetChannelByName(name, teamId string) (*model.Channel, *model.AppError) {
- var reply APIChannelReply
- if err := api.client.Call("LocalAPI.GetChannelByName", &APIGetChannelByNameArgs{
- Name: name,
- TeamId: teamId,
- }, &reply); err != nil {
- return nil, model.NewAppError("RemoteAPI.GetChannelByName", "plugin.rpcplugin.invocation.error", nil, "err="+err.Error(), http.StatusInternalServerError)
- }
- return reply.Channel, reply.Error
-}
-
-func (api *RemoteAPI) GetDirectChannel(userId1, userId2 string) (*model.Channel, *model.AppError) {
- var reply APIChannelReply
- if err := api.client.Call("LocalAPI.GetDirectChannel", &APIGetDirectChannelArgs{
- UserId1: userId1,
- UserId2: userId2,
- }, &reply); err != nil {
- return nil, model.NewAppError("RemoteAPI.GetDirectChannel", "plugin.rpcplugin.invocation.error", nil, "err="+err.Error(), http.StatusInternalServerError)
- }
- return reply.Channel, reply.Error
-}
-
-func (api *RemoteAPI) GetGroupChannel(userIds []string) (*model.Channel, *model.AppError) {
- var reply APIChannelReply
- if err := api.client.Call("LocalAPI.GetGroupChannel", &APIGetGroupChannelArgs{
- UserIds: userIds,
- }, &reply); err != nil {
- return nil, model.NewAppError("RemoteAPI.GetGroupChannel", "plugin.rpcplugin.invocation.error", nil, "err="+err.Error(), http.StatusInternalServerError)
- }
- return reply.Channel, reply.Error
-}
-
-func (api *RemoteAPI) UpdateChannel(channel *model.Channel) (*model.Channel, *model.AppError) {
- var reply APIChannelReply
- if err := api.client.Call("LocalAPI.UpdateChannel", channel, &reply); err != nil {
- return nil, model.NewAppError("RemoteAPI.UpdateChannel", "plugin.rpcplugin.invocation.error", nil, "err="+err.Error(), http.StatusInternalServerError)
- }
- return reply.Channel, reply.Error
-}
-
-func (api *RemoteAPI) AddChannelMember(channelId, userId string) (*model.ChannelMember, *model.AppError) {
- var reply APIChannelMemberReply
- if err := api.client.Call("LocalAPI.AddChannelMember", &APIAddChannelMemberArgs{
- ChannelId: channelId,
- UserId: userId,
- }, &reply); err != nil {
- return nil, model.NewAppError("RemoteAPI.AddChannelMember", "plugin.rpcplugin.invocation.error", nil, "err="+err.Error(), http.StatusInternalServerError)
- }
- return reply.ChannelMember, reply.Error
-}
-
-func (api *RemoteAPI) GetChannelMember(channelId, userId string) (*model.ChannelMember, *model.AppError) {
- var reply APIChannelMemberReply
- if err := api.client.Call("LocalAPI.GetChannelMember", &APIGetChannelMemberArgs{
- ChannelId: channelId,
- UserId: userId,
- }, &reply); err != nil {
- return nil, model.NewAppError("RemoteAPI.GetChannelMember", "plugin.rpcplugin.invocation.error", nil, "err="+err.Error(), http.StatusInternalServerError)
- }
- return reply.ChannelMember, reply.Error
-}
-
-func (api *RemoteAPI) UpdateChannelMemberRoles(channelId, userId, newRoles string) (*model.ChannelMember, *model.AppError) {
- var reply APIChannelMemberReply
- if err := api.client.Call("LocalAPI.UpdateChannelMemberRoles", &APIUpdateChannelMemberRolesArgs{
- ChannelId: channelId,
- UserId: userId,
- NewRoles: newRoles,
- }, &reply); err != nil {
- return nil, model.NewAppError("RemoteAPI.UpdateChannelMemberRoles", "plugin.rpcplugin.invocation.error", nil, "err="+err.Error(), http.StatusInternalServerError)
- }
- return reply.ChannelMember, reply.Error
-}
-
-func (api *RemoteAPI) UpdateChannelMemberNotifications(channelId, userId string, notifications map[string]string) (*model.ChannelMember, *model.AppError) {
- var reply APIChannelMemberReply
- if err := api.client.Call("LocalAPI.UpdateChannelMemberNotifications", &APIUpdateChannelMemberNotificationsArgs{
- ChannelId: channelId,
- UserId: userId,
- Notifications: notifications,
- }, &reply); err != nil {
- return nil, model.NewAppError("RemoteAPI.UpdateChannelMemberNotifications", "plugin.rpcplugin.invocation.error", nil, "err="+err.Error(), http.StatusInternalServerError)
- }
- return reply.ChannelMember, reply.Error
-}
-
-func (api *RemoteAPI) DeleteChannelMember(channelId, userId string) *model.AppError {
- var reply APIErrorReply
- if err := api.client.Call("LocalAPI.DeleteChannelMember", &APIDeleteChannelMemberArgs{
- ChannelId: channelId,
- UserId: userId,
- }, &reply); err != nil {
- return model.NewAppError("RemoteAPI.DeleteChannelMember", "plugin.rpcplugin.invocation.error", nil, "err="+err.Error(), http.StatusInternalServerError)
- }
- return reply.Error
-}
-
-func (api *RemoteAPI) CreatePost(post *model.Post) (*model.Post, *model.AppError) {
- var reply APIPostReply
- if err := api.client.Call("LocalAPI.CreatePost", post, &reply); err != nil {
- return nil, model.NewAppError("RemoteAPI.CreatePost", "plugin.rpcplugin.invocation.error", nil, "err="+err.Error(), http.StatusInternalServerError)
- }
- return reply.Post, reply.Error
-}
-
-func (api *RemoteAPI) DeletePost(postId string) *model.AppError {
- var reply APIErrorReply
- if err := api.client.Call("LocalAPI.DeletePost", postId, &reply); err != nil {
- return model.NewAppError("RemoteAPI.DeletePost", "plugin.rpcplugin.invocation.error", nil, "err="+err.Error(), http.StatusInternalServerError)
- }
- return reply.Error
-}
-
-func (api *RemoteAPI) GetPost(postId string) (*model.Post, *model.AppError) {
- var reply APIPostReply
- if err := api.client.Call("LocalAPI.GetPost", postId, &reply); err != nil {
- return nil, model.NewAppError("RemoteAPI.GetPost", "plugin.rpcplugin.invocation.error", nil, "err="+err.Error(), http.StatusInternalServerError)
- }
- return reply.Post, reply.Error
-}
-
-func (api *RemoteAPI) UpdatePost(post *model.Post) (*model.Post, *model.AppError) {
- var reply APIPostReply
- if err := api.client.Call("LocalAPI.UpdatePost", post, &reply); err != nil {
- return nil, model.NewAppError("RemoteAPI.UpdatePost", "plugin.rpcplugin.invocation.error", nil, "err="+err.Error(), http.StatusInternalServerError)
- }
- return reply.Post, reply.Error
-}
-
-func (api *RemoteAPI) KeyValueStore() plugin.KeyValueStore {
- return api.keyValueStore
-}
-
-func (s *RemoteKeyValueStore) Set(key string, value []byte) *model.AppError {
- var reply APIErrorReply
- if err := s.api.client.Call("LocalAPI.KeyValueStoreSet", &APIKeyValueStoreSetArgs{Key: key, Value: value}, &reply); err != nil {
- return model.NewAppError("RemoteAPI.KeyValueStoreSet", "plugin.rpcplugin.invocation.error", nil, "err="+err.Error(), http.StatusInternalServerError)
- }
- return reply.Error
-}
-
-func (s *RemoteKeyValueStore) Get(key string) ([]byte, *model.AppError) {
- var reply APIKeyValueStoreReply
- if err := s.api.client.Call("LocalAPI.KeyValueStoreGet", key, &reply); err != nil {
- return nil, model.NewAppError("RemoteAPI.KeyValueStoreGet", "plugin.rpcplugin.invocation.error", nil, "err="+err.Error(), http.StatusInternalServerError)
- }
- return reply.Value, reply.Error
-}
-
-func (s *RemoteKeyValueStore) Delete(key string) *model.AppError {
- var reply APIErrorReply
- if err := s.api.client.Call("LocalAPI.KeyValueStoreDelete", key, &reply); err != nil {
- return model.NewAppError("RemoteAPI.KeyValueStoreDelete", "plugin.rpcplugin.invocation.error", nil, "err="+err.Error(), http.StatusInternalServerError)
- }
- return reply.Error
-}
-
-func (h *RemoteAPI) Close() error {
- return h.client.Close()
-}
-
-func ConnectAPI(conn io.ReadWriteCloser, muxer *Muxer) *RemoteAPI {
- remoteKeyValueStore := &RemoteKeyValueStore{}
- remoteApi := &RemoteAPI{
- client: rpc.NewClient(conn),
- muxer: muxer,
- keyValueStore: remoteKeyValueStore,
- }
-
- remoteKeyValueStore.api = remoteApi
-
- return remoteApi
-}
-
-func init() {
- gob.Register([]*model.SlackAttachment{})
- gob.Register([]interface{}{})
- gob.Register(map[string]interface{}{})
-}
diff --git a/plugin/rpcplugin/api_test.go b/plugin/rpcplugin/api_test.go
deleted file mode 100644
index 04d8e5d86..000000000
--- a/plugin/rpcplugin/api_test.go
+++ /dev/null
@@ -1,300 +0,0 @@
-package rpcplugin
-
-import (
- "encoding/json"
- "fmt"
- "io"
- "net/http"
- "testing"
-
- "github.com/stretchr/testify/assert"
- "github.com/stretchr/testify/mock"
- "github.com/stretchr/testify/require"
-
- "github.com/mattermost/mattermost-server/model"
- "github.com/mattermost/mattermost-server/plugin"
- "github.com/mattermost/mattermost-server/plugin/plugintest"
-)
-
-func testAPIRPC(api plugin.API, f func(plugin.API)) {
- r1, w1 := io.Pipe()
- r2, w2 := io.Pipe()
-
- c1 := NewMuxer(NewReadWriteCloser(r1, w2), false)
- defer c1.Close()
-
- c2 := NewMuxer(NewReadWriteCloser(r2, w1), true)
- defer c2.Close()
-
- id, server := c1.Serve()
- go ServeAPI(api, server, c1)
-
- remote := ConnectAPI(c2.Connect(id), c2)
- defer remote.Close()
-
- f(remote)
-}
-
-func TestAPI(t *testing.T) {
- keyValueStore := &plugintest.KeyValueStore{}
- api := plugintest.API{Store: keyValueStore}
- defer api.AssertExpectations(t)
-
- type Config struct {
- Foo string
- Bar struct {
- Baz string
- }
- }
-
- api.On("LoadPluginConfiguration", mock.MatchedBy(func(x interface{}) bool { return true })).Run(func(args mock.Arguments) {
- dest := args.Get(0).(interface{})
- json.Unmarshal([]byte(`{"Foo": "foo", "Bar": {"Baz": "baz"}}`), dest)
- }).Return(nil)
-
- testChannel := &model.Channel{
- Id: "thechannelid",
- }
-
- testChannelMember := &model.ChannelMember{
- ChannelId: "thechannelid",
- UserId: "theuserid",
- }
-
- testTeam := &model.Team{
- Id: "theteamid",
- }
- teamNotFoundError := model.NewAppError("SqlTeamStore.GetByName", "store.sql_team.get_by_name.app_error", nil, "name=notateam", http.StatusNotFound)
-
- testUser := &model.User{
- Id: "theuserid",
- }
-
- testPost := &model.Post{
- Message: "hello",
- }
-
- testAPIRPC(&api, func(remote plugin.API) {
- var config Config
- assert.NoError(t, remote.LoadPluginConfiguration(&config))
- assert.Equal(t, "foo", config.Foo)
- assert.Equal(t, "baz", config.Bar.Baz)
-
- api.On("RegisterCommand", mock.AnythingOfType("*model.Command")).Return(fmt.Errorf("foo")).Once()
- assert.Error(t, remote.RegisterCommand(&model.Command{}))
- api.On("RegisterCommand", mock.AnythingOfType("*model.Command")).Return(nil).Once()
- assert.NoError(t, remote.RegisterCommand(&model.Command{}))
-
- api.On("UnregisterCommand", "team", "trigger").Return(fmt.Errorf("foo")).Once()
- assert.Error(t, remote.UnregisterCommand("team", "trigger"))
- api.On("UnregisterCommand", "team", "trigger").Return(nil).Once()
- assert.NoError(t, remote.UnregisterCommand("team", "trigger"))
-
- api.On("CreateChannel", mock.AnythingOfType("*model.Channel")).Return(func(c *model.Channel) *model.Channel {
- c.Id = "thechannelid"
- return c
- }, nil).Once()
- channel, err := remote.CreateChannel(testChannel)
- assert.Equal(t, "thechannelid", channel.Id)
- assert.Nil(t, err)
-
- api.On("DeleteChannel", "thechannelid").Return(nil).Once()
- assert.Nil(t, remote.DeleteChannel("thechannelid"))
-
- api.On("GetChannel", "thechannelid").Return(testChannel, nil).Once()
- channel, err = remote.GetChannel("thechannelid")
- assert.Equal(t, testChannel, channel)
- assert.Nil(t, err)
-
- api.On("GetChannelByName", "foo", "theteamid").Return(testChannel, nil).Once()
- channel, err = remote.GetChannelByName("foo", "theteamid")
- assert.Equal(t, testChannel, channel)
- assert.Nil(t, err)
-
- api.On("GetDirectChannel", "user1", "user2").Return(testChannel, nil).Once()
- channel, err = remote.GetDirectChannel("user1", "user2")
- assert.Equal(t, testChannel, channel)
- assert.Nil(t, err)
-
- api.On("GetGroupChannel", []string{"user1", "user2", "user3"}).Return(testChannel, nil).Once()
- channel, err = remote.GetGroupChannel([]string{"user1", "user2", "user3"})
- assert.Equal(t, testChannel, channel)
- assert.Nil(t, err)
-
- api.On("UpdateChannel", mock.AnythingOfType("*model.Channel")).Return(func(c *model.Channel) *model.Channel {
- return c
- }, nil).Once()
- channel, err = remote.UpdateChannel(testChannel)
- assert.Equal(t, testChannel, channel)
- assert.Nil(t, err)
-
- api.On("AddChannelMember", testChannel.Id, "theuserid").Return(testChannelMember, nil).Once()
- member, err := remote.AddChannelMember(testChannel.Id, "theuserid")
- assert.Equal(t, testChannelMember, member)
- assert.Nil(t, err)
-
- api.On("GetChannelMember", "thechannelid", "theuserid").Return(testChannelMember, nil).Once()
- member, err = remote.GetChannelMember("thechannelid", "theuserid")
- assert.Equal(t, testChannelMember, member)
- assert.Nil(t, err)
-
- api.On("UpdateChannelMemberRoles", testChannel.Id, "theuserid", model.CHANNEL_ADMIN_ROLE_ID).Return(testChannelMember, nil).Once()
- member, err = remote.UpdateChannelMemberRoles(testChannel.Id, "theuserid", model.CHANNEL_ADMIN_ROLE_ID)
- assert.Equal(t, testChannelMember, member)
- assert.Nil(t, err)
-
- notifications := map[string]string{}
- notifications[model.MARK_UNREAD_NOTIFY_PROP] = model.CHANNEL_MARK_UNREAD_MENTION
- api.On("UpdateChannelMemberNotifications", testChannel.Id, "theuserid", notifications).Return(testChannelMember, nil).Once()
- member, err = remote.UpdateChannelMemberNotifications(testChannel.Id, "theuserid", notifications)
- assert.Equal(t, testChannelMember, member)
- assert.Nil(t, err)
-
- api.On("DeleteChannelMember", "thechannelid", "theuserid").Return(nil).Once()
- err = remote.DeleteChannelMember("thechannelid", "theuserid")
- assert.Nil(t, err)
-
- api.On("CreateUser", mock.AnythingOfType("*model.User")).Return(func(u *model.User) *model.User {
- u.Id = "theuserid"
- return u
- }, nil).Once()
- user, err := remote.CreateUser(testUser)
- assert.Equal(t, "theuserid", user.Id)
- assert.Nil(t, err)
-
- api.On("DeleteUser", "theuserid").Return(nil).Once()
- assert.Nil(t, remote.DeleteUser("theuserid"))
-
- api.On("GetUser", "theuserid").Return(testUser, nil).Once()
- user, err = remote.GetUser("theuserid")
- assert.Equal(t, testUser, user)
- assert.Nil(t, err)
-
- api.On("GetUserByEmail", "foo@foo").Return(testUser, nil).Once()
- user, err = remote.GetUserByEmail("foo@foo")
- assert.Equal(t, testUser, user)
- assert.Nil(t, err)
-
- api.On("GetUserByUsername", "foo").Return(testUser, nil).Once()
- user, err = remote.GetUserByUsername("foo")
- assert.Equal(t, testUser, user)
- assert.Nil(t, err)
-
- api.On("UpdateUser", mock.AnythingOfType("*model.User")).Return(func(u *model.User) *model.User {
- return u
- }, nil).Once()
- user, err = remote.UpdateUser(testUser)
- assert.Equal(t, testUser, user)
- assert.Nil(t, err)
-
- api.On("CreateTeam", mock.AnythingOfType("*model.Team")).Return(func(t *model.Team) *model.Team {
- t.Id = "theteamid"
- return t
- }, nil).Once()
- team, err := remote.CreateTeam(testTeam)
- assert.Equal(t, "theteamid", team.Id)
- assert.Nil(t, err)
-
- api.On("DeleteTeam", "theteamid").Return(nil).Once()
- assert.Nil(t, remote.DeleteTeam("theteamid"))
-
- api.On("GetTeam", "theteamid").Return(testTeam, nil).Once()
- team, err = remote.GetTeam("theteamid")
- assert.Equal(t, testTeam, team)
- assert.Nil(t, err)
-
- api.On("GetTeamByName", "foo").Return(testTeam, nil).Once()
- team, err = remote.GetTeamByName("foo")
- assert.Equal(t, testTeam, team)
- assert.Nil(t, err)
-
- api.On("GetTeamByName", "notateam").Return(nil, teamNotFoundError).Once()
- team, err = remote.GetTeamByName("notateam")
- assert.Nil(t, team)
- assert.Equal(t, teamNotFoundError, err)
-
- api.On("UpdateTeam", mock.AnythingOfType("*model.Team")).Return(func(t *model.Team) *model.Team {
- return t
- }, nil).Once()
- team, err = remote.UpdateTeam(testTeam)
- assert.Equal(t, testTeam, team)
- assert.Nil(t, err)
-
- api.On("CreatePost", mock.AnythingOfType("*model.Post")).Return(func(p *model.Post) *model.Post {
- p.Id = "thepostid"
- return p
- }, nil).Once()
- post, err := remote.CreatePost(testPost)
- require.Nil(t, err)
- assert.NotEmpty(t, post.Id)
- assert.Equal(t, testPost.Message, post.Message)
-
- api.On("DeletePost", "thepostid").Return(nil).Once()
- assert.Nil(t, remote.DeletePost("thepostid"))
-
- api.On("GetPost", "thepostid").Return(testPost, nil).Once()
- post, err = remote.GetPost("thepostid")
- assert.Equal(t, testPost, post)
- assert.Nil(t, err)
-
- api.On("UpdatePost", mock.AnythingOfType("*model.Post")).Return(func(p *model.Post) *model.Post {
- return p
- }, nil).Once()
- post, err = remote.UpdatePost(testPost)
- assert.Equal(t, testPost, post)
- assert.Nil(t, err)
-
- api.KeyValueStore().(*plugintest.KeyValueStore).On("Set", "thekey", []byte("thevalue")).Return(nil).Once()
- err = remote.KeyValueStore().Set("thekey", []byte("thevalue"))
- assert.Nil(t, err)
-
- api.KeyValueStore().(*plugintest.KeyValueStore).On("Get", "thekey").Return(func(key string) []byte {
- return []byte("thevalue")
- }, nil).Once()
- ret, err := remote.KeyValueStore().Get("thekey")
- assert.Nil(t, err)
- assert.Equal(t, []byte("thevalue"), ret)
-
- api.KeyValueStore().(*plugintest.KeyValueStore).On("Delete", "thekey").Return(nil).Once()
- err = remote.KeyValueStore().Delete("thekey")
- assert.Nil(t, err)
- })
-}
-
-func TestAPI_GobRegistration(t *testing.T) {
- keyValueStore := &plugintest.KeyValueStore{}
- api := plugintest.API{Store: keyValueStore}
- defer api.AssertExpectations(t)
-
- testAPIRPC(&api, func(remote plugin.API) {
- api.On("CreatePost", mock.AnythingOfType("*model.Post")).Return(func(p *model.Post) *model.Post {
- p.Id = "thepostid"
- return p
- }, nil).Once()
- _, err := remote.CreatePost(&model.Post{
- Message: "hello",
- Props: map[string]interface{}{
- "attachments": []*model.SlackAttachment{
- &model.SlackAttachment{
- Actions: []*model.PostAction{
- &model.PostAction{
- Integration: &model.PostActionIntegration{
- Context: map[string]interface{}{
- "foo": "bar",
- "foos": []interface{}{"bar", "baz", 1, 2},
- "foo_map": map[string]interface{}{
- "1": "bar",
- "2": 2,
- },
- },
- },
- },
- },
- Timestamp: 1,
- },
- },
- },
- })
- require.Nil(t, err)
- })
-}
diff --git a/plugin/rpcplugin/hooks.go b/plugin/rpcplugin/hooks.go
deleted file mode 100644
index 6af98873a..000000000
--- a/plugin/rpcplugin/hooks.go
+++ /dev/null
@@ -1,398 +0,0 @@
-// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-package rpcplugin
-
-import (
- "bytes"
- "io"
- "io/ioutil"
- "net/http"
- "net/rpc"
- "reflect"
-
- "github.com/mattermost/mattermost-server/mlog"
- "github.com/mattermost/mattermost-server/model"
- "github.com/mattermost/mattermost-server/plugin"
-)
-
-type LocalHooks struct {
- hooks interface{}
- muxer *Muxer
- remoteAPI *RemoteAPI
-}
-
-// Implemented replies with the names of the hooks that are implemented.
-func (h *LocalHooks) Implemented(args struct{}, reply *[]string) error {
- ifaceType := reflect.TypeOf((*plugin.Hooks)(nil)).Elem()
- implType := reflect.TypeOf(h.hooks)
- selfType := reflect.TypeOf(h)
- var methods []string
- for i := 0; i < ifaceType.NumMethod(); i++ {
- method := ifaceType.Method(i)
- if m, ok := implType.MethodByName(method.Name); !ok {
- continue
- } else if m.Type.NumIn() != method.Type.NumIn()+1 {
- continue
- } else if m.Type.NumOut() != method.Type.NumOut() {
- continue
- } else {
- match := true
- for j := 0; j < method.Type.NumIn(); j++ {
- if m.Type.In(j+1) != method.Type.In(j) {
- match = false
- break
- }
- }
- for j := 0; j < method.Type.NumOut(); j++ {
- if m.Type.Out(j) != method.Type.Out(j) {
- match = false
- break
- }
- }
- if !match {
- continue
- }
- }
- if _, ok := selfType.MethodByName(method.Name); !ok {
- continue
- }
- methods = append(methods, method.Name)
- }
- *reply = methods
- return nil
-}
-
-func (h *LocalHooks) OnActivate(args int64, reply *struct{}) error {
- if h.remoteAPI != nil {
- h.remoteAPI.Close()
- h.remoteAPI = nil
- }
- if hook, ok := h.hooks.(interface {
- OnActivate(plugin.API) error
- }); ok {
- stream := h.muxer.Connect(args)
- h.remoteAPI = ConnectAPI(stream, h.muxer)
- return hook.OnActivate(h.remoteAPI)
- }
- return nil
-}
-
-func (h *LocalHooks) OnDeactivate(args, reply *struct{}) (err error) {
- if hook, ok := h.hooks.(interface {
- OnDeactivate() error
- }); ok {
- err = hook.OnDeactivate()
- }
- if h.remoteAPI != nil {
- h.remoteAPI.Close()
- h.remoteAPI = nil
- }
- return
-}
-
-func (h *LocalHooks) OnConfigurationChange(args, reply *struct{}) error {
- if hook, ok := h.hooks.(interface {
- OnConfigurationChange() error
- }); ok {
- return hook.OnConfigurationChange()
- }
- return nil
-}
-
-type ServeHTTPArgs struct {
- ResponseWriterStream int64
- Request *http.Request
- RequestBodyStream int64
-}
-
-func (h *LocalHooks) ServeHTTP(args ServeHTTPArgs, reply *struct{}) error {
- w := ConnectHTTPResponseWriter(h.muxer.Connect(args.ResponseWriterStream))
- defer w.Close()
-
- r := args.Request
- if args.RequestBodyStream != 0 {
- r.Body = ConnectIOReader(h.muxer.Connect(args.RequestBodyStream))
- } else {
- r.Body = ioutil.NopCloser(&bytes.Buffer{})
- }
- defer r.Body.Close()
-
- if hook, ok := h.hooks.(http.Handler); ok {
- hook.ServeHTTP(w, r)
- } else {
- http.NotFound(w, r)
- }
-
- return nil
-}
-
-type HooksExecuteCommandReply struct {
- Response *model.CommandResponse
- Error *model.AppError
-}
-
-func (h *LocalHooks) ExecuteCommand(args *model.CommandArgs, reply *HooksExecuteCommandReply) error {
- if hook, ok := h.hooks.(interface {
- ExecuteCommand(*model.CommandArgs) (*model.CommandResponse, *model.AppError)
- }); ok {
- reply.Response, reply.Error = hook.ExecuteCommand(args)
- }
- return nil
-}
-
-type MessageWillBeReply struct {
- Post *model.Post
- RejectionReason string
-}
-
-type MessageUpdatedArgs struct {
- NewPost *model.Post
- OldPost *model.Post
-}
-
-func (h *LocalHooks) MessageWillBePosted(args *model.Post, reply *MessageWillBeReply) error {
- if hook, ok := h.hooks.(interface {
- MessageWillBePosted(*model.Post) (*model.Post, string)
- }); ok {
- reply.Post, reply.RejectionReason = hook.MessageWillBePosted(args)
- }
- return nil
-}
-
-func (h *LocalHooks) MessageWillBeUpdated(args *MessageUpdatedArgs, reply *MessageWillBeReply) error {
- if hook, ok := h.hooks.(interface {
- MessageWillBeUpdated(*model.Post, *model.Post) (*model.Post, string)
- }); ok {
- reply.Post, reply.RejectionReason = hook.MessageWillBeUpdated(args.NewPost, args.OldPost)
- }
- return nil
-}
-
-func (h *LocalHooks) MessageHasBeenPosted(args *model.Post, reply *struct{}) error {
- if hook, ok := h.hooks.(interface {
- MessageHasBeenPosted(*model.Post)
- }); ok {
- hook.MessageHasBeenPosted(args)
- }
- return nil
-}
-
-func (h *LocalHooks) MessageHasBeenUpdated(args *MessageUpdatedArgs, reply *struct{}) error {
- if hook, ok := h.hooks.(interface {
- MessageHasBeenUpdated(*model.Post, *model.Post)
- }); ok {
- hook.MessageHasBeenUpdated(args.NewPost, args.OldPost)
- }
- return nil
-}
-
-func ServeHooks(hooks interface{}, conn io.ReadWriteCloser, muxer *Muxer) {
- server := rpc.NewServer()
- server.Register(&LocalHooks{
- hooks: hooks,
- muxer: muxer,
- })
- server.ServeConn(conn)
-}
-
-// These assignments are part of the wire protocol. You can add more, but should not change existing
-// assignments.
-const (
- remoteOnActivate = 0
- remoteOnDeactivate = 1
- remoteServeHTTP = 2
- remoteOnConfigurationChange = 3
- remoteExecuteCommand = 4
- remoteMessageWillBePosted = 5
- remoteMessageWillBeUpdated = 6
- remoteMessageHasBeenPosted = 7
- remoteMessageHasBeenUpdated = 8
- maxRemoteHookCount = iota
-)
-
-type RemoteHooks struct {
- client *rpc.Client
- muxer *Muxer
- apiCloser io.Closer
- implemented [maxRemoteHookCount]bool
- pluginId string
-}
-
-var _ plugin.Hooks = (*RemoteHooks)(nil)
-
-func (h *RemoteHooks) Implemented() (impl []string, err error) {
- err = h.client.Call("LocalHooks.Implemented", struct{}{}, &impl)
- return
-}
-
-func (h *RemoteHooks) OnActivate(api plugin.API) error {
- if h.apiCloser != nil {
- h.apiCloser.Close()
- h.apiCloser = nil
- }
- if !h.implemented[remoteOnActivate] {
- return nil
- }
- id, stream := h.muxer.Serve()
- h.apiCloser = stream
- go ServeAPI(api, stream, h.muxer)
- return h.client.Call("LocalHooks.OnActivate", id, nil)
-}
-
-func (h *RemoteHooks) OnDeactivate() error {
- if !h.implemented[remoteOnDeactivate] {
- return nil
- }
- return h.client.Call("LocalHooks.OnDeactivate", struct{}{}, nil)
-}
-
-func (h *RemoteHooks) OnConfigurationChange() error {
- if !h.implemented[remoteOnConfigurationChange] {
- return nil
- }
- return h.client.Call("LocalHooks.OnConfigurationChange", struct{}{}, nil)
-}
-
-func (h *RemoteHooks) ServeHTTP(w http.ResponseWriter, r *http.Request) {
- if !h.implemented[remoteServeHTTP] {
- http.NotFound(w, r)
- return
- }
-
- responseWriterStream, stream := h.muxer.Serve()
- defer stream.Close()
- go ServeHTTPResponseWriter(w, stream)
-
- requestBodyStream := int64(0)
- if r.Body != nil {
- rid, rstream := h.muxer.Serve()
- defer rstream.Close()
- go ServeIOReader(r.Body, rstream)
- requestBodyStream = rid
- }
-
- forwardedRequest := &http.Request{
- Method: r.Method,
- URL: r.URL,
- Proto: r.Proto,
- ProtoMajor: r.ProtoMajor,
- ProtoMinor: r.ProtoMinor,
- Header: r.Header,
- Host: r.Host,
- RemoteAddr: r.RemoteAddr,
- RequestURI: r.RequestURI,
- }
-
- if err := h.client.Call("LocalHooks.ServeHTTP", ServeHTTPArgs{
- ResponseWriterStream: responseWriterStream,
- Request: forwardedRequest,
- RequestBodyStream: requestBodyStream,
- }, nil); err != nil {
- mlog.Error("Plugin failed to ServeHTTP", mlog.String("plugin_id", h.pluginId), mlog.Err(err))
- http.Error(w, "500 internal server error", http.StatusInternalServerError)
- }
-}
-
-func (h *RemoteHooks) ExecuteCommand(args *model.CommandArgs) (*model.CommandResponse, *model.AppError) {
- if !h.implemented[remoteExecuteCommand] {
- return nil, model.NewAppError("RemoteHooks.ExecuteCommand", "plugin.rpcplugin.invocation.error", nil, "err=ExecuteCommand hook not implemented", http.StatusInternalServerError)
- }
- var reply HooksExecuteCommandReply
- if err := h.client.Call("LocalHooks.ExecuteCommand", args, &reply); err != nil {
- return nil, model.NewAppError("RemoteHooks.ExecuteCommand", "plugin.rpcplugin.invocation.error", nil, "err="+err.Error(), http.StatusInternalServerError)
- }
- return reply.Response, reply.Error
-}
-
-func (h *RemoteHooks) MessageWillBePosted(args *model.Post) (*model.Post, string) {
- if !h.implemented[remoteMessageWillBePosted] {
- return args, ""
- }
- var reply MessageWillBeReply
- if err := h.client.Call("LocalHooks.MessageWillBePosted", args, &reply); err != nil {
- return nil, ""
- }
- return reply.Post, reply.RejectionReason
-}
-
-func (h *RemoteHooks) MessageWillBeUpdated(newPost, oldPost *model.Post) (*model.Post, string) {
- if !h.implemented[remoteMessageWillBeUpdated] {
- return newPost, ""
- }
- var reply MessageWillBeReply
- args := &MessageUpdatedArgs{
- NewPost: newPost,
- OldPost: oldPost,
- }
- if err := h.client.Call("LocalHooks.MessageWillBeUpdated", args, &reply); err != nil {
- return nil, ""
- }
- return reply.Post, reply.RejectionReason
-}
-
-func (h *RemoteHooks) MessageHasBeenPosted(args *model.Post) {
- if !h.implemented[remoteMessageHasBeenPosted] {
- return
- }
- if err := h.client.Call("LocalHooks.MessageHasBeenPosted", args, nil); err != nil {
- return
- }
-}
-
-func (h *RemoteHooks) MessageHasBeenUpdated(newPost, oldPost *model.Post) {
- if !h.implemented[remoteMessageHasBeenUpdated] {
- return
- }
- args := &MessageUpdatedArgs{
- NewPost: newPost,
- OldPost: oldPost,
- }
- if err := h.client.Call("LocalHooks.MessageHasBeenUpdated", args, nil); err != nil {
- return
- }
-}
-
-func (h *RemoteHooks) Close() error {
- if h.apiCloser != nil {
- h.apiCloser.Close()
- h.apiCloser = nil
- }
- return h.client.Close()
-}
-
-func ConnectHooks(conn io.ReadWriteCloser, muxer *Muxer, pluginId string) (*RemoteHooks, error) {
- remote := &RemoteHooks{
- client: rpc.NewClient(conn),
- muxer: muxer,
- pluginId: pluginId,
- }
- implemented, err := remote.Implemented()
- if err != nil {
- remote.Close()
- return nil, err
- }
- for _, method := range implemented {
- switch method {
- case "OnActivate":
- remote.implemented[remoteOnActivate] = true
- case "OnDeactivate":
- remote.implemented[remoteOnDeactivate] = true
- case "OnConfigurationChange":
- remote.implemented[remoteOnConfigurationChange] = true
- case "ServeHTTP":
- remote.implemented[remoteServeHTTP] = true
- case "ExecuteCommand":
- remote.implemented[remoteExecuteCommand] = true
- case "MessageWillBePosted":
- remote.implemented[remoteMessageWillBePosted] = true
- case "MessageWillBeUpdated":
- remote.implemented[remoteMessageWillBeUpdated] = true
- case "MessageHasBeenPosted":
- remote.implemented[remoteMessageHasBeenPosted] = true
- case "MessageHasBeenUpdated":
- remote.implemented[remoteMessageHasBeenUpdated] = true
- }
- }
- return remote, nil
-}
diff --git a/plugin/rpcplugin/hooks_test.go b/plugin/rpcplugin/hooks_test.go
deleted file mode 100644
index a7bac982e..000000000
--- a/plugin/rpcplugin/hooks_test.go
+++ /dev/null
@@ -1,237 +0,0 @@
-package rpcplugin
-
-import (
- "io"
- "io/ioutil"
- "net/http"
- "net/http/httptest"
- "strings"
- "sync"
- "testing"
-
- "github.com/stretchr/testify/assert"
- "github.com/stretchr/testify/mock"
- "github.com/stretchr/testify/require"
-
- "github.com/mattermost/mattermost-server/model"
- "github.com/mattermost/mattermost-server/plugin"
- "github.com/mattermost/mattermost-server/plugin/plugintest"
-)
-
-func testHooksRPC(hooks interface{}, f func(*RemoteHooks)) error {
- r1, w1 := io.Pipe()
- r2, w2 := io.Pipe()
-
- c1 := NewMuxer(NewReadWriteCloser(r1, w2), false)
- defer c1.Close()
-
- c2 := NewMuxer(NewReadWriteCloser(r2, w1), true)
- defer c2.Close()
-
- id, server := c1.Serve()
- go ServeHooks(hooks, server, c1)
-
- remote, err := ConnectHooks(c2.Connect(id), c2, "plugin_id")
- if err != nil {
- return err
- }
- defer remote.Close()
-
- f(remote)
- return nil
-}
-
-func TestHooks(t *testing.T) {
- var api plugintest.API
- var hooks plugintest.Hooks
- defer hooks.AssertExpectations(t)
-
- assert.NoError(t, testHooksRPC(&hooks, func(remote *RemoteHooks) {
- hooks.On("OnActivate", mock.AnythingOfType("*rpcplugin.RemoteAPI")).Return(nil)
- assert.NoError(t, remote.OnActivate(&api))
-
- hooks.On("OnDeactivate").Return(nil)
- assert.NoError(t, remote.OnDeactivate())
-
- hooks.On("OnConfigurationChange").Return(nil)
- assert.NoError(t, remote.OnConfigurationChange())
-
- hooks.On("ServeHTTP", mock.AnythingOfType("*rpcplugin.RemoteHTTPResponseWriter"), mock.AnythingOfType("*http.Request")).Run(func(args mock.Arguments) {
- w := args.Get(0).(http.ResponseWriter)
- r := args.Get(1).(*http.Request)
- assert.Equal(t, "/foo", r.URL.Path)
- assert.Equal(t, "POST", r.Method)
- body, err := ioutil.ReadAll(r.Body)
- assert.NoError(t, err)
- assert.Equal(t, "asdf", string(body))
- assert.Equal(t, "header", r.Header.Get("Test-Header"))
- w.Write([]byte("bar"))
- })
-
- w := httptest.NewRecorder()
- r, err := http.NewRequest("POST", "/foo", strings.NewReader("asdf"))
- r.Header.Set("Test-Header", "header")
- assert.NoError(t, err)
- remote.ServeHTTP(w, r)
-
- resp := w.Result()
- defer resp.Body.Close()
- assert.Equal(t, http.StatusOK, resp.StatusCode)
- body, err := ioutil.ReadAll(resp.Body)
- assert.NoError(t, err)
- assert.Equal(t, "bar", string(body))
-
- hooks.On("ExecuteCommand", &model.CommandArgs{
- Command: "/foo",
- }).Return(&model.CommandResponse{
- Text: "bar",
- }, nil)
- commandResponse, appErr := hooks.ExecuteCommand(&model.CommandArgs{
- Command: "/foo",
- })
- assert.Equal(t, "bar", commandResponse.Text)
- assert.Nil(t, appErr)
-
- hooks.On("MessageWillBePosted", mock.AnythingOfType("*model.Post")).Return(func(post *model.Post) *model.Post {
- post.Message += "_testing"
- return post
- }, "changemessage")
- post, changemessage := remote.MessageWillBePosted(&model.Post{Id: "1", Message: "base"})
- assert.Equal(t, "changemessage", changemessage)
- assert.Equal(t, "base_testing", post.Message)
- assert.Equal(t, "1", post.Id)
-
- hooks.On("MessageWillBeUpdated", mock.AnythingOfType("*model.Post"), mock.AnythingOfType("*model.Post")).Return(func(newPost, oldPost *model.Post) *model.Post {
- newPost.Message += "_testing"
- return newPost
- }, "changemessage2")
- post2, changemessage2 := remote.MessageWillBeUpdated(&model.Post{Id: "2", Message: "base2"}, &model.Post{Id: "OLD", Message: "OLDMESSAGE"})
- assert.Equal(t, "changemessage2", changemessage2)
- assert.Equal(t, "base2_testing", post2.Message)
- assert.Equal(t, "2", post2.Id)
-
- hooks.On("MessageHasBeenPosted", mock.AnythingOfType("*model.Post")).Return(nil)
- remote.MessageHasBeenPosted(&model.Post{})
-
- hooks.On("MessageHasBeenUpdated", mock.AnythingOfType("*model.Post"), mock.AnythingOfType("*model.Post")).Return(nil)
- remote.MessageHasBeenUpdated(&model.Post{}, &model.Post{})
- }))
-}
-
-func TestHooks_Concurrency(t *testing.T) {
- var hooks plugintest.Hooks
- defer hooks.AssertExpectations(t)
-
- assert.NoError(t, testHooksRPC(&hooks, func(remote *RemoteHooks) {
- ch := make(chan bool)
-
- hooks.On("ServeHTTP", mock.AnythingOfType("*rpcplugin.RemoteHTTPResponseWriter"), mock.AnythingOfType("*http.Request")).Run(func(args mock.Arguments) {
- r := args.Get(1).(*http.Request)
- if r.URL.Path == "/1" {
- <-ch
- } else {
- ch <- true
- }
- })
-
- rec := httptest.NewRecorder()
-
- wg := sync.WaitGroup{}
- wg.Add(2)
-
- go func() {
- req, err := http.NewRequest("GET", "/1", nil)
- require.NoError(t, err)
- remote.ServeHTTP(rec, req)
- wg.Done()
- }()
-
- go func() {
- req, err := http.NewRequest("GET", "/2", nil)
- require.NoError(t, err)
- remote.ServeHTTP(rec, req)
- wg.Done()
- }()
-
- wg.Wait()
- }))
-}
-
-type testHooks struct {
- mock.Mock
-}
-
-func (h *testHooks) OnActivate(api plugin.API) error {
- return h.Called(api).Error(0)
-}
-
-func TestHooks_PartiallyImplemented(t *testing.T) {
- var api plugintest.API
- var hooks testHooks
- defer hooks.AssertExpectations(t)
-
- assert.NoError(t, testHooksRPC(&hooks, func(remote *RemoteHooks) {
- implemented, err := remote.Implemented()
- assert.NoError(t, err)
- assert.Equal(t, []string{"OnActivate"}, implemented)
-
- hooks.On("OnActivate", mock.AnythingOfType("*rpcplugin.RemoteAPI")).Return(nil)
- assert.NoError(t, remote.OnActivate(&api))
-
- assert.NoError(t, remote.OnDeactivate())
- }))
-}
-
-type benchmarkHooks struct{}
-
-func (*benchmarkHooks) OnDeactivate() error { return nil }
-
-func (*benchmarkHooks) ServeHTTP(w http.ResponseWriter, r *http.Request) {
- ioutil.ReadAll(r.Body)
- w.Header().Set("Foo-Header", "foo")
- http.Error(w, "foo", http.StatusBadRequest)
-}
-
-func BenchmarkHooks_OnDeactivate(b *testing.B) {
- var hooks benchmarkHooks
-
- if err := testHooksRPC(&hooks, func(remote *RemoteHooks) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- remote.OnDeactivate()
- }
- b.StopTimer()
- }); err != nil {
- b.Fatal(err.Error())
- }
-}
-
-func BenchmarkHooks_ServeHTTP(b *testing.B) {
- var hooks benchmarkHooks
-
- if err := testHooksRPC(&hooks, func(remote *RemoteHooks) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- w := httptest.NewRecorder()
- r, _ := http.NewRequest("POST", "/foo", strings.NewReader("12345678901234567890"))
- remote.ServeHTTP(w, r)
- }
- b.StopTimer()
- }); err != nil {
- b.Fatal(err.Error())
- }
-}
-
-func BenchmarkHooks_Unimplemented(b *testing.B) {
- var hooks testHooks
-
- if err := testHooksRPC(&hooks, func(remote *RemoteHooks) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- remote.OnDeactivate()
- }
- b.StopTimer()
- }); err != nil {
- b.Fatal(err.Error())
- }
-}
diff --git a/plugin/rpcplugin/http.go b/plugin/rpcplugin/http.go
deleted file mode 100644
index 72b1aa445..000000000
--- a/plugin/rpcplugin/http.go
+++ /dev/null
@@ -1,91 +0,0 @@
-// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-package rpcplugin
-
-import (
- "io"
- "net/http"
- "net/rpc"
-)
-
-type LocalHTTPResponseWriter struct {
- w http.ResponseWriter
-}
-
-func (w *LocalHTTPResponseWriter) Header(args struct{}, reply *http.Header) error {
- *reply = w.w.Header()
- return nil
-}
-
-func (w *LocalHTTPResponseWriter) Write(args []byte, reply *struct{}) error {
- _, err := w.w.Write(args)
- return err
-}
-
-func (w *LocalHTTPResponseWriter) WriteHeader(args int, reply *struct{}) error {
- w.w.WriteHeader(args)
- return nil
-}
-
-func (w *LocalHTTPResponseWriter) SyncHeader(args http.Header, reply *struct{}) error {
- dest := w.w.Header()
- for k := range dest {
- if _, ok := args[k]; !ok {
- delete(dest, k)
- }
- }
- for k, v := range args {
- dest[k] = v
- }
- return nil
-}
-
-func ServeHTTPResponseWriter(w http.ResponseWriter, conn io.ReadWriteCloser) {
- server := rpc.NewServer()
- server.Register(&LocalHTTPResponseWriter{
- w: w,
- })
- server.ServeConn(conn)
-}
-
-type RemoteHTTPResponseWriter struct {
- client *rpc.Client
- header http.Header
-}
-
-var _ http.ResponseWriter = (*RemoteHTTPResponseWriter)(nil)
-
-func (w *RemoteHTTPResponseWriter) Header() http.Header {
- if w.header == nil {
- w.client.Call("LocalHTTPResponseWriter.Header", struct{}{}, &w.header)
- }
- return w.header
-}
-
-func (w *RemoteHTTPResponseWriter) Write(b []byte) (int, error) {
- if err := w.client.Call("LocalHTTPResponseWriter.SyncHeader", w.header, nil); err != nil {
- return 0, err
- }
- if err := w.client.Call("LocalHTTPResponseWriter.Write", b, nil); err != nil {
- return 0, err
- }
- return len(b), nil
-}
-
-func (w *RemoteHTTPResponseWriter) WriteHeader(statusCode int) {
- if err := w.client.Call("LocalHTTPResponseWriter.SyncHeader", w.header, nil); err != nil {
- return
- }
- w.client.Call("LocalHTTPResponseWriter.WriteHeader", statusCode, nil)
-}
-
-func (h *RemoteHTTPResponseWriter) Close() error {
- return h.client.Close()
-}
-
-func ConnectHTTPResponseWriter(conn io.ReadWriteCloser) *RemoteHTTPResponseWriter {
- return &RemoteHTTPResponseWriter{
- client: rpc.NewClient(conn),
- }
-}
diff --git a/plugin/rpcplugin/http_test.go b/plugin/rpcplugin/http_test.go
deleted file mode 100644
index afaaf7756..000000000
--- a/plugin/rpcplugin/http_test.go
+++ /dev/null
@@ -1,61 +0,0 @@
-package rpcplugin
-
-import (
- "io"
- "io/ioutil"
- "net/http"
- "net/http/httptest"
- "testing"
-
- "github.com/stretchr/testify/assert"
-)
-
-func testHTTPResponseWriterRPC(w http.ResponseWriter, f func(w http.ResponseWriter)) {
- r1, w1 := io.Pipe()
- r2, w2 := io.Pipe()
-
- c1 := NewMuxer(NewReadWriteCloser(r1, w2), false)
- defer c1.Close()
-
- c2 := NewMuxer(NewReadWriteCloser(r2, w1), true)
- defer c2.Close()
-
- id, server := c1.Serve()
- go ServeHTTPResponseWriter(w, server)
-
- remote := ConnectHTTPResponseWriter(c2.Connect(id))
- defer remote.Close()
-
- f(remote)
-}
-
-func TestHTTP(t *testing.T) {
- w := httptest.NewRecorder()
-
- testHTTPResponseWriterRPC(w, func(w http.ResponseWriter) {
- headers := w.Header()
- headers.Set("Test-Header-A", "a")
- headers.Set("Test-Header-B", "b")
- w.Header().Set("Test-Header-C", "c")
- w.WriteHeader(http.StatusPaymentRequired)
- n, err := w.Write([]byte("this is "))
- assert.Equal(t, 8, n)
- assert.NoError(t, err)
- n, err = w.Write([]byte("a test"))
- assert.Equal(t, 6, n)
- assert.NoError(t, err)
- })
-
- r := w.Result()
- defer r.Body.Close()
-
- assert.Equal(t, http.StatusPaymentRequired, r.StatusCode)
-
- body, err := ioutil.ReadAll(r.Body)
- assert.NoError(t, err)
- assert.EqualValues(t, "this is a test", body)
-
- assert.Equal(t, "a", r.Header.Get("Test-Header-A"))
- assert.Equal(t, "b", r.Header.Get("Test-Header-B"))
- assert.Equal(t, "c", r.Header.Get("Test-Header-C"))
-}
diff --git a/plugin/rpcplugin/io.go b/plugin/rpcplugin/io.go
deleted file mode 100644
index 44b89956c..000000000
--- a/plugin/rpcplugin/io.go
+++ /dev/null
@@ -1,63 +0,0 @@
-// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-package rpcplugin
-
-import (
- "bufio"
- "encoding/binary"
- "io"
-)
-
-type rwc struct {
- io.ReadCloser
- io.WriteCloser
-}
-
-func (rwc *rwc) Close() (err error) {
- err = rwc.WriteCloser.Close()
- if rerr := rwc.ReadCloser.Close(); err == nil {
- err = rerr
- }
- return
-}
-
-func NewReadWriteCloser(r io.ReadCloser, w io.WriteCloser) io.ReadWriteCloser {
- return &rwc{r, w}
-}
-
-type RemoteIOReader struct {
- conn io.ReadWriteCloser
-}
-
-func (r *RemoteIOReader) Read(b []byte) (int, error) {
- var buf [10]byte
- n := binary.PutVarint(buf[:], int64(len(b)))
- if _, err := r.conn.Write(buf[:n]); err != nil {
- return 0, err
- }
- return r.conn.Read(b)
-}
-
-func (r *RemoteIOReader) Close() error {
- return r.conn.Close()
-}
-
-func ConnectIOReader(conn io.ReadWriteCloser) io.ReadCloser {
- return &RemoteIOReader{conn}
-}
-
-func ServeIOReader(r io.Reader, conn io.ReadWriteCloser) {
- cr := bufio.NewReader(conn)
- defer conn.Close()
- buf := make([]byte, 32*1024)
- for {
- n, err := binary.ReadVarint(cr)
- if err != nil {
- break
- }
- if written, err := io.CopyBuffer(conn, io.LimitReader(r, n), buf); err != nil || written < n {
- break
- }
- }
-}
diff --git a/plugin/rpcplugin/ipc.go b/plugin/rpcplugin/ipc.go
deleted file mode 100644
index e8dd43c04..000000000
--- a/plugin/rpcplugin/ipc.go
+++ /dev/null
@@ -1,31 +0,0 @@
-// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-package rpcplugin
-
-import (
- "io"
- "os"
-)
-
-// Returns a new IPC for the parent process and a set of files to pass on to the child.
-//
-// The returned files must be closed after the child process is started.
-func NewIPC() (io.ReadWriteCloser, []*os.File, error) {
- parentReader, childWriter, err := os.Pipe()
- if err != nil {
- return nil, nil, err
- }
- childReader, parentWriter, err := os.Pipe()
- if err != nil {
- parentReader.Close()
- childWriter.Close()
- return nil, nil, err
- }
- return NewReadWriteCloser(parentReader, parentWriter), []*os.File{childReader, childWriter}, nil
-}
-
-// Returns the IPC instance inherited by the process from its parent.
-func InheritedIPC(fd0, fd1 uintptr) (io.ReadWriteCloser, error) {
- return NewReadWriteCloser(os.NewFile(fd0, ""), os.NewFile(fd1, "")), nil
-}
diff --git a/plugin/rpcplugin/ipc_test.go b/plugin/rpcplugin/ipc_test.go
deleted file mode 100644
index 76699a11e..000000000
--- a/plugin/rpcplugin/ipc_test.go
+++ /dev/null
@@ -1,63 +0,0 @@
-package rpcplugin
-
-import (
- "context"
- "io/ioutil"
- "os"
- "path/filepath"
- "testing"
-
- "github.com/stretchr/testify/assert"
- "github.com/stretchr/testify/require"
-
- "github.com/mattermost/mattermost-server/plugin/rpcplugin/rpcplugintest"
-)
-
-func TestIPC(t *testing.T) {
- dir, err := ioutil.TempDir("", "")
- require.NoError(t, err)
- defer os.RemoveAll(dir)
-
- pingpong := filepath.Join(dir, "pingpong.exe")
- rpcplugintest.CompileGo(t, `
- package main
-
- import (
- "log"
-
- "github.com/mattermost/mattermost-server/plugin/rpcplugin"
- )
-
- func main() {
- ipc, err := rpcplugin.InheritedProcessIPC()
- if err != nil {
- log.Fatal("unable to get inherited ipc")
- }
- defer ipc.Close()
- _, err = ipc.Write([]byte("ping"))
- if err != nil {
- log.Fatal("unable to write to ipc")
- }
- b := make([]byte, 10)
- n, err := ipc.Read(b)
- if err != nil {
- log.Fatal("unable to read from ipc")
- }
- if n != 4 || string(b[:4]) != "pong" {
- log.Fatal("unexpected response")
- }
- }
- `, pingpong)
-
- p, ipc, err := NewProcess(context.Background(), pingpong)
- require.NoError(t, err)
- defer ipc.Close()
- b := make([]byte, 10)
- n, err := ipc.Read(b)
- require.NoError(t, err)
- assert.Equal(t, 4, n)
- assert.Equal(t, "ping", string(b[:4]))
- _, err = ipc.Write([]byte("pong"))
- require.NoError(t, err)
- require.NoError(t, p.Wait())
-}
diff --git a/plugin/rpcplugin/main.go b/plugin/rpcplugin/main.go
deleted file mode 100644
index efb880605..000000000
--- a/plugin/rpcplugin/main.go
+++ /dev/null
@@ -1,47 +0,0 @@
-// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-package rpcplugin
-
-import (
- "bufio"
- "encoding/binary"
- "fmt"
- "log"
- "os"
-)
-
-// Makes a set of hooks available via RPC. This function never returns.
-func Main(hooks interface{}) {
- ipc, err := InheritedProcessIPC()
- if err != nil {
- log.Fatal(err.Error())
- }
- muxer := NewMuxer(ipc, true)
- id, conn := muxer.Serve()
- buf := make([]byte, 11)
- buf[0] = 0
- n := binary.PutVarint(buf[1:], id)
- if _, err := muxer.Write(buf[:1+n]); err != nil {
- log.Fatal(err.Error())
- }
- ServeHooks(hooks, conn, muxer)
- os.Exit(0)
-}
-
-// Returns the hooks being served by a call to Main.
-func ConnectMain(muxer *Muxer, pluginId string) (*RemoteHooks, error) {
- buf := make([]byte, 1)
- if _, err := muxer.Read(buf); err != nil {
- return nil, err
- } else if buf[0] != 0 {
- return nil, fmt.Errorf("unexpected control byte")
- }
- reader := bufio.NewReader(muxer)
- id, err := binary.ReadVarint(reader)
- if err != nil {
- return nil, err
- }
-
- return ConnectHooks(muxer.Connect(id), muxer, pluginId)
-}
diff --git a/plugin/rpcplugin/main_test.go b/plugin/rpcplugin/main_test.go
deleted file mode 100644
index 06423106c..000000000
--- a/plugin/rpcplugin/main_test.go
+++ /dev/null
@@ -1,63 +0,0 @@
-package rpcplugin
-
-import (
- "context"
- "io/ioutil"
- "os"
- "path/filepath"
- "testing"
-
- "github.com/stretchr/testify/assert"
- "github.com/stretchr/testify/require"
-
- "github.com/mattermost/mattermost-server/mlog"
- "github.com/mattermost/mattermost-server/plugin/plugintest"
- "github.com/mattermost/mattermost-server/plugin/rpcplugin/rpcplugintest"
-)
-
-func TestMain(t *testing.T) {
- // Setup a global logger to catch tests logging outside of app context
- // The global logger will be stomped by apps initalizing but that's fine for testing. Ideally this won't happen.
- mlog.InitGlobalLogger(mlog.NewLogger(&mlog.LoggerConfiguration{
- EnableConsole: true,
- ConsoleJson: true,
- ConsoleLevel: "error",
- EnableFile: false,
- }))
-
- dir, err := ioutil.TempDir("", "")
- require.NoError(t, err)
- defer os.RemoveAll(dir)
-
- plugin := filepath.Join(dir, "plugin.exe")
- rpcplugintest.CompileGo(t, `
- package main
-
- import (
- "github.com/mattermost/mattermost-server/plugin/rpcplugin"
- )
-
- type MyPlugin struct {}
-
- func main() {
- rpcplugin.Main(&MyPlugin{})
- }
- `, plugin)
-
- ctx, cancel := context.WithCancel(context.Background())
- p, ipc, err := NewProcess(ctx, plugin)
- require.NoError(t, err)
- defer p.Wait()
-
- muxer := NewMuxer(ipc, false)
- defer muxer.Close()
-
- defer cancel()
-
- var api plugintest.API
-
- hooks, err := ConnectMain(muxer, "plugin_id")
- require.NoError(t, err)
- assert.NoError(t, hooks.OnActivate(&api))
- assert.NoError(t, hooks.OnDeactivate())
-}
diff --git a/plugin/rpcplugin/muxer.go b/plugin/rpcplugin/muxer.go
deleted file mode 100644
index a7260c399..000000000
--- a/plugin/rpcplugin/muxer.go
+++ /dev/null
@@ -1,264 +0,0 @@
-// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-package rpcplugin
-
-import (
- "bufio"
- "bytes"
- "encoding/binary"
- "fmt"
- "io"
- "sync"
- "sync/atomic"
-)
-
-// Muxer allows multiple bidirectional streams to be transmitted over a single connection.
-//
-// Muxer is safe for use by multiple goroutines.
-//
-// Streams opened on the muxer must be periodically drained in order to reclaim read buffer memory.
-// In other words, readers must consume incoming data as it comes in.
-type Muxer struct {
- // writeMutex guards conn writes
- writeMutex sync.Mutex
- conn io.ReadWriteCloser
-
- // didCloseConn is a boolean (0 or 1) used from multiple goroutines via atomic operations
- didCloseConn int32
-
- // streamsMutex guards streams and nextId
- streamsMutex sync.Mutex
- nextId int64
- streams map[int64]*muxerStream
-
- stream0Reader *io.PipeReader
- stream0Writer *io.PipeWriter
- result chan error
-}
-
-// Creates a new Muxer.
-//
-// conn must be safe for simultaneous reads by one goroutine and writes by another.
-//
-// For two muxers communicating with each other via a connection, parity must be true for exactly
-// one of them.
-func NewMuxer(conn io.ReadWriteCloser, parity bool) *Muxer {
- s0r, s0w := io.Pipe()
- muxer := &Muxer{
- conn: conn,
- streams: make(map[int64]*muxerStream),
- result: make(chan error, 1),
- nextId: 1,
- stream0Reader: s0r,
- stream0Writer: s0w,
- }
- if parity {
- muxer.nextId = 2
- }
- go muxer.run()
- return muxer
-}
-
-// Opens a new stream with a unique id.
-//
-// Writes made to the stream before the other end calls Connect will be discarded.
-func (m *Muxer) Serve() (int64, io.ReadWriteCloser) {
- m.streamsMutex.Lock()
- id := m.nextId
- m.nextId += 2
- m.streamsMutex.Unlock()
- return id, m.Connect(id)
-}
-
-// Opens a remotely opened stream.
-func (m *Muxer) Connect(id int64) io.ReadWriteCloser {
- m.streamsMutex.Lock()
- defer m.streamsMutex.Unlock()
- mutex := &sync.Mutex{}
- stream := &muxerStream{
- id: id,
- muxer: m,
- mutex: mutex,
- readWake: sync.NewCond(mutex),
- }
- m.streams[id] = stream
- return stream
-}
-
-// Calling Read on the muxer directly performs a read on a dedicated, always-open channel.
-func (m *Muxer) Read(p []byte) (int, error) {
- return m.stream0Reader.Read(p)
-}
-
-// Calling Write on the muxer directly performs a write on a dedicated, always-open channel.
-func (m *Muxer) Write(p []byte) (int, error) {
- return m.write(p, 0)
-}
-
-// Closes the muxer.
-func (m *Muxer) Close() error {
- if atomic.CompareAndSwapInt32(&m.didCloseConn, 0, 1) {
- m.conn.Close()
- }
- m.stream0Reader.Close()
- m.stream0Writer.Close()
- <-m.result
- return nil
-}
-
-func (m *Muxer) IsClosed() bool {
- return atomic.LoadInt32(&m.didCloseConn) > 0
-}
-
-func (m *Muxer) write(p []byte, sid int64) (int, error) {
- m.writeMutex.Lock()
- defer m.writeMutex.Unlock()
- if m.IsClosed() {
- return 0, fmt.Errorf("muxer closed")
- }
- var buf [10]byte
- n := binary.PutVarint(buf[:], sid)
- if _, err := m.conn.Write(buf[:n]); err != nil {
- m.shutdown(err)
- return 0, err
- }
- n = binary.PutVarint(buf[:], int64(len(p)))
- if _, err := m.conn.Write(buf[:n]); err != nil {
- m.shutdown(err)
- return 0, err
- }
- if len(p) > 0 {
- if _, err := m.conn.Write(p); err != nil {
- m.shutdown(err)
- return 0, err
- }
- }
- return len(p), nil
-}
-
-func (m *Muxer) rm(sid int64) {
- m.streamsMutex.Lock()
- defer m.streamsMutex.Unlock()
- delete(m.streams, sid)
-}
-
-func (m *Muxer) run() {
- m.shutdown(m.loop())
-}
-
-func (m *Muxer) loop() error {
- reader := bufio.NewReader(m.conn)
-
- for {
- sid, err := binary.ReadVarint(reader)
- if err != nil {
- return err
- }
- len, err := binary.ReadVarint(reader)
- if err != nil {
- return err
- }
-
- if sid == 0 {
- if _, err := io.CopyN(m.stream0Writer, reader, len); err != nil {
- return err
- }
- continue
- }
-
- m.streamsMutex.Lock()
- stream, ok := m.streams[sid]
- m.streamsMutex.Unlock()
- if !ok {
- if _, err := reader.Discard(int(len)); err != nil {
- return err
- }
- continue
- }
-
- stream.mutex.Lock()
- if stream.isClosed {
- stream.mutex.Unlock()
- if _, err := reader.Discard(int(len)); err != nil {
- return err
- }
- continue
- }
- if len == 0 {
- stream.remoteClosed = true
- } else {
- _, err = io.CopyN(&stream.readBuf, reader, len)
- }
- stream.mutex.Unlock()
- if err != nil {
- return err
- }
- stream.readWake.Signal()
- }
-}
-
-func (m *Muxer) shutdown(err error) {
- if atomic.CompareAndSwapInt32(&m.didCloseConn, 0, 1) {
- m.conn.Close()
- }
- go func() {
- m.streamsMutex.Lock()
- for _, stream := range m.streams {
- stream.mutex.Lock()
- stream.readWake.Signal()
- stream.mutex.Unlock()
- }
- m.streams = make(map[int64]*muxerStream)
- m.streamsMutex.Unlock()
- }()
- m.result <- err
-}
-
-type muxerStream struct {
- id int64
- muxer *Muxer
- readBuf bytes.Buffer
- mutex *sync.Mutex
- readWake *sync.Cond
- isClosed bool
- remoteClosed bool
-}
-
-func (s *muxerStream) Read(p []byte) (int, error) {
- s.mutex.Lock()
- defer s.mutex.Unlock()
- for {
- if s.muxer.IsClosed() {
- return 0, fmt.Errorf("muxer closed")
- } else if s.isClosed {
- return 0, io.EOF
- } else if s.readBuf.Len() > 0 {
- return s.readBuf.Read(p)
- } else if s.remoteClosed {
- return 0, io.EOF
- }
- s.readWake.Wait()
- }
-}
-
-func (s *muxerStream) Write(p []byte) (int, error) {
- s.mutex.Lock()
- defer s.mutex.Unlock()
- if s.isClosed {
- return 0, fmt.Errorf("stream closed")
- }
- return s.muxer.write(p, s.id)
-}
-
-func (s *muxerStream) Close() error {
- s.mutex.Lock()
- defer s.mutex.Unlock()
- if !s.isClosed {
- s.muxer.write(nil, s.id)
- s.isClosed = true
- s.muxer.rm(s.id)
- }
- s.readWake.Signal()
- return nil
-}
diff --git a/plugin/rpcplugin/muxer_test.go b/plugin/rpcplugin/muxer_test.go
deleted file mode 100644
index 795a4fb1d..000000000
--- a/plugin/rpcplugin/muxer_test.go
+++ /dev/null
@@ -1,197 +0,0 @@
-package rpcplugin
-
-import (
- "io"
- "testing"
-
- "github.com/stretchr/testify/assert"
- "github.com/stretchr/testify/require"
-)
-
-func TestMuxer(t *testing.T) {
- r1, w1 := io.Pipe()
- r2, w2 := io.Pipe()
-
- alice := NewMuxer(NewReadWriteCloser(r1, w2), false)
- defer func() { assert.NoError(t, alice.Close()) }()
-
- bob := NewMuxer(NewReadWriteCloser(r2, w1), true)
- defer func() { assert.NoError(t, bob.Close()) }()
-
- id1, alice1 := alice.Serve()
- defer func() { assert.NoError(t, alice1.Close()) }()
-
- id2, bob2 := bob.Serve()
- defer func() { assert.NoError(t, bob2.Close()) }()
-
- done1 := make(chan bool)
- done2 := make(chan bool)
-
- go func() {
- bob1 := bob.Connect(id1)
- defer func() { assert.NoError(t, bob1.Close()) }()
-
- n, err := bob1.Write([]byte("ping1.0"))
- require.NoError(t, err)
- assert.Equal(t, n, 7)
-
- n, err = bob1.Write([]byte("ping1.1"))
- require.NoError(t, err)
- assert.Equal(t, n, 7)
- }()
-
- go func() {
- alice2 := alice.Connect(id2)
- defer func() { assert.NoError(t, alice2.Close()) }()
-
- n, err := alice2.Write([]byte("ping2.0"))
- require.NoError(t, err)
- assert.Equal(t, n, 7)
-
- buf := make([]byte, 20)
- n, err = alice2.Read(buf)
- require.NoError(t, err)
- assert.Equal(t, n, 7)
- assert.Equal(t, []byte("pong2.0"), buf[:n])
-
- done2 <- true
- }()
-
- go func() {
- buf := make([]byte, 7)
- n, err := io.ReadFull(alice1, buf)
- require.NoError(t, err)
- assert.Equal(t, n, 7)
- assert.Equal(t, []byte("ping1.0"), buf[:n])
-
- n, err = alice1.Read(buf)
- require.NoError(t, err)
- assert.Equal(t, n, 7)
- assert.Equal(t, []byte("ping1.1"), buf[:n])
-
- done1 <- true
- }()
-
- go func() {
- buf := make([]byte, 20)
- n, err := bob2.Read(buf)
- require.NoError(t, err)
- assert.Equal(t, n, 7)
- assert.Equal(t, []byte("ping2.0"), buf[:n])
-
- n, err = bob2.Write([]byte("pong2.0"))
- require.NoError(t, err)
- assert.Equal(t, n, 7)
- }()
-
- <-done1
- <-done2
-}
-
-// Closing a muxer during a read should unblock, but return an error.
-func TestMuxer_CloseDuringRead(t *testing.T) {
- r1, w1 := io.Pipe()
- r2, w2 := io.Pipe()
-
- alice := NewMuxer(NewReadWriteCloser(r1, w2), false)
-
- bob := NewMuxer(NewReadWriteCloser(r2, w1), true)
- defer func() { assert.NoError(t, bob.Close()) }()
-
- _, s := alice.Serve()
-
- go alice.Close()
- buf := make([]byte, 20)
- n, err := s.Read(buf)
- assert.Equal(t, 0, n)
- assert.NotNil(t, err)
- assert.NotEqual(t, io.EOF, err)
-}
-
-// Closing a stream during a read should unblock and return io.EOF since this is the way to
-// gracefully close a connection.
-func TestMuxer_StreamCloseDuringRead(t *testing.T) {
- r1, w1 := io.Pipe()
- r2, w2 := io.Pipe()
-
- alice := NewMuxer(NewReadWriteCloser(r1, w2), false)
- defer func() { assert.NoError(t, alice.Close()) }()
-
- bob := NewMuxer(NewReadWriteCloser(r2, w1), true)
- defer func() { assert.NoError(t, bob.Close()) }()
-
- _, s := alice.Serve()
-
- go s.Close()
- buf := make([]byte, 20)
- n, err := s.Read(buf)
- assert.Equal(t, 0, n)
- assert.Equal(t, io.EOF, err)
-}
-
-// Closing a stream during a read should unblock and return io.EOF since this is the way for the
-// remote to gracefully close a connection.
-func TestMuxer_RemoteStreamCloseDuringRead(t *testing.T) {
- r1, w1 := io.Pipe()
- r2, w2 := io.Pipe()
-
- alice := NewMuxer(NewReadWriteCloser(r1, w2), false)
- defer func() { assert.NoError(t, alice.Close()) }()
-
- bob := NewMuxer(NewReadWriteCloser(r2, w1), true)
- defer func() { assert.NoError(t, bob.Close()) }()
-
- id, as := alice.Serve()
- bs := bob.Connect(id)
-
- go func() {
- as.Write([]byte("foo"))
- as.Close()
- }()
- buf := make([]byte, 20)
- n, err := bs.Read(buf)
- assert.Equal(t, 3, n)
- assert.Equal(t, "foo", string(buf[:n]))
- n, err = bs.Read(buf)
- assert.Equal(t, 0, n)
- assert.Equal(t, io.EOF, err)
-}
-
-// Closing a muxer during a write should unblock, but return an error.
-func TestMuxer_CloseDuringWrite(t *testing.T) {
- r1, w1 := io.Pipe()
- r2, w2 := io.Pipe()
-
- alice := NewMuxer(NewReadWriteCloser(r1, w2), false)
-
- // Don't connect bob to let writes will block forever.
- defer r2.Close()
- defer w1.Close()
-
- _, s := alice.Serve()
-
- go alice.Close()
- buf := make([]byte, 20)
- n, err := s.Write(buf)
- assert.Equal(t, 0, n)
- assert.NotNil(t, err)
- assert.NotEqual(t, io.EOF, err)
-}
-
-func TestMuxer_ReadWrite(t *testing.T) {
- r1, w1 := io.Pipe()
- r2, w2 := io.Pipe()
-
- alice := NewMuxer(NewReadWriteCloser(r1, w2), false)
- defer func() { assert.NoError(t, alice.Close()) }()
-
- bob := NewMuxer(NewReadWriteCloser(r2, w1), true)
- defer func() { assert.NoError(t, bob.Close()) }()
-
- go alice.Write([]byte("hello"))
- buf := make([]byte, 20)
- n, err := bob.Read(buf)
- assert.Equal(t, 5, n)
- assert.Nil(t, err)
- assert.Equal(t, []byte("hello"), buf[:n])
-}
diff --git a/plugin/rpcplugin/process.go b/plugin/rpcplugin/process.go
deleted file mode 100644
index a795be133..000000000
--- a/plugin/rpcplugin/process.go
+++ /dev/null
@@ -1,26 +0,0 @@
-// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-package rpcplugin
-
-import (
- "context"
- "io"
-)
-
-type Process interface {
- // Waits for the process to exit and returns an error if a problem occurred or the process exited
- // with a non-zero status.
- Wait() error
-}
-
-// NewProcess launches an RPC executable in a new process and returns an IPC that can be used to
-// communicate with it.
-func NewProcess(ctx context.Context, path string) (Process, io.ReadWriteCloser, error) {
- return newProcess(ctx, path)
-}
-
-// When called on a process launched with NewProcess, returns the inherited IPC.
-func InheritedProcessIPC() (io.ReadWriteCloser, error) {
- return inheritedProcessIPC()
-}
diff --git a/plugin/rpcplugin/process_test.go b/plugin/rpcplugin/process_test.go
deleted file mode 100644
index 8d1794293..000000000
--- a/plugin/rpcplugin/process_test.go
+++ /dev/null
@@ -1,60 +0,0 @@
-package rpcplugin
-
-import (
- "context"
- "io/ioutil"
- "os"
- "path/filepath"
- "testing"
-
- "github.com/stretchr/testify/assert"
- "github.com/stretchr/testify/require"
-
- "github.com/mattermost/mattermost-server/plugin/rpcplugin/rpcplugintest"
-)
-
-func TestProcess(t *testing.T) {
- dir, err := ioutil.TempDir("", "")
- require.NoError(t, err)
- defer os.RemoveAll(dir)
-
- ping := filepath.Join(dir, "ping.exe")
- rpcplugintest.CompileGo(t, `
- package main
-
- import (
- "log"
-
- "github.com/mattermost/mattermost-server/plugin/rpcplugin"
- )
-
- func main() {
- ipc, err := rpcplugin.InheritedProcessIPC()
- if err != nil {
- log.Fatal("unable to get inherited ipc")
- }
- defer ipc.Close()
- _, err = ipc.Write([]byte("ping"))
- if err != nil {
- log.Fatal("unable to write to ipc")
- }
- }
- `, ping)
-
- p, ipc, err := NewProcess(context.Background(), ping)
- require.NoError(t, err)
- defer ipc.Close()
- b := make([]byte, 10)
- n, err := ipc.Read(b)
- require.NoError(t, err)
- assert.Equal(t, 4, n)
- assert.Equal(t, "ping", string(b[:4]))
- require.NoError(t, p.Wait())
-}
-
-func TestInvalidProcess(t *testing.T) {
- p, ipc, err := NewProcess(context.Background(), "thisfileshouldnotexist")
- require.Nil(t, p)
- require.Nil(t, ipc)
- require.Error(t, err)
-}
diff --git a/plugin/rpcplugin/process_unix.go b/plugin/rpcplugin/process_unix.go
deleted file mode 100644
index 142043cc6..000000000
--- a/plugin/rpcplugin/process_unix.go
+++ /dev/null
@@ -1,48 +0,0 @@
-// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-// +build !windows
-
-package rpcplugin
-
-import (
- "context"
- "io"
- "os"
- "os/exec"
-)
-
-type process struct {
- command *exec.Cmd
-}
-
-func newProcess(ctx context.Context, path string) (Process, io.ReadWriteCloser, error) {
- ipc, childFiles, err := NewIPC()
- if err != nil {
- return nil, nil, err
- }
- defer childFiles[0].Close()
- defer childFiles[1].Close()
-
- cmd := exec.CommandContext(ctx, path)
- cmd.Stdout = os.Stdout
- cmd.Stderr = os.Stderr
- cmd.ExtraFiles = childFiles
- err = cmd.Start()
- if err != nil {
- ipc.Close()
- return nil, nil, err
- }
-
- return &process{
- command: cmd,
- }, ipc, nil
-}
-
-func (p *process) Wait() error {
- return p.command.Wait()
-}
-
-func inheritedProcessIPC() (io.ReadWriteCloser, error) {
- return InheritedIPC(3, 4)
-}
diff --git a/plugin/rpcplugin/process_windows.go b/plugin/rpcplugin/process_windows.go
deleted file mode 100644
index 069f147c1..000000000
--- a/plugin/rpcplugin/process_windows.go
+++ /dev/null
@@ -1,648 +0,0 @@
-package rpcplugin
-
-import (
- "context"
- "errors"
- "fmt"
- "io"
- "os"
- "os/exec"
- "path/filepath"
- "runtime"
- "strconv"
- "strings"
- "syscall"
- "unicode/utf16"
- "unsafe"
-
- pkgerrors "github.com/pkg/errors"
-)
-
-type process struct {
- command *cmd
-}
-
-func newProcess(ctx context.Context, path string) (Process, io.ReadWriteCloser, error) {
- ipc, childFiles, err := NewIPC()
- if err != nil {
- return nil, nil, err
- }
- defer childFiles[0].Close()
- defer childFiles[1].Close()
-
- cmd := commandContext(ctx, path)
- cmd.Stdout = os.Stdout
- cmd.Stderr = os.Stderr
- cmd.ExtraFiles = childFiles
- cmd.Env = append(os.Environ(),
- fmt.Sprintf("MM_IPC_FD0=%v", childFiles[0].Fd()),
- fmt.Sprintf("MM_IPC_FD1=%v", childFiles[1].Fd()),
- )
- err = cmd.Start()
- if err != nil {
- ipc.Close()
- return nil, nil, err
- }
-
- return &process{
- command: cmd,
- }, ipc, nil
-}
-
-func (p *process) Wait() error {
- return p.command.Wait()
-}
-
-func inheritedProcessIPC() (io.ReadWriteCloser, error) {
- fd0, err := strconv.ParseUint(os.Getenv("MM_IPC_FD0"), 0, 64)
- if err != nil {
- return nil, pkgerrors.Wrapf(err, "unable to get ipc file descriptor 0")
- }
- fd1, err := strconv.ParseUint(os.Getenv("MM_IPC_FD1"), 0, 64)
- if err != nil {
- return nil, pkgerrors.Wrapf(err, "unable to get ipc file descriptor 1")
- }
- return InheritedIPC(uintptr(fd0), uintptr(fd1))
-}
-
-// XXX: EVERYTHING BELOW THIS IS COPIED / PASTED STANDARD LIBRARY CODE!
-// IT CAN BE DELETED IF / WHEN THIS ISSUE IS RESOLVED: https://github.com/golang/go/issues/21085
-
-// Just about all of os/exec/exec.go is copied / pasted below, altered to use our modified startProcess functions even
-// further below.
-
-type cmd struct {
- // Path is the path of the command to run.
- //
- // This is the only field that must be set to a non-zero
- // value. If Path is relative, it is evaluated relative
- // to Dir.
- Path string
-
- // Args holds command line arguments, including the command as Args[0].
- // If the Args field is empty or nil, Run uses {Path}.
- //
- // In typical use, both Path and Args are set by calling Command.
- Args []string
-
- // Env specifies the environment of the process.
- // If Env is nil, Run uses the current process's environment.
- Env []string
-
- // Dir specifies the working directory of the command.
- // If Dir is the empty string, Run runs the command in the
- // calling process's current directory.
- Dir string
-
- // Stdin specifies the process's standard input.
- // If Stdin is nil, the process reads from the null device (os.DevNull).
- // If Stdin is an *os.File, the process's standard input is connected
- // directly to that file.
- // Otherwise, during the execution of the command a separate
- // goroutine reads from Stdin and delivers that data to the command
- // over a pipe. In this case, Wait does not complete until the goroutine
- // stops copying, either because it has reached the end of Stdin
- // (EOF or a read error) or because writing to the pipe returned an error.
- Stdin io.Reader
-
- // Stdout and Stderr specify the process's standard output and error.
- //
- // If either is nil, Run connects the corresponding file descriptor
- // to the null device (os.DevNull).
- //
- // If Stdout and Stderr are the same writer, at most one
- // goroutine at a time will call Write.
- Stdout io.Writer
- Stderr io.Writer
-
- // ExtraFiles specifies additional open files to be inherited by the
- // new process. It does not include standard input, standard output, or
- // standard error. If non-nil, entry i becomes file descriptor 3+i.
- //
- // BUG(rsc): On OS X 10.6, child processes may sometimes inherit unwanted fds.
- // https://golang.org/issue/2603
- ExtraFiles []*os.File
-
- // SysProcAttr holds optional, operating system-specific attributes.
- // Run passes it to os.StartProcess as the os.ProcAttr's Sys field.
- SysProcAttr *syscall.SysProcAttr
-
- // Process is the underlying process, once started.
- Process *os.Process
-
- // ProcessState contains information about an exited process,
- // available after a call to Wait or Run.
- ProcessState *os.ProcessState
-
- ctx context.Context // nil means none
- lookPathErr error // LookPath error, if any.
- finished bool // when Wait was called
- childFiles []*os.File
- closeAfterStart []io.Closer
- closeAfterWait []io.Closer
- goroutine []func() error
- errch chan error // one send per goroutine
- waitDone chan struct{}
-}
-
-func command(name string, arg ...string) *cmd {
- cmd := &cmd{
- Path: name,
- Args: append([]string{name}, arg...),
- }
- if filepath.Base(name) == name {
- if lp, err := exec.LookPath(name); err != nil {
- cmd.lookPathErr = err
- } else {
- cmd.Path = lp
- }
- }
- return cmd
-}
-
-func commandContext(ctx context.Context, name string, arg ...string) *cmd {
- if ctx == nil {
- panic("nil Context")
- }
- cmd := command(name, arg...)
- cmd.ctx = ctx
- return cmd
-}
-
-func interfaceEqual(a, b interface{}) bool {
- defer func() {
- recover()
- }()
- return a == b
-}
-
-func (c *cmd) envv() []string {
- if c.Env != nil {
- return c.Env
- }
- return os.Environ()
-}
-
-func (c *cmd) argv() []string {
- if len(c.Args) > 0 {
- return c.Args
- }
- return []string{c.Path}
-}
-
-var skipStdinCopyError func(error) bool
-
-func (c *cmd) stdin() (f *os.File, err error) {
- if c.Stdin == nil {
- f, err = os.Open(os.DevNull)
- if err != nil {
- return
- }
- c.closeAfterStart = append(c.closeAfterStart, f)
- return
- }
-
- if f, ok := c.Stdin.(*os.File); ok {
- return f, nil
- }
-
- pr, pw, err := os.Pipe()
- if err != nil {
- return
- }
-
- c.closeAfterStart = append(c.closeAfterStart, pr)
- c.closeAfterWait = append(c.closeAfterWait, pw)
- c.goroutine = append(c.goroutine, func() error {
- _, err := io.Copy(pw, c.Stdin)
- if skip := skipStdinCopyError; skip != nil && skip(err) {
- err = nil
- }
- if err1 := pw.Close(); err == nil {
- err = err1
- }
- return err
- })
- return pr, nil
-}
-
-func (c *cmd) stdout() (f *os.File, err error) {
- return c.writerDescriptor(c.Stdout)
-}
-
-func (c *cmd) stderr() (f *os.File, err error) {
- if c.Stderr != nil && interfaceEqual(c.Stderr, c.Stdout) {
- return c.childFiles[1], nil
- }
- return c.writerDescriptor(c.Stderr)
-}
-
-func (c *cmd) writerDescriptor(w io.Writer) (f *os.File, err error) {
- if w == nil {
- f, err = os.OpenFile(os.DevNull, os.O_WRONLY, 0)
- if err != nil {
- return
- }
- c.closeAfterStart = append(c.closeAfterStart, f)
- return
- }
-
- if f, ok := w.(*os.File); ok {
- return f, nil
- }
-
- pr, pw, err := os.Pipe()
- if err != nil {
- return
- }
-
- c.closeAfterStart = append(c.closeAfterStart, pw)
- c.closeAfterWait = append(c.closeAfterWait, pr)
- c.goroutine = append(c.goroutine, func() error {
- _, err := io.Copy(w, pr)
- pr.Close() // in case io.Copy stopped due to write error
- return err
- })
- return pw, nil
-}
-
-func (c *cmd) closeDescriptors(closers []io.Closer) {
- for _, fd := range closers {
- fd.Close()
- }
-}
-
-func lookExtensions(path, dir string) (string, error) {
- if filepath.Base(path) == path {
- path = filepath.Join(".", path)
- }
- if dir == "" {
- return exec.LookPath(path)
- }
- if filepath.VolumeName(path) != "" {
- return exec.LookPath(path)
- }
- if len(path) > 1 && os.IsPathSeparator(path[0]) {
- return exec.LookPath(path)
- }
- dirandpath := filepath.Join(dir, path)
- // We assume that LookPath will only add file extension.
- lp, err := exec.LookPath(dirandpath)
- if err != nil {
- return "", err
- }
- ext := strings.TrimPrefix(lp, dirandpath)
- return path + ext, nil
-}
-
-// Copied from os/exec/exec.go, altered to use osStartProcess (defined below).
-func (c *cmd) Start() error {
- if c.lookPathErr != nil {
- c.closeDescriptors(c.closeAfterStart)
- c.closeDescriptors(c.closeAfterWait)
- return c.lookPathErr
- }
- if runtime.GOOS == "windows" {
- lp, err := lookExtensions(c.Path, c.Dir)
- if err != nil {
- c.closeDescriptors(c.closeAfterStart)
- c.closeDescriptors(c.closeAfterWait)
- return err
- }
- c.Path = lp
- }
- if c.Process != nil {
- return errors.New("exec: already started")
- }
- if c.ctx != nil {
- select {
- case <-c.ctx.Done():
- c.closeDescriptors(c.closeAfterStart)
- c.closeDescriptors(c.closeAfterWait)
- return c.ctx.Err()
- default:
- }
- }
-
- type F func(*cmd) (*os.File, error)
- for _, setupFd := range []F{(*cmd).stdin, (*cmd).stdout, (*cmd).stderr} {
- fd, err := setupFd(c)
- if err != nil {
- c.closeDescriptors(c.closeAfterStart)
- c.closeDescriptors(c.closeAfterWait)
- return err
- }
- c.childFiles = append(c.childFiles, fd)
- }
- c.childFiles = append(c.childFiles, c.ExtraFiles...)
-
- var err error
- c.Process, err = osStartProcess(c.Path, c.argv(), &os.ProcAttr{
- Dir: c.Dir,
- Files: c.childFiles,
- Env: c.envv(),
- Sys: c.SysProcAttr,
- })
- if err != nil {
- c.closeDescriptors(c.closeAfterStart)
- c.closeDescriptors(c.closeAfterWait)
- return err
- }
-
- c.closeDescriptors(c.closeAfterStart)
-
- c.errch = make(chan error, len(c.goroutine))
- for _, fn := range c.goroutine {
- go func(fn func() error) {
- c.errch <- fn()
- }(fn)
- }
-
- if c.ctx != nil {
- c.waitDone = make(chan struct{})
- go func() {
- select {
- case <-c.ctx.Done():
- c.Process.Kill()
- case <-c.waitDone:
- }
- }()
- }
-
- return nil
-}
-
-func (c *cmd) Wait() error {
- if c.Process == nil {
- return errors.New("exec: not started")
- }
- if c.finished {
- return errors.New("exec: Wait was already called")
- }
- c.finished = true
-
- state, err := c.Process.Wait()
- if c.waitDone != nil {
- close(c.waitDone)
- }
- c.ProcessState = state
-
- var copyError error
- for range c.goroutine {
- if err := <-c.errch; err != nil && copyError == nil {
- copyError = err
- }
- }
-
- c.closeDescriptors(c.closeAfterWait)
-
- if err != nil {
- return err
- } else if !state.Success() {
- return &exec.ExitError{ProcessState: state}
- }
-
- return copyError
-}
-
-// Copied from os/exec_posix.go, altered to use syscallStartProcess (defined below).
-func osStartProcess(name string, argv []string, attr *os.ProcAttr) (p *os.Process, err error) {
- // If there is no SysProcAttr (ie. no Chroot or changed
- // UID/GID), double-check existence of the directory we want
- // to chdir into. We can make the error clearer this way.
- if attr != nil && attr.Sys == nil && attr.Dir != "" {
- if _, err := os.Stat(attr.Dir); err != nil {
- pe := err.(*os.PathError)
- pe.Op = "chdir"
- return nil, pe
- }
- }
-
- sysattr := &syscall.ProcAttr{
- Dir: attr.Dir,
- Env: attr.Env,
- Sys: attr.Sys,
- }
- if sysattr.Env == nil {
- sysattr.Env = os.Environ()
- }
- for _, f := range attr.Files {
- sysattr.Files = append(sysattr.Files, f.Fd())
- }
-
- pid, _, e := syscallStartProcess(name, argv, sysattr)
- if e != nil {
- return nil, &os.PathError{Op: "fork/exec", Path: name, Err: e}
- }
- return os.FindProcess(pid)
-}
-
-// Everything from this point on is copied from syscall/exec_windows.go
-
-func makeCmdLine(args []string) string {
- var s string
- for _, v := range args {
- if s != "" {
- s += " "
- }
- s += syscall.EscapeArg(v)
- }
- return s
-}
-
-func createEnvBlock(envv []string) *uint16 {
- if len(envv) == 0 {
- return &utf16.Encode([]rune("\x00\x00"))[0]
- }
- length := 0
- for _, s := range envv {
- length += len(s) + 1
- }
- length += 1
-
- b := make([]byte, length)
- i := 0
- for _, s := range envv {
- l := len(s)
- copy(b[i:i+l], []byte(s))
- copy(b[i+l:i+l+1], []byte{0})
- i = i + l + 1
- }
- copy(b[i:i+1], []byte{0})
-
- return &utf16.Encode([]rune(string(b)))[0]
-}
-
-func isSlash(c uint8) bool {
- return c == '\\' || c == '/'
-}
-
-func normalizeDir(dir string) (name string, err error) {
- ndir, err := syscall.FullPath(dir)
- if err != nil {
- return "", err
- }
- if len(ndir) > 2 && isSlash(ndir[0]) && isSlash(ndir[1]) {
- // dir cannot have \\server\share\path form
- return "", syscall.EINVAL
- }
- return ndir, nil
-}
-
-func volToUpper(ch int) int {
- if 'a' <= ch && ch <= 'z' {
- ch += 'A' - 'a'
- }
- return ch
-}
-
-func joinExeDirAndFName(dir, p string) (name string, err error) {
- if len(p) == 0 {
- return "", syscall.EINVAL
- }
- if len(p) > 2 && isSlash(p[0]) && isSlash(p[1]) {
- // \\server\share\path form
- return p, nil
- }
- if len(p) > 1 && p[1] == ':' {
- // has drive letter
- if len(p) == 2 {
- return "", syscall.EINVAL
- }
- if isSlash(p[2]) {
- return p, nil
- } else {
- d, err := normalizeDir(dir)
- if err != nil {
- return "", err
- }
- if volToUpper(int(p[0])) == volToUpper(int(d[0])) {
- return syscall.FullPath(d + "\\" + p[2:])
- } else {
- return syscall.FullPath(p)
- }
- }
- } else {
- // no drive letter
- d, err := normalizeDir(dir)
- if err != nil {
- return "", err
- }
- if isSlash(p[0]) {
- return syscall.FullPath(d[:2] + p)
- } else {
- return syscall.FullPath(d + "\\" + p)
- }
- }
-}
-
-var zeroProcAttr syscall.ProcAttr
-var zeroSysProcAttr syscall.SysProcAttr
-
-// Has minor changes to support file inheritance.
-func syscallStartProcess(argv0 string, argv []string, attr *syscall.ProcAttr) (pid int, handle uintptr, err error) {
- if len(argv0) == 0 {
- return 0, 0, syscall.EWINDOWS
- }
- if attr == nil {
- attr = &zeroProcAttr
- }
- sys := attr.Sys
- if sys == nil {
- sys = &zeroSysProcAttr
- }
-
- if len(attr.Files) < 3 {
- return 0, 0, syscall.EINVAL
- }
-
- if len(attr.Dir) != 0 {
- // StartProcess assumes that argv0 is relative to attr.Dir,
- // because it implies Chdir(attr.Dir) before executing argv0.
- // Windows CreateProcess assumes the opposite: it looks for
- // argv0 relative to the current directory, and, only once the new
- // process is started, it does Chdir(attr.Dir). We are adjusting
- // for that difference here by making argv0 absolute.
- var err error
- argv0, err = joinExeDirAndFName(attr.Dir, argv0)
- if err != nil {
- return 0, 0, err
- }
- }
- argv0p, err := syscall.UTF16PtrFromString(argv0)
- if err != nil {
- return 0, 0, err
- }
-
- var cmdline string
- // Windows CreateProcess takes the command line as a single string:
- // use attr.CmdLine if set, else build the command line by escaping
- // and joining each argument with spaces
- if sys.CmdLine != "" {
- cmdline = sys.CmdLine
- } else {
- cmdline = makeCmdLine(argv)
- }
-
- var argvp *uint16
- if len(cmdline) != 0 {
- argvp, err = syscall.UTF16PtrFromString(cmdline)
- if err != nil {
- return 0, 0, err
- }
- }
-
- var dirp *uint16
- if len(attr.Dir) != 0 {
- dirp, err = syscall.UTF16PtrFromString(attr.Dir)
- if err != nil {
- return 0, 0, err
- }
- }
-
- // Acquire the fork lock so that no other threads
- // create new fds that are not yet close-on-exec
- // before we fork.
- syscall.ForkLock.Lock()
- defer syscall.ForkLock.Unlock()
-
- p, _ := syscall.GetCurrentProcess()
- fd := make([]syscall.Handle, len(attr.Files))
- for i := range attr.Files {
- if attr.Files[i] <= 0 {
- continue
- }
- if i < 3 {
- err := syscall.DuplicateHandle(p, syscall.Handle(attr.Files[i]), p, &fd[i], 0, true, syscall.DUPLICATE_SAME_ACCESS)
- if err != nil {
- return 0, 0, err
- }
- defer syscall.CloseHandle(syscall.Handle(fd[i]))
- } else {
- // This is the modification that allows files to be inherited.
- syscall.SetHandleInformation(syscall.Handle(attr.Files[i]), syscall.HANDLE_FLAG_INHERIT, 1)
- defer syscall.SetHandleInformation(syscall.Handle(attr.Files[i]), syscall.HANDLE_FLAG_INHERIT, 0)
- }
- }
- si := new(syscall.StartupInfo)
- si.Cb = uint32(unsafe.Sizeof(*si))
- si.Flags = syscall.STARTF_USESTDHANDLES
- if sys.HideWindow {
- si.Flags |= syscall.STARTF_USESHOWWINDOW
- si.ShowWindow = syscall.SW_HIDE
- }
- si.StdInput = fd[0]
- si.StdOutput = fd[1]
- si.StdErr = fd[2]
-
- pi := new(syscall.ProcessInformation)
-
- flags := sys.CreationFlags | syscall.CREATE_UNICODE_ENVIRONMENT
- err = syscall.CreateProcess(argv0p, argvp, nil, nil, true, flags, createEnvBlock(attr.Env), dirp, si, pi)
- if err != nil {
- return 0, 0, err
- }
- defer syscall.CloseHandle(syscall.Handle(pi.Thread))
-
- return int(pi.ProcessId), uintptr(pi.Process), nil
-}
diff --git a/plugin/rpcplugin/rpcplugintest/rpcplugintest.go b/plugin/rpcplugin/rpcplugintest/rpcplugintest.go
deleted file mode 100644
index 185f741c1..000000000
--- a/plugin/rpcplugin/rpcplugintest/rpcplugintest.go
+++ /dev/null
@@ -1,26 +0,0 @@
-// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-package rpcplugintest
-
-import (
- "io/ioutil"
- "os"
- "os/exec"
- "path/filepath"
- "testing"
-
- "github.com/stretchr/testify/require"
-)
-
-func CompileGo(t *testing.T, sourceCode, outputPath string) {
- dir, err := ioutil.TempDir(".", "")
- require.NoError(t, err)
- defer os.RemoveAll(dir)
- require.NoError(t, ioutil.WriteFile(filepath.Join(dir, "main.go"), []byte(sourceCode), 0600))
- cmd := exec.Command("go", "build", "-o", outputPath, "main.go")
- cmd.Dir = dir
- cmd.Stdout = os.Stdout
- cmd.Stderr = os.Stderr
- require.NoError(t, cmd.Run())
-}
diff --git a/plugin/rpcplugin/rpcplugintest/supervisor.go b/plugin/rpcplugin/rpcplugintest/supervisor.go
deleted file mode 100644
index f3ff847a2..000000000
--- a/plugin/rpcplugin/rpcplugintest/supervisor.go
+++ /dev/null
@@ -1,312 +0,0 @@
-// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-package rpcplugintest
-
-import (
- "encoding/json"
- "fmt"
- "io/ioutil"
- "net/http"
- "net/http/httptest"
- "os"
- "path/filepath"
- "testing"
- "time"
-
- "github.com/stretchr/testify/assert"
- "github.com/stretchr/testify/mock"
- "github.com/stretchr/testify/require"
-
- "github.com/mattermost/mattermost-server/model"
- "github.com/mattermost/mattermost-server/plugin"
- "github.com/mattermost/mattermost-server/plugin/plugintest"
-)
-
-type SupervisorProviderFunc = func(*model.BundleInfo) (plugin.Supervisor, error)
-
-func TestSupervisorProvider(t *testing.T, sp SupervisorProviderFunc) {
- for name, f := range map[string]func(*testing.T, SupervisorProviderFunc){
- "Supervisor": testSupervisor,
- "Supervisor_InvalidExecutablePath": testSupervisor_InvalidExecutablePath,
- "Supervisor_NonExistentExecutablePath": testSupervisor_NonExistentExecutablePath,
- "Supervisor_StartTimeout": testSupervisor_StartTimeout,
- // "Supervisor_PluginCrash": testSupervisor_PluginCrash,
- // "Supervisor_PluginRepeatedlyCrash": testSupervisor_PluginRepeatedlyCrash,
- } {
- t.Run(name, func(t *testing.T) { f(t, sp) })
- }
-}
-
-func testSupervisor(t *testing.T, sp SupervisorProviderFunc) {
- dir, err := ioutil.TempDir("", "")
- require.NoError(t, err)
- defer os.RemoveAll(dir)
-
- backend := filepath.Join(dir, "backend.exe")
- CompileGo(t, `
- package main
-
- import (
- "github.com/mattermost/mattermost-server/plugin/rpcplugin"
- )
-
- type MyPlugin struct {}
-
- func main() {
- rpcplugin.Main(&MyPlugin{})
- }
- `, backend)
-
- ioutil.WriteFile(filepath.Join(dir, "plugin.json"), []byte(`{"id": "foo", "backend": {"executable": "backend.exe"}}`), 0600)
-
- bundle := model.BundleInfoForPath(dir)
- supervisor, err := sp(bundle)
- require.NoError(t, err)
- require.NoError(t, supervisor.Start(nil))
- require.NoError(t, supervisor.Stop())
-}
-
-func testSupervisor_InvalidExecutablePath(t *testing.T, sp SupervisorProviderFunc) {
- dir, err := ioutil.TempDir("", "")
- require.NoError(t, err)
- defer os.RemoveAll(dir)
-
- ioutil.WriteFile(filepath.Join(dir, "plugin.json"), []byte(`{"id": "foo", "backend": {"executable": "/foo/../../backend.exe"}}`), 0600)
-
- bundle := model.BundleInfoForPath(dir)
- supervisor, err := sp(bundle)
- assert.Nil(t, supervisor)
- assert.Error(t, err)
-}
-
-func testSupervisor_NonExistentExecutablePath(t *testing.T, sp SupervisorProviderFunc) {
- dir, err := ioutil.TempDir("", "")
- require.NoError(t, err)
- defer os.RemoveAll(dir)
-
- ioutil.WriteFile(filepath.Join(dir, "plugin.json"), []byte(`{"id": "foo", "backend": {"executable": "thisfileshouldnotexist"}}`), 0600)
-
- bundle := model.BundleInfoForPath(dir)
- supervisor, err := sp(bundle)
- require.NotNil(t, supervisor)
- require.NoError(t, err)
-
- require.Error(t, supervisor.Start(nil))
-}
-
-// If plugin development goes really wrong, let's make sure plugin activation won't block forever.
-func testSupervisor_StartTimeout(t *testing.T, sp SupervisorProviderFunc) {
- dir, err := ioutil.TempDir("", "")
- require.NoError(t, err)
- defer os.RemoveAll(dir)
-
- backend := filepath.Join(dir, "backend.exe")
- CompileGo(t, `
- package main
-
- func main() {
- for {
- }
- }
- `, backend)
-
- ioutil.WriteFile(filepath.Join(dir, "plugin.json"), []byte(`{"id": "foo", "backend": {"executable": "backend.exe"}}`), 0600)
-
- bundle := model.BundleInfoForPath(dir)
- supervisor, err := sp(bundle)
- require.NoError(t, err)
- require.Error(t, supervisor.Start(nil))
-}
-
-// Crashed plugins should be relaunched.
-func testSupervisor_PluginCrash(t *testing.T, sp SupervisorProviderFunc) {
- dir, err := ioutil.TempDir("", "")
- require.NoError(t, err)
- defer os.RemoveAll(dir)
-
- backend := filepath.Join(dir, "backend.exe")
- CompileGo(t, `
- package main
-
- import (
- "os"
-
- "github.com/mattermost/mattermost-server/plugin"
- "github.com/mattermost/mattermost-server/plugin/rpcplugin"
- )
-
- type Configuration struct {
- ShouldExit bool
- }
-
- type MyPlugin struct {
- config Configuration
- }
-
- func (p *MyPlugin) OnActivate(api plugin.API) error {
- api.LoadPluginConfiguration(&p.config)
- return nil
- }
-
- func (p *MyPlugin) OnDeactivate() error {
- if p.config.ShouldExit {
- os.Exit(1)
- }
- return nil
- }
-
- func main() {
- rpcplugin.Main(&MyPlugin{})
- }
- `, backend)
-
- ioutil.WriteFile(filepath.Join(dir, "plugin.json"), []byte(`{"id": "foo", "backend": {"executable": "backend.exe"}}`), 0600)
-
- var api plugintest.API
- shouldExit := true
- api.On("LoadPluginConfiguration", mock.MatchedBy(func(x interface{}) bool { return true })).Return(func(dest interface{}) error {
- err := json.Unmarshal([]byte(fmt.Sprintf(`{"ShouldExit": %v}`, shouldExit)), dest)
- shouldExit = false
- return err
- })
-
- bundle := model.BundleInfoForPath(dir)
- supervisor, err := sp(bundle)
- require.NoError(t, err)
-
- var supervisorWaitErr error
- supervisorWaitDone := make(chan bool, 1)
- go func() {
- supervisorWaitErr = supervisor.Wait()
- close(supervisorWaitDone)
- }()
-
- require.NoError(t, supervisor.Start(&api))
-
- failed := false
- recovered := false
- for i := 0; i < 30; i++ {
- if supervisor.Hooks().OnDeactivate() == nil {
- require.True(t, failed)
- recovered = true
- break
- } else {
- failed = true
- }
- time.Sleep(time.Millisecond * 100)
- }
- assert.True(t, recovered)
-
- select {
- case <-supervisorWaitDone:
- require.Fail(t, "supervisor.Wait() unexpectedly returned")
- case <-time.After(500 * time.Millisecond):
- }
-
- require.NoError(t, supervisor.Stop())
-
- select {
- case <-supervisorWaitDone:
- require.Nil(t, supervisorWaitErr)
- case <-time.After(5000 * time.Millisecond):
- require.Fail(t, "supervisor.Wait() failed to return")
- }
-}
-
-// Crashed plugins should be relaunched at most three times.
-func testSupervisor_PluginRepeatedlyCrash(t *testing.T, sp SupervisorProviderFunc) {
- dir, err := ioutil.TempDir("", "")
- require.NoError(t, err)
- defer os.RemoveAll(dir)
-
- backend := filepath.Join(dir, "backend.exe")
- CompileGo(t, `
- package main
-
- import (
- "net/http"
- "os"
-
- "github.com/mattermost/mattermost-server/plugin/rpcplugin"
- )
-
- type MyPlugin struct {
- crashing bool
- }
-
- func (p *MyPlugin) ServeHTTP(w http.ResponseWriter, r *http.Request) {
- if r.Method == http.MethodPost {
- p.crashing = true
- go func() {
- os.Exit(1)
- }()
- }
-
- if p.crashing {
- w.WriteHeader(http.StatusInternalServerError)
- } else {
- w.WriteHeader(http.StatusOK)
- }
- }
-
- func main() {
- rpcplugin.Main(&MyPlugin{})
- }
- `, backend)
-
- ioutil.WriteFile(filepath.Join(dir, "plugin.json"), []byte(`{"id": "foo", "backend": {"executable": "backend.exe"}}`), 0600)
-
- var api plugintest.API
- bundle := model.BundleInfoForPath(dir)
- supervisor, err := sp(bundle)
- require.NoError(t, err)
-
- var supervisorWaitErr error
- supervisorWaitDone := make(chan bool, 1)
- go func() {
- supervisorWaitErr = supervisor.Wait()
- close(supervisorWaitDone)
- }()
-
- require.NoError(t, supervisor.Start(&api))
-
- for attempt := 1; attempt <= 4; attempt++ {
- // Verify that the plugin is operational
- response := httptest.NewRecorder()
- supervisor.Hooks().ServeHTTP(response, httptest.NewRequest(http.MethodGet, "/plugins/id", nil))
- require.Equal(t, http.StatusOK, response.Result().StatusCode)
-
- // Crash the plugin
- supervisor.Hooks().ServeHTTP(httptest.NewRecorder(), httptest.NewRequest(http.MethodPost, "/plugins/id", nil))
-
- // Wait for it to potentially recover
- recovered := false
- for i := 0; i < 125; i++ {
- response := httptest.NewRecorder()
- supervisor.Hooks().ServeHTTP(response, httptest.NewRequest(http.MethodGet, "/plugins/id", nil))
- if response.Result().StatusCode == http.StatusOK {
- recovered = true
- break
- }
-
- time.Sleep(time.Millisecond * 100)
- }
-
- if attempt < 4 {
- require.Nil(t, supervisorWaitErr)
- require.True(t, recovered, "failed to recover after attempt %d", attempt)
- } else {
- require.False(t, recovered, "unexpectedly recovered after attempt %d", attempt)
- }
- }
-
- select {
- case <-supervisorWaitDone:
- require.NotNil(t, supervisorWaitErr)
- case <-time.After(500 * time.Millisecond):
- require.Fail(t, "supervisor.Wait() failed to return after plugin crashed")
- }
-
- require.NoError(t, supervisor.Stop())
-}
diff --git a/plugin/rpcplugin/sandbox/main_test.go b/plugin/rpcplugin/sandbox/main_test.go
deleted file mode 100644
index 4be4a42af..000000000
--- a/plugin/rpcplugin/sandbox/main_test.go
+++ /dev/null
@@ -1,18 +0,0 @@
-package sandbox
-
-import (
- "testing"
-
- "github.com/mattermost/mattermost-server/mlog"
-)
-
-func TestMain(t *testing.T) {
- // Setup a global logger to catch tests logging outside of app context
- // The global logger will be stomped by apps initalizing but that's fine for testing. Ideally this won't happen.
- mlog.InitGlobalLogger(mlog.NewLogger(&mlog.LoggerConfiguration{
- EnableConsole: true,
- ConsoleJson: true,
- ConsoleLevel: "error",
- EnableFile: false,
- }))
-}
diff --git a/plugin/rpcplugin/sandbox/sandbox.go b/plugin/rpcplugin/sandbox/sandbox.go
deleted file mode 100644
index 96eff02dd..000000000
--- a/plugin/rpcplugin/sandbox/sandbox.go
+++ /dev/null
@@ -1,34 +0,0 @@
-// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-package sandbox
-
-import (
- "context"
- "io"
-
- "github.com/mattermost/mattermost-server/plugin/rpcplugin"
-)
-
-type MountPoint struct {
- Source string
- Destination string
- Type string
- ReadOnly bool
-}
-
-type Configuration struct {
- MountPoints []*MountPoint
- WorkingDirectory string
-}
-
-// NewProcess is like rpcplugin.NewProcess, but launches the process in a sandbox.
-func NewProcess(ctx context.Context, config *Configuration, path string) (rpcplugin.Process, io.ReadWriteCloser, error) {
- return newProcess(ctx, config, path)
-}
-
-// CheckSupport inspects the platform and environment to determine whether or not there are any
-// expected issues with sandboxing. If nil is returned, sandboxing should be used.
-func CheckSupport() error {
- return checkSupport()
-}
diff --git a/plugin/rpcplugin/sandbox/sandbox_linux.go b/plugin/rpcplugin/sandbox/sandbox_linux.go
deleted file mode 100644
index beb00995d..000000000
--- a/plugin/rpcplugin/sandbox/sandbox_linux.go
+++ /dev/null
@@ -1,488 +0,0 @@
-// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-package sandbox
-
-import (
- "bytes"
- "context"
- "encoding/json"
- "fmt"
- "io"
- "io/ioutil"
- "os"
- "os/exec"
- "path/filepath"
- "syscall"
- "unsafe"
-
- "github.com/pkg/errors"
- "golang.org/x/sys/unix"
-
- "github.com/mattermost/mattermost-server/plugin/rpcplugin"
-)
-
-func init() {
- if len(os.Args) < 4 || os.Args[0] != "sandbox.runProcess" {
- return
- }
-
- var config Configuration
- if err := json.Unmarshal([]byte(os.Args[1]), &config); err != nil {
- fmt.Println(err.Error())
- os.Exit(1)
- }
- if err := runProcess(&config, os.Args[2], os.Args[3]); err != nil {
- if eerr, ok := err.(*exec.ExitError); ok {
- if status, ok := eerr.Sys().(syscall.WaitStatus); ok {
- os.Exit(status.ExitStatus())
- }
- }
- fmt.Println(err.Error())
- os.Exit(1)
- }
- os.Exit(0)
-}
-
-func systemMountPoints() (points []*MountPoint) {
- points = append(points, &MountPoint{
- Source: "proc",
- Destination: "/proc",
- Type: "proc",
- }, &MountPoint{
- Source: "/dev/null",
- Destination: "/dev/null",
- }, &MountPoint{
- Source: "/dev/zero",
- Destination: "/dev/zero",
- }, &MountPoint{
- Source: "/dev/full",
- Destination: "/dev/full",
- })
-
- readOnly := []string{
- "/dev/random",
- "/dev/urandom",
- "/etc/resolv.conf",
- "/lib",
- "/lib32",
- "/lib64",
- "/usr/lib",
- "/usr/lib32",
- "/usr/lib64",
- "/etc/ca-certificates",
- "/etc/ssl/certs",
- "/system/etc/security/cacerts",
- "/usr/local/share/certs",
- "/etc/pki/tls/certs",
- "/etc/openssl/certs",
- "/etc/ssl/ca-bundle.pem",
- "/etc/pki/tls/cacert.pem",
- "/etc/pki/ca-trust/extracted/pem/tls-ca-bundle.pem",
- }
-
- for _, v := range []string{"SSL_CERT_FILE", "SSL_CERT_DIR"} {
- if path := os.Getenv(v); path != "" {
- readOnly = append(readOnly, path)
- }
- }
-
- for _, point := range readOnly {
- points = append(points, &MountPoint{
- Source: point,
- Destination: point,
- ReadOnly: true,
- })
- }
-
- return
-}
-
-func runProcess(config *Configuration, path, root string) error {
- if err := syscall.Mount("", "/", "", syscall.MS_PRIVATE|syscall.MS_REC, ""); err != nil {
- return errors.Wrapf(err, "unable to make root private")
- }
-
- if err := mountMountPoints(root, systemMountPoints()); err != nil {
- return errors.Wrapf(err, "unable to mount sandbox system mount points")
- }
-
- if err := mountMountPoints(root, config.MountPoints); err != nil {
- return errors.Wrapf(err, "unable to mount sandbox config mount points")
- }
-
- if err := pivotRoot(root); err != nil {
- return errors.Wrapf(err, "unable to pivot sandbox root")
- }
-
- if err := os.Mkdir("/tmp", 0755); err != nil {
- return errors.Wrapf(err, "unable to create /tmp")
- }
-
- if config.WorkingDirectory != "" {
- if err := os.Chdir(config.WorkingDirectory); err != nil {
- return errors.Wrapf(err, "unable to set working directory")
- }
- }
-
- if err := dropInheritableCapabilities(); err != nil {
- return errors.Wrapf(err, "unable to drop inheritable capabilities")
- }
-
- if err := enableSeccompFilter(); err != nil {
- return errors.Wrapf(err, "unable to enable seccomp filter")
- }
-
- return runExecutable(path)
-}
-
-func mountMountPoint(root string, mountPoint *MountPoint) error {
- isDir := true
- if mountPoint.Type == "" {
- stat, err := os.Lstat(mountPoint.Source)
- if err != nil {
- return nil
- }
- if (stat.Mode() & os.ModeSymlink) != 0 {
- if path, err := filepath.EvalSymlinks(mountPoint.Source); err == nil {
- newMountPoint := *mountPoint
- newMountPoint.Source = path
- if err := mountMountPoint(root, &newMountPoint); err != nil {
- return errors.Wrapf(err, "unable to mount symbolic link target: "+mountPoint.Source)
- }
- return nil
- }
- }
- isDir = stat.IsDir()
- }
-
- target := filepath.Join(root, mountPoint.Destination)
-
- if isDir {
- if err := os.MkdirAll(target, 0755); err != nil {
- return errors.Wrapf(err, "unable to create directory: "+target)
- }
- } else {
- if err := os.MkdirAll(filepath.Dir(target), 0755); err != nil {
- return errors.Wrapf(err, "unable to create directory: "+target)
- }
- f, err := os.Create(target)
- if err != nil {
- return errors.Wrapf(err, "unable to create file: "+target)
- }
- f.Close()
- }
-
- flags := uintptr(syscall.MS_NOSUID | syscall.MS_NODEV)
- if mountPoint.Type == "" {
- flags |= syscall.MS_BIND
- }
- if mountPoint.ReadOnly {
- flags |= syscall.MS_RDONLY
- }
-
- if err := syscall.Mount(mountPoint.Source, target, mountPoint.Type, flags, ""); err != nil {
- return errors.Wrapf(err, "unable to mount "+mountPoint.Source)
- }
-
- if (flags & syscall.MS_BIND) != 0 {
- // If this was a bind mount, our other flags actually got silently ignored during the above syscall:
- //
- // If mountflags includes MS_BIND [...] The remaining bits in the mountflags argument are
- // also ignored, with the exception of MS_REC.
- //
- // Furthermore, remounting will fail if we attempt to unset a bit that was inherited from
- // the mount's parent:
- //
- // The mount(2) flags MS_RDONLY, MS_NOSUID, MS_NOEXEC, and the "atime" flags
- // (MS_NOATIME, MS_NODIRATIME, MS_RELATIME) settings become locked when propagated from
- // a more privileged to a less privileged mount namespace, and may not be changed in the
- // less privileged mount namespace.
- //
- // So we need to get the actual flags, add our new ones, then do a remount if needed.
- var stats syscall.Statfs_t
- if err := syscall.Statfs(target, &stats); err != nil {
- return errors.Wrap(err, "unable to get mount flags for target: "+target)
- }
- const lockedFlagsMask = unix.MS_RDONLY | unix.MS_NOSUID | unix.MS_NOEXEC | unix.MS_NOATIME | unix.MS_NODIRATIME | unix.MS_RELATIME
- lockedFlags := uintptr(stats.Flags & lockedFlagsMask)
- if lockedFlags != ((flags | lockedFlags) & lockedFlagsMask) {
- if err := syscall.Mount("", target, "", flags|lockedFlags|syscall.MS_REMOUNT, ""); err != nil {
- return errors.Wrapf(err, "unable to remount "+mountPoint.Source)
- }
- }
- }
-
- return nil
-}
-
-func mountMountPoints(root string, mountPoints []*MountPoint) error {
- for _, mountPoint := range mountPoints {
- if err := mountMountPoint(root, mountPoint); err != nil {
- return err
- }
- }
-
- return nil
-}
-
-func pivotRoot(newRoot string) error {
- if err := syscall.Mount(newRoot, newRoot, "", syscall.MS_BIND|syscall.MS_REC, ""); err != nil {
- return errors.Wrapf(err, "unable to mount new root")
- }
-
- prevRoot := filepath.Join(newRoot, ".prev_root")
-
- if err := os.MkdirAll(prevRoot, 0700); err != nil {
- return errors.Wrapf(err, "unable to create directory for previous root")
- }
-
- if err := syscall.PivotRoot(newRoot, prevRoot); err != nil {
- return errors.Wrapf(err, "syscall error")
- }
-
- if err := os.Chdir("/"); err != nil {
- return errors.Wrapf(err, "unable to change directory")
- }
-
- prevRoot = "/.prev_root"
-
- if err := syscall.Unmount(prevRoot, syscall.MNT_DETACH); err != nil {
- return errors.Wrapf(err, "unable to unmount previous root")
- }
-
- if err := os.RemoveAll(prevRoot); err != nil {
- return errors.Wrapf(err, "unable to remove previous root directory")
- }
-
- return nil
-}
-
-func dropInheritableCapabilities() error {
- type capHeader struct {
- version uint32
- pid int32
- }
-
- type capData struct {
- effective uint32
- permitted uint32
- inheritable uint32
- }
-
- var hdr capHeader
- var data [2]capData
-
- if _, _, errno := syscall.Syscall(syscall.SYS_CAPGET, uintptr(unsafe.Pointer(&hdr)), 0, 0); errno != 0 {
- return errors.Wrapf(syscall.Errno(errno), "unable to get capabilities version")
- }
-
- if _, _, errno := syscall.Syscall(syscall.SYS_CAPGET, uintptr(unsafe.Pointer(&hdr)), uintptr(unsafe.Pointer(&data[0])), 0); errno != 0 {
- return errors.Wrapf(syscall.Errno(errno), "unable to get capabilities")
- }
-
- data[0].inheritable = 0
- data[1].inheritable = 0
- if _, _, errno := syscall.Syscall(syscall.SYS_CAPSET, uintptr(unsafe.Pointer(&hdr)), uintptr(unsafe.Pointer(&data[0])), 0); errno != 0 {
- return errors.Wrapf(syscall.Errno(errno), "unable to set inheritable capabilities")
- }
-
- for i := 0; i < 64; i++ {
- if _, _, errno := syscall.Syscall(syscall.SYS_PRCTL, syscall.PR_CAPBSET_DROP, uintptr(i), 0); errno != 0 && errno != syscall.EINVAL {
- return errors.Wrapf(syscall.Errno(errno), "unable to drop bounding set capability")
- }
- }
-
- return nil
-}
-
-func enableSeccompFilter() error {
- return EnableSeccompFilter(SeccompFilter(NATIVE_AUDIT_ARCH, AllowedSyscalls))
-}
-
-func runExecutable(path string) error {
- childFiles := []*os.File{
- os.NewFile(3, ""), os.NewFile(4, ""),
- }
- defer childFiles[0].Close()
- defer childFiles[1].Close()
-
- cmd := exec.Command(path)
- cmd.Stdout = os.Stdout
- cmd.Stderr = os.Stderr
- cmd.ExtraFiles = childFiles
- cmd.SysProcAttr = &syscall.SysProcAttr{
- Pdeathsig: syscall.SIGTERM,
- }
-
- if err := cmd.Run(); err != nil {
- return err
- }
-
- return nil
-}
-
-type process struct {
- command *exec.Cmd
- root string
-}
-
-func newProcess(ctx context.Context, config *Configuration, path string) (pOut rpcplugin.Process, rwcOut io.ReadWriteCloser, errOut error) {
- configJSON, err := json.Marshal(config)
- if err != nil {
- return nil, nil, err
- }
-
- ipc, childFiles, err := rpcplugin.NewIPC()
- if err != nil {
- return nil, nil, err
- }
- defer childFiles[0].Close()
- defer childFiles[1].Close()
-
- root, err := ioutil.TempDir("", "")
- if err != nil {
- return nil, nil, err
- }
- defer func() {
- if errOut != nil {
- os.RemoveAll(root)
- }
- }()
-
- cmd := exec.CommandContext(ctx, "/proc/self/exe")
- cmd.Args = []string{"sandbox.runProcess", string(configJSON), path, root}
- cmd.Stdout = os.Stdout
- cmd.Stderr = os.Stderr
- cmd.ExtraFiles = childFiles
-
- cmd.SysProcAttr = &syscall.SysProcAttr{
- Cloneflags: syscall.CLONE_NEWNS | syscall.CLONE_NEWUTS | syscall.CLONE_NEWIPC | syscall.CLONE_NEWPID | syscall.CLONE_NEWUSER,
- Pdeathsig: syscall.SIGTERM,
- GidMappings: []syscall.SysProcIDMap{
- {
- ContainerID: 0,
- HostID: os.Getgid(),
- Size: 1,
- },
- },
- UidMappings: []syscall.SysProcIDMap{
- {
- ContainerID: 0,
- HostID: os.Getuid(),
- Size: 1,
- },
- },
- }
-
- err = cmd.Start()
- if err != nil {
- ipc.Close()
- return nil, nil, err
- }
-
- return &process{
- command: cmd,
- root: root,
- }, ipc, nil
-}
-
-func (p *process) Wait() error {
- defer os.RemoveAll(p.root)
- return p.command.Wait()
-}
-
-func init() {
- if len(os.Args) < 2 || os.Args[0] != "sandbox.checkSupportInNamespace" {
- return
- }
-
- if err := checkSupportInNamespace(os.Args[1]); err != nil {
- fmt.Fprintf(os.Stderr, "%v", err.Error())
- os.Exit(1)
- }
-
- os.Exit(0)
-}
-
-func checkSupportInNamespace(root string) error {
- if err := syscall.Mount("", "/", "", syscall.MS_PRIVATE|syscall.MS_REC, ""); err != nil {
- return errors.Wrapf(err, "unable to make root private")
- }
-
- if err := mountMountPoints(root, systemMountPoints()); err != nil {
- return errors.Wrapf(err, "unable to mount sandbox system mount points")
- }
-
- if err := pivotRoot(root); err != nil {
- return errors.Wrapf(err, "unable to pivot sandbox root")
- }
-
- if err := dropInheritableCapabilities(); err != nil {
- return errors.Wrapf(err, "unable to drop inheritable capabilities")
- }
-
- if err := enableSeccompFilter(); err != nil {
- return errors.Wrapf(err, "unable to enable seccomp filter")
- }
-
- if f, err := os.Create(os.DevNull); err != nil {
- return errors.Wrapf(err, "unable to open os.DevNull")
- } else {
- defer f.Close()
- if _, err = f.Write([]byte("foo")); err != nil {
- return errors.Wrapf(err, "unable to write to os.DevNull")
- }
- }
-
- return nil
-}
-
-func checkSupport() error {
- if AllowedSyscalls == nil {
- return fmt.Errorf("unsupported architecture")
- }
-
- stderr := &bytes.Buffer{}
-
- root, err := ioutil.TempDir("", "")
- if err != nil {
- return err
- }
- defer os.RemoveAll(root)
-
- cmd := exec.Command("/proc/self/exe")
- cmd.Args = []string{"sandbox.checkSupportInNamespace", root}
- cmd.Stderr = stderr
- cmd.SysProcAttr = &syscall.SysProcAttr{
- Cloneflags: syscall.CLONE_NEWNS | syscall.CLONE_NEWUTS | syscall.CLONE_NEWIPC | syscall.CLONE_NEWPID | syscall.CLONE_NEWUSER,
- Pdeathsig: syscall.SIGTERM,
- GidMappings: []syscall.SysProcIDMap{
- {
- ContainerID: 0,
- HostID: os.Getgid(),
- Size: 1,
- },
- },
- UidMappings: []syscall.SysProcIDMap{
- {
- ContainerID: 0,
- HostID: os.Getuid(),
- Size: 1,
- },
- },
- }
-
- if err := cmd.Start(); err != nil {
- return errors.Wrapf(err, "unable to create user namespace")
- }
-
- if err := cmd.Wait(); err != nil {
- if _, ok := err.(*exec.ExitError); ok {
- return errors.Wrapf(fmt.Errorf("%v", stderr.String()), "unable to prepare namespace")
- }
- return errors.Wrapf(err, "unable to prepare namespace")
- }
-
- return nil
-}
diff --git a/plugin/rpcplugin/sandbox/sandbox_linux_test.go b/plugin/rpcplugin/sandbox/sandbox_linux_test.go
deleted file mode 100644
index 2bcbf0c57..000000000
--- a/plugin/rpcplugin/sandbox/sandbox_linux_test.go
+++ /dev/null
@@ -1,159 +0,0 @@
-// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-package sandbox
-
-import (
- "context"
- "io/ioutil"
- "os"
- "path/filepath"
- "testing"
-
- "github.com/stretchr/testify/assert"
- "github.com/stretchr/testify/require"
-
- "github.com/mattermost/mattermost-server/plugin/rpcplugin/rpcplugintest"
-)
-
-func TestNewProcess(t *testing.T) {
- if err := CheckSupport(); err != nil {
- t.Skip("sandboxing not supported:", err)
- }
-
- dir, err := ioutil.TempDir("", "")
- require.NoError(t, err)
- defer os.RemoveAll(dir)
-
- ping := filepath.Join(dir, "ping.exe")
- rpcplugintest.CompileGo(t, `
- package main
-
- import (
- "crypto/rand"
- "fmt"
- "io/ioutil"
- "net/http"
- "os"
- "os/exec"
- "syscall"
-
- "github.com/stretchr/testify/assert"
- "github.com/stretchr/testify/require"
-
- "github.com/mattermost/mattermost-server/plugin/rpcplugin"
- )
-
- var failures int
-
- type T struct {}
- func (T) Errorf(format string, args ...interface{}) {
- fmt.Printf(format, args...)
- failures++
- }
- func (T) FailNow() {
- os.Exit(1)
- }
-
- func init() {
- if len(os.Args) > 0 && os.Args[0] == "exitImmediately" {
- os.Exit(0)
- }
- }
-
- func main() {
- t := &T{}
-
- pwd, err := os.Getwd()
- assert.NoError(t, err)
- assert.Equal(t, "/dir", pwd)
-
- assert.Equal(t, 0, os.Getgid(), "we should see ourselves as root")
- assert.Equal(t, 0, os.Getuid(), "we should see ourselves as root")
-
- f, err := ioutil.TempFile("", "")
- require.NoError(t, err, "we should be able to create temporary files")
- f.Close()
-
- _, err = os.Stat("ping.exe")
- assert.NoError(t, err, "we should be able to read files in the working directory")
-
- buf := make([]byte, 20)
- n, err := rand.Read(buf)
- assert.Equal(t, 20, n)
- assert.NoError(t, err, "we should be able to read from /dev/urandom")
-
- f, err = os.Create("/dev/zero")
- require.NoError(t, err, "we should be able to write to /dev/zero")
- defer f.Close()
- n, err = f.Write([]byte("foo"))
- assert.Equal(t, 3, n)
- require.NoError(t, err, "we should be able to write to /dev/zero")
-
- f, err = os.Create("/dir/foo")
- if f != nil {
- defer f.Close()
- }
- assert.Error(t, err, "we shouldn't be able to write to this read-only mount point")
-
- _, err = ioutil.ReadFile("/etc/resolv.conf")
- require.NoError(t, err, "we should be able to read /etc/resolv.conf")
-
- resp, err := http.Get("https://github.com")
- require.NoError(t, err, "we should be able to use the network")
- resp.Body.Close()
-
- status, err := ioutil.ReadFile("/proc/self/status")
- require.NoError(t, err, "we should be able to read from /proc")
- assert.Regexp(t, status, "CapEff:\\s+0000000000000000", "we should have no effective capabilities")
-
- require.NoError(t, os.MkdirAll("/tmp/dir2", 0755))
- err = syscall.Mount("/dir", "/tmp/dir2", "", syscall.MS_BIND, "")
- assert.Equal(t, syscall.EPERM, err, "we shouldn't be allowed to mount things")
-
- cmd := exec.Command("/proc/self/exe")
- cmd.Args = []string{"exitImmediately"}
- cmd.SysProcAttr = &syscall.SysProcAttr{
- Pdeathsig: syscall.SIGTERM,
- }
- assert.NoError(t, cmd.Run(), "we should be able to re-exec ourself")
-
- cmd = exec.Command("/proc/self/exe")
- cmd.Args = []string{"exitImmediately"}
- cmd.SysProcAttr = &syscall.SysProcAttr{
- Cloneflags: syscall.CLONE_NEWNS | syscall.CLONE_NEWUTS | syscall.CLONE_NEWIPC | syscall.CLONE_NEWPID | syscall.CLONE_NEWUSER,
- Pdeathsig: syscall.SIGTERM,
- }
- assert.Error(t, cmd.Run(), "we shouldn't be able to create new namespaces anymore")
-
- ipc, err := rpcplugin.InheritedProcessIPC()
- require.NoError(t, err)
- defer ipc.Close()
- _, err = ipc.Write([]byte("ping"))
- require.NoError(t, err)
-
- if failures > 0 {
- os.Exit(1)
- }
- }
- `, ping)
-
- p, ipc, err := NewProcess(context.Background(), &Configuration{
- MountPoints: []*MountPoint{
- {
- Source: dir,
- Destination: "/dir",
- ReadOnly: true,
- },
- },
- WorkingDirectory: "/dir",
- }, "/dir/ping.exe")
- require.NoError(t, err)
- defer ipc.Close()
- b := make([]byte, 10)
- n, err := ipc.Read(b)
- require.NoError(t, err)
- assert.Equal(t, 4, n)
- assert.Equal(t, "ping", string(b[:4]))
- require.NoError(t, p.Wait())
-}
diff --git a/plugin/rpcplugin/sandbox/sandbox_other.go b/plugin/rpcplugin/sandbox/sandbox_other.go
deleted file mode 100644
index 3889ecdcc..000000000
--- a/plugin/rpcplugin/sandbox/sandbox_other.go
+++ /dev/null
@@ -1,22 +0,0 @@
-// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-// +build !linux
-
-package sandbox
-
-import (
- "context"
- "fmt"
- "io"
-
- "github.com/mattermost/mattermost-server/plugin/rpcplugin"
-)
-
-func newProcess(ctx context.Context, config *Configuration, path string) (rpcplugin.Process, io.ReadWriteCloser, error) {
- return nil, nil, checkSupport()
-}
-
-func checkSupport() error {
- return fmt.Errorf("sandboxing is not supported on this platform")
-}
diff --git a/plugin/rpcplugin/sandbox/sandbox_test.go b/plugin/rpcplugin/sandbox/sandbox_test.go
deleted file mode 100644
index e0149e28d..000000000
--- a/plugin/rpcplugin/sandbox/sandbox_test.go
+++ /dev/null
@@ -1,25 +0,0 @@
-// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-package sandbox
-
-import (
- "testing"
-)
-
-// TestCheckSupport is here for debugging purposes and has no assertions. You can quickly test
-// sandboxing support with various systems by compiling the test executable and running this test on
-// your target systems. For example, with docker, executed from the root of the repo:
-//
-// docker run --rm -it -w /go/src/github.com/mattermost/mattermost-server
-// -v $(pwd):/go/src/github.com/mattermost/mattermost-server golang:1.9
-// go test -c ./plugin/rpcplugin
-//
-// docker run --rm -it --privileged -w /opt/mattermost
-// -v $(pwd):/opt/mattermost centos:6
-// ./rpcplugin.test --test.v --test.run TestCheckSupport
-func TestCheckSupport(t *testing.T) {
- if err := CheckSupport(); err != nil {
- t.Log(err.Error())
- }
-}
diff --git a/plugin/rpcplugin/sandbox/seccomp_linux.go b/plugin/rpcplugin/sandbox/seccomp_linux.go
deleted file mode 100644
index afe86e90a..000000000
--- a/plugin/rpcplugin/sandbox/seccomp_linux.go
+++ /dev/null
@@ -1,178 +0,0 @@
-// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-package sandbox
-
-import (
- "syscall"
- "unsafe"
-
- "github.com/pkg/errors"
- "golang.org/x/net/bpf"
- "golang.org/x/sys/unix"
-)
-
-const (
- SECCOMP_RET_ALLOW = 0x7fff0000
- SECCOMP_RET_ERRNO = 0x00050000
-)
-
-const (
- EM_X86_64 = 62
-
- __AUDIT_ARCH_64BIT = 0x80000000
- __AUDIT_ARCH_LE = 0x40000000
-
- AUDIT_ARCH_X86_64 = EM_X86_64 | __AUDIT_ARCH_64BIT | __AUDIT_ARCH_LE
-
- nrSize = 4
- archOffset = nrSize
- ipOffset = archOffset + 4
- argsOffset = ipOffset + 8
-)
-
-type SeccompCondition interface {
- Filter(littleEndian bool, skipFalseSentinel uint8) []bpf.Instruction
-}
-
-func seccompArgLowWord(arg int, littleEndian bool) uint32 {
- offset := uint32(argsOffset + arg*8)
- if !littleEndian {
- offset += 4
- }
- return offset
-}
-
-func seccompArgHighWord(arg int, littleEndian bool) uint32 {
- offset := uint32(argsOffset + arg*8)
- if littleEndian {
- offset += 4
- }
- return offset
-}
-
-type SeccompArgHasNoBits struct {
- Arg int
- Mask uint64
-}
-
-func (c SeccompArgHasNoBits) Filter(littleEndian bool, skipFalseSentinel uint8) []bpf.Instruction {
- return []bpf.Instruction{
- bpf.LoadAbsolute{Off: seccompArgHighWord(c.Arg, littleEndian), Size: 4},
- bpf.JumpIf{Cond: bpf.JumpBitsSet, Val: uint32(c.Mask >> 32), SkipTrue: skipFalseSentinel},
- bpf.LoadAbsolute{Off: seccompArgLowWord(c.Arg, littleEndian), Size: 4},
- bpf.JumpIf{Cond: bpf.JumpBitsSet, Val: uint32(c.Mask), SkipTrue: skipFalseSentinel},
- }
-}
-
-type SeccompArgHasAnyBit struct {
- Arg int
- Mask uint64
-}
-
-func (c SeccompArgHasAnyBit) Filter(littleEndian bool, skipFalseSentinel uint8) []bpf.Instruction {
- return []bpf.Instruction{
- bpf.LoadAbsolute{Off: seccompArgHighWord(c.Arg, littleEndian), Size: 4},
- bpf.JumpIf{Cond: bpf.JumpBitsSet, Val: uint32(c.Mask >> 32), SkipTrue: 2},
- bpf.LoadAbsolute{Off: seccompArgLowWord(c.Arg, littleEndian), Size: 4},
- bpf.JumpIf{Cond: bpf.JumpBitsSet, Val: uint32(c.Mask), SkipFalse: skipFalseSentinel},
- }
-}
-
-type SeccompArgEquals struct {
- Arg int
- Value uint64
-}
-
-func (c SeccompArgEquals) Filter(littleEndian bool, skipFalseSentinel uint8) []bpf.Instruction {
- return []bpf.Instruction{
- bpf.LoadAbsolute{Off: seccompArgHighWord(c.Arg, littleEndian), Size: 4},
- bpf.JumpIf{Cond: bpf.JumpEqual, Val: uint32(c.Value >> 32), SkipFalse: skipFalseSentinel},
- bpf.LoadAbsolute{Off: seccompArgLowWord(c.Arg, littleEndian), Size: 4},
- bpf.JumpIf{Cond: bpf.JumpEqual, Val: uint32(c.Value), SkipFalse: skipFalseSentinel},
- }
-}
-
-type SeccompConditions struct {
- All []SeccompCondition
-}
-
-type SeccompSyscall struct {
- Syscall uint32
- Any []SeccompConditions
-}
-
-func SeccompFilter(arch uint32, allowedSyscalls []SeccompSyscall) (filter []bpf.Instruction) {
- filter = append(filter,
- bpf.LoadAbsolute{Off: archOffset, Size: 4},
- bpf.JumpIf{Cond: bpf.JumpEqual, Val: arch, SkipTrue: 1},
- bpf.RetConstant{Val: uint32(SECCOMP_RET_ERRNO | unix.EPERM)},
- )
-
- filter = append(filter, bpf.LoadAbsolute{Off: 0, Size: nrSize})
- for _, s := range allowedSyscalls {
- if s.Any != nil {
- syscallStart := len(filter)
- filter = append(filter, bpf.Instruction(nil))
- for _, cs := range s.Any {
- anyStart := len(filter)
- for _, c := range cs.All {
- filter = append(filter, c.Filter((arch&__AUDIT_ARCH_LE) != 0, 255)...)
- }
- filter = append(filter, bpf.RetConstant{Val: SECCOMP_RET_ALLOW})
- for i := anyStart; i < len(filter); i++ {
- if jump, ok := filter[i].(bpf.JumpIf); ok {
- if len(filter)-i-1 > 255 {
- panic("condition too long")
- }
- if jump.SkipFalse == 255 {
- jump.SkipFalse = uint8(len(filter) - i - 1)
- }
- if jump.SkipTrue == 255 {
- jump.SkipTrue = uint8(len(filter) - i - 1)
- }
- filter[i] = jump
- }
- }
- }
- filter = append(filter, bpf.RetConstant{Val: uint32(SECCOMP_RET_ERRNO | unix.EPERM)})
- if len(filter)-syscallStart-1 > 255 {
- panic("conditions too long")
- }
- filter[syscallStart] = bpf.JumpIf{Cond: bpf.JumpEqual, Val: uint32(s.Syscall), SkipFalse: uint8(len(filter) - syscallStart - 1)}
- } else {
- filter = append(filter,
- bpf.JumpIf{Cond: bpf.JumpEqual, Val: uint32(s.Syscall), SkipFalse: 1},
- bpf.RetConstant{Val: SECCOMP_RET_ALLOW},
- )
- }
- }
-
- return append(filter, bpf.RetConstant{Val: uint32(SECCOMP_RET_ERRNO | unix.EPERM)})
-}
-
-func EnableSeccompFilter(filter []bpf.Instruction) error {
- assembled, err := bpf.Assemble(filter)
- if err != nil {
- return errors.Wrapf(err, "unable to assemble filter")
- }
-
- sockFilter := make([]unix.SockFilter, len(filter))
- for i, instruction := range assembled {
- sockFilter[i].Code = instruction.Op
- sockFilter[i].Jt = instruction.Jt
- sockFilter[i].Jf = instruction.Jf
- sockFilter[i].K = instruction.K
- }
-
- prog := unix.SockFprog{
- Len: uint16(len(sockFilter)),
- Filter: &sockFilter[0],
- }
-
- if _, _, errno := syscall.Syscall(syscall.SYS_PRCTL, unix.PR_SET_SECCOMP, unix.SECCOMP_MODE_FILTER, uintptr(unsafe.Pointer(&prog))); errno != 0 {
- return errors.Wrapf(syscall.Errno(errno), "syscall error")
- }
-
- return nil
-}
diff --git a/plugin/rpcplugin/sandbox/seccomp_linux_amd64.go b/plugin/rpcplugin/sandbox/seccomp_linux_amd64.go
deleted file mode 100644
index 7338ebbe0..000000000
--- a/plugin/rpcplugin/sandbox/seccomp_linux_amd64.go
+++ /dev/null
@@ -1,301 +0,0 @@
-// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-package sandbox
-
-import (
- "golang.org/x/sys/unix"
-)
-
-const NATIVE_AUDIT_ARCH = AUDIT_ARCH_X86_64
-
-var AllowedSyscalls = []SeccompSyscall{
- {Syscall: unix.SYS_ACCEPT},
- {Syscall: unix.SYS_ACCEPT4},
- {Syscall: unix.SYS_ACCESS},
- {Syscall: unix.SYS_ADJTIMEX},
- {Syscall: unix.SYS_ALARM},
- {Syscall: unix.SYS_ARCH_PRCTL},
- {Syscall: unix.SYS_BIND},
- {Syscall: unix.SYS_BRK},
- {Syscall: unix.SYS_CAPGET},
- {Syscall: unix.SYS_CAPSET},
- {Syscall: unix.SYS_CHDIR},
- {Syscall: unix.SYS_CHMOD},
- {Syscall: unix.SYS_CHOWN},
- {Syscall: unix.SYS_CLOCK_GETRES},
- {Syscall: unix.SYS_CLOCK_GETTIME},
- {Syscall: unix.SYS_CLOCK_NANOSLEEP},
- {
- Syscall: unix.SYS_CLONE,
- Any: []SeccompConditions{{
- All: []SeccompCondition{SeccompArgHasNoBits{
- Arg: 0,
- Mask: unix.CLONE_NEWCGROUP | unix.CLONE_NEWIPC | unix.CLONE_NEWNET | unix.CLONE_NEWNS | unix.CLONE_NEWPID | unix.CLONE_NEWUSER | unix.CLONE_NEWUTS,
- }},
- }},
- },
- {Syscall: unix.SYS_CLOSE},
- {Syscall: unix.SYS_CONNECT},
- {Syscall: unix.SYS_COPY_FILE_RANGE},
- {Syscall: unix.SYS_CREAT},
- {Syscall: unix.SYS_DUP},
- {Syscall: unix.SYS_DUP2},
- {Syscall: unix.SYS_DUP3},
- {Syscall: unix.SYS_EPOLL_CREATE},
- {Syscall: unix.SYS_EPOLL_CREATE1},
- {Syscall: unix.SYS_EPOLL_CTL},
- {Syscall: unix.SYS_EPOLL_CTL_OLD},
- {Syscall: unix.SYS_EPOLL_PWAIT},
- {Syscall: unix.SYS_EPOLL_WAIT},
- {Syscall: unix.SYS_EPOLL_WAIT_OLD},
- {Syscall: unix.SYS_EVENTFD},
- {Syscall: unix.SYS_EVENTFD2},
- {Syscall: unix.SYS_EXECVE},
- {Syscall: unix.SYS_EXECVEAT},
- {Syscall: unix.SYS_EXIT},
- {Syscall: unix.SYS_EXIT_GROUP},
- {Syscall: unix.SYS_FACCESSAT},
- {Syscall: unix.SYS_FADVISE64},
- {Syscall: unix.SYS_FALLOCATE},
- {Syscall: unix.SYS_FANOTIFY_MARK},
- {Syscall: unix.SYS_FCHDIR},
- {Syscall: unix.SYS_FCHMOD},
- {Syscall: unix.SYS_FCHMODAT},
- {Syscall: unix.SYS_FCHOWN},
- {Syscall: unix.SYS_FCHOWNAT},
- {Syscall: unix.SYS_FCNTL},
- {Syscall: unix.SYS_FDATASYNC},
- {Syscall: unix.SYS_FGETXATTR},
- {Syscall: unix.SYS_FLISTXATTR},
- {Syscall: unix.SYS_FLOCK},
- {Syscall: unix.SYS_FORK},
- {Syscall: unix.SYS_FREMOVEXATTR},
- {Syscall: unix.SYS_FSETXATTR},
- {Syscall: unix.SYS_FSTAT},
- {Syscall: unix.SYS_FSTATFS},
- {Syscall: unix.SYS_FSYNC},
- {Syscall: unix.SYS_FTRUNCATE},
- {Syscall: unix.SYS_FUTEX},
- {Syscall: unix.SYS_FUTIMESAT},
- {Syscall: unix.SYS_GETCPU},
- {Syscall: unix.SYS_GETCWD},
- {Syscall: unix.SYS_GETDENTS},
- {Syscall: unix.SYS_GETDENTS64},
- {Syscall: unix.SYS_GETEGID},
- {Syscall: unix.SYS_GETEUID},
- {Syscall: unix.SYS_GETGID},
- {Syscall: unix.SYS_GETGROUPS},
- {Syscall: unix.SYS_GETITIMER},
- {Syscall: unix.SYS_GETPEERNAME},
- {Syscall: unix.SYS_GETPGID},
- {Syscall: unix.SYS_GETPGRP},
- {Syscall: unix.SYS_GETPID},
- {Syscall: unix.SYS_GETPPID},
- {Syscall: unix.SYS_GETPRIORITY},
- {Syscall: unix.SYS_GETRANDOM},
- {Syscall: unix.SYS_GETRESGID},
- {Syscall: unix.SYS_GETRESUID},
- {Syscall: unix.SYS_GETRLIMIT},
- {Syscall: unix.SYS_GET_ROBUST_LIST},
- {Syscall: unix.SYS_GETRUSAGE},
- {Syscall: unix.SYS_GETSID},
- {Syscall: unix.SYS_GETSOCKNAME},
- {Syscall: unix.SYS_GETSOCKOPT},
- {Syscall: unix.SYS_GET_THREAD_AREA},
- {Syscall: unix.SYS_GETTID},
- {Syscall: unix.SYS_GETTIMEOFDAY},
- {Syscall: unix.SYS_GETUID},
- {Syscall: unix.SYS_GETXATTR},
- {Syscall: unix.SYS_INOTIFY_ADD_WATCH},
- {Syscall: unix.SYS_INOTIFY_INIT},
- {Syscall: unix.SYS_INOTIFY_INIT1},
- {Syscall: unix.SYS_INOTIFY_RM_WATCH},
- {Syscall: unix.SYS_IO_CANCEL},
- {Syscall: unix.SYS_IOCTL},
- {Syscall: unix.SYS_IO_DESTROY},
- {Syscall: unix.SYS_IO_GETEVENTS},
- {Syscall: unix.SYS_IOPRIO_GET},
- {Syscall: unix.SYS_IOPRIO_SET},
- {Syscall: unix.SYS_IO_SETUP},
- {Syscall: unix.SYS_IO_SUBMIT},
- {Syscall: unix.SYS_KILL},
- {Syscall: unix.SYS_LCHOWN},
- {Syscall: unix.SYS_LGETXATTR},
- {Syscall: unix.SYS_LINK},
- {Syscall: unix.SYS_LINKAT},
- {Syscall: unix.SYS_LISTEN},
- {Syscall: unix.SYS_LISTXATTR},
- {Syscall: unix.SYS_LLISTXATTR},
- {Syscall: unix.SYS_LREMOVEXATTR},
- {Syscall: unix.SYS_LSEEK},
- {Syscall: unix.SYS_LSETXATTR},
- {Syscall: unix.SYS_LSTAT},
- {Syscall: unix.SYS_MADVISE},
- {Syscall: unix.SYS_MEMFD_CREATE},
- {Syscall: unix.SYS_MINCORE},
- {Syscall: unix.SYS_MKDIR},
- {Syscall: unix.SYS_MKDIRAT},
- {Syscall: unix.SYS_MKNOD},
- {Syscall: unix.SYS_MKNODAT},
- {Syscall: unix.SYS_MLOCK},
- {Syscall: unix.SYS_MLOCK2},
- {Syscall: unix.SYS_MLOCKALL},
- {Syscall: unix.SYS_MMAP},
- {Syscall: unix.SYS_MODIFY_LDT},
- {Syscall: unix.SYS_MPROTECT},
- {Syscall: unix.SYS_MQ_GETSETATTR},
- {Syscall: unix.SYS_MQ_NOTIFY},
- {Syscall: unix.SYS_MQ_OPEN},
- {Syscall: unix.SYS_MQ_TIMEDRECEIVE},
- {Syscall: unix.SYS_MQ_TIMEDSEND},
- {Syscall: unix.SYS_MQ_UNLINK},
- {Syscall: unix.SYS_MREMAP},
- {Syscall: unix.SYS_MSGCTL},
- {Syscall: unix.SYS_MSGGET},
- {Syscall: unix.SYS_MSGRCV},
- {Syscall: unix.SYS_MSGSND},
- {Syscall: unix.SYS_MSYNC},
- {Syscall: unix.SYS_MUNLOCK},
- {Syscall: unix.SYS_MUNLOCKALL},
- {Syscall: unix.SYS_MUNMAP},
- {Syscall: unix.SYS_NANOSLEEP},
- {Syscall: unix.SYS_NEWFSTATAT},
- {Syscall: unix.SYS_OPEN},
- {Syscall: unix.SYS_OPENAT},
- {Syscall: unix.SYS_PAUSE},
- {
- Syscall: unix.SYS_PERSONALITY,
- Any: []SeccompConditions{
- {All: []SeccompCondition{SeccompArgEquals{Arg: 0, Value: 0}}},
- {All: []SeccompCondition{SeccompArgEquals{Arg: 0, Value: 8}}},
- {All: []SeccompCondition{SeccompArgEquals{Arg: 0, Value: 0x20000}}},
- {All: []SeccompCondition{SeccompArgEquals{Arg: 0, Value: 0x20008}}},
- {All: []SeccompCondition{SeccompArgEquals{Arg: 0, Value: 0xffffffff}}},
- },
- },
- {Syscall: unix.SYS_PIPE},
- {Syscall: unix.SYS_PIPE2},
- {Syscall: unix.SYS_POLL},
- {Syscall: unix.SYS_PPOLL},
- {Syscall: unix.SYS_PRCTL},
- {Syscall: unix.SYS_PREAD64},
- {Syscall: unix.SYS_PREADV},
- {Syscall: unix.SYS_PREADV2},
- {Syscall: unix.SYS_PRLIMIT64},
- {Syscall: unix.SYS_PSELECT6},
- {Syscall: unix.SYS_PWRITE64},
- {Syscall: unix.SYS_PWRITEV},
- {Syscall: unix.SYS_PWRITEV2},
- {Syscall: unix.SYS_READ},
- {Syscall: unix.SYS_READAHEAD},
- {Syscall: unix.SYS_READLINK},
- {Syscall: unix.SYS_READLINKAT},
- {Syscall: unix.SYS_READV},
- {Syscall: unix.SYS_RECVFROM},
- {Syscall: unix.SYS_RECVMMSG},
- {Syscall: unix.SYS_RECVMSG},
- {Syscall: unix.SYS_REMAP_FILE_PAGES},
- {Syscall: unix.SYS_REMOVEXATTR},
- {Syscall: unix.SYS_RENAME},
- {Syscall: unix.SYS_RENAMEAT},
- {Syscall: unix.SYS_RENAMEAT2},
- {Syscall: unix.SYS_RESTART_SYSCALL},
- {Syscall: unix.SYS_RMDIR},
- {Syscall: unix.SYS_RT_SIGACTION},
- {Syscall: unix.SYS_RT_SIGPENDING},
- {Syscall: unix.SYS_RT_SIGPROCMASK},
- {Syscall: unix.SYS_RT_SIGQUEUEINFO},
- {Syscall: unix.SYS_RT_SIGRETURN},
- {Syscall: unix.SYS_RT_SIGSUSPEND},
- {Syscall: unix.SYS_RT_SIGTIMEDWAIT},
- {Syscall: unix.SYS_RT_TGSIGQUEUEINFO},
- {Syscall: unix.SYS_SCHED_GETAFFINITY},
- {Syscall: unix.SYS_SCHED_GETATTR},
- {Syscall: unix.SYS_SCHED_GETPARAM},
- {Syscall: unix.SYS_SCHED_GET_PRIORITY_MAX},
- {Syscall: unix.SYS_SCHED_GET_PRIORITY_MIN},
- {Syscall: unix.SYS_SCHED_GETSCHEDULER},
- {Syscall: unix.SYS_SCHED_RR_GET_INTERVAL},
- {Syscall: unix.SYS_SCHED_SETAFFINITY},
- {Syscall: unix.SYS_SCHED_SETATTR},
- {Syscall: unix.SYS_SCHED_SETPARAM},
- {Syscall: unix.SYS_SCHED_SETSCHEDULER},
- {Syscall: unix.SYS_SCHED_YIELD},
- {Syscall: unix.SYS_SECCOMP},
- {Syscall: unix.SYS_SELECT},
- {Syscall: unix.SYS_SEMCTL},
- {Syscall: unix.SYS_SEMGET},
- {Syscall: unix.SYS_SEMOP},
- {Syscall: unix.SYS_SEMTIMEDOP},
- {Syscall: unix.SYS_SENDFILE},
- {Syscall: unix.SYS_SENDMMSG},
- {Syscall: unix.SYS_SENDMSG},
- {Syscall: unix.SYS_SENDTO},
- {Syscall: unix.SYS_SETFSGID},
- {Syscall: unix.SYS_SETFSUID},
- {Syscall: unix.SYS_SETGID},
- {Syscall: unix.SYS_SETGROUPS},
- {Syscall: unix.SYS_SETITIMER},
- {Syscall: unix.SYS_SETPGID},
- {Syscall: unix.SYS_SETPRIORITY},
- {Syscall: unix.SYS_SETREGID},
- {Syscall: unix.SYS_SETRESGID},
- {Syscall: unix.SYS_SETRESUID},
- {Syscall: unix.SYS_SETREUID},
- {Syscall: unix.SYS_SETRLIMIT},
- {Syscall: unix.SYS_SET_ROBUST_LIST},
- {Syscall: unix.SYS_SETSID},
- {Syscall: unix.SYS_SETSOCKOPT},
- {Syscall: unix.SYS_SET_THREAD_AREA},
- {Syscall: unix.SYS_SET_TID_ADDRESS},
- {Syscall: unix.SYS_SETUID},
- {Syscall: unix.SYS_SETXATTR},
- {Syscall: unix.SYS_SHMAT},
- {Syscall: unix.SYS_SHMCTL},
- {Syscall: unix.SYS_SHMDT},
- {Syscall: unix.SYS_SHMGET},
- {Syscall: unix.SYS_SHUTDOWN},
- {Syscall: unix.SYS_SIGALTSTACK},
- {Syscall: unix.SYS_SIGNALFD},
- {Syscall: unix.SYS_SIGNALFD4},
- {Syscall: unix.SYS_SOCKET},
- {Syscall: unix.SYS_SOCKETPAIR},
- {Syscall: unix.SYS_SPLICE},
- {Syscall: unix.SYS_STAT},
- {Syscall: unix.SYS_STATFS},
- {Syscall: unix.SYS_SYMLINK},
- {Syscall: unix.SYS_SYMLINKAT},
- {Syscall: unix.SYS_SYNC},
- {Syscall: unix.SYS_SYNC_FILE_RANGE},
- {Syscall: unix.SYS_SYNCFS},
- {Syscall: unix.SYS_SYSINFO},
- {Syscall: unix.SYS_SYSLOG},
- {Syscall: unix.SYS_TEE},
- {Syscall: unix.SYS_TGKILL},
- {Syscall: unix.SYS_TIME},
- {Syscall: unix.SYS_TIMER_CREATE},
- {Syscall: unix.SYS_TIMER_DELETE},
- {Syscall: unix.SYS_TIMERFD_CREATE},
- {Syscall: unix.SYS_TIMERFD_GETTIME},
- {Syscall: unix.SYS_TIMERFD_SETTIME},
- {Syscall: unix.SYS_TIMER_GETOVERRUN},
- {Syscall: unix.SYS_TIMER_GETTIME},
- {Syscall: unix.SYS_TIMER_SETTIME},
- {Syscall: unix.SYS_TIMES},
- {Syscall: unix.SYS_TKILL},
- {Syscall: unix.SYS_TRUNCATE},
- {Syscall: unix.SYS_UMASK},
- {Syscall: unix.SYS_UNAME},
- {Syscall: unix.SYS_UNLINK},
- {Syscall: unix.SYS_UNLINKAT},
- {Syscall: unix.SYS_UTIME},
- {Syscall: unix.SYS_UTIMENSAT},
- {Syscall: unix.SYS_UTIMES},
- {Syscall: unix.SYS_VFORK},
- {Syscall: unix.SYS_VMSPLICE},
- {Syscall: unix.SYS_WAIT4},
- {Syscall: unix.SYS_WAITID},
- {Syscall: unix.SYS_WRITE},
- {Syscall: unix.SYS_WRITEV},
-}
diff --git a/plugin/rpcplugin/sandbox/seccomp_linux_other.go b/plugin/rpcplugin/sandbox/seccomp_linux_other.go
deleted file mode 100644
index 5573943cd..000000000
--- a/plugin/rpcplugin/sandbox/seccomp_linux_other.go
+++ /dev/null
@@ -1,10 +0,0 @@
-// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-// +build linux,!amd64
-
-package sandbox
-
-const NATIVE_AUDIT_ARCH = 0
-
-var AllowedSyscalls []SeccompSyscall
diff --git a/plugin/rpcplugin/sandbox/seccomp_linux_test.go b/plugin/rpcplugin/sandbox/seccomp_linux_test.go
deleted file mode 100644
index 46fe38fe0..000000000
--- a/plugin/rpcplugin/sandbox/seccomp_linux_test.go
+++ /dev/null
@@ -1,210 +0,0 @@
-// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-package sandbox
-
-import (
- "encoding/binary"
- "syscall"
- "testing"
-
- "github.com/stretchr/testify/assert"
- "github.com/stretchr/testify/require"
- "golang.org/x/net/bpf"
-)
-
-func seccompData(nr int32, arch uint32, ip uint64, args ...uint64) []byte {
- var buf [64]byte
- binary.BigEndian.PutUint32(buf[0:], uint32(nr))
- binary.BigEndian.PutUint32(buf[4:], arch)
- binary.BigEndian.PutUint64(buf[8:], ip)
- for i := 0; i < 6 && i < len(args); i++ {
- binary.BigEndian.PutUint64(buf[16+i*8:], args[i])
- }
- return buf[:]
-}
-
-func TestSeccompFilter(t *testing.T) {
- for name, tc := range map[string]struct {
- Filter []bpf.Instruction
- Data []byte
- Expected bool
- }{
- "Allowed": {
- Filter: SeccompFilter(0xf00, []SeccompSyscall{
- {Syscall: syscall.SYS_READ},
- {Syscall: syscall.SYS_WRITE},
- }),
- Data: seccompData(syscall.SYS_READ, 0xf00, 0),
- Expected: true,
- },
- "AllFail": {
- Filter: SeccompFilter(0xf00, []SeccompSyscall{
- {
- Syscall: syscall.SYS_READ,
- Any: []SeccompConditions{
- {All: []SeccompCondition{
- &SeccompArgHasAnyBit{Arg: 0, Mask: 2},
- &SeccompArgHasAnyBit{Arg: 1, Mask: 2},
- &SeccompArgHasAnyBit{Arg: 2, Mask: 2},
- &SeccompArgHasAnyBit{Arg: 3, Mask: 2},
- }},
- },
- },
- {Syscall: syscall.SYS_WRITE},
- }),
- Data: seccompData(syscall.SYS_READ, 0xf00, 0, 1, 2, 3, 4),
- Expected: false,
- },
- "AllPass": {
- Filter: SeccompFilter(0xf00, []SeccompSyscall{
- {
- Syscall: syscall.SYS_READ,
- Any: []SeccompConditions{
- {All: []SeccompCondition{
- &SeccompArgHasAnyBit{Arg: 0, Mask: 7},
- &SeccompArgHasAnyBit{Arg: 1, Mask: 7},
- &SeccompArgHasAnyBit{Arg: 2, Mask: 7},
- &SeccompArgHasAnyBit{Arg: 3, Mask: 7},
- }},
- },
- },
- {Syscall: syscall.SYS_WRITE},
- }),
- Data: seccompData(syscall.SYS_READ, 0xf00, 0, 1, 2, 3, 4),
- Expected: true,
- },
- "AnyFail": {
- Filter: SeccompFilter(0xf00, []SeccompSyscall{
- {
- Syscall: syscall.SYS_READ,
- Any: []SeccompConditions{
- {All: []SeccompCondition{&SeccompArgHasAnyBit{Arg: 0, Mask: 8}}},
- {All: []SeccompCondition{&SeccompArgHasAnyBit{Arg: 1, Mask: 8}}},
- {All: []SeccompCondition{&SeccompArgHasAnyBit{Arg: 2, Mask: 8}}},
- {All: []SeccompCondition{&SeccompArgHasAnyBit{Arg: 3, Mask: 8}}},
- },
- },
- {Syscall: syscall.SYS_WRITE},
- }),
- Data: seccompData(syscall.SYS_READ, 0xf00, 0, 1, 2, 3, 4),
- Expected: false,
- },
- "AnyPass": {
- Filter: SeccompFilter(0xf00, []SeccompSyscall{
- {
- Syscall: syscall.SYS_READ,
- Any: []SeccompConditions{
- {All: []SeccompCondition{&SeccompArgHasAnyBit{Arg: 0, Mask: 2}}},
- {All: []SeccompCondition{&SeccompArgHasAnyBit{Arg: 1, Mask: 2}}},
- {All: []SeccompCondition{&SeccompArgHasAnyBit{Arg: 2, Mask: 2}}},
- {All: []SeccompCondition{&SeccompArgHasAnyBit{Arg: 3, Mask: 2}}},
- },
- },
- {Syscall: syscall.SYS_WRITE},
- }),
- Data: seccompData(syscall.SYS_READ, 0xf00, 0, 1, 2, 3, 4),
- Expected: true,
- },
- "BadArch": {
- Filter: SeccompFilter(0xf00, []SeccompSyscall{
- {Syscall: syscall.SYS_READ},
- {Syscall: syscall.SYS_WRITE},
- }),
- Data: seccompData(syscall.SYS_MOUNT, 0xf01, 0),
- Expected: false,
- },
- "BadSyscall": {
- Filter: SeccompFilter(0xf00, []SeccompSyscall{
- {Syscall: syscall.SYS_READ},
- {Syscall: syscall.SYS_WRITE},
- }),
- Data: seccompData(syscall.SYS_MOUNT, 0xf00, 0),
- Expected: false,
- },
- } {
- t.Run(name, func(t *testing.T) {
- vm, err := bpf.NewVM(tc.Filter)
- require.NoError(t, err)
- result, err := vm.Run(tc.Data)
- require.NoError(t, err)
- if tc.Expected {
- assert.Equal(t, SECCOMP_RET_ALLOW, result)
- } else {
- assert.Equal(t, int(SECCOMP_RET_ERRNO|syscall.EPERM), result)
- }
- })
- }
-}
-
-func TestSeccompFilter_Conditions(t *testing.T) {
- for name, tc := range map[string]struct {
- Condition SeccompCondition
- Args []uint64
- Expected bool
- }{
- "ArgHasAnyBitFail": {
- Condition: SeccompArgHasAnyBit{Arg: 0, Mask: 0x0004},
- Args: []uint64{0x0400008000},
- Expected: false,
- },
- "ArgHasAnyBitPass1": {
- Condition: SeccompArgHasAnyBit{Arg: 0, Mask: 0x400000004},
- Args: []uint64{0x8000008004},
- Expected: true,
- },
- "ArgHasAnyBitPass2": {
- Condition: SeccompArgHasAnyBit{Arg: 0, Mask: 0x400000004},
- Args: []uint64{0x8400008000},
- Expected: true,
- },
- "ArgHasNoBitsFail1": {
- Condition: SeccompArgHasNoBits{Arg: 0, Mask: 0x1100000011},
- Args: []uint64{0x0000008007},
- Expected: false,
- },
- "ArgHasNoBitsFail2": {
- Condition: SeccompArgHasNoBits{Arg: 0, Mask: 0x1100000011},
- Args: []uint64{0x0700008000},
- Expected: false,
- },
- "ArgHasNoBitsPass": {
- Condition: SeccompArgHasNoBits{Arg: 0, Mask: 0x400000004},
- Args: []uint64{0x8000008000},
- Expected: true,
- },
- "ArgEqualsPass": {
- Condition: SeccompArgEquals{Arg: 0, Value: 0x123456789ABCDEF},
- Args: []uint64{0x123456789ABCDEF},
- Expected: true,
- },
- "ArgEqualsFail1": {
- Condition: SeccompArgEquals{Arg: 0, Value: 0x123456789ABCDEF},
- Args: []uint64{0x023456789ABCDEF},
- Expected: false,
- },
- "ArgEqualsFail2": {
- Condition: SeccompArgEquals{Arg: 0, Value: 0x123456789ABCDEF},
- Args: []uint64{0x123456789ABCDE0},
- Expected: false,
- },
- } {
- t.Run(name, func(t *testing.T) {
- filter := SeccompFilter(0xf00, []SeccompSyscall{
- {
- Syscall: 1,
- Any: []SeccompConditions{{All: []SeccompCondition{tc.Condition}}},
- },
- })
- vm, err := bpf.NewVM(filter)
- require.NoError(t, err)
- result, err := vm.Run(seccompData(1, 0xf00, 0, tc.Args...))
- require.NoError(t, err)
- if tc.Expected {
- assert.Equal(t, SECCOMP_RET_ALLOW, result)
- } else {
- assert.Equal(t, int(SECCOMP_RET_ERRNO|syscall.EPERM), result)
- }
- })
- }
-}
diff --git a/plugin/rpcplugin/sandbox/supervisor.go b/plugin/rpcplugin/sandbox/supervisor.go
deleted file mode 100644
index 0e63954fd..000000000
--- a/plugin/rpcplugin/sandbox/supervisor.go
+++ /dev/null
@@ -1,33 +0,0 @@
-// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-package sandbox
-
-import (
- "context"
- "fmt"
- "io"
- "path/filepath"
- "strings"
-
- "github.com/mattermost/mattermost-server/model"
- "github.com/mattermost/mattermost-server/plugin"
- "github.com/mattermost/mattermost-server/plugin/rpcplugin"
-)
-
-func SupervisorProvider(bundle *model.BundleInfo) (plugin.Supervisor, error) {
- return rpcplugin.SupervisorWithNewProcessFunc(bundle, func(ctx context.Context) (rpcplugin.Process, io.ReadWriteCloser, error) {
- executable := filepath.Clean(filepath.Join(".", bundle.Manifest.Backend.Executable))
- if strings.HasPrefix(executable, "..") {
- return nil, nil, fmt.Errorf("invalid backend executable")
- }
- return NewProcess(ctx, &Configuration{
- MountPoints: []*MountPoint{{
- Source: bundle.Path,
- Destination: "/plugin",
- ReadOnly: true,
- }},
- WorkingDirectory: "/plugin",
- }, filepath.Join("/plugin", executable))
- })
-}
diff --git a/plugin/rpcplugin/sandbox/supervisor_test.go b/plugin/rpcplugin/sandbox/supervisor_test.go
deleted file mode 100644
index 245185dd5..000000000
--- a/plugin/rpcplugin/sandbox/supervisor_test.go
+++ /dev/null
@@ -1,18 +0,0 @@
-// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-package sandbox
-
-import (
- "testing"
-
- "github.com/mattermost/mattermost-server/plugin/rpcplugin/rpcplugintest"
-)
-
-func TestSupervisorProvider(t *testing.T) {
- if err := CheckSupport(); err != nil {
- t.Skip("sandboxing not supported:", err)
- }
-
- rpcplugintest.TestSupervisorProvider(t, SupervisorProvider)
-}
diff --git a/plugin/rpcplugin/supervisor.go b/plugin/rpcplugin/supervisor.go
deleted file mode 100644
index 246747c89..000000000
--- a/plugin/rpcplugin/supervisor.go
+++ /dev/null
@@ -1,176 +0,0 @@
-// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-package rpcplugin
-
-import (
- "context"
- "fmt"
- "io"
- "path/filepath"
- "strings"
- "sync/atomic"
- "time"
-
- "github.com/mattermost/mattermost-server/mlog"
- "github.com/mattermost/mattermost-server/model"
- "github.com/mattermost/mattermost-server/plugin"
-)
-
-const (
- MaxProcessRestarts = 3
-)
-
-// Supervisor implements a plugin.Supervisor that launches the plugin in a separate process and
-// communicates via RPC.
-//
-// If the plugin unexpectedly exits, the supervisor will relaunch it after a short delay, but will
-// only restart a plugin at most three times.
-type Supervisor struct {
- hooks atomic.Value
- done chan bool
- cancel context.CancelFunc
- newProcess func(context.Context) (Process, io.ReadWriteCloser, error)
- pluginId string
- pluginErr error
-}
-
-var _ plugin.Supervisor = (*Supervisor)(nil)
-
-// Starts the plugin. This method will block until the plugin is successfully launched for the first
-// time and will return an error if the plugin cannot be launched at all.
-func (s *Supervisor) Start(api plugin.API) error {
- ctx, cancel := context.WithCancel(context.Background())
- s.done = make(chan bool, 1)
- start := make(chan error, 1)
- go s.run(ctx, start, api)
-
- select {
- case <-time.After(time.Second * 3):
- cancel()
- <-s.done
- return fmt.Errorf("timed out waiting for plugin")
- case err := <-start:
- s.cancel = cancel
- return err
- }
-}
-
-// Waits for the supervisor to stop (on demand or of its own accord), returning any error that
-// triggered the supervisor to stop.
-func (s *Supervisor) Wait() error {
- <-s.done
- return s.pluginErr
-}
-
-// Stops the plugin.
-func (s *Supervisor) Stop() error {
- s.cancel()
- <-s.done
- return nil
-}
-
-// Returns the hooks used to communicate with the plugin. The hooks may change if the plugin is
-// restarted, so the return value should not be cached.
-func (s *Supervisor) Hooks() plugin.Hooks {
- return s.hooks.Load().(plugin.Hooks)
-}
-
-func (s *Supervisor) run(ctx context.Context, start chan<- error, api plugin.API) {
- defer func() {
- close(s.done)
- }()
- done := ctx.Done()
- for i := 0; i <= MaxProcessRestarts; i++ {
- s.runPlugin(ctx, start, api)
- select {
- case <-done:
- return
- default:
- start = nil
- if i < MaxProcessRestarts {
- mlog.Error("Plugin terminated unexpectedly", mlog.String("plugin_id", s.pluginId))
- time.Sleep(time.Duration((1 + i*i)) * time.Second)
- } else {
- s.pluginErr = fmt.Errorf("plugin terminated unexpectedly too many times")
- mlog.Error("Plugin shutdown", mlog.String("plugin_id", s.pluginId), mlog.Int("max_process_restarts", MaxProcessRestarts), mlog.Err(s.pluginErr))
- }
- }
- }
-}
-
-func (s *Supervisor) runPlugin(ctx context.Context, start chan<- error, api plugin.API) error {
- if start == nil {
- mlog.Debug("Restarting plugin", mlog.String("plugin_id", s.pluginId))
- }
-
- p, ipc, err := s.newProcess(ctx)
- if err != nil {
- if start != nil {
- start <- err
- }
- return err
- }
-
- muxer := NewMuxer(ipc, false)
- closeMuxer := make(chan bool, 1)
- muxerClosed := make(chan error, 1)
- go func() {
- select {
- case <-ctx.Done():
- break
- case <-closeMuxer:
- break
- }
- muxerClosed <- muxer.Close()
- }()
-
- hooks, err := ConnectMain(muxer, s.pluginId)
- if err == nil {
- err = hooks.OnActivate(api)
- }
-
- if err != nil {
- if start != nil {
- start <- err
- }
- closeMuxer <- true
- <-muxerClosed
- p.Wait()
- return err
- }
-
- s.hooks.Store(hooks)
-
- if start != nil {
- start <- nil
- }
- p.Wait()
- closeMuxer <- true
- <-muxerClosed
-
- return nil
-}
-
-func SupervisorProvider(bundle *model.BundleInfo) (plugin.Supervisor, error) {
- return SupervisorWithNewProcessFunc(bundle, func(ctx context.Context) (Process, io.ReadWriteCloser, error) {
- executable := filepath.Clean(filepath.Join(".", bundle.Manifest.Backend.Executable))
- if strings.HasPrefix(executable, "..") {
- return nil, nil, fmt.Errorf("invalid backend executable")
- }
- return NewProcess(ctx, filepath.Join(bundle.Path, executable))
- })
-}
-
-func SupervisorWithNewProcessFunc(bundle *model.BundleInfo, newProcess func(context.Context) (Process, io.ReadWriteCloser, error)) (plugin.Supervisor, error) {
- if bundle.Manifest == nil {
- return nil, fmt.Errorf("no manifest available")
- } else if bundle.Manifest.Backend == nil || bundle.Manifest.Backend.Executable == "" {
- return nil, fmt.Errorf("no backend executable specified")
- }
- executable := filepath.Clean(filepath.Join(".", bundle.Manifest.Backend.Executable))
- if strings.HasPrefix(executable, "..") {
- return nil, fmt.Errorf("invalid backend executable")
- }
- return &Supervisor{pluginId: bundle.Manifest.Id, newProcess: newProcess}, nil
-}
diff --git a/plugin/rpcplugin/supervisor_test.go b/plugin/rpcplugin/supervisor_test.go
deleted file mode 100644
index 06c1fafeb..000000000
--- a/plugin/rpcplugin/supervisor_test.go
+++ /dev/null
@@ -1,14 +0,0 @@
-// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-package rpcplugin
-
-import (
- "testing"
-
- "github.com/mattermost/mattermost-server/plugin/rpcplugin/rpcplugintest"
-)
-
-func TestSupervisorProvider(t *testing.T) {
- rpcplugintest.TestSupervisorProvider(t, SupervisorProvider)
-}