Initial commit after restructure

This commit is contained in:
Daniel
2018-08-13 14:14:27 +02:00
commit bdeddc41f9
177 changed files with 26108 additions and 0 deletions

21
process/doc.go Normal file
View File

@@ -0,0 +1,21 @@
// Copyright Safing ICS Technologies GmbH. Use of this source code is governed by the AGPL license that can be found in the LICENSE file.
/*
Profiles
Profiles describe the network behaviour
Profiles are found in 3 different paths:
- /Me/Profiles/: Profiles used for this system
- /Data/Profiles/: Profiles supplied by Safing
- /Company/Profiles/: Profiles supplied by the company
When a program wants to use the network for the first time, Safing first searches for a Profile in the Company namespace, then in the Data namespace. If neither is found, it searches for a default profile in the same order.
Default profiles are profiles with a path ending with a "/". The default profile with the longest matching path is chosen.
*/
package process

74
process/fileinfo.go Normal file
View File

@@ -0,0 +1,74 @@
// Copyright Safing ICS Technologies GmbH. Use of this source code is governed by the AGPL license that can be found in the LICENSE file.
package process
import (
"github.com/Safing/safing-core/database"
"strings"
"time"
datastore "github.com/ipfs/go-datastore"
)
// ExecutableSignature stores a signature of an executable.
type ExecutableSignature []byte
// FileInfo stores (security) information about a file.
type FileInfo struct {
database.Base
HumanName string
Owners []string
ApproxLastSeen int64
Signature *ExecutableSignature
}
var fileInfoModel *FileInfo // only use this as parameter for database.EnsureModel-like functions
func init() {
database.RegisterModel(fileInfoModel, func() database.Model { return new(FileInfo) })
}
// Create saves FileInfo with the provided name in the default namespace.
func (m *FileInfo) Create(name string) error {
return m.CreateObject(&database.FileInfoCache, name, m)
}
// CreateInNamespace saves FileInfo with the provided name in the provided namespace.
func (m *FileInfo) CreateInNamespace(namespace *datastore.Key, name string) error {
return m.CreateObject(namespace, name, m)
}
// Save saves FileInfo.
func (m *FileInfo) Save() error {
return m.SaveObject(m)
}
// getFileInfo fetches FileInfo with the provided name from the default namespace.
func getFileInfo(name string) (*FileInfo, error) {
return getFileInfoFromNamespace(&database.FileInfoCache, name)
}
// getFileInfoFromNamespace fetches FileInfo with the provided name from the provided namespace.
func getFileInfoFromNamespace(namespace *datastore.Key, name string) (*FileInfo, error) {
object, err := database.GetAndEnsureModel(namespace, name, fileInfoModel)
if err != nil {
return nil, err
}
model, ok := object.(*FileInfo)
if !ok {
return nil, database.NewMismatchError(object, fileInfoModel)
}
return model, nil
}
// GetFileInfo gathers information about a file and returns *FileInfo
func GetFileInfo(path string) *FileInfo {
// TODO: actually get file information
// TODO: try to load from DB
// TODO: save to DB (key: hash of some sorts)
splittedPath := strings.Split("/", path)
return &FileInfo{
HumanName: splittedPath[len(splittedPath)-1],
ApproxLastSeen: time.Now().Unix(),
}
}

148
process/find.go Normal file
View File

@@ -0,0 +1,148 @@
package process
import (
"errors"
"net"
"github.com/Safing/safing-core/network/packet"
)
var (
ErrConnectionNotFound = errors.New("could not find connection")
ErrProcessNotFound = errors.New("could not find process")
)
func GetPidByPacket(pkt packet.Packet) (pid int, direction bool, err error) {
var localIP net.IP
var localPort uint16
var remoteIP net.IP
var remotePort uint16
if pkt.IsInbound() {
localIP = pkt.GetIPHeader().Dst
remoteIP = pkt.GetIPHeader().Src
} else {
localIP = pkt.GetIPHeader().Src
remoteIP = pkt.GetIPHeader().Dst
}
if pkt.GetIPHeader().Protocol == packet.TCP || pkt.GetIPHeader().Protocol == packet.UDP {
if pkt.IsInbound() {
localPort = pkt.GetTCPUDPHeader().DstPort
remotePort = pkt.GetTCPUDPHeader().SrcPort
} else {
localPort = pkt.GetTCPUDPHeader().SrcPort
remotePort = pkt.GetTCPUDPHeader().DstPort
}
}
switch {
case pkt.GetIPHeader().Protocol == packet.TCP && pkt.IPVersion() == packet.IPv4:
return getTCP4PacketInfo(localIP, localPort, remoteIP, remotePort, pkt.IsInbound())
case pkt.GetIPHeader().Protocol == packet.UDP && pkt.IPVersion() == packet.IPv4:
return getUDP4PacketInfo(localIP, localPort, remoteIP, remotePort, pkt.IsInbound())
case pkt.GetIPHeader().Protocol == packet.TCP && pkt.IPVersion() == packet.IPv6:
return getTCP6PacketInfo(localIP, localPort, remoteIP, remotePort, pkt.IsInbound())
case pkt.GetIPHeader().Protocol == packet.UDP && pkt.IPVersion() == packet.IPv6:
return getUDP6PacketInfo(localIP, localPort, remoteIP, remotePort, pkt.IsInbound())
default:
return -1, false, errors.New("unsupported protocol for finding process")
}
}
func GetProcessByPacket(pkt packet.Packet) (process *Process, direction bool, err error) {
var pid int
pid, direction, err = GetPidByPacket(pkt)
if pid < 0 {
return nil, direction, ErrConnectionNotFound
}
if err != nil {
return nil, direction, err
}
process, err = GetOrFindProcess(pid)
if err != nil {
return nil, direction, err
}
return process, direction, nil
}
func GetPidByEndpoints(localIP net.IP, localPort uint16, remoteIP net.IP, remotePort uint16, protocol packet.IPProtocol) (pid int, direction bool, err error) {
ipVersion := packet.IPv4
if v4 := localIP.To4(); v4 == nil {
ipVersion = packet.IPv6
}
switch {
case protocol == packet.TCP && ipVersion == packet.IPv4:
return getTCP4PacketInfo(localIP, localPort, remoteIP, remotePort, false)
case protocol == packet.UDP && ipVersion == packet.IPv4:
return getUDP4PacketInfo(localIP, localPort, remoteIP, remotePort, false)
case protocol == packet.TCP && ipVersion == packet.IPv6:
return getTCP6PacketInfo(localIP, localPort, remoteIP, remotePort, false)
case protocol == packet.UDP && ipVersion == packet.IPv6:
return getUDP6PacketInfo(localIP, localPort, remoteIP, remotePort, false)
default:
return -1, false, errors.New("unsupported protocol for finding process")
}
}
func GetProcessByEndpoints(localIP net.IP, localPort uint16, remoteIP net.IP, remotePort uint16, protocol packet.IPProtocol) (process *Process, err error) {
var pid int
pid, _, err = GetPidByEndpoints(localIP, localPort, remoteIP, remotePort, protocol)
if err != nil {
return nil, err
}
if pid < 0 {
return nil, ErrConnectionNotFound
}
process, err = GetOrFindProcess(pid)
if err != nil {
return nil, err
}
return process, nil
}
func GetActiveConnectionIDs() []string {
return getActiveConnectionIDs()
}
// func GetProcessByPid(pid int) *Process {
// process, err := GetOrFindProcess(pid)
// if err != nil {
// log.Warningf("process: failed to get process %d: %s", pid, err)
// return nil
// }
// return process
// }
// func GetProcessOfConnection(localIP *net.IP, localPort uint16, protocol uint8) (process *Process, status uint8) {
// pid, status := GetPidOfConnection(localIP, localPort, protocol)
// if status == Success {
// process = GetProcessByPid(pid)
// if process == nil {
// return nil, NoProcessInfo
// }
// }
// return
// }
// func GetProcessByPacket(pkt packet.Packet) (process *Process, direction bool, status uint8) {
// pid, direction, status := GetPidByPacket(pkt)
// if status == Success {
// process = GetProcessByPid(pid)
// if process == nil {
// return nil, direction, NoProcessInfo
// }
// }
// return
// }

