| 
									
										
										
										
											2023-03-12 16:00:57 +01:00
										 |  |  | // GoToSocial | 
					
						
							|  |  |  | // Copyright (C) GoToSocial Authors admin@gotosocial.org | 
					
						
							|  |  |  | // SPDX-License-Identifier: AGPL-3.0-or-later | 
					
						
							|  |  |  | // | 
					
						
							|  |  |  | // This program is free software: you can redistribute it and/or modify | 
					
						
							|  |  |  | // it under the terms of the GNU Affero General Public License as published by | 
					
						
							|  |  |  | // the Free Software Foundation, either version 3 of the License, or | 
					
						
							|  |  |  | // (at your option) any later version. | 
					
						
							|  |  |  | // | 
					
						
							|  |  |  | // This program is distributed in the hope that it will be useful, | 
					
						
							|  |  |  | // but WITHOUT ANY WARRANTY; without even the implied warranty of | 
					
						
							|  |  |  | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | 
					
						
							|  |  |  | // GNU Affero General Public License for more details. | 
					
						
							|  |  |  | // | 
					
						
							|  |  |  | // You should have received a copy of the GNU Affero General Public License | 
					
						
							|  |  |  | // along with this program.  If not, see <http://www.gnu.org/licenses/>. | 
					
						
							| 
									
										
										
										
											2021-05-08 14:25:55 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  | package transport | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | import ( | 
					
						
							| 
									
										
										
										
											2024-02-23 15:24:40 +00:00
										 |  |  | 	"bytes" | 
					
						
							| 
									
										
										
										
											2021-08-25 15:34:33 +02:00
										 |  |  | 	"context" | 
					
						
							| 
									
										
										
										
											2022-05-15 10:16:43 +01:00
										 |  |  | 	"crypto/rsa" | 
					
						
							|  |  |  | 	"crypto/x509" | 
					
						
							| 
									
										
										
										
											2022-03-15 15:01:19 +01:00
										 |  |  | 	"encoding/json" | 
					
						
							| 
									
										
										
										
											2024-02-23 15:24:40 +00:00
										 |  |  | 	"errors" | 
					
						
							| 
									
										
										
										
											2021-05-08 14:25:55 +02:00
										 |  |  | 	"fmt" | 
					
						
							| 
									
										
										
										
											2024-02-23 15:24:40 +00:00
										 |  |  | 	"io" | 
					
						
							|  |  |  | 	"net/http" | 
					
						
							| 
									
										
										
										
											2022-03-15 15:01:19 +01:00
										 |  |  | 	"net/url" | 
					
						
							| 
									
										
										
										
											2023-04-28 16:45:21 +01:00
										 |  |  | 	"runtime" | 
					
						
							| 
									
										
										
										
											2021-05-08 14:25:55 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-05-15 10:16:43 +01:00
										 |  |  | 	"codeberg.org/gruf/go-byteutil" | 
					
						
							| 
									
										
										
										
											2022-11-15 18:45:15 +00:00
										 |  |  | 	"codeberg.org/gruf/go-cache/v3" | 
					
						
							| 
									
										
										
										
											2021-11-13 17:29:43 +01:00
										 |  |  | 	"github.com/superseriousbusiness/activity/pub" | 
					
						
							| 
									
										
										
										
											2023-05-09 12:16:10 +02:00
										 |  |  | 	"github.com/superseriousbusiness/gotosocial/internal/ap" | 
					
						
							| 
									
										
										
										
											2021-05-08 14:25:55 +02:00
										 |  |  | 	"github.com/superseriousbusiness/gotosocial/internal/config" | 
					
						
							| 
									
										
										
										
											2024-02-23 15:24:40 +00:00
										 |  |  | 	"github.com/superseriousbusiness/gotosocial/internal/db" | 
					
						
							| 
									
										
										
										
											2022-03-15 15:01:19 +01:00
										 |  |  | 	"github.com/superseriousbusiness/gotosocial/internal/federation/federatingdb" | 
					
						
							| 
									
										
										
										
											2023-03-08 13:57:41 +01:00
										 |  |  | 	"github.com/superseriousbusiness/gotosocial/internal/state" | 
					
						
							| 
									
										
										
										
											2021-05-08 14:25:55 +02:00
										 |  |  | ) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // Controller generates transports for use in making federation requests to other servers. | 
					
						
							|  |  |  | type Controller interface { | 
					
						
							| 
									
										
										
										
											2022-05-15 10:16:43 +01:00
										 |  |  | 	// NewTransport returns an http signature transport with the given public key ID (URL location of pubkey), and the given private key. | 
					
						
							|  |  |  | 	NewTransport(pubKeyID string, privkey *rsa.PrivateKey) (Transport, error) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// NewTransportForUsername searches for account with username, and returns result of .NewTransport(). | 
					
						
							| 
									
										
										
										
											2021-08-25 15:34:33 +02:00
										 |  |  | 	NewTransportForUsername(ctx context.Context, username string) (Transport, error) | 
					
						
							| 
									
										
										
										
											2021-05-08 14:25:55 +02:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | type controller struct { | 
					
						
							| 
									
										
										
										
											2023-03-08 13:57:41 +01:00
										 |  |  | 	state     *state.State | 
					
						
							| 
									
										
										
										
											2022-05-15 10:16:43 +01:00
										 |  |  | 	fedDB     federatingdb.DB | 
					
						
							|  |  |  | 	clock     pub.Clock | 
					
						
							| 
									
										
										
										
											2024-04-02 12:12:26 +01:00
										 |  |  | 	client    pub.HttpClient | 
					
						
							| 
									
										
										
										
											2023-08-03 10:34:35 +01:00
										 |  |  | 	trspCache cache.TTLCache[string, *transport] | 
					
						
							| 
									
										
										
										
											2022-05-15 10:16:43 +01:00
										 |  |  | 	userAgent string | 
					
						
							| 
									
										
										
										
											2023-04-28 16:45:21 +01:00
										 |  |  | 	senders   int // no. concurrent batch delivery routines. | 
					
						
							| 
									
										
										
										
											2022-03-15 15:01:19 +01:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-05-15 10:16:43 +01:00
										 |  |  | // NewController returns an implementation of the Controller interface for creating new transports | 
					
						
							| 
									
										
										
										
											2024-04-02 12:12:26 +01:00
										 |  |  | func NewController(state *state.State, federatingDB federatingdb.DB, clock pub.Clock, client pub.HttpClient) Controller { | 
					
						
							| 
									
										
										
										
											2023-05-08 19:03:38 +02:00
										 |  |  | 	var ( | 
					
						
							|  |  |  | 		host             = config.GetHost() | 
					
						
							|  |  |  | 		proto            = config.GetProtocol() | 
					
						
							|  |  |  | 		version          = config.GetSoftwareVersion() | 
					
						
							|  |  |  | 		senderMultiplier = config.GetAdvancedSenderMultiplier() | 
					
						
							|  |  |  | 	) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	senders := senderMultiplier * runtime.GOMAXPROCS(0) | 
					
						
							|  |  |  | 	if senders < 1 { | 
					
						
							|  |  |  | 		// Clamp senders to 1. | 
					
						
							|  |  |  | 		senders = 1 | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2022-03-15 15:01:19 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-05-15 10:16:43 +01:00
										 |  |  | 	c := &controller{ | 
					
						
							| 
									
										
										
										
											2023-03-08 13:57:41 +01:00
										 |  |  | 		state:     state, | 
					
						
							| 
									
										
										
										
											2022-05-15 10:16:43 +01:00
										 |  |  | 		fedDB:     federatingDB, | 
					
						
							|  |  |  | 		clock:     clock, | 
					
						
							|  |  |  | 		client:    client, | 
					
						
							| 
									
										
										
										
											2023-08-03 10:34:35 +01:00
										 |  |  | 		trspCache: cache.NewTTL[string, *transport](0, 100, 0), | 
					
						
							| 
									
										
										
										
											2024-02-17 09:54:10 +01:00
										 |  |  | 		userAgent: fmt.Sprintf("gotosocial/%s (+%s://%s)", version, proto, host), | 
					
						
							| 
									
										
										
										
											2023-05-08 19:03:38 +02:00
										 |  |  | 		senders:   senders, | 
					
						
							| 
									
										
										
										
											2022-03-15 15:01:19 +01:00
										 |  |  | 	} | 
					
						
							| 
									
										
										
										
											2021-05-08 14:25:55 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-05-15 10:16:43 +01:00
										 |  |  | 	return c | 
					
						
							| 
									
										
										
										
											2021-05-08 14:25:55 +02:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-05-15 10:16:43 +01:00
										 |  |  | func (c *controller) NewTransport(pubKeyID string, privkey *rsa.PrivateKey) (Transport, error) { | 
					
						
							|  |  |  | 	// Generate public key string for cache key | 
					
						
							|  |  |  | 	// | 
					
						
							|  |  |  | 	// NOTE: it is safe to use the public key as the cache | 
					
						
							|  |  |  | 	// key here as we are generating it ourselves from the | 
					
						
							|  |  |  | 	// private key. If we were simply using a public key | 
					
						
							|  |  |  | 	// provided as argument that would absolutely NOT be safe. | 
					
						
							|  |  |  | 	pubStr := privkeyToPublicStr(privkey) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// First check for cached transport | 
					
						
							| 
									
										
										
										
											2022-10-08 12:50:16 +01:00
										 |  |  | 	transp, ok := c.trspCache.Get(pubStr) | 
					
						
							| 
									
										
										
										
											2022-05-15 10:16:43 +01:00
										 |  |  | 	if ok { | 
					
						
							|  |  |  | 		return transp, nil | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2021-05-08 14:25:55 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-05-15 10:16:43 +01:00
										 |  |  | 	// Create the transport | 
					
						
							|  |  |  | 	transp = &transport{ | 
					
						
							|  |  |  | 		controller: c, | 
					
						
							|  |  |  | 		pubKeyID:   pubKeyID, | 
					
						
							|  |  |  | 		privkey:    privkey, | 
					
						
							| 
									
										
										
										
											2021-05-08 14:25:55 +02:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-05-15 10:16:43 +01:00
										 |  |  | 	// Cache this transport under pubkey | 
					
						
							| 
									
										
										
										
											2022-11-15 18:45:15 +00:00
										 |  |  | 	if !c.trspCache.Add(pubStr, transp) { | 
					
						
							| 
									
										
										
										
											2022-05-15 10:16:43 +01:00
										 |  |  | 		var cached *transport | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-10-08 12:50:16 +01:00
										 |  |  | 		cached, ok = c.trspCache.Get(pubStr) | 
					
						
							| 
									
										
										
										
											2022-05-15 10:16:43 +01:00
										 |  |  | 		if !ok { | 
					
						
							|  |  |  | 			// Some ridiculous race cond. | 
					
						
							| 
									
										
										
										
											2022-10-08 12:50:16 +01:00
										 |  |  | 			c.trspCache.Set(pubStr, transp) | 
					
						
							| 
									
										
										
										
											2022-05-15 10:16:43 +01:00
										 |  |  | 		} else { | 
					
						
							|  |  |  | 			// Use already cached | 
					
						
							|  |  |  | 			transp = cached | 
					
						
							|  |  |  | 		} | 
					
						
							| 
									
										
										
										
											2021-05-08 14:25:55 +02:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-05-15 10:16:43 +01:00
										 |  |  | 	return transp, nil | 
					
						
							| 
									
										
										
										
											2021-05-08 14:25:55 +02:00
										 |  |  | } | 
					
						
							| 
									
										
										
										
											2021-08-10 13:32:39 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-08-25 15:34:33 +02:00
										 |  |  | func (c *controller) NewTransportForUsername(ctx context.Context, username string) (Transport, error) { | 
					
						
							| 
									
										
										
										
											2021-08-10 13:32:39 +02:00
										 |  |  | 	// We need an account to use to create a transport for dereferecing something. | 
					
						
							|  |  |  | 	// If a username has been given, we can fetch the account with that username and use it. | 
					
						
							|  |  |  | 	// Otherwise, we can take the instance account and use those credentials to make the request. | 
					
						
							|  |  |  | 	var u string | 
					
						
							|  |  |  | 	if username == "" { | 
					
						
							| 
									
										
										
										
											2022-05-30 13:41:24 +01:00
										 |  |  | 		u = config.GetHost() | 
					
						
							| 
									
										
										
										
											2021-08-10 13:32:39 +02:00
										 |  |  | 	} else { | 
					
						
							|  |  |  | 		u = username | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2021-08-20 12:26:56 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-03-08 13:57:41 +01:00
										 |  |  | 	ourAccount, err := c.state.DB.GetAccountByUsernameDomain(ctx, u, "") | 
					
						
							| 
									
										
										
										
											2021-08-20 12:26:56 +02:00
										 |  |  | 	if err != nil { | 
					
						
							| 
									
										
										
										
											2021-08-10 13:32:39 +02:00
										 |  |  | 		return nil, fmt.Errorf("error getting account %s from db: %s", username, err) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	transport, err := c.NewTransport(ourAccount.PublicKeyURI, ourAccount.PrivateKey) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		return nil, fmt.Errorf("error creating transport for user %s: %s", username, err) | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2022-09-02 10:56:33 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-08-10 13:32:39 +02:00
										 |  |  | 	return transport, nil | 
					
						
							|  |  |  | } | 
					
						
							| 
									
										
										
										
											2022-05-15 10:16:43 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  | // dereferenceLocalFollowers is a shortcut to dereference followers of an | 
					
						
							|  |  |  | // account on this instance, without making any external api/http calls. | 
					
						
							|  |  |  | // | 
					
						
							|  |  |  | // It is passed to new transports, and should only be invoked when the iri.Host == this host. | 
					
						
							| 
									
										
										
										
											2024-02-23 15:24:40 +00:00
										 |  |  | func (c *controller) dereferenceLocalFollowers(ctx context.Context, iri *url.URL) (*http.Response, error) { | 
					
						
							| 
									
										
										
										
											2022-05-15 10:16:43 +01:00
										 |  |  | 	followers, err := c.fedDB.Followers(ctx, iri) | 
					
						
							| 
									
										
										
										
											2024-02-23 15:24:40 +00:00
										 |  |  | 	if err != nil && !errors.Is(err, db.ErrNoEntries) { | 
					
						
							| 
									
										
										
										
											2022-05-15 10:16:43 +01:00
										 |  |  | 		return nil, err | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-02-23 15:24:40 +00:00
										 |  |  | 	if followers == nil { | 
					
						
							|  |  |  | 		// Return a generic 404 not found response. | 
					
						
							|  |  |  | 		rsp := craftResponse(iri, http.StatusNotFound) | 
					
						
							|  |  |  | 		return rsp, nil | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-05-09 12:16:10 +02:00
										 |  |  | 	i, err := ap.Serialize(followers) | 
					
						
							| 
									
										
										
										
											2022-05-15 10:16:43 +01:00
										 |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		return nil, err | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-02-23 15:24:40 +00:00
										 |  |  | 	b, err := json.Marshal(i) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		return nil, err | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Return a response with AS data as body. | 
					
						
							|  |  |  | 	rsp := craftResponse(iri, http.StatusOK) | 
					
						
							|  |  |  | 	rsp.Body = io.NopCloser(bytes.NewReader(b)) | 
					
						
							|  |  |  | 	return rsp, nil | 
					
						
							| 
									
										
										
										
											2022-05-15 10:16:43 +01:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // dereferenceLocalUser is a shortcut to dereference followers an account on | 
					
						
							|  |  |  | // this instance, without making any external api/http calls. | 
					
						
							|  |  |  | // | 
					
						
							|  |  |  | // It is passed to new transports, and should only be invoked when the iri.Host == this host. | 
					
						
							| 
									
										
										
										
											2024-02-23 15:24:40 +00:00
										 |  |  | func (c *controller) dereferenceLocalUser(ctx context.Context, iri *url.URL) (*http.Response, error) { | 
					
						
							| 
									
										
										
										
											2022-05-15 10:16:43 +01:00
										 |  |  | 	user, err := c.fedDB.Get(ctx, iri) | 
					
						
							| 
									
										
										
										
											2024-02-23 15:24:40 +00:00
										 |  |  | 	if err != nil && !errors.Is(err, db.ErrNoEntries) { | 
					
						
							| 
									
										
										
										
											2022-05-15 10:16:43 +01:00
										 |  |  | 		return nil, err | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-02-23 15:24:40 +00:00
										 |  |  | 	if user == nil { | 
					
						
							|  |  |  | 		// Return a generic 404 not found response. | 
					
						
							|  |  |  | 		rsp := craftResponse(iri, http.StatusNotFound) | 
					
						
							|  |  |  | 		return rsp, nil | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-05-09 12:16:10 +02:00
										 |  |  | 	i, err := ap.Serialize(user) | 
					
						
							| 
									
										
										
										
											2022-05-15 10:16:43 +01:00
										 |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		return nil, err | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-02-23 15:24:40 +00:00
										 |  |  | 	b, err := json.Marshal(i) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		return nil, err | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Return a response with AS data as body. | 
					
						
							|  |  |  | 	rsp := craftResponse(iri, http.StatusOK) | 
					
						
							|  |  |  | 	rsp.Body = io.NopCloser(bytes.NewReader(b)) | 
					
						
							|  |  |  | 	return rsp, nil | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func craftResponse(url *url.URL, code int) *http.Response { | 
					
						
							|  |  |  | 	rsp := new(http.Response) | 
					
						
							|  |  |  | 	rsp.Request = new(http.Request) | 
					
						
							|  |  |  | 	rsp.Request.URL = url | 
					
						
							|  |  |  | 	rsp.Status = http.StatusText(code) | 
					
						
							|  |  |  | 	rsp.StatusCode = code | 
					
						
							|  |  |  | 	return rsp | 
					
						
							| 
									
										
										
										
											2022-05-15 10:16:43 +01:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // privkeyToPublicStr will create a string representation of RSA public key from private. | 
					
						
							|  |  |  | func privkeyToPublicStr(privkey *rsa.PrivateKey) string { | 
					
						
							|  |  |  | 	b := x509.MarshalPKCS1PublicKey(&privkey.PublicKey) | 
					
						
							|  |  |  | 	return byteutil.B2S(b) | 
					
						
							|  |  |  | } |