Skip to content

Commit

Permalink
feat: add MIME type filter
Browse files Browse the repository at this point in the history
  • Loading branch information
cyb3rko committed Dec 21, 2024
1 parent d5e8811 commit 1b59f9e
Show file tree
Hide file tree
Showing 8 changed files with 146 additions and 3 deletions.
1 change: 1 addition & 0 deletions .env_template
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,4 @@ GUARDIAN_VIRUS_TOTAL_KEY=
GUARDIAN_URL_FILTER=true
GUARDIAN_URL_CHECK_VIRUS_TOTAL=false
GUARDIAN_URL_CHECK_FISHFISH=false
GUARDIAN_MIME_FILTER=true
18 changes: 15 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
- [URL Phishing Check 🗡️](#url-phishing-check-)
- [VirusTotal](#virustotal)
- [FishFish](#fishfish)
- [planned] *File Type Filter* 📎
a - [File MIME Type Filter 📎](#file-mime-type-filter-)
- [planned] *File Virus Scan* 🦠
- [planned] *Keyword Filter* 📄
- [Protected Public Rooms (Mentions)](#protected-public-rooms-mentions)
Expand All @@ -27,8 +27,8 @@
Guardian supports URL filtering based on a customizable domain list.

**Examples**:
- `!gd block t.me`
- `!gd unblock t.me`
- `!gd url block t.me`
- `!gd url unblock t.me`

### URL Phishing Check 🗡

Expand All @@ -52,6 +52,18 @@ Guardian rates a URL "suspicious" if the statistics `malicious` and `suspicious`
FishFish allows scanning a domain and returning a rating, if found in their reports.
Guardian rates a URL "suspicious" if the FishFish rating is `malware` or `phishing` rather than `safe`.

### File MIME Type Filter 📎

**Activation (default: true)**: `GUARDIAN_MIME_FILTER: true|false`
**Help Command**: `!gd mime`

Guardian supports file MIME type filtering based on a customizable MIME type list.

**Examples**:
- `!gd mime block application/zip`
- `!gd mime unblock application/zip`
- `!gd mime list`

## Protected Public Rooms (Mentions)

This list showcases some of the rooms who use the Matrix Guardian 🛡️:
Expand Down
1 change: 1 addition & 0 deletions config.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ type Config struct {
useUrlFilter bool // "GUARDIAN_URL_FILTER", default: true
useUrlCheckVt bool // "GUARDIAN_URL_CHECK_VIRUS_TOTAL", default: false
useUrlCheckFf bool // "GUARDIAN_URL_CHECK_FISHFISH", default: false
useMimeFilter bool // "GUARDIAN_MIME_FILTER", default: true
}

func CheckForDefaultConfig(username string, password string) {
Expand Down
45 changes: 45 additions & 0 deletions db/database.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,22 @@ package db

import (
"database/sql"
"fmt"
_ "github.com/mattn/go-sqlite3"
)

type table struct {
name, values string
}

type mimetype struct {
name string
count int
}

var tables = []table{
{"domains", "name TEXT PRIMARY KEY, count INT"},
{"mimetypes", "name TEXT PRIMARY KEY, count INT"},
{"attributes", "key TEXT PRIMARY KEY, value TEXT"},
}

Expand Down Expand Up @@ -40,6 +47,20 @@ func IsDomainBlocked(db *sql.DB, domain string) bool {
return true
}

func IsMimeBlocked(db *sql.DB, mime string) bool {
query := db.QueryRow("SELECT count FROM mimetypes WHERE name = ?", mime)
var count int
err := query.Scan(&count)
if err != nil {
// not found in database, implicitly allowed
return false
}
// update usage counter
_, _ = db.Exec("UPDATE mimetypes SET count = ? WHERE name = ?", count+1, mime)
// found in database, explicitly blocked
return true
}

func BlockDomain(db *sql.DB, domain string) bool {
_, err := db.Exec("INSERT INTO domains (name, count) values (?, 0)", domain)
return err == nil
Expand All @@ -50,6 +71,30 @@ func UnblockDomain(db *sql.DB, domain string) bool {
return err == nil
}

func BlockMime(db *sql.DB, mime string) bool {
_, err := db.Exec("INSERT INTO mimetypes (name, count) values (?, 0)", mime)
return err == nil
}

func UnblockMime(db *sql.DB, mime string) bool {
_, err := db.Exec("DELETE FROM mimetypes WHERE name = ?", mime)
return err == nil
}

func ListMimes(db *sql.DB) ([]string, error) {
query, err := db.Query("SELECT name, count FROM mimetypes ORDER BY count DESC")
if err != nil {
return nil, err
}
var rows []string
for query.Next() {
var row mimetype
_ = query.Scan(&row.name, &row.count)
rows = append(rows, fmt.Sprintf("- %s (%d)", row.name, row.count))
}
return rows, nil
}

func createAllTables(db *sql.DB) {
for _, tab := range tables {
createTable(db, tab.name, tab.values)
Expand Down
1 change: 1 addition & 0 deletions docker-compose-dev.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ services:
GUARDIAN_URL_FILTER: ${GUARDIAN_URL_FILTER}
GUARDIAN_URL_CHECK_VIRUS_TOTAL: ${GUARDIAN_URL_CHECK_VIRUS_TOTAL}
GUARDIAN_URL_CHECK_FISHFISH: ${GUARDIAN_URL_CHECK_FISHFISH}
GUARDIAN_MIME_FILTER: ${GUARDIAN_MIME_FILTER}

volumes:
guardian-data:
1 change: 1 addition & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ services:
GUARDIAN_URL_FILTER: true
GUARDIAN_URL_CHECK_VIRUS_TOTAL: false
GUARDIAN_URL_CHECK_FISHFISH: false
GUARDIAN_MIME_FILTER: true

volumes:
guardian-data:
11 changes: 11 additions & 0 deletions help.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,13 @@ const urlHelp = "🛡️ <b>Guardian Help Page [url]</b> 🛡️:<br/>" +
"<code>block <<domain>></code>: <i>Block domain in messages</i><br/>" +
"<code>unblock <<domain>></code>: <i>Unblock domain in messages</i>"

const mimeHelp = "🛡️ <b>Guardian Help Page [mime]</b> 🛡️:<br/>" +
"<code>!gd mime <<args>></code><br/><br/>" +
"<b>Arguments</b>:<br/>" +
"<code>block <<mimetype>></code>: <i>Block MIME type in messages</i><br/>" +
"<code>unblock <<mimetype>></code>: <i>Unblock MIME type in messages</i><br/>" +
"<code>list</code>: <i>List blocked MIME type in messages</i>"

func getRawMessage(source string) string {
source = strings.ReplaceAll(source, "<b>", "")
source = strings.ReplaceAll(source, "</b>", "")
Expand Down Expand Up @@ -59,3 +66,7 @@ func ShowHelp(client *mautrix.Client, ctx context.Context, mngtRoomId id.RoomID)
func ShowUrlHelp(client *mautrix.Client, ctx context.Context, mngtRoomId id.RoomID) {
sendHtmlMessage(client, ctx, mngtRoomId, urlHelp)
}

func ShowMimeHelp(client *mautrix.Client, ctx context.Context, mngtRoomId id.RoomID) {
sendHtmlMessage(client, ctx, mngtRoomId, mimeHelp)
}
71 changes: 71 additions & 0 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import (
"maunium.net/go/mautrix/id"
"os"
"regexp"
"strings"
"time"
)

Expand Down Expand Up @@ -132,6 +133,38 @@ func onManagementMessage(client *mautrix.Client, ctx context.Context, evt *event
}
}
ShowUrlHelp(client, ctx, config.mngtRoomId)
case "mime":
if len(subcommands) > 0 {
switch subcommands[0] {
case "block":
if len(subcommands) == 2 {
db.BlockMime(database, subcommands[1])
return
}
case "unblock":
if len(subcommands) == 2 {
db.UnblockMime(database, subcommands[1])
return
}
case "list":
if len(subcommands) == 1 {
list, err := db.ListMimes(database)
if err != nil {
return
}
message := fmt.Sprintf(
"Configured MIME types to block:\n%s",
strings.Join(list, "\n"),
)
_, err = client.SendNotice(ctx, evt.RoomID, message)
if err != nil {
return
}
return
}
}
}
ShowMimeHelp(client, ctx, config.mngtRoomId)
default:
ShowHelp(client, ctx, config.mngtRoomId)
}
Expand All @@ -140,6 +173,8 @@ func onManagementMessage(client *mautrix.Client, ctx context.Context, evt *event

func onProtectedRoomMessage(client *mautrix.Client, ctx context.Context, evt *event.Event) {
const keyMessageType = "msgtype"
const keyInfo = "info"
const keyMimetype = "mimetype"

contentJson, err := json.Marshal(evt.Content.Parsed)
var contentParsed map[string]interface{}
Expand All @@ -149,6 +184,7 @@ func onProtectedRoomMessage(client *mautrix.Client, ctx context.Context, evt *ev
}
messageType := contentParsed[keyMessageType]
if messageType == "m.text" || messageType == "m.notice" || messageType == "m.emote" {
// text message
if !config.useUrlFilter && !config.useUrlCheckVt && !config.useUrlCheckFf {
return
}
Expand Down Expand Up @@ -182,6 +218,35 @@ func onProtectedRoomMessage(client *mautrix.Client, ctx context.Context, evt *ev
if err != nil {
return
}
} else {
// file message
if !config.useMimeFilter { // TODO check for file scan activation
return
}
messageInfo := contentParsed[keyInfo]
if messageInfo == nil {
return
}
mimetype := messageInfo.(map[string]interface{})[keyMimetype].(string)
if mimetype == "" {
return
}
if config.useMimeFilter && db.IsMimeBlocked(database, mimetype) {
redactMessage(client, ctx, evt, "found blocklisted mimetype")
return
}
if config.hiddenMode {
return
}
if strings.HasPrefix(mimetype, "image/") {
// don't show reaction in hidden mode and for images
_ = client.SendReceipt(ctx, evt.RoomID, evt.ID, event.ReceiptTypeRead, nil)
return
}
_, err := client.SendReaction(ctx, evt.RoomID, evt.ID, "🛡️")
if err != nil {
return
}
}
}

Expand Down Expand Up @@ -272,12 +337,14 @@ func readConfig() Config {
useUrlFilter := util.GetEnv("GUARDIAN_URL_FILTER", true, true)
useUrlCheckVt := util.GetEnv("GUARDIAN_URL_CHECK_VIRUS_TOTAL", true, true)
useUrlCheckFf := util.GetEnv("GUARDIAN_URL_CHECK_FISHFISH", true, true)
useMimeFilter := util.GetEnv("GUARDIAN_MIME_FILTER", true, true)
mngtRoomReportsBool := true
testModeBool := false
hiddenModeBool := false
useUrlFilterBool := true
useUrlCheckVtBool := false
useUrlCheckFfBool := false
useMimeFilterBool := true

// REQUIRED //
if !validation.IsValidUrl(homeserver) {
Expand Down Expand Up @@ -327,6 +394,9 @@ func readConfig() Config {
if useUrlCheckFf == "true" {
useUrlCheckFfBool = true
}
if useMimeFilter == "false" {
useMimeFilterBool = false
}

config = Config{
// REQUIRED //
Expand All @@ -342,6 +412,7 @@ func readConfig() Config {
useUrlFilter: useUrlFilterBool,
useUrlCheckVt: useUrlCheckVtBool,
useUrlCheckFf: useUrlCheckFfBool,
useMimeFilter: useMimeFilterBool,
}
return config
}

0 comments on commit 1b59f9e

Please sign in to comment.