11
process/getpid_linux.go Normal file
View File

@@ -0,0 +1,11 @@
package process
import "github.com/Safing/safing-core/process/proc"
var (
getTCP4PacketInfo = proc.GetTCP4PacketInfo
getTCP6PacketInfo = proc.GetTCP6PacketInfo
getUDP4PacketInfo = proc.GetUDP4PacketInfo
getUDP6PacketInfo = proc.GetUDP6PacketInfo
getActiveConnectionIDs = proc.GetActiveConnectionIDs
)

13
process/getpid_windows.go Normal file
View File

@@ -0,0 +1,13 @@
package process
import (
"github.com/Safing/safing-core/process/iphelper"
)
var (
getTCP4PacketInfo = iphelper.GetTCP4PacketInfo
getTCP6PacketInfo = iphelper.GetTCP6PacketInfo
getUDP4PacketInfo = iphelper.GetUDP4PacketInfo
getUDP6PacketInfo = iphelper.GetUDP6PacketInfo
getActiveConnectionIDs = iphelper.GetActiveConnectionIDs
)

220
process/iphelper/get.go Normal file
View File

@@ -0,0 +1,220 @@
// +build windows
package iphelper
import (
"fmt"
"net"
"sync"
)
var (
tcp4Connections []*connectionEntry
tcp4Listeners []*connectionEntry
tcp6Connections []*connectionEntry
tcp6Listeners []*connectionEntry
udp4Connections []*connectionEntry
udp4Listeners []*connectionEntry
udp6Connections []*connectionEntry
udp6Listeners []*connectionEntry
ipHelper *IPHelper
lock sync.RWMutex
)
func checkIPHelper() (err error) {
if ipHelper == nil {
ipHelper, err = New()
return err
}
return nil
}
func GetTCP4PacketInfo(localIP net.IP, localPort uint16, remoteIP net.IP, remotePort uint16, pktDirection bool) (pid int, direction bool, err error) {
// search
pid, direction = search(tcp4Connections, tcp4Listeners, localIP, remoteIP, localPort, remotePort, pktDirection)
if pid >= 0 {
return
}
// if unable to find, refresh
lock.Lock()
err = checkIPHelper()
if err == nil {
tcp4Connections, tcp4Listeners, err = ipHelper.GetTables(TCP, IPv4)
}
lock.Unlock()
if err != nil {
return -1, direction, err
}
// search
pid, direction = search(tcp4Connections, tcp4Listeners, localIP, remoteIP, localPort, remotePort, pktDirection)
if pid >= 0 {
return
}
return -1, direction, nil
}
func GetTCP6PacketInfo(localIP net.IP, localPort uint16, remoteIP net.IP, remotePort uint16, pktDirection bool) (pid int, direction bool, err error) {
// search
pid, direction = search(tcp6Connections, tcp6Listeners, localIP, remoteIP, localPort, remotePort, pktDirection)
if pid >= 0 {
return
}
// if unable to find, refresh
lock.Lock()
err = checkIPHelper()
if err == nil {
tcp6Connections, tcp6Listeners, err = ipHelper.GetTables(TCP, IPv6)
}
lock.Unlock()
if err != nil {
return -1, direction, err
}
// search
pid, direction = search(tcp6Connections, tcp6Listeners, localIP, remoteIP, localPort, remotePort, pktDirection)
if pid >= 0 {
return
}
return -1, direction, nil
}
func GetUDP4PacketInfo(localIP net.IP, localPort uint16, remoteIP net.IP, remotePort uint16, pktDirection bool) (pid int, direction bool, err error) {
// search
pid, _ = search(udp4Connections, udp4Listeners, localIP, remoteIP, localPort, remotePort, pktDirection)
if pid >= 0 {
return pid, pktDirection, nil
}
// if unable to find, refresh
lock.Lock()
err = checkIPHelper()
if err == nil {
udp4Connections, udp4Listeners, err = ipHelper.GetTables(UDP, IPv4)
}
lock.Unlock()
if err != nil {
return -1, pktDirection, err
}
// search
pid, _ = search(udp4Connections, udp4Listeners, localIP, remoteIP, localPort, remotePort, pktDirection)
if pid >= 0 {
return pid, pktDirection, nil
}
return -1, pktDirection, nil
}
func GetUDP6PacketInfo(localIP net.IP, localPort uint16, remoteIP net.IP, remotePort uint16, pktDirection bool) (pid int, direction bool, err error) {
// search
pid, _ = search(udp6Connections, udp6Listeners, localIP, remoteIP, localPort, remotePort, pktDirection)
if pid >= 0 {
return pid, pktDirection, nil
}
// if unable to find, refresh
lock.Lock()
err = checkIPHelper()
if err == nil {
udp6Connections, udp6Listeners, err = ipHelper.GetTables(UDP, IPv6)
}
lock.Unlock()
if err != nil {
return -1, pktDirection, err
}
// search
pid, _ = search(udp6Connections, udp6Listeners, localIP, remoteIP, localPort, remotePort, pktDirection)
if pid >= 0 {
return pid, pktDirection, nil
}
return -1, pktDirection, nil
}
func search(connections, listeners []*connectionEntry, localIP, remoteIP net.IP, localPort, remotePort uint16, pktDirection bool) (pid int, direction bool) {
lock.RLock()
defer lock.RUnlock()
if pktDirection {
// inbound
pid = searchListeners(listeners, localIP, localPort)
if pid >= 0 {
return pid, true
}
pid = searchConnections(connections, localIP, remoteIP, localPort, remotePort)
if pid >= 0 {
return pid, false
}
} else {
// outbound
pid = searchConnections(connections, localIP, remoteIP, localPort, remotePort)
if pid >= 0 {
return pid, false
}
pid = searchListeners(listeners, localIP, localPort)
if pid >= 0 {
return pid, true
}
}
return -1, pktDirection
}
func searchConnections(list []*connectionEntry, localIP, remoteIP net.IP, localPort, remotePort uint16) (pid int) {
for _, entry := range list {
if localPort == entry.localPort &&
remotePort == entry.remotePort &&
remoteIP.Equal(entry.remoteIP) &&
localIP.Equal(entry.localIP) {
return entry.pid
}
}
return -1
}
func searchListeners(list []*connectionEntry, localIP net.IP, localPort uint16) (pid int) {
for _, entry := range list {
if localPort == entry.localPort &&
entry.localIP == nil || // nil IP means zero IP, see tables.go
localIP.Equal(entry.localIP) {
return entry.pid
}
}
return -1
}
func GetActiveConnectionIDs() (connections []string) {
lock.Lock()
defer lock.Unlock()
for _, entry := range tcp4Connections {
connections = append(connections, fmt.Sprintf("%d-%s-%d-%s-%d", TCP, entry.localIP, entry.localPort, entry.remoteIP, entry.remotePort))
}
for _, entry := range tcp6Connections {
connections = append(connections, fmt.Sprintf("%d-%s-%d-%s-%d", TCP, entry.localIP, entry.localPort, entry.remoteIP, entry.remotePort))
}
for _, entry := range udp4Connections {
connections = append(connections, fmt.Sprintf("%d-%s-%d-%s-%d", UDP, entry.localIP, entry.localPort, entry.remoteIP, entry.remotePort))
}
for _, entry := range udp6Connections {
connections = append(connections, fmt.Sprintf("%d-%s-%d-%s-%d", UDP, entry.localIP, entry.localPort, entry.remoteIP, entry.remotePort))
}
return
}

View File

