Last active
November 15, 2021 17:33
-
-
Save ariankordi/8e80e2ab21ac9a76165b0c7f773c40a9 to your computer and use it in GitHub Desktop.
rewrite of ForwardNotifierServer for this iphone tweak https://github.com/Greg0109/ForwardNotifier
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package main | |
import ( | |
"github.com/valyala/fasthttp" | |
"github.com/gen2brain/beeep" | |
"encoding/json" | |
"encoding/base64" | |
// to create a reader for the base64 image | |
"strings" | |
// for catching os signals and deleting the temporary directory on exit | |
"os/signal" | |
"syscall" | |
// for creating and removing temporary icon files | |
"io/ioutil" | |
"io" | |
"os" | |
"flag" | |
"log" | |
"fmt" | |
) | |
var ( | |
addr = flag.String("addr", "0.0.0.0:8000", "address to run the server on") | |
logEnabled = flag.Bool("log", false, "log title and truncated message of incoming notifications") | |
dumpEnabled = flag.Bool("dump", false, "dump requests that failed in the temporary directory") | |
// imageDir is the temporary directory where images are stored and it is defined in main | |
imageDir string | |
) | |
// deleteImageDir is called when terminated and when the server fails to listen | |
func deleteImageDir() { | |
log.Println("recursively deleting temporary directory:", imageDir) | |
// i'm scared that the imageDir might be blank and then everything will be deleted | |
if strings.Contains(imageDir, "ForwardNotifierServer") { | |
if err := os.RemoveAll(imageDir); err != nil { | |
log.Println("os.RemoveAll failed:", err) | |
} | |
log.Println("done") | |
} else { | |
log.Println("never mind - the temporary directory does not contain \"ForwardNotifierServer\" - THIS WOULD'VE DELETED EVERYTHING OTHERWISE! WHAT") | |
} | |
} | |
// sometimes the message or image will be set to this for some inexplicable reason | |
const objcNull = "(null)" | |
func main() { | |
// parse command line flags, such as addr | |
flag.Parse() | |
// create temporary image directory in the directory returned by os.TempDir() | |
var err error | |
imageDir, err = os.MkdirTemp("", "ForwardNotifierServer") | |
if err != nil { | |
log.Fatalln("could not create temporary directory:", err) | |
} | |
// listen for signal to exit then delete temporary directory | |
signalChannel := make(chan os.Signal) | |
signal.Notify(signalChannel, syscall.SIGTERM) | |
signal.Notify(signalChannel, syscall.SIGINT) | |
go func() { | |
signalReceived := <-signalChannel | |
log.Println("received signal:", signalReceived) | |
// delete image directory | |
deleteImageDir() | |
// exit with code 0 | |
os.Exit(0) | |
}() | |
// addr is a pointer | |
log.Println("running server on", *addr, "temporary image directory is at:", imageDir) | |
if *logEnabled { | |
log.Println("log is enabled - notifications will be printed with log.Println (usually in stderr)") | |
} | |
if *dumpEnabled { | |
log.Println("dump is also enabled so requests will be dumped at the image directory on certain errors") | |
} | |
// create a custom server mostly for the request body limit | |
server := &fasthttp.Server{ | |
Handler: requestHandler, | |
// limit max request body to 1 mb | |
MaxRequestBodySize: 1000000, | |
// keepalive is not necessary | |
//DisableKeepalive: true, | |
// worth limiting | |
MaxConnsPerIP: 10, | |
// save bandwidth???? | |
NoDefaultContentType: true, | |
NoDefaultDate: true, | |
NoDefaultServerHeader: true, | |
} | |
if err = server.ListenAndServe(*addr); err != nil { | |
deleteImageDir() | |
log.Fatalln("error in fasthttp.ListenAndServe: ", err) | |
} | |
} | |
type forwardNotifierNotification struct { | |
// Title and Message are originally encoded in Base64 | |
Title string `json:"Title"` | |
Message string `json:"Message"` | |
// Image is encoded in Base64 too | |
Image string `json:"img"` | |
AppName string `json:"appname"` | |
} | |
// DecodeTitleAndMessage decodes the fields Title and Message from Base64 encoding. | |
func (r *forwardNotifierNotification) DecodeTitleAndMessage() error { | |
data, err := base64.StdEncoding.DecodeString(r.Title) | |
if err != nil { | |
return fmt.Errorf("could not decode title: %v", err) | |
} | |
r.Title = string(data) | |
data, err = base64.StdEncoding.DecodeString(r.Message) | |
if err != nil { | |
return fmt.Errorf("could not decode message: %v", err) | |
} | |
r.Message = string(data) | |
return nil | |
} | |
// WriteImageToFile decodes the base64 encoded Image string and writes it to a file. | |
func (r *forwardNotifierNotification) WriteImageToFile(imageFile *os.File) error { | |
// make a string reader for the base64 image because base64 decoder wants a reader | |
imageReader := strings.NewReader(r.Image) | |
// make new base64 decoder to decode the image | |
imageDecoder := base64.NewDecoder(base64.StdEncoding, imageReader) | |
// now copy the base64 image decoder to the file!!! | |
_, err := io.Copy(imageFile, imageDecoder) | |
if err != nil { | |
return fmt.Errorf("could not decode and write image: %v", err) | |
} | |
return nil | |
} | |
func dumpRequestBodyToFile(body []byte/**fasthttp.RequestCtx*/) { | |
// ioutil.TempFile just makes a temporary file in the specified directory | |
dumpFile, err := ioutil.TempFile(imageDir, "dump") | |
if err != nil { | |
log.Println("could not create dump file", err) | |
return | |
} | |
/*if _, err = io.Copy(dumpFile, ctx.RequestBodyStream()); err != nil { | |
log.Println("writing dump to file failed:", err) | |
return | |
}*/ | |
if _, err = dumpFile.Write(body); err != nil { | |
log.Println("writing dump to file failed:", err) | |
return | |
} | |
if err = dumpFile.Close(); err != nil { | |
log.Println("dump file could not be closed", err) | |
return | |
} | |
log.Println("created request dump at", dumpFile.Name()) | |
} | |
// just sends notifications from any request received | |
func requestHandler(ctx *fasthttp.RequestCtx) { | |
//fmt.Fprintln(ctx, "hi ><><><") | |
// ignore requests that are not post at /, so basically that are random | |
if !ctx.IsPost() || string(ctx.RequestURI()) != "/" { | |
ctx.Response.Header.SetNoDefaultContentType(false) | |
fmt.Fprintln(ctx, "👋 🥺") | |
return | |
} | |
// make an empty struct for the notification | |
notificationRequest := &forwardNotifierNotification{} | |
// try json decoding request to the struct | |
// this cannot actually be done because the dump function | |
// needs to read the request as well as the decode functions | |
// the dump function needs to run at any given time so it cannot just be | |
// replaced in the case that dump is enabled | |
/* | |
jsonDecoder := json.NewDecoder(ctx.RequestBodyStream()) | |
if err := jsonDecoder.Decode(¬ificationRequest); err != nil { | |
*/ | |
postBody := ctx.PostBody() | |
if err := json.Unmarshal(postBody, ¬ificationRequest); err != nil { | |
if *logEnabled { | |
log.Println("non-json request from", ctx.RemoteAddr()) | |
} | |
if *dumpEnabled { | |
dumpRequestBodyToFile(postBody) | |
} | |
ctx.Error("cannot decode request (it should be in json format!): " + err.Error(), fasthttp.StatusBadRequest) | |
return | |
} | |
// ignore "(null)" as a title because this is usually for non-notifications such as Do Not Disturb | |
if notificationRequest.Title == objcNull { | |
if *logEnabled { | |
log.Println("\"(null)\" as title from", ctx.RemoteAddr()) | |
} | |
if *dumpEnabled { | |
dumpRequestBodyToFile(postBody) | |
} | |
ctx.Error("ignoring this null title", fasthttp.StatusBadRequest) | |
return | |
} | |
// ignore notifications where the message is "(null)" this started happening when i updated to ios 14 for some reason | |
if notificationRequest.Message == objcNull { | |
if *logEnabled { | |
log.Println("\"(null)\" as message from", ctx.RemoteAddr()) | |
} | |
if *dumpEnabled { | |
dumpRequestBodyToFile(postBody) | |
} | |
ctx.Error("null? why is the message an objective-c null? why did you send this? ignoring", fasthttp.StatusBadRequest) | |
return | |
} | |
// try decoding base64 title and message from the notification | |
if err := notificationRequest.DecodeTitleAndMessage(); err != nil { | |
if *logEnabled { | |
log.Println("couldn't decode title and message from", ctx.RemoteAddr()) | |
} | |
if *dumpEnabled { | |
dumpRequestBodyToFile(postBody) | |
} | |
ctx.Error(err.Error(), fasthttp.StatusBadRequest) | |
return | |
} | |
// a file is created from AppName so if there is an attempt to descend the directory here then don't do this request | |
if strings.Contains(notificationRequest.AppName, "./") { | |
if *logEnabled { | |
log.Println("\"./\" in appname from", ctx.RemoteAddr()) | |
} | |
ctx.Error("an app name that has ../ in it? weird..........", fasthttp.StatusBadRequest) | |
return | |
} | |
var imageFileName string | |
// check if the image is not null because sometimes it is for some reason | |
if notificationRequest.Image == objcNull { | |
if *logEnabled { | |
log.Println("\"(null)\" as image, ignoring image from", ctx.RemoteAddr()) | |
} | |
if *dumpEnabled { | |
dumpRequestBodyToFile(postBody) | |
} | |
// just do not process icon at this point, leave it blank | |
imageFileName = "" | |
} else { | |
// save the image to a temporary file so that beeeeeeeeeeeeeeeeep can access it | |
// appname is the filename of the image, so that if it exists then it doesn't have to be created again | |
imageFileName = imageDir + "/" + notificationRequest.AppName + ".png" | |
// os.OpenFile will open a file or create it if it doesn't exist | |
imageFile, err := os.OpenFile(imageFileName, os.O_RDWR|os.O_CREATE, 0666) | |
if err != nil { | |
// panic if the temporary file could not be created | |
log.Panicln("could not create or open temporary file!!!!!!!!!", err) | |
return | |
} | |
// now that the file is open, decode and write the image to it | |
// assuming that the image is formatted correctly | |
if err = notificationRequest.WriteImageToFile(imageFile); err != nil { | |
log.Println("writing image to file failed:", err) | |
ctx.Error(err.Error(), fasthttp.StatusInternalServerError) | |
return | |
} | |
// close the file when it is finished | |
if err = imageFile.Close(); err != nil { | |
log.Panicln("temporary file could not be closed????????", err) | |
} | |
} | |
// now an actual desktop notification can be sent | |
if err := beeep.Notify(notificationRequest.Title, notificationRequest.Message, imageFileName); err != nil { | |
log.Println("beeep.Notify:", err) | |
} | |
// print a log of the message if *logEnabled is enabled, at the very end | |
if *logEnabled { | |
log.Printf("notification from %s: title \"%.20s\", message \"%.20s\", icon path %s\n", ctx.RemoteAddr().String(), notificationRequest.Title, notificationRequest.Message, imageFileName) | |
} | |
// idk if this is truly necessary | |
//fmt.Fprintln(ctx, "Sent!") | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# designed to be a systemd user service! systemctl --user | |
[Unit] | |
Description=ForwardNotifierServer | |
# network online??? lol?????? | |
#Requires=NetworkManager-wait-online.service | |
# check if connected to ethernet? but this is active even when it's not plugged | |
#Requires=sys-subsystem-net-devices-enp0s31f6.device | |
[email protected] | |
[Service] | |
Type=simple | |
# check if ip is correct? need to make that forwardnotifier bridge...... | |
# doesn't work because this service runs before the system comes online | |
#ExecCondition=/bin/sh -c 'ip addr | grep 192.168.2.20' | |
#ExecStart=python3 /usr/local/bin/ForwardNotifierServer.py | |
# new version in go | |
ExecStart=/usr/local/bin/ForwardNotifierServer | |
Environment="DISPLAY=:0" | |
RemainAfterExit=yes | |
# sandboxing | |
CapabilityBoundingSet= | |
DevicePolicy=strict | |
KeyringMode=private | |
LockPersonality=yes | |
MemoryDenyWriteExecute=yes | |
NoNewPrivileges=yes | |
# complains about needing setup or something | |
#PrivateIPC=yes | |
PrivateMounts=yes | |
# breaks ip addr | |
#PrivateNetwork=yes | |
# when this is set, the wrong tmp is specified | |
#PrivateTmp=yes | |
# gives full access to the real tmp | |
ReadWritePaths=/tmp | |
PrivateUsers=yes | |
ProtectClock=yes | |
ProtectControlGroups=yes | |
# notification sending won't work without this | |
#ProtectHome=yes | |
InaccessiblePaths=-/home -/root | |
# actually just needs to connect to socket /run/user/x/bus | |
ProtectHostname=yes | |
ProtectKernelModules=yes | |
ProtectKernelTunables=yes | |
ProtectKernelLogs=yes | |
ProtectProc=invisible | |
ProcSubset=pid | |
ProtectSystem=strict | |
# af_netlink is needed for ip addr.. af_unix might be necessary to talk to dbus | |
#RestrictAddressFamilies=AF_INET AF_NETLINK AF_UNIX | |
RestrictAddressFamilies=AF_INET AF_UNIX | |
RestrictNamespaces=yes | |
RestrictRealtime=yes | |
RestrictSUIDSGID=yes | |
SystemCallArchitectures=native | |
#SystemCallFilter=@system-service | |
SystemCallFilter=@basic-io | |
SystemCallErrorNumber=EPERM | |
#IPAddressAllow=192.168.2.0/24 | |
#IPAddressDeny=any | |
UMask=0066 | |
[Install] | |
WantedBy=graphical-session.target |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment