From 99eb3bf564b0e351099f4f5cdad94a0ec54c5750 Mon Sep 17 00:00:00 2001 From: tsmethurst Date: Wed, 30 Jun 2021 12:02:28 +0200 Subject: [PATCH] domain blocking more work --- internal/api/client/admin/domainblock.go | 7 ++- internal/federation/authenticate.go | 54 ++++++++-------------- internal/federation/dereference.go | 55 +++++++++++++++++++---- internal/federation/federatingprotocol.go | 17 ++++++- internal/federation/federator.go | 9 +++- internal/federation/finger.go | 3 ++ internal/federation/util.go | 23 ++++++++++ internal/processing/admin/domainblock.go | 3 +- internal/processing/federation.go | 19 ++++---- internal/visibility/statusvisible.go | 10 +++++ internal/visibility/util.go | 18 ++++++++ 11 files changed, 160 insertions(+), 58 deletions(-) create mode 100644 internal/federation/util.go diff --git a/internal/api/client/admin/domainblock.go b/internal/api/client/admin/domainblock.go index 0f944df5b..e129c5e99 100644 --- a/internal/api/client/admin/domainblock.go +++ b/internal/api/client/admin/domainblock.go @@ -1,6 +1,7 @@ package admin import ( + "errors" "fmt" "net/http" @@ -59,6 +60,10 @@ func (m *Module) DomainBlocksPOSTHandler(c *gin.Context) { } func validateCreateDomainBlock(form *model.DomainBlockCreateRequest) error { - // TODO: add some validation here later if necessary + // add some more validation here later if necessary + if form.Domain == "" { + return errors.New("empty domain provided") + } + return nil } diff --git a/internal/federation/authenticate.go b/internal/federation/authenticate.go index c4183144b..f7ca51ec3 100644 --- a/internal/federation/authenticate.go +++ b/internal/federation/authenticate.go @@ -115,7 +115,7 @@ func getPublicKeyFromResponse(c context.Context, b []byte, keyID *url.URL) (voca // // Also note that this function *does not* dereference the remote account that the signature key is associated with. // Other functions should use the returned URL to dereference the remote account, if required. -func (f *federator) AuthenticateFederatedRequest(requestedUsername string, r *http.Request) (*url.URL, error) { +func (f *federator) AuthenticateFederatedRequest(requestedUsername string, r *http.Request) (*url.URL, bool, error) { var publicKey interface{} var pkOwnerURI *url.URL @@ -126,23 +126,24 @@ func (f *federator) AuthenticateFederatedRequest(requestedUsername string, r *ht verifier, err := httpsig.NewVerifier(r) if err != nil { - return nil, fmt.Errorf("could not create http sig verifier: %s", err) + return nil, false, fmt.Errorf("could not create http sig verifier: %s", err) } // The key ID should be given in the signature so that we know where to fetch it from the remote server. // This will be something like https://example.org/users/whatever_requesting_user#main-key requestingPublicKeyID, err := url.Parse(verifier.KeyId()) if err != nil { - return nil, fmt.Errorf("could not parse key id into a url: %s", err) + return nil, false, fmt.Errorf("could not parse key id into a url: %s", err) } // if the domain is blocked we want to make as few calls towards it as possible, so already bail here if that's the case! blockedDomain, err := f.blockedDomain(requestingPublicKeyID.Host) if err != nil { - return nil, fmt.Errorf("could not tell if domain %s was blocked or not: %s", requestingPublicKeyID.Host, err) + return nil, false, fmt.Errorf("could not tell if domain %s was blocked or not: %s", requestingPublicKeyID.Host, err) } if blockedDomain { - return nil, fmt.Errorf("host %s was domain blocked, aborting auth", requestingPublicKeyID.Host) + f.log.Infof("domain %s is blocked", requestingPublicKeyID.Host) + return nil, false, nil } requestingRemoteAccount := >smodel.Account{} @@ -152,12 +153,12 @@ func (f *federator) AuthenticateFederatedRequest(requestedUsername string, r *ht // LOCAL ACCOUNT REQUEST // the request is coming from INSIDE THE HOUSE so skip the remote dereferencing if err := f.db.GetWhere([]db.Where{{Key: "public_key_uri", Value: requestingPublicKeyID.String()}}, requestingLocalAccount); err != nil { - return nil, fmt.Errorf("couldn't get local account with public key uri %s from the database: %s", requestingPublicKeyID.String(), err) + return nil, false, fmt.Errorf("couldn't get local account with public key uri %s from the database: %s", requestingPublicKeyID.String(), err) } publicKey = requestingLocalAccount.PublicKey pkOwnerURI, err = url.Parse(requestingLocalAccount.URI) if err != nil { - return nil, fmt.Errorf("error parsing url %s: %s", requestingLocalAccount.URI, err) + return nil, false, fmt.Errorf("error parsing url %s: %s", requestingLocalAccount.URI, err) } } else if err := f.db.GetWhere([]db.Where{{Key: "public_key_uri", Value: requestingPublicKeyID.String()}}, requestingRemoteAccount); err == nil { // REMOTE ACCOUNT REQUEST WITH KEY CACHED LOCALLY @@ -165,7 +166,7 @@ func (f *federator) AuthenticateFederatedRequest(requestedUsername string, r *ht publicKey = requestingRemoteAccount.PublicKey pkOwnerURI, err = url.Parse(requestingRemoteAccount.URI) if err != nil { - return nil, fmt.Errorf("error parsing url %s: %s", requestingRemoteAccount.URI, err) + return nil, false, fmt.Errorf("error parsing url %s: %s", requestingRemoteAccount.URI, err) } } else { // REMOTE ACCOUNT REQUEST WITHOUT KEY CACHED LOCALLY @@ -173,72 +174,55 @@ func (f *federator) AuthenticateFederatedRequest(requestedUsername string, r *ht // so we need to authenticate the request properly by dereferencing the remote key transport, err := f.GetTransportForUser(requestedUsername) if err != nil { - return nil, fmt.Errorf("transport err: %s", err) + return nil, false, fmt.Errorf("transport err: %s", err) } // The actual http call to the remote server is made right here in the Dereference function. b, err := transport.Dereference(context.Background(), requestingPublicKeyID) if err != nil { - return nil, fmt.Errorf("error deferencing key %s: %s", requestingPublicKeyID.String(), err) + return nil, false, fmt.Errorf("error deferencing key %s: %s", requestingPublicKeyID.String(), err) } // if the key isn't in the response, we can't authenticate the request requestingPublicKey, err := getPublicKeyFromResponse(context.Background(), b, requestingPublicKeyID) if err != nil { - return nil, fmt.Errorf("error getting key %s from response %s: %s", requestingPublicKeyID.String(), string(b), err) + return nil, false, fmt.Errorf("error getting key %s from response %s: %s", requestingPublicKeyID.String(), string(b), err) } // we should be able to get the actual key embedded in the vocab.W3IDSecurityV1PublicKey pkPemProp := requestingPublicKey.GetW3IDSecurityV1PublicKeyPem() if pkPemProp == nil || !pkPemProp.IsXMLSchemaString() { - return nil, errors.New("publicKeyPem property is not provided or it is not embedded as a value") + return nil, false, errors.New("publicKeyPem property is not provided or it is not embedded as a value") } // and decode the PEM so that we can parse it as a golang public key pubKeyPem := pkPemProp.Get() block, _ := pem.Decode([]byte(pubKeyPem)) if block == nil || block.Type != "PUBLIC KEY" { - return nil, errors.New("could not decode publicKeyPem to PUBLIC KEY pem block type") + return nil, false, errors.New("could not decode publicKeyPem to PUBLIC KEY pem block type") } publicKey, err = x509.ParsePKIXPublicKey(block.Bytes) if err != nil { - return nil, fmt.Errorf("could not parse public key from block bytes: %s", err) + return nil, false, fmt.Errorf("could not parse public key from block bytes: %s", err) } // all good! we just need the URI of the key owner to return pkOwnerProp := requestingPublicKey.GetW3IDSecurityV1Owner() if pkOwnerProp == nil || !pkOwnerProp.IsIRI() { - return nil, errors.New("publicKeyOwner property is not provided or it is not embedded as a value") + return nil, false, errors.New("publicKeyOwner property is not provided or it is not embedded as a value") } pkOwnerURI = pkOwnerProp.GetIRI() } if publicKey == nil { - return nil, errors.New("returned public key was empty") + return nil, false, errors.New("returned public key was empty") } // do the actual authentication here! algo := httpsig.RSA_SHA256 // TODO: make this more robust if err := verifier.Verify(publicKey, algo); err != nil { - return nil, fmt.Errorf("error verifying key %s: %s", requestingPublicKeyID.String(), err) + return nil, false, nil } - return pkOwnerURI, nil -} - -func (f *federator) blockedDomain(host string) (bool, error) { - b := >smodel.DomainBlock{} - err := f.db.GetWhere([]db.Where{{Key: "domain", Value: host, CaseInsensitive: true}}, b) - if err == nil { - // block exists - return true, nil - } - - if _, ok := err.(db.ErrNoEntries); ok { - // there are no entries so there's no block - return false, nil - } - - // there's an actual error - return false, err + return pkOwnerURI, true, nil } diff --git a/internal/federation/dereference.go b/internal/federation/dereference.go index 4b14f4606..20ffa3a8d 100644 --- a/internal/federation/dereference.go +++ b/internal/federation/dereference.go @@ -21,6 +21,10 @@ func (f *federator) DereferenceRemoteAccount(username string, remoteAccountID *u f.startHandshake(username, remoteAccountID) defer f.stopHandshake(username, remoteAccountID) + if blocked, err := f.blockedDomain(remoteAccountID.Host); blocked || err != nil { + return nil, fmt.Errorf("DereferenceRemoteAccount: domain %s is blocked", remoteAccountID.Host) + } + transport, err := f.GetTransportForUser(username) if err != nil { return nil, fmt.Errorf("transport err: %s", err) @@ -66,6 +70,10 @@ func (f *federator) DereferenceRemoteAccount(username string, remoteAccountID *u } func (f *federator) DereferenceRemoteStatus(username string, remoteStatusID *url.URL) (typeutils.Statusable, error) { + if blocked, err := f.blockedDomain(remoteStatusID.Host); blocked || err != nil { + return nil, fmt.Errorf("DereferenceRemoteStatus: domain %s is blocked", remoteStatusID.Host) + } + transport, err := f.GetTransportForUser(username) if err != nil { return nil, fmt.Errorf("transport err: %s", err) @@ -148,6 +156,10 @@ func (f *federator) DereferenceRemoteStatus(username string, remoteStatusID *url } func (f *federator) DereferenceRemoteInstance(username string, remoteInstanceURI *url.URL) (*gtsmodel.Instance, error) { + if blocked, err := f.blockedDomain(remoteInstanceURI.Host); blocked || err != nil { + return nil, fmt.Errorf("DereferenceRemoteInstance: domain %s is blocked", remoteInstanceURI.Host) + } + transport, err := f.GetTransportForUser(username) if err != nil { return nil, fmt.Errorf("transport err: %s", err) @@ -186,6 +198,14 @@ func (f *federator) DereferenceStatusFields(status *gtsmodel.Status, requestingU }) l.Debug("entering function") + statusURI, err := url.Parse(status.URI) + if err != nil { + return fmt.Errorf("DereferenceStatusFields: couldn't parse status URI %s: %s", status.URI, err) + } + if blocked, err := f.blockedDomain(statusURI.Host); blocked || err != nil { + return fmt.Errorf("DereferenceStatusFields: domain %s is blocked", statusURI.Host) + } + t, err := f.GetTransportForUser(requestingUsername) if err != nil { return fmt.Errorf("error creating transport: %s", err) @@ -321,6 +341,14 @@ func (f *federator) DereferenceAccountFields(account *gtsmodel.Account, requesti "requestingUsername": requestingUsername, }) + accountURI, err := url.Parse(account.URI) + if err != nil { + return fmt.Errorf("DereferenceAccountFields: couldn't parse account URI %s: %s", account.URI, err) + } + if blocked, err := f.blockedDomain(accountURI.Host); blocked || err != nil { + return fmt.Errorf("DereferenceAccountFields: domain %s is blocked", accountURI.Host) + } + t, err := f.GetTransportForUser(requestingUsername) if err != nil { return fmt.Errorf("error getting transport for user: %s", err) @@ -342,12 +370,20 @@ func (f *federator) DereferenceAccountFields(account *gtsmodel.Account, requesti func (f *federator) DereferenceAnnounce(announce *gtsmodel.Status, requestingUsername string) error { if announce.GTSBoostedStatus == nil || announce.GTSBoostedStatus.URI == "" { // we can't do anything unfortunately - return errors.New("dereferenceAnnounce: no URI to dereference") + return errors.New("DereferenceAnnounce: no URI to dereference") + } + + boostedStatusURI, err := url.Parse(announce.GTSBoostedStatus.URI) + if err != nil { + return fmt.Errorf("DereferenceAnnounce: couldn't parse boosted status URI %s: %s", announce.GTSBoostedStatus.URI, err) + } + if blocked, err := f.blockedDomain(boostedStatusURI.Host); blocked || err != nil { + return fmt.Errorf("DereferenceAnnounce: domain %s is blocked", boostedStatusURI.Host) } // check if we already have the boosted status in the database boostedStatus := >smodel.Status{} - err := f.db.GetWhere([]db.Where{{Key: "uri", Value: announce.GTSBoostedStatus.URI}}, boostedStatus) + err = f.db.GetWhere([]db.Where{{Key: "uri", Value: announce.GTSBoostedStatus.URI}}, boostedStatus) if err == nil { // nice, we already have it so we don't actually need to dereference it from remote announce.Content = boostedStatus.Content @@ -364,12 +400,7 @@ func (f *federator) DereferenceAnnounce(announce *gtsmodel.Status, requestingUse } // we don't have it so we need to dereference it - remoteStatusURI, err := url.Parse(announce.GTSBoostedStatus.URI) - if err != nil { - return fmt.Errorf("dereferenceAnnounce: error parsing url %s: %s", announce.GTSBoostedStatus.URI, err) - } - - statusable, err := f.DereferenceRemoteStatus(requestingUsername, remoteStatusURI) + statusable, err := f.DereferenceRemoteStatus(requestingUsername, boostedStatusURI) if err != nil { return fmt.Errorf("dereferenceAnnounce: error dereferencing remote status with id %s: %s", announce.GTSBoostedStatus.URI, err) } @@ -460,6 +491,14 @@ func (f *federator) DereferenceAnnounce(announce *gtsmodel.Status, requestingUse // SIDE EFFECTS: remote header and avatar will be stored in local storage, and the database will be updated // to reflect the creation of these new attachments. func (f *federator) fetchHeaderAndAviForAccount(targetAccount *gtsmodel.Account, t transport.Transport, refresh bool) error { + accountURI, err := url.Parse(targetAccount.URI) + if err != nil { + return fmt.Errorf("fetchHeaderAndAviForAccount: couldn't parse account URI %s: %s", targetAccount.URI, err) + } + if blocked, err := f.blockedDomain(accountURI.Host); blocked || err != nil { + return fmt.Errorf("fetchHeaderAndAviForAccount: domain %s is blocked", accountURI.Host) + } + if targetAccount.AvatarRemoteURL != "" && (targetAccount.AvatarMediaAttachmentID == "" || refresh) { a, err := f.mediaHandler.ProcessRemoteHeaderOrAvatar(t, >smodel.MediaAttachment{ RemoteURL: targetAccount.AvatarRemoteURL, diff --git a/internal/federation/federatingprotocol.go b/internal/federation/federatingprotocol.go index bd540af2c..299fcf8f6 100644 --- a/internal/federation/federatingprotocol.go +++ b/internal/federation/federatingprotocol.go @@ -119,10 +119,15 @@ func (f *federator) AuthenticatePostInbox(ctx context.Context, w http.ResponseWr return nil, false, fmt.Errorf("could not fetch requested account with username %s: %s", username, err) } - publicKeyOwnerURI, err := f.AuthenticateFederatedRequest(requestedAccount.Username, r) + publicKeyOwnerURI, authenticated, err := f.AuthenticateFederatedRequest(requestedAccount.Username, r) if err != nil { l.Debugf("request not authenticated: %s", err) - return ctx, false, fmt.Errorf("not authenticated: %s", err) + return ctx, false, err + } + + if !authenticated { + w.WriteHeader(http.StatusForbidden) + return ctx, false, nil } // authentication has passed, so add an instance entry for this instance if it hasn't been done already @@ -230,6 +235,14 @@ func (f *federator) Blocked(ctx context.Context, actorIRIs []*url.URL) (bool, er } for _, uri := range actorIRIs { + blockedDomain, err := f.blockedDomain(uri.Host); + if err != nil { + return false, fmt.Errorf("error checking domain block: %s", err) + } + if blockedDomain { + return true, nil + } + a := >smodel.Account{} if err := f.db.GetWhere([]db.Where{{Key: "uri", Value: uri.String()}}, a); err != nil { _, ok := err.(db.ErrNoEntries) diff --git a/internal/federation/federator.go b/internal/federation/federator.go index 6d16f730b..1a72fa348 100644 --- a/internal/federation/federator.go +++ b/internal/federation/federator.go @@ -42,7 +42,13 @@ type Federator interface { FederatingDB() federatingdb.DB // AuthenticateFederatedRequest can be used to check the authenticity of incoming http-signed requests for federating resources. // The given username will be used to create a transport for making outgoing requests. See the implementation for more detailed comments. - AuthenticateFederatedRequest(username string, r *http.Request) (*url.URL, error) + // + // If the request is valid and passes authentication, the URL of the key owner ID will be returned, as well as true, and nil. + // + // If the request does not pass authentication, or there's a domain block, nil, false, nil will be returned. + // + // If something goes wrong during authentication, nil, false, and an error will be returned. + AuthenticateFederatedRequest(username string, r *http.Request) (*url.URL, bool, error) // FingerRemoteAccount performs a webfinger lookup for a remote account, using the .well-known path. It will return the ActivityPub URI for that // account, or an error if it doesn't exist or can't be retrieved. FingerRemoteAccount(requestingUsername string, targetUsername string, targetDomain string) (*url.URL, error) @@ -97,6 +103,7 @@ func NewFederator(db db.DB, federatingDB federatingdb.DB, transportController tr clock: &Clock{}, typeConverter: typeConverter, transportController: transportController, + mediaHandler: mediaHandler, log: log, handshakeSync: &sync.Mutex{}, } diff --git a/internal/federation/finger.go b/internal/federation/finger.go index 047f8c95a..6c6e9f6dc 100644 --- a/internal/federation/finger.go +++ b/internal/federation/finger.go @@ -30,6 +30,9 @@ import ( ) func (f *federator) FingerRemoteAccount(requestingUsername string, targetUsername string, targetDomain string) (*url.URL, error) { + if blocked, err := f.blockedDomain(targetDomain); blocked || err != nil { + return nil, fmt.Errorf("FingerRemoteAccount: domain %s is blocked", targetDomain) + } t, err := f.GetTransportForUser(requestingUsername) if err != nil { diff --git a/internal/federation/util.go b/internal/federation/util.go new file mode 100644 index 000000000..de8654d32 --- /dev/null +++ b/internal/federation/util.go @@ -0,0 +1,23 @@ +package federation + +import ( + "github.com/superseriousbusiness/gotosocial/internal/db" + "github.com/superseriousbusiness/gotosocial/internal/gtsmodel" +) + +func (f *federator) blockedDomain(host string) (bool, error) { + b := >smodel.DomainBlock{} + err := f.db.GetWhere([]db.Where{{Key: "domain", Value: host, CaseInsensitive: true}}, b) + if err == nil { + // block exists + return true, nil + } + + if _, ok := err.(db.ErrNoEntries); ok { + // there are no entries so there's no block + return false, nil + } + + // there's an actual error + return false, err +} diff --git a/internal/processing/admin/domainblock.go b/internal/processing/admin/domainblock.go index 183930255..0e80ca28c 100644 --- a/internal/processing/admin/domainblock.go +++ b/internal/processing/admin/domainblock.go @@ -35,7 +35,7 @@ func (p *processor) DomainBlockCreate(account *gtsmodel.Account, form *apimodel. domainBlock := >smodel.DomainBlock{} err := p.db.GetWhere([]db.Where{{Key: "domain", Value: form.Domain, CaseInsensitive: true}}, domainBlock) if err != nil { - if _, ok := err.(db.ErrNoEntries); ok { + if _, ok := err.(db.ErrNoEntries); !ok { // something went wrong in the DB return nil, gtserror.NewErrorInternalError(fmt.Errorf("DomainBlockCreate: db error checking for existence of domain block %s: %s", form.Domain, err)) } @@ -130,6 +130,7 @@ selectAccountsLoop: } // an actual error has occurred l.Errorf("domainBlockProcessSideEffects: db error selecting accounts for domain %s: %s", block.Domain, err) + break selectAccountsLoop } for i, a := range accounts { diff --git a/internal/processing/federation.go b/internal/processing/federation.go index 3bcda866b..41e6cf2fe 100644 --- a/internal/processing/federation.go +++ b/internal/processing/federation.go @@ -106,8 +106,8 @@ func (p *processor) GetFediUser(requestedUsername string, request *http.Request) } } else if util.IsUserPath(request.URL) { // if it's a user path, we want to fully authenticate the request before we serve any data, and then we can serve a more complete profile - requestingAccountURI, err := p.federator.AuthenticateFederatedRequest(requestedUsername, request) - if err != nil { + requestingAccountURI, authenticated, err := p.federator.AuthenticateFederatedRequest(requestedUsername, request) + if err != nil || !authenticated { return nil, gtserror.NewErrorNotAuthorized(err) } @@ -152,8 +152,8 @@ func (p *processor) GetFediFollowers(requestedUsername string, request *http.Req } // authenticate the request - requestingAccountURI, err := p.federator.AuthenticateFederatedRequest(requestedUsername, request) - if err != nil { + requestingAccountURI, authenticated, err := p.federator.AuthenticateFederatedRequest(requestedUsername, request) + if err != nil || !authenticated { return nil, gtserror.NewErrorNotAuthorized(err) } @@ -197,8 +197,8 @@ func (p *processor) GetFediFollowing(requestedUsername string, request *http.Req } // authenticate the request - requestingAccountURI, err := p.federator.AuthenticateFederatedRequest(requestedUsername, request) - if err != nil { + requestingAccountURI, authenticated, err := p.federator.AuthenticateFederatedRequest(requestedUsername, request) + if err != nil || !authenticated { return nil, gtserror.NewErrorNotAuthorized(err) } @@ -242,8 +242,8 @@ func (p *processor) GetFediStatus(requestedUsername string, requestedStatusID st } // authenticate the request - requestingAccountURI, err := p.federator.AuthenticateFederatedRequest(requestedUsername, request) - if err != nil { + requestingAccountURI, authenticated, err := p.federator.AuthenticateFederatedRequest(requestedUsername, request) + if err != nil || !authenticated { return nil, gtserror.NewErrorNotAuthorized(err) } @@ -356,6 +356,5 @@ func (p *processor) GetNodeInfo(request *http.Request) (*apimodel.Nodeinfo, gtse func (p *processor) InboxPost(ctx context.Context, w http.ResponseWriter, r *http.Request) (bool, error) { contextWithChannel := context.WithValue(ctx, util.APFromFederatorChanKey, p.fromFederator) - posted, err := p.federator.FederatingActor().PostInbox(contextWithChannel, w, r) - return posted, err + return p.federator.FederatingActor().PostInbox(contextWithChannel, w, r) } diff --git a/internal/visibility/statusvisible.go b/internal/visibility/statusvisible.go index c022be359..6c3b40500 100644 --- a/internal/visibility/statusvisible.go +++ b/internal/visibility/statusvisible.go @@ -2,6 +2,7 @@ package visibility import ( "errors" + "net/url" "fmt" @@ -16,6 +17,15 @@ func (f *filter) StatusVisible(targetStatus *gtsmodel.Status, requestingAccount "statusID": targetStatus.ID, }) + uri, err := url.Parse(targetStatus.URI) + if err != nil { + return false, fmt.Errorf("StatusVisible: error parsing uri: %s", targetStatus.URI) + } + if blocked, err := f.blockedDomain(uri.Host); blocked || err != nil { + l.Debugf("domain %s is blocked", uri.Host) + return blocked, err + } +aaaaaaaaaa relevantAccounts, err := f.pullRelevantAccountsFromStatus(targetStatus) if err != nil { l.Debugf("error pulling relevant accounts for status %s: %s", targetStatus.ID, err) diff --git a/internal/visibility/util.go b/internal/visibility/util.go index f52661d0b..47ced2884 100644 --- a/internal/visibility/util.go +++ b/internal/visibility/util.go @@ -3,6 +3,7 @@ package visibility import ( "fmt" + "github.com/superseriousbusiness/gotosocial/internal/db" "github.com/superseriousbusiness/gotosocial/internal/gtsmodel" ) @@ -79,3 +80,20 @@ type relevantAccounts struct { BoostedReplyToAccount *gtsmodel.Account MentionedAccounts []*gtsmodel.Account } + +func (f *filter) blockedDomain(host string) (bool, error) { + b := >smodel.DomainBlock{} + err := f.db.GetWhere([]db.Where{{Key: "domain", Value: host, CaseInsensitive: true}}, b) + if err == nil { + // block exists + return true, nil + } + + if _, ok := err.(db.ErrNoEntries); ok { + // there are no entries so there's no block + return false, nil + } + + // there's an actual error + return false, err +}