@@ -0,0 +1,77 @@
// +build windows
package iphelper
import (
"errors"
"fmt"
"github.com/tevino/abool"
"golang.org/x/sys/windows"
)
var (
errInvalid = errors.New("IPHelper not initialzed or broken")
)
type IPHelper struct {
dll *windows.LazyDLL
getExtendedTcpTable *windows.LazyProc
getExtendedUdpTable *windows.LazyProc
// getOwnerModuleFromTcpEntry *windows.LazyProc
// getOwnerModuleFromTcp6Entry *windows.LazyProc
// getOwnerModuleFromUdpEntry *windows.LazyProc
// getOwnerModuleFromUdp6Entry *windows.LazyProc
valid *abool.AtomicBool
}
func New() (*IPHelper, error) {
new := &IPHelper{}
new.valid = abool.NewBool(false)
var err error
// load dll
new.dll = windows.NewLazySystemDLL("iphlpapi.dll")
new.dll.Load()
if err != nil {
return nil, err
}
// load functions
new.getExtendedTcpTable = new.dll.NewProc("GetExtendedTcpTable")
err = new.getExtendedTcpTable.Find()
if err != nil {
return nil, fmt.Errorf("could find proc GetExtendedTcpTable: %s", err)
}
new.getExtendedUdpTable = new.dll.NewProc("GetExtendedUdpTable")
err = new.getExtendedUdpTable.Find()
if err != nil {
return nil, fmt.Errorf("could find proc GetExtendedUdpTable: %s", err)
}
// new.getOwnerModuleFromTcpEntry = new.dll.NewProc("GetOwnerModuleFromTcpEntry")
// err = new.getOwnerModuleFromTcpEntry.Find()
// if err != nil {
// return nil, fmt.Errorf("could find proc GetOwnerModuleFromTcpEntry: %s", err)
// }
// new.getOwnerModuleFromTcp6Entry = new.dll.NewProc("GetOwnerModuleFromTcp6Entry")
// err = new.getOwnerModuleFromTcp6Entry.Find()
// if err != nil {
// return nil, fmt.Errorf("could find proc GetOwnerModuleFromTcp6Entry: %s", err)
// }
// new.getOwnerModuleFromUdpEntry = new.dll.NewProc("GetOwnerModuleFromUdpEntry")
// err = new.getOwnerModuleFromUdpEntry.Find()
// if err != nil {
// return nil, fmt.Errorf("could find proc GetOwnerModuleFromUdpEntry: %s", err)
// }
// new.getOwnerModuleFromUdp6Entry = new.dll.NewProc("GetOwnerModuleFromUdp6Entry")
// err = new.getOwnerModuleFromUdp6Entry.Find()
// if err != nil {
// return nil, fmt.Errorf("could find proc GetOwnerModuleFromUdp6Entry: %s", err)
// }
new.valid.Set()
return new, nil
}

263
process/iphelper/tables.go Normal file
View File

@@ -0,0 +1,263 @@
// +build windows
package iphelper
import (
"encoding/binary"
"errors"
"fmt"
"net"
"unsafe"
"golang.org/x/sys/windows"
)
const (
iphelper_TCP_TABLE_OWNER_PID_ALL uintptr = 5
iphelper_UDP_TABLE_OWNER_PID uintptr = 1
iphelper_TCP_STATE_LISTEN uint32 = 2
)
type connectionEntry struct {
localIP net.IP
remoteIP net.IP
localPort uint16
remotePort uint16
pid int
}
func (entry *connectionEntry) String() string {
return fmt.Sprintf("PID=%d %s:%d <> %s:%d", entry.pid, entry.localIP, entry.localPort, entry.remoteIP, entry.remotePort)
}
type iphelperTcpTable struct {
// docs: https://msdn.microsoft.com/en-us/library/windows/desktop/aa366921(v=vs.85).aspx
numEntries uint32
table [4096]iphelperTcpRow
}
type iphelperTcpRow struct {
// docs: https://msdn.microsoft.com/en-us/library/windows/desktop/aa366913(v=vs.85).aspx
state uint32
localAddr uint32
localPort uint32
remoteAddr uint32
remotePort uint32
owningPid uint32
}
type iphelperTcp6Table struct {
// docs: https://msdn.microsoft.com/en-us/library/windows/desktop/aa366905(v=vs.85).aspx
numEntries uint32
table [4096]iphelperTcp6Row
}
type iphelperTcp6Row struct {
// docs: https://msdn.microsoft.com/en-us/library/windows/desktop/aa366896(v=vs.85).aspx
localAddr [16]byte
localScopeId uint32
localPort uint32
remoteAddr [16]byte
remoteScopeId uint32
remotePort uint32
state uint32
owningPid uint32
}
type iphelperUdpTable struct {
// docs: https://msdn.microsoft.com/en-us/library/windows/desktop/aa366932(v=vs.85).aspx
numEntries uint32
table [4096]iphelperUdpRow
}
type iphelperUdpRow struct {
// docs: https://msdn.microsoft.com/en-us/library/windows/desktop/aa366928(v=vs.85).aspx
localAddr uint32
localPort uint32
owningPid uint32
}
type iphelperUdp6Table struct {
// docs: https://msdn.microsoft.com/en-us/library/windows/desktop/aa366925(v=vs.85).aspx
numEntries uint32
table [4096]iphelperUdp6Row
}
type iphelperUdp6Row struct {
// docs: https://msdn.microsoft.com/en-us/library/windows/desktop/aa366923(v=vs.85).aspx
localAddr [16]byte
localScopeId uint32
localPort uint32
owningPid uint32
}
const (
IPv4 uint8 = 4
IPv6 uint8 = 6
TCP uint8 = 6
UDP uint8 = 17
)
func (ipHelper *IPHelper) GetTables(protocol uint8, ipVersion uint8) (connections []*connectionEntry, listeners []*connectionEntry, err error) {
// docs: https://msdn.microsoft.com/en-us/library/windows/desktop/aa365928(v=vs.85).aspx
if !ipHelper.valid.IsSet() {
return nil, nil, errInvalid
}
var afClass int
switch ipVersion {
case IPv4:
afClass = windows.AF_INET
case IPv6:
afClass = windows.AF_INET6
default:
return nil, nil, errors.New("invalid protocol")
}
bufSize := 4096
buf := make([]byte, bufSize)
var r1 uintptr
switch protocol {
case TCP:
r1, _, err = ipHelper.getExtendedTcpTable.Call(
uintptr(unsafe.Pointer(&buf[0])), // _Out_ PVOID pTcpTable
uintptr(unsafe.Pointer(&bufSize)), // _Inout_ PDWORD pdwSize
0, // _In_ BOOL bOrder
uintptr(afClass), // _In_ ULONG ulAf
iphelper_TCP_TABLE_OWNER_PID_ALL, // _In_ TCP_TABLE_CLASS TableClass
0, // _In_ ULONG Reserved
)
case UDP:
r1, _, err = ipHelper.getExtendedUdpTable.Call(
uintptr(unsafe.Pointer(&buf[0])), // _Out_ PVOID pUdpTable,
uintptr(unsafe.Pointer(&bufSize)), // _Inout_ PDWORD pdwSize,
0, // _In_ BOOL bOrder,
uintptr(afClass), // _In_ ULONG ulAf,
iphelper_UDP_TABLE_OWNER_PID, // _In_ UDP_TABLE_CLASS TableClass,
0, // _In_ ULONG Reserved
)
}
switch r1 {
// case windows.ERROR_INSUFFICIENT_BUFFER:
// return nil, fmt.Errorf("insufficient buffer error: %s", err)
// case windows.ERROR_INVALID_PARAMETER:
// return nil, fmt.Errorf("invalid parameter: %s", err)
case windows.NO_ERROR:
default:
return nil, nil, fmt.Errorf("unexpected error: %s", err)
}
// parse output
switch {
case protocol == TCP && ipVersion == IPv4:
tcpTable := (*iphelperTcpTable)(unsafe.Pointer(&buf[0]))
table := tcpTable.table[:tcpTable.numEntries]
for _, row := range table {
new := &connectionEntry{}
// PID
new.pid = int(row.owningPid)
// local
new.localIP = make([]byte, 4)
binary.LittleEndian.PutUint32(new.localIP, row.localAddr)
new.localPort = uint16(row.localPort>>8 | row.localPort<<8)
// remote
if row.state == iphelper_TCP_STATE_LISTEN {
if new.localIP.Equal(net.IPv4zero) {
new.localIP = nil
}
listeners = append(listeners, new)
} else {
new.remoteIP = make([]byte, 4)
binary.LittleEndian.PutUint32(new.remoteIP, row.remoteAddr)
new.remotePort = uint16(row.remotePort>>8 | row.remotePort<<8)
connections = append(connections, new)
}
}
case protocol == TCP && ipVersion == IPv6:
tcpTable := (*iphelperTcp6Table)(unsafe.Pointer(&buf[0]))
table := tcpTable.table[:tcpTable.numEntries]
for _, row := range table {
new := &connectionEntry{}
// PID
new.pid = int(row.owningPid)
// local
new.localIP = net.IP(row.localAddr[:])
new.localPort = uint16(row.localPort>>8 | row.localPort<<8)
// remote
if row.state == iphelper_TCP_STATE_LISTEN {
if new.localIP.Equal(net.IPv6zero) {
new.localIP = nil
}
listeners = append(listeners, new)
} else {
new.remoteIP = net.IP(row.remoteAddr[:])
new.remotePort = uint16(row.remotePort>>8 | row.remotePort<<8)
connections = append(connections, new)
}
}
case protocol == UDP && ipVersion == IPv4:
udpTable := (*iphelperUdpTable)(unsafe.Pointer(&buf[0]))
table := udpTable.table[:udpTable.numEntries]
for _, row := range table {
new := &connectionEntry{}
// PID
new.pid = int(row.owningPid)
// local
new.localPort = uint16(row.localPort>>8 | row.localPort<<8)
if row.localAddr == 0 {
listeners = append(listeners, new)
} else {
new.localIP = make([]byte, 4)
binary.LittleEndian.PutUint32(new.localIP, row.localAddr)
connections = append(connections, new)
}
}
case protocol == UDP && ipVersion == IPv6:
udpTable := (*iphelperUdp6Table)(unsafe.Pointer(&buf[0]))
table := udpTable.table[:udpTable.numEntries]
for _, row := range table {
new := &connectionEntry{}
// PID
new.pid = int(row.owningPid)
// local
new.localIP = net.IP(row.localAddr[:])
new.localPort = uint16(row.localPort>>8 | row.localPort<<8)
if new.localIP.Equal(net.IPv6zero) {
new.localIP = nil
listeners = append(listeners, new)
} else {
connections = append(connections, new)
}
}
}
return connections, listeners, nil
}

