From f1e579bc427df418af96da61498fe0cdb4cecb69 Mon Sep 17 00:00:00 2001 From: dautor Date: Sun, 17 Nov 2024 20:53:51 +0100 Subject: Add COPYRIGHT and rename module directories --- src/module.jail/main.c | 525 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 525 insertions(+) create mode 100644 src/module.jail/main.c (limited to 'src/module.jail/main.c') diff --git a/src/module.jail/main.c b/src/module.jail/main.c new file mode 100644 index 0000000..d71511b --- /dev/null +++ b/src/module.jail/main.c @@ -0,0 +1,525 @@ +#include "../module/module.h" +#include "../util.h" +#include "state.h" + +#include +#include +#include + +#include +#include +#include + +#include +#include +#include + +char const *Usage = "usage: %1$s start [name] [configuration]\n" + " %1$s stop [name] [configuration] [data]\n" + " %1$s get-endpoint [name] [configuration] [data] [interface]\n" + " %1$s cmd [name] [configuration] [data] sh\n" + " %1$s cmd [name] [configuration] [data] wireshark [interface]\n" + "unsupported commands: mod\n"; + +static configuration Configuration; +static data Data; +static bool ConfigurationLoaded; +static bool DataLoaded; + +// HELPERS + +static void +Free_all(void) +{ + if(ConfigurationLoaded == true) Free_configuration(&Configuration); + if(DataLoaded == true) Free_data(&Data); +} + +static void +Load_configuration(ucl_object_t *root) +{ + char *Error = Parse_configuration(&Configuration, root, ""); + if(Error != NULL) + { + fprintf(stderr, "%s: configuration error: %s\n", Arg0, Error); + free(Error); + Free_all(); + exit(-1); + } + ConfigurationLoaded = true; +} + +static void +Load_data(ucl_object_t *root) +{ + char *Error = Parse_data(&Data, root, ""); + if(Error != NULL) + { + fprintf(stderr, "%s: data error: %s\n", Arg0, Error); + free(Error); + Free_all(); + exit(-1); + } + DataLoaded = true; + // TODO: check that data matches configuration +} + +static int +start_interface(interface *E, interface_configuration *C, fd Control, char const *JailName) +{ + E->Type = C->Type; + switch(C->Type) + { + case interface_type_eiface: + { + if(Create_eiface(&E->ID, Control) == -1) return -1; + char *InterfaceName = getifname(E->ID, Control); + if(InterfaceName == NULL) + { + DestroyNetgraphNode(E->ID, Control); + return -1; + } + // NOTE: This first renames the interface and only then vnets it. + int Result = + command("ifconfig", "ifconfig", InterfaceName, "name", C->Name, "vnet", JailName, NULL); + free(InterfaceName); + return Result; + } + case interface_type_steal: + { + return command("ifconfig", "ifconfig", C->steal.Interface, "name", C->Name, "vnet", JailName, NULL); + } + case interface_type_COUNT: + default: __builtin_unreachable(); + } +} + +static int +stop_interface(interface *E, interface_configuration *C, fd Control, char const *JailName) +{ + switch(C->Type) + { + case interface_type_eiface: return DestroyNetgraphNode(E->ID, Control); + case interface_type_steal: + { + return command("ifconfig", + "ifconfig", + C->Name, + "-vnet", + JailName, + "name", + C->steal.Interface, + NULL); + } + case interface_type_COUNT: + default: __builtin_unreachable(); + } +} + +static int +stop_interfaces(interfaces *E, interfaces_configuration *C, fd Control, char const *JailName) +{ + for(size_t i = 0; i < E->Count; ++i) + { + if(stop_interface(E->_ + i, C->_ + i, Control, JailName) == -1) + { + fprintf(stderr, "failed to stop interface '%s'\n", C->_[i].Name); + return -1; + } + } + return 0; +} + +static int +start_interfaces(interfaces *E, interfaces_configuration *C, fd Control, char const *Name) +{ + Data.Interfaces._ = calloc(sizeof(interface), Configuration.Interfaces.Count); + for(E->Count = 0; E->Count < C->Count; ++E->Count) + { + interface *E_ = E->_ + E->Count; + interface_configuration *C_ = C->_ + E->Count; + if(start_interface(E_, C_, Control, Name) == -1) goto error; + } + return 0; +error: + stop_interfaces(E, C, Control, Name); + Free_interfaces(E); + return -1; +} + +static int +start_jail(jid_t *JID, char const *Name, char const *Path) +{ + *JID = jail_setv(JAIL_CREATE, "name", Name, "vnet", "new", "persist", "true", "path", Path, NULL); + if(*JID == -1) + { + fprintf(stderr, "jail creation failed: %s\n", strerror(errno)); + return -1; + } + return 0; +} + +static int +stop_jail(jid_t JID) +{ + if(jail_remove(JID) == -1) + { + fprintf(stderr, "jail removal failed: %s\n", strerror(errno)); + Free_all(); + return -1; + } + return 0; +} + +NAMED_ENUM(mapping_type, unionfs_below, unionfs, devfs, nullfs); + +static int +mapping_mount(mapping *M, char const *TargetPrefix, mapping_type Type) +{ + char Target[MAXPATHLEN]; + snprintf(Target, sizeof(Target), "%s/%s", TargetPrefix, M->Target); + if(mkdir(Target, 755) == -1) + { + if(errno != EEXIST) + { + fprintf(stderr, "mkdir %s: %s\n", Target, strerror(errno)); + return -1; + } + } + switch(Type) + { + case mapping_type_unionfs_below: return mount_unionfs(Target, M->Source, true); + case mapping_type_unionfs: return mount_unionfs(Target, M->Source, false); + case mapping_type_devfs: return mount_devfs(Target); + case mapping_type_nullfs: return mount_nullfs(Target, M->Source); + case mapping_type_COUNT: + default: __builtin_unreachable(); + } +} + +static int +mapping_umount(char const *TargetPrefix, char const *Target_) +{ + char Target[MAXPATHLEN]; + snprintf(Target, sizeof(Target), "%s/%s", TargetPrefix, Target_); + // NOTE: unmount fails because some processes somehow still hold a reference to the jail FS + while(unmount(Target, MNT_FORCE) == -1) + { + fprintf(stderr, "unmount %s failed: %s\n", Target, strerror(errno)); + if(errno != EBUSY) return -1; + sleep(1); + } + return 0; +} + +static int +rm(char const *Path) +{ + pid_t PID = fork(); + if(PID == -1) return -1; + if(PID == 0) + { + close(STDIN_FILENO); + dup2(STDERR_FILENO, STDOUT_FILENO); + close_all(); + fprintf(stderr, "rm -r %s\n", Path); + execlp("rm", "rm", "-r", Path, NULL); + exit(-1); + } + int Exit; + waitpid(PID, &Exit, 0); + if(!WIFEXITED(Exit) || (WEXITSTATUS(Exit) != 0)) return -1; + return 0; +} + +static int +ApplyDevfsRuleset(char const *Prefix, s64 Ruleset) +{ + if(Ruleset == 0) return 0; + devfs_rsnum RS = (devfs_rsnum)Ruleset; + if(RS != Ruleset) + { + fprintf(stderr, "Ruleset invalid: %ld\n", Ruleset); + return -1; + } + char Path[MAXPATHLEN]; + snprintf(Path, sizeof(Path), "%s/dev", Prefix); + fd DevFD = open(Path, O_RDONLY); + if(DevFD == -1) + { + fprintf(stderr, "open '%s' failed: %s\n", Path, strerror(errno)); + return -1; + } + if(ioctl(DevFD, DEVFSIO_SAPPLY, &RS) == -1) + { + fprintf(stderr, "ruleset %hu apply failed: %s\n", RS, strerror(errno)); + close(DevFD); + return -1; + } + close(DevFD); + return 0; +} + +static int +start_fs(filesystem_configuration *E, char const *Path) +{ + bool CreatedPath = false; + size_t LayerAt = 0; + size_t VolumeAt = 0; + bool Mounted_devfs = false; + bool Mounted_temporary = false; + if(mkdir(Path, 0755) == -1) + { + fprintf(stderr, "mkdir %s: %s\n", Path, strerror(errno)); + goto error; + } + CreatedPath = true; + for(LayerAt = 0; LayerAt < E->LayerCount; ++LayerAt) + { + if(mapping_mount(&(mapping){ .Source = E->Layer[LayerAt], .Target = "/" }, + Path, + mapping_type_unionfs_below) == -1) + goto error; + } + if(E->Temporary != NULL) + { + if(mkdir(E->Temporary, 755) == -1) + { + if(errno != EEXIST) + { + fprintf(stderr, "mkdir %s: %s\n", E->Temporary, strerror(errno)); + goto error; + } + } + if(mapping_mount(&(mapping){ .Source = E->Temporary, .Target = "/" }, Path, mapping_type_unionfs) == -1) + goto error; + } + Mounted_temporary = true; + if(mapping_mount(&(mapping){ .Target = "/dev" }, Path, mapping_type_devfs) == -1) goto error; + if(ApplyDevfsRuleset(Path, E->DevfsRuleset) == -1) goto error; + for(VolumeAt = 0; VolumeAt < E->VolumeCount; ++VolumeAt) + { + if(mapping_mount(E->Volume + VolumeAt, Path, mapping_type_nullfs) == -1) goto error; + } + return 0; +error: + for(size_t i = 0; i < VolumeAt; ++i) mapping_umount(Path, E->Volume[E->VolumeCount - 1 - i].Target); + if(Mounted_devfs) mapping_umount(Path, "/dev"); + if(Mounted_temporary) mapping_umount(Path, "/"); + for(size_t i = 0; i < LayerAt; ++i) mapping_umount(Path, "/"); + if(CreatedPath) rm(Path); + return -1; +} + +static int +stop_fs(filesystem_configuration *E, char const *Path) +{ + for(size_t i = 0; i < E->VolumeCount; ++i) mapping_umount(Path, E->Volume[E->VolumeCount - 1 - i].Target); + mapping_umount(Path, "/dev"); + if(E->Temporary != NULL) mapping_umount(Path, "/"); + for(size_t i = 0; i < E->LayerCount; ++i) mapping_umount(Path, "/"); + if(rm(Path) == -1) + { + fprintf(stderr, "rm -r %s: %s\n", Path, strerror(errno)); + goto error; + } + return 0; +error: + return -1; +} + +static bool +ValidJailName(char const *Name) +{ + for(char const *At = Name; *At; ++At) + { + if(isnumber(*At) == false) return true; + } + return true; +} + +// COMMANDS + +static char const *FilesystemPrefix = "/tmp/sf"; + +int +start(char const *Name, ucl_object_t *configuration) +{ + if(ValidJailName(Name) == false) return -1; + Load_configuration(configuration); + fd Control = -1; + Data.JID = -1; + char Path[PATH_MAX]; + snprintf(Path, sizeof(Path), "%s/%s", FilesystemPrefix, Name); + if(mkdir(FilesystemPrefix, 0755) == -1) + { + if(errno != EEXIST) + { + fprintf(stderr, "mkdir %s: %s\n", FilesystemPrefix, strerror(errno)); + goto error; + } + } + if(start_fs(&Configuration.Filesystem, Path) == -1) goto error; + if(start_jail(&Data.JID, Name, Path) == -1) goto error; + if(NgMkSockNode(NULL, &Control, NULL) == -1) + { + fprintf(stderr, "Failed to create netgraph socket: %s\n", strerror(errno)); + goto error; + } + if(start_interfaces(&Data.Interfaces, &Configuration.Interfaces, Control, Name) == -1) + { + fprintf(stderr, "failed to start interfaces\n"); + goto error; + } + DataLoaded = true; + close(Control); + exec_string_array(&Configuration.Init, false, Data.JID); + jprint_state S; + bzero(&S, sizeof(S)); + S.F = stdout; + Save_data(&S, &Data); + Free_all(); + return 0; +error: + if(Control != -1) close(Control); + if(Data.JID != -1) stop_jail(Data.JID); + Free_all(); + return -1; +} + +int +stop(char const *Name, ucl_object_t *configuration, ucl_object_t *data) +{ + if(ValidJailName(Name) == false) return -1; + Load_configuration(configuration); + Load_data(data); + exec_string_array(&Configuration.Shutdown, true, Data.JID); + fd Control = -1; + if(NgMkSockNode(NULL, &Control, NULL) == -1) + { + fprintf(stderr, "Failed to create netgraph socket: %s\n", strerror(errno)); + goto error; + } + if(stop_interfaces(&Data.Interfaces, &Configuration.Interfaces, Control, Name) == -1) goto error; + if(stop_jail(Data.JID) == -1) goto error; + char Path[PATH_MAX]; + snprintf(Path, sizeof(Path), "%s/%s", FilesystemPrefix, Name); + if(stop_fs(&Configuration.Filesystem, Path) == -1) goto error; + Free_all(); + return 0; +error: + Free_all(); + return -1; +} + +int +get_endpoint(char const *Name, ucl_object_t *configuration, ucl_object_t *data, char const *Interface) +{ + if(ValidJailName(Name) == false) return -1; + Load_configuration(configuration); + Load_data(data); + for(size_t i = 0; i < Configuration.Interfaces.Count; ++i) + { + interface_configuration *C = Configuration.Interfaces._ + i; + if(strcmp(C->Name, Interface) == 0) + { + interface *E = Data.Interfaces._ + i; + jprint_state S; + bzero(&S, sizeof(S)); + S.F = stdout; + JPrintObjectBegin(&S); + JPrintMember(&S, "address"); + char Address[NG_PATHSIZ]; + snprintf(Address, sizeof(Address), "[%x]:", E->ID); + JPrint_string(&S, Address); + JPrintMember(&S, "hook"); + JPrint_string(&S, "ether"); + JPrintObjectEnd(&S); + Free_all(); + return 0; + } + } + Free_all(); + return -1; +} + +int +cmd(char const *Name, ucl_object_t *configuration, ucl_object_t *data, size_t ArgCount, char **Arg) +{ + if(ValidJailName(Name) == false) return -1; + Load_configuration(configuration); + Load_data(data); + if(ArgCount < 1) usage(); + char const *Command = Arg[0]; + --ArgCount; + ++Arg; + if(strcmp(Command, "sh") == 0) + { + size_t ExtraArguments = 2; + char **Args = alloca(sizeof(char *) * (ArgCount + ExtraArguments + 1)); + Args[0] = "jexec"; + Args[1] = strdup(Name); + memcpy(Args + ExtraArguments, Arg, sizeof(char *) * ArgCount); + Args[ArgCount + ExtraArguments] = NULL; + execvp("jexec", Args); + fprintf(stderr, "execvp failed: %s\n", strerror(errno)); + exit(-1); + } else if(strcmp(Command, "wireshark") == 0) + { + if(ArgCount < 1) usage(); + char const *Interface = Arg[0]; + --ArgCount; + ++Arg; + fd Pipe[2]; + if(pipe(Pipe) == -1) + { + fprintf(stderr, "pipe: %s\n", strerror(errno)); + return -1; + } + pid_t PID = fork(); + if(PID == -1) + { + fprintf(stderr, "fork: %s\n", strerror(errno)); + return -1; + } else if(PID == 0) + { + dup2(Pipe[1], STDOUT_FILENO); + close(Pipe[0]); + close(Pipe[1]); + close_all(); + if(jail_attach(Data.JID)) + { + fprintf(stderr, "jail_attach(%d): %s\n", Data.JID, strerror(errno)); + exit(-1); + } + execlp("tcpdump", "tcpdump", "-s", "-0", "-U", "-w", "-", "-i", Interface, NULL); + fprintf(stderr, "execlp failed: %s\n", strerror(errno)); + exit(-1); + } else + { + dup2(Pipe[0], STDIN_FILENO); + close(Pipe[0]); + close(Pipe[1]); + close_all(); + char Title[64]; + snprintf(Title, sizeof(Title), "gui.window_title:%s:%s", Name, Interface); + execlp("wireshark", "wireshark", "-o", Title, "-k", "-i", "-", NULL); + fprintf(stderr, "execlp failed: %s\n", strerror(errno)); + exit(-1); + } + } else + { + fprintf(stderr, "unknown command %s\n", Command); + usage(); + } +} + +int +mod(char const *Name, ucl_object_t *configuration, ucl_object_t *data, size_t ArgCount, char **Arg) +{ + if(ValidJailName(Name) == false) return -1; + Load_configuration(configuration); + Load_data(data); + UNUSED(ArgCount, Arg); + usage(); +} -- cgit v1.2.3