View File

@@ -0,0 +1,62 @@
// +build windows
package main
import (
"fmt"
"github.com/Safing/safing-core/process/iphelper"
)
func main() {
iph, err := iphelper.New()
if err != nil {
panic(err)
}
fmt.Printf("TCP4\n")
conns, lConns, err := iph.GetTables(iphelper.TCP, iphelper.IPv4)
if err != nil {
panic(err)
}
fmt.Printf("Connections:\n")
for _, conn := range conns {
fmt.Printf("%s\n", conn)
}
fmt.Printf("Listeners:\n")
for _, conn := range lConns {
fmt.Printf("%s\n", conn)
}
fmt.Printf("\nTCP6\n")
conns, lConns, err = iph.GetTables(iphelper.TCP, iphelper.IPv6)
if err != nil {
panic(err)
}
fmt.Printf("Connections:\n")
for _, conn := range conns {
fmt.Printf("%s\n", conn)
}
fmt.Printf("Listeners:\n")
for _, conn := range lConns {
fmt.Printf("%s\n", conn)
}
fmt.Printf("\nUDP4\n")
_, lConns, err = iph.GetTables(iphelper.UDP, iphelper.IPv4)
if err != nil {
panic(err)
}
for _, conn := range lConns {
fmt.Printf("%s\n", conn)
}
fmt.Printf("\nUDP6\n")
_, lConns, err = iph.GetTables(iphelper.UDP, iphelper.IPv6)
if err != nil {
panic(err)
}
for _, conn := range lConns {
fmt.Printf("%s\n", conn)
}
}

Binary file not shown.

56
process/proc/gather.go Normal file
View File

@@ -0,0 +1,56 @@
// Copyright Safing ICS Technologies GmbH. Use of this source code is governed by the AGPL license that can be found in the LICENSE file.
package proc
import (
"net"
"time"
)
const (
Success uint8 = iota
NoSocket
NoProcess
)
func GetPidOfConnection(localIP *net.IP, localPort uint16, protocol uint8) (pid int, status uint8) {
uid, inode, ok := getConnectionSocket(localIP, localPort, protocol)
if !ok {
uid, inode, ok = getListeningSocket(localIP, localPort, protocol)
for i := 0; i < 3 && !ok; i++ {
// give kernel some time, then try again
// log.Tracef("process: giving kernel some time to think")
time.Sleep(15 * time.Millisecond)
uid, inode, ok = getConnectionSocket(localIP, localPort, protocol)
if !ok {
uid, inode, ok = getListeningSocket(localIP, localPort, protocol)
}
}
if !ok {
return -1, NoSocket
}
}
pid, ok = GetPidOfInode(uid, inode)
for i := 0; i < 3 && !ok; i++ {
// give kernel some time, then try again
// log.Tracef("process: giving kernel some time to think")
time.Sleep(15 * time.Millisecond)
pid, ok = GetPidOfInode(uid, inode)
}
if !ok {
return -1, NoProcess
}
return
}
func GetPidOfIncomingConnection(localIP *net.IP, localPort uint16, protocol uint8) (pid int, status uint8) {
uid, inode, ok := getListeningSocket(localIP, localPort, protocol)
if !ok {
return -1, NoSocket
}
pid, ok = GetPidOfInode(uid, inode)
if !ok {
return -1, NoProcess
}
return
}

56
process/proc/get.go Normal file
View File

@@ -0,0 +1,56 @@
package proc
import (
"errors"
"net"
)
func GetTCP4PacketInfo(localIP net.IP, localPort uint16, remoteIP net.IP, remotePort uint16, pktDirection bool) (pid int, direction bool, err error) {
return search(TCP4, localIP, localPort, direction)
}
func GetTCP6PacketInfo(localIP net.IP, localPort uint16, remoteIP net.IP, remotePort uint16, pktDirection bool) (pid int, direction bool, err error) {
return search(TCP6, localIP, localPort, direction)
}
func GetUDP4PacketInfo(localIP net.IP, localPort uint16, remoteIP net.IP, remotePort uint16, pktDirection bool) (pid int, direction bool, err error) {
return search(UDP4, localIP, localPort, direction)
}
func GetUDP6PacketInfo(localIP net.IP, localPort uint16, remoteIP net.IP, remotePort uint16, pktDirection bool) (pid int, direction bool, err error) {
return search(UDP6, localIP, localPort, direction)
}
func search(protocol uint8, localIP net.IP, localPort uint16, pktDirection bool) (pid int, direction bool, err error) {
var status uint8
if pktDirection {
pid, status = GetPidOfIncomingConnection(&localIP, localPort, protocol)
if pid >= 0 {
return pid, true, nil
}
// pid, status = GetPidOfConnection(&localIP, localPort, protocol)
// if pid >= 0 {
// return pid, false, nil
// }
} else {
pid, status = GetPidOfConnection(&localIP, localPort, protocol)
if pid >= 0 {
return pid, false, nil
}
// pid, status = GetPidOfIncomingConnection(&localIP, localPort, protocol)
// if pid >= 0 {
// return pid, true, nil
// }
}
switch status {
case NoSocket:
return -1, direction, errors.New("could not find socket")
case NoProcess:
return -1, direction, errors.New("could not find PID")
default:
return -1, direction, nil
}
}

View File

@@ -0,0 +1,163 @@
// Copyright Safing ICS Technologies GmbH. Use of this source code is governed by the AGPL license that can be found in the LICENSE file.
package proc
import (
"fmt"
"os"
"sort"
"strconv"
"sync"
"syscall"
"github.com/Safing/safing-core/log"
)
var (
pidsByUserLock sync.Mutex
pidsByUser = make(map[int][]int)
)
func GetPidOfInode(uid, inode int) (int, bool) {
pidsByUserLock.Lock()
defer pidsByUserLock.Unlock()
pidsUpdated := false
// get pids of user, update if missing
pids, ok := pidsByUser[uid]
if !ok {
// log.Trace("process: no processes of user, updating table")
updatePids()
pidsUpdated = true
pids, ok = pidsByUser[uid]
}
if ok {
// if user has pids, start checking them first
var checkedUserPids []int
for _, possiblePID := range pids {
if findSocketFromPid(possiblePID, inode) {
return possiblePID, true
}
checkedUserPids = append(checkedUserPids, possiblePID)
}
// if we fail on the first run and have not updated, update and check the ones we haven't tried so far.
if !pidsUpdated {
// log.Trace("process: socket not found in any process of user, updating table")
// update
updatePids()
// sort for faster search
for i, j := 0, len(checkedUserPids)-1; i < j; i, j = i+1, j-1 {
checkedUserPids[i], checkedUserPids[j] = checkedUserPids[j], checkedUserPids[i]
}
len := len(checkedUserPids)
// check unchecked pids
for _, possiblePID := range pids {
// only check if not already checked
if sort.SearchInts(checkedUserPids, possiblePID) == len {
if findSocketFromPid(possiblePID, inode) {
return possiblePID, true
}
}
}
}
}
// check all other pids
// log.Trace("process: socket not found in any process of user, checking all pids")
// TODO: find best order for pidsByUser for best performance
for possibleUID, pids := range pidsByUser {
if possibleUID != uid {
for _, possiblePID := range pids {
if findSocketFromPid(possiblePID, inode) {
return possiblePID, true
}
}
}
}
return -1, false
}
func findSocketFromPid(pid, inode int) bool {
socketName := fmt.Sprintf("socket:[%d]", inode)
entries := readDirNames(fmt.Sprintf("/proc/%d/fd", pid))
if len(entries) == 0 {
return false
}
for _, entry := range entries {
link, err := os.Readlink(fmt.Sprintf("/proc/%d/fd/%s", pid, entry))
if err != nil {
if !os.IsNotExist(err) {
log.Warningf("process: failed to read link /proc/%d/fd/%s: %s", pid, entry, err)
}
continue
}
if link == socketName {
return true
}
}
return false
}
func updatePids() {
pidsByUser = make(map[int][]int)
entries := readDirNames("/proc")
if len(entries) == 0 {
return
}
entryLoop:
for _, entry := range entries {
pid, err := strconv.ParseInt(entry, 10, 32)
if err != nil {
continue entryLoop
}
statData, err := os.Stat(fmt.Sprintf("/proc/%d", pid))
if err != nil {
log.Warningf("process: could not stat /proc/%d: %s", pid, err)
continue entryLoop
}
sys, ok := statData.Sys().(*syscall.Stat_t)
if !ok {
log.Warningf("process: unable to parse /proc/%d: wrong type", pid)
continue entryLoop
}
pids, ok := pidsByUser[int(sys.Uid)]
if ok {
pidsByUser[int(sys.Uid)] = append(pids, int(pid))
} else {
pidsByUser[int(sys.Uid)] = []int{int(pid)}
}
}
for _, slice := range pidsByUser {
for i, j := 0, len(slice)-1; i < j; i, j = i+1, j-1 {
slice[i], slice[j] = slice[j], slice[i]
}
}
}
func readDirNames(dir string) (names []string) {
file, err := os.Open(dir)
if err != nil {
if !os.IsNotExist(err) {
log.Warningf("process: could not open directory %s: %s", dir, err)
}
return
}
defer file.Close()
names, err = file.Readdirnames(0)
if err != nil {
log.Warningf("process: could not get entries from direcotry %s: %s", dir, err)
return []string{}
}
return
}

View File

@@ -0,0 +1,18 @@
// Copyright Safing ICS Technologies GmbH. Use of this source code is governed by the AGPL license that can be found in the LICENSE file.
package proc
import (
"log"
"testing"
)
func TestProcessFinder(t *testing.T) {
updatePids()
log.Printf("pidsByUser: %v", pidsByUser)
pid, _ := GetPidOfInode(1000, 112588)
log.Printf("pid: %d", pid)
}

381
process/proc/sockets.go Normal file
View File

@@ -0,0 +1,381 @@
// Copyright Safing ICS Technologies GmbH. Use of this source code is governed by the AGPL license that can be found in the LICENSE file.
package proc
import (
"bufio"
"encoding/hex"
"fmt"
"net"
"os"
"strconv"
"strings"
"sync"
"unicode"
"github.com/Safing/safing-core/log"
)
/*
1. find socket inode
- by incoming (listenting sockets) or outgoing (local port + external IP + port) - also local IP?
- /proc/net/{tcp|udp}[6]
2. get list of processes of uid
3. find socket inode in process fds
- if not found, refresh map of uid->pids
- if not found, check ALL pids: maybe euid != uid
4. gather process info
Cache every step!
*/
const (
TCP4 uint8 = iota
UDP4
TCP6
UDP6
ICMP4
ICMP6
TCP4Data = "/proc/net/tcp"
UDP4Data = "/proc/net/udp"
TCP6Data = "/proc/net/tcp6"
UDP6Data = "/proc/net/udp6"
ICMP4Data = "/proc/net/icmp"
ICMP6Data = "/proc/net/icmp6"
TCP_ESTABLISHED = iota + 1
TCP_SYN_SENT
TCP_SYN_RECV
TCP_FIN_WAIT1
TCP_FIN_WAIT2
TCP_TIME_WAIT
TCP_CLOSE
TCP_CLOSE_WAIT
TCP_LAST_ACK
TCP_LISTEN
TCP_CLOSING
TCP_NEW_SYN_RECV
)
var (
// connectionSocketsLock sync.Mutex
// connectionTCP4 = make(map[string][]int)
// connectionUDP4 = make(map[string][]int)
// connectionTCP6 = make(map[string][]int)
// connectionUDP6 = make(map[string][]int)
listeningSocketsLock sync.Mutex
addressListeningTCP4 = make(map[string][]int)
addressListeningUDP4 = make(map[string][]int)
addressListeningTCP6 = make(map[string][]int)
addressListeningUDP6 = make(map[string][]int)
globalListeningTCP4 = make(map[uint16][]int)
globalListeningUDP4 = make(map[uint16][]int)
globalListeningTCP6 = make(map[uint16][]int)
globalListeningUDP6 = make(map[uint16][]int)
)
func getConnectionSocket(localIP *net.IP, localPort uint16, protocol uint8) (int, int, bool) {
// listeningSocketsLock.Lock()
// defer listeningSocketsLock.Unlock()
var procFile string
var localIPHex string
switch protocol {
case TCP4:
procFile = TCP4Data
localIPBytes := []byte(localIP.To4())
localIPHex = strings.ToUpper(hex.EncodeToString([]byte{localIPBytes[3], localIPBytes[2], localIPBytes[1], localIPBytes[0]}))
case UDP4:
procFile = UDP4Data
localIPBytes := []byte(localIP.To4())
localIPHex = strings.ToUpper(hex.EncodeToString([]byte{localIPBytes[3], localIPBytes[2], localIPBytes[1], localIPBytes[0]}))
case TCP6:
procFile = TCP6Data
localIPHex = hex.EncodeToString([]byte(*localIP))
case UDP6:
procFile = UDP6Data
localIPHex = hex.EncodeToString([]byte(*localIP))
}
localPortHex := fmt.Sprintf("%04X", localPort)
// log.Tracef("process/proc: searching for PID of: %s:%d (%s:%s)", localIP, localPort, localIPHex, localPortHex)
// open file
socketData, err := os.Open(procFile)
if err != nil {
log.Warningf("process/proc: could not read %s: %s", procFile, err)
return -1, -1, false
}
defer socketData.Close()
// file scanner
scanner := bufio.NewScanner(socketData)
scanner.Split(bufio.ScanLines)
// parse
scanner.Scan() // skip first line
for scanner.Scan() {
line := strings.FieldsFunc(scanner.Text(), procDelimiter)
// log.Tracef("line: %s", line)
if len(line) < 14 {
// log.Tracef("process: too short: %s", line)
continue
}
if line[1] != localIPHex {
continue
}
if line[2] != localPortHex {
continue
}
ok := true
uid, err := strconv.ParseInt(line[11], 10, 32)
if err != nil {
log.Warningf("process: could not parse uid %s: %s", line[11], err)
uid = -1
ok = false
}
inode, err := strconv.ParseInt(line[13], 10, 32)
if err != nil {
log.Warningf("process: could not parse inode %s: %s", line[13], err)
inode = -1
ok = false
}
// log.Tracef("process/proc: identified process of %s:%d: socket=%d uid=%d", localIP, localPort, int(inode), int(uid))
return int(uid), int(inode), ok
}
return -1, -1, false
}
func getListeningSocket(localIP *net.IP, localPort uint16, protocol uint8) (uid, inode int, ok bool) {
listeningSocketsLock.Lock()
defer listeningSocketsLock.Unlock()
var addressListening *map[string][]int
var globalListening *map[uint16][]int
switch protocol {
case TCP4:
addressListening = &addressListeningTCP4
globalListening = &globalListeningTCP4
case UDP4:
addressListening = &addressListeningUDP4
globalListening = &globalListeningUDP4
case TCP6:
addressListening = &addressListeningTCP6
globalListening = &globalListeningTCP6
case UDP6:
addressListening = &addressListeningUDP6
globalListening = &globalListeningUDP6
}
data, ok := (*addressListening)[fmt.Sprintf("%s:%d", localIP, localPort)]
if !ok {
data, ok = (*globalListening)[localPort]
}
if ok {
return data[0], data[1], true
}
updateListeners(protocol)
data, ok = (*addressListening)[fmt.Sprintf("%s:%d", localIP, localPort)]
if !ok {
data, ok = (*globalListening)[localPort]
}
if ok {
return data[0], data[1], true
}
return 0, 0, false
}
func procDelimiter(c rune) bool {
return unicode.IsSpace(c) || c == ':'
}
func convertIPv4(data string) *net.IP {
decoded, err := hex.DecodeString(data)
if err != nil {
log.Warningf("process: could not parse IPv4 %s: %s", data, err)
return nil
}
if len(decoded) != 4 {
log.Warningf("process: decoded IPv4 %s has wrong length")
return nil
}
ip := net.IPv4(decoded[3], decoded[2], decoded[1], decoded[0])
return &ip
}
func convertIPv6(data string) *net.IP {
decoded, err := hex.DecodeString(data)
if err != nil {
log.Warningf("process: could not parse IPv6 %s: %s", data, err)
return nil
}
if len(decoded) != 16 {
log.Warningf("process: decoded IPv6 %s has wrong length")
return nil
}
ip := net.IP(decoded)
return &ip
}
func updateListeners(protocol uint8) {
switch protocol {
case TCP4:
addressListeningTCP4, globalListeningTCP4 = getListenerMaps(TCP4Data, "00000000", "0A", convertIPv4)
case UDP4:
addressListeningUDP4, globalListeningUDP4 = getListenerMaps(UDP4Data, "00000000", "07", convertIPv4)
case TCP6:
addressListeningTCP6, globalListeningTCP6 = getListenerMaps(TCP6Data, "00000000000000000000000000000000", "0A", convertIPv6)
case UDP6:
addressListeningUDP6, globalListeningUDP6 = getListenerMaps(UDP6Data, "00000000000000000000000000000000", "07", convertIPv6)
}
}
func getListenerMaps(procFile, zeroIP, socketStatusListening string, ipConverter func(string) *net.IP) (map[string][]int, map[uint16][]int) {
addressListening := make(map[string][]int)
globalListening := make(map[uint16][]int)
// open file
socketData, err := os.Open(procFile)
if err != nil {
log.Warningf("process: could not read %s: %s", procFile, err)
return addressListening, globalListening
}
defer socketData.Close()
// file scanner
scanner := bufio.NewScanner(socketData)
scanner.Split(bufio.ScanLines)
// parse
scanner.Scan() // skip first line
for scanner.Scan() {
line := strings.FieldsFunc(scanner.Text(), procDelimiter)
if len(line) < 14 {
// log.Tracef("process: too short: %s", line)
continue
}
if line[5] != socketStatusListening {
// skip if not listening
// log.Tracef("process: not listening %s: %s", line, line[5])
continue
}
port, err := strconv.ParseUint(line[2], 16, 16)
// log.Tracef("port: %s", line[2])
if err != nil {
log.Warningf("process: could not parse port %s: %s", line[2], err)
continue
}
uid, err := strconv.ParseInt(line[11], 10, 32)
// log.Tracef("uid: %s", line[11])
if err != nil {
log.Warningf("process: could not parse uid %s: %s", line[11], err)
continue
}
inode, err := strconv.ParseInt(line[13], 10, 32)
// log.Tracef("inode: %s", line[13])
if err != nil {
log.Warningf("process: could not parse inode %s: %s", line[13], err)
continue
}
if line[1] == zeroIP {
globalListening[uint16(port)] = []int{int(uid), int(inode)}
} else {
address := ipConverter(line[1])
if address != nil {
addressListening[fmt.Sprintf("%s:%d", address, port)] = []int{int(uid), int(inode)}
}
}
}
return addressListening, globalListening
}
func GetActiveConnectionIDs() []string {
var connections []string
connections = append(connections, getConnectionIDsFromSource(TCP4Data, 6, convertIPv4)...)
connections = append(connections, getConnectionIDsFromSource(UDP4Data, 17, convertIPv4)...)
connections = append(connections, getConnectionIDsFromSource(TCP6Data, 6, convertIPv6)...)
connections = append(connections, getConnectionIDsFromSource(UDP6Data, 17, convertIPv6)...)
return connections
}
func getConnectionIDsFromSource(source string, protocol uint16, ipConverter func(string) *net.IP) []string {
var connections []string
// open file
socketData, err := os.Open(source)
if err != nil {
log.Warningf("process: could not read %s: %s", source, err)
return connections
}
defer socketData.Close()
// file scanner
scanner := bufio.NewScanner(socketData)
scanner.Split(bufio.ScanLines)
// parse
scanner.Scan() // skip first line
for scanner.Scan() {
line := strings.FieldsFunc(scanner.Text(), procDelimiter)
if len(line) < 14 {
// log.Tracef("process: too short: %s", line)
continue
}
// skip listeners and closed connections
if line[5] == "0A" || line[5] == "07" {
continue
}
localIP := ipConverter(line[1])
if localIP == nil {
continue
}
localPort, err := strconv.ParseUint(line[2], 16, 16)
if err != nil {
log.Warningf("process: could not parse port: %s", err)
continue
}
remoteIP := ipConverter(line[3])
if remoteIP == nil {
continue
}
remotePort, err := strconv.ParseUint(line[4], 16, 16)
if err != nil {
log.Warningf("process: could not parse port: %s", err)
continue
}
connections = append(connections, fmt.Sprintf("%d-%s-%d-%s-%d", protocol, localIP, localPort, remoteIP, remotePort))
}
return connections
}

View File

@@ -0,0 +1,40 @@
// Copyright Safing ICS Technologies GmbH. Use of this source code is governed by the AGPL license that can be found in the LICENSE file.
package proc
import (
"net"
"testing"
)
func TestSockets(t *testing.T) {
updateListeners(TCP4)
updateListeners(UDP4)
updateListeners(TCP6)
updateListeners(UDP6)
t.Logf("addressListeningTCP4: %v", addressListeningTCP4)
t.Logf("globalListeningTCP4: %v", globalListeningTCP4)
t.Logf("addressListeningUDP4: %v", addressListeningUDP4)
t.Logf("globalListeningUDP4: %v", globalListeningUDP4)
t.Logf("addressListeningTCP6: %v", addressListeningTCP6)
t.Logf("globalListeningTCP6: %v", globalListeningTCP6)
t.Logf("addressListeningUDP6: %v", addressListeningUDP6)
t.Logf("globalListeningUDP6: %v", globalListeningUDP6)
getListeningSocket(&net.IPv4zero, 53, TCP4)
getListeningSocket(&net.IPv4zero, 53, UDP4)
getListeningSocket(&net.IPv6zero, 53, TCP6)
getListeningSocket(&net.IPv6zero, 53, UDP6)
// spotify: 192.168.0.102:5353 192.121.140.65:80
localIP := net.IPv4(192, 168, 127, 10)
uid, inode, ok := getConnectionSocket(&localIP, 46634, TCP4)
t.Logf("getConnectionSocket: %d %d %v", uid, inode, ok)
activeConnectionIDs := GetActiveConnectionIDs()
for _, connID := range activeConnectionIDs {
t.Logf("active: %s", connID)
}
}

251
process/process.go Normal file
View File

@@ -0,0 +1,251 @@
// Copyright Safing ICS Technologies GmbH. Use of this source code is governed by the AGPL license that can be found in the LICENSE file.
package process
import (
"fmt"
"runtime"
"strconv"
"strings"
datastore "github.com/ipfs/go-datastore"
processInfo "github.com/shirou/gopsutil/process"
"github.com/Safing/safing-core/database"
"github.com/Safing/safing-core/log"
"github.com/Safing/safing-core/profiles"
)
// A Process represents a process running on the operating system
type Process struct {
database.Base
UserID int
UserName string
UserHome string
Pid int
ParentPid int
Path string
Cwd string
FileInfo *FileInfo
CmdLine string
FirstArg string
ProfileKey string
Profile *profiles.Profile
Name string
Icon string
// Icon is a path to the icon and is either prefixed "f:" for filepath, "d:" for database cache path or "c:"/"a:" for a the icon key to fetch it from a company / authoritative node and cache it in its own cache.
}
var processModel *Process // only use this as parameter for database.EnsureModel-like functions
func init() {
database.RegisterModel(processModel, func() database.Model { return new(Process) })
}
// Create saves Process with the provided name in the default namespace.
func (m *Process) Create(name string) error {
return m.CreateObject(&database.Processes, name, m)
}
// CreateInNamespace saves Process with the provided name in the provided namespace.
func (m *Process) CreateInNamespace(namespace *datastore.Key, name string) error {
return m.CreateObject(namespace, name, m)
}
// Save saves Process.
func (m *Process) Save() error {
return m.SaveObject(m)
}
// GetProcess fetches Process with the provided name from the default namespace.
func GetProcess(name string) (*Process, error) {
return GetProcessFromNamespace(&database.Processes, name)
}
// GetProcessFromNamespace fetches Process with the provided name from the provided namespace.
func GetProcessFromNamespace(namespace *datastore.Key, name string) (*Process, error) {
object, err := database.GetAndEnsureModel(namespace, name, processModel)
if err != nil {
return nil, err
}
model, ok := object.(*Process)
if !ok {
return nil, database.NewMismatchError(object, processModel)
}
return model, nil
}
func (m *Process) String() string {
if m == nil {
return "?"
}
if m.Profile != nil && !m.Profile.Default {
return fmt.Sprintf("%s:%s:%d", m.UserName, m.Profile, m.Pid)
}
return fmt.Sprintf("%s:%s:%d", m.UserName, m.Path, m.Pid)
}
func GetOrFindProcess(pid int) (*Process, error) {
process, err := GetProcess(strconv.Itoa(pid))
if err == nil {
return process, nil
}
new := &Process{
Pid: pid,
}
switch {
case (pid == 0 && runtime.GOOS == "linux") || (pid == 4 && runtime.GOOS == "windows"):
new.UserName = "Kernel"
new.Name = "Operating System"
new.Profile = &profiles.Profile{
Name: "OS",
Flags: []int8{profiles.Internet, profiles.LocalNet, profiles.Directconnect, profiles.Service},
}
default:
pInfo, err := processInfo.NewProcess(int32(pid))
if err != nil {
return nil, err
}
// UID
// net yet implemented for windows
if runtime.GOOS == "linux" {
uids, err := pInfo.Uids()
if err != nil {
log.Warningf("process: failed to get UID: %s", err)
} else {
new.UserID = int(uids[0])
}
}
// Username
new.UserName, err = pInfo.Username()
if err != nil {
log.Warningf("process: failed to get Username: %s", err)
}
// TODO: User Home
// new.UserHome, err =
// PPID
ppid, err := pInfo.Ppid()
if err != nil {
log.Warningf("process: failed to get PPID: %s", err)
} else {
new.ParentPid = int(ppid)
}
// Path
new.Path, err = pInfo.Exe()
if err != nil {
log.Warningf("process: failed to get Path: %s", err)
}
// Current working directory
// net yet implemented for windows
// new.Cwd, err = pInfo.Cwd()
// if err != nil {
// log.Warningf("process: failed to get Cwd: %s", err)
// }
// Command line arguments
new.CmdLine, err = pInfo.Cmdline()
if err != nil {
log.Warningf("process: failed to get Cmdline: %s", err)
}
// Name
new.Name, err = pInfo.Name()
if err != nil {
log.Warningf("process: failed to get Name: %s", err)
}
// TODO: App Icon
// new.Icon, err =
// get Profile
processPath := new.Path
var applyProfile *profiles.Profile
iterations := 0
for applyProfile == nil {
iterations++
if iterations > 10 {
log.Warningf("process: got into loop while getting profile for %s", new)
break
}
applyProfile, err = profiles.GetActiveProfileByPath(processPath)
if err == database.ErrNotFound {
applyProfile, err = profiles.FindProfileByPath(processPath, new.UserHome)
}
if err != nil {
log.Warningf("process: could not get profile for %s: %s", new, err)
} else if applyProfile == nil {
log.Warningf("process: no default profile found for %s", new)
} else {
// TODO: there is a lot of undefined behaviour if chaining framework profiles
// process framework
if applyProfile.Framework != nil {
if applyProfile.Framework.FindParent > 0 {
var ppid int32
for i := uint8(1); i < applyProfile.Framework.FindParent; i++ {
parent, err := pInfo.Parent()
if err != nil {
return nil, err
}
ppid = parent.Pid
}
if applyProfile.Framework.MergeWithParent {
return GetOrFindProcess(int(ppid))
}
// processPath, err = os.Readlink(fmt.Sprintf("/proc/%d/exe", pid))
// if err != nil {
// return nil, fmt.Errorf("could not read /proc/%d/exe: %s", pid, err)
// }
continue
}
newCommand, err := applyProfile.Framework.GetNewPath(new.CmdLine, new.Cwd)
if err != nil {
return nil, err
}
// assign
new.CmdLine = newCommand
new.Path = strings.SplitN(newCommand, " ", 2)[0]
processPath = new.Path
// make sure we loop
applyProfile = nil
continue
}
// apply profile to process
log.Debugf("process: applied profile to %s: %s", new, applyProfile)
new.Profile = applyProfile
new.ProfileKey = applyProfile.GetKey().String()
// update Profile with Process icon if Profile does not have one
if !new.Profile.Default && new.Icon != "" && new.Profile.Icon == "" {
new.Profile.Icon = new.Icon
new.Profile.Save()
}
}
}
// get FileInfo
new.FileInfo = GetFileInfo(new.Path)
}
// save to DB
new.Create(strconv.Itoa(new.Pid))
return new, nil
}

13
process/process_linux.go Normal file
View File

@@ -0,0 +1,13 @@
package process
func (m *Process) IsUser() bool {
return m.UserID >= 1000
}
func (m *Process) IsAdmin() bool {
return m.UserID >= 0
}
func (m *Process) IsSystem() bool {
return m.UserID == 0
}

View File

@@ -0,0 +1,16 @@
package process
import "strings"
func (m *Process) IsUser() bool {
return m.Pid != 4 && // Kernel
!strings.HasPrefix(m.UserName, "NT-") // NT-Authority (localized!)
}
func (m *Process) IsAdmin() bool {
return strings.HasPrefix(m.UserName, "NT-") // NT-Authority (localized!)
}
func (m *Process) IsSystem() bool {
return m.Pid == 4
}

View File

@@ -0,0 +1,219 @@
// Copyright Safing ICS Technologies GmbH. Use of this source code is governed by the AGPL license that can be found in the LICENSE file.
package process
import (
"bufio"
"os"
"path/filepath"
"strings"
"sync"
"time"
"github.com/Safing/safing-core/log"
)
var (
humanInfoCache map[string]*humanInfo
humanInfoCacheNamesOnly map[string]*humanInfo
nextHumanInfoCacheUpdate time.Time
searchPathCache map[string]string
nextSearchPathCacheUpdate time.Time
humanInfoCacheLock sync.Mutex
)
type humanInfo struct {
Name string
IconName string
IconPath string
ExecCmd string
}
func (m *Process) GetHumanInfo() {
humanInfoCacheLock.Lock()
defer humanInfoCacheLock.Unlock()
// TODO: do some proper fuzzy matching to get these icons!
// take special care of:
// - flatpak
// - snapd
fuzzyMatch := false
info, ok := humanInfoCache[m.Path]
if !ok {
splitted := strings.Split(m.Path, "/")
nameOnly := splitted[len(splitted)-1]
info, ok = humanInfoCacheNamesOnly[nameOnly]
if ok {
fuzzyMatch = true
} else {
updateHumanInfoCache()
info, ok = humanInfoCache[m.Path]
if !ok {
fuzzyMatch = true
info, ok = humanInfoCacheNamesOnly[nameOnly]
if !ok {
if len(splitted) > 3 {
info, ok = humanInfoCacheNamesOnly[splitted[len(splitted)-2]]
if !ok {
return
}
} else {
return
}
}
}
}
}
if info.IconPath == "" && info.IconName != "" {
info.IconPath = getIconPathFromName(info.IconName)
}
if !fuzzyMatch {
m.Name = info.Name
}
if info.IconPath != "" {
m.Icon = "f:" + info.IconPath
}
}
func updateHumanInfoCache() {
if time.Now().Before(nextHumanInfoCacheUpdate) {
return
}
humanInfoCache = make(map[string]*humanInfo)
humanInfoCacheNamesOnly = make(map[string]*humanInfo)
log.Tracef("process: updating human info cache")
for _, starterDir := range starterLocations {
for _, dirEntry := range readDirNames(starterDir) {
// only look at .desktop files
if !strings.HasSuffix(dirEntry, ".desktop") {
continue
}
execCmd, name, iconPath := getStarterInfo(filepath.Join(starterDir, dirEntry))
if execCmd == "" {
continue
}
execPath := strings.SplitN(execCmd, " ", 2)[0]
var ok bool
if !strings.HasPrefix(execPath, "/") {
execPath, ok = searchPathCache[execPath]
if !ok {
updateSearchPathCache()
execPath, ok = searchPathCache[execPath]
if !ok {
continue
}
}
}
new := humanInfo{
Name: name,
}
if strings.HasPrefix(iconPath, "/") {
new.IconPath = iconPath
} else {
new.IconName = iconPath
}
humanInfoCache[execPath] = &new
splitted := strings.Split(execPath, "/")
humanInfoCacheNamesOnly[splitted[len(splitted)-1]] = &new
// log.Tracef("process: new cache entry: %s - %s - %s - %s", execPath, name, new.IconName, new.IconPath)
}
}
nextHumanInfoCacheUpdate = time.Now().Add(10 * time.Second)
}
func getStarterInfo(filePath string) (execCmd, name, iconPath string) {
// open file
file, err := os.Open(filePath)
if err != nil {
log.Warningf("process: could not read %s: %s", filePath, err)
return
}
defer file.Close()
// file scanner
scanner := bufio.NewScanner(file)
scanner.Split(bufio.ScanLines)
// parse
seen := 0
scanner.Scan() // skip first line
for scanner.Scan() {
line := strings.SplitN(scanner.Text(), "=", 2)
if len(line) < 2 {
continue
}
switch {
case line[0] == "Name":
if name == "" {
name = line[1]
seen += 1
}
case line[0] == "Exec":
if execCmd == "" {
execCmd = line[1]
seen += 1
}
case line[0] == "Icon":
if iconPath == "" {
iconPath = line[1]
seen += 1
}
}
if seen >= 3 {
return
}
}
return
}
func updateSearchPathCache() {
if time.Now().Before(nextSearchPathCacheUpdate) {
return
}
searchPathCache = make(map[string]string)
for _, searchEntry := range strings.Split(os.Getenv("PATH"), ":") {
names := readDirNames(searchEntry)
for _, name := range names {
// only save first occurence
if _, ok := searchPathCache[name]; !ok {
searchPathCache[name] = filepath.Join(searchEntry, name)
}
}
}
nextSearchPathCacheUpdate = time.Now().Add(10 * time.Second)
}
func getIconPathFromName(iconName string) string {
for _, location := range xdgIconLocations {
// skip everything that does not exist or we do not have access to.
if _, err := os.Stat(location); err != nil {
continue
}
// check for icon
for _, path := range xdgIconPaths {
possibleIcon := filepath.Join(location, path, iconName+".png")
if _, err := os.Stat(possibleIcon); err == nil {
return possibleIcon
}
}
}
for _, location := range iconLocations {
possibleIcon := filepath.Join(location, iconName+".png")
if _, err := os.Stat(possibleIcon); err == nil {
return possibleIcon
}
}
return ""
}

View File

@@ -0,0 +1,32 @@
// Copyright Safing ICS Technologies GmbH. Use of this source code is governed by the AGPL license that can be found in the LICENSE file.
package process
import (
"fmt"
"strconv"
"testing"
)
func TestGetHumanInfo(t *testing.T) {
// gather processes for testing
pids := readDirNames("/proc")
var allProcs []*Process
for _, pidString := range pids {
pid, err := strconv.ParseInt(pidString, 10, 32)
if err != nil {
continue
}
next, err := GetOrFindProcess(int(pid))
if err != nil {
continue
}
allProcs = append(allProcs, next)
}
// test
for _, process := range allProcs {
process.GetHumanInfo()
fmt.Printf("%d - %s - %s - %s\n", process.Pid, process.Path, process.Name, process.Icon)
}
}

View File

@@ -0,0 +1,39 @@
// Copyright Safing ICS Technologies GmbH. Use of this source code is governed by the AGPL license that can be found in the LICENSE file.
package process
// spec: https://specifications.freedesktop.org/icon-theme-spec/icon-theme-spec-latest.html
// alternatives:
// gtk3 lib: https://developer.gnome.org/gtk3/stable/GtkIconTheme.html
// qt lib: ?
var (
starterLocations = []string{
"/usr/share/applications",
"/var/lib/flatpak/exports/share/applications/",
}
iconLocations = []string{
"/usr/share/pixmaps",
}
xdgIconLocations = []string{
"/usr/share/default",
"/usr/share/gnome",
"/var/lib/flatpak/exports/share",
"/usr/local/share",
"/usr/share",
"/var/lib/snapd/desktop",
}
xdgIconPaths = []string{
"icons/hicolor/512x512/apps",
"icons/hicolor/256x256/apps",
"icons/hicolor/192x192/apps",
"icons/hicolor/128x128/apps",
"icons/hicolor/96x96/apps",
"icons/hicolor/72x72/apps",
"icons/hicolor/64x64/apps",
"icons/hicolor/48x48/apps",
}
)