commit fb1a36c0a6028fb69d26ed62cafee077a0c345ce from: Omar Polo date: Sun Jan 09 15:33:55 2022 UTC restructure project and switch build system use by default the OpenBSD mk infrastructure to build and test all the kamid components. commit - 507b56e50b583013fc420b2f728f2506dd6243bd commit + fb1a36c0a6028fb69d26ed62cafee077a0c345ce blob - d66373e2c39f38c9784d99526c519fbd3bae5a86 blob + 9bed9c14e5443d7a8d3b464cfde197fd0ad7abf9 --- .gitignore +++ .gitignore @@ -1,37 +1,14 @@ -Makefile -Makefile.in -aclocal.m4 -autom4te.cache -compile -config.h.in -configure -depcomp -install-sh -missing -ylwrap -stamp-h1 -config.guess -config.h -config.log -config.status -config.sub -.deps *~ +**/obj +**/tags +**/*.d +**/*.o -*.log -*.trs +**/parse.c +**/parse.h -compile_flags.txt - -kamid -kamictl -kamiftp -kamirepl -*.o -kamid.conf -parse.c -np.c -ninepscript - -*.crt -*.pem +kamictl/kamictl +kamid/kamid +kamiftp/kamiftp +kamirepl/kamirepl +ninepscript/ninepscript blob - 13e41c7900a696a6570e9a462d3bef9d850745a0 (mode 644) blob + /dev/null --- 9p.7 +++ /dev/null @@ -1,393 +0,0 @@ -.\" Copyright (c) 2021 Omar Polo -.\" -.\" Permission to use, copy, modify, and distribute this software for any -.\" purpose with or without fee is hereby granted, provided that the above -.\" copyright notice and this permission notice appear in all copies. -.\" -.\" THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES -.\" WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF -.\" MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR -.\" ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES -.\" WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN -.\" ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF -.\" OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. -.\" -.Dd $Mdocdate: July 30 2021 $ -.Dt 9P 7 -.Os -.Sh NAME -.Nm 9P -.Nd Simple Distributed File System -.Sh DESCRIPTION -.Nm -is a protocol that implements a distributed file systems. -It provides primitives to manage -.Pq create, read, write and delete -sets of files remotely. -These files don't necessarily need to be actually stored on a disk, -they may be, for example, synthesise on demand from external sources. -.Pp -A client transmits requests -.Pq T-messages -to a server, which returns replies -.Pq R-messages -to the client. -The combined acts of transmitting a request of a particular type and -receiving a reply is called a transaction of that type. -.Pp -Each message consists of a sequence of bytes mostly grouped in one, -two or four integer fields transmitted in little-endian order -.Pq least significant byte first . -Data items of larger or variable lengths are represented by a two-byte -field specifying the length followed by the actual data. -The only exception to this rule are QIDs, thirteen byte long -objects, that are sent as-is. -.Pp -Text strings are represented with a two-byte count and the sequence of -UNICODE codepoints encoded in UTF-8. -Text strings in 9p are not NUL-terminated. -The NUL-terminator is illegal in all text strings and thus excluded -from paths, user names and so on. -.Pp -Fields are hereafter denoted as -.Bd -literal -offset indent -type[1] tag[2] fid[4] -.Ed -.Pp -to indicate that type is one byte long, tag two and fid four. -Strings are denoted as name[s] and are sent on the wire as -.Bd -literal -offset indent -length[2] string[length] -.Ed -.Pp -A qid, described later, is a 13-byte value that is sent on the wire as -.Bd -literal -offset indent -type[1] version[4] path[8] -.Ed -.Sh MESSAGE STRUCTURE -Every message has a header with the following fields: -.Bd -literal -offset indent -len[4] type[1] tag[2] -.Ed -.Pp -where len indicates the overall length of the message, including -itself; type is one byte indicating the type of the message and the -tag is a number choosen by the client that indicate uniquely the -request. -Then follows an optional body whose structure depends on the type of -the message. -.Pp -The message types are as follows: -.Pq the header is omitted for brevity -.Bl -tag -width versionxx -.It Ic version -Negotiate the version and maximum message size. -.Bd -literal -msize[4] version[s] -msize[4] version[s] -.Ed -.Pp -The -.Ic version -request must be the first message sent, and the client cannot issue -further requests until receiving the Rversion reply. -.Cm tag -should be -.Dv NOTAG -.Pq \-1 or 255 . -The client suggest a -.Cm msize -.Pq the maximum size for packets -and the protocol version used, the server replies with a -.Cm msize -smaller or equal to the one proposed by the client. -The version string must always begin with the two character -.Dq 9P . -If the server don't understand the client required version, should -reply with a Rversion using the version string -.Dq unknown -and not use a Rerror. -.It Ic attach -Populate the namespace -.Bd -literal -fid[4] afid[4] uname[s] aname[s] -qid[13] -.Ed -.Pp -The -.Ic attach -message binds the given -.Ar fid -to the root of the file tree identified by -.Ar aname . -.Ar uname -identifies the user and -.Ar afid -specifies a fid previously established by an auth message, or the -special -.Dv NOFID -value -.Pq defined as (u32int)~0 -if the authentication is not required. -.It Ic clunk -Close fids. -.Bd -literal -fid[4] -.Aq empty response -.Ed -.Pp -Once a fid has been clunked -.Pq closed -it becomes -.Dq free -and the same value can be used for subsequential -.Ic walk -or -.Ic attach -requests. -.Pp -The actual file on the disk is not remove unless it was opened with the -.Dv ORCLOSE -flag. -.It Ic error -Return an error string. -.Bd -literal -ename[s] -.Ed -.Pp -The Rerror message is used to return an error string describing the -failure of a request. -The -.Cm tag -indicates the failed request. -.Pp -Note that there isn't a -.Ic Terror -request for obvious reason and it's not possible for a server to reply to -a -.Ic Tversion -or -.Ic Tflush -using -.Ic Rerror . -.It Ic flush -Abort an ongoing operation. -.Bd -literal -oldtag[2] -.Aq empty response -.Ed -.Pp -Given the asynchronous nature of the protocol, the server may respond to -the pending request before responding to the -.Ic Tflush -and is possible for a client to send multiple -.Ic Tflush -for the same operation. -The client must wait to receive a corresponding -.Ic Rflush -before reusing -.Ar oldtag -for subsequent messages. -.Pp -If a response for -.Ar oldtag -is received before the -.Ic Rflush -reply, the client must assume that the operation was completed with success -.Pq fid allocated, files created, ... -If no response is received before the -.Ic Rflush -then the transaction is considered to have been successfully cancelled. -.Pp -Note that the tag of this request and the corresponding reply is NOT -.Ar oldtag -but a new tag value. -.It Ic walk -Traverse a file tree. -.Bd -literal -fid[4] newfid[4] nwname[2] nwname*(wname[s]) -nwqid[2] nwqid*(qid[13]) -.Ed -.Pp -The -.Ar nwname -components are walked in order starting from -.Ar fid -.Pq which must point to a directory -and, if successful, -.Ar newfid -is associated to the reached file. -.Pp -It is possible for -.Ar fid -and -.Ar newfid -to be equal, in this case the fid is -.Dq mutated , -otherwise -.Ar newfid -must be unused. -As a special case, a walk of zero components duplicates the fid. -.Pp -If the first element cannot be walked for any reason an -.Ic Rerror -is returned. -Otherwise, -.Ic Rwalk -is returned with a number of qids equal to the file viside by the walk. -A client can thus detect a walk when that the replied -.Ar nwqid -number is not equal to the -.Ar nwname -field in the request. -Only when walk return successfully -.Ar newfid -will be affected. -.Pp -A maximum of 16 component can be used per walk request. -.It Ic open -Prepare a fid for I/O. -.Bd -literal -fid[4] mode[1] -qid[13] iounit[4] -.Ed -.Pp -.Ar mode -determines the type of I/O: -.Bl -tag -width Ds -offset indent -compact -.It 0 Dv OREAD -.It 1 Dv OWRITE -.It 2 Dv ORDWD -.It 3 Dv OEXEC -.El -.Pp -The returned -.Ar iounit -is the optimal blocksize for I/O. -.It Ic create -Create a file -.Bd -literal -fid[4] name[s] perm[4] mode[1] -qid[13] iounit[4] -.Ed -.Pp -The call attempts to create a file named -.Ar name -in the directory identified by -.Ar fid -according to -.Ar perm -and then to open it with -.Ar mode -into the given -.Ar fid . -.Pp -It is illegal to use an already opened -.Ar fid -or to attempt to create the -.Dq \&. -or -.Dq .. -entries. -.It Ic read -Read data at offset from file -.Bd -literal -fid[4] offset[8] count[4] -count[4] data[count] -.Ed -.Pp -.Ar fid -must have been prepared for I/O with a previous -.Ic open -call. -The returned -.Ar count -is zero when reaching end-of-file and may be lesser than what requested. -.Pp -Directories are a stream of stat structures, as described in -.Ic stat , -and for them the read request message must have offset equal to zero or -the value of -.Ar offset -in the previous read on the directory plus the number of bytes returned -in the previous read. -Thus, is not possible to seek into directories except for rewinding. -.It Ic write -Write data at offset -.Bd -literal -fid[4] offset[8] count[4] data[count] -count[4] -.Ed -.It Ic stat -get file status -.Bd -literal -fid[4] -stat[n] -.Ed -.Pp -The stat structure is made by the following fields: -.Bl -tag -width twelveletters -compact -.It size[2] -total byte count of the following data -.It type[2] -for kernel use -.It dev[4] -for kernel use -.It qid[13] -server unique identifier of the file -.It mode[4] -permissions and flags -.It atime[4] -last access time -.It mtime[4] -last modification time -.It length[8] -length of file in bytes -.It name[s] -file name -(must be -.Dq / -if the file is the root directory of the server) -.It uid[s] -owner name -.It gid[s] -group name -.It muid[s] -name of the user who last modified the file. -.El -.It Ic remove -Remove a file -.Bd -literal -fid[4] -.Aq empty response -.Ed -.El -.\" .Sh 9P2000.L EXTENSIONS -.\" .Xr kamid 8 -.\" supports also a subset of the -.\" .Sq 9P2000.L -.\" dialect. -.\" The supported messages are -.\" .Bl -tag -width readdir -.\" .It Ic readdir -.\" Read directory entries -.\" .Bd -literal -.\" fid[4] offset[8] count[4] -.\" count[4] data[count] -.\" .Ed -.\" .Pp -.\" Each directory entry is described by a variable-length record: -.\" .Ql qid[13] offset[8] type[1] name[s] . -.\" Offset is zero upon the first call. -.\" If the -.\" .Ar count -.\" field in the -.\" .Ic Rreaddir -.\" response is not zero then more data is available. -.\" .Pp -.\" .Ar count -.\" is allowed to be zero in the request. -.\" .El -.Sh SEE ALSO -.Xr utf8 7 , -.Xr kamid 8 blob - 1fc4c49d81df79abe7260d7022578376090f177a (mode 644) blob + /dev/null --- 9pclib.c +++ /dev/null @@ -1,252 +0,0 @@ -/* - * Copyright (c) 2021 Omar Polo - * - * Permission to use, copy, modify, and distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. - * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - */ - -#include "compat.h" - -#include -#include -#include - -#include "9pclib.h" -#include "kamid.h" -#include "log.h" -#include "utils.h" - -uint16_t iota_tag; - -struct evbuffer *evb; - -void -write_hdr(uint32_t len, uint8_t type, uint16_t tag) -{ - len += HEADERSIZE; - - log_debug("enqueuing a packet; len=%"PRIu32" type=%d[%s] tag=%d", - len, type, pp_msg_type(type), tag); - - len = htole32(len); - /* type is one byte, no endiannes issues */ - tag = htole16(tag); - - evbuffer_add(evb, &len, sizeof(len)); - evbuffer_add(evb, &type, sizeof(type)); - evbuffer_add(evb, &tag, sizeof(tag)); -} - -void -write_hdr_auto(uint32_t len, uint8_t type) -{ - if (++iota_tag == NOTAG) - ++iota_tag; - write_hdr(len, type, iota_tag); -} - -void -write_str(uint16_t len, const char *str) -{ - uint16_t l = len; - - len = htole16(len); - evbuffer_add(evb, &len, sizeof(len)); - evbuffer_add(evb, str, l); -} - -void -write_str_auto(const char *str) -{ - write_str(strlen(str), str); -} - -void -write_buf(const void *d, uint32_t len) -{ - write_32(len); - evbuffer_add(evb, d, len); -} - -void -write_64(uint64_t x) -{ - x = htole64(x); - evbuffer_add(evb, &x, sizeof(x)); -} - -void -write_32(uint32_t fid) -{ - fid = htole32(fid); - evbuffer_add(evb, &fid, sizeof(fid)); -} - -void -write_16(uint16_t tag) -{ - tag = htole16(tag); - evbuffer_add(evb, &tag, sizeof(tag)); -} - -void -write_8(uint8_t x) -{ - evbuffer_add(evb, &x, sizeof(x)); -} - - - -void -tversion(const char *v, uint32_t msize) -{ - uint32_t len; - uint16_t sl; - - sl = strlen(v); - - /* msize[4] version[s] */ - len = sizeof(msize) + sizeof(sl) + sl; - write_hdr(len, Tversion, NOTAG); - write_32(msize); - write_str(sl, v); -} - -void -tattach(uint32_t fid, uint32_t afid, const char *uname, const char *aname) -{ - uint32_t len; - uint16_t ul, al; - - ul = strlen(uname); - al = strlen(aname); - - /* fid[4] afid[4] uname[s] aname[s] */ - len = sizeof(fid) + sizeof(afid) + sizeof(ul) + ul - + sizeof(al) + al; - write_hdr_auto(len, Tattach); - write_fid(fid); - write_fid(afid); - write_str(ul, uname); - write_str(al, aname); -} - -void -tclunk(uint32_t fid) -{ - uint32_t len; - - /* fid[4] */ - len = sizeof(fid); - write_hdr_auto(len, Tclunk); - write_fid(fid); -} - -void -tflush(uint16_t oldtag) -{ - uint32_t len; - - /* oldtag[2] */ - len = sizeof(oldtag); - write_hdr_auto(len, Tflush); - write_tag(oldtag); -} - -void -twalk(uint32_t fid, uint32_t newfid, const char **wnames, size_t nwname) -{ - size_t i; - uint32_t len; - - /* fid[4] newfid[4] nwname[2] nwname*(wname[s]) */ - len = sizeof(fid) + sizeof(newfid) + 2; - for (i = 0; i < nwname; ++i) - len += 2 + strlen(wnames[i]); - - write_hdr_auto(len, Twalk); - write_fid(fid); - write_fid(newfid); - write_16(nwname); - for (i = 0; i < nwname; ++i) - write_str_auto(wnames[i]); -} - -void -topen(uint32_t fid, uint8_t mode) -{ - uint32_t len; - - /* fid[4] mode[1] */ - len = sizeof(fid) + sizeof(mode); - write_hdr_auto(len, Topen); - write_fid(fid); - write_8(mode); -} - -void -tcreate(uint32_t fid, const char *name, uint32_t perm, uint8_t mode) -{ - uint32_t len; - uint16_t nl; - - /* fid[4] name[s] perm[4] mode[1] */ - nl = strlen(name); - len = sizeof(fid) + sizeof(nl) + nl + sizeof(perm) + sizeof(mode); - write_hdr_auto(len, Tcreate); - write_fid(fid); - write_str(nl, name); - write_32(perm); - write_8(mode); -} - -void -tread(uint32_t fid, uint64_t off, uint32_t count) -{ - uint32_t len; - - /* fid[4] off[8] count[4] */ - len = sizeof(fid) + sizeof(off) + sizeof(count); - write_hdr_auto(len, Tread); - write_fid(fid); - write_off(off); - write_32(count); -} - -void -twrite(uint32_t fid, uint64_t off, const void *data, uint32_t count) -{ - uint32_t len; - - /* fid[4] off[8] count[4] data[count] */ - len = sizeof(fid) + sizeof(off) + sizeof(count) + count; - write_hdr_auto(len, Twrite); - write_fid(fid); - write_off(off); - write_buf(data, count); -} - -void -tstat(uint32_t fid) -{ - /* fid[4] */ - write_hdr_auto(sizeof(fid), Tstat); - write_fid(fid); -} - -void -tremove(uint32_t fid) -{ - /* fid[4] */ - write_hdr_auto(sizeof(fid), Tremove); - write_fid(fid); -} blob - /dev/null blob + 1b2fbf9ebe7ea86acf1e7d76453c86314a33db66 (mode 644) --- /dev/null +++ Makefile @@ -0,0 +1,30 @@ +SUBDIR = +SUBDIR += kamictl +SUBDIR += kamid +SUBDIR += kamiftp +SUBDIR += kamirepl +SUBDIR += ninepscript + +.if make(regress) || make(obj) || make(clean) || make(release) +SUBDIR += regress +.endif + +.if make(tags) || make(cleandir) +SUBDIR += lib +.endif + +.include "kamid-version.mk" + +release: clean + sed -i -e 's/_RELEASE=No/_RELEASE=Yes/' kamid-version.mk + ${MAKE} dist + sed -i -e 's/_RELEASE=Yes/_RELEASE=No/' kamid-version.mk + +dist: clean + mkdir /tmp/kamid-${KAMID_VERSION} + pax -rw * /tmp/kamid-${KAMID_VERSION} + find /tmp/kamid-${KAMID_VERSION} -name obj -type d -delete + tar -C /tmp -zcf kamid-${KAMID_VERSION}.tar.gz kamid-${KAMID_VERSION} + rm -rf /tmp/kamid-${KAMID_VERSION} + +.include blob - 8f3dce09953080d4a0196e351d08a1bc474801f0 (mode 644) blob + /dev/null --- 9pclib.h +++ /dev/null @@ -1,55 +0,0 @@ -/* - * Copyright (c) 2021 Omar Polo - * - * Permission to use, copy, modify, and distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. - * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - */ - -/* 9p client library */ - -#ifndef NPCLIB_H -#define NPCLIB_H - -#include "compat.h" - -#include - -extern uint16_t iota_tag; -extern struct evbuffer *evb; - -void write_hdr(uint32_t, uint8_t, uint16_t); -void write_hdr_auto(uint32_t, uint8_t); -void write_str(uint16_t, const char *); -void write_str_auto(const char *); -void write_buf(const void *, uint32_t); -void write_64(uint64_t); -void write_32(uint32_t); -void write_16(uint16_t); -void write_8(uint8_t); - -#define write_off write_64 -#define write_fid write_32 -#define write_tag write_16 - -void tversion(const char *, uint32_t); -void tattach(uint32_t, uint32_t, const char *, const char *); -void tclunk(uint32_t); -void tflush(uint16_t); -void twalk(uint32_t, uint32_t, const char **, size_t); -void topen(uint32_t, uint8_t); -void tcreate(uint32_t, const char *, uint32_t, uint8_t); -void tread(uint32_t, uint64_t, uint32_t); -void twrite(uint32_t, uint64_t, const void *, uint32_t); -void tstat(uint32_t); -void tremove(uint32_t); - -#endif blob - /dev/null blob + 03a3fcfd656b454b409ec8f124c32499814a081b (mode 644) --- /dev/null +++ Makefile.inc @@ -0,0 +1,17 @@ +CPPFLAGS += -DKAMID_VERSION="\"${KAMID_VERSION}\"" + +.if ${KAMID_RELEASE} == Yes +PREFIX ?= /usr/local +BINDIR ?= ${PREFIX}/bin +MANDIR ?= ${PREFIX}/man/man +.else +CFLAGS += -Werror -Wall -Wmissing-prototypes -Wstrict-prototypes +CFLAGS += -Wwrite-strings -Wno-unused-parameter +PREFIX ?= ${HOME} +BINDIR ?= ${PREFIX}/bin + +BINOWN ?= ${USER} +.if !defined(BINGRP) +BINGRP != id -g -n +.endif +.endif blob - 02f6fb699db91df77c25a037d80d3d05bbf03f3f (mode 644) blob + /dev/null --- Makefile.am +++ /dev/null @@ -1,90 +0,0 @@ -bin_PROGRAMS = kamictl kamid kamiftp kamirepl -check_PROGRAMS = ninepscript - -EXTRA_kamid_SOURCES = compat/ohash.h \ - compat/imsg.h \ - compat/queue.h \ - compat/tree.h \ - compat/vis.c \ - compat/vis.h - -kamictl_SOURCES = compat.h \ - ctl_parser.c \ - ctl_parser.h \ - kamictl.c \ - kamid.h \ - log.c \ - log.h \ - sandbox.c \ - sandbox.h - -kamid_SOURCES = client.c \ - client.h \ - control.c \ - control.h \ - compat.h \ - kamid.c \ - kamid.h \ - listener.c \ - listener.h \ - log.c \ - log.h \ - parse.y \ - sandbox.c \ - sandbox.h \ - table.c \ - table.h \ - table_static.c \ - utils.c \ - utils.h - -kamiftp_SOURCES = 9pclib.c \ - 9pclib.h \ - ftp.c \ - kamid.h \ - log.c \ - log.h \ - utils.c \ - utils.h - -kamirepl_SOURCES = 9pclib.c \ - 9pclib.h \ - kamid.h \ - kamirepl.c \ - log.c \ - log.h \ - utils.c \ - utils.h - -ninepscript_SOURCES = client.c \ - client.h \ - compat.h \ - log.c \ - log.h \ - np.y \ - sandbox.c \ - script.c \ - script.h \ - utils.c \ - utils.h - -AM_CFLAGS = @AM_CFLAGS@ -LDADD = $(LIBOBJS) - -kamiftp_LDFLAGS = @KAMIFTP_LIBS@ - -BUILT_SOURCES = compile_flags.txt -CLEANFILES = compile_flags.txt - -dist_doc_DATA = README.md LICENSE -dist_man1_MANS = kamiftp.1 kamirepl.1 -dist_man5_MANS = kamid.conf.5 -dist_man7_MANS = 9p.7 -dist_man8_MANS = kamictl.8 kamid.8 - -EXTRA_DIST = regress ninepscript.5 ninepscript.8 -TESTS = run-tests.sh -run-tests.sh: ninepscript - -compile_flags.txt: - printf "%s\n" ${CFLAGS} > compile_flags.txt blob - fd41e06c1f4122da4d2d057b858dea606c200931 blob + 5d6c473507d76e97317947a11b206258c7280214 --- TODO +++ TODO @@ -34,6 +34,6 @@ Stuff that's still to-do One solution may be to not use readdir and roll our own on top of getdents(2) and lseek(2), but that may not be portable. - - reply with an Rerror in listener.c:/^client_read when we get an - invalid message (too big or too small) before closing the + - reply with an Rerror in kamid/listener.c:/^client_read when we get + an invalid message (too big or too small) before closing the connection. blob - /dev/null blob + 9074383dc2bbfc282ccb63be5fc10a3585b6c817 (mode 644) --- /dev/null +++ kamictl/Makefile @@ -0,0 +1,18 @@ +.PATH:${.CURDIR}/../lib + +.include "../kamid-version.mk" + +PROG= kamictl +SRCS= ctl_parser.c kamictl.c log.c sandbox.c +MAN= kamictl.8 + +CPPFLAGS= -I${.CURDIR}/../lib -I${.CURDIR}/../kamid/ -I${.CURDIR} + +LDADD+= -lutil +DPADD+= ${LIBUTIL} + +.if ${KAMID_RELEASE} != Yes +NOMAN= Yes +.endif + +.include blob - /dev/null blob + b0515084f057d8663be62be76614f58434f45c2c (mode 644) --- /dev/null +++ kamictl/ctl_parser.c @@ -0,0 +1,158 @@ +/* + * Copyright (c) 2004 Esben Norby + * Copyright (c) 2003, 2004 Henning Brauer + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "ctl_parser.h" +#include "kamid.h" + +enum token_type { + NOTOKEN, + ENDTOKEN, + KEYWORD, +}; + + +struct token { + enum token_type type; + const char *keyword; + int value; + const struct token *next; +}; + +static const struct token t_main[]; +static const struct token t_log[]; + +static const struct token t_main[] = { + {KEYWORD, "reload", RELOAD, NULL}, + {KEYWORD, "log", NONE, t_log}, + {ENDTOKEN, "", NONE, NULL}, +}; + +static const struct token t_log[] = { + {KEYWORD, "verbose", LOG_VERBOSE, NULL}, + {KEYWORD, "brief", LOG_BRIEF, NULL}, + {ENDTOKEN, "", NONE, NULL}, +}; + +static const struct token *match_token(const char *, const struct token *, + struct parse_result *); +static void show_valid_args(const struct token *); + +struct parse_result * +parse(int argc, char **argv) +{ + static struct parse_result res; + const struct token *table = t_main; + const struct token *match; + + memset(&res, 0, sizeof(res)); + + while (argc >= 0) { + if ((match = match_token(argv[0], table, &res)) == NULL) { + fprintf(stderr, "valid commands/args:\n"); + show_valid_args(table); + return NULL; + } + + argc--; + argv++; + + if (match->type == NOTOKEN || match->next == NULL) + break; + + table = match->next; + } + + if (argc > 0) { + fprintf(stderr, "superfluous argument: %s\n", argv[0]); + return NULL; + } + + return &res; +} + +static const struct token * +match_token(const char *word, const struct token *table, + struct parse_result *res) +{ + size_t i, match; + const struct token *t = NULL; + + match = 0; + + for (i = 0; table[i].type != ENDTOKEN; i++) { + switch (table[i].type) { + case NOTOKEN: + if (word == NULL || strlen(word) == 0) { + match++; + t = &table[i]; + } + break; + case KEYWORD: + if (word != NULL && strncmp(word, table[i].keyword, + strlen(word)) == 0) { + match++; + t = &table[i]; + if (t->value) + res->action = t->value; + } + break; + case ENDTOKEN: + break; + } + } + + if (match != 1) { + if (word == NULL) + fprintf(stderr, "missing argument:\n"); + else if (match > 1) + fprintf(stderr, "ambiuous argument: %s\n", word); + else if (match < 1) + fprintf(stderr, "unknown argument: %s\n", word); + return NULL; + } + + return t; +} + +static void +show_valid_args(const struct token *table) +{ + int i; + + for (i = 0; table[i].type != ENDTOKEN; i++) { + switch (table[i].type) { + case NOTOKEN: + fprintf(stderr, " \n"); + break; + case KEYWORD: + fprintf(stderr, " %s\n", table[i].keyword); + break; + case ENDTOKEN: + break; + } + } +} blob - /dev/null blob + 1c41496f316a505b67099be11f5f5d3deec51a7b (mode 644) --- /dev/null +++ kamictl/ctl_parser.h @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2004 Esben Norby + * Copyright (c) 2003, 2004 Henning Brauer + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef CTL_PARSER_H +#define CTL_PARSER_H + +enum actions { + NONE, + LOG_VERBOSE, + LOG_BRIEF, + RELOAD, +}; + +struct parse_result { + enum actions action; +}; + +struct parse_result *parse(int, char **); + +#endif blob - /dev/null blob + b263ba9f41d13275d28d6a215aa1419302259a89 (mode 644) --- /dev/null +++ kamictl/kamictl.8 @@ -0,0 +1,61 @@ +.\" Copyright (c) 2021 Omar Polo +.\" +.\" Permission to use, copy, modify, and distribute this software for any +.\" purpose with or without fee is hereby granted, provided that the above +.\" copyright notice and this permission notice appear in all copies. +.\" +.\" THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +.\" WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +.\" MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +.\" ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +.\" WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +.\" ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +.\" OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +.\" +.Dd $Mdocdate: July 07 2021 $ +.Dt KAMICTL 8 +.Os +.Sh NAME +.Nm kamictl +.Nd control the kamid daemon +.Sh SYNOPSIS +.Nm +.Op Fl s Ar socket +.Ar command +.Op Ar argument ... +.Sh DESCRIPTION +The +.Nm +program controls the +.Xr kamid 8 +daemon. +.Pp +The following options are available: +.Bl -tag -width Ds +.It Fl s Ar socket +Use +.Ar socket +instead of the default +.Pa /var/run/kamid.sock +to communicate with +.Xr kamid 8 . +.El +.Pp +The following commands are available: +.Bl -tag -width Ds +.It Cm log brief +Disable verbose debug logging. +.It Cm log verbose +Enable verbose debug logging. +.It Cm reload +Reload the configuration file. +.El +.Sh FILES +.Bl -tag -width "/var/run/kamid.sockXX" -compact +.It Pa /var/run/kamid.sock +UNIX-domain socket used for communication with +.Xr kamid 8 . +.El +.Sh SEE ALSO +.Xr kamid.conf 5 , +.Xr kamid 8 blob - /dev/null blob + be14f113641e49a974eaa5f420de82e4b67dccfa (mode 644) --- /dev/null +++ kamictl/kamictl.c @@ -0,0 +1,145 @@ +/* + * Copyright (c) 2021 Omar Polo + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "ctl_parser.h" +#include "kamid.h" +#include "log.h" + +__dead void usage(void); + +struct imsgbuf *ibuf; + +__dead void +usage(void) +{ + /* + * XXX: this will print `kamid' if compat/getprogname.c is + * used. + */ + fprintf(stderr, "usage: %s [-s socket] command [argument ...]\n", + getprogname()); + exit(1); +} + +int +main(int argc, char **argv) +{ + struct sockaddr_un sun; + struct parse_result *res; + struct imsg imsg; + int ctl_sock; + int done = 0; + int n, verbose = 0; + int ch; + const char *sockname; + + log_init(1, LOG_DAEMON); /* Log to stderr. */ + + sockname = KD_SOCKET; + while ((ch = getopt(argc, argv, "s:")) != -1) { + switch (ch) { + case 's': + sockname = optarg; + break; + default: + usage(); + } + } + argc -= optind; + argv += optind; + + /* parse command line */ + if ((res = parse(argc, argv)) == NULL) + exit(1); + + /* connect to control socket */ + if ((ctl_sock = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) + err(1, "socket"); + + memset(&sun, 0, sizeof(sun)); + sun.sun_family = AF_UNIX; + strlcpy(sun.sun_path, sockname, sizeof(sun.sun_path)); + + if (connect(ctl_sock, (struct sockaddr*)&sun, sizeof(sun)) == -1) + err(1, "connect: %s", sockname); + +#ifdef __OpenBSD__ + if (pledge("stdio", NULL) == -1) + err(1, "pledge"); +#endif + + if ((ibuf = calloc(1, sizeof(*ibuf))) == NULL) + err(1, NULL); + imsg_init(ibuf, ctl_sock); + done = 0; + + /* process user request */ + switch (res->action) { + case LOG_VERBOSE: + verbose = 1; + /* fallthrough */ + case LOG_BRIEF: + imsg_compose(ibuf, IMSG_CTL_LOG_VERBOSE, 0, 0, -1, + &verbose, sizeof(verbose)); + puts("logging request sent."); + done = 1; + break; + case RELOAD: + imsg_compose(ibuf, IMSG_CTL_RELOAD, 0, 0, -1, NULL, 0); + puts("reload request sent."); + done = 1; + break; + default: + usage(); + } + + imsg_flush(ibuf); + + /* + * Later we may add commands which requires a response. + */ + while (!done) { + if ((n = imsg_get(ibuf, &imsg)) == -1) + errx(1, "imsg_get error"); + if (n == 0) + break; + + switch (res->action) { + default: + break; + } + imsg_free(&imsg); + } + close(ctl_sock); + free(ibuf); + + return 0; +} blob - 70027cbf3cf809c67ca8b5cff38df304aa29bcf6 (mode 755) blob + /dev/null --- bootstrap +++ /dev/null @@ -1,3 +0,0 @@ -#!/bin/sh - -exec autoreconf -vfi blob - /dev/null blob + 13e41c7900a696a6570e9a462d3bef9d850745a0 (mode 644) --- /dev/null +++ kamid/9p.7 @@ -0,0 +1,393 @@ +.\" Copyright (c) 2021 Omar Polo +.\" +.\" Permission to use, copy, modify, and distribute this software for any +.\" purpose with or without fee is hereby granted, provided that the above +.\" copyright notice and this permission notice appear in all copies. +.\" +.\" THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +.\" WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +.\" MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +.\" ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +.\" WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +.\" ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +.\" OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +.\" +.Dd $Mdocdate: July 30 2021 $ +.Dt 9P 7 +.Os +.Sh NAME +.Nm 9P +.Nd Simple Distributed File System +.Sh DESCRIPTION +.Nm +is a protocol that implements a distributed file systems. +It provides primitives to manage +.Pq create, read, write and delete +sets of files remotely. +These files don't necessarily need to be actually stored on a disk, +they may be, for example, synthesise on demand from external sources. +.Pp +A client transmits requests +.Pq T-messages +to a server, which returns replies +.Pq R-messages +to the client. +The combined acts of transmitting a request of a particular type and +receiving a reply is called a transaction of that type. +.Pp +Each message consists of a sequence of bytes mostly grouped in one, +two or four integer fields transmitted in little-endian order +.Pq least significant byte first . +Data items of larger or variable lengths are represented by a two-byte +field specifying the length followed by the actual data. +The only exception to this rule are QIDs, thirteen byte long +objects, that are sent as-is. +.Pp +Text strings are represented with a two-byte count and the sequence of +UNICODE codepoints encoded in UTF-8. +Text strings in 9p are not NUL-terminated. +The NUL-terminator is illegal in all text strings and thus excluded +from paths, user names and so on. +.Pp +Fields are hereafter denoted as +.Bd -literal -offset indent +type[1] tag[2] fid[4] +.Ed +.Pp +to indicate that type is one byte long, tag two and fid four. +Strings are denoted as name[s] and are sent on the wire as +.Bd -literal -offset indent +length[2] string[length] +.Ed +.Pp +A qid, described later, is a 13-byte value that is sent on the wire as +.Bd -literal -offset indent +type[1] version[4] path[8] +.Ed +.Sh MESSAGE STRUCTURE +Every message has a header with the following fields: +.Bd -literal -offset indent +len[4] type[1] tag[2] +.Ed +.Pp +where len indicates the overall length of the message, including +itself; type is one byte indicating the type of the message and the +tag is a number choosen by the client that indicate uniquely the +request. +Then follows an optional body whose structure depends on the type of +the message. +.Pp +The message types are as follows: +.Pq the header is omitted for brevity +.Bl -tag -width versionxx +.It Ic version +Negotiate the version and maximum message size. +.Bd -literal +msize[4] version[s] +msize[4] version[s] +.Ed +.Pp +The +.Ic version +request must be the first message sent, and the client cannot issue +further requests until receiving the Rversion reply. +.Cm tag +should be +.Dv NOTAG +.Pq \-1 or 255 . +The client suggest a +.Cm msize +.Pq the maximum size for packets +and the protocol version used, the server replies with a +.Cm msize +smaller or equal to the one proposed by the client. +The version string must always begin with the two character +.Dq 9P . +If the server don't understand the client required version, should +reply with a Rversion using the version string +.Dq unknown +and not use a Rerror. +.It Ic attach +Populate the namespace +.Bd -literal +fid[4] afid[4] uname[s] aname[s] +qid[13] +.Ed +.Pp +The +.Ic attach +message binds the given +.Ar fid +to the root of the file tree identified by +.Ar aname . +.Ar uname +identifies the user and +.Ar afid +specifies a fid previously established by an auth message, or the +special +.Dv NOFID +value +.Pq defined as (u32int)~0 +if the authentication is not required. +.It Ic clunk +Close fids. +.Bd -literal +fid[4] +.Aq empty response +.Ed +.Pp +Once a fid has been clunked +.Pq closed +it becomes +.Dq free +and the same value can be used for subsequential +.Ic walk +or +.Ic attach +requests. +.Pp +The actual file on the disk is not remove unless it was opened with the +.Dv ORCLOSE +flag. +.It Ic error +Return an error string. +.Bd -literal +ename[s] +.Ed +.Pp +The Rerror message is used to return an error string describing the +failure of a request. +The +.Cm tag +indicates the failed request. +.Pp +Note that there isn't a +.Ic Terror +request for obvious reason and it's not possible for a server to reply to +a +.Ic Tversion +or +.Ic Tflush +using +.Ic Rerror . +.It Ic flush +Abort an ongoing operation. +.Bd -literal +oldtag[2] +.Aq empty response +.Ed +.Pp +Given the asynchronous nature of the protocol, the server may respond to +the pending request before responding to the +.Ic Tflush +and is possible for a client to send multiple +.Ic Tflush +for the same operation. +The client must wait to receive a corresponding +.Ic Rflush +before reusing +.Ar oldtag +for subsequent messages. +.Pp +If a response for +.Ar oldtag +is received before the +.Ic Rflush +reply, the client must assume that the operation was completed with success +.Pq fid allocated, files created, ... +If no response is received before the +.Ic Rflush +then the transaction is considered to have been successfully cancelled. +.Pp +Note that the tag of this request and the corresponding reply is NOT +.Ar oldtag +but a new tag value. +.It Ic walk +Traverse a file tree. +.Bd -literal +fid[4] newfid[4] nwname[2] nwname*(wname[s]) +nwqid[2] nwqid*(qid[13]) +.Ed +.Pp +The +.Ar nwname +components are walked in order starting from +.Ar fid +.Pq which must point to a directory +and, if successful, +.Ar newfid +is associated to the reached file. +.Pp +It is possible for +.Ar fid +and +.Ar newfid +to be equal, in this case the fid is +.Dq mutated , +otherwise +.Ar newfid +must be unused. +As a special case, a walk of zero components duplicates the fid. +.Pp +If the first element cannot be walked for any reason an +.Ic Rerror +is returned. +Otherwise, +.Ic Rwalk +is returned with a number of qids equal to the file viside by the walk. +A client can thus detect a walk when that the replied +.Ar nwqid +number is not equal to the +.Ar nwname +field in the request. +Only when walk return successfully +.Ar newfid +will be affected. +.Pp +A maximum of 16 component can be used per walk request. +.It Ic open +Prepare a fid for I/O. +.Bd -literal +fid[4] mode[1] +qid[13] iounit[4] +.Ed +.Pp +.Ar mode +determines the type of I/O: +.Bl -tag -width Ds -offset indent -compact +.It 0 Dv OREAD +.It 1 Dv OWRITE +.It 2 Dv ORDWD +.It 3 Dv OEXEC +.El +.Pp +The returned +.Ar iounit +is the optimal blocksize for I/O. +.It Ic create +Create a file +.Bd -literal +fid[4] name[s] perm[4] mode[1] +qid[13] iounit[4] +.Ed +.Pp +The call attempts to create a file named +.Ar name +in the directory identified by +.Ar fid +according to +.Ar perm +and then to open it with +.Ar mode +into the given +.Ar fid . +.Pp +It is illegal to use an already opened +.Ar fid +or to attempt to create the +.Dq \&. +or +.Dq .. +entries. +.It Ic read +Read data at offset from file +.Bd -literal +fid[4] offset[8] count[4] +count[4] data[count] +.Ed +.Pp +.Ar fid +must have been prepared for I/O with a previous +.Ic open +call. +The returned +.Ar count +is zero when reaching end-of-file and may be lesser than what requested. +.Pp +Directories are a stream of stat structures, as described in +.Ic stat , +and for them the read request message must have offset equal to zero or +the value of +.Ar offset +in the previous read on the directory plus the number of bytes returned +in the previous read. +Thus, is not possible to seek into directories except for rewinding. +.It Ic write +Write data at offset +.Bd -literal +fid[4] offset[8] count[4] data[count] +count[4] +.Ed +.It Ic stat +get file status +.Bd -literal +fid[4] +stat[n] +.Ed +.Pp +The stat structure is made by the following fields: +.Bl -tag -width twelveletters -compact +.It size[2] +total byte count of the following data +.It type[2] +for kernel use +.It dev[4] +for kernel use +.It qid[13] +server unique identifier of the file +.It mode[4] +permissions and flags +.It atime[4] +last access time +.It mtime[4] +last modification time +.It length[8] +length of file in bytes +.It name[s] +file name +(must be +.Dq / +if the file is the root directory of the server) +.It uid[s] +owner name +.It gid[s] +group name +.It muid[s] +name of the user who last modified the file. +.El +.It Ic remove +Remove a file +.Bd -literal +fid[4] +.Aq empty response +.Ed +.El +.\" .Sh 9P2000.L EXTENSIONS +.\" .Xr kamid 8 +.\" supports also a subset of the +.\" .Sq 9P2000.L +.\" dialect. +.\" The supported messages are +.\" .Bl -tag -width readdir +.\" .It Ic readdir +.\" Read directory entries +.\" .Bd -literal +.\" fid[4] offset[8] count[4] +.\" count[4] data[count] +.\" .Ed +.\" .Pp +.\" Each directory entry is described by a variable-length record: +.\" .Ql qid[13] offset[8] type[1] name[s] . +.\" Offset is zero upon the first call. +.\" If the +.\" .Ar count +.\" field in the +.\" .Ic Rreaddir +.\" response is not zero then more data is available. +.\" .Pp +.\" .Ar count +.\" is allowed to be zero in the request. +.\" .El +.Sh SEE ALSO +.Xr utf8 7 , +.Xr kamid 8 blob - /dev/null blob + ade79764eaa3d1b8896ff9c6e16ee466ae08cbc5 (mode 644) --- /dev/null +++ kamid/Makefile @@ -0,0 +1,19 @@ +.PATH:${.CURDIR}/../lib + +.include "../kamid-version.mk" + +PROG= kamid +SRCS= client.c control.c kamid.c listener.c log.c parse.y sandbox.c \ + table.c table_static.c utils.c +MAN= kamid.conf.5 9p.7 kamid.8 + +CPPFLAGS= -I${.CURDIR}/../lib -I${.CURDIR} + +LDADD+= -levent -lutil -ltls +DPADD+= ${LIBEVENT} ${LIBUTIL} ${LIBTLS} + +.if ${KAMID_RELEASE} != Yes +NOMAN= Yes +.endif + +.include blob - /dev/null blob + 74734329db79092dc0f40c306b3c44998dd8cf27 (mode 644) --- /dev/null +++ kamid/client.c @@ -0,0 +1,1628 @@ +/* + * Copyright (c) 2021 Omar Polo + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "client.h" +#include "kami.h" +#include "kamid.h" +#include "log.h" +#include "sandbox.h" +#include "utils.h" + +/* + * XXX: atm is difficult to accept messages bigger than MAX_IMSGSIZE + * minus IMSG_HEADER_SIZE, we need something to split messages into + * chunks and receive them one by the other. + * + * CLIENT_MSIZE is thus the maximum message size we can handle now. + */ +#define CLIENT_MSIZE (MAX_IMSGSIZE - IMSG_HEADER_SIZE) + +#define DEBUG_PACKETS 0 + +/* straight outta /src/usr.bin/ssh/scp.c */ +#define TYPE_OVERFLOW(type, val) \ + ((sizeof(type) == 4 && (val) > INT32_MAX) || \ + (sizeof(type) == 8 && (val) > INT64_MAX) || \ + (sizeof(type) != 4 && sizeof(type) != 8)) + +STAILQ_HEAD(dirhead, dir) dirs; +struct dir { + int refcount; + int fd; + STAILQ_ENTRY(dir) entries; +}; + +STAILQ_HEAD(fidhead, fid) fids; +struct fid { + uint32_t fid; + + char fpath[PATH_MAX]; + + /* + * the flags passed to open(2). O_CLOEXEC means ORCLOSE, that + * is to unlink the file upon Tclunk. + */ + int iomode; + + /* + * if fd is not -1 this fid was opened, fd represents its + * file descriptor and iomode the flags passed to open(2). + */ + int fd; + DIR *d; + struct evbuffer *evb; + + /* + * expected offset for Tread against a directory. + */ + uint64_t offset; + + struct qid qid; + struct dir *dir; + STAILQ_ENTRY(fid) entries; +}; + +static struct imsgev *iev_listener; +static struct evbuffer *evb; +static uint32_t peerid; + +static int handshaked; +uint32_t msize; + +static __dead void client_shutdown(void); +static void client_sig_handler(int, short, void *); +static void client_dispatch_listener(int, short, void *); +static void client_privdrop(const char *, const char *); + +static int client_send_listener(int, const void *, uint16_t); + +static void qid_update_from_sb(struct qid *, struct stat *); + +static struct dir *new_dir(int); +static struct dir *dir_incref(struct dir *); +static void dir_decref(struct dir *); + +static struct fid *new_fid(struct dir *, uint32_t, const char *, struct qid *); +static struct fid *fid_by_id(uint32_t); +static void free_fid(struct fid *); + +static void parse_message(const uint8_t *, size_t, + struct np_msg_header *, uint8_t **); + +static void np_write16(struct evbuffer *, uint16_t); +static void np_write32(struct evbuffer *, uint32_t); +static void np_write64(struct evbuffer *, uint64_t); +static void np_header(uint32_t, uint8_t, uint16_t); +static void np_string(struct evbuffer *, uint16_t, const char *); +static void np_qid(struct evbuffer *, struct qid *); +static void do_send(void); + +static void np_version(uint16_t, uint32_t, const char *); +static void np_attach(uint16_t, struct qid *); +static void np_clunk(uint16_t); +static void np_flush(uint16_t); +static void np_walk(uint16_t, int, struct qid *); +static void np_open(uint16_t, struct qid *, uint32_t); +static void np_create(uint16_t, struct qid *, uint32_t); +static void np_read(uint16_t, uint32_t, void *); +static void np_write(uint16_t, uint32_t); +static void np_stat(uint16_t, uint32_t, void *); +static void np_remove(uint16_t); +static void np_error(uint16_t, const char *); +static void np_errno(uint16_t); + +static int np_read8(const char *, const char *, uint8_t *, + const uint8_t **, size_t *); +static int np_read16(const char *, const char *, uint16_t *, + const uint8_t **, size_t *); +static int np_read32(const char *, const char *, uint32_t *, + const uint8_t **, size_t *); +static int np_read64(const char *, const char *, uint64_t *, + const uint8_t **, size_t *); + +#define READSTRERR -1 +#define READSTRTRUNC -2 +static int np_readstr(const char *, const char *, char *, size_t, + const uint8_t **, size_t *); + +#define NPREAD8(f, dst, src, len) np_read8(__func__, f, dst, src, len) +#define NPREAD16(f, dst, src, len) np_read16(__func__, f, dst, src, len) +#define NPREAD32(f, dst, src, len) np_read32(__func__, f, dst, src, len) +#define NPREAD64(f, dst, src, len) np_read64(__func__, f, dst, src, len) + +#define NPREADSTR(f, b, bl, src, len) np_readstr(__func__, f, b, bl, src, len) + +static void tversion(struct np_msg_header *, const uint8_t *, size_t); +static void tattach(struct np_msg_header *, const uint8_t *, size_t); +static void tclunk(struct np_msg_header *, const uint8_t *, size_t); +static void tflush(struct np_msg_header *, const uint8_t *, size_t); +static void twalk(struct np_msg_header *, const uint8_t *, size_t); +static void topen(struct np_msg_header *, const uint8_t *, size_t); +static void tcreate(struct np_msg_header *, const uint8_t *, size_t); +static void tread(struct np_msg_header *, const uint8_t *, size_t); +static void twrite(struct np_msg_header *, const uint8_t *, size_t); +static void tstat(struct np_msg_header *, const uint8_t *, size_t); +static void tremove(struct np_msg_header *, const uint8_t *, size_t); +static void handle_message(struct imsg *, size_t); + +__dead void +client(int debug, int verbose) +{ + struct event ev_sigint, ev_sigterm; + + log_init(debug, LOG_DAEMON); + log_setverbose(verbose); + + setproctitle("client"); + log_procinit("client"); + + log_debug("warming up"); + + event_init(); + + /* Setup signal handlers */ + signal_set(&ev_sigint, SIGINT, client_sig_handler, NULL); + signal_set(&ev_sigterm, SIGTERM, client_sig_handler, NULL); + + signal_add(&ev_sigint, NULL); + signal_add(&ev_sigterm, NULL); + + signal(SIGPIPE, SIG_IGN); + signal(SIGHUP, SIG_IGN); + + /* Setup pipe and event handler to the listener process */ + if ((iev_listener = malloc(sizeof(*iev_listener))) == NULL) + fatal(NULL); + + imsg_init(&iev_listener->ibuf, 3); + iev_listener->handler = client_dispatch_listener; + + /* Setup event handlers. */ + iev_listener->events = EV_READ; + event_set(&iev_listener->ev, iev_listener->ibuf.fd, + iev_listener->events, iev_listener->handler, iev_listener); + event_add(&iev_listener->ev, NULL); + + event_dispatch(); + client_shutdown(); +} + +static __dead void +client_shutdown(void) +{ + if (evb != NULL) + evbuffer_free(evb); + + msgbuf_clear(&iev_listener->ibuf.w); + close(iev_listener->ibuf.fd); + + free(iev_listener); + + log_debug("client exiting"); + exit(0); +} + +static void +client_sig_handler(int sig, short event, void *d) +{ + /* + * Normal signal handler rules don't apply because libevent + * decouples for us. + */ + + switch (sig) { + case SIGINT: + case SIGTERM: + client_shutdown(); + default: + fatalx("unexpected signal %d", sig); + } +} + +#define AUTH_NONE 0 +#define AUTH_USER 1 +#define AUTH_DONE 2 + +static void +client_dispatch_listener(int fd, short event, void *d) +{ + static int auth = AUTH_NONE; + static char username[64] = {0}; + static char dir[PATH_MAX] = {0}; + struct imsg imsg; + struct imsgev *iev = d; + struct imsgbuf *ibuf; + ssize_t n; + int shut = 0; + + ibuf = &iev->ibuf; + + if (event & EV_READ) { + if ((n = imsg_read(ibuf)) == -1 && errno != EAGAIN) + fatal("imsg_read error"); + if (n == 0) /* Connection closed */ + shut = 1; + } + if (event & EV_WRITE) { + if ((n = msgbuf_write(&ibuf->w)) == -1 && errno != EAGAIN) + fatal("msgbuf_write"); + if (n == 0) /* Connection closed */ + shut = 1; + } + + for (;;) { + if ((n = imsg_get(ibuf, &imsg)) == -1) + fatal("%s: imsg_get error", __func__); + if (n == 0) /* No more messages. */ + break; + + switch (imsg.hdr.type) { + case IMSG_AUTH: + peerid = imsg.hdr.peerid; + if (auth) + fatalx("%s: IMSG_AUTH already done", __func__); + auth = AUTH_USER; + ((char *)imsg.data)[IMSG_DATA_SIZE(imsg)-1] = '\0'; + strlcpy(username, imsg.data, sizeof(username)); + break; + case IMSG_AUTH_DIR: + if (auth != AUTH_USER) + fatalx("%s: IMSG_AUTH_DIR not after IMSG_AUTH", + __func__); + auth = AUTH_DONE; + ((char *)imsg.data)[IMSG_DATA_SIZE(imsg)-1] = '\0'; + strlcpy(dir, imsg.data, sizeof(dir)); + client_privdrop(username, dir); + memset(username, 0, sizeof(username)); + memset(dir, 0, sizeof(username)); + break; + case IMSG_BUF: + /* echo! */ + if (!auth) + fatalx("%s: can't handle messages before" + " doing the auth", __func__); + handle_message(&imsg, IMSG_DATA_SIZE(imsg)); + break; + case IMSG_CONN_GONE: + log_debug("closing"); + shut = 1; + break; + default: + log_debug("%s: unexpected imsg %d", + __func__, imsg.hdr.type); + break; + } + imsg_free(&imsg); + } + + if (!shut) + imsg_event_add(iev); + else { + /* This pipe is dead. Remove its event handler. */ + event_del(&iev->ev); + log_debug("pipe closed, shutting down..."); + event_loopexit(NULL); + } +} + +static void +client_privdrop(const char *username, const char *dir) +{ + struct passwd *pw; + + setproctitle("client %s", username); + + if ((pw = getpwnam(username)) == NULL) + fatalx("getpwnam(%s) failed", username); + + if (chroot(dir) == -1) + fatal("chroot"); + if (chdir("/") == -1) + fatal("chdir(\"/\")"); + + if (setgroups(1, &pw->pw_gid) || + setresgid(pw->pw_gid, pw->pw_gid, pw->pw_gid) || + setresuid(pw->pw_uid, pw->pw_uid, pw->pw_uid)) + fatal("can't drop privileges"); + + sandbox_client(); + log_debug("client ready; user=%s dir=%s", username, dir); + + if ((evb = evbuffer_new()) == NULL) + fatal("evbuffer_new"); +} + +static int +client_send_listener(int type, const void *data, uint16_t len) +{ + int ret; + + if ((ret = imsg_compose(&iev_listener->ibuf, type, peerid, 0, -1, + data, len)) != -1) + imsg_event_add(iev_listener); + + return ret; +} + +/* set qid fields from sb */ +static void +qid_update_from_sb(struct qid *qid, struct stat *sb) +{ + qid->path = sb->st_ino; + + /* + * Theoretically (and hopefully!) this should be a 64 bit + * number. Unfortunately, 9P uses 32 bit timestamps. + */ + qid->vers = sb->st_mtim.tv_sec; + + if (S_ISREG(sb->st_mode)) + qid->type = QTFILE; + else if (S_ISDIR(sb->st_mode)) + qid->type = QTDIR; + else if (S_ISLNK(sb->st_mode)) + qid->type = QTSYMLINK; +} + +/* creates a qid given a fd */ +static struct dir * +new_dir(int fd) +{ + struct dir *dir; + + if ((dir = calloc(1, sizeof(*dir))) == NULL) + return NULL; + + dir->fd = fd; + STAILQ_INSERT_HEAD(&dirs, dir, entries); + return dir; +} + +static struct dir * +dir_incref(struct dir *dir) +{ + dir->refcount++; + return dir; +} + +static void +dir_decref(struct dir *dir) +{ + if (--dir->refcount > 0) + return; + + STAILQ_REMOVE(&dirs, dir, dir, entries); + + close(dir->fd); + free(dir); +} + +static struct fid * +new_fid(struct dir *dir, uint32_t fid, const char *path, struct qid *qid) +{ + struct fid *f; + struct qid q; + struct stat sb; + + if (qid == NULL) { + if (fstatat(dir->fd, path, &sb, 0)) { + log_warn("fstatat(%s)", path); + return NULL; + } + qid_update_from_sb(&q, &sb); + qid = &q; + } + + if ((f = calloc(1, sizeof(*f))) == NULL) + return NULL; + + f->dir = dir_incref(dir); + f->fid = fid; + f->fd = -1; + + strlcpy(f->fpath, path, sizeof(f->fpath)); + + memcpy(&f->qid, qid, sizeof(f->qid)); + + STAILQ_INSERT_HEAD(&fids, f, entries); + + return f; +} + +static struct fid * +fid_by_id(uint32_t fid) +{ + struct fid *f; + + STAILQ_FOREACH(f, &fids, entries) { + if (f->fid == fid) + return f; + } + + return NULL; +} + +static void +free_fid(struct fid *f) +{ + int r; + + if (f->fd != -1) { + if (f->d != NULL) + r = closedir(f->d); + else + r = close(f->fd); + + if (r == -1) + fatal("can't close fid %d", f->fid); + + if (f->evb != NULL) + evbuffer_free(f->evb); + + /* try to honour ORCLOSE if requested */ + if (f->iomode & O_CLOEXEC) + unlinkat(f->dir->fd, f->fpath, 0); + } + + dir_decref(f->dir); + + STAILQ_REMOVE(&fids, f, fid, entries); + free(f); +} + +static void +parse_message(const uint8_t *data, size_t len, struct np_msg_header *hdr, + uint8_t **cnt) +{ + size_t olen = len; + + if (!NPREAD32("len", &hdr->len, &data, &len) || + !NPREAD8("type", &hdr->type, &data, &len) || + !NPREAD16("tag", &hdr->tag, &data, &len)) + goto err; + + if (olen != hdr->len) + goto err; + + if (hdr->type < Tversion || + hdr->type >= Tmax || + hdr->type == Terror || + (hdr->type & 0x1) != 0) /* cannot recv a R* */ + goto err; + + hdr->tag = le32toh(hdr->tag); + + *cnt = (uint8_t *)data; + return; + +err: + /* TODO: send a proper message to terminate the connection. */ + fatalx("got invalid message"); +} + +static void +np_write16(struct evbuffer *e, uint16_t x) +{ + x = htole16(x); + evbuffer_add(e, &x, sizeof(x)); +} + +static void +np_write32(struct evbuffer *e, uint32_t x) +{ + x = htole32(x); + evbuffer_add(e, &x, sizeof(x)); +} + +static void +np_write64(struct evbuffer *e, uint64_t x) +{ + x = htole64(x); + evbuffer_add(e, &x, sizeof(x)); +} + +static void +np_writebuf(struct evbuffer *e, size_t len, void *data) +{ + evbuffer_add(e, data, len); +} + +static void +np_header(uint32_t len, uint8_t type, uint16_t tag) +{ + len += HEADERSIZE; + + len = htole32(len); + tag = htole16(tag); + + evbuffer_add(evb, &len, sizeof(len)); + evbuffer_add(evb, &type, sizeof(type)); + evbuffer_add(evb, &tag, sizeof(tag)); +} + +static void +np_string(struct evbuffer *e, uint16_t len, const char *str) +{ + uint16_t l = len; + + len = htole16(len); + evbuffer_add(e, &len, sizeof(len)); + evbuffer_add(e, str, l); +} + +static void +np_qid(struct evbuffer *e, struct qid *qid) +{ + uint64_t path; + uint32_t vers; + + path = htole64(qid->path); + vers = htole32(qid->vers); + + evbuffer_add(e, &qid->type, sizeof(qid->type)); + evbuffer_add(e, &vers, sizeof(vers)); + evbuffer_add(e, &path, sizeof(path)); +} + +static void +do_send(void) +{ + size_t len; + void *data; + + len = EVBUFFER_LENGTH(evb); + data = EVBUFFER_DATA(evb); + +#if DEBUG_PACKETS + hexdump("outgoing packet", data, len); +#endif + client_send_listener(IMSG_BUF, data, len); + evbuffer_drain(evb, len); +} + +static void +np_version(uint16_t tag, uint32_t msize, const char *version) +{ + uint16_t l; + + l = strlen(version); + + msize = htole32(msize); + + np_header(sizeof(msize) + sizeof(l) + l, Rversion, tag); + evbuffer_add(evb, &msize, sizeof(msize)); + np_string(evb, l, version); + do_send(); +} + +static void +np_attach(uint16_t tag, struct qid *qid) +{ + np_header(QIDSIZE, Rattach, tag); + np_qid(evb, qid); + do_send(); +} + +static void +np_clunk(uint16_t tag) +{ + np_header(0, Rclunk, tag); + do_send(); +} + +static void +np_flush(uint16_t tag) +{ + np_header(0, Rflush, tag); + do_send(); +} + +static void +np_walk(uint16_t tag, int nwqid, struct qid *wqid) +{ + int i; + + /* two bytes for the counter */ + np_header(2 + QIDSIZE * nwqid, Rwalk, tag); + np_write16(evb, nwqid); + for (i = 0; i < nwqid; ++i) + np_qid(evb, wqid + i); + + do_send(); +} + +static void +np_open(uint16_t tag, struct qid *qid, uint32_t iounit) +{ + np_header(QIDSIZE + sizeof(iounit), Ropen, tag); + np_qid(evb, qid); + np_write32(evb, iounit); + do_send(); +} + +static void +np_create(uint16_t tag, struct qid *qid, uint32_t iounit) +{ + np_header(QIDSIZE + sizeof(iounit), Rcreate, tag); + np_qid(evb, qid); + np_write32(evb, iounit); + do_send(); +} + +static void +np_read(uint16_t tag, uint32_t count, void *data) +{ + if (sizeof(count) + count + HEADERSIZE >= msize) { + np_error(tag, "Rread would overflow"); + return; + } + + np_header(sizeof(count) + count, Rread, tag); + np_write32(evb, count); + np_writebuf(evb, count, data); + do_send(); +} + +static void +np_write(uint16_t tag, uint32_t count) +{ + np_header(sizeof(count), Rwrite, tag); + np_write32(evb, count); + do_send(); +} + +static void +np_stat(uint16_t tag, uint32_t count, void *data) +{ + if (sizeof(count) + count + HEADERSIZE >= msize) { + np_error(tag, "Rstat would overflow"); + return; + } + + np_header(count, Rstat, tag); + np_writebuf(evb, count, data); + do_send(); +} + +static void +np_remove(uint16_t tag) +{ + np_header(0, Rremove, tag); + do_send(); +} + +static void +np_error(uint16_t tag, const char *errstr) +{ + uint16_t l; + + l = strlen(errstr); + + np_header(sizeof(l) + l, Rerror, tag); + np_string(evb, l, errstr); + do_send(); +} + +static void +np_errno(uint16_t tag) +{ + int saved_errno; + char buf[NL_TEXTMAX] = {0}; + + saved_errno = errno; + + strerror_r(errno, buf, sizeof(buf)); + np_error(tag, buf); + + errno = saved_errno; +} + +static int +np_read8(const char *t, const char *f, uint8_t *dst, const uint8_t **src, + size_t *len) +{ + if (*len < sizeof(*dst)) { + log_warnx("%s: wanted %zu bytes for the %s field but only " + "%zu are available.", t, sizeof(*dst), f, *len); + return -1; + } + + memcpy(dst, *src, sizeof(*dst)); + *src += sizeof(*dst); + *len -= sizeof(*dst); + + return 1; +} + +static int +np_read16(const char *t, const char *f, uint16_t *dst, const uint8_t **src, + size_t *len) +{ + if (*len < sizeof(*dst)) { + log_warnx("%s: wanted %zu bytes for the %s field but only " + "%zu are available.", t, sizeof(*dst), f, *len); + return -1; + } + + memcpy(dst, *src, sizeof(*dst)); + *src += sizeof(*dst); + *len -= sizeof(*dst); + *dst = le16toh(*dst); + + return 1; +} + +static int +np_read32(const char *t, const char *f, uint32_t *dst, const uint8_t **src, + size_t *len) +{ + if (*len < sizeof(*dst)) { + log_warnx("%s: wanted %zu bytes for the %s field but only " + "%zu are available.", t, sizeof(*dst), f, *len); + return -1; + } + + memcpy(dst, *src, sizeof(*dst)); + *src += sizeof(*dst); + *len -= sizeof(*dst); + *dst = le32toh(*dst); + + return 1; +} + +static int +np_read64(const char *t, const char *f, uint64_t *dst, const uint8_t **src, + size_t *len) +{ + if (*len < sizeof(*dst)) { + log_warnx("%s: wanted %zu bytes for the %s field but only " + "%zu are available.", t, sizeof(*dst), f, *len); + return -1; + } + + memcpy(dst, *src, sizeof(*dst)); + *src += sizeof(*dst); + *len -= sizeof(*dst); + *dst = le64toh(*dst); + + return 1; +} + +static int +np_readstr(const char *t, const char *f, char *res, size_t reslen, + const uint8_t **src, size_t *len) +{ + uint16_t sl; + char buf[32]; + + strlcpy(buf, f, sizeof(buf)); + strlcat(buf, "-len", sizeof(buf)); + + if (!np_read16(t, buf, &sl, src, len)) + return READSTRERR; + + if (*len < sl) { + log_warnx("%s: wanted %d bytes for the %s field but only " + "%zu are available.", t, sl, f, *len); + return READSTRERR; + } + + if (*len > reslen-1) + return READSTRTRUNC; + + memcpy(res, *src, sl); + res[sl] = '\0'; + *src += sl; + *len -= sl; + + return 0; +} + +static void +tversion(struct np_msg_header *hdr, const uint8_t *data, size_t len) +{ + char *dot, version[32]; + + if (handshaked) + goto err; + + /* msize[4] version[s] */ + if (!NPREAD32("msize", &msize, &data, &len)) + goto err; + + switch (NPREADSTR("version", version, sizeof(version), &data, &len)) { + case READSTRERR: + goto err; + case READSTRTRUNC: + log_warnx("9P version string too long, truncated"); + goto mismatch; + } + + if ((dot = strchr(version, '.')) != NULL) + *dot = '\0'; + + if (strcmp(version, VERSION9P) != 0 || + msize == 0) + goto mismatch; + + /* version matched */ + handshaked = 1; + msize = MIN(msize, CLIENT_MSIZE); + client_send_listener(IMSG_MSIZE, &msize, sizeof(msize)); + np_version(hdr->tag, msize, VERSION9P); + return; + +mismatch: + log_warnx("unknown 9P version string: \"%s\", want "VERSION9P, + version); + np_version(hdr->tag, MSIZE9P, "unknown"); + return; + +err: + client_send_listener(IMSG_CLOSE, NULL, 0); + client_shutdown(); +} + +static void +tattach(struct np_msg_header *hdr, const uint8_t *data, size_t len) +{ + struct dir *dir; + struct fid *f; + uint32_t fid, afid; + int fd; + char aname[PATH_MAX]; + + /* fid[4] afid[4] uname[s] aname[s] */ + + if (!NPREAD32("fid", &fid, &data, &len) || + !NPREAD32("afid", &afid, &data, &len)) + goto err; + + /* read the uname but don't actually use it */ + switch (NPREADSTR("uname", aname, sizeof(aname), &data, &len)) { + case READSTRERR: + goto err; + case READSTRTRUNC: + np_error(hdr->tag, "name too long"); + return; + } + + switch (NPREADSTR("aname", aname, sizeof(aname), &data, &len)) { + case READSTRERR: + goto err; + case READSTRTRUNC: + np_error(hdr->tag, "name too long"); + return; + } + + if (fid_by_id(fid) != NULL || afid != NOFID) { + np_error(hdr->tag, "invalid fid or afid"); + return; + } + + if ((fd = open(aname, O_RDONLY|O_DIRECTORY)) == -1) + goto fail; + + if ((dir = new_dir(fd)) == NULL) + goto fail; + + log_debug("attached %s to %d", aname, fid); + + if ((f = new_fid(dir, fid, aname, NULL)) == NULL) { + dir_decref(dir); + goto fail; + } + + np_attach(hdr->tag, &f->qid); + return; + +fail: + np_errno(hdr->tag); + log_warn("failed to attach %s", aname); + return; + +err: + client_send_listener(IMSG_CLOSE, NULL, 0); + client_shutdown(); +} + +static void +tclunk(struct np_msg_header *hdr, const uint8_t *data, size_t len) +{ + struct fid *f; + uint32_t fid; + + /* fid[4] */ + if (!NPREAD32("fid", &fid, &data, &len)) { + client_send_listener(IMSG_CLOSE, NULL, 0); + client_shutdown(); + return; + } + + if ((f = fid_by_id(fid)) == NULL) { + np_error(hdr->tag, "invalid fid"); + return; + } + + free_fid(f); + np_clunk(hdr->tag); +} + +static void +tflush(struct np_msg_header *hdr, const uint8_t *data, size_t len) +{ + uint16_t oldtag; + + /* + * We're doing only synchronous I/O. Tflush is implemented + * only because it's illegal to reply with a Rerror. + */ + + /* oldtag[2] */ + if (len != sizeof(oldtag)) { + log_warnx("Tflush with the wrong size: got %zu want %zu", + len, sizeof(oldtag)); + client_send_listener(IMSG_CLOSE, NULL, 0); + client_shutdown(); + return; + } + + np_flush(hdr->tag); +} + +static void +twalk(struct np_msg_header *hdr, const uint8_t *data, size_t len) +{ + struct stat sb; + struct dir *dir; + struct qid wqid[MAXWELEM] = {0}; + struct fid *f, *nf; + uint32_t fid, newfid; + uint16_t nwname; + int fd, oldfd, no, nwqid = 0; + char wnam[PATH_MAX]; + + if (!NPREAD32("fid", &fid, &data, &len) || + !NPREAD32("newfid", &newfid, &data, &len) || + !NPREAD16("nwname", &nwname, &data, &len)) + goto err; + + if (nwname > MAXWELEM) { + log_warnx("Twalk: more than %d path elements: %d", + MAXWELEM, nwname); + goto err; + } + + if ((f = fid_by_id(fid)) == NULL) { + np_error(hdr->tag, "invalid fid"); + return; + } + + if (f->fd != -1) { + np_error(hdr->tag, "fid already opened for I/O"); + return; + } + + if (fid == newfid) + nf = f; + else if ((nf = fid_by_id(newfid)) != NULL) { + np_error(hdr->tag, "newfid already in use"); + return; + } else + nf = NULL; + + /* special case: fid duplication */ + if (nwname == 0) { + /* + * TODO: should we forbid fids duplication when fid == + * newfid? + */ + if (nf == NULL && + (nf = new_fid(f->dir, newfid, f->fpath, &f->qid)) == NULL) + fatal("new_fid duplication"); + + np_walk(hdr->tag, 0, NULL); + return; + } + + if (!(f->qid.type & QTDIR)) { + np_error(hdr->tag, "fid doesn't represent a directory"); + return; + } + + oldfd = f->dir->fd; + + for (nwqid = 0; nwqid < nwname; nwqid++) { + switch (NPREADSTR("wname", wnam, sizeof(wnam), &data, &len)) { + case READSTRERR: + goto err; + case READSTRTRUNC: + np_error(hdr->tag, "wname too long"); + return; + } + + if (*wnam == '\0' || + strchr(wnam, '/') != NULL || + !strcmp(wnam, ".")) { + errno = EINVAL; + goto cantopen; + } + + if ((fd = openat(oldfd, wnam, O_RDONLY|O_DIRECTORY)) == -1 && + errno != ENOTDIR) + goto cantopen; + + if ((fd == -1 && fstatat(oldfd, wnam, &sb, 0) == -1) || + (fd != -1 && fstat(fd, &sb) == -1)) + goto cantopen; + + qid_update_from_sb(&wqid[nwqid], &sb); + + /* reached a file but we still have other components */ + if (fd == -1 && nwqid+1 < nwname) + goto cantopen; + + /* reached the end and found a file */ + if (fd == -1 && nwqid+1 == nwname) + continue; + + if (oldfd != f->dir->fd) + close(oldfd); + oldfd = fd; + } + + /* + * If fd is -1 we've reached a file, otherwise we've just + * reached another directory. We must pay attention to what + * file descriptor we use to create the dir, because if we've + * reached a file and oldfd is f->dir->fd then we *must* share + * the same dir (it was a walk of one path from a directory to a + * file, otherwise fun is bound to happen as soon as the client + * closes the fid for the directory but keeps the one for the + * file. + */ + if (fd == -1 && oldfd == f->dir->fd) + dir = f->dir; + else if (fd == -1) + dir = new_dir(oldfd); + else + dir = new_dir(fd); + + if (dir == NULL) + fatal("new_dir"); + + if (nf == NULL) { + if ((nf = new_fid(dir, newfid, wnam, &wqid[nwqid-1])) == NULL) + fatal("new fid"); + } else { + /* update the dir */ + dir_decref(nf->dir); + nf->dir = dir_incref(dir); + } + + np_walk(hdr->tag, nwqid, wqid); + return; + +cantopen: + if (oldfd != f->dir->fd) + close(oldfd); + no = errno; + if (nwqid == 0) + np_error(hdr->tag, strerror(no)); + else + np_walk(hdr->tag, nwqid, wqid); + return; + +err: + client_send_listener(IMSG_CLOSE, NULL, 0); + client_shutdown(); +} + +static inline int +npmode_to_unix(uint8_t mode, int *flags) +{ + switch (mode & 0x0F) { + case KOREAD: + *flags = O_RDONLY; + break; + case KOWRITE: + *flags = O_WRONLY; + break; + case KORDWR: + *flags = O_RDWR; + break; + case KOEXEC: + log_warnx("tried to open something with KOEXEC"); + /* fallthrough */ + default: + return -1; + } + + if (mode & KOTRUNC) + *flags |= O_TRUNC; + if (mode & KORCLOSE) + *flags |= O_CLOEXEC; + + return 0; +} + +static void +topen(struct np_msg_header *hdr, const uint8_t *data, size_t len) +{ + struct stat sb; + struct qid qid; + struct fid *f; + uint32_t fid; + uint8_t mode; + const char *path; + + /* fid[4] mode[1] */ + if (!NPREAD32("fid", &fid, &data, &len) || + !NPREAD8("mode", &mode, &data, &len)) { + client_send_listener(IMSG_CLOSE, NULL, 0); + client_shutdown(); + return; + } + + if ((f = fid_by_id(fid)) == NULL || f->fd != -1) { + np_error(hdr->tag, "invalid fid"); + return; + } + + if (npmode_to_unix(mode, &f->iomode) == -1) { + np_error(hdr->tag, "invalid mode"); + return; + } + + path = f->fpath; + if (f->qid.type & QTDIR) + path = "."; + + if ((f->fd = openat(f->dir->fd, path, f->iomode)) == -1) { + np_error(hdr->tag, strerror(errno)); + return; + } + + if (fstat(f->fd, &sb) == -1) + fatal("fstat"); + + if (S_ISDIR(sb.st_mode)) { + if ((f->d = fdopendir(f->fd)) == NULL) { + np_errno(hdr->tag); + close(f->fd); + f->fd = -1; + return; + } + + if ((f->evb = evbuffer_new()) == NULL) { + np_errno(hdr->tag); + closedir(f->d); + f->d = NULL; + f->fd = -1; + } + } + + f->offset = 0; + + qid_update_from_sb(&qid, &sb); + np_open(hdr->tag, &qid, sb.st_blksize); +} + +static void +tcreate(struct np_msg_header *hdr, const uint8_t *data, size_t len) +{ + struct stat sb; + struct qid qid; + struct fid *f; + uint32_t fid, perm; + uint8_t mode; + char name[PATH_MAX]; + + /* fid[4] name[s] perm[4] mode[1] */ + if (!NPREAD32("fid", &fid, &data, &len)) + goto err; + switch (NPREADSTR("name", name, sizeof(name), &data, &len)) { + case READSTRERR: + goto err; + case READSTRTRUNC: + np_error(hdr->tag, "name too long"); + return; + } + if (!NPREAD32("perm", &perm, &data, &len) || + !NPREAD8("mode", &mode, &data, &len)) + goto err; + + if (!strcmp(name, ".") || !strcmp(name, "..") || + strchr(name, '/') != NULL) { + np_error(hdr->tag, "invalid name"); + return; + } + + if ((f = fid_by_id(fid)) == NULL || f->fd != -1) { + np_error(hdr->tag, "invalid fid"); + return; + } + + if (!(f->qid.type & QTDIR)) { + np_error(hdr->tag, "fid doesn't identify a directory"); + return; + } + + if (npmode_to_unix(mode, &f->iomode) == -1) { + np_error(hdr->tag, "invalid mode"); + return; + } + + if (f->iomode & O_RDONLY) { + np_error(hdr->tag, "can't create a read-only file"); + return; + } + + /* TODO: parse the mode */ + + if (perm & 0x80000000) { + /* create a directory */ + f->fd = mkdirat(f->dir->fd, name, 0755); + } else { + /* create a file */ + f->fd = openat(f->dir->fd, name, f->iomode | O_CREAT | O_TRUNC, + 0644); + } + + if (f->fd == -1) { + np_errno(hdr->tag); + return; + } + + if (fstat(f->fd, &sb) == -1) + fatal("fstat"); + + if (S_ISDIR(sb.st_mode)) { + if ((f->d = fdopendir(f->fd)) == NULL) { + np_errno(hdr->tag); + close(f->fd); + f->fd = -1; + return; + } + + if ((f->evb = evbuffer_new()) == NULL) { + np_errno(hdr->tag); + closedir(f->d); + f->d = NULL; + f->fd = -1; + } + } + + f->offset = 0; + + qid_update_from_sb(&qid, &sb); + np_create(hdr->tag, &qid, sb.st_blksize); + + return; + +err: + client_send_listener(IMSG_CLOSE, NULL, 0); + client_shutdown(); +} + +static inline void +serialize_stat(const char *fname, struct stat *sb, struct evbuffer *evb) +{ + struct qid qid; + const char *uid, *gid, *muid; + size_t tot; + uint16_t namlen, uidlen, gidlen, ulen; + + qid_update_from_sb(&qid, sb); + + /* TODO: fill these fields */ + uid = ""; + gid = ""; + muid = ""; + + namlen = strlen(fname); + uidlen = strlen(uid); + gidlen = strlen(gid); + ulen = strlen(muid); + + tot = NPSTATSIZ(namlen, uidlen, gidlen, ulen); + if (tot > UINT32_MAX) { + log_warnx("stat info for dir entry %s would overflow", + fname); + return; + } + + np_write16(evb, tot); /* size[2] */ + np_write16(evb, sb->st_rdev); /* type[2] */ + np_write32(evb, sb->st_dev); /* dev[4] */ + np_qid(evb, &qid); /* qid[13] */ + + /* XXX: translate? */ + np_write32(evb, sb->st_mode); /* mode[4] */ + + np_write32(evb, sb->st_atim.tv_sec); /* atime[4] */ + np_write32(evb, sb->st_mtim.tv_sec); /* mtime[4] */ + np_write64(evb, sb->st_size); /* length[8] */ + np_string(evb, namlen, fname); /* name[s] */ + np_string(evb, uidlen, uid); /* uid[s] */ + np_string(evb, gidlen, gid); /* gid[s] */ + np_string(evb, ulen, muid); /* muid[s] */ +} + +static void +tread(struct np_msg_header *hdr, const uint8_t *data, size_t len) +{ + struct fid *f; + ssize_t r; + size_t howmuch; + uint64_t off; + uint32_t fid, count; + char buf[2048]; + + /* fid[4] offset[8] count[4] */ + if (!NPREAD32("fid", &fid, &data, &len) || + !NPREAD64("offset", &off, &data, &len) || + !NPREAD32("count", &count, &data, &len)) { + client_send_listener(IMSG_CLOSE, NULL, 0); + client_shutdown(); + return; + } + + if ((f = fid_by_id(fid)) == NULL || f->fd == -1) { + np_error(hdr->tag, "invalid fid"); + return; + } + + if (TYPE_OVERFLOW(off_t, off)) { + log_warnx("unexpected off_t size"); + np_error(hdr->tag, "invalid offset"); + return; + } + + if (f->d == NULL) { + /* read a file */ + howmuch = MIN(sizeof(buf), count); + r = pread(f->fd, buf, howmuch, (off_t)off); + if (r == -1) + np_errno(hdr->tag); + else + np_read(hdr->tag, r, buf); + } else { + if (off == 0 && f->offset != 0) { + rewinddir(f->d); + f->offset = 0; + evbuffer_drain(f->evb, EVBUFFER_LENGTH(f->evb)); + } + + if (off != f->offset) { + np_error(hdr->tag, "can't seek in directories"); + return; + } + + while (EVBUFFER_LENGTH(f->evb) < count) { + struct dirent *d; + struct stat sb; + + if ((d = readdir(f->d)) == NULL) + break; + if (fstatat(f->fd, d->d_name, &sb, 0) == -1) { + warn("fstatat"); + continue; + } + serialize_stat(d->d_name, &sb, f->evb); + } + + count = MIN(count, EVBUFFER_LENGTH(f->evb)); + np_read(hdr->tag, count, EVBUFFER_DATA(f->evb)); + evbuffer_drain(f->evb, count); + + f->offset += count; + } +} + +static void +twrite(struct np_msg_header *hdr, const uint8_t *data, size_t len) +{ + struct fid *f; + ssize_t r; + uint64_t off; + uint32_t fid, count; + + /* fid[4] offset[8] count[4] data[count] */ + if (!NPREAD32("fid", &fid, &data, &len) || + !NPREAD64("off", &off, &data, &len) || + !NPREAD32("count", &count, &data, &len) || + len != count) { + client_send_listener(IMSG_CLOSE, NULL, 0); + client_shutdown(); + return; + } + + if ((f = fid_by_id(fid)) == NULL || f->fd == -1) { + np_error(hdr->tag, "invalid fid"); + return; + } + + if (!(f->iomode & O_WRONLY) && + !(f->iomode & O_RDWR)) { + np_error(hdr->tag, "fid not opened for writing"); + return; + } + + if (TYPE_OVERFLOW(off_t, off)) { + log_warnx("unexpected off_t size"); + np_error(hdr->tag, "invalid offset"); + return; + } + + if ((r = pwrite(f->fd, data, len, off)) == -1) + np_errno(hdr->tag); + else + np_write(hdr->tag, r); +} + +static void +tstat(struct np_msg_header *hdr, const uint8_t *data, size_t len) +{ + struct evbuffer *evb; + struct stat sb; + struct fid *f; + int r; + uint32_t fid; + + /* fid[4] */ + if (!NPREAD32("fid", &fid, &data, &len)) { + client_send_listener(IMSG_CLOSE, NULL, 0); + client_shutdown(); + return; + } + + /* + * plan9' stat(9P) is not clear on whether the stat is allowed + * on opened fids or not. We're allowing stat regardless of the + * status of the fid. + */ + + if ((f = fid_by_id(fid)) == NULL) { + np_error(hdr->tag, "invalid fid"); + return; + } + + if ((evb = evbuffer_new()) == NULL) + fatal("evbuffer_new"); + + if (f->fd != -1) + r = fstat(f->fd, &sb); + else if (f->qid.type & QTDIR) + r = fstat(f->dir->fd, &sb); + else + r = fstatat(f->dir->fd, f->fpath, &sb, 0); + + if (r == -1) { + np_errno(hdr->tag); + evbuffer_free(evb); + return; + } + + serialize_stat(f->fpath, &sb, evb); + np_stat(hdr->tag, EVBUFFER_LENGTH(evb), EVBUFFER_DATA(evb)); + evbuffer_free(evb); +} + +static void +tremove(struct np_msg_header *hdr, const uint8_t *data, size_t len) +{ + struct fid *f; + uint32_t fid; + int r; + char dirpath[PATH_MAX + 3]; + + /* fid[4] */ + if (!NPREAD32("fid", &fid, &data, &len)) { + client_send_listener(IMSG_CLOSE, NULL, 0); + client_shutdown(); + return; + } + + if ((f = fid_by_id(fid)) == NULL) { + np_error(hdr->tag, "invalid fid"); + return; + } + + if (f->qid.type & QTDIR) { /* directory */ + strlcpy(dirpath, "../", sizeof(dirpath)); + strlcat(dirpath, f->fpath, sizeof(dirpath)); + r = unlinkat(f->dir->fd, dirpath, AT_REMOVEDIR); + } else /* file */ + r = unlinkat(f->dir->fd, f->fpath, 0); + + if (r == -1) + np_errno(hdr->tag); + else + np_remove(hdr->tag); + + free_fid(f); +} + +static void +handle_message(struct imsg *imsg, size_t len) +{ + struct msg { + uint8_t type; + void (*fn)(struct np_msg_header *, const uint8_t *, size_t); + } msgs[] = { + {Tversion, tversion}, + {Tattach, tattach}, + {Tclunk, tclunk}, + {Tflush, tflush}, + {Twalk, twalk}, + {Topen, topen}, + {Tcreate, tcreate}, + {Tread, tread}, + {Twrite, twrite}, + {Tstat, tstat}, + {Tremove, tremove}, + }; + struct np_msg_header hdr; + size_t i; + uint8_t *data; + +#if DEBUG_PACKETS + hexdump("incoming packet", imsg->data, len); +#endif + + parse_message(imsg->data, len, &hdr, &data); + len -= HEADERSIZE; + + log_debug("got request: len=%d type=%d[%s] tag=%d", + hdr.len, hdr.type, pp_msg_type(hdr.type), hdr.tag); + + if (!handshaked && hdr.type != Tversion) { + client_send_listener(IMSG_CLOSE, NULL, 0); + client_shutdown(); + return; + } + + for (i = 0; i < sizeof(msgs)/sizeof(msgs[0]); ++i) { + if (msgs[i].type != hdr.type) + continue; + + msgs[i].fn(&hdr, data, len); + return; + } + + np_error(hdr.tag, "Not supported."); +} blob - /dev/null blob + 3797d2e8d3a63327a887ac03ec59c48ceb7f2e5e (mode 644) --- /dev/null +++ kamid/client.h @@ -0,0 +1,22 @@ +/* + * Copyright (c) 2021 Omar Polo + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef CLIENT_H +#define CLIENT_H + +__dead void client(int, int); + +#endif blob - /dev/null blob + 5d8abc786358b8715dec9ab680a868c59bf80c0d (mode 644) --- /dev/null +++ kamid/control.c @@ -0,0 +1,293 @@ +/* + * Copyright (c) 2003, 2004 Henning Brauer + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "control.h" +#include "kamid.h" +#include "listener.h" +#include "log.h" +#include "utils.h" + +#define CONTROL_BACKLOG 5 + +struct { + struct event ev; + struct event evt; + int fd; +} control_state = {.fd = -1}; + +struct ctl_conn { + TAILQ_ENTRY(ctl_conn) entry; + struct imsgev iev; +}; + +TAILQ_HEAD(ctl_conns, ctl_conn) ctl_conns = TAILQ_HEAD_INITIALIZER(ctl_conns); + +struct ctl_conn *control_connbyfd(int); +struct ctl_conn *control_connbypid(pid_t); +void control_close(int); + +int +control_init(const char *path) +{ + struct sockaddr_un sun; + int fd; + mode_t old_umask; + + if ((fd = socket(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC | SOCK_NONBLOCK, + 0)) == -1) { + log_warn("%s: socket", __func__); + return (-1); + } + + memset(&sun, 0, sizeof(sun)); + sun.sun_family = AF_UNIX; + strlcpy(sun.sun_path, path, sizeof(sun.sun_path)); + + if (unlink(path) == -1) + if (errno != ENOENT) { + log_warn("%s: unlink %s", __func__, path); + close(fd); + return (-1); + } + + old_umask = umask(S_IXUSR|S_IXGRP|S_IWOTH|S_IROTH|S_IXOTH); + if (bind(fd, (struct sockaddr *)&sun, sizeof(sun)) == -1) { + log_warn("%s: bind: %s", __func__, path); + close(fd); + umask(old_umask); + return (-1); + } + umask(old_umask); + + if (chmod(path, S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP) == -1) { + log_warn("%s: chmod", __func__); + close(fd); + (void)unlink(path); + return (-1); + } + + return (fd); +} + +int +control_listen(int fd) +{ + if (control_state.fd != -1) + fatalx("%s: received unexpected controlsock", __func__); + + control_state.fd = fd; + if (listen(control_state.fd, CONTROL_BACKLOG) == -1) { + log_warn("%s: listen", __func__); + return (-1); + } + + event_set(&control_state.ev, control_state.fd, EV_READ, + control_accept, NULL); + event_add(&control_state.ev, NULL); + evtimer_set(&control_state.evt, control_accept, NULL); + + return (0); +} + +void +control_accept(int listenfd, short event, void *bula) +{ + int connfd; + socklen_t len; + struct sockaddr_un sun; + struct ctl_conn *c; + + event_add(&control_state.ev, NULL); + if ((event & EV_TIMEOUT)) + return; + + len = sizeof(sun); + if ((connfd = accept4(listenfd, (struct sockaddr *)&sun, &len, + SOCK_CLOEXEC | SOCK_NONBLOCK)) == -1) { + /* + * Pause accept if we are out of file descriptors, or + * libevent will haunt us here too. + */ + if (errno == ENFILE || errno == EMFILE) { + struct timeval evtpause = { 1, 0 }; + + event_del(&control_state.ev); + evtimer_add(&control_state.evt, &evtpause); + } else if (errno != EWOULDBLOCK && errno != EINTR && + errno != ECONNABORTED) + log_warn("%s: accept4", __func__); + return; + } + + if ((c = calloc(1, sizeof(struct ctl_conn))) == NULL) { + log_warn("%s: calloc", __func__); + close(connfd); + return; + } + + imsg_init(&c->iev.ibuf, connfd); + c->iev.handler = control_dispatch_imsg; + c->iev.events = EV_READ; + event_set(&c->iev.ev, c->iev.ibuf.fd, c->iev.events, + c->iev.handler, &c->iev); + event_add(&c->iev.ev, NULL); + + TAILQ_INSERT_TAIL(&ctl_conns, c, entry); +} + +struct ctl_conn * +control_connbyfd(int fd) +{ + struct ctl_conn *c; + + TAILQ_FOREACH(c, &ctl_conns, entry) { + if (c->iev.ibuf.fd == fd) + break; + } + + return (c); +} + +struct ctl_conn * +control_connbypid(pid_t pid) +{ + struct ctl_conn *c; + + TAILQ_FOREACH(c, &ctl_conns, entry) { + if (c->iev.ibuf.pid == pid) + break; + } + + return (c); +} + +void +control_close(int fd) +{ + struct ctl_conn *c; + + if ((c = control_connbyfd(fd)) == NULL) { + log_warnx("%s: fd %d: not found", __func__, fd); + return; + } + + msgbuf_clear(&c->iev.ibuf.w); + TAILQ_REMOVE(&ctl_conns, c, entry); + + event_del(&c->iev.ev); + close(c->iev.ibuf.fd); + + /* Some file descriptors are available again. */ + if (evtimer_pending(&control_state.evt, NULL)) { + evtimer_del(&control_state.evt); + event_add(&control_state.ev, NULL); + } + + free(c); +} + +void +control_dispatch_imsg(int fd, short event, void *bula) +{ + struct ctl_conn *c; + struct imsg imsg; + ssize_t n; + int verbose; + + if ((c = control_connbyfd(fd)) == NULL) { + log_warnx("%s: fd %d: not found", __func__, fd); + return; + } + + if (event & EV_READ) { + if (((n = imsg_read(&c->iev.ibuf)) == -1 && errno != EAGAIN) || + n == 0) { + control_close(fd); + return; + } + } + if (event & EV_WRITE) { + if (msgbuf_write(&c->iev.ibuf.w) <= 0 && errno != EAGAIN) { + control_close(fd); + return; + } + } + + for (;;) { + if ((n = imsg_get(&c->iev.ibuf, &imsg)) == -1) { + control_close(fd); + return; + } + if (n == 0) + break; + + switch (imsg.hdr.type) { + case IMSG_CTL_RELOAD: + listener_imsg_compose_main(imsg.hdr.type, 0, NULL, 0); + break; + case IMSG_CTL_LOG_VERBOSE: + if (IMSG_DATA_SIZE(imsg) != sizeof(verbose)) + break; + + /* Forward to all other processes. */ + listener_imsg_compose_main(imsg.hdr.type, imsg.hdr.pid, + imsg.data, IMSG_DATA_SIZE(imsg)); + + /* XXX: send to every client? */ + + memcpy(&verbose, imsg.data, sizeof(verbose)); + log_setverbose(verbose); + break; + default: + log_debug("%s: error handling imsg %d", __func__, + imsg.hdr.type); + break; + } + imsg_free(&imsg); + } + + imsg_event_add(&c->iev); +} + +int +control_imsg_relay(struct imsg *imsg) +{ + struct ctl_conn *c; + + if ((c = control_connbypid(imsg->hdr.pid)) == NULL) + return (0); + + return (imsg_compose_event(&c->iev, imsg->hdr.type, 0, imsg->hdr.pid, + -1, imsg->data, IMSG_DATA_SIZE(*imsg))); +} blob - /dev/null blob + f9782b86db958a0bb3c26ecb88d581980ffd7dcd (mode 644) --- /dev/null +++ kamid/control.h @@ -0,0 +1,26 @@ +/* + * Copyright (c) 2003, 2004 Henning Brauer + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef CONTROL_H +#define CONTROL_H + +int control_init(const char *); +int control_listen(int fd); +void control_accept(int, short, void *); +void control_dispatch_imsg(int, short, void *); +int control_imsg_relay(struct imsg *); + +#endif blob - /dev/null blob + e0f398aaf1fb243d4b11fbef4b3e08442fd9ea46 (mode 644) --- /dev/null +++ kamid/kamid.8 @@ -0,0 +1,81 @@ +.\" Copyright (c) 2021 Omar Polo +.\" +.\" Permission to use, copy, modify, and distribute this software for any +.\" purpose with or without fee is hereby granted, provided that the above +.\" copyright notice and this permission notice appear in all copies. +.\" +.\" THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +.\" WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +.\" MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +.\" ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +.\" WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +.\" ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +.\" OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +.\" +.Dd $Mdocdate: July 07 2021 $ +.Dt KAMID 8 +.Os +.Sh NAME +.Nm kamid +.Nd 9p file server daemon +.Sh SYNOPSIS +.Nm +.Op Fl dnv +.Op Fl D Ar macro Ns = Ns Ar value +.Op Fl f Pa file +.Op Fl s Pa socket +.Sh DESCRIPTION +.Nm +is a 9p file server daemon. +.Pp +The options are as follows: +.Bl -tag -width Ds +.It Fl D Ar macro Ns = Ns Ar value +Set a +.Ar macro +to a +.Ar value . +Macros can be referenced in the configuration files. +.It Fl d +Do not daemonize. +If this option is specified, +.Nm +will run in the foreground and log to +.Em stderr . +.It Fl f Ar file +specify an alternative configuration file. +.It Fl n +Configtest mode. +Only check the configuration file for validity. +.It Fl s Ar socket +Use an alternate location for the default control socket. +.It Fl v +Produce more verbose output. +.El +.Sh FILES +.Bl -tag -width "/var/run/kamid.sockXX" -compact +.It Pa /etc/kamid.conf +Default +.Nm +configuration file. +.It Pa /var/run/kamid.sock +UNIX-domain socket used for communication with +.Xr kamictl 8 . +.El +.Sh SEE ALSO +.Xr kami.conf 5 , +.Xr kamictl 8 +.Sh AUTHORS +.An -nosplit +The +.Nm +program was written by +.An Omar Polo Aq Mt op@omarpolo.com . +.Sh CAVEATS +.Nm +doesn't handle very well when a user directory is over multiple +devices since it uses the inode of files to build the QID path field. +.Pp +Opening/creating a file with the +.Dv OEXEC +result in an error. blob - /dev/null blob + cd27289756b18d4d7bceb853c1b5d5f210686808 (mode 644) --- /dev/null +++ kamid/kamid.c @@ -0,0 +1,632 @@ +/* + * Copyright (c) 2021 Omar Polo + * Copyright (c) 2018 Florian Obser + * Copyright (c) 2005 Claudio Jeker + * Copyright (c) 2004 Esben Norby + * Copyright (c) 2003, 2004 Henning Brauer + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "client.h" +#include "control.h" +#include "kamid.h" +#include "listener.h" +#include "log.h" +#include "sandbox.h" +#include "table.h" +#include "utils.h" + +enum kd_process { + PROC_MAIN, + PROC_LISTENER, + PROC_CLIENTCONN, +}; + +const char *saved_argv0; +static int debug, nflag; +int verbose; + +__dead void usage(void); + +void main_sig_handler(int, short, void *); +void main_dispatch_listener(int, short, void *); +int main_reload(void); +int main_imsg_send_config(struct kd_conf *); +void main_dispatch_listener(int, short, void *); +__dead void main_shutdown(void); + +static pid_t start_child(enum kd_process, int, int, int); + +struct kd_conf *main_conf; +static struct imsgev *iev_listener; +const char *conffile; +pid_t listener_pid; +uint32_t cmd_opts; + +__dead void +usage(void) +{ + fprintf(stderr, "usage: %s [-dnv] [-f file] [-s socket]\n", + getprogname()); + exit(1); +} + +int +main(int argc, char **argv) +{ + struct event ev_sigint, ev_sigterm, ev_sighup; + int ch; + int listener_flag = 0, client_flag = 0; + int pipe_main2listener[2]; + int control_fd; + const char *csock; + + conffile = KD_CONF_FILE; + csock = KD_SOCKET; + + log_init(1, LOG_DAEMON); /* Log to stderr until deamonized. */ + log_setverbose(1); + + saved_argv0 = argv[0]; + if (saved_argv0 == NULL) + saved_argv0 = "kamid"; + + while ((ch = getopt(argc, argv, "D:df:nsT:v")) != -1) { + switch (ch) { + case 'D': + if (cmdline_symset(optarg) == -1) + log_warnx("could not parse macro definition %s", + optarg); + break; + case 'd': + debug = 1; + break; + case 'f': + conffile = optarg; + break; + case 'n': + nflag = 1; + break; + case 's': + csock = optarg; + break; + case 'T': + switch (*optarg) { + case 'c': + client_flag = 1; + break; + case 'l': + listener_flag = 1; + break; + default: + fatalx("invalid process spec %c", *optarg); + } + break; + case 'v': + verbose = 1; + break; + default: + usage(); + } + } + + argc -= optind; + argv += optind; + if (argc > 0 || (listener_flag && client_flag)) + usage(); + + if (client_flag) + client(debug, verbose); + else if (listener_flag) + listener(debug, verbose); + + if ((main_conf = parse_config(conffile)) == NULL) + exit(1); + + if (nflag) { + fprintf(stderr, "configuration OK\n"); + exit(0); + } + + /* Check for root privileges. */ + if (geteuid()) + fatalx("need root privileges"); + + /* Check for assigned daemon user. */ + if (getpwnam(KD_USER) == NULL) + fatalx("unknown user %s", KD_USER); + + log_init(debug, LOG_DAEMON); + log_setverbose(verbose); + + if (!debug) + daemon(1, 0); + + log_info("startup"); + + if (socketpair(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC | SOCK_NONBLOCK, + PF_UNSPEC, pipe_main2listener) == -1) + fatal("main2listener socketpair"); + + /* Start children. */ + listener_pid = start_child(PROC_LISTENER, pipe_main2listener[1], + debug, verbose); + + log_procinit("main"); + + event_init(); + + /* Setup signal handler */ + signal_set(&ev_sigint, SIGINT, main_sig_handler, NULL); + signal_set(&ev_sigterm, SIGTERM, main_sig_handler, NULL); + signal_set(&ev_sighup, SIGHUP, main_sig_handler, NULL); + + signal_add(&ev_sigint, NULL); + signal_add(&ev_sigterm, NULL); + signal_add(&ev_sighup, NULL); + + signal(SIGCHLD, SIG_IGN); + signal(SIGPIPE, SIG_IGN); + + if ((iev_listener = malloc(sizeof(*iev_listener))) == NULL) + fatal(NULL); + imsg_init(&iev_listener->ibuf, pipe_main2listener[0]); + iev_listener->handler = main_dispatch_listener; + + /* Setup event handlers for pipes to listener. */ + iev_listener->events = EV_READ; + event_set(&iev_listener->ev, iev_listener->ibuf.fd, + iev_listener->events, iev_listener->handler, iev_listener); + event_add(&iev_listener->ev, NULL); + + if ((control_fd = control_init(csock)) == -1) + fatalx("control socket setup failed"); + + main_imsg_compose_listener(IMSG_CONTROLFD, control_fd, 0, + NULL, 0); + main_imsg_send_config(main_conf); + + sandbox_main(); + + event_dispatch(); + + main_shutdown(); + return 0; +} + +void +main_sig_handler(int sig, short event, void *arg) +{ + /* + * Normal signal handler rules don't apply because libevent + * decouples for us. + */ + + switch (sig) { + case SIGTERM: + case SIGINT: + main_shutdown(); + break; + case SIGHUP: + if (main_reload() == -1) + log_warnx("configuration reload failed"); + else + log_debug("configuration reloaded"); + break; + default: + fatalx("unexpected signal %d", sig); + } +} + +static inline struct table * +auth_table_by_id(uint32_t id) +{ + struct kd_listen_conf *listen; + + STAILQ_FOREACH(listen, &main_conf->listen_head, entry) { + if (listen->id == id) + return listen->auth_table; + } + + return NULL; +} + +static inline struct table * +virtual_table_by_id(uint32_t id) +{ + struct kd_listen_conf *listen; + + STAILQ_FOREACH(listen, &main_conf->listen_head, entry) { + if (listen->id == id) + return listen->virtual_table; + } + + return NULL; +} + +static inline struct table * +userdata_table_by_id(uint32_t id) +{ + struct kd_listen_conf *listen; + + STAILQ_FOREACH(listen, &main_conf->listen_head, entry) { + if (listen->id == id) + return listen->userdata_table; + } + + return NULL; +} + +static inline void +do_auth_tls(struct imsg *imsg) +{ + char *username = NULL, *user = NULL, *home = NULL, *local_user; + struct passwd *pw; + struct table *auth, *virt, *userdata; + struct kd_auth_req kauth; + int p[2], free_home = 1; + + if (sizeof(kauth) != IMSG_DATA_SIZE(*imsg)) + fatal("wrong size for IMSG_AUTH_TLS: " + "got %lu; want %lu", IMSG_DATA_SIZE(*imsg), + sizeof(kauth)); + memcpy(&kauth, imsg->data, sizeof(kauth)); + + if (memmem(kauth.hash, sizeof(kauth.hash), "", 1) == NULL) + fatal("non NUL-terminated hash received"); + + log_debug("tls id=%u hash=%s", kauth.listen_id, kauth.hash); + + if ((auth = auth_table_by_id(kauth.listen_id)) == NULL) + fatal("request for invalid listener id %d", imsg->hdr.pid); + + virt = virtual_table_by_id(kauth.listen_id); + userdata = userdata_table_by_id(kauth.listen_id); + + if (table_lookup(auth, kauth.hash, &username) == -1) { + log_warnx("login failed for hash %s", kauth.hash); + goto err; + } + + if (virt != NULL && table_lookup(virt, username, &user) == -1) { + log_warnx("virtual lookup failed for user %s", username); + goto err; + } + + /* the local user */ + local_user = user != NULL ? user : username; + + if (user != NULL) + log_debug("virtual user %s matched local user %s", + username, user); + else + log_debug("matched local user %s", username); + + if (userdata != NULL && table_lookup(userdata, username, &home) + == -1) { + log_warnx("userdata lookup failed for user %s", username); + goto err; + } else if (userdata == NULL) { + if ((pw = getpwnam(local_user)) == NULL) { + log_warnx("getpwnam(%s) failed", local_user); + goto err; + } + + free_home = 0; + home = pw->pw_dir; + } + + if (user != NULL) + log_debug("matched home %s for virtual user %s", + home, username); + else + log_debug("matched home %s for local user %s", + home, username); + + if (socketpair(AF_UNIX, SOCK_STREAM|SOCK_CLOEXEC|SOCK_NONBLOCK, + PF_UNSPEC, p) == -1) + fatal("socketpair"); + + start_child(PROC_CLIENTCONN, p[1], debug, verbose); + + main_imsg_compose_listener(IMSG_AUTH, p[0], imsg->hdr.peerid, + local_user, strlen(local_user)+1); + main_imsg_compose_listener(IMSG_AUTH_DIR, -1, imsg->hdr.peerid, + home, strlen(home)+1); + + free(username); + free(user); + if (free_home) + free(home); + return; + +err: + free(username); + free(user); + if (free_home) + free(home); + main_imsg_compose_listener(IMSG_AUTH, -1, imsg->hdr.peerid, + NULL, 0); +} + +void +main_dispatch_listener(int fd, short event, void *d) +{ + struct imsgev *iev = d; + struct imsgbuf *ibuf; + struct imsg imsg; + ssize_t n; + int shut = 0; + + ibuf = &iev->ibuf; + + if (event & EV_READ) { + if ((n = imsg_read(ibuf)) == -1 && errno != EAGAIN) + fatal("imsg_read error"); + if (n == 0) /* Connection closed. */ + shut = 1; + } + if (event & EV_WRITE) { + if ((n = msgbuf_write(&ibuf->w)) == -1 && errno != EAGAIN) + fatal("msgbuf_write"); + if (n == 0) /* Connection closed. */ + shut = 1; + } + + for (;;) { + if ((n = imsg_get(ibuf, &imsg)) == -1) + fatal("imsg_get"); + if (n == 0) /* No more messages. */ + break; + + switch (imsg.hdr.type) { + case IMSG_AUTH_TLS: + do_auth_tls(&imsg); + break; + default: + log_debug("%s: error handling imsg %d", __func__, + imsg.hdr.type); + break; + } + imsg_free(&imsg); + } + if (!shut) + imsg_event_add(iev); + else { + /* This pipe is dead. Remove its event handler. */ + event_del(&iev->ev); + event_loopexit(NULL); + } +} + +int +main_reload(void) +{ + struct kd_conf *xconf; + + if ((xconf = parse_config(conffile)) == NULL) + return -1; + + if (main_imsg_send_config(xconf) == -1) + return -1; + + merge_config(main_conf, xconf); + + return 0; +} + +static inline int +make_socket_for(struct kd_listen_conf *l) +{ + struct sockaddr_in addr4; + size_t len; + int fd, v; + + memset(&addr4, 0, sizeof(addr4)); + addr4.sin_family = AF_INET; + addr4.sin_port = htons(l->port); + addr4.sin_addr.s_addr = INADDR_ANY; + + if ((fd = socket(AF_INET, SOCK_STREAM, 0)) == -1) + fatal("socket"); + + v = 1; + if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &v, sizeof(v)) == -1) + fatal("setsockopt(SO_REUSEADDR)"); + + v = 1; + if (setsockopt(fd, SOL_SOCKET, SO_REUSEPORT, &v, sizeof(v)) == -1) + fatal("setsockopt(SO_REUSEPORT)"); + + len = sizeof(addr4); + if (bind(fd, (struct sockaddr *)&addr4, len) == -1) + fatal("bind(%s, %d)", l->iface, l->port); + + if (listen(fd, 16) == -1) + fatal("l(%s, %d)", l->iface, l->port); + + return fd; +} + +int +main_imsg_send_config(struct kd_conf *xconf) +{ + struct kd_pki_conf *pki; + struct kd_listen_conf *listen; + +#define SEND(type, fd, data, len) do { \ + if (main_imsg_compose_listener(type, fd, 0, data, len) \ + == -1) \ + return -1; \ + } while (0) + + /* Send fixed part of config to children. */ + SEND(IMSG_RECONF_CONF, -1, xconf, sizeof(*xconf)); + + STAILQ_FOREACH(pki, &xconf->pki_head, entry) { + log_debug("sending pki %s", pki->name); + SEND(IMSG_RECONF_PKI, -1, pki->name, sizeof(pki->name)); + SEND(IMSG_RECONF_PKI_CERT, -1, pki->cert, pki->certlen); + SEND(IMSG_RECONF_PKI_KEY, -1, pki->key, pki->keylen); + } + + STAILQ_FOREACH(listen, &xconf->listen_head, entry) { + log_debug("sending listen on port %d", listen->port); + SEND(IMSG_RECONF_LISTEN, make_socket_for(listen), listen, + sizeof(*listen)); + } + + SEND(IMSG_RECONF_END, -1, NULL, 0); + return 0; + +#undef SEND +} + +void +merge_config(struct kd_conf *conf, struct kd_conf *xconf) +{ + /* do stuff... */ + + free(xconf); +} + +struct kd_conf * +config_new_empty(void) +{ + struct kd_conf *xconf; + + if ((xconf = calloc(1, sizeof(*xconf))) == NULL) + fatal(NULL); + + /* set default values */ + + return xconf; +} + +void +config_clear(struct kd_conf *conf) +{ + struct kd_conf *xconf; + + /* Merge current config with an empty one. */ + xconf = config_new_empty(); + merge_config(conf, xconf); + + free(conf); +} + +__dead void +main_shutdown(void) +{ + pid_t pid; + int status; + + /* close pipes. */ + config_clear(main_conf); + + log_debug("waiting for children to terminate"); + do { + pid = wait(&status); + if (pid == -1) { + if (errno != EINTR && errno != ECHILD) + fatal("wait"); + } else if (WIFSIGNALED(status)) + log_warnx("%s terminated; signal %d", + (pid == listener_pid) ? "logger" : "clientconn", + WTERMSIG(status)); + } while (pid != -1 || (pid == -1 && errno == EINTR)); + + free(iev_listener); + + log_info("terminating"); + exit(0); +} + +static pid_t +start_child(enum kd_process p, int fd, int debug, int verbose) +{ + const char *argv[5]; + int argc = 0; + pid_t pid; + + switch (pid = fork()) { + case -1: + fatal("cannot fork"); + case 0: + break; + default: + close(fd); + return pid; + } + + if (fd != 3) { + if (dup2(fd, 3) == -1) + fatal("cannot setup imsg fd"); + } else if (fcntl(F_SETFD, 0) == -1) + fatal("cannot setup imsg fd"); + + argv[argc++] = saved_argv0; + switch (p) { + case PROC_MAIN: + fatalx("Can not start main process"); + case PROC_LISTENER: + argv[argc++] = "-Tl"; + break; + case PROC_CLIENTCONN: + argv[argc++] = "-Tc"; + break; + } + if (debug) + argv[argc++] = "-d"; + if (verbose) + argv[argc++] = "-v"; + argv[argc++] = NULL; + + /* really? */ + execvp(saved_argv0, (char *const *)argv); + fatal("execvp"); +} + +int +main_imsg_compose_listener(int type, int fd, uint32_t peerid, + const void *data, uint16_t datalen) +{ + if (iev_listener) + return imsg_compose_event(iev_listener, type, peerid, 0, + fd, data, datalen); + else + return -1; +} blob - /dev/null blob + fd14ab7c7c727a8d7d30270434c294902e08c6e2 (mode 644) --- /dev/null +++ kamid/kamid.conf.5 @@ -0,0 +1,131 @@ +.\" Copyright (c) 2021 Omar Polo +.\" +.\" Permission to use, copy, modify, and distribute this software for any +.\" purpose with or without fee is hereby granted, provided that the above +.\" copyright notice and this permission notice appear in all copies. +.\" +.\" THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +.\" WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +.\" MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +.\" ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +.\" WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +.\" ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +.\" OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +.\" +.Dd $Mdocdate: December 14 2021 $ +.Dt KAMID.CONF 5 +.Os +.Sh NAME +.Nm kamid.conf +.Nd 9p file server daemon configuration file +.Sh DESCRIPTION +.Nm +is the configuration file for the 9p file server daemon +.Xr kamid 8 . +.Pp +The format of the configuration file is fairly flexible. +The current line can be extended over multiple lines using a backslash +.Pq Sq \e . +Comments can be put anywhere in the file using a hash mark +.Pq Sq # , +and extend to the end of the current line. +Care should be taken when commenting out multi-line text: the comment is +effective until the end of the entire block. +Arguments names not beginning with a letter, digit, or underscore, as +well as reserved words +(such as +.Ic listen , +.Ic pki +and +.Ic table ) +must be quoted. +Arguments containing whitespace should be surrounded by double quotes +.Pq \&" . +.Pp +Macros can be defined that are later expanded in context. +Macro names must start with a letter, digit, or underscore, and may +contain any of those characters, but may not be reserved words. +Macros are not expanded inside quotes. +For example: +.Bd -literal -offset indent +lan_addr = "192.168.0.1" +listen on $lan_addr +listen on $lan_addr tls auth +.Ed +.Pp +Additional configuration files can be included with the +.Ic include +keyword, for example: +.Bd -literal -offset indent +include "/etc/kamid.conf.local" +.Ed +.Pp +The syntax of +.Nm +is described below. +.Bl -tag -width Ds +.It Ic listen Op Ar options... +The options are as follows: +.Bl -tag -width Ds +.It Ic on Ar address Ic port Ar number +Listen on the +.Ar address +for incoming connection on the given port +.Ar number . +.Ar address +can be an IP address or a domain name. +.It Ic tls Ic pki Ar name +Use the tls certificate +.Ar name +previously defined with the +.Ic pki +rule. +.It Ic auth Pf < Ar table Ns > +Use the given authentication +.Ar table +to authorize the clients. +.It Ic userdata Pf < Ar table Ns > +Maps user +.Pq virtuals or not +to their exported tree. +By default the user home directory obtained with +.Xr getpwnam 3 +is used. +.It Ic virtual Pf < Ar table Ns > +Maps virtual users to local user. +.El +.It Ic pki Ar pkiname Ic cert Ar certfile +Associate certificate file +.Ar certfile +with pki entry +.Ar pkiname . +The pki entry defines a keypair configuration that can be referenced in +listener rules. +.It Ic pki Ar pkiname Ic key Ar keyfile +Associate the key located in +.Ar keyfile +with pki entry +.Ar pkiname . +.\" TODO: document the other syntax for the table +.It Ic table Ar name Brq Ar value Cm => Ar value Oo , Ar ... Oc +Tables provide additional configuration information for +.Xr kamid 8 +in the form of key-value mappings. +.Pp +Declare a mapping table containing the given static +.Ar key Ns Pf - Ar value +pairs. +.El +.Sh EXAMPLES +A sample configuration file: +.Bd -literal -offset indent +pki localhost cert "/etc/ssl/localhost.crt" +pki localhost key "/etc/ssl/private/localhost.key" + +table users { "SHA256:..." => "op" } + +listen on localhost port 1337 tls pki localhost auth +.Ed +.Sh SEE ALSO +.Xr kamictl 8 , +.Xr kamid 8 blob - /dev/null blob + db48d7e0a64590177becf74fe80661d26f1c0d01 (mode 644) --- /dev/null +++ kamid/kamid.h @@ -0,0 +1,143 @@ +/* + * Copyright (c) 2021 Omar Polo + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef KAMID_H +#define KAMID_H + +#include +#include +#include + +/* TODO: make these customizable */ +#define KD_CONF_FILE "/etc/kamid.conf" +#define KD_USER "_kamid" +#define KD_SOCKET "/var/run/kamid.sock" + +#define IMSG_DATA_SIZE(imsg) ((imsg).hdr.len - IMSG_HEADER_SIZE) + +enum imsg_type { + IMSG_NONE, + IMSG_CTL_LOG_VERBOSE, + IMSG_CTL_RELOAD, + IMSG_CONTROLFD, + IMSG_STARTUP, + IMSG_RECONF_CONF, + IMSG_RECONF_PKI, + IMSG_RECONF_PKI_CERT, + IMSG_RECONF_PKI_KEY, + IMSG_RECONF_LISTEN, + IMSG_RECONF_END, + IMSG_AUTH, + IMSG_AUTH_DIR, + IMSG_AUTH_TLS, + IMSG_CONN_GONE, + IMSG_BUF, + IMSG_MSIZE, + IMSG_CLOSE, +}; + +struct kd_options_conf { + /* ... */ +}; + +enum table_type { + T_NONE = 0, + T_HASH = 0x01, +}; + +struct table { + char t_name[LINE_MAX]; + enum table_type t_type; + char t_path[PATH_MAX]; + void *t_handle; + struct table_backend *t_backend; +}; + +struct table_backend { + const char *name; + int (*open)(struct table *); + int (*add)(struct table *, const char *, const char *); + int (*lookup)(struct table *, const char *, char **); + void (*close)(struct table *); +}; + +/* table_static.c */ +extern struct table_backend table_static; + +#define L_NONE 0x0 +#define L_TLS 0x1 +struct kd_listen_conf { + STAILQ_ENTRY(kd_listen_conf) entry; + uint32_t id; + uint32_t flags; + int fd; + char iface[LINE_MAX]; + uint16_t port; + + /* certificate hash => (virtual) user */ + struct table *auth_table; + + /* virtual user => local user */ + struct table *virtual_table; + + /* (virtual) user => export directory */ + struct table *userdata_table; + + char pki[LINE_MAX]; + struct event ev; + struct tls *ctx; +}; + +struct kd_pki_conf { + STAILQ_ENTRY(kd_pki_conf) entry; + char name[LINE_MAX]; + uint8_t *cert; + size_t certlen; + uint8_t *key; + size_t keylen; + struct tls_config *tlsconf; +}; + +struct kd_tables_conf { + STAILQ_ENTRY(kd_tables_conf) entry; + struct table *table; +}; + +struct kd_conf { + struct kd_options_conf kd_options; + STAILQ_HEAD(kd_pki_conf_head, kd_pki_conf) pki_head; + STAILQ_HEAD(kd_tables_conf_head, kd_tables_conf) table_head; + STAILQ_HEAD(kd_listen_conf_head, kd_listen_conf) listen_head; +}; + +struct kd_auth_req { + uint32_t listen_id; + char hash[128+1]; +}; + +/* kamid.c */ +extern int verbose; +int main_imsg_compose_listener(int, int, uint32_t, const void *, uint16_t); +void merge_config(struct kd_conf *, struct kd_conf *); + +struct kd_conf *config_new_empty(void); +void config_clear(struct kd_conf *); + +/* parse.y */ +struct kd_conf *parse_config(const char *); +int cmdline_symset(char *); + +#endif blob - /dev/null blob + 655c05f33499a226890eee263131d3efa95be0b9 (mode 644) --- /dev/null +++ kamid/listener.c @@ -0,0 +1,939 @@ +/* + * Copyright (c) 2021 Omar Polo + * Copyright (c) 2018 Florian Obser + * Copyright (c) 2004, 2005 Claudio Jeker + * Copyright (c) 2004 Esben Norby + * Copyright (c) 2003, 2004 Henning Brauer + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "control.h" +#include "kami.h" +#include "kamid.h" +#include "listener.h" +#include "log.h" +#include "sandbox.h" +#include "utils.h" + +static struct kd_conf *listener_conf; +static struct imsgev *iev_main; + +static void listener_sig_handler(int, short, void *); +__dead void listener_shutdown(void); + +SPLAY_HEAD(clients_tree_id, client) clients; + +struct client { + uint32_t id; + uint32_t lid; + uint32_t msize; + int fd; + int done; + struct tls *ctx; + struct event event; + struct imsgev iev; + struct bufferevent *bev; + SPLAY_ENTRY(client) sp_entry; +}; + +static void listener_imsg_event_add(struct imsgev *, void *); +static void listener_dispatch_client(int, short, void *); +static int listener_imsg_compose_client(struct client *, int, + uint32_t, const void *, uint16_t); + +static void apply_config(struct kd_conf *); +static void handle_accept(int, short, void *); + +static void handle_handshake(int, short, void *); +static void client_read(struct bufferevent *, void *); +static void client_write(struct bufferevent *, void *); +static void client_error(struct bufferevent *, short, void *); +static void client_tls_readcb(int, short, void *); +static void client_tls_writecb(int, short, void *); +static void close_conn(struct client *); +static void handle_close(int, short, void *); + +static inline int +clients_tree_cmp(struct client *a, struct client *b) +{ + if (a->id == b->id) + return 0; + else if (a->id < b->id) + return -1; + else + return +1; +} + +SPLAY_PROTOTYPE(clients_tree_id, client, sp_entry, clients_tree_cmp); +SPLAY_GENERATE(clients_tree_id, client, sp_entry, clients_tree_cmp) + +static void +listener_sig_handler(int sig, short event, void *d) +{ + /* + * Normal signal handler rules don't apply because libevent + * decouples for us. + */ + + switch (sig) { + case SIGINT: + case SIGTERM: + listener_shutdown(); + default: + fatalx("unexpected signal %d", sig); + } +} + +void +listener(int debug, int verbose) +{ + struct event ev_sigint, ev_sigterm; + struct passwd *pw; + + /* listener_conf = config_new_empty(); */ + + log_init(debug, LOG_DAEMON); + log_setverbose(verbose); + + if ((pw = getpwnam(KD_USER)) == NULL) + fatal("getpwnam"); + + if (chroot(pw->pw_dir) == -1) + fatal("chroot"); + if (chdir("/") == -1) + fatal("chdir(\"/\")"); + + setproctitle("listener"); + log_procinit("listener"); + + if (setgroups(1, &pw->pw_gid) || + setresgid(pw->pw_gid, pw->pw_gid, pw->pw_gid) || + setresuid(pw->pw_uid, pw->pw_uid, pw->pw_uid)) + fatal("can't drop privileges"); + + event_init(); + + /* Setup signal handlers(s). */ + signal_set(&ev_sigint, SIGINT, listener_sig_handler, NULL); + signal_set(&ev_sigterm, SIGTERM, listener_sig_handler, NULL); + + signal_add(&ev_sigint, NULL); + signal_add(&ev_sigterm, NULL); + + signal(SIGPIPE, SIG_IGN); + signal(SIGHUP, SIG_IGN); + + /* Setup pipe and event handler to the main process. */ + if ((iev_main = malloc(sizeof(*iev_main))) == NULL) + fatal(NULL); + + imsg_init(&iev_main->ibuf, 3); + iev_main->handler = listener_dispatch_main; + + /* Setup event handlers. */ + iev_main->events = EV_READ; + event_set(&iev_main->ev, iev_main->ibuf.fd, iev_main->events, + iev_main->handler, iev_main); + event_add(&iev_main->ev, NULL); + + sandbox_listener(); + event_dispatch(); + listener_shutdown(); +} + +__dead void +listener_shutdown(void) +{ + msgbuf_clear(&iev_main->ibuf.w); + close(iev_main->ibuf.fd); + + config_clear(listener_conf); + + free(iev_main); + + log_info("listener exiting"); + exit(0); +} + +static void +listener_receive_config(struct imsg *imsg, struct kd_conf **nconf, + struct kd_pki_conf **pki) +{ + struct kd_listen_conf *listen; + char *t; + + switch (imsg->hdr.type) { + case IMSG_RECONF_CONF: + if (*nconf != NULL) + fatalx("%s: IMSG_RECONF_CONF already in " + "progress", __func__); + + if (listener_conf != NULL) + fatalx("%s: don't know how reload the " + "configuration yet", __func__); + + if (IMSG_DATA_SIZE(*imsg) != sizeof(struct kd_conf)) + fatalx("%s: IMSG_RECONF_CONF wrong length: %lu", + __func__, IMSG_DATA_SIZE(*imsg)); + if ((*nconf = malloc(sizeof(**nconf))) == NULL) + fatal(NULL); + memcpy(*nconf, imsg->data, sizeof(**nconf)); + memset(&(*nconf)->pki_head, 0, sizeof((*nconf)->pki_head)); + memset(&(*nconf)->table_head, 0, sizeof((*nconf)->table_head)); + memset(&(*nconf)->listen_head, 0, sizeof((*nconf)->listen_head)); + break; + case IMSG_RECONF_PKI: + if (*nconf == NULL) + fatalx("%s: IMSG_RECONF_PKI without " + "IMSG_RECONF_CONF", __func__); + *pki = xcalloc(1, sizeof(**pki)); + t = imsg->data; + t[IMSG_DATA_SIZE(*imsg)-1] = '\0'; + strlcpy((*pki)->name, t, sizeof((*pki)->name)); + break; + case IMSG_RECONF_PKI_CERT: + if (*pki == NULL) + fatalx("%s: IMSG_RECONF_PKI_CERT without " + "IMSG_RECONF_PKI", __func__); + (*pki)->certlen = IMSG_DATA_SIZE(*imsg); + (*pki)->cert = xmemdup(imsg->data, (*pki)->certlen); + break; + case IMSG_RECONF_PKI_KEY: + if (*pki == NULL) + fatalx("%s: IMSG_RECONF_PKI_KEY without " + "IMSG_RECONF_PKI", __func__); + (*pki)->keylen = IMSG_DATA_SIZE(*imsg); + (*pki)->key = xmemdup(imsg->data, (*pki)->keylen); + STAILQ_INSERT_HEAD(&(*nconf)->pki_head, *pki, entry); + pki = NULL; + break; + case IMSG_RECONF_LISTEN: + if (*nconf == NULL) + fatalx("%s: IMSG_RECONF_LISTEN without " + "IMSG_RECONF_CONF", __func__); + if (IMSG_DATA_SIZE(*imsg) != sizeof(*listen)) + fatalx("%s: IMSG_RECONF_LISTEN wrong length: %lu", + __func__, IMSG_DATA_SIZE(*imsg)); + listen = xcalloc(1, sizeof(*listen)); + memcpy(listen, imsg->data, sizeof(*listen)); + memset(&listen->entry, 0, sizeof(listen->entry)); + if ((listen->fd = imsg->fd) == -1) + fatalx("%s: IMSG_RECONF_LISTEN no fd", + __func__); + listen->auth_table = NULL; + memset(&listen->ev, 0, sizeof(listen->ev)); + STAILQ_INSERT_HEAD(&(*nconf)->listen_head, listen, entry); + break; + case IMSG_RECONF_END: + if (*nconf == NULL) + fatalx("%s: IMSG_RECONF_END without " + "IMSG_RECONF_CONF", __func__); + /* merge_config(listener_conf, nconf); */ + apply_config(*nconf); + *nconf = NULL; + break; + } +} + +static inline struct kd_listen_conf * +listen_by_id(uint32_t id) +{ + struct kd_listen_conf *l; + + STAILQ_FOREACH(l, &listener_conf->listen_head, entry) { + if (l->id == id) + return l; + } + return NULL; +} + +void +listener_dispatch_main(int fd, short event, void *d) +{ + static struct kd_conf *nconf; + static struct kd_pki_conf *pki; + struct kd_listen_conf *listen; + struct client *client, find; + struct imsg imsg; + struct imsgev *iev = d; + struct imsgbuf *ibuf; + ssize_t n; + int shut = 0; + + ibuf = &iev->ibuf; + + if (event & EV_READ) { + if ((n = imsg_read(ibuf)) == -1 && errno != EAGAIN) + fatal("imsg_read error"); + if (n == 0) /* Connection closed. */ + shut = 1; + } + if (event & EV_WRITE) { + if ((n = msgbuf_write(&ibuf->w)) == -1 && errno != EAGAIN) + fatal("msgbuf_write"); + if (n == 0) /* Connection closed. */ + shut = 1; + } + + for (;;) { + if ((n = imsg_get(ibuf, &imsg)) == -1) + fatal("%s: imsg_get error", __func__); + if (n == 0) /* No more messages. */ + break; + + switch (imsg.hdr.type) { + case IMSG_CONTROLFD: + if ((fd = imsg.fd) == -1) + fatalx("%s: expected to receive imsg " + "control fd but didn't receive any", + __func__); + /* Listen on control socket. */ + control_listen(fd); + break; + case IMSG_RECONF_CONF: + case IMSG_RECONF_PKI: + case IMSG_RECONF_PKI_CERT: + case IMSG_RECONF_PKI_KEY: + case IMSG_RECONF_LISTEN: + case IMSG_RECONF_END: + listener_receive_config(&imsg, &nconf, &pki); + break; + case IMSG_AUTH: + find.id = imsg.hdr.peerid; + client = SPLAY_FIND(clients_tree_id, &clients, &find); + if (client == NULL) { + if (imsg.fd != -1) + close(imsg.fd); + break; + } + if (imsg.fd == -1) { + log_info("got fd = -1, auth failed?"); + close_conn(client); + break; + } + imsg_init(&client->iev.ibuf, imsg.fd); + client->iev.events = EV_READ; + client->iev.handler = listener_dispatch_client; + event_set(&client->iev.ev, client->iev.ibuf.fd, + client->iev.events, client->iev.handler, client); + listener_imsg_compose_client(client, IMSG_AUTH, + client->id, imsg.data, IMSG_DATA_SIZE(imsg)); + break; + case IMSG_AUTH_DIR: + find.id = imsg.hdr.peerid; + client = SPLAY_FIND(clients_tree_id, &clients, &find); + if (client == NULL) { + log_info("got AUTH_DIR but client gone"); + break; + } + + listener_imsg_compose_client(client, IMSG_AUTH_DIR, + 0, imsg.data, IMSG_DATA_SIZE(imsg)); + + client->bev = bufferevent_new(client->fd, + client_read, client_write, client_error, + client); + if (client->bev == NULL) { + log_info("failed to allocate client buffer"); + close_conn(client); + return; + } + +#if HAVE_EVENT2 + evbuffer_unfreeze(client->bev->input, 0); + evbuffer_unfreeze(client->bev->output, 1); +#endif + + listen = listen_by_id(client->lid); + if (listen->flags & L_TLS) { + event_set(&client->bev->ev_read, client->fd, + EV_READ, client_tls_readcb, client->bev); + event_set(&client->bev->ev_write, client->fd, + EV_WRITE, client_tls_writecb, client->bev); + } + + /* + * Read or write at least a header before + * firing the callbacks. High watermark of 0 + * to never stop reading/writing; probably to + * be revisited. + */ + /* bufferevent_setwatermark(client->bev, EV_READ|EV_WRITE, */ + /* sizeof(struct np_msg_header), 0); */ + bufferevent_enable(client->bev, EV_READ|EV_WRITE); + break; + + default: + log_debug("%s: unexpected imsg %d", __func__, + imsg.hdr.type); + break; + } + imsg_free(&imsg); + } + + if (!shut) + listener_imsg_event_add(iev, d); + else { + /* This pipe is dead. Remove its event handler. */ + event_del(&iev->ev); + log_warnx("pipe closed, shutting down..."); + event_loopexit(NULL); + } +} + +int +listener_imsg_compose_main(int type, uint32_t peerid, const void *data, + uint16_t datalen) +{ + return imsg_compose_event(iev_main, type, peerid, 0, -1, data, + datalen); +} + +static void +listener_imsg_event_add(struct imsgev *iev, void *d) +{ + iev->events = EV_READ; + if (iev->ibuf.w.queued) + iev->events |= EV_WRITE; + + event_del(&iev->ev); + event_set(&iev->ev, iev->ibuf.fd, iev->events, iev->handler, d); + event_add(&iev->ev, NULL); +} + +static void +listener_dispatch_client(int fd, short event, void *d) +{ + struct client find, *client = d; + struct imsg imsg; + struct imsgev *iev; + struct imsgbuf *ibuf; + ssize_t n; + int r, shut = 0; + + iev = &client->iev; + ibuf = &iev->ibuf; + + if (event & EV_READ) { + if ((n = imsg_read(ibuf)) == -1 && errno != EAGAIN) + fatal("imsg_read error"); + if (n == 0) /* Connection closed */ + shut = 1; + } + + if (event & EV_WRITE) { + if ((n = msgbuf_write(&ibuf->w)) == -1 && errno != EAGAIN) + fatal("msgbuf_write"); + if (n == 0) /* Connection closed. */ + shut = 1; + } + + for (;;) { + if ((n = imsg_get(ibuf, &imsg)) == -1) + fatal("%s: imsg_get error", __func__); + if (n == 0) /* No more messages. */ + break; + + switch (imsg.hdr.type) { + case IMSG_BUF: + find.id = imsg.hdr.peerid; + client = SPLAY_FIND(clients_tree_id, &clients, &find); + if (client == NULL) { + log_info("got IMSG_BUF but client (%d) gone", + imsg.hdr.peerid); + break; + } + r = bufferevent_write(client->bev, imsg.data, + IMSG_DATA_SIZE(imsg)); + if (r == -1) { + log_warn("%s: bufferevent_write failed", + __func__); + close_conn(client); + break; + } + break; + + case IMSG_MSIZE: + if (IMSG_DATA_SIZE(imsg) != sizeof(client->msize)) + fatal("IMSG_MSIZE size mismatch: " + "got %zu want %zu", IMSG_DATA_SIZE(imsg), + sizeof(client->msize)); + + memcpy(&client->msize, imsg.data, + sizeof(client->msize)); + + if (client->msize == 0) + fatal("IMSG_MSIZE got msize = 0"); + + break; + + case IMSG_CLOSE: + /* + * Both EVBUFFER_READ or EVBUFFER_WRITE should + * be fine. + */ + client_error(client->bev, EVBUFFER_READ, client); + break; + + default: + log_debug("%s: unexpected imsg %d", __func__, + imsg.hdr.type); + break; + } + imsg_free(&imsg); + } + + if (!shut) + listener_imsg_event_add(iev, d); + else { + /* This pipe is dead. Remove its handler */ + log_debug("client proc vanished"); + close_conn(client); + } +} + +static int +listener_imsg_compose_client(struct client *client, int type, + uint32_t peerid, const void *data, uint16_t len) +{ + int ret; + + if ((ret = imsg_compose(&client->iev.ibuf, type, peerid, 0, -1, + data, len)) != -1) + listener_imsg_event_add(&client->iev, client); + + return ret; +} + +static inline struct kd_pki_conf * +pki_by_name(const char *name) +{ + struct kd_pki_conf *pki; + + STAILQ_FOREACH(pki, &listener_conf->pki_head, entry) { + if (!strcmp(name, pki->name)) + return pki; + } + + return NULL; +} + +static void +apply_config(struct kd_conf *conf) +{ + struct kd_pki_conf *pki; + struct kd_listen_conf *listen; + + listener_conf = conf; + + /* prepare the various tls_config */ + STAILQ_FOREACH(pki, &listener_conf->pki_head, entry) { + if ((pki->tlsconf = tls_config_new()) == NULL) + fatal("tls_config_new"); + tls_config_verify_client_optional(pki->tlsconf); + tls_config_insecure_noverifycert(pki->tlsconf); + if (tls_config_set_keypair_mem(pki->tlsconf, + pki->cert, pki->certlen, + pki->key, pki->keylen) == -1) + fatalx("tls_config_set_keypair_mem: %s", + tls_config_error(pki->tlsconf)); + } + + /* prepare and kickoff the listeners */ + STAILQ_FOREACH(listen, &listener_conf->listen_head, entry) { + if ((listen->ctx = tls_server()) == NULL) + fatal("tls_server"); + + pki = pki_by_name(listen->pki); + if (tls_configure(listen->ctx, pki->tlsconf) == -1) + fatalx("tls_configure: %s", + tls_config_error(pki->tlsconf)); + + event_set(&listen->ev, listen->fd, EV_READ|EV_PERSIST, + handle_accept, listen); + event_add(&listen->ev, NULL); + } +} + +static inline void +yield_r(struct client *c, void (*fn)(int, short, void *)) +{ + if (event_pending(&c->event, EV_WRITE|EV_READ, NULL)) + event_del(&c->event); + event_set(&c->event, c->fd, EV_READ, fn, c); + event_add(&c->event, NULL); +} + +static inline void +yield_w(struct client *c, void (*fn)(int, short, void *)) +{ + if (event_pending(&c->event, EV_WRITE|EV_READ, NULL)) + event_del(&c->event); + event_set(&c->event, c->fd, EV_WRITE, fn, c); + event_add(&c->event, NULL); +} + +static inline uint32_t +random_id(void) +{ +#if HAVE_ARC4RANDOM +# define RANDID() arc4random() +#else + /* not as pretty as a random id */ + static uint32_t counter = 0; +# define RANDID() counter++ +#endif + + struct client find, *res; + + for (;;) { + find.id = RANDID(); + res = SPLAY_FIND(clients_tree_id, &clients, &find); + if (res == NULL) + return find.id; + } + +#undef RANDID +} + +static void +handle_accept(int fd, short ev, void *data) +{ + struct kd_listen_conf *listen = data; + struct client *c; + int s; + + if ((s = accept(fd, NULL, NULL)) == -1) { + log_warn("accept"); + return; + } + + c = xcalloc(1, sizeof(*c)); + c->msize = MSIZE9P; + c->lid = listen->id; + c->iev.ibuf.fd = -1; + + if (tls_accept_socket(listen->ctx, &c->ctx, s) == -1) { + log_warnx("tls_accept_socket: %s", + tls_error(listen->ctx)); + free(c); + close(s); + return; + } + + c->fd = s; + c->id = random_id(); + + SPLAY_INSERT(clients_tree_id, &clients, c); + + /* initialize the event */ + event_set(&c->event, c->fd, EV_READ, NULL, NULL); + + yield_r(c, handle_handshake); +} + +static void +handle_handshake(int fd, short ev, void *data) +{ + struct client *c = data; + struct kd_auth_req auth; + ssize_t r; + const char *hash; + + switch (r = tls_handshake(c->ctx)) { + case TLS_WANT_POLLIN: + yield_r(c, handle_handshake); + return; + case TLS_WANT_POLLOUT: + yield_w(c, handle_handshake); + return; + case -1: + log_debug("handhsake failed: %s", tls_error(c->ctx)); + close_conn(c); + return; + } + + if ((hash = tls_peer_cert_hash(c->ctx)) == NULL) { + log_warnx("client didn't provide certificate"); + close_conn(c); + return; + } + + memset(&auth, 0, sizeof(auth)); + auth.listen_id = c->lid; + strlcpy(auth.hash, hash, sizeof(auth.hash)); + log_debug("sending hash %s", auth.hash); + + listener_imsg_compose_main(IMSG_AUTH_TLS, c->id, + &auth, sizeof(auth)); +} + +static void +client_read(struct bufferevent *bev, void *d) +{ + struct client *client = d; + struct evbuffer *src = EVBUFFER_INPUT(bev); + uint32_t len; + + for (;;) { + if (EVBUFFER_LENGTH(src) < 4) + return; + + memcpy(&len, EVBUFFER_DATA(src), sizeof(len)); + len = le32toh(len); + log_debug("expecting a message %"PRIu32" bytes long " + "(of wich %zu already read)", + len, EVBUFFER_LENGTH(src)); + + if (len < HEADERSIZE) { + log_warnx("invalid message size %d (too low)", len); + client_error(bev, EVBUFFER_READ, client); + return; + } + + if (len > client->msize) { + log_warnx("incoming message bigger than msize " + "(%"PRIu32" vs %"PRIu32")", len, client->msize); + client_error(bev, EVBUFFER_READ, client); + return; + } + + if (len > EVBUFFER_LENGTH(src)) + return; + + listener_imsg_compose_client(client, IMSG_BUF, client->id, + EVBUFFER_DATA(src), len); + evbuffer_drain(src, len); + } +} + +static void +client_write(struct bufferevent *bev, void *d) +{ + /* + * here we can do some fancy logic like deciding when to call + * + * (*bev->errorcb)(bev, EVBUFFER_WRITE, bev->cbarg) + * + * to signal the end of the transaction. + */ + + return; +} + +static void +client_error(struct bufferevent *bev, short err, void *d) +{ + struct client *client = d; + struct evbuffer *buf; + + if (err & EVBUFFER_ERROR) { + if (errno == EFBIG) { + bufferevent_enable(bev, EV_READ); + return; + } + log_debug("buffer event error"); + close_conn(client); + return; + } + + if (err & EVBUFFER_EOF) { + close_conn(client); + return; + } + + if (err & (EVBUFFER_READ|EVBUFFER_WRITE)) { + bufferevent_disable(bev, EV_READ|EV_WRITE); + client->done = 1; + + buf = EVBUFFER_OUTPUT(client->bev); + if (EVBUFFER_LENGTH(buf) != 0) { + /* finish writing all the data first */ + bufferevent_enable(client->bev, EV_WRITE); + return; + } + + close_conn(client); + return; + } + + log_warnx("unknown event error, closing client connection"); + close_conn(client); +} + +static void +client_tls_readcb(int fd, short event, void *d) +{ + struct bufferevent *bufev = d; + struct client *client = bufev->cbarg; + char buf[IBUF_READ_SIZE]; + int what = EVBUFFER_READ; + int howmuch = IBUF_READ_SIZE; + ssize_t ret; + size_t len; + + if (event == EV_TIMEOUT) { + what |= EVBUFFER_TIMEOUT; + goto err; + } + + if (bufev->wm_read.high != 0) + howmuch = MIN(sizeof(buf), bufev->wm_read.high); + + switch (ret = tls_read(client->ctx, buf, howmuch)) { + case TLS_WANT_POLLIN: + case TLS_WANT_POLLOUT: + goto retry; + case -1: + what |= EVBUFFER_ERROR; + goto err; + } + len = ret; + + if (len == 0) { + what |= EVBUFFER_EOF; + goto err; + } + + if (evbuffer_add(bufev->input, buf, len) == -1) { + what |= EVBUFFER_ERROR; + goto err; + } + + event_add(&bufev->ev_read, NULL); + + len = EVBUFFER_LENGTH(bufev->input); + if (bufev->wm_read.low != 0 && len < bufev->wm_read.low) + return; + if (bufev->wm_read.high != 0 && len > bufev->wm_read.high) { + /* + * here we could implement some read pressure + * mechanism. + */ + } + + if (bufev->readcb != NULL) + (*bufev->readcb)(bufev, bufev->cbarg); + + return; + +retry: + event_add(&bufev->ev_read, NULL); + return; + +err: + (*bufev->errorcb)(bufev, what, bufev->cbarg); +} + +static void +client_tls_writecb(int fd, short event, void *d) +{ + struct bufferevent *bufev = d; + struct client *client = bufev->cbarg; + ssize_t ret; + size_t len; + short what = EVBUFFER_WRITE; + + if (event == EV_TIMEOUT) { + what |= EVBUFFER_TIMEOUT; + goto err; + } + + if (EVBUFFER_LENGTH(bufev->output) != 0) { + ret = tls_write(client->ctx, + EVBUFFER_DATA(bufev->output), + EVBUFFER_LENGTH(bufev->output)); + switch (ret) { + case TLS_WANT_POLLIN: + case TLS_WANT_POLLOUT: + goto retry; + case -1: + what |= EVBUFFER_ERROR; + goto err; + } + len = ret; + evbuffer_drain(bufev->output, len); + } + + if (EVBUFFER_LENGTH(bufev->output) != 0) + event_add(&bufev->ev_write, NULL); + + if (bufev->writecb != NULL && + EVBUFFER_LENGTH(bufev->output) <= bufev->wm_write.low) + (*bufev->writecb)(bufev, bufev->cbarg); + return; + +retry: + event_add(&bufev->ev_write, NULL); + return; + +err: + (*bufev->errorcb)(bufev, what, bufev->cbarg); +} + +static void +close_conn(struct client *c) +{ + log_debug("closing connection"); + + if (c->iev.ibuf.fd != -1) { + listener_imsg_compose_client(c, IMSG_CONN_GONE, 0, NULL, 0); + imsg_flush(&c->iev.ibuf); + msgbuf_clear(&c->iev.ibuf.w); + event_del(&c->iev.ev); + close(c->iev.ibuf.fd); + } + + handle_close(c->fd, 0, c); +} + +static void +handle_close(int fd, short ev, void *d) +{ + struct client *c = d; + + switch (tls_close(c->ctx)) { + case TLS_WANT_POLLIN: + yield_r(c, handle_close); + return; + case TLS_WANT_POLLOUT: + yield_w(c, handle_close); + return; + } + + event_del(&c->event); + tls_free(c->ctx); + close(c->fd); + free(c); +} blob - /dev/null blob + 2583bbb2538aa0ddf70b6d0c6ce612bf307d63df (mode 644) --- /dev/null +++ kamid/listener.h @@ -0,0 +1,24 @@ +/* + * Copyright (c) 2021 Omar Polo + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef LISTENER_H +#define LISTENER_H + +void listener(int, int); +void listener_dispatch_main(int, short, void *); +int listener_imsg_compose_main(int, uint32_t, const void *, uint16_t); + +#endif blob - /dev/null blob + 342a0800da97c13c5140ccfbd7fe240b178499b9 (mode 644) --- /dev/null +++ kamid/parse.y @@ -0,0 +1,989 @@ +/* + * Copyright (c) 2021 Omar Polo + * Copyright (c) 2018 Florian Obser + * Copyright (c) 2004, 2005 Esben Norby + * Copyright (c) 2004 Ryan McBride + * Copyright (c) 2002, 2003, 2004 Henning Brauer + * Copyright (c) 2001 Markus Friedl. All rights reserved. + * Copyright (c) 2001 Daniel Hartmeier. All rights reserved. + * Copyright (c) 2001 Theo de Raadt. All rights reserved. + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +%{ + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "log.h" +#include "kamid.h" +#include "table.h" +#include "utils.h" + +TAILQ_HEAD(files, file) files = TAILQ_HEAD_INITIALIZER(files); +static struct file { + TAILQ_ENTRY(file) entry; + FILE *stream; + char *name; + size_t ungetpos; + size_t ungetsize; + u_char *ungetbuf; + int eof_reached; + int lineno; + int errors; +} *file, *topfile; +struct file *pushfile(const char *, int); +int popfile(void); +int check_file_secrecy(int, const char *); +int yyparse(void); +int yylex(void); +int yyerror(const char *, ...) + __attribute__((__format__ (printf, 1, 2))) + __attribute__((__nonnull__ (1))); +int kw_cmp(const void *, const void *); +int lookup(char *); +int igetc(void); +int lgetc(int); +void lungetc(int); +int findeol(void); + +TAILQ_HEAD(symhead, sym) symhead = TAILQ_HEAD_INITIALIZER(symhead); +struct sym { + TAILQ_ENTRY(sym) entry; + int used; + int persist; + char *nam; + char *val; +}; + +int symset(const char *, const char *, int); +char *symget(const char *); + +void clear_config(struct kd_conf *xconf); + +static void add_table(const char *, const char *, const char *); +static struct table *findtable(const char *name); +static void add_cert(const char *, const char *); +static void add_key(const char *, const char *); +static struct kd_listen_conf *listen_new(void); + +static uint32_t counter; +static struct table *table; +static struct kd_listen_conf *listener; +static struct kd_conf *conf; +static int errors; + +typedef struct { + union { + int64_t number; + char *string; + struct table *table; + } v; + int lineno; +} YYSTYPE; + +%} + +%token AUTH +%token CERT +%token ERROR +%token INCLUDE +%token KEY +%token LISTEN +%token NO +%token ON +%token PKI PORT +%token TABLE TLS +%token USERDATA +%token VIRTUAL +%token YES + +%token STRING +%token NUMBER +%type yesno +%type string +%type tableref + +%% + +grammar : /* empty */ + | grammar include '\n' + | grammar '\n' + | grammar table '\n' + | grammar pki '\n' + | grammar listen '\n' + | grammar varset '\n' + | grammar error '\n' { file->errors++; } + ; + +include : INCLUDE STRING { + struct file *nfile; + + if ((nfile = pushfile($2, 0)) == NULL) { + yyerror("failed to include file %s", $2); + free($2); + YYERROR; + } + free($2); + + file = nfile; + lungetc('\n'); + } + ; + +string : string STRING { + if (asprintf(&$$, "%s %s", $1, $2) == -1) { + free($1); + free($2); + yyerror("string: asprintf"); + YYERROR; + } + free($1); + free($2); + } + | STRING + ; + +yesno : YES { $$ = 1; } + | NO { $$ = 0; } + ; + +optnl : '\n' optnl /* zero or more newlines */ + | /*empty*/ + ; + +nl : '\n' optnl /* one or more newlines */ + ; + +arrow : '=' '>' ; + +comma : ',' optnl + ; + +varset : STRING '=' string { + char *s = $1; + if (verbose) + printf("%s = \"%s\"\n", $1, $3); + while (*s++) { + if (isspace((unsigned char)*s)) { + yyerror("macro name cannot contain " + "whitespace"); + free($1); + free($3); + YYERROR; + } + } + if (symset($1, $3, 0) == -1) + fatal("cannot store variable"); + free($1); + free($3); + } + ; + +pki : PKI STRING CERT STRING { add_cert($2, $4); } + | PKI STRING KEY STRING { add_key($2, $4); } + ; + +table_kp : string arrow string optnl { + if (table_add(table, $1, $3) == -1) + yyerror("can't add to table %s", + table->t_name); + free($1); + free($3); + } + ; + +table_kps : table_kp + | table_kp comma table_kps + ; + +stringel : STRING { + if (table_add(table, $1, NULL) == -1) + yyerror("can't add to table %s", + table->t_name); + free($1); + } + ; + +string_list : stringel + | stringel comma string_list + ; + +table_vals : table_kps + | string_list + ; + +table : TABLE STRING STRING { + char *p; + + if ((p = strchr($3, ':')) == NULL) { + yyerror("invalid table %s", $2); + YYERROR; + } + + *p = '\0'; + add_table($2, $3, p+1); + free($2); + free($3); + } + | TABLE STRING { + add_table($2, "static", NULL); + } '{' optnl table_vals '}' { + table = NULL; + } + ; + +tableref : '<' STRING '>' { + struct table *t; + + t = findtable($2); + free($2); + if (t == NULL) + YYERROR; + $$ = t; + } + ; + +listen : LISTEN { listener = listen_new(); } + listen_opts { + if (listener->auth_table == NULL) + yyerror("missing auth table"); + if (!(listener->flags & L_TLS)) + yyerror("can't define a non-tls listener"); + listener = NULL; + } + ; + +listen_opts : listen_opt + | listen_opt listen_opts + ; + +listen_opt : ON STRING PORT NUMBER { + if (*listener->iface != '\0') + yyerror("listen address and port already" + " defined"); + strlcpy(listener->iface, $2, sizeof(listener->iface)); + listener->port = $4; + } + | TLS PKI STRING { + if (*listener->pki != '\0') + yyerror("listen tls pki already defined"); + listener->flags |= L_TLS; + strlcpy(listener->pki, $3, sizeof(listener->pki)); + } + | AUTH tableref { + if (listener->auth_table != NULL) + yyerror("listen auth already defined"); + listener->auth_table = $2; + } + | USERDATA tableref { + if (listener->userdata_table != NULL) + yyerror("userdata table already defined"); + listener->userdata_table = $2; + } + | VIRTUAL tableref { + if (listener->virtual_table != NULL) + yyerror("virtual table already defined"); + listener->virtual_table = $2; + } + ; + +%% + +struct keywords { + const char *k_name; + int k_val; +}; + +int +yyerror(const char *fmt, ...) +{ + va_list ap; + char *msg; + + file->errors++; + va_start(ap, fmt); + if (vasprintf(&msg, fmt, ap) == -1) + fatalx("yyerror vasprintf"); + va_end(ap); + logit(LOG_CRIT, "%s:%d: %s", file->name, yylval.lineno, msg); + free(msg); + return 0; +} + +int +kw_cmp(const void *k, const void *e) +{ + return strcmp(k, ((const struct keywords *)e)->k_name); +} + +int +lookup(char *s) +{ + /* This has to be sorted always. */ + static const struct keywords keywords[] = { + {"auth", AUTH}, + {"cert", CERT}, + {"include", INCLUDE}, + {"key", KEY}, + {"listen", LISTEN}, + {"no", NO}, + {"on", ON}, + {"pki", PKI}, + {"port", PORT}, + {"table", TABLE}, + {"tls", TLS}, + {"userdata", USERDATA}, + {"virtual", VIRTUAL}, + {"yes", YES}, + }; + const struct keywords *p; + + p = bsearch(s, keywords, sizeof(keywords)/sizeof(keywords[0]), + sizeof(keywords[0]), kw_cmp); + + if (p) + return p->k_val; + else + return STRING; +} + +#define START_EXPAND 1 +#define DONE_EXPAND 2 + +static int expanding; + +int +igetc(void) +{ + int c; + + while (1) { + if (file->ungetpos > 0) + c = file->ungetbuf[--file->ungetpos]; + else + c = getc(file->stream); + + if (c == START_EXPAND) + expanding = 1; + else if (c == DONE_EXPAND) + expanding = 0; + else + break; + } + return c; +} + +int +lgetc(int quotec) +{ + int c, next; + + if (quotec) { + if ((c = igetc()) == EOF) { + yyerror("reached end of file while parsing " + "quoted string"); + if (file == topfile || popfile() == EOF) + return EOF; + return quotec; + } + return c; + } + + while ((c = igetc()) == '\\') { + next = igetc(); + if (next != '\n') { + c = next; + break; + } + yylval.lineno = file->lineno; + file->lineno++; + } + + if (c == EOF) { + /* + * Fake EOL when hit EOF for the first time. This gets line + * count right if last line in included file is syntactically + * invalid and has no newline. + */ + if (file->eof_reached == 0) { + file->eof_reached = 1; + return '\n'; + } + while (c == EOF) { + if (file == topfile || popfile() == EOF) + return EOF; + c = igetc(); + } + } + return c; +} + +void +lungetc(int c) +{ + if (c == EOF) + return; + + if (file->ungetpos >= file->ungetsize) { + void *p = reallocarray(file->ungetbuf, file->ungetsize, 2); + if (p == NULL) + err(1, "lungetc"); + file->ungetbuf = p; + file->ungetsize *= 2; + } + file->ungetbuf[file->ungetpos++] = c; +} + +int +findeol(void) +{ + int c; + + /* Skip to either EOF or the first real EOL. */ + while (1) { + c = lgetc(0); + if (c == '\n') { + file->lineno++; + break; + } + if (c == EOF) + break; + } + return ERROR; +} + +#if 0 +int my_yylex(void); + +int +yylex(void) +{ + int x; + + switch (x = my_yylex()) { + case AUTH: + puts("auth"); + break; + case CERT: + puts("cert"); + break; + case ERROR: + puts("error"); + break; + case INCLUDE: + puts("include"); + break; + case KEY: + puts("key"); + break; + case LISTEN: + puts("listen"); + break; + case NO: + puts("no"); + break; + case ON: + puts("on"); + break; + case PKI: + puts("pki"); + break; + case PORT: + puts("port"); + break; + case TABLE: + puts("table"); + break; + case TLS: + puts("tls"); + break; + case YES: + puts("yes"); + break; + case STRING: + printf("string \"%s\"\n", yylval.v.string); + break; + case NUMBER: + printf("number %"PRIi64"\n", yylval.v.number); + default: + printf("character "); + if (x == '\n') + printf("\\n"); + else + printf("%c", x); + printf(" [0x%x]", x); + printf("\n"); + break; + } + + return x; +} + +int +my_yylex(void) +#else +int +yylex(void) +#endif +{ + char buf[8096]; + char *p, *val; + int quotec, next, c; + int token; + +top: + p = buf; + while ((c = lgetc(0)) == ' ' || c == '\t') + ; /* nothing */ + + yylval.lineno = file->lineno; + if (c == '#') + while ((c = lgetc(0)) != '\n' && c != EOF) + ; /* nothing */ + if (c == '$' && !expanding) { + while (1) { + if ((c = lgetc(0)) == EOF) + return 0; + + if (p + 1 >= buf + sizeof(buf) - 1) { + yyerror("string too long"); + return findeol(); + } + if (isalnum(c) || c == '_') { + *p++ = c; + continue; + } + *p = '\0'; + lungetc(c); + break; + } + val = symget(buf); + if (val == NULL) { + yyerror("macro '%s' not defined", buf); + return findeol(); + } + p = val + strlen(val) - 1; + lungetc(DONE_EXPAND); + while (p >= val) { + lungetc((unsigned char)*p); + p--; + } + lungetc(START_EXPAND); + goto top; + } + + switch (c) { + case '\'': + case '"': + quotec = c; + while (1) { + if ((c = lgetc(quotec)) == EOF) + return 0; + if (c == '\n') { + file->lineno++; + continue; + } else if (c == '\\') { + if ((next = lgetc(quotec)) == EOF) + return (0); + if (next == quotec || next == ' ' || + next == '\t') + c = next; + else if (next == '\n') { + file->lineno++; + continue; + } else + lungetc(next); + } else if (c == quotec) { + *p = '\0'; + break; + } else if (c == '\0') { + yyerror("syntax error"); + return findeol(); + } + if (p + 1 >= buf + sizeof(buf) - 1) { + yyerror("string too long"); + return findeol(); + } + *p++ = c; + } + yylval.v.string = strdup(buf); + if (yylval.v.string == NULL) + err(1, "yylex: strdup"); + return STRING; + } + +#define allowed_to_end_number(x) \ + (isspace(x) || x == ')' || x ==',' || x == '/' || x == '}' || x == '=') + + if (c == '-' || isdigit(c)) { + do { + *p++ = c; + if ((size_t)(p-buf) >= sizeof(buf)) { + yyerror("string too long"); + return findeol(); + } + } while ((c = lgetc(0)) != EOF && isdigit(c)); + lungetc(c); + if (p == buf + 1 && buf[0] == '-') + goto nodigits; + if (c == EOF || allowed_to_end_number(c)) { + const char *errstr = NULL; + + *p = '\0'; + yylval.v.number = strtonum(buf, LLONG_MIN, + LLONG_MAX, &errstr); + if (errstr) { + yyerror("\"%s\" invalid number: %s", + buf, errstr); + return findeol(); + } + return NUMBER; + } else { +nodigits: + while (p > buf + 1) + lungetc((unsigned char)*--p); + c = (unsigned char)*--p; + if (c == '-') + return c; + } + } + +#define allowed_in_string(x) \ + (isalnum(x) || (ispunct(x) && x != '(' && x != ')' && \ + x != '{' && x != '}' && \ + x != '!' && x != '=' && x != '#' && \ + x != ',' && x != '>')) + + if (isalnum(c) || c == ':' || c == '_') { + do { + *p++ = c; + if ((size_t)(p-buf) >= sizeof(buf)) { + yyerror("string too long"); + return findeol(); + } + } while ((c = lgetc(0)) != EOF && (allowed_in_string(c))); + lungetc(c); + *p = '\0'; + if ((token = lookup(buf)) == STRING) + if ((yylval.v.string = strdup(buf)) == NULL) + err(1, "yylex: strdup"); + return token; + } + if (c == '\n') { + yylval.lineno = file->lineno; + file->lineno++; + } + if (c == EOF) + return 0; + return c; +} + +int +check_file_secrecy(int fd, const char *fname) +{ + struct stat st; + + if (fstat(fd, &st)) { + log_warn("cannot stat %s", fname); + return -1; + } + if (st.st_uid != 0 && st.st_uid != getuid()) { + log_warnx("%s: owner not root or current user", fname); + return -1; + } + if (st.st_mode & (S_IWGRP | S_IXGRP | S_IRWXO)) { + log_warnx("%s: group writable or world read/writable", fname); + return -1; + } + return 0; +} + +struct file * +pushfile(const char *name, int secret) +{ + struct file *nfile; + + if ((nfile = calloc(1, sizeof(struct file))) == NULL) { + log_warn("calloc"); + return NULL; + } + if ((nfile->name = strdup(name)) == NULL) { + log_warn("strdup"); + free(nfile); + return NULL; + } + if ((nfile->stream = fopen(nfile->name, "r")) == NULL) { + log_warn("%s", nfile->name); + free(nfile->name); + free(nfile); + return NULL; + } else if (secret && + check_file_secrecy(fileno(nfile->stream), nfile->name)) { + fclose(nfile->stream); + free(nfile->name); + free(nfile); + return NULL; + } + nfile->lineno = TAILQ_EMPTY(&files) ? 1 : 0; + nfile->ungetsize = 16; + nfile->ungetbuf = malloc(nfile->ungetsize); + if (nfile->ungetbuf == NULL) { + log_warn("malloc"); + fclose(nfile->stream); + free(nfile->name); + free(nfile); + return NULL; + } + TAILQ_INSERT_TAIL(&files, nfile, entry); + return nfile; +} + +int +popfile(void) +{ + struct file *prev; + + if ((prev = TAILQ_PREV(file, files, entry)) != NULL) + prev->errors += file->errors; + + TAILQ_REMOVE(&files, file, entry); + fclose(file->stream); + free(file->name); + free(file->ungetbuf); + free(file); + file = prev; + return file ? 0 : EOF; +} + +struct kd_conf * +parse_config(const char *filename) +{ + struct sym *sym, *next; + + counter = 0; + conf = config_new_empty(); + + file = pushfile(filename, 0); + if (file == NULL) { + free(conf); + return NULL; + } + topfile = file; + + yyparse(); + errors = file->errors; + popfile(); + + /* Free macros and check which have not been used. */ + TAILQ_FOREACH_SAFE(sym, &symhead, entry, next) { + if (verbose && !sym->used) + fprintf(stderr, "warning: macro '%s' not used\n", + sym->nam); + if (!sym->persist) { + free(sym->nam); + free(sym->val); + TAILQ_REMOVE(&symhead, sym, entry); + free(sym); + } + } + + if (errors) { + clear_config(conf); + return NULL; + } + + return conf; +} + +int +symset(const char *nam, const char *val, int persist) +{ + struct sym *sym; + + TAILQ_FOREACH(sym, &symhead, entry) { + if (strcmp(nam, sym->nam) == 0) + break; + } + + if (sym != NULL) { + if (sym->persist == 1) + return 0; + else { + free(sym->nam); + free(sym->val); + TAILQ_REMOVE(&symhead, sym, entry); + free(sym); + } + } + if ((sym = calloc(1, sizeof(*sym))) == NULL) + return -1; + + sym->nam = strdup(nam); + if (sym->nam == NULL) { + free(sym); + return -1; + } + sym->val = strdup(val); + if (sym->val == NULL) { + free(sym->nam); + free(sym); + return -1; + } + sym->used = 0; + sym->persist = persist; + TAILQ_INSERT_TAIL(&symhead, sym, entry); + return 0; +} + +int +cmdline_symset(char *s) +{ + char *sym, *val; + int ret; + + if ((val = strrchr(s, '=')) == NULL) + return -1; + sym = strndup(s, val - s); + if (sym == NULL) + errx(1, "%s: strndup", __func__); + ret = symset(sym, val + 1, 1); + free(sym); + + return ret; +} + +char * +symget(const char *nam) +{ + struct sym *sym; + + TAILQ_FOREACH(sym, &symhead, entry) { + if (strcmp(nam, sym->nam) == 0) { + sym->used = 1; + return sym->val; + } + } + return NULL; +} + +void +clear_config(struct kd_conf *xconf) +{ + /* free stuff? */ + + free(xconf); +} + +static void +add_table(const char *name, const char *type, const char *path) +{ + if (table_open(conf, name, type, path) == -1) + yyerror("can't initialize table %s", name); + table = STAILQ_FIRST(&conf->table_head)->table; +} + +static struct table * +findtable(const char *name) +{ + struct kd_tables_conf *i; + + STAILQ_FOREACH(i, &conf->table_head, entry) { + if (!strcmp(i->table->t_name, name)) + return i->table; + } + + yyerror("unknown table %s", name); + return NULL; +} + +static void +add_cert(const char *name, const char *path) +{ + struct kd_pki_conf *pki; + + STAILQ_FOREACH(pki, &conf->pki_head, entry) { + if (strcmp(name, pki->name) != 0) + continue; + + if (pki->cert != NULL) { + yyerror("duplicate `pki %s cert'", name); + return; + } + + goto set; + } + + pki = xcalloc(1, sizeof(*pki)); + strlcpy(pki->name, name, sizeof(pki->name)); + STAILQ_INSERT_HEAD(&conf->pki_head, pki, entry); + +set: + if ((pki->cert = tls_load_file(path, &pki->certlen, NULL)) == NULL) + fatal(NULL); +} + +static void +add_key(const char *name, const char *path) +{ + struct kd_pki_conf *pki; + + STAILQ_FOREACH(pki, &conf->pki_head, entry) { + if (strcmp(name, pki->name) != 0) + continue; + + if (pki->key != NULL) { + yyerror("duplicate `pki %s key'", name); + return; + } + + goto set; + } + + pki = xcalloc(1, sizeof(*pki)); + strlcpy(pki->name, name, sizeof(pki->name)); + STAILQ_INSERT_HEAD(&conf->pki_head, pki, entry); + +set: + if ((pki->key = tls_load_file(path, &pki->keylen, NULL)) == NULL) + fatal(NULL); +} + +static struct kd_listen_conf * +listen_new(void) +{ + struct kd_listen_conf *l; + + l = xcalloc(1, sizeof(*l)); + l->id = counter++; + l->fd = -1; + + STAILQ_INSERT_HEAD(&conf->listen_head, l, entry); + return l; +} blob - /dev/null blob + e7c8a5965571c231fcca030c4048a11e0f7bfd28 (mode 644) --- /dev/null +++ kamid/table.c @@ -0,0 +1,105 @@ +/* + * Copyright (c) 2021 Omar Polo + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "log.h" +#include "utils.h" +#include "kamid.h" + +#include "table.h" + +int +table_open(struct kd_conf *conf, const char *name, const char *type, + const char *path) +{ + struct table *t; + struct kd_tables_conf *entry; + struct table_backend *backends[] = { + &table_static, + NULL, + }, *b; + size_t i; + + for (i = 0; backends[i] != NULL; ++i) { + b = backends[i]; + if (!strcmp(type, b->name)) + goto found; + } + log_warn("unknown table type %s", type); + return -1; + +found: + if (b->open == NULL) { + log_warn("can't open table %s (type %s)", + name, b->name); + return -1; + } + + t = xcalloc(1, sizeof(*t)); + strlcpy(t->t_name, name, sizeof(t->t_name)); + if (path != NULL) + strlcpy(t->t_path, path, sizeof(t->t_path)); + t->t_backend = b; + + if (t->t_backend->open(t) == -1) + fatal("can't open table %s (type %s)", + name, path); + + entry = xcalloc(1, sizeof(*entry)); + entry->table = t; + STAILQ_INSERT_HEAD(&conf->table_head, entry, entry); + return 0; +} + +int +table_add(struct table *t, const char *key, const char *val) +{ + if (t->t_backend->add == NULL) { + log_warn("can't add to table %s (type %s)", + t->t_name, t->t_backend->name); + return -1; + } + + return t->t_backend->add(t, key, val); +} + +int +table_lookup(struct table *t, const char *key, char **ret_val) +{ + if (t->t_backend->lookup == NULL) { + log_warn("can't lookup table %s (type %s)", + t->t_name, t->t_backend->name); + return -1; + } + + return t->t_backend->lookup(t, key, ret_val); +} + +void +table_close(struct table *t) +{ + if (t->t_backend->close != NULL) + t->t_backend->close(t); +} blob - /dev/null blob + 441a98c2d2203d5c5de63b34018689b42d132d86 (mode 644) --- /dev/null +++ kamid/table.h @@ -0,0 +1,25 @@ +/* + * Copyright (c) 2021 Omar Polo + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef TABLE_H +#define TABLE_H + +int table_open(struct kd_conf *, const char *, const char *, const char *); +int table_add(struct table *, const char *, const char *); +int table_lookup(struct table *, const char *, char **); +void table_close(struct table *); + +#endif blob - /dev/null blob + e83954e7280640d8f36108cc971e7028a7600c9c (mode 644) --- /dev/null +++ kamid/table_static.c @@ -0,0 +1,135 @@ +/* + * Copyright (c) 2021 Omar Polo + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "utils.h" +#include "kamid.h" + +static void *hash_alloc(size_t, void *); +static void *hash_calloc(size_t, size_t, void *); +static void hash_free(void *, void *); + +static int table_static_open(struct table *); +static int table_static_add(struct table *, const char *, const char *); +static int table_static_lookup(struct table *, const char *, char **); +static void table_static_close(struct table *); + +struct table_backend table_static = { + "static", + table_static_open, + table_static_add, + table_static_lookup, + table_static_close, +}; + +struct kp { + char *val; + char key[]; +}; + +static void * +hash_alloc(size_t len, void *d) +{ + return xmalloc(len); +} + +static void * +hash_calloc(size_t nmemb, size_t size, void *d) +{ + return xcalloc(nmemb, size); +} + +static void +hash_free(void *ptr, void *d) +{ + free(ptr); +} + +static int +table_static_open(struct table *t) +{ + struct ohash_info info = { + .key_offset = offsetof(struct kp, key), + .calloc = hash_calloc, + .free = hash_free, + .alloc = hash_alloc, + }; + + t->t_handle = xmalloc(sizeof(struct ohash)); + ohash_init(t->t_handle, 5, &info); + return 0; +} + +int +table_static_add(struct table *t, const char *key, const char *val) +{ + struct kp *kp; + unsigned int slot; + + if (key == NULL) + return -1; + + kp = xcalloc(1, sizeof(*kp) + strlen(key) + 1); + strcpy(kp->key, key); + if (val != NULL) + kp->val = xstrdup(val); + + slot = ohash_qlookup(t->t_handle, kp->key); + ohash_insert(t->t_handle, slot, kp); + + return 0; +} + +int +table_static_lookup(struct table *t, const char *key, char **ret_val) +{ + struct kp *kp; + unsigned int slot; + + slot = ohash_qlookup(t->t_handle, key); + if ((kp = ohash_find(t->t_handle, slot)) == NULL) + return -1; + + *ret_val = xstrdup(kp->val); + return 0; +} + +static void +table_static_close(struct table *t) +{ + struct kp *kp; + unsigned int i; + + for (kp = ohash_first(t->t_handle, &i); + kp != NULL; + kp = ohash_next(t->t_handle, &i)) { + ohash_remove(t->t_handle, i); + free(kp->key); + free(kp->val); + free(kp); + } + + free(t->t_handle); +} blob - fe44bea30966734e0e1715e60e70f6496cb0d631 (mode 644) blob + /dev/null --- client.c +++ /dev/null @@ -1,1622 +0,0 @@ -/* - * Copyright (c) 2021 Omar Polo - * - * Permission to use, copy, modify, and distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. - * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - */ - -#include "compat.h" - -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "client.h" -#include "kamid.h" -#include "log.h" -#include "sandbox.h" -#include "utils.h" - -/* - * XXX: atm is difficult to accept messages bigger than MAX_IMSGSIZE - * minus IMSG_HEADER_SIZE, we need something to split messages into - * chunks and receive them one by the other. - * - * CLIENT_MSIZE is thus the maximum message size we can handle now. - */ -#define CLIENT_MSIZE (MAX_IMSGSIZE - IMSG_HEADER_SIZE) - -#define DEBUG_PACKETS 0 - -/* straight outta /src/usr.bin/ssh/scp.c */ -#define TYPE_OVERFLOW(type, val) \ - ((sizeof(type) == 4 && (val) > INT32_MAX) || \ - (sizeof(type) == 8 && (val) > INT64_MAX) || \ - (sizeof(type) != 4 && sizeof(type) != 8)) - -STAILQ_HEAD(dirhead, dir) dirs; -struct dir { - int refcount; - int fd; - STAILQ_ENTRY(dir) entries; -}; - -STAILQ_HEAD(fidhead, fid) fids; -struct fid { - uint32_t fid; - - char fpath[PATH_MAX]; - - /* - * the flags passed to open(2). O_CLOEXEC means ORCLOSE, that - * is to unlink the file upon Tclunk. - */ - int iomode; - - /* - * if fd is not -1 this fid was opened, fd represents its - * file descriptor and iomode the flags passed to open(2). - */ - int fd; - DIR *d; - struct evbuffer *evb; - - /* - * expected offset for Tread against a directory. - */ - uint64_t offset; - - struct qid qid; - struct dir *dir; - STAILQ_ENTRY(fid) entries; -}; - -static struct imsgev *iev_listener; -static struct evbuffer *evb; -static uint32_t peerid; - -static int handshaked; -uint32_t msize; - -static __dead void client_shutdown(void); -static void client_sig_handler(int, short, void *); -static void client_dispatch_listener(int, short, void *); -static void client_privdrop(const char *, const char *); - -static int client_send_listener(int, const void *, uint16_t); - -static void qid_update_from_sb(struct qid *, struct stat *); - -static struct dir *new_dir(int); -static struct dir *dir_incref(struct dir *); -static void dir_decref(struct dir *); - -static struct fid *new_fid(struct dir *, uint32_t, const char *, struct qid *); -static struct fid *fid_by_id(uint32_t); -static void free_fid(struct fid *); - -static void parse_message(const uint8_t *, size_t, - struct np_msg_header *, uint8_t **); - -static void np_write16(struct evbuffer *, uint16_t); -static void np_write32(struct evbuffer *, uint32_t); -static void np_write64(struct evbuffer *, uint64_t); -static void np_header(uint32_t, uint8_t, uint16_t); -static void np_string(struct evbuffer *, uint16_t, const char *); -static void np_qid(struct evbuffer *, struct qid *); -static void do_send(void); - -static void np_version(uint16_t, uint32_t, const char *); -static void np_attach(uint16_t, struct qid *); -static void np_clunk(uint16_t); -static void np_flush(uint16_t); -static void np_walk(uint16_t, int, struct qid *); -static void np_open(uint16_t, struct qid *, uint32_t); -static void np_create(uint16_t, struct qid *, uint32_t); -static void np_read(uint16_t, uint32_t, void *); -static void np_write(uint16_t, uint32_t); -static void np_stat(uint16_t, uint32_t, void *); -static void np_remove(uint16_t); -static void np_error(uint16_t, const char *); -static void np_errno(uint16_t); - -static int np_read8(const char *, const char *, uint8_t *, - const uint8_t **, size_t *); -static int np_read16(const char *, const char *, uint16_t *, - const uint8_t **, size_t *); -static int np_read32(const char *, const char *, uint32_t *, - const uint8_t **, size_t *); -static int np_read64(const char *, const char *, uint64_t *, - const uint8_t **, size_t *); - -#define READSTRERR -1 -#define READSTRTRUNC -2 -static int np_readstr(const char *, const char *, char *, size_t, - const uint8_t **, size_t *); - -#define NPREAD8(f, dst, src, len) np_read8(__func__, f, dst, src, len) -#define NPREAD16(f, dst, src, len) np_read16(__func__, f, dst, src, len) -#define NPREAD32(f, dst, src, len) np_read32(__func__, f, dst, src, len) -#define NPREAD64(f, dst, src, len) np_read64(__func__, f, dst, src, len) - -#define NPREADSTR(f, b, bl, src, len) np_readstr(__func__, f, b, bl, src, len) - -static void tversion(struct np_msg_header *, const uint8_t *, size_t); -static void tattach(struct np_msg_header *, const uint8_t *, size_t); -static void tclunk(struct np_msg_header *, const uint8_t *, size_t); -static void tflush(struct np_msg_header *, const uint8_t *, size_t); -static void twalk(struct np_msg_header *, const uint8_t *, size_t); -static void topen(struct np_msg_header *, const uint8_t *, size_t); -static void tcreate(struct np_msg_header *, const uint8_t *, size_t); -static void tread(struct np_msg_header *, const uint8_t *, size_t); -static void twrite(struct np_msg_header *, const uint8_t *, size_t); -static void tstat(struct np_msg_header *, const uint8_t *, size_t); -static void tremove(struct np_msg_header *, const uint8_t *, size_t); -static void handle_message(struct imsg *, size_t); - -__dead void -client(int debug, int verbose) -{ - struct event ev_sigint, ev_sigterm; - - log_init(debug, LOG_DAEMON); - log_setverbose(verbose); - - setproctitle("client"); - log_procinit("client"); - - log_debug("warming up"); - - event_init(); - - /* Setup signal handlers */ - signal_set(&ev_sigint, SIGINT, client_sig_handler, NULL); - signal_set(&ev_sigterm, SIGTERM, client_sig_handler, NULL); - - signal_add(&ev_sigint, NULL); - signal_add(&ev_sigterm, NULL); - - signal(SIGPIPE, SIG_IGN); - signal(SIGHUP, SIG_IGN); - - /* Setup pipe and event handler to the listener process */ - if ((iev_listener = malloc(sizeof(*iev_listener))) == NULL) - fatal(NULL); - - imsg_init(&iev_listener->ibuf, 3); - iev_listener->handler = client_dispatch_listener; - - /* Setup event handlers. */ - iev_listener->events = EV_READ; - event_set(&iev_listener->ev, iev_listener->ibuf.fd, - iev_listener->events, iev_listener->handler, iev_listener); - event_add(&iev_listener->ev, NULL); - - event_dispatch(); - client_shutdown(); -} - -static __dead void -client_shutdown(void) -{ - if (evb != NULL) - evbuffer_free(evb); - - msgbuf_clear(&iev_listener->ibuf.w); - close(iev_listener->ibuf.fd); - - free(iev_listener); - - log_debug("client exiting"); - exit(0); -} - -static void -client_sig_handler(int sig, short event, void *d) -{ - /* - * Normal signal handler rules don't apply because libevent - * decouples for us. - */ - - switch (sig) { - case SIGINT: - case SIGTERM: - client_shutdown(); - default: - fatalx("unexpected signal %d", sig); - } -} - -#define AUTH_NONE 0 -#define AUTH_USER 1 -#define AUTH_DONE 2 - -static void -client_dispatch_listener(int fd, short event, void *d) -{ - static int auth = AUTH_NONE; - static char username[64] = {0}; - static char dir[PATH_MAX] = {0}; - struct imsg imsg; - struct imsgev *iev = d; - struct imsgbuf *ibuf; - ssize_t n; - int shut = 0; - - ibuf = &iev->ibuf; - - if (event & EV_READ) { - if ((n = imsg_read(ibuf)) == -1 && errno != EAGAIN) - fatal("imsg_read error"); - if (n == 0) /* Connection closed */ - shut = 1; - } - if (event & EV_WRITE) { - if ((n = msgbuf_write(&ibuf->w)) == -1 && errno != EAGAIN) - fatal("msgbuf_write"); - if (n == 0) /* Connection closed */ - shut = 1; - } - - for (;;) { - if ((n = imsg_get(ibuf, &imsg)) == -1) - fatal("%s: imsg_get error", __func__); - if (n == 0) /* No more messages. */ - break; - - switch (imsg.hdr.type) { - case IMSG_AUTH: - peerid = imsg.hdr.peerid; - if (auth) - fatalx("%s: IMSG_AUTH already done", __func__); - auth = AUTH_USER; - ((char *)imsg.data)[IMSG_DATA_SIZE(imsg)-1] = '\0'; - strlcpy(username, imsg.data, sizeof(username)); - break; - case IMSG_AUTH_DIR: - if (auth != AUTH_USER) - fatalx("%s: IMSG_AUTH_DIR not after IMSG_AUTH", - __func__); - auth = AUTH_DONE; - ((char *)imsg.data)[IMSG_DATA_SIZE(imsg)-1] = '\0'; - strlcpy(dir, imsg.data, sizeof(dir)); - client_privdrop(username, dir); - memset(username, 0, sizeof(username)); - memset(dir, 0, sizeof(username)); - break; - case IMSG_BUF: - /* echo! */ - if (!auth) - fatalx("%s: can't handle messages before" - " doing the auth", __func__); - handle_message(&imsg, IMSG_DATA_SIZE(imsg)); - break; - case IMSG_CONN_GONE: - log_debug("closing"); - shut = 1; - break; - default: - log_debug("%s: unexpected imsg %d", - __func__, imsg.hdr.type); - break; - } - imsg_free(&imsg); - } - - if (!shut) - imsg_event_add(iev); - else { - /* This pipe is dead. Remove its event handler. */ - event_del(&iev->ev); - log_debug("pipe closed, shutting down..."); - event_loopexit(NULL); - } -} - -static void -client_privdrop(const char *username, const char *dir) -{ - struct passwd *pw; - - setproctitle("client %s", username); - - if ((pw = getpwnam(username)) == NULL) - fatalx("getpwnam(%s) failed", username); - - if (chroot(dir) == -1) - fatal("chroot"); - if (chdir("/") == -1) - fatal("chdir(\"/\")"); - - if (setgroups(1, &pw->pw_gid) || - setresgid(pw->pw_gid, pw->pw_gid, pw->pw_gid) || - setresuid(pw->pw_uid, pw->pw_uid, pw->pw_uid)) - fatal("can't drop privileges"); - - sandbox_client(); - log_debug("client ready; user=%s dir=%s", username, dir); - - if ((evb = evbuffer_new()) == NULL) - fatal("evbuffer_new"); -} - -static int -client_send_listener(int type, const void *data, uint16_t len) -{ - int ret; - - if ((ret = imsg_compose(&iev_listener->ibuf, type, peerid, 0, -1, - data, len)) != -1) - imsg_event_add(iev_listener); - - return ret; -} - -/* set qid fields from sb */ -static void -qid_update_from_sb(struct qid *qid, struct stat *sb) -{ - qid->path = sb->st_ino; - - /* - * Theoretically (and hopefully!) this should be a 64 bit - * number. Unfortunately, 9P uses 32 bit timestamps. - */ - qid->vers = sb->st_mtim.tv_sec; - - if (S_ISREG(sb->st_mode)) - qid->type = QTFILE; - else if (S_ISDIR(sb->st_mode)) - qid->type = QTDIR; - else if (S_ISLNK(sb->st_mode)) - qid->type = QTSYMLINK; -} - -/* creates a qid given a fd */ -static struct dir * -new_dir(int fd) -{ - struct dir *dir; - - if ((dir = calloc(1, sizeof(*dir))) == NULL) - return NULL; - - dir->fd = fd; - STAILQ_INSERT_HEAD(&dirs, dir, entries); - return dir; -} - -static struct dir * -dir_incref(struct dir *dir) -{ - dir->refcount++; - return dir; -} - -static void -dir_decref(struct dir *dir) -{ - if (--dir->refcount > 0) - return; - - STAILQ_REMOVE(&dirs, dir, dir, entries); - - close(dir->fd); - free(dir); -} - -static struct fid * -new_fid(struct dir *dir, uint32_t fid, const char *path, struct qid *qid) -{ - struct fid *f; - struct qid q; - struct stat sb; - - if (qid == NULL) { - if (fstatat(dir->fd, path, &sb, 0)) { - log_warn("fstatat(%s)", path); - return NULL; - } - qid_update_from_sb(&q, &sb); - qid = &q; - } - - if ((f = calloc(1, sizeof(*f))) == NULL) - return NULL; - - f->dir = dir_incref(dir); - f->fid = fid; - f->fd = -1; - - strlcpy(f->fpath, path, sizeof(f->fpath)); - - memcpy(&f->qid, qid, sizeof(f->qid)); - - STAILQ_INSERT_HEAD(&fids, f, entries); - - return f; -} - -static struct fid * -fid_by_id(uint32_t fid) -{ - struct fid *f; - - STAILQ_FOREACH(f, &fids, entries) { - if (f->fid == fid) - return f; - } - - return NULL; -} - -static void -free_fid(struct fid *f) -{ - int r; - - if (f->fd != -1) { - if (f->d != NULL) - r = closedir(f->d); - else - r = close(f->fd); - - if (r == -1) - fatal("can't close fid %d", f->fid); - - if (f->evb != NULL) - evbuffer_free(f->evb); - - /* try to honour ORCLOSE if requested */ - if (f->iomode & O_CLOEXEC) - unlinkat(f->dir->fd, f->fpath, 0); - } - - dir_decref(f->dir); - - STAILQ_REMOVE(&fids, f, fid, entries); - free(f); -} - -static void -parse_message(const uint8_t *data, size_t len, struct np_msg_header *hdr, - uint8_t **cnt) -{ - size_t olen = len; - - if (!NPREAD32("len", &hdr->len, &data, &len) || - !NPREAD8("type", &hdr->type, &data, &len) || - !NPREAD16("tag", &hdr->tag, &data, &len)) - goto err; - - if (olen != hdr->len) - goto err; - - if (hdr->type < Tversion || - hdr->type >= Tmax || - hdr->type == Terror || - (hdr->type & 0x1) != 0) /* cannot recv a R* */ - goto err; - - hdr->tag = le32toh(hdr->tag); - - *cnt = (uint8_t *)data; - return; - -err: - /* TODO: send a proper message to terminate the connection. */ - fatalx("got invalid message"); -} - -static void -np_write16(struct evbuffer *e, uint16_t x) -{ - x = htole16(x); - evbuffer_add(e, &x, sizeof(x)); -} - -static void -np_write32(struct evbuffer *e, uint32_t x) -{ - x = htole32(x); - evbuffer_add(e, &x, sizeof(x)); -} - -static void -np_write64(struct evbuffer *e, uint64_t x) -{ - x = htole64(x); - evbuffer_add(e, &x, sizeof(x)); -} - -static void -np_writebuf(struct evbuffer *e, size_t len, void *data) -{ - evbuffer_add(e, data, len); -} - -static void -np_header(uint32_t len, uint8_t type, uint16_t tag) -{ - len += HEADERSIZE; - - len = htole32(len); - tag = htole16(tag); - - evbuffer_add(evb, &len, sizeof(len)); - evbuffer_add(evb, &type, sizeof(type)); - evbuffer_add(evb, &tag, sizeof(tag)); -} - -static void -np_string(struct evbuffer *e, uint16_t len, const char *str) -{ - uint16_t l = len; - - len = htole16(len); - evbuffer_add(e, &len, sizeof(len)); - evbuffer_add(e, str, l); -} - -static void -np_qid(struct evbuffer *e, struct qid *qid) -{ - uint64_t path; - uint32_t vers; - - path = htole64(qid->path); - vers = htole32(qid->vers); - - evbuffer_add(e, &qid->type, sizeof(qid->type)); - evbuffer_add(e, &vers, sizeof(vers)); - evbuffer_add(e, &path, sizeof(path)); -} - -static void -do_send(void) -{ - size_t len; - void *data; - - len = EVBUFFER_LENGTH(evb); - data = EVBUFFER_DATA(evb); - -#if DEBUG_PACKETS - hexdump("outgoing packet", data, len); -#endif - client_send_listener(IMSG_BUF, data, len); - evbuffer_drain(evb, len); -} - -static void -np_version(uint16_t tag, uint32_t msize, const char *version) -{ - uint16_t l; - - l = strlen(version); - - msize = htole32(msize); - - np_header(sizeof(msize) + sizeof(l) + l, Rversion, tag); - evbuffer_add(evb, &msize, sizeof(msize)); - np_string(evb, l, version); - do_send(); -} - -static void -np_attach(uint16_t tag, struct qid *qid) -{ - np_header(QIDSIZE, Rattach, tag); - np_qid(evb, qid); - do_send(); -} - -static void -np_clunk(uint16_t tag) -{ - np_header(0, Rclunk, tag); - do_send(); -} - -static void -np_flush(uint16_t tag) -{ - np_header(0, Rflush, tag); - do_send(); -} - -static void -np_walk(uint16_t tag, int nwqid, struct qid *wqid) -{ - int i; - - /* two bytes for the counter */ - np_header(2 + QIDSIZE * nwqid, Rwalk, tag); - np_write16(evb, nwqid); - for (i = 0; i < nwqid; ++i) - np_qid(evb, wqid + i); - - do_send(); -} - -static void -np_open(uint16_t tag, struct qid *qid, uint32_t iounit) -{ - np_header(QIDSIZE + sizeof(iounit), Ropen, tag); - np_qid(evb, qid); - np_write32(evb, iounit); - do_send(); -} - -static void -np_create(uint16_t tag, struct qid *qid, uint32_t iounit) -{ - np_header(QIDSIZE + sizeof(iounit), Rcreate, tag); - np_qid(evb, qid); - np_write32(evb, iounit); - do_send(); -} - -static void -np_read(uint16_t tag, uint32_t count, void *data) -{ - if (sizeof(count) + count + HEADERSIZE >= msize) { - np_error(tag, "Rread would overflow"); - return; - } - - np_header(sizeof(count) + count, Rread, tag); - np_write32(evb, count); - np_writebuf(evb, count, data); - do_send(); -} - -static void -np_write(uint16_t tag, uint32_t count) -{ - np_header(sizeof(count), Rwrite, tag); - np_write32(evb, count); - do_send(); -} - -static void -np_stat(uint16_t tag, uint32_t count, void *data) -{ - if (sizeof(count) + count + HEADERSIZE >= msize) { - np_error(tag, "Rstat would overflow"); - return; - } - - np_header(count, Rstat, tag); - np_writebuf(evb, count, data); - do_send(); -} - -static void -np_remove(uint16_t tag) -{ - np_header(0, Rremove, tag); - do_send(); -} - -static void -np_error(uint16_t tag, const char *errstr) -{ - uint16_t l; - - l = strlen(errstr); - - np_header(sizeof(l) + l, Rerror, tag); - np_string(evb, l, errstr); - do_send(); -} - -static void -np_errno(uint16_t tag) -{ - int saved_errno; - char buf[NL_TEXTMAX] = {0}; - - saved_errno = errno; - - strerror_r(errno, buf, sizeof(buf)); - np_error(tag, buf); - - errno = saved_errno; -} - -static int -np_read8(const char *t, const char *f, uint8_t *dst, const uint8_t **src, - size_t *len) -{ - if (*len < sizeof(*dst)) { - log_warnx("%s: wanted %zu bytes for the %s field but only " - "%zu are available.", t, sizeof(*dst), f, *len); - return -1; - } - - memcpy(dst, *src, sizeof(*dst)); - *src += sizeof(*dst); - *len -= sizeof(*dst); - - return 1; -} - -static int -np_read16(const char *t, const char *f, uint16_t *dst, const uint8_t **src, - size_t *len) -{ - if (*len < sizeof(*dst)) { - log_warnx("%s: wanted %zu bytes for the %s field but only " - "%zu are available.", t, sizeof(*dst), f, *len); - return -1; - } - - memcpy(dst, *src, sizeof(*dst)); - *src += sizeof(*dst); - *len -= sizeof(*dst); - *dst = le16toh(*dst); - - return 1; -} - -static int -np_read32(const char *t, const char *f, uint32_t *dst, const uint8_t **src, - size_t *len) -{ - if (*len < sizeof(*dst)) { - log_warnx("%s: wanted %zu bytes for the %s field but only " - "%zu are available.", t, sizeof(*dst), f, *len); - return -1; - } - - memcpy(dst, *src, sizeof(*dst)); - *src += sizeof(*dst); - *len -= sizeof(*dst); - *dst = le32toh(*dst); - - return 1; -} - -static int -np_read64(const char *t, const char *f, uint64_t *dst, const uint8_t **src, - size_t *len) -{ - if (*len < sizeof(*dst)) { - log_warnx("%s: wanted %zu bytes for the %s field but only " - "%zu are available.", t, sizeof(*dst), f, *len); - return -1; - } - - memcpy(dst, *src, sizeof(*dst)); - *src += sizeof(*dst); - *len -= sizeof(*dst); - *dst = le64toh(*dst); - - return 1; -} - -static int -np_readstr(const char *t, const char *f, char *res, size_t reslen, - const uint8_t **src, size_t *len) -{ - uint16_t sl; - char buf[32]; - - strlcpy(buf, f, sizeof(buf)); - strlcat(buf, "-len", sizeof(buf)); - - if (!np_read16(t, buf, &sl, src, len)) - return READSTRERR; - - if (*len < sl) { - log_warnx("%s: wanted %d bytes for the %s field but only " - "%zu are available.", t, sl, f, *len); - return READSTRERR; - } - - if (*len > reslen-1) - return READSTRTRUNC; - - memcpy(res, *src, sl); - res[sl] = '\0'; - *src += sl; - *len -= sl; - - return 0; -} - -static void -tversion(struct np_msg_header *hdr, const uint8_t *data, size_t len) -{ - char *dot, version[32]; - - if (handshaked) - goto err; - - /* msize[4] version[s] */ - if (!NPREAD32("msize", &msize, &data, &len)) - goto err; - - switch (NPREADSTR("version", version, sizeof(version), &data, &len)) { - case READSTRERR: - goto err; - case READSTRTRUNC: - log_warnx("9P version string too long, truncated"); - goto mismatch; - } - - if ((dot = strchr(version, '.')) != NULL) - *dot = '\0'; - - if (strcmp(version, VERSION9P) != 0 || - msize == 0) - goto mismatch; - - /* version matched */ - handshaked = 1; - msize = MIN(msize, CLIENT_MSIZE); - client_send_listener(IMSG_MSIZE, &msize, sizeof(msize)); - np_version(hdr->tag, msize, VERSION9P); - return; - -mismatch: - log_warnx("unknown 9P version string: \"%s\", want "VERSION9P, - version); - np_version(hdr->tag, MSIZE9P, "unknown"); - return; - -err: - client_send_listener(IMSG_CLOSE, NULL, 0); - client_shutdown(); -} - -static void -tattach(struct np_msg_header *hdr, const uint8_t *data, size_t len) -{ - struct dir *dir; - struct fid *f; - uint32_t fid, afid; - int fd; - char aname[PATH_MAX]; - - /* fid[4] afid[4] uname[s] aname[s] */ - - if (!NPREAD32("fid", &fid, &data, &len) || - !NPREAD32("afid", &afid, &data, &len)) - goto err; - - /* read the uname but don't actually use it */ - switch (NPREADSTR("uname", aname, sizeof(aname), &data, &len)) { - case READSTRERR: - goto err; - case READSTRTRUNC: - np_error(hdr->tag, "name too long"); - return; - } - - switch (NPREADSTR("aname", aname, sizeof(aname), &data, &len)) { - case READSTRERR: - goto err; - case READSTRTRUNC: - np_error(hdr->tag, "name too long"); - return; - } - - if (fid_by_id(fid) != NULL || afid != NOFID) { - np_error(hdr->tag, "invalid fid or afid"); - return; - } - - if ((fd = open(aname, O_RDONLY|O_DIRECTORY)) == -1) - goto fail; - - if ((dir = new_dir(fd)) == NULL) - goto fail; - - log_debug("attached %s to %d", aname, fid); - - if ((f = new_fid(dir, fid, aname, NULL)) == NULL) { - dir_decref(dir); - goto fail; - } - - np_attach(hdr->tag, &f->qid); - return; - -fail: - np_errno(hdr->tag); - log_warn("failed to attach %s", aname); - return; - -err: - client_send_listener(IMSG_CLOSE, NULL, 0); - client_shutdown(); -} - -static void -tclunk(struct np_msg_header *hdr, const uint8_t *data, size_t len) -{ - struct fid *f; - uint32_t fid; - - /* fid[4] */ - if (!NPREAD32("fid", &fid, &data, &len)) { - client_send_listener(IMSG_CLOSE, NULL, 0); - client_shutdown(); - return; - } - - if ((f = fid_by_id(fid)) == NULL) { - np_error(hdr->tag, "invalid fid"); - return; - } - - free_fid(f); - np_clunk(hdr->tag); -} - -static void -tflush(struct np_msg_header *hdr, const uint8_t *data, size_t len) -{ - uint16_t oldtag; - - /* - * We're doing only synchronous I/O. Tflush is implemented - * only because it's illegal to reply with a Rerror. - */ - - /* oldtag[2] */ - if (len != sizeof(oldtag)) { - log_warnx("Tflush with the wrong size: got %zu want %zu", - len, sizeof(oldtag)); - client_send_listener(IMSG_CLOSE, NULL, 0); - client_shutdown(); - return; - } - - np_flush(hdr->tag); -} - -static void -twalk(struct np_msg_header *hdr, const uint8_t *data, size_t len) -{ - struct stat sb; - struct dir *dir; - struct qid wqid[MAXWELEM] = {0}; - struct fid *f, *nf; - uint32_t fid, newfid; - uint16_t nwname; - int fd, oldfd, no, nwqid = 0; - char wnam[PATH_MAX]; - - if (!NPREAD32("fid", &fid, &data, &len) || - !NPREAD32("newfid", &newfid, &data, &len) || - !NPREAD16("nwname", &nwname, &data, &len)) - goto err; - - if (nwname > MAXWELEM) { - log_warnx("Twalk: more than %d path elements: %d", - MAXWELEM, nwname); - goto err; - } - - if ((f = fid_by_id(fid)) == NULL) { - np_error(hdr->tag, "invalid fid"); - return; - } - - if (f->fd != -1) { - np_error(hdr->tag, "fid already opened for I/O"); - return; - } - - if (fid == newfid) - nf = f; - else if ((nf = fid_by_id(newfid)) != NULL) { - np_error(hdr->tag, "newfid already in use"); - return; - } else - nf = NULL; - - /* special case: fid duplication */ - if (nwname == 0) { - /* - * TODO: should we forbid fids duplication when fid == - * newfid? - */ - if (nf == NULL && - (nf = new_fid(f->dir, newfid, f->fpath, &f->qid)) == NULL) - fatal("new_fid duplication"); - - np_walk(hdr->tag, 0, NULL); - return; - } - - if (!(f->qid.type & QTDIR)) { - np_error(hdr->tag, "fid doesn't represent a directory"); - return; - } - - oldfd = f->dir->fd; - - for (nwqid = 0; nwqid < nwname; nwqid++) { - switch (NPREADSTR("wname", wnam, sizeof(wnam), &data, &len)) { - case READSTRERR: - goto err; - case READSTRTRUNC: - np_error(hdr->tag, "wname too long"); - return; - } - - if (*wnam == '\0' || - strchr(wnam, '/') != NULL || - !strcmp(wnam, ".")) { - errno = EINVAL; - goto cantopen; - } - - if ((fd = openat(oldfd, wnam, O_RDONLY|O_DIRECTORY)) == -1 && - errno != ENOTDIR) - goto cantopen; - - if ((fd == -1 && fstatat(oldfd, wnam, &sb, 0) == -1) || - (fd != -1 && fstat(fd, &sb) == -1)) - goto cantopen; - - qid_update_from_sb(&wqid[nwqid], &sb); - - /* reached a file but we still have other components */ - if (fd == -1 && nwqid+1 < nwname) - goto cantopen; - - /* reached the end and found a file */ - if (fd == -1 && nwqid+1 == nwname) - continue; - - if (oldfd != f->dir->fd) - close(oldfd); - oldfd = fd; - } - - /* - * If fd is -1 we've reached a file, otherwise we've just - * reached another directory. We must pay attention to what - * file descriptor we use to create the dir, because if we've - * reached a file and oldfd is f->dir->fd then we *must* share - * the same dir (it was a walk of one path from a directory to a - * file, otherwise fun is bound to happen as soon as the client - * closes the fid for the directory but keeps the one for the - * file. - */ - if (fd == -1 && oldfd == f->dir->fd) - dir = f->dir; - else if (fd == -1) - dir = new_dir(oldfd); - else - dir = new_dir(fd); - - if (dir == NULL) - fatal("new_dir"); - - if (nf == NULL) { - if ((nf = new_fid(dir, newfid, wnam, &wqid[nwqid-1])) == NULL) - fatal("new fid"); - } else { - /* update the dir */ - dir_decref(nf->dir); - nf->dir = dir_incref(dir); - } - - np_walk(hdr->tag, nwqid, wqid); - return; - -cantopen: - if (oldfd != f->dir->fd) - close(oldfd); - no = errno; - if (nwqid == 0) - np_error(hdr->tag, strerror(no)); - else - np_walk(hdr->tag, nwqid, wqid); - return; - -err: - client_send_listener(IMSG_CLOSE, NULL, 0); - client_shutdown(); -} - -static inline int -npmode_to_unix(uint8_t mode, int *flags) -{ - switch (mode & 0x0F) { - case KOREAD: - *flags = O_RDONLY; - break; - case KOWRITE: - *flags = O_WRONLY; - break; - case KORDWR: - *flags = O_RDWR; - break; - case KOEXEC: - log_warnx("tried to open something with KOEXEC"); - /* fallthrough */ - default: - return -1; - } - - if (mode & KOTRUNC) - *flags |= O_TRUNC; - if (mode & KORCLOSE) - *flags |= O_CLOEXEC; - - return 0; -} - -static void -topen(struct np_msg_header *hdr, const uint8_t *data, size_t len) -{ - struct stat sb; - struct qid qid; - struct fid *f; - uint32_t fid; - uint8_t mode; - const char *path; - - /* fid[4] mode[1] */ - if (!NPREAD32("fid", &fid, &data, &len) || - !NPREAD8("mode", &mode, &data, &len)) { - client_send_listener(IMSG_CLOSE, NULL, 0); - client_shutdown(); - return; - } - - if ((f = fid_by_id(fid)) == NULL || f->fd != -1) { - np_error(hdr->tag, "invalid fid"); - return; - } - - if (npmode_to_unix(mode, &f->iomode) == -1) { - np_error(hdr->tag, "invalid mode"); - return; - } - - path = f->fpath; - if (f->qid.type & QTDIR) - path = "."; - - if ((f->fd = openat(f->dir->fd, path, f->iomode)) == -1) { - np_error(hdr->tag, strerror(errno)); - return; - } - - if (fstat(f->fd, &sb) == -1) - fatal("fstat"); - - if (S_ISDIR(sb.st_mode)) { - if ((f->d = fdopendir(f->fd)) == NULL) { - np_errno(hdr->tag); - close(f->fd); - f->fd = -1; - return; - } - - if ((f->evb = evbuffer_new()) == NULL) { - np_errno(hdr->tag); - closedir(f->d); - f->d = NULL; - f->fd = -1; - } - } - - f->offset = 0; - - qid_update_from_sb(&qid, &sb); - np_open(hdr->tag, &qid, sb.st_blksize); -} - -static void -tcreate(struct np_msg_header *hdr, const uint8_t *data, size_t len) -{ - struct stat sb; - struct qid qid; - struct fid *f; - uint32_t fid, perm; - uint8_t mode; - char name[PATH_MAX]; - - /* fid[4] name[s] perm[4] mode[1] */ - if (!NPREAD32("fid", &fid, &data, &len)) - goto err; - switch (NPREADSTR("name", name, sizeof(name), &data, &len)) { - case READSTRERR: - goto err; - case READSTRTRUNC: - np_error(hdr->tag, "name too long"); - return; - } - if (!NPREAD32("perm", &perm, &data, &len) || - !NPREAD8("mode", &mode, &data, &len)) - goto err; - - if (!strcmp(name, ".") || !strcmp(name, "..") || - strchr(name, '/') != NULL) { - np_error(hdr->tag, "invalid name"); - return; - } - - if ((f = fid_by_id(fid)) == NULL || f->fd != -1) { - np_error(hdr->tag, "invalid fid"); - return; - } - - if (!(f->qid.type & QTDIR)) { - np_error(hdr->tag, "fid doesn't identify a directory"); - return; - } - - if (npmode_to_unix(mode, &f->iomode) == -1) { - np_error(hdr->tag, "invalid mode"); - return; - } - - if (f->iomode & O_RDONLY) { - np_error(hdr->tag, "can't create a read-only file"); - return; - } - - /* TODO: parse the mode */ - - if (perm & 0x80000000) { - /* create a directory */ - f->fd = mkdirat(f->dir->fd, name, 0755); - } else { - /* create a file */ - f->fd = openat(f->dir->fd, name, f->iomode | O_CREAT | O_TRUNC, - 0644); - } - - if (f->fd == -1) { - np_errno(hdr->tag); - return; - } - - if (fstat(f->fd, &sb) == -1) - fatal("fstat"); - - if (S_ISDIR(sb.st_mode)) { - if ((f->d = fdopendir(f->fd)) == NULL) { - np_errno(hdr->tag); - close(f->fd); - f->fd = -1; - return; - } - - if ((f->evb = evbuffer_new()) == NULL) { - np_errno(hdr->tag); - closedir(f->d); - f->d = NULL; - f->fd = -1; - } - } - - f->offset = 0; - - qid_update_from_sb(&qid, &sb); - np_create(hdr->tag, &qid, sb.st_blksize); - - return; - -err: - client_send_listener(IMSG_CLOSE, NULL, 0); - client_shutdown(); -} - -static inline void -serialize_stat(const char *fname, struct stat *sb, struct evbuffer *evb) -{ - struct qid qid; - const char *uid, *gid, *muid; - size_t tot; - uint16_t namlen, uidlen, gidlen, ulen; - - qid_update_from_sb(&qid, sb); - - /* TODO: fill these fields */ - uid = ""; - gid = ""; - muid = ""; - - namlen = strlen(fname); - uidlen = strlen(uid); - gidlen = strlen(gid); - ulen = strlen(muid); - - tot = NPSTATSIZ(namlen, uidlen, gidlen, ulen); - if (tot > UINT32_MAX) { - log_warnx("stat info for dir entry %s would overflow", - fname); - return; - } - - np_write16(evb, tot); /* size[2] */ - np_write16(evb, sb->st_rdev); /* type[2] */ - np_write32(evb, sb->st_dev); /* dev[4] */ - np_qid(evb, &qid); /* qid[13] */ - - /* XXX: translate? */ - np_write32(evb, sb->st_mode); /* mode[4] */ - - np_write32(evb, sb->st_atim.tv_sec); /* atime[4] */ - np_write32(evb, sb->st_mtim.tv_sec); /* mtime[4] */ - np_write64(evb, sb->st_size); /* length[8] */ - np_string(evb, namlen, fname); /* name[s] */ - np_string(evb, uidlen, uid); /* uid[s] */ - np_string(evb, gidlen, gid); /* gid[s] */ - np_string(evb, ulen, muid); /* muid[s] */ -} - -static void -tread(struct np_msg_header *hdr, const uint8_t *data, size_t len) -{ - struct fid *f; - ssize_t r; - size_t howmuch; - uint64_t off; - uint32_t fid, count; - char buf[2048]; - - /* fid[4] offset[8] count[4] */ - if (!NPREAD32("fid", &fid, &data, &len) || - !NPREAD64("offset", &off, &data, &len) || - !NPREAD32("count", &count, &data, &len)) { - client_send_listener(IMSG_CLOSE, NULL, 0); - client_shutdown(); - return; - } - - if ((f = fid_by_id(fid)) == NULL || f->fd == -1) { - np_error(hdr->tag, "invalid fid"); - return; - } - - if (TYPE_OVERFLOW(off_t, off)) { - log_warnx("unexpected off_t size"); - np_error(hdr->tag, "invalid offset"); - return; - } - - if (f->d == NULL) { - /* read a file */ - howmuch = MIN(sizeof(buf), count); - r = pread(f->fd, buf, howmuch, (off_t)off); - if (r == -1) - np_errno(hdr->tag); - else - np_read(hdr->tag, r, buf); - } else { - if (off == 0 && f->offset != 0) { - rewinddir(f->d); - f->offset = 0; - evbuffer_drain(f->evb, EVBUFFER_LENGTH(f->evb)); - } - - if (off != f->offset) { - np_error(hdr->tag, "can't seek in directories"); - return; - } - - while (EVBUFFER_LENGTH(f->evb) < count) { - struct dirent *d; - struct stat sb; - - if ((d = readdir(f->d)) == NULL) - break; - if (fstatat(f->fd, d->d_name, &sb, 0) == -1) { - warn("fstatat"); - continue; - } - serialize_stat(d->d_name, &sb, f->evb); - } - - count = MIN(count, EVBUFFER_LENGTH(f->evb)); - np_read(hdr->tag, count, EVBUFFER_DATA(f->evb)); - evbuffer_drain(f->evb, count); - - f->offset += count; - } -} - -static void -twrite(struct np_msg_header *hdr, const uint8_t *data, size_t len) -{ - struct fid *f; - ssize_t r; - uint64_t off; - uint32_t fid, count; - - /* fid[4] offset[8] count[4] data[count] */ - if (!NPREAD32("fid", &fid, &data, &len) || - !NPREAD64("off", &off, &data, &len) || - !NPREAD32("count", &count, &data, &len) || - len != count) { - client_send_listener(IMSG_CLOSE, NULL, 0); - client_shutdown(); - return; - } - - if ((f = fid_by_id(fid)) == NULL || f->fd == -1) { - np_error(hdr->tag, "invalid fid"); - return; - } - - if (!(f->iomode & O_WRONLY) && - !(f->iomode & O_RDWR)) { - np_error(hdr->tag, "fid not opened for writing"); - return; - } - - if (TYPE_OVERFLOW(off_t, off)) { - log_warnx("unexpected off_t size"); - np_error(hdr->tag, "invalid offset"); - return; - } - - if ((r = pwrite(f->fd, data, len, off)) == -1) - np_errno(hdr->tag); - else - np_write(hdr->tag, r); -} - -static void -tstat(struct np_msg_header *hdr, const uint8_t *data, size_t len) -{ - struct evbuffer *evb; - struct stat sb; - struct fid *f; - int r; - uint32_t fid; - - /* fid[4] */ - if (!NPREAD32("fid", &fid, &data, &len)) { - client_send_listener(IMSG_CLOSE, NULL, 0); - client_shutdown(); - return; - } - - /* - * plan9' stat(9P) is not clear on whether the stat is allowed - * on opened fids or not. We're allowing stat regardless of the - * status of the fid. - */ - - if ((f = fid_by_id(fid)) == NULL) { - np_error(hdr->tag, "invalid fid"); - return; - } - - if ((evb = evbuffer_new()) == NULL) - fatal("evbuffer_new"); - - if (f->fd != -1) - r = fstat(f->fd, &sb); - else if (f->qid.type & QTDIR) - r = fstat(f->dir->fd, &sb); - else - r = fstatat(f->dir->fd, f->fpath, &sb, 0); - - if (r == -1) { - np_errno(hdr->tag); - evbuffer_free(evb); - return; - } - - serialize_stat(f->fpath, &sb, evb); - np_stat(hdr->tag, EVBUFFER_LENGTH(evb), EVBUFFER_DATA(evb)); - evbuffer_free(evb); -} - -static void -tremove(struct np_msg_header *hdr, const uint8_t *data, size_t len) -{ - struct fid *f; - uint32_t fid; - int r; - char dirpath[PATH_MAX + 3]; - - /* fid[4] */ - if (!NPREAD32("fid", &fid, &data, &len)) { - client_send_listener(IMSG_CLOSE, NULL, 0); - client_shutdown(); - return; - } - - if ((f = fid_by_id(fid)) == NULL) { - np_error(hdr->tag, "invalid fid"); - return; - } - - if (f->qid.type & QTDIR) { /* directory */ - strlcpy(dirpath, "../", sizeof(dirpath)); - strlcat(dirpath, f->fpath, sizeof(dirpath)); - r = unlinkat(f->dir->fd, dirpath, AT_REMOVEDIR); - } else /* file */ - r = unlinkat(f->dir->fd, f->fpath, 0); - - if (r == -1) - np_errno(hdr->tag); - else - np_remove(hdr->tag); - - free_fid(f); -} - -static void -handle_message(struct imsg *imsg, size_t len) -{ - struct msg { - uint8_t type; - void (*fn)(struct np_msg_header *, const uint8_t *, size_t); - } msgs[] = { - {Tversion, tversion}, - {Tattach, tattach}, - {Tclunk, tclunk}, - {Tflush, tflush}, - {Twalk, twalk}, - {Topen, topen}, - {Tcreate, tcreate}, - {Tread, tread}, - {Twrite, twrite}, - {Tstat, tstat}, - {Tremove, tremove}, - }; - struct np_msg_header hdr; - size_t i; - uint8_t *data; - -#if DEBUG_PACKETS - hexdump("incoming packet", imsg->data, len); -#endif - - parse_message(imsg->data, len, &hdr, &data); - len -= HEADERSIZE; - - log_debug("got request: len=%d type=%d[%s] tag=%d", - hdr.len, hdr.type, pp_msg_type(hdr.type), hdr.tag); - - if (!handshaked && hdr.type != Tversion) { - client_send_listener(IMSG_CLOSE, NULL, 0); - client_shutdown(); - return; - } - - for (i = 0; i < sizeof(msgs)/sizeof(msgs[0]); ++i) { - if (msgs[i].type != hdr.type) - continue; - - msgs[i].fn(&hdr, data, len); - return; - } - - np_error(hdr.tag, "Not supported."); -} blob - /dev/null blob + af396af039bb01b69bd7997b71bc09cd8dec950e (mode 644) --- /dev/null +++ kamid-version.mk @@ -0,0 +1,8 @@ +KAMID_RELEASE=No +KAMID_VERSION_NUMBER=0.1 + +.if ${KAMID_RELEASE} == Yes +KAMID_VERSION=${KAMID_VERSION_NUMBER} +.else +KAMID_VERSION=${KAMID_VERSION_NUMBER}-current +.endif blob - a007ec7f0ca02d97660866c9201bfe28e6af56a4 (mode 644) blob + /dev/null --- client.h +++ /dev/null @@ -1,24 +0,0 @@ -/* - * Copyright (c) 2021 Omar Polo - * - * Permission to use, copy, modify, and distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. - * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - */ - -#ifndef CLIENT_H -#define CLIENT_H - -#include "compat.h" - -__dead void client(int, int); - -#endif blob - /dev/null blob + 690778f94a83a991200822a1c38328ca1cbbe480 (mode 644) --- /dev/null +++ kamiftp/Makefile @@ -0,0 +1,18 @@ +.PATH:${.CURDIR}/../lib + +.include "../kamid-version.mk" + +PROG= kamiftp +SRCS= 9pclib.c ftp.c log.c utils.c +MAN= kamiftp.1 + +CPPFLAGS= -I${.CURDIR}/../lib -I${.CURDIR} + +LDADD+= -levent -lutil -ltls +DPADD+= ${LIBEVENT} ${LIBUTIL} ${LIBTLS} + +.if ${KAMID_RELEASE} != Yes +NOMAN= Yes +.endif + +.include blob - /dev/null blob + 8dd79f8941e896cd85d3f57cd9d5cb8a9d6af4a3 (mode 644) --- /dev/null +++ kamiftp/ftp.c @@ -0,0 +1,1012 @@ +/* + * Copyright (c) 2021 Omar Polo + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#if HAVE_LIBREADLINE +#include +#include +#endif + +#ifndef nitems +#define nitems(_a) (sizeof((_a)) / sizeof((_a)[0])) +#endif + +#include "9pclib.h" +#include "kami.h" +#include "utils.h" +#include "log.h" + +/* flags */ +int tls; +const char *crtpath; +const char *keypath; + +/* state */ +struct tls_config *tlsconf; +struct tls *ctx; +int sock; +struct evbuffer *buf; +struct evbuffer *dirbuf; +uint32_t msize; +int bell; + +volatile sig_atomic_t resized; +int tty_p; +int tty_width; + +struct np_stat { + uint16_t type; + uint32_t dev; + struct qid qid; + uint32_t mode; + uint32_t atime; + uint32_t mtime; + uint64_t length; + char *name; + char *uid; + char *gid; + char *muid; +}; + +struct progress { + uint64_t max; + uint64_t done; +}; + +int pwdfid; + +#define ASSERT_EMPTYBUF() assert(EVBUFFER_LENGTH(buf) == 0) + +#if HAVE_LIBREADLINE +static char * +read_line(const char *prompt) +{ + char *line; + +again: + if ((line = readline(prompt)) == NULL) + return NULL; + /* XXX: trim spaces? */ + if (*line == '\0') { + free(line); + goto again; + } + + add_history(line); + return line; +} +#else +static char * +read_line(const char *prompt) +{ + char *ch, *line = NULL; + size_t linesize = 0; + ssize_t linelen; + + printf("%s", prompt); + fflush(stdout); + + linelen = getline(&line, &linesize, stdin); + if (linelen == -1) + return NULL; + + if ((ch = strchr(line, '\n')) != NULL) + *ch = '\0'; + return line; +} +#endif + +static void +tty_resized(int signo) +{ + resized = 1; +} + +static void __dead +usage(int ret) +{ + fprintf(stderr, "usage: %s [-c] host[:port] [path]\n", + getprogname()); + fprintf(stderr, "kamid suite version " KAMID_VERSION "\n"); + exit(ret); +} + +static void +do_send(void) +{ + const void *buf; + size_t nbytes; + ssize_t r; + + while (EVBUFFER_LENGTH(evb) != 0) { + buf = EVBUFFER_DATA(evb); + nbytes = EVBUFFER_LENGTH(evb); + + if (ctx == NULL) { + r = write(sock, buf, nbytes); + if (r == 0 || r == -1) + errx(1, "EOF"); + } else { + r = tls_write(ctx, buf, nbytes); + if (r == TLS_WANT_POLLIN || r == TLS_WANT_POLLOUT) + continue; + if (r == -1) + errx(1, "tls: %s", tls_error(ctx)); + } + + evbuffer_drain(evb, r); + } +} + +static void +mustread(void *d, size_t len) +{ + ssize_t r; + + while (len != 0) { + if (ctx == NULL) { + r = read(sock, d, len); + if (r == 0 || r == -1) + errx(1, "EOF"); + } else { + r = tls_read(ctx, d, len); + if (r == TLS_WANT_POLLIN || r == TLS_WANT_POLLOUT) + continue; + if (r == -1) + errx(1, "tls: %s", tls_error(ctx)); + } + + d += r; + len -= r; + } +} + +static void +recv_msg(void) +{ + uint32_t len, l; + char tmp[BUFSIZ]; + + mustread(&len, sizeof(len)); + len = le32toh(len); + if (len < HEADERSIZE) + errx(1, "read message of invalid length %d", len); + + len -= 4; /* skip the length just read */ + + while (len != 0) { + l = MIN(len, sizeof(tmp)); + mustread(tmp, l); + len -= l; + evbuffer_add(buf, tmp, l); + } +} + +static uint64_t +np_read64(struct evbuffer *buf) +{ + uint64_t n; + + evbuffer_remove(buf, &n, sizeof(n)); + return le64toh(n); +} + +static uint32_t +np_read32(struct evbuffer *buf) +{ + uint32_t n; + + evbuffer_remove(buf, &n, sizeof(n)); + return le32toh(n); +} + +static uint16_t +np_read16(struct evbuffer *buf) +{ + uint16_t n; + + evbuffer_remove(buf, &n, sizeof(n)); + return le16toh(n); +} + +static uint16_t +np_read8(struct evbuffer *buf) +{ + uint8_t n; + + evbuffer_remove(buf, &n, sizeof(n)); + return n; +} + +static char * +np_readstr(struct evbuffer *buf) +{ + uint16_t len; + char *str; + + len = np_read16(buf); + assert(EVBUFFER_LENGTH(buf) >= len); + + if ((str = calloc(1, len+1)) == NULL) + err(1, "calloc"); + evbuffer_remove(buf, str, len); + return str; +} + +static void +np_read_qid(struct evbuffer *buf, struct qid *qid) +{ + assert(EVBUFFER_LENGTH(buf) >= QIDSIZE); + + qid->type = np_read8(buf); + qid->vers = np_read32(buf); + qid->path = np_read64(buf); +} + +static int +np_read_stat(struct evbuffer *buf, struct np_stat *st) +{ + uint16_t size; + + memset(st, 0, sizeof(*st)); + + size = np_read16(buf); + if (size > EVBUFFER_LENGTH(buf)) + return -1; + + st->type = np_read16(buf); + st->dev = np_read32(buf); + np_read_qid(buf, &st->qid); + st->mode = np_read32(buf); + st->atime = np_read32(buf); + st->mtime = np_read32(buf); + st->length = np_read64(buf); + st->name = np_readstr(buf); + st->uid = np_readstr(buf); + st->gid = np_readstr(buf); + st->muid = np_readstr(buf); + + return 0; +} + +static void +expect(uint8_t type) +{ + uint8_t t; + + t = np_read8(buf); + if (t == type) + return; + + if (t == Rerror) { + char *err; + + /* skip tag */ + np_read16(buf); + + err = np_readstr(buf); + errx(1, "expected %s, got error %s", + pp_msg_type(type), err); + } + + errx(1, "expected %s, got msg type %s", + pp_msg_type(type), pp_msg_type(t)); +} + +static void +expect2(uint8_t type, uint16_t tag) +{ + uint16_t t; + + expect(type); + + t = np_read16(buf); + if (t == tag) + return; + + errx(1, "expected tag 0x%x, got 0x%x", tag, t); +} + +static void +do_version(void) +{ + char *version; + + tversion(VERSION9P, MSIZE9P); + do_send(); + recv_msg(); + expect2(Rversion, NOTAG); + + msize = np_read32(buf); + version = np_readstr(buf); + + if (msize > MSIZE9P) + errx(1, "got unexpected msize: %d", msize); + if (strcmp(version, VERSION9P)) + errx(1, "unexpected 9p version: %s", version); + + free(version); + ASSERT_EMPTYBUF(); +} + +static void +do_attach(const char *path) +{ + const char *user; + struct qid qid; + + if (path == NULL) + path = "/"; + if ((user = getenv("USER")) == NULL) + user = "flan"; + + tattach(pwdfid, NOFID, user, path); + do_send(); + recv_msg(); + expect2(Rattach, iota_tag); + np_read_qid(buf, &qid); + + ASSERT_EMPTYBUF(); +} + +static uint32_t +do_open(uint32_t fid, uint8_t mode) +{ + struct qid qid; + uint32_t iounit; + + topen(fid, mode); + do_send(); + recv_msg(); + expect2(Ropen, iota_tag); + + np_read_qid(buf, &qid); + iounit = np_read32(buf); + + ASSERT_EMPTYBUF(); + + return iounit; +} + +static void +do_clunk(uint32_t fid) +{ + tclunk(fid); + do_send(); + recv_msg(); + expect2(Rclunk, iota_tag); + + ASSERT_EMPTYBUF(); +} + +static void +dup_fid(int fid, int nfid) +{ + uint16_t nwqid; + + twalk(fid, nfid, NULL, 0); + do_send(); + recv_msg(); + expect2(Rwalk, iota_tag); + + nwqid = np_read16(buf); + assert(nwqid == 0); + + ASSERT_EMPTYBUF(); +} + +static int +walk_path(int fid, int newfid, const char *path, struct qid *qid) +{ + char *wnames[MAXWELEM], *p, *t; + size_t nwname, i; + uint16_t nwqid; + + if ((p = strdup(path)) == NULL) + err(1, "strdup"); + t = p; + + /* strip initial ./ */ + if (t[0] == '.' && t[1] == '/') + t += 2; + + for (nwname = 0; nwname < nitems(wnames) && + (wnames[nwname] = strsep(&t, "/")) != NULL;) { + if (*wnames[nwname] != '\0') + nwname++; + } + + twalk(fid, newfid, (const char **)wnames, nwname); + do_send(); + recv_msg(); + expect2(Rwalk, iota_tag); + + nwqid = np_read16(buf); + assert(nwqid <= nwname); + + /* consume all qids */ + for (i = 0; i < nwname; ++i) + np_read_qid(buf, qid); + + free(p); + + return nwqid == nwname; +} + +static void +do_stat(int fid, struct np_stat *st) +{ + tstat(fid); + do_send(); + recv_msg(); + expect2(Rstat, iota_tag); + + if (np_read_stat(buf, st) == -1) + errx(1, "invalid stat struct read"); + + ASSERT_EMPTYBUF(); +} + +static size_t +do_read(int fid, uint64_t off, uint32_t count, void *data) +{ + uint32_t r; + + tread(fid, off, count); + do_send(); + recv_msg(); + expect2(Rread, iota_tag); + + r = np_read32(buf); + assert(r == EVBUFFER_LENGTH(buf)); + assert(r <= count); + evbuffer_remove(buf, data, r); + + ASSERT_EMPTYBUF(); + + return r; +} + +static void +draw_progress(const char *pre, const struct progress *p) +{ + struct winsize ws; + int i, l, w; + double perc; + + perc = 100.0 * p->done / p->max; + if (!tty_p) { + fprintf(stderr, "%s: %d%%\n", pre, (int)perc); + return; + } + + if (resized) { + resized = 0; + + if (ioctl(0, TIOCGWINSZ, &ws) == -1) + return; + tty_width = ws.ws_col; + } + w = tty_width; + + if (pre == NULL || + ((l = printf("\r%s ", pre)) == -1 || l >= w)) + return; + + w -= l + 2 + 5; /* 2 for |, 5 for percentage + \n */ + if (w < 0) { + printf("%4d%%\n", (int)perc); + return; + } + + printf("|"); + + l = w * MIN(100.0, perc) / 100.0; + for (i = 0; i < l; i++) + printf("*"); + for (; i < w; i++) + printf(" "); + printf("|%4d%%", (int)perc); + + fflush(stdout); +} + +static int +fetch_fid(int fid, const char *path) +{ + struct progress p = {0}; + struct np_stat st; + size_t r; + int fd; + char buf[BUFSIZ]; + + do_stat(fid, &st); + do_open(fid, KOREAD); + + if ((fd = open(path, O_WRONLY|O_CREAT|O_TRUNC, 0644)) == -1) { + warn("can't open %s", path); + return -1; + } + + p.max = st.length; + for (;;) { + size_t siz, off; + ssize_t nw; + + r = do_read(fid, p.done, sizeof(buf), buf); + if (r == 0) + break; + + siz = sizeof(buf); + for (off = 0; off < siz; off += nw) + if ((nw = write(fd, buf + off, siz - off)) == 0 || + nw == -1) + err(1, "write"); + + p.done += r; + draw_progress(path, &p); + +#if 0 + /* throttle, for debugging purpose */ + { + struct timespec ts = { 0, 500000000 }; + nanosleep(&ts, NULL); + } +#endif + } + + putchar('\n'); + + close(fd); + do_clunk(fid); + return 0; +} + +static void +do_tls_connect(const char *host, const char *port) +{ + int handshake; + + if ((tlsconf = tls_config_new()) == NULL) + fatalx("tls_config_new"); + tls_config_insecure_noverifycert(tlsconf); + tls_config_insecure_noverifyname(tlsconf); + if (tls_config_set_keypair_file(tlsconf, crtpath, keypath) == -1) + fatalx("can't load certs (%s, %s)", crtpath, keypath); + + if ((ctx = tls_client()) == NULL) + fatal("tls_client"); + if (tls_configure(ctx, tlsconf) == -1) + fatalx("tls_configure: %s", tls_error(ctx)); + + if (tls_connect(ctx, host, port) == -1) + fatalx("can't connect to %s:%s: %s", host, port, + tls_error(ctx)); + + for (handshake = 0; !handshake;) { + switch (tls_handshake(ctx)) { + case -1: + fatalx("tls_handshake: %s", tls_error(ctx)); + case 0: + handshake = 1; + break; + } + } +} + +static void +do_ctxt_connect(const char *host, const char *port) +{ + struct addrinfo hints, *res, *res0; + int error, saved_errno; + const char *cause = NULL; + + memset(&hints, 0, sizeof(hints)); + hints.ai_family = AF_UNSPEC; + hints.ai_socktype = SOCK_STREAM; + error = getaddrinfo(host, port, &hints, &res0); + if (error) + errx(1, "%s", gai_strerror(error)); + + sock = -1; + for (res = res0; res != NULL; res = res->ai_next) { + sock = socket(res->ai_family, res->ai_socktype, + res->ai_protocol); + if (sock == -1) { + cause = "socket"; + continue; + } + + if (connect(sock, res->ai_addr, res->ai_addrlen) == -1) { + cause = "connect"; + saved_errno = errno; + close(sock); + errno = saved_errno; + sock = -1; + continue; + } + + break; + } + + if (sock == -1) + err(1, "%s", cause); + freeaddrinfo(res0); +} + +static void +do_connect(const char *connspec, const char *path) +{ + char *host, *colon; + const char *port; + + host = xstrdup(connspec); + if ((colon = strchr(host, ':')) != NULL) { + *colon = '\0'; + port = ++colon; + } else + port = "1337"; + + printf("connecting to %s:%s...", host, port); + fflush(stdout); + + if (tls) + do_tls_connect(host, port); + else + do_ctxt_connect(host, port); + + printf(" done!\n"); + + do_version(); + do_attach(path); + + free(host); +} + +static void +cmd_bell(int argc, const char **argv) +{ + if (argc == 0) { + bell = !bell; + if (bell) + puts("bell mode enabled"); + else + puts("bell mode disabled"); + return; + } + + if (argc != 1) + goto usage; + + if (!strcmp(*argv, "on")) { + bell = 1; + puts("bell mode enabled"); + return; + } + + if (!strcmp(*argv, "off")) { + bell = 0; + puts("bell mode disabled"); + return; + } + +usage: + printf("bell [on | off]\n"); +} + +static void +cmd_bye(int argc, const char **argv) +{ + log_warnx("bye\n"); + exit(0); +} + +static void +cmd_cd(int argc, const char **argv) +{ + struct qid qid; + int nfid; + + if (argc != 1) { + printf("usage: cd remote-path\n"); + return; + } + + nfid = pwdfid+1; + if (walk_path(pwdfid, nfid, argv[0], &qid) == -1 || + !(qid.type & QTDIR)) { + printf("can't cd %s\n", argv[0]); + do_clunk(nfid); + } else { + do_clunk(pwdfid); + pwdfid = nfid; + } +} + +static void +cmd_get(int argc, const char **argv) +{ + struct qid qid; + const char *l; + int nfid; + + if (argc != 1 && argc != 2) { + printf("usage: get remote-file [local-file]\n"); + return; + } + + if (argc == 2) + l = argv[1]; + else if ((l = strrchr(argv[0], '/')) != NULL) + l++; /* skip / */ + else + l = argv[1]; + + nfid = pwdfid+1; + if (walk_path(pwdfid, nfid, argv[0], &qid) == -1) { + printf("can't fetch %s\n", argv[0]); + return; + } + + if (qid.type != 0) { + printf("can't fetch %s\n", argv[0]); + do_clunk(nfid); + return; + } + + fetch_fid(nfid, l); +} + +static void +cmd_lcd(int argc, const char **argv) +{ + const char *dir; + + if (argc > 1) { + printf("lcd takes only one argument\n"); + return; + } + + if (argc == 1) + dir = *argv; + + if (argc == 0 && (dir = getenv("HOME")) == NULL) { + printf("HOME is not defined\n"); + return; + } + + if (chdir(dir) == -1) + printf("cd: %s: %s\n", dir, strerror(errno)); +} + +static void +cmd_lpwd(int argc, const char **argv) +{ + char path[PATH_MAX]; + + if (getcwd(path, sizeof(path)) == NULL) { + printf("lpwd: %s\n", strerror(errno)); + return; + } + + printf("%s\n", path); +} + +static void +cmd_ls(int argc, const char **argv) +{ + struct np_stat st; + uint64_t off = 0; + uint32_t len; + char fmt[FMT_SCALED_STRSIZE]; + + if (argc != 0) { + printf("ls don't take arguments (yet)\n"); + return; + } + + dup_fid(pwdfid, 1); + do_open(1, KOREAD); + + evbuffer_drain(dirbuf, EVBUFFER_LENGTH(dirbuf)); + + for (;;) { + tread(1, off, BUFSIZ); + do_send(); + recv_msg(); + expect2(Rread, iota_tag); + + len = np_read32(buf); + if (len == 0) + break; + + evbuffer_add_buffer(dirbuf, buf); + off += len; + + ASSERT_EMPTYBUF(); + } + + while (EVBUFFER_LENGTH(dirbuf) != 0) { + if (np_read_stat(dirbuf, &st) == -1) + errx(1, "invalid stat struct read"); + + if (fmt_scaled(st.length, fmt) == -1) + strlcpy(fmt, "xxx", sizeof(fmt)); + + printf("%4s %8s %s\n", pp_qid_type(st.qid.type), fmt, st.name); + + free(st.name); + free(st.uid); + free(st.gid); + free(st.muid); + } + + do_clunk(1); +} + +static void +cmd_verbose(int argc, const char **argv) +{ + if (argc == 0) { + log_setverbose(!log_getverbose()); + if (log_getverbose()) + puts("verbose mode enabled"); + else + puts("verbose mode disabled"); + return; + } + + if (argc != 1) + goto usage; + + if (!strcmp(*argv, "on")) { + log_setverbose(1); + puts("verbose mode enabled"); + return; + } + + if (!strcmp(*argv, "off")) { + log_setverbose(0); + puts("verbose mode disabled"); + return; + } + +usage: + printf("verbose [on | off]\n"); +} + +static void +excmd(int argc, const char **argv) +{ + struct cmd { + const char *name; + void (*fn)(int, const char **); + } cmds[] = { + {"bell", cmd_bell}, + {"bye", cmd_bye}, + {"cd", cmd_cd}, + {"get", cmd_get}, + {"lcd", cmd_lcd}, + {"lpwd", cmd_lpwd}, + {"ls", cmd_ls}, + {"quit", cmd_bye}, + {"verbose", cmd_verbose}, + }; + size_t i; + + if (argc == 0) + return; + for (i = 0; i < nitems(cmds); ++i) { + if (!strcmp(cmds[i].name, *argv)) { + cmds[i].fn(argc-1, argv+1); + return; + } + } + + log_warnx("unknown command %s", *argv); +} + +int +main(int argc, char **argv) +{ + int ch; + + log_init(1, LOG_DAEMON); + log_setverbose(0); + log_procinit(getprogname()); + + while ((ch = getopt(argc, argv, "C:cK:")) != -1) { + switch (ch) { + case 'C': + crtpath = optarg; + break; + case 'c': + tls = 1; + break; + case 'K': + keypath = optarg; + break; + default: + usage(1); + } + } + argc -= optind; + argv += optind; + + if (argc == 0) + usage(1); + + if (isatty(1)) { + tty_p = 1; + resized = 1; + signal(SIGWINCH, tty_resized); + } + + if ((evb = evbuffer_new()) == NULL) + fatal("evbuffer_new"); + + if ((buf = evbuffer_new()) == NULL) + fatal("evbuffer_new"); + + if ((dirbuf = evbuffer_new()) == NULL) + fatal("evbuferr_new"); + + do_connect(argv[0], argv[1]); + + for (;;) { + int argc = 0; + char *line, *argv[16] = {0}, **ap; + + if ((line = read_line("kamiftp> ")) == NULL) + break; + + for (argc = 0, ap = argv; ap < &argv[15] && + (*ap = strsep(&line, " \t")) != NULL;) { + if (**ap != '\0') + ap++, argc++; + } + excmd(argc, (const char **)argv); + + if (bell) { + printf("\a"); + fflush(stdout); + } + + free(line); + } + + printf("\n"); +} blob - /dev/null blob + bbb82ddbd18431c0c4cc0cc05d9e9273e5bf4d26 (mode 644) --- /dev/null +++ kamiftp/kamiftp.1 @@ -0,0 +1,80 @@ +.\" Copyright (c) 2021 Omar Polo +.\" +.\" Permission to use, copy, modify, and distribute this software for any +.\" purpose with or without fee is hereby granted, provided that the above +.\" copyright notice and this permission notice appear in all copies. +.\" +.\" THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +.\" WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +.\" MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +.\" ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +.\" WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +.\" ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +.\" OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +.\" +.Dd $Mdocdate: December 14 2021 $ +.Dt KAMIFTP 1 +.Os +.Sh NAME +.Nm kamiftp +.Nd 9p client +.Sh SYNOPSIS +.Nm +.Op Fl c +.Op Fl C Ar cert +.Op Fl K Ar key +.Ar host Op Ar path +.Sh DESCRIPTION +.Nm +is a +.Xr 9p 7 +client. +.Pp +The options are as follows: +.Bl -tag -width Ds +.It Fl c +Use TLS for the connection. +.Fl K +and +.Fl C +are mandatory if +.Fl c +is used. +.It Fl C Ar certificate +Specify the path to the client +.Ar certificate +to be use during the TLS handsahke. +.It Fl K Ar key +Specify the path to the client certificate +.Ar key +to be used during the TLS handshake. +.El +.Pp +The following commands are recognized by +.Nm : +.Bl -tag -width Ds +.It Ic bell Oo Cm on | off Oc +Request terminal to sound a bell after each command. +Without arguments toggle the current state. +.It Ic bye +Terminate the session. +Synomym of +.Ic quit . +.It Ic ls +List the file in the current directory +.It Ic quit +Terminate the session. +Synomym of +.Ic bye . +.It Ic verbose Oo Cm on | off Oc +Print verbose information. +Without arguments toggle the current state. +.El +.Sh SEE ALSO +.Xr 9p 7 , +.Xr kamid 8 +.Sh AUTHORS +The +.Nm +utility was written by +.An Omar Polo Aq Mt op@omarpolo.com . blob - 70d2ea27dc6aebc565bc7ae98b4afe074d88045f (mode 644) blob + /dev/null --- compat/asprintf.c +++ /dev/null @@ -1,64 +0,0 @@ -/* - * Copyright (c) 2006 Nicholas Marriott - * - * Permission to use, copy, modify, and distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. - * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER - * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING - * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - */ - -#include - -#include -#include -#include -#include - -#include "compat.h" - -int -asprintf(char **ret, const char *fmt, ...) -{ - va_list ap; - int n; - - va_start(ap, fmt); - n = vasprintf(ret, fmt, ap); - va_end(ap); - - return (n); -} - -int -vasprintf(char **ret, const char *fmt, va_list ap) -{ - int n; - va_list ap2; - - va_copy(ap2, ap); - - if ((n = vsnprintf(NULL, 0, fmt, ap)) < 0) - goto error; - - if ((*ret = malloc(n + 1)) == NULL) - goto error; - if ((n = vsnprintf(*ret, n + 1, fmt, ap2)) < 0) { - free(*ret); - goto error; - } - va_end(ap2); - - return (n); - -error: - va_end(ap2); - *ret = NULL; - return (-1); -} blob - 244909c09b1829522fc3429f5e207de7edd34bc2 (mode 644) blob + /dev/null --- compat/err.c +++ /dev/null @@ -1,84 +0,0 @@ -/* - * Copyright (c) 2021 Omar Polo - * - * Permission to use, copy, modify, and distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. - * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - */ - -#include -#include -#include -#include -#include - -#include "compat.h" - -static void vwarn(const char*, va_list); -static void vwarnx(const char*, va_list); - -static void -vwarn(const char *fmt, va_list ap) -{ - fprintf(stderr, "%s: ", getprogname()); - vfprintf(stderr, fmt, ap); - fprintf(stderr, ": %s\n", strerror(errno)); -} - -static void -vwarnx(const char *fmt, va_list ap) -{ - fprintf(stderr, "%s: ", getprogname()); - vfprintf(stderr, fmt, ap); - fprintf(stderr, "\n"); -} - -void -err(int ret, const char *fmt, ...) -{ - va_list ap; - - va_start(ap, fmt); - vwarn(fmt, ap); - va_end(ap); - exit(ret); -} - -void -errx(int ret, const char *fmt, ...) -{ - va_list ap; - - va_start(ap, fmt); - vwarnx(fmt, ap); - va_end(ap); - exit(ret); -} - -void -warn(const char *fmt, ...) -{ - va_list ap; - - va_start(ap, fmt); - vwarn(fmt, ap); - va_end(ap); -} - -void -warnx(const char *fmt, ...) -{ - va_list ap; - - va_start(ap, fmt); - vwarnx(fmt, ap); - va_end(ap); -} blob - 1b2d5f59da6911eb007cd5a1fae0d58fc2fce889 (mode 644) blob + /dev/null --- compat/fmt_scaled.c +++ /dev/null @@ -1,159 +0,0 @@ -/* $OpenBSD: fmt_scaled.c,v 1.19 2020/10/12 22:08:34 deraadt Exp $ */ - -/* - * Copyright (c) 2001, 2002, 2003 Ian F. Darwin. All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * 1. Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * 3. The name of the author may not be used to endorse or promote products - * derived from this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR - * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES - * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. - * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, - * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT - * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, - * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY - * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF - * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -/* - * fmt_scaled: Format numbers scaled for human comprehension - * scan_scaled: Scan numbers in this format. - * - * "Human-readable" output uses 4 digits max, and puts a unit suffix at - * the end. Makes output compact and easy-to-read esp. on huge disks. - * Formatting code was originally in OpenBSD "df", converted to library routine. - * Scanning code written for OpenBSD libutil. - */ - -#include "compat.h" - -#include -#include -#include -#include -#include -#include - -typedef enum { - NONE = 0, KILO = 1, MEGA = 2, GIGA = 3, TERA = 4, PETA = 5, EXA = 6 -} unit_type; - -/* These three arrays MUST be in sync! XXX make a struct */ -static const unit_type units[] = { NONE, KILO, MEGA, GIGA, TERA, PETA, EXA }; -static const char scale_chars[] = "BKMGTPE"; -static const long long scale_factors[] = { - 1LL, - 1024LL, - 1024LL*1024, - 1024LL*1024*1024, - 1024LL*1024*1024*1024, - 1024LL*1024*1024*1024*1024, - 1024LL*1024*1024*1024*1024*1024, -}; -#define SCALE_LENGTH (sizeof(units)/sizeof(units[0])) - -#define MAX_DIGITS (SCALE_LENGTH * 3) /* XXX strlen(sprintf("%lld", -1)? */ - -/* Format the given "number" into human-readable form in "result". - * Result must point to an allocated buffer of length FMT_SCALED_STRSIZE. - * Return 0 on success, -1 and errno set if error. - */ -int -fmt_scaled(long long number, char *result) -{ - long long abval, fract = 0; - unsigned int i; - unit_type unit = NONE; - - /* Not every negative long long has a positive representation. */ - if (number == LLONG_MIN) { - errno = ERANGE; - return -1; - } - - abval = llabs(number); - - /* Also check for numbers that are just too darned big to format. */ - if (abval / 1024 >= scale_factors[SCALE_LENGTH-1]) { - errno = ERANGE; - return -1; - } - - /* scale whole part; get unscaled fraction */ - for (i = 0; i < SCALE_LENGTH; i++) { - if (abval/1024 < scale_factors[i]) { - unit = units[i]; - fract = (i == 0) ? 0 : abval % scale_factors[i]; - number /= scale_factors[i]; - if (i > 0) - fract /= scale_factors[i - 1]; - break; - } - } - - fract = (10 * fract + 512) / 1024; - /* if the result would be >= 10, round main number */ - if (fract >= 10) { - if (number >= 0) - number++; - else - number--; - fract = 0; - } else if (fract < 0) { - /* shouldn't happen */ - fract = 0; - } - - if (number == 0) - strlcpy(result, "0B", FMT_SCALED_STRSIZE); - else if (unit == NONE || number >= 100 || number <= -100) { - if (fract >= 5) { - if (number >= 0) - number++; - else - number--; - } - (void)snprintf(result, FMT_SCALED_STRSIZE, "%lld%c", - number, scale_chars[unit]); - } else - (void)snprintf(result, FMT_SCALED_STRSIZE, "%lld.%1lld%c", - number, fract, scale_chars[unit]); - - return 0; -} - -#ifdef MAIN -/* - * This is the original version of the program in the man page. - * Copy-and-paste whatever you need from it. - */ -int -main(int argc, char **argv) -{ - char *cinput = "1.5K", buf[FMT_SCALED_STRSIZE]; - long long ninput = 10483892, result; - - if (scan_scaled(cinput, &result) == 0) - printf("\"%s\" -> %lld\n", cinput, result); - else - perror(cinput); - - if (fmt_scaled(ninput, buf) == 0) - printf("%lld -> \"%s\"\n", ninput, buf); - else - fprintf(stderr, "%lld invalid (%s)\n", ninput, strerror(errno)); - - return 0; -} -#endif blob - 9929f01e8cc1fccffb64b4699ec6fdf5ad566446 (mode 644) blob + /dev/null --- compat/freezero.c +++ /dev/null @@ -1,30 +0,0 @@ -/* - * Copyright (c) 2021 Omar Polo - * - * Permission to use, copy, modify, and distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. - * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - */ - -#include "../compat.h" - -#include -#include - -void -freezero(void *ptr, size_t len) -{ - if (ptr == NULL) - return; - - memset(ptr, 0, len); - free(ptr); -} blob - 1d75ae2114b24059d3b4f0fe18e8d925db430b5d (mode 644) blob + /dev/null --- compat/getdtablecount.c +++ /dev/null @@ -1,26 +0,0 @@ -/* - * Copyright (c) 2021 Omar Polo - * - * Permission to use, copy, modify, and distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. - * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - */ - -/* XXX: on linux it's possible to glob("/proc/$pid/fd/ *") to know the - * dtablecount. */ - -#include "compat.h" - -int -getdtablecount(void) -{ - return 0; -} blob - a9a8e9d52cba2c1315734677e41e7c83d6399f4b (mode 644) blob + /dev/null --- compat/getdtablesize.c +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright (c) 2021 Omar Polo - * - * Permission to use, copy, modify, and distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. - * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - */ - -#include "compat.h" - -#include - -int -getdtablesize(void) -{ - return sysconf(_SC_OPEN_MAX); -} blob - 1b50f0c49503e60bd7ce9207d9cc07e7686673c2 (mode 644) blob + /dev/null --- compat/getprogname.c +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Copyright (c) 2021 Omar Polo - * - * Permission to use, copy, modify, and distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. - * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - */ - -#include "compat.h" - -#ifdef HAVE_PROGRAM_INVOCATION_SHORT_NAME - -#include - -extern char *program_invocation_short_name; - -const char * -getprogname(void) -{ - return program_invocation_short_name; -} - -#else - -const char * -getprogname(void) -{ - return "kamid"; -} - -#endif blob - 7bd827d6354db030b1fbffc9a365200e017a557e (mode 644) blob + /dev/null --- compat/imsg-buffer.c +++ /dev/null @@ -1,310 +0,0 @@ -/* $OpenBSD: imsg-buffer.c,v 1.12 2019/01/20 02:50:03 bcook Exp $ */ - -/* - * Copyright (c) 2003, 2004 Henning Brauer - * - * Permission to use, copy, modify, and distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. - * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - */ - -#include "compat.h" - -#include -#include -#include - -#include -#include -#include -#include -#include - -#include "imsg.h" - -static int ibuf_realloc(struct ibuf *, size_t); -static void ibuf_enqueue(struct msgbuf *, struct ibuf *); -static void ibuf_dequeue(struct msgbuf *, struct ibuf *); - -struct ibuf * -ibuf_open(size_t len) -{ - struct ibuf *buf; - - if ((buf = calloc(1, sizeof(struct ibuf))) == NULL) - return (NULL); - if ((buf->buf = malloc(len)) == NULL) { - free(buf); - return (NULL); - } - buf->size = buf->max = len; - buf->fd = -1; - - return (buf); -} - -struct ibuf * -ibuf_dynamic(size_t len, size_t max) -{ - struct ibuf *buf; - - if (max < len) - return (NULL); - - if ((buf = ibuf_open(len)) == NULL) - return (NULL); - - if (max > 0) - buf->max = max; - - return (buf); -} - -static int -ibuf_realloc(struct ibuf *buf, size_t len) -{ - unsigned char *b; - - /* on static buffers max is eq size and so the following fails */ - if (buf->wpos + len > buf->max) { - errno = ERANGE; - return (-1); - } - - b = recallocarray(buf->buf, buf->size, buf->wpos + len, 1); - if (b == NULL) - return (-1); - buf->buf = b; - buf->size = buf->wpos + len; - - return (0); -} - -int -ibuf_add(struct ibuf *buf, const void *data, size_t len) -{ - if (buf->wpos + len > buf->size) - if (ibuf_realloc(buf, len) == -1) - return (-1); - - memcpy(buf->buf + buf->wpos, data, len); - buf->wpos += len; - return (0); -} - -void * -ibuf_reserve(struct ibuf *buf, size_t len) -{ - void *b; - - if (buf->wpos + len > buf->size) - if (ibuf_realloc(buf, len) == -1) - return (NULL); - - b = buf->buf + buf->wpos; - buf->wpos += len; - return (b); -} - -void * -ibuf_seek(struct ibuf *buf, size_t pos, size_t len) -{ - /* only allowed to seek in already written parts */ - if (pos + len > buf->wpos) - return (NULL); - - return (buf->buf + pos); -} - -size_t -ibuf_size(struct ibuf *buf) -{ - return (buf->wpos); -} - -size_t -ibuf_left(struct ibuf *buf) -{ - return (buf->max - buf->wpos); -} - -void -ibuf_close(struct msgbuf *msgbuf, struct ibuf *buf) -{ - ibuf_enqueue(msgbuf, buf); -} - -int -ibuf_write(struct msgbuf *msgbuf) -{ - struct iovec iov[IOV_MAX]; - struct ibuf *buf; - unsigned int i = 0; - ssize_t n; - - memset(&iov, 0, sizeof(iov)); - TAILQ_FOREACH(buf, &msgbuf->bufs, entry) { - if (i >= IOV_MAX) - break; - iov[i].iov_base = buf->buf + buf->rpos; - iov[i].iov_len = buf->wpos - buf->rpos; - i++; - } - -again: - if ((n = writev(msgbuf->fd, iov, i)) == -1) { - if (errno == EINTR) - goto again; - if (errno == ENOBUFS) - errno = EAGAIN; - return (-1); - } - - if (n == 0) { /* connection closed */ - errno = 0; - return (0); - } - - msgbuf_drain(msgbuf, n); - - return (1); -} - -void -ibuf_free(struct ibuf *buf) -{ - if (buf == NULL) - return; - freezero(buf->buf, buf->size); - free(buf); -} - -void -msgbuf_init(struct msgbuf *msgbuf) -{ - msgbuf->queued = 0; - msgbuf->fd = -1; - TAILQ_INIT(&msgbuf->bufs); -} - -void -msgbuf_drain(struct msgbuf *msgbuf, size_t n) -{ - struct ibuf *buf, *next; - - for (buf = TAILQ_FIRST(&msgbuf->bufs); buf != NULL && n > 0; - buf = next) { - next = TAILQ_NEXT(buf, entry); - if (buf->rpos + n >= buf->wpos) { - n -= buf->wpos - buf->rpos; - ibuf_dequeue(msgbuf, buf); - } else { - buf->rpos += n; - n = 0; - } - } -} - -void -msgbuf_clear(struct msgbuf *msgbuf) -{ - struct ibuf *buf; - - while ((buf = TAILQ_FIRST(&msgbuf->bufs)) != NULL) - ibuf_dequeue(msgbuf, buf); -} - -int -msgbuf_write(struct msgbuf *msgbuf) -{ - struct iovec iov[IOV_MAX]; - struct ibuf *buf; - unsigned int i = 0; - ssize_t n; - struct msghdr msg; - struct cmsghdr *cmsg; - union { - struct cmsghdr hdr; - char buf[CMSG_SPACE(sizeof(int))]; - } cmsgbuf; - - memset(&iov, 0, sizeof(iov)); - memset(&msg, 0, sizeof(msg)); - memset(&cmsgbuf, 0, sizeof(cmsgbuf)); - TAILQ_FOREACH(buf, &msgbuf->bufs, entry) { - if (i >= IOV_MAX) - break; - iov[i].iov_base = buf->buf + buf->rpos; - iov[i].iov_len = buf->wpos - buf->rpos; - i++; - if (buf->fd != -1) - break; - } - - msg.msg_iov = iov; - msg.msg_iovlen = i; - - if (buf != NULL && buf->fd != -1) { - msg.msg_control = (caddr_t)&cmsgbuf.buf; - msg.msg_controllen = sizeof(cmsgbuf.buf); - cmsg = CMSG_FIRSTHDR(&msg); - cmsg->cmsg_len = CMSG_LEN(sizeof(int)); - cmsg->cmsg_level = SOL_SOCKET; - cmsg->cmsg_type = SCM_RIGHTS; - *(int *)CMSG_DATA(cmsg) = buf->fd; - } - -again: - if ((n = sendmsg(msgbuf->fd, &msg, 0)) == -1) { - if (errno == EINTR) - goto again; - if (errno == ENOBUFS) - errno = EAGAIN; - return (-1); - } - - if (n == 0) { /* connection closed */ - errno = 0; - return (0); - } - - /* - * assumption: fd got sent if sendmsg sent anything - * this works because fds are passed one at a time - */ - if (buf != NULL && buf->fd != -1) { - close(buf->fd); - buf->fd = -1; - } - - msgbuf_drain(msgbuf, n); - - return (1); -} - -static void -ibuf_enqueue(struct msgbuf *msgbuf, struct ibuf *buf) -{ - TAILQ_INSERT_TAIL(&msgbuf->bufs, buf, entry); - msgbuf->queued++; -} - -static void -ibuf_dequeue(struct msgbuf *msgbuf, struct ibuf *buf) -{ - TAILQ_REMOVE(&msgbuf->bufs, buf, entry); - - if (buf->fd != -1) - close(buf->fd); - - msgbuf->queued--; - ibuf_free(buf); -} blob - 6c746751450bd523a4fb6736a4347900fd01d5bb (mode 644) blob + /dev/null --- compat/imsg.c +++ /dev/null @@ -1,303 +0,0 @@ -/* $OpenBSD: imsg.c,v 1.16 2017/12/14 09:27:44 kettenis Exp $ */ - -/* - * Copyright (c) 2003, 2004 Henning Brauer - * - * Permission to use, copy, modify, and distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. - * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - */ - -#include "compat.h" - -#include -#include -#include - -#include -#include -#include -#include - -#include "imsg.h" - -int imsg_fd_overhead = 0; - -static int imsg_get_fd(struct imsgbuf *); - -void -imsg_init(struct imsgbuf *ibuf, int fd) -{ - msgbuf_init(&ibuf->w); - memset(&ibuf->r, 0, sizeof(ibuf->r)); - ibuf->fd = fd; - ibuf->w.fd = fd; - ibuf->pid = getpid(); - TAILQ_INIT(&ibuf->fds); -} - -ssize_t -imsg_read(struct imsgbuf *ibuf) -{ - struct msghdr msg; - struct cmsghdr *cmsg; - union { - struct cmsghdr hdr; - char buf[CMSG_SPACE(sizeof(int) * 1)]; - } cmsgbuf; - struct iovec iov; - ssize_t n = -1; - int fd; - struct imsg_fd *ifd; - - memset(&msg, 0, sizeof(msg)); - memset(&cmsgbuf, 0, sizeof(cmsgbuf)); - - iov.iov_base = ibuf->r.buf + ibuf->r.wpos; - iov.iov_len = sizeof(ibuf->r.buf) - ibuf->r.wpos; - msg.msg_iov = &iov; - msg.msg_iovlen = 1; - msg.msg_control = &cmsgbuf.buf; - msg.msg_controllen = sizeof(cmsgbuf.buf); - - if ((ifd = calloc(1, sizeof(struct imsg_fd))) == NULL) - return (-1); - -again: - if (getdtablecount() + imsg_fd_overhead + - (int)((CMSG_SPACE(sizeof(int))-CMSG_SPACE(0))/sizeof(int)) - >= getdtablesize()) { - errno = EAGAIN; - free(ifd); - return (-1); - } - - if ((n = recvmsg(ibuf->fd, &msg, 0)) == -1) { - if (errno == EINTR) - goto again; - goto fail; - } - - ibuf->r.wpos += n; - - for (cmsg = CMSG_FIRSTHDR(&msg); cmsg != NULL; - cmsg = CMSG_NXTHDR(&msg, cmsg)) { - if (cmsg->cmsg_level == SOL_SOCKET && - cmsg->cmsg_type == SCM_RIGHTS) { - int i; - int j; - - /* - * We only accept one file descriptor. Due to C - * padding rules, our control buffer might contain - * more than one fd, and we must close them. - */ - j = ((char *)cmsg + cmsg->cmsg_len - - (char *)CMSG_DATA(cmsg)) / sizeof(int); - for (i = 0; i < j; i++) { - fd = ((int *)CMSG_DATA(cmsg))[i]; - if (ifd != NULL) { - ifd->fd = fd; - TAILQ_INSERT_TAIL(&ibuf->fds, ifd, - entry); - ifd = NULL; - } else - close(fd); - } - } - /* we do not handle other ctl data level */ - } - -fail: - free(ifd); - return (n); -} - -ssize_t -imsg_get(struct imsgbuf *ibuf, struct imsg *imsg) -{ - size_t av, left, datalen; - - av = ibuf->r.wpos; - - if (IMSG_HEADER_SIZE > av) - return (0); - - memcpy(&imsg->hdr, ibuf->r.buf, sizeof(imsg->hdr)); - if (imsg->hdr.len < IMSG_HEADER_SIZE || - imsg->hdr.len > MAX_IMSGSIZE) { - errno = ERANGE; - return (-1); - } - if (imsg->hdr.len > av) - return (0); - datalen = imsg->hdr.len - IMSG_HEADER_SIZE; - ibuf->r.rptr = ibuf->r.buf + IMSG_HEADER_SIZE; - if (datalen == 0) - imsg->data = NULL; - else if ((imsg->data = malloc(datalen)) == NULL) - return (-1); - - if (imsg->hdr.flags & IMSGF_HASFD) - imsg->fd = imsg_get_fd(ibuf); - else - imsg->fd = -1; - - memcpy(imsg->data, ibuf->r.rptr, datalen); - - if (imsg->hdr.len < av) { - left = av - imsg->hdr.len; - memmove(&ibuf->r.buf, ibuf->r.buf + imsg->hdr.len, left); - ibuf->r.wpos = left; - } else - ibuf->r.wpos = 0; - - return (datalen + IMSG_HEADER_SIZE); -} - -int -imsg_compose(struct imsgbuf *ibuf, uint32_t type, uint32_t peerid, pid_t pid, - int fd, const void *data, uint16_t datalen) -{ - struct ibuf *wbuf; - - if ((wbuf = imsg_create(ibuf, type, peerid, pid, datalen)) == NULL) - return (-1); - - if (imsg_add(wbuf, data, datalen) == -1) - return (-1); - - wbuf->fd = fd; - - imsg_close(ibuf, wbuf); - - return (1); -} - -int -imsg_composev(struct imsgbuf *ibuf, uint32_t type, uint32_t peerid, pid_t pid, - int fd, const struct iovec *iov, int iovcnt) -{ - struct ibuf *wbuf; - int i, datalen = 0; - - for (i = 0; i < iovcnt; i++) - datalen += iov[i].iov_len; - - if ((wbuf = imsg_create(ibuf, type, peerid, pid, datalen)) == NULL) - return (-1); - - for (i = 0; i < iovcnt; i++) - if (imsg_add(wbuf, iov[i].iov_base, iov[i].iov_len) == -1) - return (-1); - - wbuf->fd = fd; - - imsg_close(ibuf, wbuf); - - return (1); -} - -/* ARGSUSED */ -struct ibuf * -imsg_create(struct imsgbuf *ibuf, uint32_t type, uint32_t peerid, pid_t pid, - uint16_t datalen) -{ - struct ibuf *wbuf; - struct imsg_hdr hdr; - - datalen += IMSG_HEADER_SIZE; - if (datalen > MAX_IMSGSIZE) { - errno = ERANGE; - return (NULL); - } - - hdr.type = type; - hdr.flags = 0; - hdr.peerid = peerid; - if ((hdr.pid = pid) == 0) - hdr.pid = ibuf->pid; - if ((wbuf = ibuf_dynamic(datalen, MAX_IMSGSIZE)) == NULL) { - return (NULL); - } - if (imsg_add(wbuf, &hdr, sizeof(hdr)) == -1) - return (NULL); - - return (wbuf); -} - -int -imsg_add(struct ibuf *msg, const void *data, uint16_t datalen) -{ - if (datalen) - if (ibuf_add(msg, data, datalen) == -1) { - ibuf_free(msg); - return (-1); - } - return (datalen); -} - -void -imsg_close(struct imsgbuf *ibuf, struct ibuf *msg) -{ - struct imsg_hdr *hdr; - - hdr = (struct imsg_hdr *)msg->buf; - - hdr->flags &= ~IMSGF_HASFD; - if (msg->fd != -1) - hdr->flags |= IMSGF_HASFD; - - hdr->len = (uint16_t)msg->wpos; - - ibuf_close(&ibuf->w, msg); -} - -void -imsg_free(struct imsg *imsg) -{ - freezero(imsg->data, imsg->hdr.len - IMSG_HEADER_SIZE); -} - -static int -imsg_get_fd(struct imsgbuf *ibuf) -{ - int fd; - struct imsg_fd *ifd; - - if ((ifd = TAILQ_FIRST(&ibuf->fds)) == NULL) - return (-1); - - fd = ifd->fd; - TAILQ_REMOVE(&ibuf->fds, ifd, entry); - free(ifd); - - return (fd); -} - -int -imsg_flush(struct imsgbuf *ibuf) -{ - while (ibuf->w.queued) - if (msgbuf_write(&ibuf->w) <= 0) - return (-1); - return (0); -} - -void -imsg_clear(struct imsgbuf *ibuf) -{ - int fd; - - msgbuf_clear(&ibuf->w); - while ((fd = imsg_get_fd(ibuf)) != -1) - close(fd); -} blob - 5b092cfcfd53b5b09a2d5932010f421d6795e0aa (mode 644) blob + /dev/null --- compat/imsg.h +++ /dev/null @@ -1,113 +0,0 @@ -/* $OpenBSD: imsg.h,v 1.5 2019/01/20 02:50:03 bcook Exp $ */ - -/* - * Copyright (c) 2006, 2007 Pierre-Yves Ritschard - * Copyright (c) 2006, 2007, 2008 Reyk Floeter - * Copyright (c) 2003, 2004 Henning Brauer - * - * Permission to use, copy, modify, and distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. - * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - */ - -#ifndef _IMSG_H_ -#define _IMSG_H_ - -#include - -#define IBUF_READ_SIZE 65535 -#define IMSG_HEADER_SIZE sizeof(struct imsg_hdr) -#define MAX_IMSGSIZE 16384 - -struct ibuf { - TAILQ_ENTRY(ibuf) entry; - unsigned char *buf; - size_t size; - size_t max; - size_t wpos; - size_t rpos; - int fd; -}; - -struct msgbuf { - TAILQ_HEAD(, ibuf) bufs; - uint32_t queued; - int fd; -}; - -struct ibuf_read { - unsigned char buf[IBUF_READ_SIZE]; - unsigned char *rptr; - size_t wpos; -}; - -struct imsg_fd { - TAILQ_ENTRY(imsg_fd) entry; - int fd; -}; - -struct imsgbuf { - TAILQ_HEAD(, imsg_fd) fds; - struct ibuf_read r; - struct msgbuf w; - int fd; - pid_t pid; -}; - -#define IMSGF_HASFD 1 - -struct imsg_hdr { - uint32_t type; - uint16_t len; - uint16_t flags; - uint32_t peerid; - uint32_t pid; -}; - -struct imsg { - struct imsg_hdr hdr; - int fd; - void *data; -}; - - -/* buffer.c */ -struct ibuf *ibuf_open(size_t); -struct ibuf *ibuf_dynamic(size_t, size_t); -int ibuf_add(struct ibuf *, const void *, size_t); -void *ibuf_reserve(struct ibuf *, size_t); -void *ibuf_seek(struct ibuf *, size_t, size_t); -size_t ibuf_size(struct ibuf *); -size_t ibuf_left(struct ibuf *); -void ibuf_close(struct msgbuf *, struct ibuf *); -int ibuf_write(struct msgbuf *); -void ibuf_free(struct ibuf *); -void msgbuf_init(struct msgbuf *); -void msgbuf_clear(struct msgbuf *); -int msgbuf_write(struct msgbuf *); -void msgbuf_drain(struct msgbuf *, size_t); - -/* imsg.c */ -void imsg_init(struct imsgbuf *, int); -ssize_t imsg_read(struct imsgbuf *); -ssize_t imsg_get(struct imsgbuf *, struct imsg *); -int imsg_compose(struct imsgbuf *, uint32_t, uint32_t, pid_t, int, - const void *, uint16_t); -int imsg_composev(struct imsgbuf *, uint32_t, uint32_t, pid_t, int, - const struct iovec *, int); -struct ibuf *imsg_create(struct imsgbuf *, uint32_t, uint32_t, pid_t, uint16_t); -int imsg_add(struct ibuf *, const void *, uint16_t); -void imsg_close(struct imsgbuf *, struct ibuf *); -void imsg_free(struct imsg *); -int imsg_flush(struct imsgbuf *); -void imsg_clear(struct imsgbuf *); - -#endif blob - 9b11960def28ef34941658a0091836c135a3d8b4 (mode 644) blob + /dev/null --- compat/memmem.c +++ /dev/null @@ -1,183 +0,0 @@ -/* $OpenBSD: memmem.c,v 1.5 2020/04/16 12:39:28 claudio Exp $ */ - -/* - * Copyright (c) 2005-2020 Rich Felker, et al. - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files (the - * "Software"), to deal in the Software without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Software, and to - * permit persons to whom the Software is furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice shall be - * included in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. - * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY - * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, - * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE - * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ - -#include -#include - -static char * -twobyte_memmem(const unsigned char *h, size_t k, const unsigned char *n) -{ - uint16_t nw = n[0]<<8 | n[1], hw = h[0]<<8 | h[1]; - for (h+=2, k-=2; k; k--, hw = hw<<8 | *h++) - if (hw == nw) return (char *)h-2; - return hw == nw ? (char *)h-2 : 0; -} - -static char * -threebyte_memmem(const unsigned char *h, size_t k, const unsigned char *n) -{ - uint32_t nw = n[0]<<24 | n[1]<<16 | n[2]<<8; - uint32_t hw = h[0]<<24 | h[1]<<16 | h[2]<<8; - for (h+=3, k-=3; k; k--, hw = (hw|*h++)<<8) - if (hw == nw) return (char *)h-3; - return hw == nw ? (char *)h-3 : 0; -} - -static char * -fourbyte_memmem(const unsigned char *h, size_t k, const unsigned char *n) -{ - uint32_t nw = n[0]<<24 | n[1]<<16 | n[2]<<8 | n[3]; - uint32_t hw = h[0]<<24 | h[1]<<16 | h[2]<<8 | h[3]; - for (h+=4, k-=4; k; k--, hw = hw<<8 | *h++) - if (hw == nw) return (char *)h-4; - return hw == nw ? (char *)h-4 : 0; -} - -#define MAX(a,b) ((a)>(b)?(a):(b)) -#define MIN(a,b) ((a)<(b)?(a):(b)) - -#define BITOP(a,b,op) \ - ((a)[(size_t)(b)/(8*sizeof *(a))] op (size_t)1<<((size_t)(b)%(8*sizeof *(a)))) - -/* - * Maxime Crochemore and Dominique Perrin, Two-way string-matching, - * Journal of the ACM, 38(3):651-675, July 1991. - */ -static char * -twoway_memmem(const unsigned char *h, const unsigned char *z, - const unsigned char *n, size_t l) -{ - size_t i, ip, jp, k, p, ms, p0, mem, mem0; - size_t byteset[32 / sizeof(size_t)] = { 0 }; - size_t shift[256]; - - /* Computing length of needle and fill shift table */ - for (i=0; i n[jp+k]) { - jp += k; - k = 1; - p = jp - ip; - } else { - ip = jp++; - k = p = 1; - } - } - ms = ip; - p0 = p; - - /* And with the opposite comparison */ - ip = -1; jp = 0; k = p = 1; - while (jp+k ms+1) ms = ip; - else p = p0; - - /* Periodic needle? */ - if (memcmp(n, n+p, ms+1)) { - mem0 = 0; - p = MAX(ms, l-ms-1) + 1; - } else mem0 = l-p; - mem = 0; - - /* Search loop */ - for (;;) { - /* If remainder of haystack is shorter than needle, done */ - if (z-h < l) return 0; - - /* Check last byte first; advance by shift on mismatch */ - if (BITOP(byteset, h[l-1], &)) { - k = l-shift[h[l-1]]; - if (k) { - if (k < mem) k = mem; - h += k; - mem = 0; - continue; - } - } else { - h += l; - mem = 0; - continue; - } - - /* Compare right half */ - for (k=MAX(ms+1,mem); kmem && n[k-1] == h[k-1]; k--); - if (k <= mem) return (char *)h; - h += p; - mem = mem0; - } -} - -void * -memmem(const void *h0, size_t k, const void *n0, size_t l) -{ - const unsigned char *h = h0, *n = n0; - - /* Return immediately on empty needle */ - if (!l) return (void *)h; - - /* Return immediately when needle is longer than haystack */ - if (k - * - * Permission to use, copy, modify, and distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. - * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - */ - -#include "compat.h" - -#include -#include -#include -#include -#include -#include "ohash.h" - -struct _ohash_record { - uint32_t hv; - const char *p; -}; - -#define DELETED ((const char *)h) -#define NONE (h->size) - -/* Don't bother changing the hash table if the change is small enough. */ -#define MINSIZE (1UL << 4) -#define MINDELETED 4 - -static void ohash_resize(struct ohash *); - - -/* This handles the common case of variable length keys, where the - * key is stored at the end of the record. - */ -void * -ohash_create_entry(struct ohash_info *i, const char *start, const char **end) -{ - char *p; - - if (!*end) - *end = start + strlen(start); - p = (i->alloc)(i->key_offset + (*end - start) + 1, i->data); - if (p) { - memcpy(p+i->key_offset, start, *end-start); - p[i->key_offset + (*end - start)] = '\0'; - } - return (void *)p; -} - -/* hash_delete only frees the hash structure. Use hash_first/hash_next - * to free entries as well. */ -void -ohash_delete(struct ohash *h) -{ - (h->info.free)(h->t, h->info.data); -#ifndef NDEBUG - h->t = NULL; -#endif -} - -static void -ohash_resize(struct ohash *h) -{ - struct _ohash_record *n; - size_t ns; - unsigned int j; - unsigned int i, incr; - - if (4 * h->deleted < h->total) { - if (h->size >= (UINT_MAX >> 1U)) - ns = UINT_MAX; - else - ns = h->size << 1U; - } else if (3 * h->deleted > 2 * h->total) - ns = h->size >> 1U; - else - ns = h->size; - if (ns < MINSIZE) - ns = MINSIZE; -#ifdef STATS_HASH - STAT_HASH_EXPAND++; - STAT_HASH_SIZE += ns - h->size; -#endif - - n = (h->info.calloc)(ns, sizeof(struct _ohash_record), h->info.data); - if (!n) - return; - - for (j = 0; j < h->size; j++) { - if (h->t[j].p != NULL && h->t[j].p != DELETED) { - i = h->t[j].hv % ns; - incr = ((h->t[j].hv % (ns - 2)) & ~1) + 1; - while (n[i].p != NULL) { - i += incr; - if (i >= ns) - i -= ns; - } - n[i].hv = h->t[j].hv; - n[i].p = h->t[j].p; - } - } - (h->info.free)(h->t, h->info.data); - h->t = n; - h->size = ns; - h->total -= h->deleted; - h->deleted = 0; -} - -void * -ohash_remove(struct ohash *h, unsigned int i) -{ - void *result = (void *)h->t[i].p; - - if (result == NULL || result == DELETED) - return NULL; - -#ifdef STATS_HASH - STAT_HASH_ENTRIES--; -#endif - h->t[i].p = DELETED; - h->deleted++; - if (h->deleted >= MINDELETED && 4 * h->deleted > h->total) - ohash_resize(h); - return result; -} - -void * -ohash_find(struct ohash *h, unsigned int i) -{ - if (h->t[i].p == DELETED) - return NULL; - else - return (void *)h->t[i].p; -} - -void * -ohash_insert(struct ohash *h, unsigned int i, void *p) -{ -#ifdef STATS_HASH - STAT_HASH_ENTRIES++; -#endif - if (h->t[i].p == DELETED) { - h->deleted--; - h->t[i].p = p; - } else { - h->t[i].p = p; - /* Arbitrary resize boundary. Tweak if not efficient enough. */ - if (++h->total * 4 > h->size * 3) - ohash_resize(h); - } - return p; -} - -unsigned int -ohash_entries(struct ohash *h) -{ - return h->total - h->deleted; -} - -void * -ohash_first(struct ohash *h, unsigned int *pos) -{ - *pos = 0; - return ohash_next(h, pos); -} - -void * -ohash_next(struct ohash *h, unsigned int *pos) -{ - for (; *pos < h->size; (*pos)++) - if (h->t[*pos].p != DELETED && h->t[*pos].p != NULL) - return (void *)h->t[(*pos)++].p; - return NULL; -} - -void -ohash_init(struct ohash *h, unsigned int size, struct ohash_info *info) -{ - h->size = 1UL << size; - if (h->size < MINSIZE) - h->size = MINSIZE; -#ifdef STATS_HASH - STAT_HASH_CREATION++; - STAT_HASH_SIZE += h->size; -#endif - /* Copy info so that caller may free it. */ - h->info.key_offset = info->key_offset; - h->info.calloc = info->calloc; - h->info.free = info->free; - h->info.alloc = info->alloc; - h->info.data = info->data; - h->t = (h->info.calloc)(h->size, sizeof(struct _ohash_record), - h->info.data); - h->total = h->deleted = 0; -} - -uint32_t -ohash_interval(const char *s, const char **e) -{ - uint32_t k; - - if (!*e) - *e = s + strlen(s); - if (s == *e) - k = 0; - else - k = *s++; - while (s != *e) - k = ((k << 2) | (k >> 30)) ^ *s++; - return k; -} - -unsigned int -ohash_lookup_interval(struct ohash *h, const char *start, const char *end, - uint32_t hv) -{ - unsigned int i, incr; - unsigned int empty; - -#ifdef STATS_HASH - STAT_HASH_LOOKUP++; -#endif - empty = NONE; - i = hv % h->size; - incr = ((hv % (h->size-2)) & ~1) + 1; - while (h->t[i].p != NULL) { -#ifdef STATS_HASH - STAT_HASH_LENGTH++; -#endif - if (h->t[i].p == DELETED) { - if (empty == NONE) - empty = i; - } else if (h->t[i].hv == hv && - strncmp(h->t[i].p+h->info.key_offset, start, - end - start) == 0 && - (h->t[i].p+h->info.key_offset)[end-start] == '\0') { - if (empty != NONE) { - h->t[empty].hv = hv; - h->t[empty].p = h->t[i].p; - h->t[i].p = DELETED; - return empty; - } else { -#ifdef STATS_HASH - STAT_HASH_POSITIVE++; -#endif - return i; - } - } - i += incr; - if (i >= h->size) - i -= h->size; - } - - /* Found an empty position. */ - if (empty != NONE) - i = empty; - h->t[i].hv = hv; - return i; -} - -unsigned int -ohash_lookup_memory(struct ohash *h, const char *k, size_t size, uint32_t hv) -{ - unsigned int i, incr; - unsigned int empty; - -#ifdef STATS_HASH - STAT_HASH_LOOKUP++; -#endif - empty = NONE; - i = hv % h->size; - incr = ((hv % (h->size-2)) & ~1) + 1; - while (h->t[i].p != NULL) { -#ifdef STATS_HASH - STAT_HASH_LENGTH++; -#endif - if (h->t[i].p == DELETED) { - if (empty == NONE) - empty = i; - } else if (h->t[i].hv == hv && - memcmp(h->t[i].p+h->info.key_offset, k, size) == 0) { - if (empty != NONE) { - h->t[empty].hv = hv; - h->t[empty].p = h->t[i].p; - h->t[i].p = DELETED; - return empty; - } else { -#ifdef STATS_HASH - STAT_HASH_POSITIVE++; -#endif - } return i; - } - i += incr; - if (i >= h->size) - i -= h->size; - } - - /* Found an empty position. */ - if (empty != NONE) - i = empty; - h->t[i].hv = hv; - return i; -} - -unsigned int -ohash_qlookup(struct ohash *h, const char *s) -{ - const char *e = NULL; - return ohash_qlookupi(h, s, &e); -} - -unsigned int -ohash_qlookupi(struct ohash *h, const char *s, const char **e) -{ - uint32_t hv; - - hv = ohash_interval(s, e); - return ohash_lookup_interval(h, s, *e, hv); -} blob - 3f070e2c7257456fa8401d225eea13c7eea93d1c (mode 644) blob + /dev/null --- compat/ohash.h +++ /dev/null @@ -1,71 +0,0 @@ -/* $OpenBSD: ohash.h,v 1.2 2014/06/02 18:52:03 deraadt Exp $ */ - -/* Copyright (c) 1999, 2004 Marc Espie - * - * Permission to use, copy, modify, and distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. - * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - */ - -#ifndef OHASH_H -#define OHASH_H - -/* Open hashing support. - * Open hashing was chosen because it is much lighter than other hash - * techniques, and more efficient in most cases. - */ - -/* user-visible data structure */ -struct ohash_info { - ptrdiff_t key_offset; - void *data; /* user data */ - void *(*calloc)(size_t, size_t, void *); - void (*free)(void *, void *); - void *(*alloc)(size_t, void *); -}; - -struct _ohash_record; - -/* private structure. It's there just so you can do a sizeof */ -struct ohash { - struct _ohash_record *t; - struct ohash_info info; - unsigned int size; - unsigned int total; - unsigned int deleted; -}; - -/* For this to be tweakable, we use small primitives, and leave part of the - * logic to the client application. e.g., hashing is left to the client - * application. We also provide a simple table entry lookup that yields - * a hashing table index (opaque) to be used in find/insert/remove. - * The keys are stored at a known position in the client data. - */ -void ohash_init(struct ohash *, unsigned, struct ohash_info *); -void ohash_delete(struct ohash *); - -unsigned int ohash_lookup_interval(struct ohash *, const char *, - const char *, uint32_t); -unsigned int ohash_lookup_memory(struct ohash *, const char *, - size_t, uint32_t); -void *ohash_find(struct ohash *, unsigned int); -void *ohash_remove(struct ohash *, unsigned int); -void *ohash_insert(struct ohash *, unsigned int, void *); -void *ohash_first(struct ohash *, unsigned int *); -void *ohash_next(struct ohash *, unsigned int *); -unsigned int ohash_entries(struct ohash *); - -void *ohash_create_entry(struct ohash_info *, const char *, const char **); -uint32_t ohash_interval(const char *, const char **); - -unsigned int ohash_qlookupi(struct ohash *, const char *, const char **); -unsigned int ohash_qlookup(struct ohash *, const char *); -#endif blob - bc1568be67482bf033c0da484f8bd9fd41426d03 (mode 644) blob + /dev/null --- compat/queue.h +++ /dev/null @@ -1,631 +0,0 @@ -/* $OpenBSD: queue.h,v 1.46 2020/12/30 13:33:12 millert Exp $ */ -/* $NetBSD: queue.h,v 1.11 1996/05/16 05:17:14 mycroft Exp $ */ - -/* - * Copyright (c) 1991, 1993 - * The Regents of the University of California. All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * 1. Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * 3. Neither the name of the University nor the names of its contributors - * may be used to endorse or promote products derived from this software - * without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND - * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS - * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) - * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT - * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY - * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF - * SUCH DAMAGE. - * - * @(#)queue.h 8.5 (Berkeley) 8/20/94 - */ - -#ifndef _SYS_QUEUE_H_ -#define _SYS_QUEUE_H_ - -/* - * This file defines five types of data structures: singly-linked lists, - * lists, simple queues, tail queues and XOR simple queues. - * - * - * A singly-linked list is headed by a single forward pointer. The elements - * are singly linked for minimum space and pointer manipulation overhead at - * the expense of O(n) removal for arbitrary elements. New elements can be - * added to the list after an existing element or at the head of the list. - * Elements being removed from the head of the list should use the explicit - * macro for this purpose for optimum efficiency. A singly-linked list may - * only be traversed in the forward direction. Singly-linked lists are ideal - * for applications with large datasets and few or no removals or for - * implementing a LIFO queue. - * - * A list is headed by a single forward pointer (or an array of forward - * pointers for a hash table header). The elements are doubly linked - * so that an arbitrary element can be removed without a need to - * traverse the list. New elements can be added to the list before - * or after an existing element or at the head of the list. A list - * may only be traversed in the forward direction. - * - * A simple queue is headed by a pair of pointers, one to the head of the - * list and the other to the tail of the list. The elements are singly - * linked to save space, so elements can only be removed from the - * head of the list. New elements can be added to the list before or after - * an existing element, at the head of the list, or at the end of the - * list. A simple queue may only be traversed in the forward direction. - * - * A tail queue is headed by a pair of pointers, one to the head of the - * list and the other to the tail of the list. The elements are doubly - * linked so that an arbitrary element can be removed without a need to - * traverse the list. New elements can be added to the list before or - * after an existing element, at the head of the list, or at the end of - * the list. A tail queue may be traversed in either direction. - * - * An XOR simple queue is used in the same way as a regular simple queue. - * The difference is that the head structure also includes a "cookie" that - * is XOR'd with the queue pointer (first, last or next) to generate the - * real pointer value. - * - * For details on the use of these macros, see the queue(3) manual page. - */ - -#if defined(QUEUE_MACRO_DEBUG) || (defined(_KERNEL) && defined(DIAGNOSTIC)) -#define _Q_INVALID ((void *)-1) -#define _Q_INVALIDATE(a) (a) = _Q_INVALID -#else -#define _Q_INVALIDATE(a) -#endif - -/* - * Singly-linked List definitions. - */ -#define SLIST_HEAD(name, type) \ -struct name { \ - struct type *slh_first; /* first element */ \ -} - -#define SLIST_HEAD_INITIALIZER(head) \ - { NULL } - -#define SLIST_ENTRY(type) \ -struct { \ - struct type *sle_next; /* next element */ \ -} - -/* - * Singly-linked List access methods. - */ -#define SLIST_FIRST(head) ((head)->slh_first) -#define SLIST_END(head) NULL -#define SLIST_EMPTY(head) (SLIST_FIRST(head) == SLIST_END(head)) -#define SLIST_NEXT(elm, field) ((elm)->field.sle_next) - -#define SLIST_FOREACH(var, head, field) \ - for((var) = SLIST_FIRST(head); \ - (var) != SLIST_END(head); \ - (var) = SLIST_NEXT(var, field)) - -#define SLIST_FOREACH_SAFE(var, head, field, tvar) \ - for ((var) = SLIST_FIRST(head); \ - (var) && ((tvar) = SLIST_NEXT(var, field), 1); \ - (var) = (tvar)) - -/* - * Singly-linked List functions. - */ -#define SLIST_INIT(head) { \ - SLIST_FIRST(head) = SLIST_END(head); \ -} - -#define SLIST_INSERT_AFTER(slistelm, elm, field) do { \ - (elm)->field.sle_next = (slistelm)->field.sle_next; \ - (slistelm)->field.sle_next = (elm); \ -} while (0) - -#define SLIST_INSERT_HEAD(head, elm, field) do { \ - (elm)->field.sle_next = (head)->slh_first; \ - (head)->slh_first = (elm); \ -} while (0) - -#define SLIST_REMOVE_AFTER(elm, field) do { \ - (elm)->field.sle_next = (elm)->field.sle_next->field.sle_next; \ -} while (0) - -#define SLIST_REMOVE_HEAD(head, field) do { \ - (head)->slh_first = (head)->slh_first->field.sle_next; \ -} while (0) - -#define SLIST_REMOVE(head, elm, type, field) do { \ - if ((head)->slh_first == (elm)) { \ - SLIST_REMOVE_HEAD((head), field); \ - } else { \ - struct type *curelm = (head)->slh_first; \ - \ - while (curelm->field.sle_next != (elm)) \ - curelm = curelm->field.sle_next; \ - curelm->field.sle_next = \ - curelm->field.sle_next->field.sle_next; \ - } \ - _Q_INVALIDATE((elm)->field.sle_next); \ -} while (0) - -/* - * List definitions. - */ -#define LIST_HEAD(name, type) \ -struct name { \ - struct type *lh_first; /* first element */ \ -} - -#define LIST_HEAD_INITIALIZER(head) \ - { NULL } - -#define LIST_ENTRY(type) \ -struct { \ - struct type *le_next; /* next element */ \ - struct type **le_prev; /* address of previous next element */ \ -} - -/* - * List access methods. - */ -#define LIST_FIRST(head) ((head)->lh_first) -#define LIST_END(head) NULL -#define LIST_EMPTY(head) (LIST_FIRST(head) == LIST_END(head)) -#define LIST_NEXT(elm, field) ((elm)->field.le_next) - -#define LIST_FOREACH(var, head, field) \ - for((var) = LIST_FIRST(head); \ - (var)!= LIST_END(head); \ - (var) = LIST_NEXT(var, field)) - -#define LIST_FOREACH_SAFE(var, head, field, tvar) \ - for ((var) = LIST_FIRST(head); \ - (var) && ((tvar) = LIST_NEXT(var, field), 1); \ - (var) = (tvar)) - -/* - * List functions. - */ -#define LIST_INIT(head) do { \ - LIST_FIRST(head) = LIST_END(head); \ -} while (0) - -#define LIST_INSERT_AFTER(listelm, elm, field) do { \ - if (((elm)->field.le_next = (listelm)->field.le_next) != NULL) \ - (listelm)->field.le_next->field.le_prev = \ - &(elm)->field.le_next; \ - (listelm)->field.le_next = (elm); \ - (elm)->field.le_prev = &(listelm)->field.le_next; \ -} while (0) - -#define LIST_INSERT_BEFORE(listelm, elm, field) do { \ - (elm)->field.le_prev = (listelm)->field.le_prev; \ - (elm)->field.le_next = (listelm); \ - *(listelm)->field.le_prev = (elm); \ - (listelm)->field.le_prev = &(elm)->field.le_next; \ -} while (0) - -#define LIST_INSERT_HEAD(head, elm, field) do { \ - if (((elm)->field.le_next = (head)->lh_first) != NULL) \ - (head)->lh_first->field.le_prev = &(elm)->field.le_next;\ - (head)->lh_first = (elm); \ - (elm)->field.le_prev = &(head)->lh_first; \ -} while (0) - -#define LIST_REMOVE(elm, field) do { \ - if ((elm)->field.le_next != NULL) \ - (elm)->field.le_next->field.le_prev = \ - (elm)->field.le_prev; \ - *(elm)->field.le_prev = (elm)->field.le_next; \ - _Q_INVALIDATE((elm)->field.le_prev); \ - _Q_INVALIDATE((elm)->field.le_next); \ -} while (0) - -#define LIST_REPLACE(elm, elm2, field) do { \ - if (((elm2)->field.le_next = (elm)->field.le_next) != NULL) \ - (elm2)->field.le_next->field.le_prev = \ - &(elm2)->field.le_next; \ - (elm2)->field.le_prev = (elm)->field.le_prev; \ - *(elm2)->field.le_prev = (elm2); \ - _Q_INVALIDATE((elm)->field.le_prev); \ - _Q_INVALIDATE((elm)->field.le_next); \ -} while (0) - -/* - * Simple queue definitions. - */ -#define SIMPLEQ_HEAD(name, type) \ -struct name { \ - struct type *sqh_first; /* first element */ \ - struct type **sqh_last; /* addr of last next element */ \ -} - -#define SIMPLEQ_HEAD_INITIALIZER(head) \ - { NULL, &(head).sqh_first } - -#define SIMPLEQ_ENTRY(type) \ -struct { \ - struct type *sqe_next; /* next element */ \ -} - -/* - * Simple queue access methods. - */ -#define SIMPLEQ_FIRST(head) ((head)->sqh_first) -#define SIMPLEQ_END(head) NULL -#define SIMPLEQ_EMPTY(head) (SIMPLEQ_FIRST(head) == SIMPLEQ_END(head)) -#define SIMPLEQ_NEXT(elm, field) ((elm)->field.sqe_next) - -#define SIMPLEQ_FOREACH(var, head, field) \ - for((var) = SIMPLEQ_FIRST(head); \ - (var) != SIMPLEQ_END(head); \ - (var) = SIMPLEQ_NEXT(var, field)) - -#define SIMPLEQ_FOREACH_SAFE(var, head, field, tvar) \ - for ((var) = SIMPLEQ_FIRST(head); \ - (var) && ((tvar) = SIMPLEQ_NEXT(var, field), 1); \ - (var) = (tvar)) - -/* - * Simple queue functions. - */ -#define SIMPLEQ_INIT(head) do { \ - (head)->sqh_first = NULL; \ - (head)->sqh_last = &(head)->sqh_first; \ -} while (0) - -#define SIMPLEQ_INSERT_HEAD(head, elm, field) do { \ - if (((elm)->field.sqe_next = (head)->sqh_first) == NULL) \ - (head)->sqh_last = &(elm)->field.sqe_next; \ - (head)->sqh_first = (elm); \ -} while (0) - -#define SIMPLEQ_INSERT_TAIL(head, elm, field) do { \ - (elm)->field.sqe_next = NULL; \ - *(head)->sqh_last = (elm); \ - (head)->sqh_last = &(elm)->field.sqe_next; \ -} while (0) - -#define SIMPLEQ_INSERT_AFTER(head, listelm, elm, field) do { \ - if (((elm)->field.sqe_next = (listelm)->field.sqe_next) == NULL)\ - (head)->sqh_last = &(elm)->field.sqe_next; \ - (listelm)->field.sqe_next = (elm); \ -} while (0) - -#define SIMPLEQ_REMOVE_HEAD(head, field) do { \ - if (((head)->sqh_first = (head)->sqh_first->field.sqe_next) == NULL) \ - (head)->sqh_last = &(head)->sqh_first; \ -} while (0) - -#define SIMPLEQ_REMOVE_AFTER(head, elm, field) do { \ - if (((elm)->field.sqe_next = (elm)->field.sqe_next->field.sqe_next) \ - == NULL) \ - (head)->sqh_last = &(elm)->field.sqe_next; \ -} while (0) - -#define SIMPLEQ_CONCAT(head1, head2) do { \ - if (!SIMPLEQ_EMPTY((head2))) { \ - *(head1)->sqh_last = (head2)->sqh_first; \ - (head1)->sqh_last = (head2)->sqh_last; \ - SIMPLEQ_INIT((head2)); \ - } \ -} while (0) - -/* - * XOR Simple queue definitions. - */ -#define XSIMPLEQ_HEAD(name, type) \ -struct name { \ - struct type *sqx_first; /* first element */ \ - struct type **sqx_last; /* addr of last next element */ \ - unsigned long sqx_cookie; \ -} - -#define XSIMPLEQ_ENTRY(type) \ -struct { \ - struct type *sqx_next; /* next element */ \ -} - -/* - * XOR Simple queue access methods. - */ -#define XSIMPLEQ_XOR(head, ptr) ((__typeof(ptr))((head)->sqx_cookie ^ \ - (unsigned long)(ptr))) -#define XSIMPLEQ_FIRST(head) XSIMPLEQ_XOR(head, ((head)->sqx_first)) -#define XSIMPLEQ_END(head) NULL -#define XSIMPLEQ_EMPTY(head) (XSIMPLEQ_FIRST(head) == XSIMPLEQ_END(head)) -#define XSIMPLEQ_NEXT(head, elm, field) XSIMPLEQ_XOR(head, ((elm)->field.sqx_next)) - - -#define XSIMPLEQ_FOREACH(var, head, field) \ - for ((var) = XSIMPLEQ_FIRST(head); \ - (var) != XSIMPLEQ_END(head); \ - (var) = XSIMPLEQ_NEXT(head, var, field)) - -#define XSIMPLEQ_FOREACH_SAFE(var, head, field, tvar) \ - for ((var) = XSIMPLEQ_FIRST(head); \ - (var) && ((tvar) = XSIMPLEQ_NEXT(head, var, field), 1); \ - (var) = (tvar)) - -/* - * XOR Simple queue functions. - */ -#define XSIMPLEQ_INIT(head) do { \ - arc4random_buf(&(head)->sqx_cookie, sizeof((head)->sqx_cookie)); \ - (head)->sqx_first = XSIMPLEQ_XOR(head, NULL); \ - (head)->sqx_last = XSIMPLEQ_XOR(head, &(head)->sqx_first); \ -} while (0) - -#define XSIMPLEQ_INSERT_HEAD(head, elm, field) do { \ - if (((elm)->field.sqx_next = (head)->sqx_first) == \ - XSIMPLEQ_XOR(head, NULL)) \ - (head)->sqx_last = XSIMPLEQ_XOR(head, &(elm)->field.sqx_next); \ - (head)->sqx_first = XSIMPLEQ_XOR(head, (elm)); \ -} while (0) - -#define XSIMPLEQ_INSERT_TAIL(head, elm, field) do { \ - (elm)->field.sqx_next = XSIMPLEQ_XOR(head, NULL); \ - *(XSIMPLEQ_XOR(head, (head)->sqx_last)) = XSIMPLEQ_XOR(head, (elm)); \ - (head)->sqx_last = XSIMPLEQ_XOR(head, &(elm)->field.sqx_next); \ -} while (0) - -#define XSIMPLEQ_INSERT_AFTER(head, listelm, elm, field) do { \ - if (((elm)->field.sqx_next = (listelm)->field.sqx_next) == \ - XSIMPLEQ_XOR(head, NULL)) \ - (head)->sqx_last = XSIMPLEQ_XOR(head, &(elm)->field.sqx_next); \ - (listelm)->field.sqx_next = XSIMPLEQ_XOR(head, (elm)); \ -} while (0) - -#define XSIMPLEQ_REMOVE_HEAD(head, field) do { \ - if (((head)->sqx_first = XSIMPLEQ_XOR(head, \ - (head)->sqx_first)->field.sqx_next) == XSIMPLEQ_XOR(head, NULL)) \ - (head)->sqx_last = XSIMPLEQ_XOR(head, &(head)->sqx_first); \ -} while (0) - -#define XSIMPLEQ_REMOVE_AFTER(head, elm, field) do { \ - if (((elm)->field.sqx_next = XSIMPLEQ_XOR(head, \ - (elm)->field.sqx_next)->field.sqx_next) \ - == XSIMPLEQ_XOR(head, NULL)) \ - (head)->sqx_last = \ - XSIMPLEQ_XOR(head, &(elm)->field.sqx_next); \ -} while (0) - - -/* - * Tail queue definitions. - */ -#define TAILQ_HEAD(name, type) \ -struct name { \ - struct type *tqh_first; /* first element */ \ - struct type **tqh_last; /* addr of last next element */ \ -} - -#define TAILQ_HEAD_INITIALIZER(head) \ - { NULL, &(head).tqh_first } - -#define TAILQ_ENTRY(type) \ -struct { \ - struct type *tqe_next; /* next element */ \ - struct type **tqe_prev; /* address of previous next element */ \ -} - -/* - * Tail queue access methods. - */ -#define TAILQ_FIRST(head) ((head)->tqh_first) -#define TAILQ_END(head) NULL -#define TAILQ_NEXT(elm, field) ((elm)->field.tqe_next) -#define TAILQ_LAST(head, headname) \ - (*(((struct headname *)((head)->tqh_last))->tqh_last)) -/* XXX */ -#define TAILQ_PREV(elm, headname, field) \ - (*(((struct headname *)((elm)->field.tqe_prev))->tqh_last)) -#define TAILQ_EMPTY(head) \ - (TAILQ_FIRST(head) == TAILQ_END(head)) - -#define TAILQ_FOREACH(var, head, field) \ - for((var) = TAILQ_FIRST(head); \ - (var) != TAILQ_END(head); \ - (var) = TAILQ_NEXT(var, field)) - -#define TAILQ_FOREACH_SAFE(var, head, field, tvar) \ - for ((var) = TAILQ_FIRST(head); \ - (var) != TAILQ_END(head) && \ - ((tvar) = TAILQ_NEXT(var, field), 1); \ - (var) = (tvar)) - - -#define TAILQ_FOREACH_REVERSE(var, head, headname, field) \ - for((var) = TAILQ_LAST(head, headname); \ - (var) != TAILQ_END(head); \ - (var) = TAILQ_PREV(var, headname, field)) - -#define TAILQ_FOREACH_REVERSE_SAFE(var, head, headname, field, tvar) \ - for ((var) = TAILQ_LAST(head, headname); \ - (var) != TAILQ_END(head) && \ - ((tvar) = TAILQ_PREV(var, headname, field), 1); \ - (var) = (tvar)) - -/* - * Tail queue functions. - */ -#define TAILQ_INIT(head) do { \ - (head)->tqh_first = NULL; \ - (head)->tqh_last = &(head)->tqh_first; \ -} while (0) - -#define TAILQ_INSERT_HEAD(head, elm, field) do { \ - if (((elm)->field.tqe_next = (head)->tqh_first) != NULL) \ - (head)->tqh_first->field.tqe_prev = \ - &(elm)->field.tqe_next; \ - else \ - (head)->tqh_last = &(elm)->field.tqe_next; \ - (head)->tqh_first = (elm); \ - (elm)->field.tqe_prev = &(head)->tqh_first; \ -} while (0) - -#define TAILQ_INSERT_TAIL(head, elm, field) do { \ - (elm)->field.tqe_next = NULL; \ - (elm)->field.tqe_prev = (head)->tqh_last; \ - *(head)->tqh_last = (elm); \ - (head)->tqh_last = &(elm)->field.tqe_next; \ -} while (0) - -#define TAILQ_INSERT_AFTER(head, listelm, elm, field) do { \ - if (((elm)->field.tqe_next = (listelm)->field.tqe_next) != NULL)\ - (elm)->field.tqe_next->field.tqe_prev = \ - &(elm)->field.tqe_next; \ - else \ - (head)->tqh_last = &(elm)->field.tqe_next; \ - (listelm)->field.tqe_next = (elm); \ - (elm)->field.tqe_prev = &(listelm)->field.tqe_next; \ -} while (0) - -#define TAILQ_INSERT_BEFORE(listelm, elm, field) do { \ - (elm)->field.tqe_prev = (listelm)->field.tqe_prev; \ - (elm)->field.tqe_next = (listelm); \ - *(listelm)->field.tqe_prev = (elm); \ - (listelm)->field.tqe_prev = &(elm)->field.tqe_next; \ -} while (0) - -#define TAILQ_REMOVE(head, elm, field) do { \ - if (((elm)->field.tqe_next) != NULL) \ - (elm)->field.tqe_next->field.tqe_prev = \ - (elm)->field.tqe_prev; \ - else \ - (head)->tqh_last = (elm)->field.tqe_prev; \ - *(elm)->field.tqe_prev = (elm)->field.tqe_next; \ - _Q_INVALIDATE((elm)->field.tqe_prev); \ - _Q_INVALIDATE((elm)->field.tqe_next); \ -} while (0) - -#define TAILQ_REPLACE(head, elm, elm2, field) do { \ - if (((elm2)->field.tqe_next = (elm)->field.tqe_next) != NULL) \ - (elm2)->field.tqe_next->field.tqe_prev = \ - &(elm2)->field.tqe_next; \ - else \ - (head)->tqh_last = &(elm2)->field.tqe_next; \ - (elm2)->field.tqe_prev = (elm)->field.tqe_prev; \ - *(elm2)->field.tqe_prev = (elm2); \ - _Q_INVALIDATE((elm)->field.tqe_prev); \ - _Q_INVALIDATE((elm)->field.tqe_next); \ -} while (0) - -#define TAILQ_CONCAT(head1, head2, field) do { \ - if (!TAILQ_EMPTY(head2)) { \ - *(head1)->tqh_last = (head2)->tqh_first; \ - (head2)->tqh_first->field.tqe_prev = (head1)->tqh_last; \ - (head1)->tqh_last = (head2)->tqh_last; \ - TAILQ_INIT((head2)); \ - } \ -} while (0) - -/* - * Singly-linked Tail queue declarations. - */ -#define STAILQ_HEAD(name, type) \ -struct name { \ - struct type *stqh_first; /* first element */ \ - struct type **stqh_last; /* addr of last next element */ \ -} - -#define STAILQ_HEAD_INITIALIZER(head) \ - { NULL, &(head).stqh_first } - -#define STAILQ_ENTRY(type) \ -struct { \ - struct type *stqe_next; /* next element */ \ -} - -/* - * Singly-linked Tail queue access methods. - */ -#define STAILQ_FIRST(head) ((head)->stqh_first) -#define STAILQ_END(head) NULL -#define STAILQ_EMPTY(head) (STAILQ_FIRST(head) == STAILQ_END(head)) -#define STAILQ_NEXT(elm, field) ((elm)->field.stqe_next) - -#define STAILQ_FOREACH(var, head, field) \ - for ((var) = STAILQ_FIRST(head); \ - (var) != STAILQ_END(head); \ - (var) = STAILQ_NEXT(var, field)) - -#define STAILQ_FOREACH_SAFE(var, head, field, tvar) \ - for ((var) = STAILQ_FIRST(head); \ - (var) && ((tvar) = STAILQ_NEXT(var, field), 1); \ - (var) = (tvar)) - -/* - * Singly-linked Tail queue functions. - */ -#define STAILQ_INIT(head) do { \ - STAILQ_FIRST((head)) = NULL; \ - (head)->stqh_last = &STAILQ_FIRST((head)); \ -} while (0) - -#define STAILQ_INSERT_HEAD(head, elm, field) do { \ - if ((STAILQ_NEXT((elm), field) = STAILQ_FIRST((head))) == NULL) \ - (head)->stqh_last = &STAILQ_NEXT((elm), field); \ - STAILQ_FIRST((head)) = (elm); \ -} while (0) - -#define STAILQ_INSERT_TAIL(head, elm, field) do { \ - STAILQ_NEXT((elm), field) = NULL; \ - *(head)->stqh_last = (elm); \ - (head)->stqh_last = &STAILQ_NEXT((elm), field); \ -} while (0) - -#define STAILQ_INSERT_AFTER(head, listelm, elm, field) do { \ - if ((STAILQ_NEXT((elm), field) = STAILQ_NEXT((elm), field)) == NULL)\ - (head)->stqh_last = &STAILQ_NEXT((elm), field); \ - STAILQ_NEXT((elm), field) = (elm); \ -} while (0) - -#define STAILQ_REMOVE_HEAD(head, field) do { \ - if ((STAILQ_FIRST((head)) = \ - STAILQ_NEXT(STAILQ_FIRST((head)), field)) == NULL) \ - (head)->stqh_last = &STAILQ_FIRST((head)); \ -} while (0) - -#define STAILQ_REMOVE_AFTER(head, elm, field) do { \ - if ((STAILQ_NEXT(elm, field) = \ - STAILQ_NEXT(STAILQ_NEXT(elm, field), field)) == NULL) \ - (head)->stqh_last = &STAILQ_NEXT((elm), field); \ -} while (0) - -#define STAILQ_REMOVE(head, elm, type, field) do { \ - if (STAILQ_FIRST((head)) == (elm)) { \ - STAILQ_REMOVE_HEAD((head), field); \ - } else { \ - struct type *curelm = (head)->stqh_first; \ - while (STAILQ_NEXT(curelm, field) != (elm)) \ - curelm = STAILQ_NEXT(curelm, field); \ - STAILQ_REMOVE_AFTER(head, curelm, field); \ - } \ -} while (0) - -#define STAILQ_CONCAT(head1, head2) do { \ - if (!STAILQ_EMPTY((head2))) { \ - *(head1)->stqh_last = (head2)->stqh_first; \ - (head1)->stqh_last = (head2)->stqh_last; \ - STAILQ_INIT((head2)); \ - } \ -} while (0) - -#define STAILQ_LAST(head, type, field) \ - (STAILQ_EMPTY((head)) ? NULL : \ - ((struct type *)(void *) \ - ((char *)((head)->stqh_last) - offsetof(struct type, field)))) - -#endif /* !_SYS_QUEUE_H_ */ blob - bbdbefedb14cf927c971d7f6f37b7192baeac977 (mode 644) blob + /dev/null --- compat/recallocarray.c +++ /dev/null @@ -1,89 +0,0 @@ -/* - * Copyright (c) 2008, 2017 Otto Moerbeek - * - * Permission to use, copy, modify, and distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. - * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - */ - -#include "compat.h" - -#include -#include -#include -#include -#include - -/* - * This is sqrt(SIZE_MAX+1), as s1*s2 <= SIZE_MAX - * if both s1 < MUL_NO_OVERFLOW and s2 < MUL_NO_OVERFLOW - */ -#define MUL_NO_OVERFLOW ((size_t)1 << (sizeof(size_t) * 4)) - -/* - * Even though specified in POSIX, the PAGESIZE and PAGE_SIZE - * macros have very poor portability. Since we only use this - * to avoid free() overhead for small shrinking, simply pick - * an arbitrary number. - */ -#define getpagesize() (1UL << 12) - -void * -recallocarray(void *ptr, size_t oldnmemb, size_t newnmemb, size_t size) -{ - size_t oldsize, newsize; - void *newptr; - - if (ptr == NULL) - return calloc(newnmemb, size); - - if ((newnmemb >= MUL_NO_OVERFLOW || size >= MUL_NO_OVERFLOW) && - newnmemb > 0 && SIZE_MAX / newnmemb < size) { - errno = ENOMEM; - return NULL; - } - newsize = newnmemb * size; - - if ((oldnmemb >= MUL_NO_OVERFLOW || size >= MUL_NO_OVERFLOW) && - oldnmemb > 0 && SIZE_MAX / oldnmemb < size) { - errno = EINVAL; - return NULL; - } - oldsize = oldnmemb * size; - - /* - * Don't bother too much if we're shrinking just a bit, - * we do not shrink for series of small steps, oh well. - */ - if (newsize <= oldsize) { - size_t d = oldsize - newsize; - - if (d < oldsize / 2 && d < getpagesize()) { - memset((char *)ptr + newsize, 0, d); - return ptr; - } - } - - newptr = malloc(newsize); - if (newptr == NULL) - return NULL; - - if (newsize > oldsize) { - memcpy(newptr, ptr, oldsize); - memset((char *)newptr + oldsize, 0, newsize - oldsize); - } else - memcpy(newptr, ptr, newsize); - - explicit_bzero(ptr, oldsize); - free(ptr); - - return newptr; -} blob - 15c6f0991321ddd8d80d0f6ad75937e68c84a810 (mode 644) blob + /dev/null --- compat/setproctitle.c +++ /dev/null @@ -1,54 +0,0 @@ -/* - * Copyright (c) 2016 Nicholas Marriott - * - * Permission to use, copy, modify, and distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. - * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER - * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING - * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - */ - -#include "compat.h" - -#include - -#include -#include -#include - -#if HAVE_PR_SET_NAME - -#include - -void -setproctitle(const char *fmt, ...) -{ - char title[16], name[16], *cp; - va_list ap; - int used; - - va_start(ap, fmt); - vsnprintf(title, sizeof title, fmt, ap); - va_end(ap); - - used = snprintf(name, sizeof name, "%s: %s", getprogname(), title); - if (used >= (int)sizeof name) { - cp = strrchr(name, ' '); - if (cp != NULL) - *cp = '\0'; - } - prctl(PR_SET_NAME, name); -} -#else -void -setproctitle(const char *fmt, ...) -{ - (void)fmt; -} -#endif blob - ddfd2925e5893cc1e9112bbe443911c66a82e3b1 (mode 644) blob + /dev/null --- compat/setprogname.c +++ /dev/null @@ -1,23 +0,0 @@ -/* - * Copyright (c) 2021 Omar Polo - * - * Permission to use, copy, modify, and distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. - * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER - * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING - * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - */ - -#include "../compat.h" - -void -setprogname(const char *name) -{ - return; -} blob - c096bc91af0bd03b8a9700bdfe642541a00e2d1b (mode 644) blob + /dev/null --- compat/strlcat.c +++ /dev/null @@ -1,57 +0,0 @@ -/* $OpenBSD: strlcat.c,v 1.19 2019/01/25 00:19:25 millert Exp $ */ - -/* - * Copyright (c) 1998, 2015 Todd C. Miller - * - * Permission to use, copy, modify, and distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. - * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - */ - -#include -#include - -#include "compat.h" - -/* - * Appends src to string dst of size dsize (unlike strncat, dsize is the - * full size of dst, not space left). At most dsize-1 characters - * will be copied. Always NUL terminates (unless dsize <= strlen(dst)). - * Returns strlen(src) + MIN(dsize, strlen(initial dst)). - * If retval >= dsize, truncation occurred. - */ -size_t -strlcat(char *dst, const char *src, size_t dsize) -{ - const char *odst = dst; - const char *osrc = src; - size_t n = dsize; - size_t dlen; - - /* Find the end of dst and adjust bytes left but don't go past end. */ - while (n-- != 0 && *dst != '\0') - dst++; - dlen = dst - odst; - n = dsize - dlen; - - if (n-- == 0) - return(dlen + strlen(src)); - while (*src != '\0') { - if (n != 0) { - *dst++ = *src; - n--; - } - src++; - } - *dst = '\0'; - - return(dlen + (src - osrc)); /* count does not include NUL */ -} blob - 5d17ee7e953b68de4fd0eb168e96e38b362631f8 (mode 644) blob + /dev/null --- compat/strlcpy.c +++ /dev/null @@ -1,52 +0,0 @@ -/* $OpenBSD: strlcpy.c,v 1.16 2019/01/25 00:19:25 millert Exp $ */ - -/* - * Copyright (c) 1998, 2015 Todd C. Miller - * - * Permission to use, copy, modify, and distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. - * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - */ - -#include -#include - -#include "compat.h" - -/* - * Copy string src to buffer dst of size dsize. At most dsize-1 - * chars will be copied. Always NUL terminates (unless dsize == 0). - * Returns strlen(src); if retval >= dsize, truncation occurred. - */ -size_t -strlcpy(char *dst, const char *src, size_t dsize) -{ - const char *osrc = src; - size_t nleft = dsize; - - /* Copy as many bytes as will fit. */ - if (nleft != 0) { - while (--nleft != 0) { - if ((*dst++ = *src++) == '\0') - break; - } - } - - /* Not enough room in dst, add NUL and traverse rest of src. */ - if (nleft == 0) { - if (dsize != 0) - *dst = '\0'; /* NUL-terminate dst */ - while (*src++) - ; - } - - return(src - osrc - 1); /* count does not include NUL */ -} blob - ea71d7bba91e7b03ad3b9049d129e90752f3c457 (mode 644) blob + /dev/null --- compat/strsep.c +++ /dev/null @@ -1,72 +0,0 @@ -/* $OpenBSD: strsep.c,v 1.8 2015/08/31 02:53:57 guenther Exp $ */ - -/*- - * Copyright (c) 1990, 1993 - * The Regents of the University of California. All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * 1. Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * 3. Neither the name of the University nor the names of its contributors - * may be used to endorse or promote products derived from this software - * without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND - * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS - * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) - * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT - * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY - * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF - * SUCH DAMAGE. - */ - -#include "compat.h" - -#include - -/* - * Get next token from string *stringp, where tokens are possibly-empty - * strings separated by characters from delim. - * - * Writes NULs into the string at *stringp to end tokens. - * delim need not remain constant from call to call. - * On return, *stringp points past the last NUL written (if there might - * be further tokens), or is NULL (if there are definitely no more tokens). - * - * If *stringp is NULL, strsep returns NULL. - */ -char * -strsep(char **stringp, const char *delim) -{ - char *s; - const char *spanp; - int c, sc; - char *tok; - - if ((s = *stringp) == NULL) - return (NULL); - for (tok = s;;) { - c = *s++; - spanp = delim; - do { - if ((sc = *spanp++) == c) { - if (c == 0) - s = NULL; - else - s[-1] = 0; - *stringp = s; - return (tok); - } - } while (sc != 0); - } - /* NOTREACHED */ -} blob - a3d382ca22b6753ad4c1f7ab8bddaa9fa2eb4336 (mode 644) blob + /dev/null --- compat/strtonum.c +++ /dev/null @@ -1,65 +0,0 @@ -/* - * Copyright (c) 2004 Ted Unangst and Todd Miller - * All rights reserved. - * - * Permission to use, copy, modify, and distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. - * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - */ - -#include -#include -#include - -#include "compat.h" - -#define INVALID 1 -#define TOOSMALL 2 -#define TOOLARGE 3 - -long long -strtonum(const char *numstr, long long minval, long long maxval, - const char **errstrp) -{ - long long ll = 0; - int error = 0; - char *ep; - struct errval { - const char *errstr; - int err; - } ev[4] = { - { NULL, 0 }, - { "invalid", EINVAL }, - { "too small", ERANGE }, - { "too large", ERANGE }, - }; - - ev[0].err = errno; - errno = 0; - if (minval > maxval) { - error = INVALID; - } else { - ll = strtoll(numstr, &ep, 10); - if (numstr == ep || *ep != '\0') - error = INVALID; - else if ((ll == LLONG_MIN && errno == ERANGE) || ll < minval) - error = TOOSMALL; - else if ((ll == LLONG_MAX && errno == ERANGE) || ll > maxval) - error = TOOLARGE; - } - if (errstrp != NULL) - *errstrp = ev[error].errstr; - errno = ev[error].err; - if (error) - ll = 0; - - return (ll); -} blob - fb56e32567bed50a2455b9b8fb87e809bba9acd0 (mode 644) blob + /dev/null --- compat/tree.h +++ /dev/null @@ -1,1012 +0,0 @@ -/* $OpenBSD: tree.h,v 1.30 2020/10/10 18:03:41 otto Exp $ */ -/* - * Copyright 2002 Niels Provos - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * 1. Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR - * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES - * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. - * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, - * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT - * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, - * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY - * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF - * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -#ifndef _SYS_TREE_H_ -#define _SYS_TREE_H_ - -#include - -/* - * Local modifications: - * - dropped __unused - * - __inline -> inline - */ - -/* - * This file defines data structures for different types of trees: - * splay trees and red-black trees. - * - * A splay tree is a self-organizing data structure. Every operation - * on the tree causes a splay to happen. The splay moves the requested - * node to the root of the tree and partly rebalances it. - * - * This has the benefit that request locality causes faster lookups as - * the requested nodes move to the top of the tree. On the other hand, - * every lookup causes memory writes. - * - * The Balance Theorem bounds the total access time for m operations - * and n inserts on an initially empty tree as O((m + n)lg n). The - * amortized cost for a sequence of m accesses to a splay tree is O(lg n); - * - * A red-black tree is a binary search tree with the node color as an - * extra attribute. It fulfills a set of conditions: - * - every search path from the root to a leaf consists of the - * same number of black nodes, - * - each red node (except for the root) has a black parent, - * - each leaf node is black. - * - * Every operation on a red-black tree is bounded as O(lg n). - * The maximum height of a red-black tree is 2lg (n+1). - */ - -#define SPLAY_HEAD(name, type) \ -struct name { \ - struct type *sph_root; /* root of the tree */ \ -} - -#define SPLAY_INITIALIZER(root) \ - { NULL } - -#define SPLAY_INIT(root) do { \ - (root)->sph_root = NULL; \ -} while (0) - -#define SPLAY_ENTRY(type) \ -struct { \ - struct type *spe_left; /* left element */ \ - struct type *spe_right; /* right element */ \ -} - -#define SPLAY_LEFT(elm, field) (elm)->field.spe_left -#define SPLAY_RIGHT(elm, field) (elm)->field.spe_right -#define SPLAY_ROOT(head) (head)->sph_root -#define SPLAY_EMPTY(head) (SPLAY_ROOT(head) == NULL) - -/* SPLAY_ROTATE_{LEFT,RIGHT} expect that tmp hold SPLAY_{RIGHT,LEFT} */ -#define SPLAY_ROTATE_RIGHT(head, tmp, field) do { \ - SPLAY_LEFT((head)->sph_root, field) = SPLAY_RIGHT(tmp, field); \ - SPLAY_RIGHT(tmp, field) = (head)->sph_root; \ - (head)->sph_root = tmp; \ -} while (0) - -#define SPLAY_ROTATE_LEFT(head, tmp, field) do { \ - SPLAY_RIGHT((head)->sph_root, field) = SPLAY_LEFT(tmp, field); \ - SPLAY_LEFT(tmp, field) = (head)->sph_root; \ - (head)->sph_root = tmp; \ -} while (0) - -#define SPLAY_LINKLEFT(head, tmp, field) do { \ - SPLAY_LEFT(tmp, field) = (head)->sph_root; \ - tmp = (head)->sph_root; \ - (head)->sph_root = SPLAY_LEFT((head)->sph_root, field); \ -} while (0) - -#define SPLAY_LINKRIGHT(head, tmp, field) do { \ - SPLAY_RIGHT(tmp, field) = (head)->sph_root; \ - tmp = (head)->sph_root; \ - (head)->sph_root = SPLAY_RIGHT((head)->sph_root, field); \ -} while (0) - -#define SPLAY_ASSEMBLE(head, node, left, right, field) do { \ - SPLAY_RIGHT(left, field) = SPLAY_LEFT((head)->sph_root, field); \ - SPLAY_LEFT(right, field) = SPLAY_RIGHT((head)->sph_root, field);\ - SPLAY_LEFT((head)->sph_root, field) = SPLAY_RIGHT(node, field); \ - SPLAY_RIGHT((head)->sph_root, field) = SPLAY_LEFT(node, field); \ -} while (0) - -/* Generates prototypes and inline functions */ - -#define SPLAY_PROTOTYPE(name, type, field, cmp) \ -void name##_SPLAY(struct name *, struct type *); \ -void name##_SPLAY_MINMAX(struct name *, int); \ -struct type *name##_SPLAY_INSERT(struct name *, struct type *); \ -struct type *name##_SPLAY_REMOVE(struct name *, struct type *); \ - \ -/* Finds the node with the same key as elm */ \ -static inline struct type * \ -name##_SPLAY_FIND(struct name *head, struct type *elm) \ -{ \ - if (SPLAY_EMPTY(head)) \ - return(NULL); \ - name##_SPLAY(head, elm); \ - if ((cmp)(elm, (head)->sph_root) == 0) \ - return (head->sph_root); \ - return (NULL); \ -} \ - \ -static inline struct type * \ -name##_SPLAY_NEXT(struct name *head, struct type *elm) \ -{ \ - name##_SPLAY(head, elm); \ - if (SPLAY_RIGHT(elm, field) != NULL) { \ - elm = SPLAY_RIGHT(elm, field); \ - while (SPLAY_LEFT(elm, field) != NULL) { \ - elm = SPLAY_LEFT(elm, field); \ - } \ - } else \ - elm = NULL; \ - return (elm); \ -} \ - \ -static inline struct type * \ -name##_SPLAY_MIN_MAX(struct name *head, int val) \ -{ \ - name##_SPLAY_MINMAX(head, val); \ - return (SPLAY_ROOT(head)); \ -} - -/* Main splay operation. - * Moves node close to the key of elm to top - */ -#define SPLAY_GENERATE(name, type, field, cmp) \ -struct type * \ -name##_SPLAY_INSERT(struct name *head, struct type *elm) \ -{ \ - if (SPLAY_EMPTY(head)) { \ - SPLAY_LEFT(elm, field) = SPLAY_RIGHT(elm, field) = NULL; \ - } else { \ - int __comp; \ - name##_SPLAY(head, elm); \ - __comp = (cmp)(elm, (head)->sph_root); \ - if(__comp < 0) { \ - SPLAY_LEFT(elm, field) = SPLAY_LEFT((head)->sph_root, field);\ - SPLAY_RIGHT(elm, field) = (head)->sph_root; \ - SPLAY_LEFT((head)->sph_root, field) = NULL; \ - } else if (__comp > 0) { \ - SPLAY_RIGHT(elm, field) = SPLAY_RIGHT((head)->sph_root, field);\ - SPLAY_LEFT(elm, field) = (head)->sph_root; \ - SPLAY_RIGHT((head)->sph_root, field) = NULL; \ - } else \ - return ((head)->sph_root); \ - } \ - (head)->sph_root = (elm); \ - return (NULL); \ -} \ - \ -struct type * \ -name##_SPLAY_REMOVE(struct name *head, struct type *elm) \ -{ \ - struct type *__tmp; \ - if (SPLAY_EMPTY(head)) \ - return (NULL); \ - name##_SPLAY(head, elm); \ - if ((cmp)(elm, (head)->sph_root) == 0) { \ - if (SPLAY_LEFT((head)->sph_root, field) == NULL) { \ - (head)->sph_root = SPLAY_RIGHT((head)->sph_root, field);\ - } else { \ - __tmp = SPLAY_RIGHT((head)->sph_root, field); \ - (head)->sph_root = SPLAY_LEFT((head)->sph_root, field);\ - name##_SPLAY(head, elm); \ - SPLAY_RIGHT((head)->sph_root, field) = __tmp; \ - } \ - return (elm); \ - } \ - return (NULL); \ -} \ - \ -void \ -name##_SPLAY(struct name *head, struct type *elm) \ -{ \ - struct type __node, *__left, *__right, *__tmp; \ - int __comp; \ -\ - SPLAY_LEFT(&__node, field) = SPLAY_RIGHT(&__node, field) = NULL;\ - __left = __right = &__node; \ -\ - while ((__comp = (cmp)(elm, (head)->sph_root))) { \ - if (__comp < 0) { \ - __tmp = SPLAY_LEFT((head)->sph_root, field); \ - if (__tmp == NULL) \ - break; \ - if ((cmp)(elm, __tmp) < 0){ \ - SPLAY_ROTATE_RIGHT(head, __tmp, field); \ - if (SPLAY_LEFT((head)->sph_root, field) == NULL)\ - break; \ - } \ - SPLAY_LINKLEFT(head, __right, field); \ - } else if (__comp > 0) { \ - __tmp = SPLAY_RIGHT((head)->sph_root, field); \ - if (__tmp == NULL) \ - break; \ - if ((cmp)(elm, __tmp) > 0){ \ - SPLAY_ROTATE_LEFT(head, __tmp, field); \ - if (SPLAY_RIGHT((head)->sph_root, field) == NULL)\ - break; \ - } \ - SPLAY_LINKRIGHT(head, __left, field); \ - } \ - } \ - SPLAY_ASSEMBLE(head, &__node, __left, __right, field); \ -} \ - \ -/* Splay with either the minimum or the maximum element \ - * Used to find minimum or maximum element in tree. \ - */ \ -void name##_SPLAY_MINMAX(struct name *head, int __comp) \ -{ \ - struct type __node, *__left, *__right, *__tmp; \ -\ - SPLAY_LEFT(&__node, field) = SPLAY_RIGHT(&__node, field) = NULL;\ - __left = __right = &__node; \ -\ - while (1) { \ - if (__comp < 0) { \ - __tmp = SPLAY_LEFT((head)->sph_root, field); \ - if (__tmp == NULL) \ - break; \ - if (__comp < 0){ \ - SPLAY_ROTATE_RIGHT(head, __tmp, field); \ - if (SPLAY_LEFT((head)->sph_root, field) == NULL)\ - break; \ - } \ - SPLAY_LINKLEFT(head, __right, field); \ - } else if (__comp > 0) { \ - __tmp = SPLAY_RIGHT((head)->sph_root, field); \ - if (__tmp == NULL) \ - break; \ - if (__comp > 0) { \ - SPLAY_ROTATE_LEFT(head, __tmp, field); \ - if (SPLAY_RIGHT((head)->sph_root, field) == NULL)\ - break; \ - } \ - SPLAY_LINKRIGHT(head, __left, field); \ - } \ - } \ - SPLAY_ASSEMBLE(head, &__node, __left, __right, field); \ -} - -#define SPLAY_NEGINF -1 -#define SPLAY_INF 1 - -#define SPLAY_INSERT(name, x, y) name##_SPLAY_INSERT(x, y) -#define SPLAY_REMOVE(name, x, y) name##_SPLAY_REMOVE(x, y) -#define SPLAY_FIND(name, x, y) name##_SPLAY_FIND(x, y) -#define SPLAY_NEXT(name, x, y) name##_SPLAY_NEXT(x, y) -#define SPLAY_MIN(name, x) (SPLAY_EMPTY(x) ? NULL \ - : name##_SPLAY_MIN_MAX(x, SPLAY_NEGINF)) -#define SPLAY_MAX(name, x) (SPLAY_EMPTY(x) ? NULL \ - : name##_SPLAY_MIN_MAX(x, SPLAY_INF)) - -#define SPLAY_FOREACH(x, name, head) \ - for ((x) = SPLAY_MIN(name, head); \ - (x) != NULL; \ - (x) = SPLAY_NEXT(name, head, x)) - -/* Macros that define a red-black tree */ -#define RB_HEAD(name, type) \ -struct name { \ - struct type *rbh_root; /* root of the tree */ \ -} - -#define RB_INITIALIZER(root) \ - { NULL } - -#define RB_INIT(root) do { \ - (root)->rbh_root = NULL; \ -} while (0) - -#define RB_BLACK 0 -#define RB_RED 1 -#define RB_ENTRY(type) \ -struct { \ - struct type *rbe_left; /* left element */ \ - struct type *rbe_right; /* right element */ \ - struct type *rbe_parent; /* parent element */ \ - int rbe_color; /* node color */ \ -} - -#define RB_LEFT(elm, field) (elm)->field.rbe_left -#define RB_RIGHT(elm, field) (elm)->field.rbe_right -#define RB_PARENT(elm, field) (elm)->field.rbe_parent -#define RB_COLOR(elm, field) (elm)->field.rbe_color -#define RB_ROOT(head) (head)->rbh_root -#define RB_EMPTY(head) (RB_ROOT(head) == NULL) - -#define RB_SET(elm, parent, field) do { \ - RB_PARENT(elm, field) = parent; \ - RB_LEFT(elm, field) = RB_RIGHT(elm, field) = NULL; \ - RB_COLOR(elm, field) = RB_RED; \ -} while (0) - -#define RB_SET_BLACKRED(black, red, field) do { \ - RB_COLOR(black, field) = RB_BLACK; \ - RB_COLOR(red, field) = RB_RED; \ -} while (0) - -#ifndef RB_AUGMENT -#define RB_AUGMENT(x) do {} while (0) -#endif - -#define RB_ROTATE_LEFT(head, elm, tmp, field) do { \ - (tmp) = RB_RIGHT(elm, field); \ - if ((RB_RIGHT(elm, field) = RB_LEFT(tmp, field))) { \ - RB_PARENT(RB_LEFT(tmp, field), field) = (elm); \ - } \ - RB_AUGMENT(elm); \ - if ((RB_PARENT(tmp, field) = RB_PARENT(elm, field))) { \ - if ((elm) == RB_LEFT(RB_PARENT(elm, field), field)) \ - RB_LEFT(RB_PARENT(elm, field), field) = (tmp); \ - else \ - RB_RIGHT(RB_PARENT(elm, field), field) = (tmp); \ - } else \ - (head)->rbh_root = (tmp); \ - RB_LEFT(tmp, field) = (elm); \ - RB_PARENT(elm, field) = (tmp); \ - RB_AUGMENT(tmp); \ - if ((RB_PARENT(tmp, field))) \ - RB_AUGMENT(RB_PARENT(tmp, field)); \ -} while (0) - -#define RB_ROTATE_RIGHT(head, elm, tmp, field) do { \ - (tmp) = RB_LEFT(elm, field); \ - if ((RB_LEFT(elm, field) = RB_RIGHT(tmp, field))) { \ - RB_PARENT(RB_RIGHT(tmp, field), field) = (elm); \ - } \ - RB_AUGMENT(elm); \ - if ((RB_PARENT(tmp, field) = RB_PARENT(elm, field))) { \ - if ((elm) == RB_LEFT(RB_PARENT(elm, field), field)) \ - RB_LEFT(RB_PARENT(elm, field), field) = (tmp); \ - else \ - RB_RIGHT(RB_PARENT(elm, field), field) = (tmp); \ - } else \ - (head)->rbh_root = (tmp); \ - RB_RIGHT(tmp, field) = (elm); \ - RB_PARENT(elm, field) = (tmp); \ - RB_AUGMENT(tmp); \ - if ((RB_PARENT(tmp, field))) \ - RB_AUGMENT(RB_PARENT(tmp, field)); \ -} while (0) - -/* Generates prototypes and inline functions */ -#define RB_PROTOTYPE(name, type, field, cmp) \ - RB_PROTOTYPE_INTERNAL(name, type, field, cmp,) -#define RB_PROTOTYPE_STATIC(name, type, field, cmp) \ - RB_PROTOTYPE_INTERNAL(name, type, field, cmp, __attribute__((__unused__)) static) -#define RB_PROTOTYPE_INTERNAL(name, type, field, cmp, attr) \ -attr void name##_RB_INSERT_COLOR(struct name *, struct type *); \ -attr void name##_RB_REMOVE_COLOR(struct name *, struct type *, struct type *);\ -attr struct type *name##_RB_REMOVE(struct name *, struct type *); \ -attr struct type *name##_RB_INSERT(struct name *, struct type *); \ -attr struct type *name##_RB_FIND(struct name *, struct type *); \ -attr struct type *name##_RB_NFIND(struct name *, struct type *); \ -attr struct type *name##_RB_NEXT(struct type *); \ -attr struct type *name##_RB_PREV(struct type *); \ -attr struct type *name##_RB_MINMAX(struct name *, int); \ - \ - -/* Main rb operation. - * Moves node close to the key of elm to top - */ -#define RB_GENERATE(name, type, field, cmp) \ - RB_GENERATE_INTERNAL(name, type, field, cmp,) -#define RB_GENERATE_STATIC(name, type, field, cmp) \ - RB_GENERATE_INTERNAL(name, type, field, cmp, __attribute__((__unused__)) static) -#define RB_GENERATE_INTERNAL(name, type, field, cmp, attr) \ -attr void \ -name##_RB_INSERT_COLOR(struct name *head, struct type *elm) \ -{ \ - struct type *parent, *gparent, *tmp; \ - while ((parent = RB_PARENT(elm, field)) && \ - RB_COLOR(parent, field) == RB_RED) { \ - gparent = RB_PARENT(parent, field); \ - if (parent == RB_LEFT(gparent, field)) { \ - tmp = RB_RIGHT(gparent, field); \ - if (tmp && RB_COLOR(tmp, field) == RB_RED) { \ - RB_COLOR(tmp, field) = RB_BLACK; \ - RB_SET_BLACKRED(parent, gparent, field);\ - elm = gparent; \ - continue; \ - } \ - if (RB_RIGHT(parent, field) == elm) { \ - RB_ROTATE_LEFT(head, parent, tmp, field);\ - tmp = parent; \ - parent = elm; \ - elm = tmp; \ - } \ - RB_SET_BLACKRED(parent, gparent, field); \ - RB_ROTATE_RIGHT(head, gparent, tmp, field); \ - } else { \ - tmp = RB_LEFT(gparent, field); \ - if (tmp && RB_COLOR(tmp, field) == RB_RED) { \ - RB_COLOR(tmp, field) = RB_BLACK; \ - RB_SET_BLACKRED(parent, gparent, field);\ - elm = gparent; \ - continue; \ - } \ - if (RB_LEFT(parent, field) == elm) { \ - RB_ROTATE_RIGHT(head, parent, tmp, field);\ - tmp = parent; \ - parent = elm; \ - elm = tmp; \ - } \ - RB_SET_BLACKRED(parent, gparent, field); \ - RB_ROTATE_LEFT(head, gparent, tmp, field); \ - } \ - } \ - RB_COLOR(head->rbh_root, field) = RB_BLACK; \ -} \ - \ -attr void \ -name##_RB_REMOVE_COLOR(struct name *head, struct type *parent, struct type *elm) \ -{ \ - struct type *tmp; \ - while ((elm == NULL || RB_COLOR(elm, field) == RB_BLACK) && \ - elm != RB_ROOT(head)) { \ - if (RB_LEFT(parent, field) == elm) { \ - tmp = RB_RIGHT(parent, field); \ - if (RB_COLOR(tmp, field) == RB_RED) { \ - RB_SET_BLACKRED(tmp, parent, field); \ - RB_ROTATE_LEFT(head, parent, tmp, field);\ - tmp = RB_RIGHT(parent, field); \ - } \ - if ((RB_LEFT(tmp, field) == NULL || \ - RB_COLOR(RB_LEFT(tmp, field), field) == RB_BLACK) &&\ - (RB_RIGHT(tmp, field) == NULL || \ - RB_COLOR(RB_RIGHT(tmp, field), field) == RB_BLACK)) {\ - RB_COLOR(tmp, field) = RB_RED; \ - elm = parent; \ - parent = RB_PARENT(elm, field); \ - } else { \ - if (RB_RIGHT(tmp, field) == NULL || \ - RB_COLOR(RB_RIGHT(tmp, field), field) == RB_BLACK) {\ - struct type *oleft; \ - if ((oleft = RB_LEFT(tmp, field)))\ - RB_COLOR(oleft, field) = RB_BLACK;\ - RB_COLOR(tmp, field) = RB_RED; \ - RB_ROTATE_RIGHT(head, tmp, oleft, field);\ - tmp = RB_RIGHT(parent, field); \ - } \ - RB_COLOR(tmp, field) = RB_COLOR(parent, field);\ - RB_COLOR(parent, field) = RB_BLACK; \ - if (RB_RIGHT(tmp, field)) \ - RB_COLOR(RB_RIGHT(tmp, field), field) = RB_BLACK;\ - RB_ROTATE_LEFT(head, parent, tmp, field);\ - elm = RB_ROOT(head); \ - break; \ - } \ - } else { \ - tmp = RB_LEFT(parent, field); \ - if (RB_COLOR(tmp, field) == RB_RED) { \ - RB_SET_BLACKRED(tmp, parent, field); \ - RB_ROTATE_RIGHT(head, parent, tmp, field);\ - tmp = RB_LEFT(parent, field); \ - } \ - if ((RB_LEFT(tmp, field) == NULL || \ - RB_COLOR(RB_LEFT(tmp, field), field) == RB_BLACK) &&\ - (RB_RIGHT(tmp, field) == NULL || \ - RB_COLOR(RB_RIGHT(tmp, field), field) == RB_BLACK)) {\ - RB_COLOR(tmp, field) = RB_RED; \ - elm = parent; \ - parent = RB_PARENT(elm, field); \ - } else { \ - if (RB_LEFT(tmp, field) == NULL || \ - RB_COLOR(RB_LEFT(tmp, field), field) == RB_BLACK) {\ - struct type *oright; \ - if ((oright = RB_RIGHT(tmp, field)))\ - RB_COLOR(oright, field) = RB_BLACK;\ - RB_COLOR(tmp, field) = RB_RED; \ - RB_ROTATE_LEFT(head, tmp, oright, field);\ - tmp = RB_LEFT(parent, field); \ - } \ - RB_COLOR(tmp, field) = RB_COLOR(parent, field);\ - RB_COLOR(parent, field) = RB_BLACK; \ - if (RB_LEFT(tmp, field)) \ - RB_COLOR(RB_LEFT(tmp, field), field) = RB_BLACK;\ - RB_ROTATE_RIGHT(head, parent, tmp, field);\ - elm = RB_ROOT(head); \ - break; \ - } \ - } \ - } \ - if (elm) \ - RB_COLOR(elm, field) = RB_BLACK; \ -} \ - \ -attr struct type * \ -name##_RB_REMOVE(struct name *head, struct type *elm) \ -{ \ - struct type *child, *parent, *old = elm; \ - int color; \ - if (RB_LEFT(elm, field) == NULL) \ - child = RB_RIGHT(elm, field); \ - else if (RB_RIGHT(elm, field) == NULL) \ - child = RB_LEFT(elm, field); \ - else { \ - struct type *left; \ - elm = RB_RIGHT(elm, field); \ - while ((left = RB_LEFT(elm, field))) \ - elm = left; \ - child = RB_RIGHT(elm, field); \ - parent = RB_PARENT(elm, field); \ - color = RB_COLOR(elm, field); \ - if (child) \ - RB_PARENT(child, field) = parent; \ - if (parent) { \ - if (RB_LEFT(parent, field) == elm) \ - RB_LEFT(parent, field) = child; \ - else \ - RB_RIGHT(parent, field) = child; \ - RB_AUGMENT(parent); \ - } else \ - RB_ROOT(head) = child; \ - if (RB_PARENT(elm, field) == old) \ - parent = elm; \ - (elm)->field = (old)->field; \ - if (RB_PARENT(old, field)) { \ - if (RB_LEFT(RB_PARENT(old, field), field) == old)\ - RB_LEFT(RB_PARENT(old, field), field) = elm;\ - else \ - RB_RIGHT(RB_PARENT(old, field), field) = elm;\ - RB_AUGMENT(RB_PARENT(old, field)); \ - } else \ - RB_ROOT(head) = elm; \ - RB_PARENT(RB_LEFT(old, field), field) = elm; \ - if (RB_RIGHT(old, field)) \ - RB_PARENT(RB_RIGHT(old, field), field) = elm; \ - if (parent) { \ - left = parent; \ - do { \ - RB_AUGMENT(left); \ - } while ((left = RB_PARENT(left, field))); \ - } \ - goto color; \ - } \ - parent = RB_PARENT(elm, field); \ - color = RB_COLOR(elm, field); \ - if (child) \ - RB_PARENT(child, field) = parent; \ - if (parent) { \ - if (RB_LEFT(parent, field) == elm) \ - RB_LEFT(parent, field) = child; \ - else \ - RB_RIGHT(parent, field) = child; \ - RB_AUGMENT(parent); \ - } else \ - RB_ROOT(head) = child; \ -color: \ - if (color == RB_BLACK) \ - name##_RB_REMOVE_COLOR(head, parent, child); \ - return (old); \ -} \ - \ -/* Inserts a node into the RB tree */ \ -attr struct type * \ -name##_RB_INSERT(struct name *head, struct type *elm) \ -{ \ - struct type *tmp; \ - struct type *parent = NULL; \ - int comp = 0; \ - tmp = RB_ROOT(head); \ - while (tmp) { \ - parent = tmp; \ - comp = (cmp)(elm, parent); \ - if (comp < 0) \ - tmp = RB_LEFT(tmp, field); \ - else if (comp > 0) \ - tmp = RB_RIGHT(tmp, field); \ - else \ - return (tmp); \ - } \ - RB_SET(elm, parent, field); \ - if (parent != NULL) { \ - if (comp < 0) \ - RB_LEFT(parent, field) = elm; \ - else \ - RB_RIGHT(parent, field) = elm; \ - RB_AUGMENT(parent); \ - } else \ - RB_ROOT(head) = elm; \ - name##_RB_INSERT_COLOR(head, elm); \ - return (NULL); \ -} \ - \ -/* Finds the node with the same key as elm */ \ -attr struct type * \ -name##_RB_FIND(struct name *head, struct type *elm) \ -{ \ - struct type *tmp = RB_ROOT(head); \ - int comp; \ - while (tmp) { \ - comp = cmp(elm, tmp); \ - if (comp < 0) \ - tmp = RB_LEFT(tmp, field); \ - else if (comp > 0) \ - tmp = RB_RIGHT(tmp, field); \ - else \ - return (tmp); \ - } \ - return (NULL); \ -} \ - \ -/* Finds the first node greater than or equal to the search key */ \ -attr struct type * \ -name##_RB_NFIND(struct name *head, struct type *elm) \ -{ \ - struct type *tmp = RB_ROOT(head); \ - struct type *res = NULL; \ - int comp; \ - while (tmp) { \ - comp = cmp(elm, tmp); \ - if (comp < 0) { \ - res = tmp; \ - tmp = RB_LEFT(tmp, field); \ - } \ - else if (comp > 0) \ - tmp = RB_RIGHT(tmp, field); \ - else \ - return (tmp); \ - } \ - return (res); \ -} \ - \ -/* ARGSUSED */ \ -attr struct type * \ -name##_RB_NEXT(struct type *elm) \ -{ \ - if (RB_RIGHT(elm, field)) { \ - elm = RB_RIGHT(elm, field); \ - while (RB_LEFT(elm, field)) \ - elm = RB_LEFT(elm, field); \ - } else { \ - if (RB_PARENT(elm, field) && \ - (elm == RB_LEFT(RB_PARENT(elm, field), field))) \ - elm = RB_PARENT(elm, field); \ - else { \ - while (RB_PARENT(elm, field) && \ - (elm == RB_RIGHT(RB_PARENT(elm, field), field)))\ - elm = RB_PARENT(elm, field); \ - elm = RB_PARENT(elm, field); \ - } \ - } \ - return (elm); \ -} \ - \ -/* ARGSUSED */ \ -attr struct type * \ -name##_RB_PREV(struct type *elm) \ -{ \ - if (RB_LEFT(elm, field)) { \ - elm = RB_LEFT(elm, field); \ - while (RB_RIGHT(elm, field)) \ - elm = RB_RIGHT(elm, field); \ - } else { \ - if (RB_PARENT(elm, field) && \ - (elm == RB_RIGHT(RB_PARENT(elm, field), field))) \ - elm = RB_PARENT(elm, field); \ - else { \ - while (RB_PARENT(elm, field) && \ - (elm == RB_LEFT(RB_PARENT(elm, field), field)))\ - elm = RB_PARENT(elm, field); \ - elm = RB_PARENT(elm, field); \ - } \ - } \ - return (elm); \ -} \ - \ -attr struct type * \ -name##_RB_MINMAX(struct name *head, int val) \ -{ \ - struct type *tmp = RB_ROOT(head); \ - struct type *parent = NULL; \ - while (tmp) { \ - parent = tmp; \ - if (val < 0) \ - tmp = RB_LEFT(tmp, field); \ - else \ - tmp = RB_RIGHT(tmp, field); \ - } \ - return (parent); \ -} - -#define RB_NEGINF -1 -#define RB_INF 1 - -#define RB_INSERT(name, x, y) name##_RB_INSERT(x, y) -#define RB_REMOVE(name, x, y) name##_RB_REMOVE(x, y) -#define RB_FIND(name, x, y) name##_RB_FIND(x, y) -#define RB_NFIND(name, x, y) name##_RB_NFIND(x, y) -#define RB_NEXT(name, x, y) name##_RB_NEXT(y) -#define RB_PREV(name, x, y) name##_RB_PREV(y) -#define RB_MIN(name, x) name##_RB_MINMAX(x, RB_NEGINF) -#define RB_MAX(name, x) name##_RB_MINMAX(x, RB_INF) - -#define RB_FOREACH(x, name, head) \ - for ((x) = RB_MIN(name, head); \ - (x) != NULL; \ - (x) = name##_RB_NEXT(x)) - -#define RB_FOREACH_SAFE(x, name, head, y) \ - for ((x) = RB_MIN(name, head); \ - ((x) != NULL) && ((y) = name##_RB_NEXT(x), 1); \ - (x) = (y)) - -#define RB_FOREACH_REVERSE(x, name, head) \ - for ((x) = RB_MAX(name, head); \ - (x) != NULL; \ - (x) = name##_RB_PREV(x)) - -#define RB_FOREACH_REVERSE_SAFE(x, name, head, y) \ - for ((x) = RB_MAX(name, head); \ - ((x) != NULL) && ((y) = name##_RB_PREV(x), 1); \ - (x) = (y)) - - -/* - * Copyright (c) 2016 David Gwynne - * - * Permission to use, copy, modify, and distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. - * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - */ - -struct rb_type { - int (*t_compare)(const void *, const void *); - void (*t_augment)(void *); - unsigned int t_offset; /* offset of rb_entry in type */ -}; - -struct rb_tree { - struct rb_entry *rbt_root; -}; - -struct rb_entry { - struct rb_entry *rbt_parent; - struct rb_entry *rbt_left; - struct rb_entry *rbt_right; - unsigned int rbt_color; -}; - -#define RBT_HEAD(_name, _type) \ -struct _name { \ - struct rb_tree rbh_root; \ -} - -#define RBT_ENTRY(_type) struct rb_entry - -static inline void -_rb_init(struct rb_tree *rbt) -{ - rbt->rbt_root = NULL; -} - -static inline int -_rb_empty(struct rb_tree *rbt) -{ - return (rbt->rbt_root == NULL); -} - -void *_rb_insert(const struct rb_type *, struct rb_tree *, void *); -void *_rb_remove(const struct rb_type *, struct rb_tree *, void *); -void *_rb_find(const struct rb_type *, struct rb_tree *, const void *); -void *_rb_nfind(const struct rb_type *, struct rb_tree *, const void *); -void *_rb_root(const struct rb_type *, struct rb_tree *); -void *_rb_min(const struct rb_type *, struct rb_tree *); -void *_rb_max(const struct rb_type *, struct rb_tree *); -void *_rb_next(const struct rb_type *, void *); -void *_rb_prev(const struct rb_type *, void *); -void *_rb_left(const struct rb_type *, void *); -void *_rb_right(const struct rb_type *, void *); -void *_rb_parent(const struct rb_type *, void *); -void _rb_set_left(const struct rb_type *, void *, void *); -void _rb_set_right(const struct rb_type *, void *, void *); -void _rb_set_parent(const struct rb_type *, void *, void *); -void _rb_poison(const struct rb_type *, void *, unsigned long); -int _rb_check(const struct rb_type *, void *, unsigned long); - -#define RBT_INITIALIZER(_head) { { NULL } } - -#define RBT_PROTOTYPE(_name, _type, _field, _cmp) \ -extern const struct rb_type *const _name##_RBT_TYPE; \ - \ -static inline void \ -_name##_RBT_INIT(struct _name *head) \ -{ \ - _rb_init(&head->rbh_root); \ -} \ - \ -static inline struct _type * \ -_name##_RBT_INSERT(struct _name *head, struct _type *elm) \ -{ \ - return _rb_insert(_name##_RBT_TYPE, &head->rbh_root, elm); \ -} \ - \ -static inline struct _type * \ -_name##_RBT_REMOVE(struct _name *head, struct _type *elm) \ -{ \ - return _rb_remove(_name##_RBT_TYPE, &head->rbh_root, elm); \ -} \ - \ -static inline struct _type * \ -_name##_RBT_FIND(struct _name *head, const struct _type *key) \ -{ \ - return _rb_find(_name##_RBT_TYPE, &head->rbh_root, key); \ -} \ - \ -static inline struct _type * \ -_name##_RBT_NFIND(struct _name *head, const struct _type *key) \ -{ \ - return _rb_nfind(_name##_RBT_TYPE, &head->rbh_root, key); \ -} \ - \ -static inline struct _type * \ -_name##_RBT_ROOT(struct _name *head) \ -{ \ - return _rb_root(_name##_RBT_TYPE, &head->rbh_root); \ -} \ - \ -static inline int \ -_name##_RBT_EMPTY(struct _name *head) \ -{ \ - return _rb_empty(&head->rbh_root); \ -} \ - \ -static inline struct _type * \ -_name##_RBT_MIN(struct _name *head) \ -{ \ - return _rb_min(_name##_RBT_TYPE, &head->rbh_root); \ -} \ - \ -static inline struct _type * \ -_name##_RBT_MAX(struct _name *head) \ -{ \ - return _rb_max(_name##_RBT_TYPE, &head->rbh_root); \ -} \ - \ -static inline struct _type * \ -_name##_RBT_NEXT(struct _type *elm) \ -{ \ - return _rb_next(_name##_RBT_TYPE, elm); \ -} \ - \ -static inline struct _type * \ -_name##_RBT_PREV(struct _type *elm) \ -{ \ - return _rb_prev(_name##_RBT_TYPE, elm); \ -} \ - \ -static inline struct _type * \ -_name##_RBT_LEFT(struct _type *elm) \ -{ \ - return _rb_left(_name##_RBT_TYPE, elm); \ -} \ - \ -static inline struct _type * \ -_name##_RBT_RIGHT(struct _type *elm) \ -{ \ - return _rb_right(_name##_RBT_TYPE, elm); \ -} \ - \ -static inline struct _type * \ -_name##_RBT_PARENT(struct _type *elm) \ -{ \ - return _rb_parent(_name##_RBT_TYPE, elm); \ -} \ - \ -static inline void \ -_name##_RBT_SET_LEFT(struct _type *elm, struct _type *left) \ -{ \ - _rb_set_left(_name##_RBT_TYPE, elm, left); \ -} \ - \ -static inline void \ -_name##_RBT_SET_RIGHT(struct _type *elm, struct _type *right) \ -{ \ - _rb_set_right(_name##_RBT_TYPE, elm, right); \ -} \ - \ -static inline void \ -_name##_RBT_SET_PARENT(struct _type *elm, struct _type *parent) \ -{ \ - _rb_set_parent(_name##_RBT_TYPE, elm, parent); \ -} \ - \ -static inline void \ -_name##_RBT_POISON(struct _type *elm, unsigned long poison) \ -{ \ - _rb_poison(_name##_RBT_TYPE, elm, poison); \ -} \ - \ -static inline int \ -_name##_RBT_CHECK(struct _type *elm, unsigned long poison) \ -{ \ - return _rb_check(_name##_RBT_TYPE, elm, poison); \ -} - -#define RBT_GENERATE_INTERNAL(_name, _type, _field, _cmp, _aug) \ -static int \ -_name##_RBT_COMPARE(const void *lptr, const void *rptr) \ -{ \ - const struct _type *l = lptr, *r = rptr; \ - return _cmp(l, r); \ -} \ -static const struct rb_type _name##_RBT_INFO = { \ - _name##_RBT_COMPARE, \ - _aug, \ - offsetof(struct _type, _field), \ -}; \ -const struct rb_type *const _name##_RBT_TYPE = &_name##_RBT_INFO - -#define RBT_GENERATE_AUGMENT(_name, _type, _field, _cmp, _aug) \ -static void \ -_name##_RBT_AUGMENT(void *ptr) \ -{ \ - struct _type *p = ptr; \ - return _aug(p); \ -} \ -RBT_GENERATE_INTERNAL(_name, _type, _field, _cmp, _name##_RBT_AUGMENT) - -#define RBT_GENERATE(_name, _type, _field, _cmp) \ - RBT_GENERATE_INTERNAL(_name, _type, _field, _cmp, NULL) - -#define RBT_INIT(_name, _head) _name##_RBT_INIT(_head) -#define RBT_INSERT(_name, _head, _elm) _name##_RBT_INSERT(_head, _elm) -#define RBT_REMOVE(_name, _head, _elm) _name##_RBT_REMOVE(_head, _elm) -#define RBT_FIND(_name, _head, _key) _name##_RBT_FIND(_head, _key) -#define RBT_NFIND(_name, _head, _key) _name##_RBT_NFIND(_head, _key) -#define RBT_ROOT(_name, _head) _name##_RBT_ROOT(_head) -#define RBT_EMPTY(_name, _head) _name##_RBT_EMPTY(_head) -#define RBT_MIN(_name, _head) _name##_RBT_MIN(_head) -#define RBT_MAX(_name, _head) _name##_RBT_MAX(_head) -#define RBT_NEXT(_name, _elm) _name##_RBT_NEXT(_elm) -#define RBT_PREV(_name, _elm) _name##_RBT_PREV(_elm) -#define RBT_LEFT(_name, _elm) _name##_RBT_LEFT(_elm) -#define RBT_RIGHT(_name, _elm) _name##_RBT_RIGHT(_elm) -#define RBT_PARENT(_name, _elm) _name##_RBT_PARENT(_elm) -#define RBT_SET_LEFT(_name, _elm, _l) _name##_RBT_SET_LEFT(_elm, _l) -#define RBT_SET_RIGHT(_name, _elm, _r) _name##_RBT_SET_RIGHT(_elm, _r) -#define RBT_SET_PARENT(_name, _elm, _p) _name##_RBT_SET_PARENT(_elm, _p) -#define RBT_POISON(_name, _elm, _p) _name##_RBT_POISON(_elm, _p) -#define RBT_CHECK(_name, _elm, _p) _name##_RBT_CHECK(_elm, _p) - -#define RBT_FOREACH(_e, _name, _head) \ - for ((_e) = RBT_MIN(_name, (_head)); \ - (_e) != NULL; \ - (_e) = RBT_NEXT(_name, (_e))) - -#define RBT_FOREACH_SAFE(_e, _name, _head, _n) \ - for ((_e) = RBT_MIN(_name, (_head)); \ - (_e) != NULL && ((_n) = RBT_NEXT(_name, (_e)), 1); \ - (_e) = (_n)) - -#define RBT_FOREACH_REVERSE(_e, _name, _head) \ - for ((_e) = RBT_MAX(_name, (_head)); \ - (_e) != NULL; \ - (_e) = RBT_PREV(_name, (_e))) - -#define RBT_FOREACH_REVERSE_SAFE(_e, _name, _head, _n) \ - for ((_e) = RBT_MAX(_name, (_head)); \ - (_e) != NULL && ((_n) = RBT_PREV(_name, (_e)), 1); \ - (_e) = (_n)) - -#endif /* _SYS_TREE_H_ */ blob - 1b8ec6ca2b31e2f06cfed55fcc17f69e53f30573 (mode 644) blob + /dev/null --- compat/vis.c +++ /dev/null @@ -1,242 +0,0 @@ -/* $OpenBSD: vis.c,v 1.25 2015/09/13 11:32:51 guenther Exp $ */ -/*- - * Copyright (c) 1989, 1993 - * The Regents of the University of California. All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * 1. Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * 3. Neither the name of the University nor the names of its contributors - * may be used to endorse or promote products derived from this software - * without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND - * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS - * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) - * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT - * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY - * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF - * SUCH DAMAGE. - */ - -#include "compat.h" - -#include -#include -#include -#include -#include -#include - -#define isoctal(c) (((u_char)(c)) >= '0' && ((u_char)(c)) <= '7') -#define isvisible(c,flag) \ - (((c) == '\\' || (flag & VIS_ALL) == 0) && \ - (((u_int)(c) <= UCHAR_MAX && isascii((u_char)(c)) && \ - (((c) != '*' && (c) != '?' && (c) != '[' && (c) != '#') || \ - (flag & VIS_GLOB) == 0) && isgraph((u_char)(c))) || \ - ((flag & VIS_SP) == 0 && (c) == ' ') || \ - ((flag & VIS_TAB) == 0 && (c) == '\t') || \ - ((flag & VIS_NL) == 0 && (c) == '\n') || \ - ((flag & VIS_SAFE) && ((c) == '\b' || \ - (c) == '\007' || (c) == '\r' || \ - isgraph((u_char)(c)))))) - -/* - * vis - visually encode characters - */ -char * -vis(char *dst, int c, int flag, int nextc) -{ - if (isvisible(c, flag)) { - if ((c == '"' && (flag & VIS_DQ) != 0) || - (c == '\\' && (flag & VIS_NOSLASH) == 0)) - *dst++ = '\\'; - *dst++ = c; - *dst = '\0'; - return (dst); - } - - if (flag & VIS_CSTYLE) { - switch(c) { - case '\n': - *dst++ = '\\'; - *dst++ = 'n'; - goto done; - case '\r': - *dst++ = '\\'; - *dst++ = 'r'; - goto done; - case '\b': - *dst++ = '\\'; - *dst++ = 'b'; - goto done; - case '\a': - *dst++ = '\\'; - *dst++ = 'a'; - goto done; - case '\v': - *dst++ = '\\'; - *dst++ = 'v'; - goto done; - case '\t': - *dst++ = '\\'; - *dst++ = 't'; - goto done; - case '\f': - *dst++ = '\\'; - *dst++ = 'f'; - goto done; - case ' ': - *dst++ = '\\'; - *dst++ = 's'; - goto done; - case '\0': - *dst++ = '\\'; - *dst++ = '0'; - if (isoctal(nextc)) { - *dst++ = '0'; - *dst++ = '0'; - } - goto done; - } - } - if (((c & 0177) == ' ') || (flag & VIS_OCTAL) || - ((flag & VIS_GLOB) && (c == '*' || c == '?' || c == '[' || c == '#'))) { - *dst++ = '\\'; - *dst++ = ((u_char)c >> 6 & 07) + '0'; - *dst++ = ((u_char)c >> 3 & 07) + '0'; - *dst++ = ((u_char)c & 07) + '0'; - goto done; - } - if ((flag & VIS_NOSLASH) == 0) - *dst++ = '\\'; - if (c & 0200) { - c &= 0177; - *dst++ = 'M'; - } - if (iscntrl((u_char)c)) { - *dst++ = '^'; - if (c == 0177) - *dst++ = '?'; - else - *dst++ = c + '@'; - } else { - *dst++ = '-'; - *dst++ = c; - } -done: - *dst = '\0'; - return (dst); -} - -/* - * strvis, strnvis, strvisx - visually encode characters from src into dst - * - * Dst must be 4 times the size of src to account for possible - * expansion. The length of dst, not including the trailing NULL, - * is returned. - * - * Strnvis will write no more than siz-1 bytes (and will NULL terminate). - * The number of bytes needed to fully encode the string is returned. - * - * Strvisx encodes exactly len bytes from src into dst. - * This is useful for encoding a block of data. - */ -int -strvis(char *dst, const char *src, int flag) -{ - char c; - char *start; - - for (start = dst; (c = *src);) - dst = vis(dst, c, flag, *++src); - *dst = '\0'; - return (dst - start); -} - -int -strnvis(char *dst, const char *src, size_t siz, int flag) -{ - char *start, *end; - char tbuf[5]; - int c, i; - - i = 0; - for (start = dst, end = start + siz - 1; (c = *src) && dst < end; ) { - if (isvisible(c, flag)) { - if ((c == '"' && (flag & VIS_DQ) != 0) || - (c == '\\' && (flag & VIS_NOSLASH) == 0)) { - /* need space for the extra '\\' */ - if (dst + 1 >= end) { - i = 2; - break; - } - *dst++ = '\\'; - } - i = 1; - *dst++ = c; - src++; - } else { - i = vis(tbuf, c, flag, *++src) - tbuf; - if (dst + i <= end) { - memcpy(dst, tbuf, i); - dst += i; - } else { - src--; - break; - } - } - } - if (siz > 0) - *dst = '\0'; - if (dst + i > end) { - /* adjust return value for truncation */ - while ((c = *src)) - dst += vis(tbuf, c, flag, *++src) - tbuf; - } - return (dst - start); -} - -int -stravis(char **outp, const char *src, int flag) -{ - char *buf; - int len, serrno; - - buf = reallocarray(NULL, 4, strlen(src) + 1); - if (buf == NULL) - return -1; - len = strvis(buf, src, flag); - serrno = errno; - *outp = realloc(buf, len + 1); - if (*outp == NULL) { - *outp = buf; - errno = serrno; - } - return (len); -} - -int -strvisx(char *dst, const char *src, size_t len, int flag) -{ - char c; - char *start; - - for (start = dst; len > 1; len--) { - c = *src; - dst = vis(dst, c, flag, *++src); - } - if (len) - dst = vis(dst, *src, flag, '\0'); - *dst = '\0'; - return (dst - start); -} blob - 59b7d6b81777229668dfbb0bb1aaaca8e126eb8c (mode 644) blob + /dev/null --- compat/vis.h +++ /dev/null @@ -1,90 +0,0 @@ -/* $OpenBSD: vis.h,v 1.15 2015/07/20 01:52:27 millert Exp $ */ -/* $NetBSD: vis.h,v 1.4 1994/10/26 00:56:41 cgd Exp $ */ - -/*- - * Copyright (c) 1990 The Regents of the University of California. - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * 1. Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * 3. Neither the name of the University nor the names of its contributors - * may be used to endorse or promote products derived from this software - * without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND - * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS - * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) - * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT - * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY - * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF - * SUCH DAMAGE. - * - * @(#)vis.h 5.9 (Berkeley) 4/3/91 - */ - -#ifndef _VIS_H_ -#define _VIS_H_ - -/* - * to select alternate encoding format - */ -#define VIS_OCTAL 0x01 /* use octal \ddd format */ -#define VIS_CSTYLE 0x02 /* use \[nrft0..] where appropriate */ - -/* - * to alter set of characters encoded (default is to encode all - * non-graphic except space, tab, and newline). - */ -#define VIS_SP 0x04 /* also encode space */ -#define VIS_TAB 0x08 /* also encode tab */ -#define VIS_NL 0x10 /* also encode newline */ -#define VIS_WHITE (VIS_SP | VIS_TAB | VIS_NL) -#define VIS_SAFE 0x20 /* only encode "unsafe" characters */ -#define VIS_DQ 0x200 /* backslash-escape double quotes */ -#define VIS_ALL 0x400 /* encode all characters */ - -/* - * other - */ -#define VIS_NOSLASH 0x40 /* inhibit printing '\' */ -#define VIS_GLOB 0x100 /* encode glob(3) magics and '#' */ - -/* - * unvis return codes - */ -#define UNVIS_VALID 1 /* character valid */ -#define UNVIS_VALIDPUSH 2 /* character valid, push back passed char */ -#define UNVIS_NOCHAR 3 /* valid sequence, no character produced */ -#define UNVIS_SYNBAD -1 /* unrecognized escape sequence */ -#define UNVIS_ERROR -2 /* decoder in unknown state (unrecoverable) */ - -/* - * unvis flags - */ -#define UNVIS_END 1 /* no more characters */ - -#include - -char *vis(char *, int, int, int); -int strvis(char *, const char *, int); -int stravis(char **, const char *, int); -int strnvis(char *, const char *, size_t, int) - __attribute__ ((__bounded__(__string__,1,3))); -int strvisx(char *, const char *, size_t, int) - __attribute__ ((__bounded__(__string__,1,3))); -int strunvis(char *, const char *); -int unvis(char *, char, int *, int); -ssize_t strnunvis(char *, const char *, size_t) - __attribute__ ((__bounded__(__string__,1,3))); - -#endif /* !_VIS_H_ */ blob - /dev/null blob + 7de065f7200b6f32f7f8e5fd9ada3f4e07f51ad2 (mode 644) --- /dev/null +++ kamirepl/Makefile @@ -0,0 +1,17 @@ +.PATH:${.CURDIR}/../lib + +.include "../kamid-version.mk" + +PROG= kamirepl +SRCS= 9pclib.c kamirepl.c log.c utils.c + +CPPFLAGS= -I${.CURDIR}/../lib -I${.CURDIR} + +LDADD+= -levent -lutil -ltls +DPADD+= ${LIBEVENT} ${LIBUTIL} ${LIBTLS} + +.if ${KAMID_RELEASE} != Yes +NOMAN= Yes +.endif + +.include blob - /dev/null blob + fb0c86530732d462858c57359e8a544d794d6b06 (mode 644) --- /dev/null +++ kamirepl/kamirepl.1 @@ -0,0 +1,152 @@ +.\" Copyright (c) 2021 Omar Polo +.\" +.\" Permission to use, copy, modify, and distribute this software for any +.\" purpose with or without fee is hereby granted, provided that the above +.\" copyright notice and this permission notice appear in all copies. +.\" +.\" THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +.\" WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +.\" MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +.\" ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +.\" WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +.\" ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +.\" OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +.\" +.Dd $Mdocdate: December 16 2021 $ +.Dt KAMIREPL 1 +.Os +.Sh NAME +.Nm kamirepl +.Nd 9p repl client +.Sh SYNOPSIS +.Nm +.Op Fl chv +.Op Fl C Ar cert +.Op Fl H Ar host +.Op Fl K Ar key +.Op Fl P Ar port +.Sh DESCRIPTION +.Nm +is a +.Xr 9p 7 +repl client. +.Pp +The optinos are as follows: +.Bl -tag -width tenletters +.It Fl C Ar cert +Path to the TLS client certificate to use. +.It Fl c +Use TLS for the connection. +.Fl C +and +.Fl K +are mandatory if used. +.It Fl H Ar host +Hostname of the file server. +.It Fl h +Display usage and exit. +.It Fl K Ar key +Path to the TLS client certificate private key. +.It Fl P Ar port +Port number to connect to. +.It Fl v +Verbose logging. +.El +.Pp +The interactive commands are +.Bl -tag -width Ds +.It Ic version Op Ar version-string +.Ar version-string +is +.Dq 9P2000 +by default. +.It Ic attach Ar fid Ar uname Ar aname +Request the file server to attach the file tree identified by +.Ar aname +to the specified +.Ar fid +number. +.Ar aname +is the identifier for the user. +The afid used is implicitly NOFID. +.It Ic clunk Ar fid +Closes +.Ar fid. +.It Ic flush Ar oldtag +Require the server to flush +.Ar oldtag . +.It Ic walk Ar fid Ar newfid Ar wnames... +Do a walk from +.Ar fid +following +.Ar wnames +component and associating the reached file to +.Ar newfid . +.It Ic open Ar fid Ar mode Op Ar flag +Prepare +.Ar fid +for I/O. +.Ar mode +can be one of +.Sq read +or +.Sq r , +.Sq write +or +.Sq w , +.Sq readwrite +or +.Sq rdwr . +Optionally, +.Ar flag +can be on of +.Sq trunc +to truncate the file or +.Sq rclose +to remove the file upon +.Ic clunk . +.It Ic create Ar fid Ar name Ar perm Ar mode +Create the file +.Ar name +and open it with +.Ar mode +as the given +.Ar fid. +.Ar perm +should be used to select the permissions of the file, but is currently +unused. +.It Ic read Ar fid Ar offset Ar count +Issue a read request for the given +.Ar fid , +which must have been prepared for I/O with +.Ic open , +at +.Ar offset +and for +.Ar count +bytes. +.It Ic write Ar fid Ar offset Ar content +Writes +.Ar content +to +.Ar fid +starting at +.Ar offset . +.It Ic remove Ar fid +Delete the file identified by +.Ar fid +and close it. +Even in case of error, +.Ar fid +is clunked. +.El +.Sh SEE ALSO +.Xr kamiftp 1 +.Xr 9p 7 +.Xr kamid 8 +.Sh AUTHORS +.An -nosplit +The +.Nm +utility was written by +.An Omar Polo Aq Mt op@omarpolo.com . blob - /dev/null blob + d811e395f1890a7cf094ee8612574ff27b3e5763 (mode 644) --- /dev/null +++ kamirepl/kamirepl.c @@ -0,0 +1,1074 @@ +/* + * Copyright (c) 2021 Omar Polo + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include +#include + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "9pclib.h" +#include "kami.h" +#include "log.h" +#include "utils.h" + +#define DEBUG_PACKETS 0 + +#define PROMPT "=% " + +/* flags */ +int verbose; +int tls; +const char *keypath; +const char *crtpath; +const char *host; +const char *port; + +/* state */ +struct tls_config *tlsconf; +struct tls *ctx; +struct bufferevent *bev, *inbev; + +static void __dead usage(int); + +static void sig_handler(int, short, void *); + +static int openconn(void); +static void mark_nonblock(int); + +static void tls_readcb(int, short, void *); +static void tls_writecb(int, short, void *); + +static void client_read(struct bufferevent *, void *); +static void client_write(struct bufferevent *, void *); +static void client_error(struct bufferevent *, short, void *); + +static void repl_read(struct bufferevent *, void *); +static void repl_error(struct bufferevent *, short, void *); + +static void excmd_version(const char **, int); +static void excmd_attach(const char **, int); +static void excmd_clunk(const char **, int); +static void excmd_flush(const char **, int); +static void excmd_walk(const char ** , int); +static void excmd_open(const char ** , int); +static void excmd_create(const char ** , int); +static void excmd_read(const char ** , int); +static void excmd_write(const char **, int); +static void excmd(const char **, int); + +static void pp_qid(const uint8_t *, uint32_t); +static void pp_msg(uint32_t, uint8_t, uint16_t, const uint8_t *); +static void handle_9p(const uint8_t *, size_t); +static void clr(void); +static void prompt(void); + +static void __dead +usage(int ret) +{ + fprintf(stderr, + "usage: %s [-chv] [-C crt] [-K key] [-H host] [-P port]\n", + getprogname()); + fprintf(stderr, "kamid suite version " KAMID_VERSION "\n"); + exit(ret); +} + +static void +sig_handler(int sig, short event, void *d) +{ + /* + * Normal signal handler rules don't apply because libevent + * decouples for us. + */ + + switch (sig) { + case SIGINT: + case SIGTERM: + clr(); + log_warnx("Shutting down..."); + event_loopbreak(); + return; + default: + fatalx("unexpected signal %d", sig); + } +} + +static int +openconn(void) +{ + struct addrinfo hints, *res, *res0; + int error; + int save_errno; + int s; + const char *cause = NULL; + + memset(&hints, 0, sizeof(hints)); + hints.ai_family = AF_UNSPEC; + hints.ai_socktype = SOCK_STREAM; + if ((error = getaddrinfo(host, port, &hints, &res0))) { + warnx("%s", gai_strerror(error)); + return -1; + } + + s = -1; + for (res = res0; res; res = res->ai_next) { + s = socket(res->ai_family, res->ai_socktype, + res->ai_protocol); + if (s == -1) { + cause = "socket"; + continue; + } + + if (connect(s, res->ai_addr, res->ai_addrlen) == -1) { + cause = "connect"; + save_errno = errno; + close(s); + errno = save_errno; + s = -1; + continue; + } + + break; + } + + freeaddrinfo(res0); + + if (s == -1) + warn("%s", cause); + + return s; +} + +static void +mark_nonblock(int fd) +{ + int flags; + + if ((flags = fcntl(fd, F_GETFL)) == -1) + fatal("fcntl(F_GETFL)"); + if (fcntl(fd, F_SETFL, flags | O_NONBLOCK) == -1) + fatal("fcntl(F_SETFL)"); +} + +static void +tls_readcb(int fd, short event, void *d) +{ + struct bufferevent *bufev = d; + char buf[IBUF_READ_SIZE]; + int what = EVBUFFER_READ; + int howmuch = IBUF_READ_SIZE; + ssize_t ret; + size_t len; + + if (event == EV_TIMEOUT) { + what |= EVBUFFER_TIMEOUT; + goto err; + } + + if (bufev->wm_read.high != 0) + howmuch = MIN(sizeof(buf), bufev->wm_read.high); + + switch (ret = tls_read(ctx, buf, howmuch)) { + case TLS_WANT_POLLIN: + case TLS_WANT_POLLOUT: + goto retry; + case -1: + what |= EVBUFFER_ERROR; + goto err; + } + len = ret; + + if (len == 0) { + what |= EVBUFFER_EOF; + goto err; + } + + if (evbuffer_add(bufev->input, buf, len) == -1) { + what |= EVBUFFER_ERROR; + goto err; + } + + event_add(&bufev->ev_read, NULL); + + len = EVBUFFER_LENGTH(bufev->input); + if (bufev->wm_read.low != 0 && len < bufev->wm_read.low) + return; + if (bufev->readcb != NULL) + (*bufev->readcb)(bufev, bufev->cbarg); + return; + +retry: + event_add(&bufev->ev_read, NULL); + return; + +err: + (*bufev->errorcb)(bufev, what, bufev->cbarg); +} + +static void +tls_writecb(int fd, short event, void *d) +{ + struct bufferevent *bufev = d; + ssize_t ret; + size_t len; + short what = EVBUFFER_WRITE; + void *data; + + if (event == EV_TIMEOUT) { + what |= EVBUFFER_TIMEOUT; + goto err; + } + + len = EVBUFFER_LENGTH(bufev->output); + if (len != 0) { + data = EVBUFFER_DATA(bufev->output); + +#if DEBUG_PACKETS + hexdump("outgoing msg", data, len); +#endif + + switch (ret = tls_write(ctx, data, len)) { + case TLS_WANT_POLLIN: + case TLS_WANT_POLLOUT: + goto retry; + case -1: + what |= EVBUFFER_ERROR; + goto err; + } + evbuffer_drain(bufev->output, ret); + } + + if (EVBUFFER_LENGTH(bufev->output) != 0) + event_add(&bufev->ev_write, NULL); + + if (bufev->writecb != NULL && + EVBUFFER_LENGTH(bufev->output) <= bufev->wm_write.low) + (*bufev->writecb)(bufev, bufev->cbarg); + return; + +retry: + event_add(&bufev->ev_write, NULL); + return; +err: + (*bufev->errorcb)(bufev, what, bufev->cbarg); +} + +static void +client_read(struct bufferevent *bev, void *d) +{ + struct evbuffer *src = EVBUFFER_INPUT(bev); + uint32_t len; + uint8_t *data; + + for (;;) { + if (EVBUFFER_LENGTH(src) < sizeof(len)) + return; + + data = EVBUFFER_DATA(src); + + memcpy(&len, data, sizeof(len)); + len = le32toh(len); + + if (len < HEADERSIZE) + fatal("incoming message is too small! (%d bytes)", + len); + + if (len > EVBUFFER_LENGTH(src)) + return; + +#if DEBUG_PACKETS + hexdump("incoming msg", data, len); +#endif + + handle_9p(data, len); + evbuffer_drain(src, len); + } +} + +static void +client_write(struct bufferevent *bev, void *data) +{ + return; /* nothing to do */ +} + +static void +client_error(struct bufferevent *bev, short err, void *data) +{ + if (err & EVBUFFER_ERROR) + fatal("buffer event error"); + + if (err & EVBUFFER_EOF) { + clr(); + log_info("EOF"); + event_loopbreak(); + return; + } + + clr(); + log_warnx("unknown event error"); + event_loopbreak(); +} + +static void +repl_read(struct bufferevent *bev, void *d) +{ + size_t len; + int argc; + const char *argv[10], **ap; + char *line; + + line = evbuffer_readln(bev->input, &len, EVBUFFER_EOL_LF); + if (line == NULL) + return; + + for (argc = 0, ap = argv; ap < &argv[9] && + (*ap = strsep(&line, " \t")) != NULL;) { + if (**ap != '\0') + ap++, argc++; + } + + clr(); + excmd(argv, argc); + prompt(); + + free(line); +} + +static void +repl_error(struct bufferevent *bev, short error, void *d) +{ + fatalx("an error occurred"); +} + +static inline void +do_send(void) +{ + bufferevent_write_buffer(bev, evb); +} + +/* version [version-str] */ +static void +excmd_version(const char **argv, int argc) +{ + const char *s; + + s = VERSION9P; + if (argc == 2) + s = argv[1]; + + tversion(s, MSIZE9P); + do_send(); +} + +/* attach fid uname aname */ +static void +excmd_attach(const char **argv, int argc) +{ + uint32_t fid; + const char *errstr; + + if (argc != 4) + goto usage; + + fid = strtonum(argv[1], 0, UINT32_MAX, &errstr); + if (errstr != NULL) { + log_warnx("fid is %s: %s", errstr, argv[1]); + return; + } + + tattach(fid, NOFID, argv[2], argv[3]); + do_send(); + return; + +usage: + log_warnx("usage: attach fid uname aname"); +} + +/* clunk fid */ +static void +excmd_clunk(const char **argv, int argc) +{ + uint32_t fid; + const char *errstr; + + if (argc != 2) + goto usage; + + fid = strtonum(argv[1], 0, UINT32_MAX, &errstr); + if (errstr != NULL) { + log_warnx("fid is %s: %s", errstr, argv[1]); + return; + } + + tclunk(fid); + do_send(); + return; + +usage: + log_warnx("usage: clunk fid"); +} + +/* flush oldtag */ +static void +excmd_flush(const char **argv, int argc) +{ + uint16_t oldtag; + const char *errstr; + + if (argc != 2) + goto usage; + + oldtag = strtonum(argv[1], 0, UINT16_MAX, &errstr); + if (errstr != NULL) { + log_warnx("oldtag is %s: %s", errstr, argv[1]); + return; + } + + tflush(oldtag); + do_send(); + return; + +usage: + log_warnx("usage: flush oldtag"); +} + +/* walk fid newfid wnames... */ +static void +excmd_walk(const char **argv, int argc) +{ + uint32_t fid, newfid; + const char *errstr; + + if (argc < 3) + goto usage; + + fid = strtonum(argv[1], 0, UINT32_MAX, &errstr); + if (errstr != NULL) { + log_warnx("fid is %s: %s", errstr, argv[1]); + return; + } + + newfid = strtonum(argv[2], 0, UINT32_MAX, &errstr); + if (errstr != NULL) { + log_warnx("newfid is %s: %s", errstr, argv[1]); + return; + } + + twalk(fid, newfid, argv + 3, argc - 3); + do_send(); + return; + +usage: + log_warnx("usage: walk fid newfid wnames..."); +} + +/* open fid mode [flag] */ +static void +excmd_open(const char **argv, int argc) +{ + const char *errstr; + uint32_t fid; + uint8_t mode = 0; + + if (argc != 3 && argc != 4) + goto usage; + + fid = strtonum(argv[1], 0, UINT32_MAX, &errstr); + if (errstr != NULL) { + log_warnx("fid is %s: %s", errstr, argv[1]); + return; + } + + /* parse mode */ + if (!strcmp("read", argv[2]) || !strcmp("r", argv[2])) + mode = KOREAD; + else if (!strcmp("write", argv[2]) || !strcmp("w", argv[2])) + mode = KOWRITE; + else if (!strcmp("readwrite", argv[2]) || !strcmp("rw", argv[2])) + mode = KORDWR; + else { + log_warnx("invalid mode %s", argv[2]); + return; + } + + /* parse flag */ + if (argv[3] != NULL) { + if (!strcmp("trunc", argv[3])) + mode |= KOTRUNC; + else if (!strcmp("rclose", argv[3])) + mode |= KORCLOSE; + else { + log_warnx("invalid flag %s", argv[3]); + return; + } + } + + topen(fid, mode); + do_send(); + return; + +usage: + log_warnx("usage: open fid mode [flag]"); +} + +/* create fid path perm mode */ +static void +excmd_create(const char **argv, int argc) +{ + const char *errstr; + uint32_t fid; + uint8_t mode = 0; + + if (argc != 5) + goto usage; + + fid = strtonum(argv[1], 0, UINT32_MAX, &errstr); + if (errstr != NULL) { + log_warnx("fid is %s: %s", errstr, argv[1]); + return; + } + + /* parse mode */ + if (!strcmp("write", argv[4]) || !strcmp("w", argv[4])) + mode = KOWRITE; + else if (!strcmp("readwrite", argv[4]) || !strcmp("rw", argv[4])) + mode = KORDWR; + else { + log_warnx("invalid mode %s for create", argv[4]); + return; + } + + tcreate(fid, argv[2], 0, mode); + do_send(); + return; + +usage: + log_warnx("usage: create fid path perm mode ; perm is unused"); +} + + +/* read fid offset count */ +static void +excmd_read(const char **argv, int argc) +{ + uint64_t off; + uint32_t fid, count; + const char *errstr; + + if (argc != 4) + goto usage; + + fid = strtonum(argv[1], 0, UINT32_MAX, &errstr); + if (errstr != NULL) { + log_warnx("fid is %s: %s", errstr, argv[1]); + return; + } + + /* should really be UNT64_MAX but it fails... */ + off = strtonum(argv[2], -1, UINT32_MAX, &errstr); + if (errstr != NULL) { + log_warnx("offset is %s: %s", errstr, argv[2]); + return; + } + + count = strtonum(argv[3], 0, UINT32_MAX, &errstr); + if (errstr != NULL) { + log_warnx("count is %s: %s", errstr, argv[3]); + return; + } + + tread(fid, off, count); + do_send(); + return; + +usage: + log_warnx("usage: read fid offset count"); +} + +/* write fid offset content */ +static void +excmd_write(const char **argv, int argc) +{ + uint64_t off; + uint32_t fid, count; + const char *errstr; + + if (argc != 4) + goto usage; + + fid = strtonum(argv[1], 0, UINT32_MAX, &errstr); + if (errstr != NULL) { + log_warnx("fid is %s: %s", errstr, argv[1]); + return; + } + + /* should really be UINT64_MAX but... */ + off = strtonum(argv[2], 0, UINT32_MAX, &errstr); + if (errstr != NULL) { + log_warnx("offset is %s: %s", errstr, argv[2]); + return; + } + + count = strlen(argv[3]); + twrite(fid, off, argv[3], count); + do_send(); + return; + +usage: + log_warnx("usage: write fid offset content"); +} + +/* remove fid */ +static void +excmd_remove(const char **argv, int argc) +{ + const char *errstr; + uint32_t fid; + + if (argc != 2) + goto usage; + + fid = strtonum(argv[1], 0, UINT32_MAX, &errstr); + if (errstr != NULL) { + log_warnx("fid is %s: %s", errstr, argv[1]); + return; + } + + tremove(fid); + do_send(); + return; + +usage: + log_warnx("usage: remove fid"); +} + +static void +excmd(const char **argv, int argc) +{ + struct cmd { + const char *name; + void (*fn)(const char **, int); + } cmds[] = { + {"version", excmd_version}, + {"attach", excmd_attach}, + {"clunk", excmd_clunk}, + {"flush", excmd_flush}, + {"walk", excmd_walk}, + {"open", excmd_open}, + {"create", excmd_create}, + {"read", excmd_read}, + {"write", excmd_write}, + /* TODO: stat */ + {"remove", excmd_remove}, + }; + size_t i; + + if (argc == 0) + return; + + for (i = 0; i < sizeof(cmds)/sizeof(cmds[0]); ++i) { + if (!strcmp(cmds[i].name, argv[0])) { + cmds[i].fn(argv, argc); + return; + } + } + + log_warnx("Unknown command %s", *argv); +} + +static void +pp_qid(const uint8_t *d, uint32_t len) +{ + uint64_t path; + uint32_t vers; + uint8_t type; + + if (len < 13) { + printf("invalid"); + return; + } + + type = *d++; + + memcpy(&vers, d, sizeof(vers)); + d += sizeof(vers); + vers = le64toh(vers); + + memcpy(&path, d, sizeof(path)); + d += sizeof(path); + path = le64toh(path); + + printf("qid{path=%"PRIu64" version=%"PRIu32" type=0x%x\"%s\"}", + path, vers, type, pp_qid_type(type)); +} + +static void +pp_msg(uint32_t len, uint8_t type, uint16_t tag, const uint8_t *d) +{ + uint32_t msize, iounit, count; + uint16_t slen; + char *v; + + printf("len=%"PRIu32" type=%d[%s] tag=0x%x[%d] ", len, + type, pp_msg_type(type), tag, tag); + + len -= HEADERSIZE; + + switch (type) { + case Rversion: + if (len < 6) { + printf("invalid: not enough space for msize " + "and version provided."); + break; + } + + memcpy(&msize, d, sizeof(msize)); + d += sizeof(msize); + len -= sizeof(msize); + msize = le32toh(msize); + + memcpy(&slen, d, sizeof(slen)); + d += sizeof(slen); + len -= sizeof(slen); + slen = le16toh(slen); + + if (len != slen) { + printf("invalid: version string length doesn't " + "match. Got %d; want %d", slen, len); + break; + } + + printf("msize=%"PRIu32" version[%"PRIu16"]=\"", + msize, slen); + fwrite(d, 1, slen, stdout); + printf("\""); + + break; + + case Rattach: + pp_qid(d, len); + break; + + case Rclunk: + case Rflush: + case Rremove: + if (len != 0) + printf("invalid %s: %"PRIu32" extra bytes", + pp_msg_type(type), len); + break; + + case Rwalk: + if (len < 2) { + printf("invaild Rwalk: less than two bytes (%d)", + (int)len); + break; + } + + memcpy(&slen, d, sizeof(slen)); + d += sizeof(slen); + len -= sizeof(slen); + slen = le16toh(slen); + + if (len != QIDSIZE * slen) { + printf("invalid Rwalk: wanted %d bytes for %d qids " + "but got %"PRIu32" bytes instead", + QIDSIZE*slen, slen, len); + break; + } + + printf("nwqid=%"PRIu16, slen); + + for (; slen != 0; slen--) { + printf(" "); + pp_qid(d, len); + d += QIDSIZE; + len -= QIDSIZE; + } + + break; + + case Ropen: + case Rcreate: + if (len != QIDSIZE + 4) { + printf("invalid %s: expected %d bytes; " + "got %u\n", pp_msg_type(type), QIDSIZE + 4, len); + break; + } + + pp_qid(d, len); + d += QIDSIZE; + len -= QIDSIZE; + + memcpy(&iounit, d, sizeof(iounit)); + d += sizeof(iounit); + len -= sizeof(iounit); + iounit = le32toh(iounit); + printf(" iounit=%"PRIu32, iounit); + break; + + case Rread: + if (len < sizeof(count)) { + printf("invalid Rread: expected %zu bytes at least; " + "got %u\n", sizeof(count), len); + break; + } + + memcpy(&count, d, sizeof(count)); + d += sizeof(count); + len -= sizeof(count); + count = le32toh(count); + + if (len != count) { + printf("invalid Rread: expected %d data bytes; " + "got %u\n", count, len); + break; + } + + /* allocates three extra bytes, oh well... */ + if ((v = calloc(count + 1, 4)) == NULL) + fatal("calloc"); + strvisx(v, d, count, VIS_SAFE | VIS_TAB | VIS_NL | VIS_CSTYLE); + printf("data=%s", v); + free(v); + + break; + + case Rwrite: + if (len != sizeof(count)) { + printf("invalid Rwrite: expected %zu data bytes; " + "got %u\n", sizeof(count), len); + break; + } + + memcpy(&count, d, sizeof(count)); + d += sizeof(count); + len -= sizeof(count); + count = le32toh(count); + + printf("count=%d", count); + break; + + case Rerror: + memcpy(&slen, d, sizeof(slen)); + d += sizeof(slen); + len -= sizeof(slen); + slen = le16toh(slen); + + if (slen != len) { + printf("invalid: error string length doesn't " + "match. Got %d; want %d", slen, len); + break; + } + + printf("error=\""); + fwrite(d, 1, slen, stdout); + printf("\""); + + break; + + default: + if ((v = calloc(len + 1, 4)) == NULL) + fatal("calloc"); + strvisx(v, d, len, VIS_SAFE | VIS_TAB | VIS_NL | VIS_CSTYLE); + printf("body=%s", v); + free(v); + } + + printf("\n"); +} + +static void +handle_9p(const uint8_t *data, size_t size) +{ + uint32_t len; + uint16_t tag; + uint8_t type; + + assert(size >= HEADERSIZE); + + memcpy(&len, data, sizeof(len)); + data += sizeof(len); + + memcpy(&type, data, sizeof(type)); + data += sizeof(type); + + memcpy(&tag, data, sizeof(tag)); + data += sizeof(tag); + + len = le32toh(len); + /* type is one byte long, no endianness issues */ + tag = le16toh(tag); + + clr(); + pp_msg(len, type, tag, data); + prompt(); +} + +static void +clr(void) +{ + printf("\r"); + fflush(stdout); +} + +static void +prompt(void) +{ + printf("%s", PROMPT); + fflush(stdout); +} + +int +main(int argc, char **argv) +{ + int ch, sock, handshake; + struct event ev_sigint, ev_sigterm; + + signal(SIGPIPE, SIG_IGN); + + while ((ch = getopt(argc, argv, "C:cH:hK:P:v")) != -1) { + switch (ch) { + case 'C': + crtpath = optarg; + break; + case 'c': + tls = 1; + break; + case 'H': + host = optarg; + break; + case 'h': + usage(0); + break; + case 'K': + keypath = optarg; + break; + case 'P': + port = optarg; + break; + case 'v': + verbose = 1; + break; + default: + usage(1); + } + } + + if (host == NULL) + host = "localhost"; + if (port == NULL) + port = "1337"; + + argc -= optind; + argv += optind; + + if (argc != 0) + usage(1); + /* if (!tls || (crtpath != NULL || keypath != NULL)) */ + /* usage(1); */ + if (!tls) + errx(1, "must enable tls (for now)"); + + log_init(1, LOG_DAEMON); + log_setverbose(verbose); + log_procinit(getprogname()); + + if ((tlsconf = tls_config_new()) == NULL) + fatalx("tls_config_new"); + tls_config_insecure_noverifycert(tlsconf); + tls_config_insecure_noverifyname(tlsconf); + if (tls_config_set_keypair_file(tlsconf, crtpath, keypath) == -1) + fatalx("can't load certs (%s, %s)", crtpath, keypath); + + if ((ctx = tls_client()) == NULL) + fatal("tls_client"); + if (tls_configure(ctx, tlsconf) == -1) + fatalx("tls_configure: %s", tls_error(ctx)); + + log_info("connecting to %s:%s...", host, port); + + if ((sock = openconn()) == -1) + fatalx("can't connect to %s:%s", host, port); + + if (tls_connect_socket(ctx, sock, host) == -1) + fatalx("tls_connect_socket: %s", tls_error(ctx)); + + for (handshake = 0; !handshake;) { + switch (tls_handshake(ctx)) { + case -1: + fatalx("tls_handshake: %s", tls_error(ctx)); + case 0: + handshake = 1; + break; + } + } + + log_info("connected!"); + + mark_nonblock(sock); + + event_init(); + + /* initialize global evb */ + if ((evb = evbuffer_new()) == NULL) + fatal("evbuffer_new"); + + signal_set(&ev_sigint, SIGINT, sig_handler, NULL); + signal_set(&ev_sigterm, SIGINT, sig_handler, NULL); + + signal_add(&ev_sigint, NULL); + signal_add(&ev_sigterm, NULL); + + bev = bufferevent_new(sock, client_read, client_write, client_error, + NULL); + if (bev == NULL) + fatal("bufferevent_new"); + + /* setup tls/io */ + event_set(&bev->ev_read, sock, EV_READ, tls_readcb, bev); + event_set(&bev->ev_write, sock, EV_WRITE, tls_writecb, bev); + + bufferevent_enable(bev, EV_READ|EV_WRITE); + + mark_nonblock(0); + inbev = bufferevent_new(0, repl_read, NULL, repl_error, NULL); + bufferevent_enable(inbev, EV_READ); + + prompt(); + event_dispatch(); + + bufferevent_free(bev); + tls_free(ctx); + tls_config_free(tlsconf); + close(sock); + + return 0; +} blob - 84b60242793c3a21ddc50fae638a208fb4f5ee29 (mode 644) blob + /dev/null --- compat.h +++ /dev/null @@ -1,144 +0,0 @@ -/* - * Copyright (c) 2021 Omar Polo - * - * Permission to use, copy, modify, and distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. - * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - */ - -#ifndef COMPAT_H -#define COMPAT_H - -#include "config.h" - -#include -#include - -#include -#include -#include - -#ifndef __dead -#define __dead __attribute__((noreturn)) -#endif - -#if HAVE_EVENT2 -# include -# include -# include -# include -# include -# include -# include -# include -#else -# include -#endif - -#ifdef HAVE_QUEUE_H -# include -#else -# include "compat/queue.h" -#endif - -#ifdef HAVE_SYS_TREE_H -# include -#else -# include "compat/tree.h" -#endif - -#ifdef HAVE_LIBUTIL -# include -# include -# include -#else -# include "compat/imsg.h" -# include "compat/ohash.h" -# define FMT_SCALED_STRSIZE 7 /* minus sign, 4 digits, suffix, NUL */ -int fmt_scaled(long long, char *); -#endif - -#ifndef HAVE_ARC4RANDOM -# include -uint32_t arc4random(void); -void arc4random_buf(void *, size_t); -uint32_t arc4random_uniform(uint32_t); -#endif - -#ifndef HAVE_ASPRINTF -int asprintf(char **, const char *, ...); -int vasprintf(char **, const char *, ...); -#endif - -#ifndef HAVE_ERR -void err(int, const char *, ...); -void errx(int, const char *, ...); -void warn(int, const char *, ...); -void warnx(int, const char *, ...); -#else -#include -#endif - -#ifndef FREEZERO -void freezero(void *, size_t); -#endif - -#ifndef HAVE_GETDTABLECOUNT -int getdtablecount(void); -#endif - -#ifndef HAVE_GETDTABLESIZE -int getdtablesize(void); -#endif - -#ifndef HAVE_GETPROGNAME -const char *getprogname(void); -#endif - -#ifndef HAVE_MEMMEM -void *memmem(const void *, size_t, const void *, size_t); -#endif - -#ifndef HAVE_RECALLOCARRAY -void *recallocarray(void *, size_t, size_t, size_t); -#endif - -#ifndef HAVE_SETPROCTITLE -void setproctitle(const char *, ...); -#endif - -#ifndef HAVE_SETPROGNAME -void setprogname(const char *); -#endif - -#ifndef HAVE_STRLCAT -size_t strlcat(char *, const char *, size_t); -#endif - -#ifndef HAVE_STRLCPY -size_t strlcpy(char *, const char *, size_t); -#endif - -#ifndef HAVE_STRSEP -char *strsep(char **, const char *); -#endif - -#ifndef HAVE_STRTONUM -long long strtonum(const char *, long long, long long, const char **); -#endif - -#ifdef HAVE_VIS -# include -#else -# include "compat/vis.h" -#endif - -#endif /* COMPAT_H */ blob - /dev/null blob + 41698f3563582a33b0dcd1b46aac1564ca3e56c0 (mode 644) --- /dev/null +++ lib/9pclib.c @@ -0,0 +1,251 @@ +/* + * Copyright (c) 2021 Omar Polo + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include +#include +#include +#include + +#include "9pclib.h" +#include "kami.h" +#include "log.h" +#include "utils.h" + +uint16_t iota_tag; + +struct evbuffer *evb; + +void +write_hdr(uint32_t len, uint8_t type, uint16_t tag) +{ + len += HEADERSIZE; + + log_debug("enqueuing a packet; len=%"PRIu32" type=%d[%s] tag=%d", + len, type, pp_msg_type(type), tag); + + len = htole32(len); + /* type is one byte, no endiannes issues */ + tag = htole16(tag); + + evbuffer_add(evb, &len, sizeof(len)); + evbuffer_add(evb, &type, sizeof(type)); + evbuffer_add(evb, &tag, sizeof(tag)); +} + +void +write_hdr_auto(uint32_t len, uint8_t type) +{ + if (++iota_tag == NOTAG) + ++iota_tag; + write_hdr(len, type, iota_tag); +} + +void +write_str(uint16_t len, const char *str) +{ + uint16_t l = len; + + len = htole16(len); + evbuffer_add(evb, &len, sizeof(len)); + evbuffer_add(evb, str, l); +} + +void +write_str_auto(const char *str) +{ + write_str(strlen(str), str); +} + +void +write_buf(const void *d, uint32_t len) +{ + write_32(len); + evbuffer_add(evb, d, len); +} + +void +write_64(uint64_t x) +{ + x = htole64(x); + evbuffer_add(evb, &x, sizeof(x)); +} + +void +write_32(uint32_t fid) +{ + fid = htole32(fid); + evbuffer_add(evb, &fid, sizeof(fid)); +} + +void +write_16(uint16_t tag) +{ + tag = htole16(tag); + evbuffer_add(evb, &tag, sizeof(tag)); +} + +void +write_8(uint8_t x) +{ + evbuffer_add(evb, &x, sizeof(x)); +} + + + +void +tversion(const char *v, uint32_t msize) +{ + uint32_t len; + uint16_t sl; + + sl = strlen(v); + + /* msize[4] version[s] */ + len = sizeof(msize) + sizeof(sl) + sl; + write_hdr(len, Tversion, NOTAG); + write_32(msize); + write_str(sl, v); +} + +void +tattach(uint32_t fid, uint32_t afid, const char *uname, const char *aname) +{ + uint32_t len; + uint16_t ul, al; + + ul = strlen(uname); + al = strlen(aname); + + /* fid[4] afid[4] uname[s] aname[s] */ + len = sizeof(fid) + sizeof(afid) + sizeof(ul) + ul + + sizeof(al) + al; + write_hdr_auto(len, Tattach); + write_fid(fid); + write_fid(afid); + write_str(ul, uname); + write_str(al, aname); +} + +void +tclunk(uint32_t fid) +{ + uint32_t len; + + /* fid[4] */ + len = sizeof(fid); + write_hdr_auto(len, Tclunk); + write_fid(fid); +} + +void +tflush(uint16_t oldtag) +{ + uint32_t len; + + /* oldtag[2] */ + len = sizeof(oldtag); + write_hdr_auto(len, Tflush); + write_tag(oldtag); +} + +void +twalk(uint32_t fid, uint32_t newfid, const char **wnames, size_t nwname) +{ + size_t i; + uint32_t len; + + /* fid[4] newfid[4] nwname[2] nwname*(wname[s]) */ + len = sizeof(fid) + sizeof(newfid) + 2; + for (i = 0; i < nwname; ++i) + len += 2 + strlen(wnames[i]); + + write_hdr_auto(len, Twalk); + write_fid(fid); + write_fid(newfid); + write_16(nwname); + for (i = 0; i < nwname; ++i) + write_str_auto(wnames[i]); +} + +void +topen(uint32_t fid, uint8_t mode) +{ + uint32_t len; + + /* fid[4] mode[1] */ + len = sizeof(fid) + sizeof(mode); + write_hdr_auto(len, Topen); + write_fid(fid); + write_8(mode); +} + +void +tcreate(uint32_t fid, const char *name, uint32_t perm, uint8_t mode) +{ + uint32_t len; + uint16_t nl; + + /* fid[4] name[s] perm[4] mode[1] */ + nl = strlen(name); + len = sizeof(fid) + sizeof(nl) + nl + sizeof(perm) + sizeof(mode); + write_hdr_auto(len, Tcreate); + write_fid(fid); + write_str(nl, name); + write_32(perm); + write_8(mode); +} + +void +tread(uint32_t fid, uint64_t off, uint32_t count) +{ + uint32_t len; + + /* fid[4] off[8] count[4] */ + len = sizeof(fid) + sizeof(off) + sizeof(count); + write_hdr_auto(len, Tread); + write_fid(fid); + write_off(off); + write_32(count); +} + +void +twrite(uint32_t fid, uint64_t off, const void *data, uint32_t count) +{ + uint32_t len; + + /* fid[4] off[8] count[4] data[count] */ + len = sizeof(fid) + sizeof(off) + sizeof(count) + count; + write_hdr_auto(len, Twrite); + write_fid(fid); + write_off(off); + write_buf(data, count); +} + +void +tstat(uint32_t fid) +{ + /* fid[4] */ + write_hdr_auto(sizeof(fid), Tstat); + write_fid(fid); +} + +void +tremove(uint32_t fid) +{ + /* fid[4] */ + write_hdr_auto(sizeof(fid), Tremove); + write_fid(fid); +} blob - /dev/null blob + 9952a8b55f1c826652ef1bd543c9218344942d47 (mode 644) --- /dev/null +++ lib/9pclib.h @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2021 Omar Polo + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +/* 9p client library */ + +#ifndef NPCLIB_H +#define NPCLIB_H + +#include + +extern uint16_t iota_tag; +extern struct evbuffer *evb; + +void write_hdr(uint32_t, uint8_t, uint16_t); +void write_hdr_auto(uint32_t, uint8_t); +void write_str(uint16_t, const char *); +void write_str_auto(const char *); +void write_buf(const void *, uint32_t); +void write_64(uint64_t); +void write_32(uint32_t); +void write_16(uint16_t); +void write_8(uint8_t); + +#define write_off write_64 +#define write_fid write_32 +#define write_tag write_16 + +void tversion(const char *, uint32_t); +void tattach(uint32_t, uint32_t, const char *, const char *); +void tclunk(uint32_t); +void tflush(uint16_t); +void twalk(uint32_t, uint32_t, const char **, size_t); +void topen(uint32_t, uint8_t); +void tcreate(uint32_t, const char *, uint32_t, uint8_t); +void tread(uint32_t, uint64_t, uint32_t); +void twrite(uint32_t, uint64_t, const void *, uint32_t); +void tstat(uint32_t); +void tremove(uint32_t); + +#endif blob - /dev/null blob + 196bf03630b229bbf3afdd96ac209990a24c3387 (mode 644) --- /dev/null +++ lib/Makefile @@ -0,0 +1,4 @@ +# simple Makefile stub to make "tags" target work +SRCS = *.c + +.include blob - /dev/null blob + bd12b3ea1fd8a67b4f3c88dd48ea53ae701669ea (mode 644) --- /dev/null +++ lib/kami.h @@ -0,0 +1,115 @@ +/* + * Copyright (c) 2021, 2022 Omar Polo + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef KAMI_H +#define KAMI_H + +/* + * 9p message header. + * + * The message itself is len bytes long (counting the whole header + * too.) + */ +struct np_msg_header { + uint32_t len; + uint8_t type; + uint16_t tag; +}; + +struct qid { + uint64_t path; + uint32_t vers; + uint8_t type; +}; + +/* useful constants */ +#define HEADERSIZE (4 + 1 + 2) +#define VERSION9P "9P2000" +#define MSIZE9P ((uint32_t)4*1024*1024) +#define NOTAG ((uint16_t)~0U) +#define NOFID ((uint32_t)~0U) +#define NOUID (-1) +#define QIDSIZE 13 +#define MAXWELEM 16 + +#define NPSTATSIZ(namlen, uidnam, gidnam, unam) \ + (6 + QIDSIZE + 20 + 2 + namlen + 2 + uidnam + 2 + gidnam + 2 + unam) + +/* bits in Qid.type */ +#define QTDIR 0x80 /* type bit for directories */ +#define QTAPPEND 0x40 /* type bit for append only files */ +#define QTEXCL 0x20 /* type bit for exclusive use files */ +#define QTMOUNT 0x10 /* type bit for mounted channel */ +#define QTAUTH 0x08 /* type bit for authentication file */ +#define QTTMP 0x04 /* type bit for non-backed-up file */ +#define QTSYMLINK 0x02 /* type bit for symbolic link */ +#define QTFILE 0x00 /* type bits for plain file */ + +/* Topen mode/flags */ +#define KOREAD 0x00 +#define KOWRITE 0x01 +#define KORDWR 0x02 +#define KOEXEC 0x03 +#define KOTRUNC 0x10 +#define KORCLOSE 0x40 + +/* 9p message types */ +enum { + Treaddir = 40, /* .L */ + Rreaddir, + + Tversion = 100, + Rversion, + Tauth = 102, + Rauth, + Tattach = 104, + Rattach, + Terror = 106, /* illegal */ + Rerror, + Tflush = 108, + Rflush, + Twalk = 110, + Rwalk, + Topen = 112, + Ropen, + Tcreate = 114, + Rcreate, + Tread = 116, + Rread, + Twrite = 118, + Rwrite, + Tclunk = 120, + Rclunk, + Tremove = 122, + Rremove, + Tstat = 124, + Rstat, + Twstat = 126, + Rwstat, + Tmax, + + /* + * plan9ports' include/fcall.h also has a + * + * Topenfd = 98, + * Ropenfd, + * + * which it's not mentioned in the 9p "rfc" over at + * 9p.cat-v.org. Ignoring that for now. + */ +}; + +#endif blob - /dev/null blob + 362b9c4cbfe5998442528d99f3faaa2315f44972 (mode 644) --- /dev/null +++ lib/log.c @@ -0,0 +1,196 @@ +/* + * Copyright (c) 2003, 2004 Henning Brauer + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include +#include +#include +#include +#include +#include +#include + +#include "log.h" + +static int debug; +static int verbose; +static const char *log_procname; + +void +log_init(int n_debug, int facility) +{ + debug = n_debug; + verbose = n_debug; + log_procinit(getprogname()); + + if (!debug) + openlog(getprogname(), LOG_PID | LOG_NDELAY, facility); + + tzset(); +} + +void +log_procinit(const char *procname) +{ + if (procname != NULL) + log_procname = procname; +} + +void +log_setverbose(int v) +{ + verbose = v; +} + +int +log_getverbose(void) +{ + return (verbose); +} + +void +logit(int pri, const char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + vlog(pri, fmt, ap); + va_end(ap); +} + +void +vlog(int pri, const char *fmt, va_list ap) +{ + char *nfmt; + int saved_errno = errno; + + if (debug) { + /* best effort in out of mem situations */ + if (asprintf(&nfmt, "%s: %s\n", log_procname, fmt) == -1) { + fprintf(stderr, "%s: ", log_procname); + vfprintf(stderr, fmt, ap); + fprintf(stderr, "\n"); + } else { + vfprintf(stderr, nfmt, ap); + free(nfmt); + } + fflush(stderr); + } else + vsyslog(pri, fmt, ap); + + errno = saved_errno; +} + +void +log_warn(const char *emsg, ...) +{ + char *nfmt; + va_list ap; + int saved_errno = errno; + + /* best effort to even work in out of memory situations */ + if (emsg == NULL) + logit(LOG_ERR, "%s", strerror(saved_errno)); + else { + va_start(ap, emsg); + + if (asprintf(&nfmt, "%s: %s", emsg, + strerror(saved_errno)) == -1) { + /* we tried it... */ + vlog(LOG_ERR, emsg, ap); + logit(LOG_ERR, "%s", strerror(saved_errno)); + } else { + vlog(LOG_ERR, nfmt, ap); + free(nfmt); + } + va_end(ap); + } + + errno = saved_errno; +} + +void +log_warnx(const char *emsg, ...) +{ + va_list ap; + + va_start(ap, emsg); + vlog(LOG_ERR, emsg, ap); + va_end(ap); +} + +void +log_info(const char *emsg, ...) +{ + va_list ap; + + va_start(ap, emsg); + vlog(LOG_INFO, emsg, ap); + va_end(ap); +} + +void +log_debug(const char *emsg, ...) +{ + va_list ap; + + if (verbose) { + va_start(ap, emsg); + vlog(LOG_DEBUG, emsg, ap); + va_end(ap); + } +} + +static void +vfatalc(int code, const char *emsg, va_list ap) +{ + static char s[BUFSIZ]; + const char *sep; + + if (emsg != NULL) { + (void)vsnprintf(s, sizeof(s), emsg, ap); + sep = ": "; + } else { + s[0] = '\0'; + sep = ""; + } + if (code) + logit(LOG_CRIT, "fatal in %s: %s%s%s", + log_procname, s, sep, strerror(code)); + else + logit(LOG_CRIT, "fatal in %s%s%s", log_procname, sep, s); +} + +void +fatal(const char *emsg, ...) +{ + va_list ap; + + va_start(ap, emsg); + vfatalc(errno, emsg, ap); + va_end(ap); + exit(1); +} + +void +fatalx(const char *emsg, ...) +{ + va_list ap; + + va_start(ap, emsg); + vfatalc(0, emsg, ap); + va_end(ap); + exit(1); +} blob - /dev/null blob + 408dec92e050085f6a48c675e9cb0dde00869cd4 (mode 644) --- /dev/null +++ lib/log.h @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2003, 2004 Henning Brauer + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef LOG_H +#define LOG_H + +#include +#include + +void log_init(int, int); +void log_procinit(const char *); +void log_setverbose(int); +int log_getverbose(void); +void log_warn(const char *, ...) + __attribute__((__format__ (printf, 1, 2))); +void log_warnx(const char *, ...) + __attribute__((__format__ (printf, 1, 2))); +void log_info(const char *, ...) + __attribute__((__format__ (printf, 1, 2))); +void log_debug(const char *, ...) + __attribute__((__format__ (printf, 1, 2))); +void logit(int, const char *, ...) + __attribute__((__format__ (printf, 2, 3))); +void vlog(int, const char *, va_list) + __attribute__((__format__ (printf, 2, 0))); +__dead void fatal(const char *, ...) + __attribute__((__format__ (printf, 1, 2))); +__dead void fatalx(const char *, ...) + __attribute__((__format__ (printf, 1, 2))); + +#endif /* LOG_H */ blob - /dev/null blob + cf1ac0e9438622950da2943dd9476e842bc8531c (mode 644) --- /dev/null +++ lib/sandbox.c @@ -0,0 +1,64 @@ +/* + * Copyright (c) 2021 Omar Polo + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include "log.h" +#include "sandbox.h" + +#ifdef __OpenBSD__ + +#include + +void +sandbox_main(void) +{ + return; +} + +void +sandbox_listener(void) +{ + return; +} + +void +sandbox_client(void) +{ + return; +} + +#else +#warning "No sandbox available for this OS" + +void +sandbox_main(void) +{ + log_warnx("No sandbox available for this os"); + return; +} + +void +sandbox_listener(void) +{ + return; +} + +void +sandbox_client(void) +{ + return; +} + +#endif blob - /dev/null blob + c964b983ea10e3af850adc627f269bd1051a2619 (mode 644) --- /dev/null +++ lib/sandbox.h @@ -0,0 +1,24 @@ +/* + * Copyright (c) 2021 Omar Polo + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef SANDBOX_H +#define SANDBOX_H + +void sandbox_main(void); +void sandbox_listener(void); +void sandbox_client(void); + +#endif blob - /dev/null blob + 19c5accb28cf9b5a472ffc64462f0fd1e645a970 (mode 644) --- /dev/null +++ lib/utils.c @@ -0,0 +1,206 @@ +/* + * Copyright (c) 2021 Omar Polo + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "kami.h" +#include "log.h" +#include "utils.h" + +void * +xmalloc(size_t size) +{ + void *r; + + if ((r = malloc(size)) == NULL) + fatal("malloc"); + return r; +} + +void * +xcalloc(size_t nmemb, size_t size) +{ + void *r; + + if ((r = calloc(nmemb, size)) == NULL) + fatal("calloc"); + return r; +} + +char * +xstrdup(const char *s) +{ + char *r; + + if ((r = strdup(s)) == NULL) + fatal("strdup"); + return r; +} + +void * +xmemdup(const void *d, size_t len) +{ + void *r; + + if ((r = malloc(len)) == NULL) + fatal("malloc"); + memcpy(r, d, len); + return r; +} + +const char * +pp_msg_type(uint8_t type) +{ + switch (type) { + case Tversion: return "Tversion"; + case Rversion: return "Rversion"; + case Tauth: return "Tauth"; + case Rauth: return "Rauth"; + case Tattach: return "Tattach"; + case Rattach: return "Rattach"; + case Terror: return "Terror"; /* illegal */ + case Rerror: return "Rerror"; + case Tflush: return "Tflush"; + case Rflush: return "Rflush"; + case Twalk: return "Twalk"; + case Rwalk: return "Rwalk"; + case Topen: return "Topen"; + case Ropen: return "Ropen"; + case Tcreate: return "Tcreate"; + case Rcreate: return "Rcreate"; + case Tread: return "Tread"; + case Rread: return "Rread"; + case Twrite: return "Twrite"; + case Rwrite: return "Rwrite"; + case Tclunk: return "Tclunk"; + case Rclunk: return "Rclunk"; + case Tremove: return "Tremove"; + case Rremove: return "Rremove"; + case Tstat: return "Tstat"; + case Rstat: return "Rstat"; + case Twstat: return "Twstat"; + case Rwstat: return "Rwstat"; + default: return "unknown"; + } +} + +const char * +pp_qid_type(uint8_t type) +{ + switch (type) { + case QTDIR: return "dir"; + case QTAPPEND: return "append-only"; + case QTEXCL: return "exclusive"; + case QTMOUNT: return "mounted-channel"; + case QTAUTH: return "authentication"; + case QTTMP: return "non-backed-up"; + case QTSYMLINK: return "symlink"; + case QTFILE: return "file"; + } + + return "unknown"; +} + +static void +hexdump_ppline(int x, uint8_t *data, size_t len) +{ + for (; x < 50; x++) + printf(" "); + + printf("|"); + + for (x = 0; x < (int)len; ++x) { + if (isgraph(data[x])) + printf("%c", data[x]); + else + printf("."); + } + + printf("|\n"); +} + +void +hexdump(const char *label, uint8_t *data, size_t len) +{ + size_t i; + int x, n; + + /* + * Layout: + * === first block === == second block == |........|\n + * first and second block are 8 bytes long (for a total of 48 + * columns), plus two separator plus two | plus 16 chars, for + * a total of 68 characters. + */ + + printf("\nhexdump \"%s\": (%zu bytes)\n", label, len); + for (x = 0, n = 0, i = 0; i < len; ++i) { + if (i != 0 && i % 8 == 0) { + printf(" "); + x++; + } + + if (n == 16) { + hexdump_ppline(x, &data[i - 16], 16); + x = 0; + n = 0; + } + + printf("%02x ", data[i]); + x += 3; + n++; + } + + if (n != 0) + hexdump_ppline(x, &data[i - n], n); + + printf("\n"); +} + +void +imsg_event_add(struct imsgev *iev) +{ + iev->events = EV_READ; + if (iev->ibuf.w.queued) + iev->events |= EV_WRITE; + + event_del(&iev->ev); + event_set(&iev->ev, iev->ibuf.fd, iev->events, iev->handler, iev); + event_add(&iev->ev, NULL); +} + +int +imsg_compose_event(struct imsgev *iev, uint16_t type, uint32_t peerid, + pid_t pid, int fd, const void *data, uint16_t datalen) +{ + int ret; + + if ((ret = imsg_compose(&iev->ibuf, type, peerid, pid, fd, data, + datalen) != -1)) + imsg_event_add(iev); + + return ret; +} blob - /dev/null blob + be7be3e1e7d43208a380f99283ed303709a37d0f (mode 644) --- /dev/null +++ lib/utils.h @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2021 Omar Polo + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef UTILS_H +#define UTILS_H + +#include + +#define MIN(a, b) ((a) < (b) ? (a) : (b)) + +struct imsgev { + struct imsgbuf ibuf; + void (*handler)(int, short, void *); + struct event ev; + short events; +}; + +void *xmalloc(size_t); +void *xcalloc(size_t, size_t); +char *xstrdup(const char *); +void *xmemdup(const void *, size_t); + +const char *pp_msg_type(uint8_t); +const char *pp_qid_type(uint8_t); + +void hexdump(const char *, uint8_t *data, size_t len); + +void imsg_event_add(struct imsgev *); +int imsg_compose_event(struct imsgev *, uint16_t, uint32_t, pid_t, + int, const void *, uint16_t); + +#endif blob - ba451bdd0d365e2c95e1d8bebcf0ee850163c8e7 (mode 644) blob + /dev/null --- configure.ac +++ /dev/null @@ -1,160 +0,0 @@ -AC_INIT([kamid], [0.1], [kamid@omarpolo.com], [kamid], [gemini://kamid.omarpolo.com]) -AC_CONFIG_LIBOBJ_DIR(compat) -AC_CANONICAL_HOST -AM_INIT_AUTOMAKE([-Wall foreign subdir-objects]) -AC_PROG_CC -AC_USE_SYSTEM_EXTENSIONS -AC_PROG_YACC - -PKG_PROG_PKG_CONFIG - -# Some functions can be in libbsd. Thanks to lldpb for the inspiration :) -AC_ARG_WITH([libbsd], - AS_HELP_STRING([--with-libbsd], [Use libbsd @<:@default=auto@:>@]), - [], - [with_libbsd=auto]) -if test x"$with_libbsd" != x"no"; then - PKG_CHECK_MODULES([libbsd], [libbsd-overlay libbsd-ctor], [ - _save_AM_CFLAGS="$AM_CFLAGS" - _save_LIBS="$LIBS" - AM_CFLAGS="$AM_CFLAGS $libbsd_CFLAGS" - LIBS="$LIBS $libbsd_LIBS" - AC_MSG_CHECKING([if libbsd can be linked correctly]) - AC_LINK_IFELSE([AC_LANG_PROGRAM([[ - @%:@include - @%:@include - ]], [[]])],[ - AC_MSG_RESULT(yes) - AM_CFLAGS="$AM_CFLAGS $libbsd_CFLAGS" - AM_LDFLAGS="$AM_LDFLAGS $libbsd_LIBS" - with_libbsd=yes - ],[ - AC_MSG_RESULT(no) - AM_CFLAGS="$_save_AM_CFLAGS" - LIBS="$_save_LIBS" - if test x"$with_libbsd" = x"yes"; then - AC_MSG_FAILURE([*** no libbsd support found]) - fi - with_libbsd=no - ]) - ], [ - if test x"$with_libbsd" = x"yes"; then - AC_MSG_FAILURE([*** no libbsd support found]) - fi - with_libbsd=no - ]) -fi - -AC_SEARCH_LIBS([arc4random], [], - [AC_DEFINE([HAVE_ARC4RANDOM], 1, [arc4random])], - [AC_DEFINE([HAVE_ARC4RANDOM], 0, [arc4random])]) - -AC_REPLACE_FUNCS([ - asprintf \ - err \ - freezero \ - getdtablecount \ - getdtablesize \ - getprogname \ - memmem \ - recallocarray \ - setproctitle \ - setprogname \ - strlcat \ - strlcpy \ - strsep \ - strtonum \ - vis \ -]) - -AC_MSG_CHECKING([for sys/queue.h with TAILQ_FOREACH_SAFE and STAILQ_ENTRY]) -AC_COMPILE_IFELSE([AC_LANG_PROGRAM([ -#include -#include -], [ - TAILQ_HEAD(tailhead, entry) head; - struct entry { - TAILQ_ENTRY(entry) entries; - } *np, *nt; - TAILQ_INIT(&head); - TAILQ_FOREACH_SAFE(np, &head, entries, nt) { - /* nop */ ; - } - - STAILQ_HEAD(listhead, qentry) qhead = STAILQ_HEAD_INITIALIZER(qhead); - struct qentry { - STAILQ_ENTRY(qentry) entries; - } foo; - - return 0; -])], [ - AC_MSG_RESULT(yes) - AC_DEFINE([HAVE_QUEUE_H], 1, [QUEUE_H]) -], AC_MSG_RESULT(no)) - -AC_CHECK_HEADERS([sys/tree.h]) - -AC_CHECK_DECL(PR_SET_NAME, AC_DEFINE([HAVE_PR_SET_NAME], 1, [pr_set_name]), [], - [[#include ]]) - -AC_CHECK_LIB([crypto], [RAND_add], [], [ - AC_MSG_ERROR([requires openssl]) -]) - -AC_CHECK_LIB(tls, tls_init, [], [ - AC_MSG_ERROR([requires libtls]) -]) - -# small hack to avoid linking *everything* to readline. -libs_orig="${LIBS}" -AC_CHECK_LIB(readline, readline, [], [ - AC_DEFINE([HAVE_READLINE], 0, [1 if readline is found]) -]) -KAMIFTP_LIBS="${LIBS}" -LIBS="${libs_orig}" -AC_SUBST([KAMIFTP_LIBS]) - -AS_CASE([$host_os], - [*openbsd*], [AC_CHECK_LIB([event], [event_init], [], - [AC_MSG_ERROR([requires libevent])])], - [PKG_CHECK_MODULES([libevent2], [libevent_core >= 2], - [ - AC_DEFINE([HAVE_EVENT2], 1, [1 if using event2]) - AM_CFLAGS="$libevent2_CFLAGS $AM_CFLAGS" - LIBS="$libevent2_LIBS $LIBS" - ], [AC_MSG_ERROR([requires libevent])])]) - -AC_CHECK_LIB(util, imsg_init, [], [ - AC_LIBOBJ(fmt_scaled) - AC_LIBOBJ(imsg) - AC_LIBOBJ(imsg-buffer) - AC_LIBOBJ(ohash) -]) - -# check compiler flags -AC_DEFUN([CC_ADD_CHECK_FLAGS], [ - AC_MSG_CHECKING([if $CC supports $1 flag]) - old_AM_CFLAGS="$AM_CFLAGS" - AM_CFLAGS="$AM_CFLAGS $1" - AC_COMPILE_IFELSE([AC_LANG_PROGRAM([],[])], [ - AC_MSG_RESULT(yes) - ], [ - AC_MSG_RESULT(no) - AM_CFLAGS="$old_AM_CFLAGS" - ]) -]) -CC_ADD_CHECK_FLAGS([-Wall]) -CC_ADD_CHECK_FLAGS([-Wextra]) -CC_ADD_CHECK_FLAGS([-Wmissing-prototypes]) -CC_ADD_CHECK_FLAGS([-Wstrict-prototypes]) -CC_ADD_CHECK_FLAGS([-Wwrite-strings]) -CC_ADD_CHECK_FLAGS([-Wno-unused-parameter]) - -AC_SUBST([AM_CFLAGS]) - -AC_CONFIG_HEADERS([config.h]) -AC_CONFIG_FILES([ - Makefile -]) - -AC_OUTPUT blob - /dev/null blob + fb2fec0c8c16141e0cb61f5dcb1150d73bfd9b31 (mode 644) --- /dev/null +++ ninepscript/Makefile @@ -0,0 +1,18 @@ +.PATH:${.CURDIR}/../lib +.PATH:${.CURDIR}/../kamid + +.include "../kamid-version.mk" + +PROG= ninepscript +SRCS= client.c log.c parse.y sandbox.c script.c utils.c +MAN= ninepscript.5 ninepscript.8 + +CPPFLAGS= -I${.CURDIR}/../kamid/ \ + -I${.CURDIR}/../lib -I${.CURDIR} + +LDADD+= -levent -lutil -ltls +DPADD+= ${LIBEVENT} ${LIBUTIL} ${LIBTLS} + +NOMAN = Yes + +.include blob - /dev/null blob + 6da049b0758a48894158408f942e91823baa3ade (mode 644) --- /dev/null +++ ninepscript/ninepscript.5 @@ -0,0 +1,265 @@ +.\" Copyright (c) 2021 Omar Polo +.\" +.\" Permission to use, copy, modify, and distribute this software for any +.\" purpose with or without fee is hereby granted, provided that the above +.\" copyright notice and this permission notice appear in all copies. +.\" +.\" THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +.\" WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +.\" MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +.\" ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +.\" WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +.\" ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +.\" OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +.\" +.Dd $Mdocdate: December 02 2021$ +.Dt NINEPSCRIPT 5 +.Os +.Sh NAME +.Nm ninepscript +.Nd kamid regress test scripting language +.Sh DESCRIPTION +.Nm +is a custom DSL +.Pq domain specific language +used to write the regression suite of +.Xr kamid 8 . +it has a fairly simple and regular syntax that features constant +declarations, routines, test cases. +It does not support conditional or loops. +.Pp +Additional files can be included with the +.Ic include +keyword, for example +.Bd -literal -offset Ds +include "lib.9ps" +.Ed +.Pp +Comments can be placed anywhere, start with the # character and extend +until the end of the line. +.Pp +An expression is a fundamental building block. +It is something that yields a value. +An expression may be either a: +.Bl -tag -width variable_reference +.It literal +a bare number or string. +A string is a sequence of characters enclosed in single or double quotes +.Sq like this +or +.Dq like this . +.It routine call +Evaluate the routine code and return the value computed by it. +The syntax is +.Bd -literal -offset Ds +.Ar routine Ns Po Ar arguments... Pc +.Ed +.Pp +The +.Ql ... +special syntax expands to the list of variable arguments of the +current routine. +Be aware that the implementation of the variable arguments is quirky +and has a lot of corner cases, use with care! +.It variable reference +a variable +.Pq or constant +reference is the name of a previously defined variable or constant. +It evaluates to the value of the variable or constant in the current +scope. +.It comparison +The syntax is +.Bd -literal -offset Ds +.Ar expression Cm == Ar expression +.Ar expression Cm <= Ar expression +.Ed +.Pp +and yields a true value if the two expressions are considered to be +respectively +.Sq equal +or +.Sq lesser equal , +a false value otherwise. +Two values are equal if they are both number and represent the same +value +.Pq regardless of the size +or if they're both the same string. +.It cast +convert one value to another type. +The syntax is +.Ql Ar expression : Ns Ar type +where type is one of +.Sq u8 , +.Sq u16 , +.Sq u32 +or +.Sq str . +.It field access +Access a field of a complex object. +The syntax is +.Ql Ar object . Ns Ar field . +See the +.Sx OBJECTS AND FIELDS +section for the description of objects types and fields allowed. +.El +.Pp +An expression is considered to be +.Dq false +if evaluates to a number and its value is zero. +Otherwise, it's considered to be +.Dq true . +.Pp +The top-level declarations are: +.Bl -tag -width Ds +.It Ic const Ar identifier No = Ar value +Declare +.Ar identifier +to be a constant that evaluates to +.Ar value . +.Ar value +must be a literal or a cast from a literal. +Multiple constant can be declared at the same time using the following +syntax: +.Bd -literal -offset Ds +.Ic const ( + foo = 5 + bar = 7 +) +.Ed +.Pp +Note that newlines are mandatory after an +.Ar identifier No = Ar value +line in this case. +.It Ic proc Ar name Ns Po Ar arguments ... Pc Brq code ... +Define a routine called +.Ar name +that accepts the comma-separated list of +.Ar arguments . +When a routine is called, its +.Ar code +gets evaluated in a lexical scope where +.Ar arguments +are defined to the value passed by the caller. +A routine may be called only within another routine body or inside a +.Ic testing +body. +.It Ic testing Ar reason Ic dir Ar path Brq code ... +Define a test case. +.Ar reason +is what the test block is about and +.Ar path +is the path to the root directory where the test will be executed. +.Ar reason +and +.Ar path +must be string literals. +.El +.Pp +Inside a +.Ic proc +or +.Ic testing +code block the following instructions are allowed: +.Bl -tag -width Ds +.It Ar variable Cm = Ar expression +Set a local lexical +.Ar variable +to the value yielded by +.Ar expression . +The +.Ar variable +lifetime last from this declaration until the end of the current +block. +.It Ar procedure Ns Pq Ar arguments ... +Execute +.Ar procedure +with the given +.Ar arguments . +.It Ic assert Ar comparison +Evaluate +.Ar comparison +and if it not yields a true-ish value terminate the current running +test and mark it as failed. +Multiple assertion can be done in one single +.Ic assert +block using the following syntax: +.Bd -literal -offset Ds +.Ic assert ( + comparison_1 + comparison_2 + ... + comparison_n +) +.Ed +.Pp +Note that newlines are mandatory after every +.Ar comparison +in this case. +.It Ic should-fail Ar expression Op : Ar reason +Evaluate +.Ar expression +and continue only if the evaluation produced an error. +If the execution of +.Ar expression +is successful, terminate the current test. +.Ar reason +is optional and, if present, must be a literal string. +It is similar to the +.Sq try-catch +statement of other programming languages. +.El +.Sh BUILT IN FUNCTIONS +These functions are built into the language and provided by the +interpreter: +.Bl -tag -width Ds +.It Ic debug Ns Po Ar arg, ... Pc +Print the argument list separated by a space and followed by a newline +if the interpreter runs with the verbose flag set. +.It Ic iota Ns Pq +Return distinct u16 integer every time it's called. +Starts at zero and goes up to 254 to then wrap around to zero again. +255 is skipped because +.Ic iota +is intended to be used to provide the tag for +.Ic send +and 255 is the special +.Sq NOTAG +value in 9P200. +.It Ic print Ns Po Ar arg, ... Pc +Print the argument list separated by a space and followed by a +newline. +.It Ic recv Ns Pq +Receive a message from the server and return it as an object. +A +.Dv Terror +doesn't stop the execution of the test, rather, an error object is +returned. +See +.Sx OBJECTS AND FIELDS +for the complete list of objects. +.It Ic send Ns Po Ar type, tag, ... Pc +Send a 9P message with the given +.Ar type +and +.Ar tag . +Other arguments, if given, are packed into the message and sent as +well, respecting the given order. +The overall length of the message is computed automatically. +.It Ic skip Ns Pq +Terminate the execution of the current test suite immediately. +The test won't be counted as passed nor failed, but as skipped. +.El +.Sh OBJECTS AND FIELDS +List of objects and fields... +.Sh SEE ALSO +.Xr 9p 7 , +.Xr kamid 8 , +.Xr ninepscript 8 +.Sh AUTHORS +.An -nosplit +.Nm +was designed and implemented by +.An Omar Polo Aq Mt op@omarpolo.com +for the +.Xr kamid 8 +daemon regression suite. blob - /dev/null blob + 2df71001f2c0ddff2c4217feba1820fecb17df36 (mode 644) --- /dev/null +++ ninepscript/ninepscript.8 @@ -0,0 +1,54 @@ +.\" Copyright (c) 2021 Omar Polo +.\" +.\" Permission to use, copy, modify, and distribute this software for any +.\" purpose with or without fee is hereby granted, provided that the above +.\" copyright notice and this permission notice appear in all copies. +.\" +.\" THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +.\" WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +.\" MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +.\" ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +.\" WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +.\" ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +.\" OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +.\" +.Dd $Mdocdate: December 02 2021$ +.Dt NINEPSCRIPT 8 +.Os +.Sh NAME +.Nm ninepscript +.Nd scripting language for the kamid regress suite +.Sh SYNOPSIS +.Nm +.Op Fl nv +.Op Fl x Ar pattern +.Ar +.Sh DESCRIPTION +.Nm +is an interpreter for a custom DSL +.Pq domain sppecific language +used to write the regression suite of +.Xr kamid 8 . +The test themselves are written in +.Xr ninepscript 5 . +.Pp +The options are as follows: +.Bl -tag -width Ds +.It Fl n +don't run the test, check only the syntax of the provided +.Ar files . +.It Fl v +verbose logging, print more information during the tests execution. +.It Fl x Ar pattern +Run only the tests that match the given +.Ar pattern . +.El +.Pp +.Nm +first loads all the given +.Ar files +then proceeds to execute each defined test. +.Pp +See +.Xr ninepscript 5 +for the description of the ninepscript language. blob - /dev/null blob + d8897db51f613efeec14222cf68a621ef949d128 (mode 644) --- /dev/null +++ ninepscript/parse.y @@ -0,0 +1,688 @@ +/* + * Copyright (c) 2021 Omar Polo + * Copyright (c) 2018 Florian Obser + * Copyright (c) 2004, 2005 Esben Norby + * Copyright (c) 2004 Ryan McBride + * Copyright (c) 2002, 2003, 2004 Henning Brauer + * Copyright (c) 2001 Markus Friedl. All rights reserved. + * Copyright (c) 2001 Daniel Hartmeier. All rights reserved. + * Copyright (c) 2001 Theo de Raadt. All rights reserved. + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +%{ + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "log.h" +#include "kami.h" +#include "utils.h" + +#include "script.h" + +TAILQ_HEAD(files, file) files = TAILQ_HEAD_INITIALIZER(files); +static struct file { + TAILQ_ENTRY(file) entry; + FILE *stream; + char *name; + size_t ungetpos; + size_t ungetsize; + u_char *ungetbuf; + int eof_reached; + int lineno; + int errors; +} *file, *topfile; +struct file *pushfile(const char *); +int popfile(void); +int yyparse(void); +int yylex(void); +int yyerror(const char *, ...) + __attribute__((__format__ (printf, 1, 2))) + __attribute__((__nonnull__ (1))); +int kw_cmp(const void *, const void *); +int lookup(char *); +int igetc(void); +int lgetc(int); +void lungetc(int); +int findeol(void); + +static int shouldfail; + +typedef struct { + union { + struct op *op; + struct proc *proc; + char *str; + int64_t num; + } v; + int lineno; +} YYSTYPE; + +%} + +/* + * for bison: + * %define parse.error verbose + */ + +%token ASSERT +%token CONST +%token DIR +%token ERROR +%token INCLUDE +%token PROC +%token REPEAT +%token SHOULD_FAIL STR +%token TESTING +%token U8 U16 U32 +%token VARGS + +%token STRING SYMBOL +%token NUMBER + +%type cast cexpr check expr faccess funcall +%type literal sfail var varref vargs + +%type procname + +%% + +program : /* empty */ + | program '\n' + | program include '\n' + | program const '\n' + | program proc '\n' + | program test '\n' + ; + +optnl : '\n' optnl /* zero or more newlines */ + | /*empty*/ + ; + +nl : '\n' optnl ; + +include : INCLUDE STRING { + struct file *nfile; + + if ((nfile = pushfile($2)) == NULL) { + yyerror("failed to include file %s", $2); + free($2); + YYERROR; + } + free($2); + + file = nfile; + lungetc('\n'); + } + ; + +const : CONST consti + | CONST '(' optnl mconst ')' + ; + +mconst : consti nl | mconst consti nl ; + +consti : SYMBOL '=' expr { + if (!global_set($1, $3)) { + yyerror("can't set %s: illegal expression", $1); + free($1); + free_op($3); + YYERROR; + } + } + ; + +var : SYMBOL '=' expr { $$ = op_assign($1, $3); } ; +varref : SYMBOL { $$ = op_var($1); } ; +literal : STRING { $$ = op_lit_str($1); } + | NUMBER { $$ = op_lit_num($1); } ; + +/* + * `expr '=' '=' expr` is ambiguous. furthermore, we're not + * interested in checking all the possibilities here. + */ +cexpr : literal | varref | funcall | faccess ; +check : cexpr '=' '=' cexpr { $$ = op_cmp_eq($1, $4); } + | cexpr '<' '=' cexpr { $$ = op_cmp_leq($1, $4); } + ; + +expr : literal | funcall | varref | check | cast | faccess | vargs ; + +vargs : VARGS { $$ = op_vargs(); } ; + +cast : expr ':' U8 { $$ = op_cast($1, V_U8); } + | expr ':' U16 { $$ = op_cast($1, V_U16); } + | expr ':' U32 { $$ = op_cast($1, V_U32); } + | expr ':' STR { $$ = op_cast($1, V_STR); } + ; + +faccess : varref '.' SYMBOL { $$ = op_faccess($1, $3); } + | faccess '.' SYMBOL { $$ = op_faccess($1, $3); } + ; + +procname: SYMBOL { + if (($$ = proc_by_name($1)) == NULL) { + yyerror("unknown proc %s", $1); + free($1); + YYERROR; + } + free($1); + } + ; + +funcall : procname { + prepare_funcall(); + } '(' args optcomma ')' { + struct proc *proc; + int argc; + + $$ = op_funcall($1); + proc = $$->v.funcall.proc; + argc = $$->v.funcall.argc; + + if (argc != proc->minargs && !proc->vararg) { + yyerror("invalid arity for `%s': want %d arguments " + "but %d given.", $1->name, proc->minargs, argc); + /* TODO: recursively free $$ */ + YYERROR; + } + + if (argc < proc->minargs && proc->vararg) { + yyerror("invalid arity for `%s': want at least %d " + "arguments but %d given.", $1->name, proc->minargs, + argc); + /* TODO: recursively free $$ */ + YYERROR; + } + } + ; + +optcomma: /* empty */ | ',' ; + +dots : '.' '.' '.' ; + +args : /* empty */ + | args ',' expr { push_arg($3); } + | args ',' dots { push_arg(op_rest()); } + | expr { push_arg($1); } + | dots { push_arg(op_rest()); } + ; + +proc : PROC SYMBOL { + prepare_proc(); + } '(' args ')' { + if (!proc_setup_body()) { + yyerror("invalid argument in proc `%s' definition", + $2); + free($2); + YYERROR; + } + } '{' optnl block '}' { + proc_done($2); + } + ; + +block : /* empty */ + | block var nl { block_push($2); } + | block funcall nl { block_push($2); } + | block assert nl + | block sfail nl { block_push($2); } + ; + +sfail : SHOULD_FAIL expr { $$ = op_sfail($2, NULL); } + | SHOULD_FAIL expr ':' STRING { $$ = op_sfail($2, $4); } + ; + +assert : ASSERT asserti + | ASSERT '(' optnl massert ')' + ; + +massert : asserti nl | massert asserti nl ; + +asserti : check { block_push(op_assert($1)); } + ; + +test : TESTING STRING DIR STRING { + prepare_test(); + } testopt '{' optnl block '}' { + test_done(shouldfail, $2, $4); + shouldfail = 0; + } + ; + +testopt : /* empty */ + | SHOULD_FAIL { shouldfail = 1; } + ; + +%% + +struct keywords { + const char *k_name; + int k_val; +}; + +int +yyerror(const char *fmt, ...) +{ + va_list ap; + char *msg; + + file->errors++; + va_start(ap, fmt); + if (vasprintf(&msg, fmt, ap) == -1) + fatalx("yyerror vasprintf"); + va_end(ap); + logit(LOG_CRIT, "%s:%d: %s", file->name, yylval.lineno, msg); + free(msg); + return 0; +} + +int +kw_cmp(const void *k, const void *e) +{ + return strcmp(k, ((const struct keywords *)e)->k_name); +} + +int +lookup(char *s) +{ + /* This has to be sorted always. */ + static const struct keywords keywords[] = { + {"assert", ASSERT}, + {"const", CONST}, + {"dir", DIR}, + {"include", INCLUDE}, + {"proc", PROC}, + {"repeat", REPEAT}, + {"should-fail", SHOULD_FAIL}, + {"str", STR}, + {"testing", TESTING}, + {"u16", U16}, + {"u32", U32}, + {"u8", U8}, + {"vargs", VARGS}, + }; + const struct keywords *p; + + p = bsearch(s, keywords, sizeof(keywords)/sizeof(keywords[0]), + sizeof(keywords[0]), kw_cmp); + + if (p) + return p->k_val; + else + return SYMBOL; +} + +#define START_EXPAND 1 +#define DONE_EXPAND 2 + +static int expanding; + +int +igetc(void) +{ + int c; + + while (1) { + if (file->ungetpos > 0) + c = file->ungetbuf[--file->ungetpos]; + else + c = getc(file->stream); + + if (c == START_EXPAND) + expanding = 1; + else if (c == DONE_EXPAND) + expanding = 0; + else + break; + } + return c; +} + +int +lgetc(int quotec) +{ + int c, next; + + if (quotec) { + if ((c = igetc()) == EOF) { + yyerror("reached end of file while parsing " + "quoted string"); + if (file == topfile || popfile() == EOF) + return EOF; + return quotec; + } + return c; + } + + while ((c = igetc()) == '\\') { + next = igetc(); + if (next != '\n') { + c = next; + break; + } + yylval.lineno = file->lineno; + file->lineno++; + } + + if (c == EOF) { + /* + * Fake EOL when hit EOF for the first time. This gets line + * count right if last line in included file is syntactically + * invalid and has no newline. + */ + if (file->eof_reached == 0) { + file->eof_reached = 1; + return '\n'; + } + while (c == EOF) { + if (file == topfile || popfile() == EOF) + return EOF; + c = igetc(); + } + } + return c; +} + +void +lungetc(int c) +{ + if (c == EOF) + return; + + if (file->ungetpos >= file->ungetsize) { + void *p = reallocarray(file->ungetbuf, file->ungetsize, 2); + if (p == NULL) + err(1, "lungetc"); + file->ungetbuf = p; + file->ungetsize *= 2; + } + file->ungetbuf[file->ungetpos++] = c; +} + +int +findeol(void) +{ + int c; + + /* Skip to either EOF or the first real EOL. */ + while (1) { + c = lgetc(0); + if (c == '\n') { + file->lineno++; + break; + } + if (c == EOF) + break; + } + return ERROR; +} + + +#if 0 +int my_yylex(void); + +int +yylex(void) +{ + int x; + + switch (x = my_yylex()) { + case ASSERT: puts("assert"); break; + case CONST: puts("const"); break; + case DIR: puts("dir"); break; + case ERROR: puts("error"); break; + case INCLUDE: puts("include"); break; + case PROC: puts("proc"); break; + case REPEAT: puts("repeat"); break; + case STR: puts(":str"); break; + case TESTING: puts("testing"); break; + case U8: puts(":u8"); break; + case U16: puts(":u16"); break; + case U32: puts(":u32"); break; + + case STRING: printf("string \"%s\"\n", yylval.v.str); break; + case SYMBOL: printf("symbol %s\n", yylval.v.str); break; + case NUMBER: printf("number %"PRIu64"\n", yylval.v.num); break; + + default: + printf("character "); + if (x == '\n') + printf("\\n"); + else + printf("%c", x); + printf(" [0x%x]", x); + printf("\n"); + break; + } + + return x; +} + +int +my_yylex(void) +#else +int +yylex(void) +#endif +{ + unsigned char buf[8096]; + unsigned char *p; + int quotec, next, c; + int token; + + p = buf; + while ((c = lgetc(0)) == ' ' || c == '\t' || c == '\f') + ; /* nop */ + + yylval.lineno = file->lineno; + if (c == '#') + while ((c = lgetc(0)) != '\n' && c != EOF) + ; /* nop */ + + switch (c) { + case ':': + return c; + break; + case '\'': + case '\"': + quotec = c; + while (1) { + if ((c = lgetc(quotec)) == EOF) + return 0; + if (c == '\n') { + file->lineno++; + continue; + } else if (c == '\\') { + if ((next = lgetc(quotec)) == EOF) + return 0; + if (next == quotec || next == ' ' || + next == '\t') + c = next; + else if (next == '\n') { + file->lineno++; + continue; + } else + lungetc(next); + } else if (c == quotec) { + *p = '\0'; + break; + } else if (c == '\0') { + yyerror("syntax error"); + return findeol(); + } + + if (p + 1 >= buf + sizeof(buf) - 1) { + yyerror("string too long"); + return findeol(); + } + + *p++ = c; + } + + yylval.v.str = xstrdup(buf); + return STRING; + } + +#define allowed_to_end_number(x) \ + (isspace(x) || x == ')' || x == ',' || x == '/' || x == '}' \ + || x == '=' || x == ':') + + if (c == '-' || isdigit(c)) { + do { + *p++ = c; + if ((size_t)(p-buf) >= sizeof(buf)) { + yyerror("string too long"); + return findeol(); + } + } while ((c = lgetc(0)) != EOF && (isdigit(c) || c == 'x')); + lungetc(c); + if (p == buf + 1 && buf[0] == '-') + goto nodigits; + if (c == EOF || allowed_to_end_number(c)) { + char *ep; + + *p = '\0'; + errno = 0; + yylval.v.num = strtoll(buf, &ep, 0); + if (*ep != '\0' || (errno == ERANGE && + (yylval.v.num == LONG_MAX || + yylval.v.num == LONG_MIN))) { + yyerror("\"%s\" invalid number or out of range", + buf); + return findeol(); + } + + return NUMBER; + } else { +nodigits: + while (p > buf + 1) + lungetc(*--p); + c = *--p; + if (c == '-') + return c; + } + } + +#define allowed_in_symbol(x) \ + (isalnum(x) || (ispunct(x) && x != '(' && x != ')' && \ + x != '{' && x != '}' && \ + x != '!' && x != '=' && \ + x != '#' && x != ',' && \ + x != '.' && x != ':')) + + if (isalnum(c) || c == ':' || c == '_') { + do { + *p++ = c; + if ((size_t)(p-buf) >= sizeof(buf)) { + yyerror("string too long"); + return findeol(); + } + } while ((c = lgetc(0)) != EOF && (allowed_in_symbol(c))); + lungetc(c); + *p = '\0'; + if ((token = lookup(buf)) == SYMBOL) + yylval.v.str = xstrdup(buf); + return token; + } + + if (c == '\n') { + yylval.lineno = file->lineno; + file->lineno++; + } + if (c == EOF) + return 0; + return c; +} + +struct file * +pushfile(const char *name) +{ + struct file *nfile; + + if ((nfile = calloc(1, sizeof(struct file))) == NULL) { + log_warn("calloc"); + return NULL; + } + if ((nfile->name = strdup(name)) == NULL) { + log_warn("strdup"); + free(nfile); + return NULL; + } + if ((nfile->stream = fopen(nfile->name, "r")) == NULL) { + log_warn("%s", nfile->name); + free(nfile->name); + free(nfile); + return NULL; + } + nfile->lineno = TAILQ_EMPTY(&files) ? 1 : 0; + nfile->ungetsize = 16; + nfile->ungetbuf = malloc(nfile->ungetsize); + if (nfile->ungetbuf == NULL) { + log_warn("malloc"); + fclose(nfile->stream); + free(nfile->name); + free(nfile); + return NULL; + } + TAILQ_INSERT_TAIL(&files, nfile, entry); + return nfile; +} + +int +popfile(void) +{ + struct file *prev; + + if ((prev = TAILQ_PREV(file, files, entry)) != NULL) + prev->errors += file->errors; + + TAILQ_REMOVE(&files, file, entry); + fclose(file->stream); + free(file->name); + free(file->ungetbuf); + free(file); + file = prev; + return file ? 0 : EOF; +} + +void +loadfile(const char *path) +{ + int errors; + + file = pushfile(path); + if (file == NULL) + err(1, "pushfile"); + topfile = file; + + yyparse(); + errors = file->errors; + popfile(); + + if (errors) + errx(1, "can't load %s because of errors", path); +} blob - /dev/null blob + c6b2837f20fc239ffd568d478a748fd27272ed11 (mode 644) --- /dev/null +++ ninepscript/script.c @@ -0,0 +1,1771 @@ +/* + * Copyright (c) 2021 Omar Polo + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "client.h" +#include "kami.h" +#include "kamid.h" +#include "log.h" +#include "script.h" +#include "utils.h" + +#define DEBUG 0 + +#ifndef INFTIM +#define INFTIM -1 +#endif + +static const char *argv0; + +static uint8_t *lastmsg; + +static struct imsgbuf ibuf; +static int ibuf_inuse; +static int child_out = -1; + +static struct procs procs = TAILQ_HEAD_INITIALIZER(procs); +static struct tests tests = TAILQ_HEAD_INITIALIZER(tests); + +static int ntests; + +static struct opstacks blocks = TAILQ_HEAD_INITIALIZER(blocks); +static struct opstacks args = TAILQ_HEAD_INITIALIZER(args); + +#define STACK_HEIGHT 64 +static struct value vstack[STACK_HEIGHT]; +static int stackh; + +static struct envs envs = TAILQ_HEAD_INITIALIZER(envs); + +static struct value v_false = {.type = V_NUM, .v = {.num = 0}}; +static struct value v_true = {.type = V_NUM, .v = {.num = 1}}; + +static uint8_t lasttag; + +static int debug; +static int syntaxcheck; + +static const char *filler; + +static inline void +before_printing(void) +{ + if (filler != NULL) { + printf("%s", filler); + filler = NULL; + } +} + +static inline void +check_for_output(void) +{ + static char buf[BUFSIZ]; + struct pollfd pfd; + ssize_t r; + + pfd.fd = child_out; + pfd.events = POLLIN; + if (poll(&pfd, 1, 0) == -1) + fatal("poll"); + + if (!(pfd.revents & POLLIN)) + return; + + for (;;) { + if ((r = read(child_out, buf, sizeof(buf))) == -1) { + if (errno == EAGAIN) + break; + fatal("read"); + } + if (r == 0) + break; + before_printing(); + fwrite(buf, 1, r, stdout); + } +} + +static inline void +peekn(int depth, struct value *v) +{ + if (depth > stackh) + errx(1, "can't peek the stack at %d: underflow", + depth); + memcpy(v, &vstack[stackh - depth], sizeof(*v)); + +#if DEBUG + printf("peeking(%d) ", depth); pp_val(v); printf("\n"); +#endif +} + +static inline void +popv(struct value *v) +{ + if (stackh == 0) + errx(1, "can't pop the stack: underflow"); + memcpy(v, &vstack[--stackh], sizeof(*v)); + +#if DEBUG + printf("popping "); pp_val(v); printf("\n"); +#endif +} + +static inline void +popvn(int n) +{ + struct value v; + + while (n-- > 0) + popv(&v); +} + +static inline void +pushv(struct value *v) +{ + if (stackh == STACK_HEIGHT) + errx(1, "can't push the stack: overflow"); + +#if DEBUG + printf("pushing "); pp_val(v); printf("\n"); +#endif + + memcpy(&vstack[stackh++], v, sizeof(*v)); +} + +static inline void +pushbool(int n) +{ + pushv(n ? &v_true : &v_false); +} + +static inline void +pushnum(int64_t n) +{ + struct value v; + + v.type = V_NUM; + v.v.num = n; + pushv(&v); +} + +static inline struct opstack * +pushstack(struct opstacks *stack) +{ + struct opstack *ops; + + ops = xcalloc(1, sizeof(*ops)); + TAILQ_INSERT_HEAD(stack, ops, entry); + return ops; +} + +static inline struct opstack * +peek(struct opstacks *stack) +{ + if (TAILQ_EMPTY(stack)) + errx(1, "%s: args underflow", __func__); + + return TAILQ_FIRST(stack); +} + +static inline struct op * +finalize(struct opstacks *stack, int *argc) +{ + struct opstack *ops; + struct op *op; + + if (TAILQ_EMPTY(stack)) + errx(1, "%s: args underflow", __func__); + + ops = peek(stack); + TAILQ_REMOVE(&args, ops, entry); + op = ops->base.next; + + if (argc != NULL) + *argc = ops->counter; + + free(ops); + return op; +} + +static inline void +push(struct opstacks *stack, struct op *op) +{ + struct opstack *ops; + + ops = peek(stack); + if (ops->last == NULL) { + ops->base.next = op; + ops->last = op; + } else { + ops->last->next = op; + ops->last = op; + } + + ops->counter++; +} + +static inline void +pushenv(void) +{ + struct env *e; + + e = xcalloc(1, sizeof(*e)); + TAILQ_INSERT_HEAD(&envs, e, entry); +} + +static inline struct env * +currentenv(void) +{ + assert(!TAILQ_EMPTY(&envs)); + return TAILQ_FIRST(&envs); +} + +static void +popenv(void) +{ + struct env *e; + struct binding *b, *tb; + + e = currentenv(); + TAILQ_REMOVE(&envs, e, entry); + + TAILQ_FOREACH_SAFE(b, &e->bindings, entry, tb) + free(b); + + free(e); +} + +static inline int +setvar(char *sym, struct op *op) +{ + struct binding *b; + struct env *e; + int ret, height; + + height = stackh; + if ((ret = eval(op)) != EVAL_OK) + return ret; + + if (stackh != height + 1) { + before_printing(); + printf("trying to assign to `%s' a void value: ", sym); + pp_op(op); + printf("\n"); + return EVAL_ERR; + } + + b = xcalloc(1, sizeof(*b)); + b->name = sym; + popv(&b->val); + + e = TAILQ_FIRST(&envs); + TAILQ_INSERT_HEAD(&e->bindings, b, entry); + + return EVAL_OK; +} + +static inline void +setvar_raw(char *sym, struct op *op) +{ + struct binding *b; + struct env *e; + + b = xcalloc(1, sizeof(*b)); + b->name = sym; + b->raw = op; + + e = TAILQ_FIRST(&envs); + TAILQ_INSERT_HEAD(&e->bindings, b, entry); +} + +static inline int +getvar(const char *sym, struct value *v) +{ + struct env *e; + struct binding *b; + + TAILQ_FOREACH(e, &envs, entry) { + TAILQ_FOREACH(b, &e->bindings, entry) { + if (!strcmp(sym, b->name)) { + memcpy(v, &b->val, sizeof(*v)); + return EVAL_OK; + } + } + } + + before_printing(); + fprintf(stderr, "unbound variable %s\n", sym); + return EVAL_ERR; +} + +static inline int +getvar_raw(const char *sym, struct op **raw) +{ + struct env *e; + struct binding *b; + + TAILQ_FOREACH(e, &envs, entry) { + TAILQ_FOREACH(b, &e->bindings, entry) { + if (!strcmp(sym, b->name)) { + *raw = b->raw; + return EVAL_OK; + } + } + } + + return EVAL_ERR; +} + +int +global_set(char *sym, struct op *op) +{ + struct binding *b; + struct env *e; + + /* TODO: check for duplicates */ + + if (op->type != OP_LITERAL && + (op->type == OP_CAST && op->v.cast.expr->type != OP_LITERAL)) + return 0; + + b = xcalloc(1, sizeof(*b)); + b->name = sym; + + /* it's only a cast on a literal! */ + if (op->type == OP_CAST) { + if (eval(op) != EVAL_OK) { + free(b); + return 0; + } + popv(&b->val); + } else + memcpy(&b->val, &op->v.literal, sizeof(b->val)); + + e = TAILQ_LAST(&envs, envs); + TAILQ_INSERT_HEAD(&e->bindings, b, entry); + + return 1; +} + +struct op * +newop(int type) +{ + struct op *op; + + op = xcalloc(1, sizeof(*op)); + op->type = type; + + return op; +} + +void +free_op_rec(struct op *op) +{ + struct op *n; + + while (op != NULL) { + n = op->next; + free_op(op); + op = n; + } +} + +void +free_op(struct op *op) +{ + if (op == NULL) + return; + + switch (op->type) { + case OP_REST: + case OP_LITERAL: + case OP_VARGS: + break; + case OP_ASSIGN: + free(op->v.assign.name); + free_op_rec(op->v.assign.expr); + break; + case OP_ASSERT: + free_op_rec(op->v.assert); + break; + case OP_FUNCALL: + free_op_rec(op->v.funcall.argv); + break; + case OP_VAR: + free(op->v.var); + break; + case OP_CAST: + free_op_rec(op->v.cast.expr); + break; + case OP_CMP_EQ: + case OP_CMP_LEQ: + free_op_rec(op->v.bin_cmp.a); + free_op_rec(op->v.bin_cmp.b); + break; + case OP_FACCESS: + free_op_rec(op->v.faccess.expr); + free(op->v.faccess.field); + break; + case OP_SFAIL: + free(op->v.sfail.msg); + free_op_rec(op->v.sfail.expr); + break; + default: + /* unreachable */ + abort(); + } + + free(op); +} + +struct op * +op_rest(void) +{ + return newop(OP_REST); +} + +struct op * +op_assign(char *sym, struct op *expr) +{ + struct op *op; + + op = newop(OP_ASSIGN); + op->v.assign.name = sym; + op->v.assign.expr = expr; + + return op; +} + +struct op * +op_assert(struct op *expr) +{ + struct op *op; + + op = newop(OP_ASSERT); + op->v.assert = expr; + + return op; +} + +struct op * +op_var(char *sym) +{ + struct op *op; + + op = newop(OP_VAR); + op->v.var = sym; + + return op; +} + +struct op * +op_lit_str(char *str) +{ + struct op *op; + + op = newop(OP_LITERAL); + op->v.literal.type = V_STR; + op->v.literal.v.str = str; + + return op; +} + +struct op * +op_lit_num(uint64_t n) +{ + struct op *op; + + op = newop(OP_LITERAL); + op->v.literal.type = V_NUM; + op->v.literal.v.num = n; + + return op; +} + +struct op * +op_cmp_eq(struct op *a, struct op *b) +{ + struct op *op; + + op = newop(OP_CMP_EQ); + op->v.bin_cmp.a = a; + op->v.bin_cmp.b = b; + + return op; +} + +struct op * +op_cmp_leq(struct op *a, struct op *b) +{ + struct op *op; + + op = newop(OP_CMP_LEQ); + op->v.bin_cmp.a = a; + op->v.bin_cmp.b = b; + + return op; +} + +struct op * +op_cast(struct op *expr, int totype) +{ + struct op *op; + + op = newop(OP_CAST); + op->v.cast.expr = expr; + op->v.cast.totype = totype; + + return op; +} + +struct op * +op_faccess(struct op *expr, char *field) +{ + struct op *op; + + op = newop(OP_FACCESS); + op->v.faccess.expr = expr; + op->v.faccess.field = field; + + return op; +} + +struct op * +op_sfail(struct op *expr, char *msg) +{ + struct op *op; + + op = newop(OP_SFAIL); + op->v.sfail.expr = expr; + op->v.sfail.msg = msg; + + return op; +} + +struct op * +op_vargs(void) +{ + struct op *op; + + op = newop(OP_VARGS); + + return op; +} + +void +ppf_val(FILE *f, struct value *val) +{ + size_t i; + + switch (val->type) { + case V_SYM: + fprintf(f, "%s", val->v.str); + break; + case V_STR: + fprintf(f, "\"%s\"", val->v.str); + break; + case V_NUM: + fprintf(f, "%"PRIi64, val->v.num); + break; + case V_U8: + fprintf(f, "%"PRIu8, val->v.u8); + break; + case V_U16: + fprintf(f, "%"PRIu16, val->v.u16); + break; + case V_U32: + fprintf(f, "%"PRIu32, val->v.u32); + break; + case V_MSG: + fprintf(f, "("); + for (i = 0; i < val->v.msg.len; ++i) + fprintf(f, "%x%s", val->v.msg.msg[i], + i == val->v.msg.len-1 ? "" : " "); + fprintf(f, ")"); + break; + case V_QIDVEC: + fprintf(f, "qids[n=%zu]", val->v.qidvec.len); + break; + default: + fprintf(f, ""); + break; + } +} + +void +pp_val(struct value *val) +{ + ppf_val(stdout, val); +} + +const char * +val_type(struct value *v) +{ + switch (v->type) { + case V_SYM: return "symbol"; + case V_STR: return "string"; + case V_NUM: return "number"; + case V_MSG: return "message"; + case V_QID: return "qid"; + case V_U8: return "u8"; + case V_U16: return "u16"; + case V_U32: return "u32"; + default: return "unknown"; + } +} + +int +val_trueish(struct value *a) +{ + if (val_isnum(a)) + return val_tonum(a); + return 1; +} + +int +val_isnum(struct value *a) +{ + return a->type == V_NUM + || a->type == V_U8 + || a->type == V_U16 + || a->type == V_U32; +} + +int64_t +val_tonum(struct value *a) +{ + switch (a->type) { + case V_NUM: return a->v.num; + case V_U8: return a->v.u8; + case V_U16: return a->v.u16; + case V_U32: return a->v.u32; + default: + before_printing(); + fprintf(stderr, "%s: given value is not a number\n", __func__); + abort(); + } +} + +int +val_eq(struct value *a, struct value *b) +{ + if (val_isnum(a) && val_isnum(b)) + return val_tonum(a) == val_tonum(b); + + if (a->type != b->type) + return 0; + + switch (a->type) { + case V_STR: + case V_SYM: + return !strcmp(a->v.str, b->v.str); + } + + return 0; +} + +int +val_leq(struct value *a, struct value *b) +{ + if (val_isnum(a) && val_isnum(b)) + return val_tonum(a) <= val_tonum(b); + return 0; +} + +static inline const char * +pp_totype(int totype) +{ + /* + * Not all of these are valid cast type thought, including + * every possibility only to aid debugging. + */ + switch (totype) { + case V_STR: return "str"; + case V_SYM: return "sym"; + case V_NUM: return "num"; + case V_QID: return "qid"; + case V_U8: return "u8"; + case V_U16: return "u16"; + case V_U32: return "u32"; + default: return "unknown"; + } +} + +int +val_cast(struct value *a, int totype) +{ + int64_t v; + +#define NUMCAST(val, t, c, totype, max) do { \ + if (val > max) { \ + before_printing(); \ + fprintf(stderr, "can't cast %"PRIu64 \ + " to %s\n", val, pp_totype(totype)); \ + return EVAL_ERR; \ + } \ + a->type = totype; \ + a->v.t = (c)val; \ + return EVAL_OK; \ + } while (0) + + if (a->type == totype) + return EVAL_OK; + + if (!val_isnum(a)) { + before_printing(); + fprintf(stderr, "can't cast "); + ppf_val(stderr, a); + fprintf(stderr, " to type %s\n", pp_totype(totype)); + return EVAL_ERR; + } + + v = a->v.num; + switch (totype) { + case V_U8: NUMCAST(v, u8, uint8_t, totype, UINT8_MAX); + case V_U16: NUMCAST(v, u16, uint16_t, totype, UINT16_MAX); + case V_U32: NUMCAST(v, u32, uint32_t, totype, UINT32_MAX); + default: + before_printing(); + fprintf(stderr, "can't cast %"PRIu64" to %s\n", + v, pp_totype(totype)); + return EVAL_ERR; + } + +#undef NUMCAST +} + +int +val_faccess(struct value *a, const char *field, struct value *ret) +{ + uint8_t mtype; + uint16_t len; + const char *errstr; + +#define MSGTYPE(m) *(m.msg + 4) /* skip the length */ + + switch (a->type) { + case V_QID: + /* TODO: add path. needs uint64_t values thought! */ + if (!strcmp(field, "vers")) { + ret->type = V_U32; + memcpy(&ret->v.u32, a->v.qid+1, 4); + return EVAL_OK; + } else if (!strcmp(field, "type")) { + ret->type = V_U8; + ret->v.u8 = *a->v.qid; + return EVAL_OK; + } + break; + + case V_MSG: + mtype = MSGTYPE(a->v.msg); + if (!strcmp(field, "type")) { + ret->type = V_U8; + ret->v.u8 = MSGTYPE(a->v.msg); + return EVAL_OK; + } else if (!strcmp(field, "tag")) { + ret->type = V_U16; + memcpy(&ret->v.u16, &a->v.msg.msg[5], 2); + ret->v.u16 = le16toh(ret->v.u16); + return EVAL_OK; + } else if (!strcmp(field, "msize") && mtype == Rversion) { + ret->type = V_U32; + memcpy(&ret->v.u32, &a->v.msg.msg[7], 4); + ret->v.u32 = le32toh(ret->v.u32); + return EVAL_OK; + } else if (!strcmp(field, "qid") && mtype == Rattach) { + ret->type = V_QID; + memcpy(&ret->v.qid, &a->v.msg.msg[7], QIDSIZE); + return EVAL_OK; + } else if (!strcmp(field, "nwqid") && mtype == Rwalk) { + ret->type = V_U16; + memcpy(&ret->v.u16, &a->v.msg.msg[7], 2); + ret->v.u16 = le16toh(ret->v.u16); + return EVAL_OK; + } else if (!strcmp(field, "wqid") && mtype == Rwalk) { + ret->type = V_QIDVEC; + ret->v.qidvec.start = &a->v.msg.msg[9]; + memcpy(&len, &a->v.msg.msg[7], 2); + len = le16toh(len); + ret->v.qidvec.len = len; + return EVAL_OK; + } + break; + + case V_QIDVEC: + len = strtonum(field, 0, MAXWELEM, &errstr); + if (errstr != NULL) { + before_printing(); + printf("can't access qid #%s: %s\n", field, errstr); + return EVAL_ERR; + } + + if (len >= a->v.qidvec.len) { + before_printing(); + printf("can't access qid #%d: out-of-bound " + "(max %zu)\n", len, a->v.qidvec.len); + return EVAL_ERR; + } + + ret->type = V_QID; + memcpy(&ret->v.qid, a->v.qidvec.start + len * QIDSIZE, + QIDSIZE); + + return EVAL_OK; + + default: + break; + } + + before_printing(); + printf("can't access field `%s' on type %s (", field, val_type(a)); + pp_val(a); + printf(")\n"); + return EVAL_ERR; + +#undef MSGTYPE +} + +void +pp_op(struct op *op) +{ + struct op *aux; + + switch (op->type) { + case OP_REST: + printf("..."); + break; + case OP_ASSIGN: + printf("%s = ", op->v.assign.name); + pp_op(op->v.assign.expr); + break; + case OP_ASSERT: + printf("assert "); + pp_op(op->v.assert); + break; + case OP_FUNCALL: + printf("funcall %s(", op->v.funcall.proc->name); + for (aux = op->v.funcall.argv; aux != NULL; aux = aux->next) { + pp_op(aux); + if (aux->next != NULL) + printf(", "); + } + printf(")"); + break; + case OP_LITERAL: + pp_val(&op->v.literal); + break; + case OP_VAR: + printf("%s", op->v.var); + break; + case OP_CAST: + pp_op(op->v.cast.expr); + printf(":"); + switch (op->v.cast.totype) { + case V_U8: printf("u8"); break; + case V_U16: printf("u16"); break; + case V_U32: printf("u32"); break; + case V_STR: printf("str"); break; + default: printf("???"); break; + } + break; + case OP_CMP_EQ: + pp_op(op->v.bin_cmp.a); + printf(" == "); + pp_op(op->v.bin_cmp.b); + break; + case OP_CMP_LEQ: + pp_op(op->v.bin_cmp.a); + printf(" <= "); + pp_op(op->v.bin_cmp.b); + break; + case OP_FACCESS: + pp_op(op->v.faccess.expr); + printf(".%s", op->v.faccess.field); + break; + case OP_SFAIL: + printf("should-fail "); + pp_op(op->v.sfail.expr); + if (op->v.sfail.msg != NULL) + printf(": \"%s\"", op->v.sfail.msg); + break; + case OP_VARGS: + printf("vargs"); + break; + default: + printf(" ???[%d] ", op->type); + } +} + +void +pp_block(struct op *op) +{ + while (op != NULL) { + printf("> "); + pp_op(op); + printf("\n"); + + op = op->next; + } +} + +int +eval(struct op *op) +{ + struct value a, b; + struct proc *proc; + struct op *t, *tnext; + int i, ret; + +#if DEBUG + pp_op(op); + printf("\n"); +#endif + + switch (op->type) { + case OP_REST: + /* + * Try to load the rest argument. Note that it can be + * empty! + */ + if ((ret = getvar_raw("...", &t)) == EVAL_OK) + if ((ret = eval(t)) != EVAL_OK) + return ret; + break; + + case OP_ASSIGN: + ret = setvar(op->v.assign.name, op->v.assign.expr); + if (ret != EVAL_OK) + return ret; + break; + + case OP_ASSERT: + if ((ret = eval(op->v.assert)) != EVAL_OK) + return ret; + popv(&a); + if (!val_trueish(&a)) { + before_printing(); + printf("assertion failed: "); + pp_op(op->v.assert); + printf("\n"); + return EVAL_ERR; + } + break; + + case OP_FUNCALL: + /* assume airity matches */ + + proc = op->v.funcall.proc; + if (proc->nativefn != NULL) { + /* + * Push arguments on the stack for builtin + * functions. Counting the height of the + * stack is done to compute the correct number + * in the vararg case. argc only counts the + * "syntactical" arguments, i.e. foo(x, ...) + * has argc == 2, but at runtime argc may be + * 1, 2 or a greater number! + */ + + i = stackh; + t = op->v.funcall.argv; + if (t != NULL && (ret = eval(t)) != EVAL_OK) + return ret; + i = stackh - i; + + assert(i >= 0); + + if ((ret = proc->nativefn(i)) + != EVAL_OK) + return ret; + } else { + if (proc->body == NULL) { + before_printing(); + printf("warn: calling the empty proc `%s'\n", + proc->name); + break; + } + + pushenv(); + + for (t = op->v.funcall.argv, i = 0; + t != NULL; + t = t->next, i++) { + /* + * Push a pseudo variable `...' (and + * don't evaluate it) in the vararg + * case. A special case is when the + * variable is itself `...'. + */ + if (proc->vararg && i == proc->minargs) { + if (t->type != OP_REST) + setvar_raw(xstrdup("..."), t); + break; + } + + /* + * The arguments are a linked list of + * ops. Setvar will call eval that + * will evaluate *all* the arguments. + * The dance here that sets next to + * NULL and then restores it is to + * avoid this behaviour. + */ + tnext = t->next; + t->next = NULL; + ret = setvar(proc->args[i], t); + t->next = tnext; + + if (ret != EVAL_OK) + return ret; + } + + if ((ret = eval(proc->body)) != EVAL_OK) + return ret; + + popenv(); + } + + break; + + case OP_LITERAL: + pushv(&op->v.literal); + break; + + case OP_VAR: + if ((ret = getvar(op->v.var, &a)) != EVAL_OK) + return ret; + pushv(&a); + break; + + case OP_CAST: + if ((ret = eval(op->v.cast.expr)) != EVAL_OK) + return ret; + popv(&a); + if ((ret = val_cast(&a, op->v.cast.totype)) != EVAL_OK) + return ret; + pushv(&a); + break; + + case OP_CMP_EQ: + if ((ret = eval(op->v.bin_cmp.a)) != EVAL_OK) + return ret; + if ((ret = eval(op->v.bin_cmp.b)) != EVAL_OK) + return ret; + + popv(&b); + popv(&a); + pushbool(val_eq(&a, &b)); + break; + + case OP_CMP_LEQ: + if ((ret = eval(op->v.bin_cmp.a)) != EVAL_OK) + return ret; + if ((ret = eval(op->v.bin_cmp.b)) != EVAL_OK) + return ret; + + popv(&b); + popv(&a); + pushbool(val_leq(&a, &b)); + break; + + case OP_FACCESS: + if ((ret = eval(op->v.faccess.expr)) != EVAL_OK) + return ret; + popv(&a); + if ((ret = val_faccess(&a, op->v.faccess.field, &b)) + != EVAL_OK) + return ret; + pushv(&b); + break; + + case OP_SFAIL: + if ((ret = eval(op->v.sfail.expr)) == EVAL_OK) { + before_printing(); + printf("expecting failure"); + if (op->v.sfail.msg != NULL) + printf(" \"%s\"", op->v.sfail.msg); + printf("\n"); + printf("expression: "); + pp_op(op->v.sfail.expr); + printf("\n"); + return EVAL_ERR; + } + if (ret == EVAL_SKIP) + return ret; + break; + + case OP_VARGS: + if ((ret = getvar_raw("...", &t)) == EVAL_OK) { + for (i = 0; t != NULL; t = t->next) + i++; + pushnum(i); + } else + pushnum(0); + break; + + default: + before_printing(); + fprintf(stderr, "invalid op, aborting.\n"); + abort(); + } + + if (op->next) + return eval(op->next); + return EVAL_OK; +} + +void +prepare_funcall(void) +{ + pushstack(&args); +} + +void +push_arg(struct op *op) +{ + push(&args, op); +} + +struct op * +op_funcall(struct proc *proc) +{ + struct op *op, *argv; + int argc; + + argv = finalize(&args, &argc); + + op = newop(OP_FUNCALL); + op->v.funcall.proc = proc; + op->v.funcall.argv = argv; + op->v.funcall.argc = argc; + + return op; +} + +void +add_builtin_proc(const char *name, int (*fn)(int), int argc, int vararg) +{ + struct proc *proc; + + proc = xcalloc(1, sizeof(*proc)); + proc->name = xstrdup(name); + proc->nativefn = fn; + proc->minargs = argc; + proc->vararg = vararg; + + TAILQ_INSERT_HEAD(&procs, proc, entry); +} + +void +prepare_proc(void) +{ + pushstack(&args); +} + +int +proc_setup_body(void) +{ + struct opstack *argv; + struct op *op; + int i; + + argv = peek(&args); + for (i = 0, op = argv->base.next; op != NULL; i++) { + /* + * TODO: should free the whole list on error but.., + * we're gonna exit real soon(tm)! + */ + if (op->type != OP_VAR && op->type != OP_REST) + return 0; + + op = op->next; + } + + assert(i == argv->counter); + pushstack(&blocks); + return 1; +} + +void +proc_done(char *name) +{ + struct proc *proc; + struct op *op, *next, *argv, *body; + int i, argc; + + argv = finalize(&args, &argc); + body = finalize(&blocks, NULL); + + proc = xcalloc(1, sizeof(*proc)); + proc->name = name; + proc->minargs = argc; + + for (i = 0, op = argv; op != NULL; ++i) { + if (op->type == OP_REST) { + proc->vararg = 1; + proc->minargs = i; + break; + } + + proc->args[i] = xstrdup(op->v.var); + + next = op->next; + free_op(op); + op = next; + } + assert(i == argc || (proc->vararg && i == proc->minargs)); + + proc->body = body; + + TAILQ_INSERT_HEAD(&procs, proc, entry); +} + +void +block_push(struct op *op) +{ + push(&blocks, op); +} + +struct proc * +proc_by_name(const char *name) +{ + struct proc *p; + + TAILQ_FOREACH(p, &procs, entry) { + if (!strcmp(p->name, name)) + return p; + } + + return NULL; +} + +void +prepare_test(void) +{ + pushstack(&blocks); +} + +void +test_done(int shouldfail, char *name, char *dir) +{ + struct test *test; + + test = xcalloc(1, sizeof(*test)); + test->shouldfail = shouldfail; + test->name = name; + test->dir = dir; + test->body = finalize(&blocks, NULL); + + if (TAILQ_EMPTY(&tests)) + TAILQ_INSERT_HEAD(&tests, test, entry); + else + TAILQ_INSERT_TAIL(&tests, test, entry); + + ntests++; +} + +static int +builtin_print(int argc) +{ + struct value v; + int i; + + before_printing(); + + for (i = argc; i > 0; --i) { + peekn(i, &v); + if (v.type == V_STR) + printf("%s", v.v.str); + else + pp_val(&v); + printf(" "); + } + + printf("\n"); + + popvn(argc); + + return EVAL_OK; +} + +static int +builtin_debug(int argc) +{ + if (debug) + return builtin_print(argc); + + popvn(argc); + return EVAL_OK; +} + +static int +builtin_skip(int argc) +{ + return EVAL_SKIP; +} + +static int +builtin_iota(int argc) +{ + struct value v; + + v.type = V_U16; + if ((v.v.u16 = ++lasttag) == 255) + v.v.u16 = ++lasttag; + + pushv(&v); + return EVAL_OK; +} + +static int +builtin_send(int argc) +{ + struct ibuf *buf; + struct value v; + uint32_t len; + uint16_t slen; + int i; + + check_for_output(); + + /* + * Compute the length of the packet. 4 is for the initial + * length field + */ + len = 4; + + for (i = argc; i > 0; --i) { + peekn(i, &v); + switch (v.type) { + case V_STR: + len += 2; /* count */ + len += strlen(v.v.str); + break; + + case V_U8: + len += 1; + break; + + case V_U16: + len += 2; + break; + + case V_U32: + len += 4; + break; + + default: + before_printing(); + printf("%s: can't serialize ", __func__); + pp_val(&v); + printf("\n"); + return EVAL_ERR; + } + } + + if (len > UINT16_MAX) { + before_printing(); + printf("%s: message size too long: got %d when max is %d\n", + __func__, len, UINT16_MAX); + return EVAL_ERR; + } + + if ((buf = imsg_create(&ibuf, IMSG_BUF, 0, 0, len)) == NULL) + fatal("imsg_create(%d)", len); + + len = htole32(len); + imsg_add(buf, &len, sizeof(len)); + + for (i = argc; i > 0; --i) { + peekn(i, &v); + switch (v.type) { + case V_STR: + slen = strlen(v.v.str); + slen = htole16(slen); + imsg_add(buf, &slen, sizeof(slen)); + imsg_add(buf, v.v.str, strlen(v.v.str)); + break; + + case V_U8: + imsg_add(buf, &v.v.u8, 1); + break; + + case V_U16: + v.v.u16 = htole16(v.v.u16); + imsg_add(buf, &v.v.u16, 2); + break; + + case V_U32: + v.v.u32 = htole32(v.v.u32); + imsg_add(buf, &v.v.u32, 4); + break; + } + } + + imsg_close(&ibuf, buf); + + if (imsg_flush(&ibuf) == -1) { + i = errno; + before_printing(); + printf("%s: imsg_flush failed: %s\n", __func__, strerror(i)); + return EVAL_ERR; + } + + check_for_output(); + return EVAL_OK; +} + +static int +builtin_recv(int argc) +{ + struct pollfd pfd; + struct value v; + struct imsg imsg; + ssize_t n, datalen; + int serrno; + + if (lastmsg != NULL) { + free(lastmsg); + lastmsg = NULL; + } + + pfd.fd = ibuf.fd; + pfd.events = POLLIN; + if (poll(&pfd, 1, INFTIM) == -1) { + serrno = errno; + before_printing(); + printf("%s: poll failed: %s\n", __func__, strerror(serrno)); + return EVAL_ERR; + } + +again: + if ((n = imsg_read(&ibuf)) == -1) { + if (errno == EAGAIN) + goto again; + fatal("imsg_read"); + } + if (n == 0) { +disconnect: + before_printing(); + printf("child disconnected\n"); + return EVAL_ERR; + } + +nextmessage: + check_for_output(); + + /* read only one message */ + if ((n = imsg_get(&ibuf, &imsg)) == -1) + fatal("imsg_get"); + if (n == 0) + goto disconnect; + + datalen = imsg.hdr.len - IMSG_HEADER_SIZE; + switch (imsg.hdr.type) { + case IMSG_BUF: + v.type = V_MSG; + if ((v.v.msg.msg = malloc(datalen)) == NULL) + fatal("malloc"); + memcpy(v.v.msg.msg, imsg.data, datalen); + v.v.msg.len = datalen; + pushv(&v); + imsg_free(&imsg); + return EVAL_OK; + + case IMSG_CLOSE: + before_printing(); + printf("subprocess closed the connection\n"); + imsg_free(&imsg); + return EVAL_ERR; + + case IMSG_MSIZE: + imsg_free(&imsg); + goto nextmessage; + + default: + before_printing(); + printf("got unknown message from subprocess: %d\n", + imsg.hdr.type); + imsg_free(&imsg); + return EVAL_ERR; + } +} + +static pid_t +spawn_client_proc(void) +{ + const char *argv[4]; + int p[2], out[2], argc = 0; + pid_t pid; + + if (child_out != -1) + close(child_out); + + if (socketpair(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC | SOCK_NONBLOCK, + PF_UNSPEC, p) == -1) + fatal("socketpair"); + + if (socketpair(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC | SOCK_NONBLOCK, + PF_UNSPEC, out) == -1) + fatal("socketpair"); + + switch (pid = fork()) { + case -1: + fatal("cannot fork"); + case 0: + break; + default: + close(p[1]); + close(out[1]); + child_out = out[0]; + if (ibuf_inuse) { + msgbuf_clear(&ibuf.w); + close(ibuf.fd); + } + imsg_init(&ibuf, p[0]); + ibuf_inuse = 1; + return pid; + } + + close(p[0]); + close(out[0]); + + if (dup2(out[1], 1) == -1 || + dup2(out[1], 2) == -1) + fatal("dup2"); + + if (p[1] != 3) { + if (dup2(p[1], 3) == -1) + fatal("cannot setup imsg fd"); + } else if (fcntl(F_SETFD, 0) == -1) + fatal("cannot setup imsg fd"); + + argv[argc++] = argv0; + argv[argc++] = "-Tc"; + +#if DEBUG + argv[argc++] = "-v"; +#endif + + argv[argc++] = NULL; + + execvp(argv0, (char *const *)argv); + fatal("execvp"); +} + +static void +prepare_child_for_test(struct test *t) +{ + struct passwd *pw; + struct stat sb; + + if (stat(t->dir, &sb) == -1) + fatal("stat(\"%s\")", t->dir); + + if ((pw = getpwuid(sb.st_uid)) == NULL) + fatal("getpwuid(%d)", sb.st_uid); + + imsg_compose(&ibuf, IMSG_AUTH, 0, 0, -1, + pw->pw_name, strlen(pw->pw_name)+1); + imsg_compose(&ibuf, IMSG_AUTH_DIR, 0, 0, -1, + t->dir, strlen(t->dir)+1); + + if (imsg_flush(&ibuf) == -1) + fatal("imsg_flush"); +} + +static int +run_test(struct test *t) +{ + pid_t pid; + int ret; + +#if DEBUG + before_printing(); + puts("====================="); + pp_block(t->body); + puts("====================="); +#endif + + if (stackh != 0) + popvn(stackh); + + if (t->body == NULL) { + before_printing(); + printf("no instructions, skipping...\n"); + return EVAL_SKIP; + } + + pid = spawn_client_proc(); + prepare_child_for_test(t); + ret = eval(t->body); + + imsg_compose(&ibuf, IMSG_CONN_GONE, 0, 0, -1, NULL, 0); + imsg_flush(&ibuf); + + while (waitpid(pid, NULL, 0) != pid) + ; /* nop */ + + check_for_output(); + + if (t->shouldfail) { + if (ret == EVAL_OK) { + before_printing(); + printf("test was expected to fail\n"); + return EVAL_ERR; + } else if (ret == EVAL_ERR) + return EVAL_OK; + } + + return ret; +} + +int +main(int argc, char **argv) +{ + struct test *t; + int ch, i, r, passed = 0, failed = 0, skipped = 0; + int runclient = 0; + const char *pat = NULL; + regex_t reg; + + assert(argv0 = argv[0]); + + signal(SIGPIPE, SIG_IGN); + + log_init(1, LOG_DAEMON); + log_setverbose(1); + + /* prepare the global env */ + pushenv(); + + add_builtin_proc("print", builtin_print, 1, 1); + add_builtin_proc("debug", builtin_debug, 1, 1); + add_builtin_proc("skip", builtin_skip, 0, 0); + add_builtin_proc("iota", builtin_iota, 0, 0); + add_builtin_proc("send", builtin_send, 2, 1); + add_builtin_proc("recv", builtin_recv, 0, 0); + + while ((ch = getopt(argc, argv, "nT:vx:")) != -1) { + switch (ch) { + case 'n': + syntaxcheck = 1; + break; + case 'T': + assert(*optarg == 'c'); + runclient = 1; + break; + case 'v': + debug = 1; + break; + case 'x': + pat = optarg; + break; + default: + fprintf(stderr, "Usage: %s [-nv] [files...]\n", + *argv); + exit(1); + } + } + argc -= optind; + argv += optind; + + if (runclient) + client(1, debug); + + if (pat == NULL) + pat = ".*"; + + if (regcomp(®, pat, REG_BASIC | REG_ICASE | REG_NOSUB) != 0) + fatalx("invalid regexp: %s", pat); + + for (i = 0; i < argc; ++i) + loadfile(argv[i]); + + if (syntaxcheck) { + fprintf(stderr, "files OK\n"); + return 0; + } + + /* Check for root privileges. */ + if (geteuid()) + fatalx("need root privileges"); + + i = 0; + TAILQ_FOREACH(t, &tests, entry) { + if (regexec(®, t->name, 0, NULL, 0) != 0) + continue; + + printf("===> [%d/%d] running test \"%s\"... ", i+1, ntests, + t->name); + fflush(stdout); + + filler = "\n"; + r = run_test(t); + if (filler == NULL) + printf("=> test "); + + switch (r) { + case EVAL_OK: + printf("passed\n"); + passed++; + break; + case EVAL_ERR: + failed++; + printf("failed\n"); + break; + case EVAL_SKIP: + printf("skipped\n"); + skipped++; + break; + } + + if (filler == NULL) + printf("\n"); + i++; + } + + printf("\n"); + printf("%d/%d passed (%d skipped and %d failed)\n", + passed, i, skipped, failed); + + popenv(); + free(lastmsg); + regfree(®); + + return failed != 0; +} blob - /dev/null blob + 1e5584e6ac031d9590e267d34b6c282095a7a1be (mode 644) --- /dev/null +++ ninepscript/script.h @@ -0,0 +1,217 @@ +/* + * Copyright (c) 2021 Omar Polo + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef SCRIPT_H +#define SCRIPT_H + +enum { + /* literals */ + V_SYM, + V_STR, + V_NUM, + + /* foreign */ + V_MSG, + V_QIDVEC, + V_QID, + + /* casted */ + V_U8, + V_U16, + V_U32, +}; + +struct value { + int type; + union { + char *str; + int64_t num; + uint8_t u8; + uint16_t u16; + uint32_t u32; + struct { + uint8_t *msg; + size_t len; + } msg; + struct { + uint8_t *start; + size_t len; + } qidvec; + uint8_t qid[QIDSIZE]; + } v; +}; + +enum { + OP_REST, + OP_ASSIGN, + OP_ASSERT, + OP_FUNCALL, + OP_LITERAL, + OP_VAR, + OP_CAST, + OP_CMP_EQ, + OP_CMP_LEQ, + OP_FACCESS, + OP_SFAIL, + OP_VARGS, +}; + +struct proc; + +struct op { + struct op *next; + int type; + union { + struct { + char *name; + struct op *expr; + } assign; + struct op *assert; + struct { + struct proc *proc; + struct op *argv; + int argc; + } funcall; + struct value literal; + char *var; + struct { + struct op *expr; + int totype; + } cast; + struct { + struct op *a; + struct op *b; + } bin_cmp; + struct { + struct op *expr; + char *field; + } faccess; + struct { + char *msg; + struct op *expr; + } sfail; + } v; +}; + +TAILQ_HEAD(bindings, binding); +struct binding { + TAILQ_ENTRY(binding) entry; + char *name; + struct value val; + + /* + * Hack to support varargs. We set a special variable named + * "..." that contains the list of ops that will evaluate to + * the arguments. + */ + struct op *raw; +}; + +TAILQ_HEAD(envs, env); +struct env { + TAILQ_ENTRY(env) entry; + struct bindings bindings; +}; + +TAILQ_HEAD(opstacks, opstack); +struct opstack { + TAILQ_ENTRY(opstack) entry; + struct op base; + struct op *last; + int counter; +}; + +TAILQ_HEAD(procs, proc); +struct proc { + TAILQ_ENTRY(proc) entry; + char *name; + int minargs; + int vararg; + char *args[MAXWELEM]; + struct op *body; + int (*nativefn)(int); +}; + +TAILQ_HEAD(tests, test); +struct test { + TAILQ_ENTRY(test) entry; + int shouldfail; + char *name; + char *dir; + struct op *body; +}; + +enum { + EVAL_OK, + EVAL_ERR, + EVAL_SKIP, +}; + +int global_set(char *, struct op *); + +struct op *newop(int); +void free_op_rec(struct op *); +void free_op(struct op *); +struct op *op_rest(void); +struct op *op_assign(char *, struct op *); +struct op *op_assert(struct op *); +struct op *op_var(char *); +struct op *op_lit_str(char *); +struct op *op_lit_num(uint64_t); +struct op *op_cmp_eq(struct op *, struct op *); +struct op *op_cmp_leq(struct op *, struct op *); +struct op *op_cast(struct op *, int); +struct op *op_faccess(struct op *, char *); +struct op *op_sfail(struct op *, char *); +struct op *op_vargs(void); + +void ppf_val(FILE *, struct value *); +void pp_val(struct value *); +void pp_val(struct value *); +const char *val_type(struct value *); +int val_trueish(struct value *); +int val_isnum(struct value *); +int64_t val_tonum(struct value *); +int val_eq(struct value *, struct value *); +int val_leq(struct value *, struct value *); +int val_cast(struct value *, int); +int val_faccess(struct value *, const char *, struct value *); +void pp_op(struct op *); +void pp_block(struct op *); +int eval(struct op *); + +/* funcall */ +void prepare_funcall(void); +void push_arg(struct op *); +struct op *op_funcall(struct proc *); + +/* proc */ +void add_builtin_proc(const char *name, int (*)(int), int, int); +void prepare_proc(void); +/* push_arg works on procs too */ +int proc_setup_body(void); +void proc_done(char *name); +void block_push(struct op *); +struct proc *proc_by_name(const char *); + +/* testing */ +void prepare_test(void); +void test_done(int, char *, char *); + +/* np.y */ +void loadfile(const char *); + +#endif blob - 16e2d92655d1686171735ca8ef78d3ddb69907f9 (mode 644) blob + /dev/null --- control.c +++ /dev/null @@ -1,291 +0,0 @@ -/* - * Copyright (c) 2003, 2004 Henning Brauer - * - * Permission to use, copy, modify, and distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. - * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - */ - -#include "compat.h" - -#include -#include -#include -#include -#include - -#include -#include - -#include -#include -#include -#include - -#include "control.h" -#include "kamid.h" -#include "listener.h" -#include "log.h" -#include "utils.h" - -#define CONTROL_BACKLOG 5 - -struct { - struct event ev; - struct event evt; - int fd; -} control_state = {.fd = -1}; - -struct ctl_conn { - TAILQ_ENTRY(ctl_conn) entry; - struct imsgev iev; -}; - -TAILQ_HEAD(ctl_conns, ctl_conn) ctl_conns = TAILQ_HEAD_INITIALIZER(ctl_conns); - -struct ctl_conn *control_connbyfd(int); -struct ctl_conn *control_connbypid(pid_t); -void control_close(int); - -int -control_init(const char *path) -{ - struct sockaddr_un sun; - int fd; - mode_t old_umask; - - if ((fd = socket(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC | SOCK_NONBLOCK, - 0)) == -1) { - log_warn("%s: socket", __func__); - return (-1); - } - - memset(&sun, 0, sizeof(sun)); - sun.sun_family = AF_UNIX; - strlcpy(sun.sun_path, path, sizeof(sun.sun_path)); - - if (unlink(path) == -1) - if (errno != ENOENT) { - log_warn("%s: unlink %s", __func__, path); - close(fd); - return (-1); - } - - old_umask = umask(S_IXUSR|S_IXGRP|S_IWOTH|S_IROTH|S_IXOTH); - if (bind(fd, (struct sockaddr *)&sun, sizeof(sun)) == -1) { - log_warn("%s: bind: %s", __func__, path); - close(fd); - umask(old_umask); - return (-1); - } - umask(old_umask); - - if (chmod(path, S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP) == -1) { - log_warn("%s: chmod", __func__); - close(fd); - (void)unlink(path); - return (-1); - } - - return (fd); -} - -int -control_listen(int fd) -{ - if (control_state.fd != -1) - fatalx("%s: received unexpected controlsock", __func__); - - control_state.fd = fd; - if (listen(control_state.fd, CONTROL_BACKLOG) == -1) { - log_warn("%s: listen", __func__); - return (-1); - } - - event_set(&control_state.ev, control_state.fd, EV_READ, - control_accept, NULL); - event_add(&control_state.ev, NULL); - evtimer_set(&control_state.evt, control_accept, NULL); - - return (0); -} - -void -control_accept(int listenfd, short event, void *bula) -{ - int connfd; - socklen_t len; - struct sockaddr_un sun; - struct ctl_conn *c; - - event_add(&control_state.ev, NULL); - if ((event & EV_TIMEOUT)) - return; - - len = sizeof(sun); - if ((connfd = accept4(listenfd, (struct sockaddr *)&sun, &len, - SOCK_CLOEXEC | SOCK_NONBLOCK)) == -1) { - /* - * Pause accept if we are out of file descriptors, or - * libevent will haunt us here too. - */ - if (errno == ENFILE || errno == EMFILE) { - struct timeval evtpause = { 1, 0 }; - - event_del(&control_state.ev); - evtimer_add(&control_state.evt, &evtpause); - } else if (errno != EWOULDBLOCK && errno != EINTR && - errno != ECONNABORTED) - log_warn("%s: accept4", __func__); - return; - } - - if ((c = calloc(1, sizeof(struct ctl_conn))) == NULL) { - log_warn("%s: calloc", __func__); - close(connfd); - return; - } - - imsg_init(&c->iev.ibuf, connfd); - c->iev.handler = control_dispatch_imsg; - c->iev.events = EV_READ; - event_set(&c->iev.ev, c->iev.ibuf.fd, c->iev.events, - c->iev.handler, &c->iev); - event_add(&c->iev.ev, NULL); - - TAILQ_INSERT_TAIL(&ctl_conns, c, entry); -} - -struct ctl_conn * -control_connbyfd(int fd) -{ - struct ctl_conn *c; - - TAILQ_FOREACH(c, &ctl_conns, entry) { - if (c->iev.ibuf.fd == fd) - break; - } - - return (c); -} - -struct ctl_conn * -control_connbypid(pid_t pid) -{ - struct ctl_conn *c; - - TAILQ_FOREACH(c, &ctl_conns, entry) { - if (c->iev.ibuf.pid == pid) - break; - } - - return (c); -} - -void -control_close(int fd) -{ - struct ctl_conn *c; - - if ((c = control_connbyfd(fd)) == NULL) { - log_warnx("%s: fd %d: not found", __func__, fd); - return; - } - - msgbuf_clear(&c->iev.ibuf.w); - TAILQ_REMOVE(&ctl_conns, c, entry); - - event_del(&c->iev.ev); - close(c->iev.ibuf.fd); - - /* Some file descriptors are available again. */ - if (evtimer_pending(&control_state.evt, NULL)) { - evtimer_del(&control_state.evt); - event_add(&control_state.ev, NULL); - } - - free(c); -} - -void -control_dispatch_imsg(int fd, short event, void *bula) -{ - struct ctl_conn *c; - struct imsg imsg; - ssize_t n; - int verbose; - - if ((c = control_connbyfd(fd)) == NULL) { - log_warnx("%s: fd %d: not found", __func__, fd); - return; - } - - if (event & EV_READ) { - if (((n = imsg_read(&c->iev.ibuf)) == -1 && errno != EAGAIN) || - n == 0) { - control_close(fd); - return; - } - } - if (event & EV_WRITE) { - if (msgbuf_write(&c->iev.ibuf.w) <= 0 && errno != EAGAIN) { - control_close(fd); - return; - } - } - - for (;;) { - if ((n = imsg_get(&c->iev.ibuf, &imsg)) == -1) { - control_close(fd); - return; - } - if (n == 0) - break; - - switch (imsg.hdr.type) { - case IMSG_CTL_RELOAD: - listener_imsg_compose_main(imsg.hdr.type, 0, NULL, 0); - break; - case IMSG_CTL_LOG_VERBOSE: - if (IMSG_DATA_SIZE(imsg) != sizeof(verbose)) - break; - - /* Forward to all other processes. */ - listener_imsg_compose_main(imsg.hdr.type, imsg.hdr.pid, - imsg.data, IMSG_DATA_SIZE(imsg)); - - /* XXX: send to every client? */ - - memcpy(&verbose, imsg.data, sizeof(verbose)); - log_setverbose(verbose); - break; - default: - log_debug("%s: error handling imsg %d", __func__, - imsg.hdr.type); - break; - } - imsg_free(&imsg); - } - - imsg_event_add(&c->iev); -} - -int -control_imsg_relay(struct imsg *imsg) -{ - struct ctl_conn *c; - - if ((c = control_connbypid(imsg->hdr.pid)) == NULL) - return (0); - - return (imsg_compose_event(&c->iev, imsg->hdr.type, 0, imsg->hdr.pid, - -1, imsg->data, IMSG_DATA_SIZE(*imsg))); -} blob - 81ce3ca4ad9e77106d6d9b9d2f3d4af999039bdc (mode 644) blob + /dev/null --- control.h +++ /dev/null @@ -1,28 +0,0 @@ -/* - * Copyright (c) 2003, 2004 Henning Brauer - * - * Permission to use, copy, modify, and distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. - * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - */ - -#ifndef CONTROL_H -#define CONTROL_H - -#include "compat.h" - -int control_init(const char *); -int control_listen(int fd); -void control_accept(int, short, void *); -void control_dispatch_imsg(int, short, void *); -int control_imsg_relay(struct imsg *); - -#endif blob - 491222d3473842f9bcedd0560be0a98b6150472b (mode 644) blob + /dev/null --- ctl_parser.c +++ /dev/null @@ -1,151 +0,0 @@ -/* - * Copyright (c) 2004 Esben Norby - * Copyright (c) 2003, 2004 Henning Brauer - * - * Permission to use, copy, modify, and distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. - * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - */ - -#include -#include - -#include "ctl_parser.h" -#include "kamid.h" - -enum token_type { - NOTOKEN, - ENDTOKEN, - KEYWORD, -}; - - -struct token { - enum token_type type; - const char *keyword; - int value; - const struct token *next; -}; - -static const struct token t_main[]; -static const struct token t_log[]; - -static const struct token t_main[] = { - {KEYWORD, "reload", RELOAD, NULL}, - {KEYWORD, "log", NONE, t_log}, - {ENDTOKEN, "", NONE, NULL}, -}; - -static const struct token t_log[] = { - {KEYWORD, "verbose", LOG_VERBOSE, NULL}, - {KEYWORD, "brief", LOG_BRIEF, NULL}, - {ENDTOKEN, "", NONE, NULL}, -}; - -static const struct token *match_token(const char *, const struct token *, - struct parse_result *); -static void show_valid_args(const struct token *); - -struct parse_result * -parse(int argc, char **argv) -{ - static struct parse_result res; - const struct token *table = t_main; - const struct token *match; - - memset(&res, 0, sizeof(res)); - - while (argc >= 0) { - if ((match = match_token(argv[0], table, &res)) == NULL) { - fprintf(stderr, "valid commands/args:\n"); - show_valid_args(table); - return NULL; - } - - argc--; - argv++; - - if (match->type == NOTOKEN || match->next == NULL) - break; - - table = match->next; - } - - if (argc > 0) { - fprintf(stderr, "superfluous argument: %s\n", argv[0]); - return NULL; - } - - return &res; -} - -static const struct token * -match_token(const char *word, const struct token *table, - struct parse_result *res) -{ - size_t i, match; - const struct token *t = NULL; - - match = 0; - - for (i = 0; table[i].type != ENDTOKEN; i++) { - switch (table[i].type) { - case NOTOKEN: - if (word == NULL || strlen(word) == 0) { - match++; - t = &table[i]; - } - break; - case KEYWORD: - if (word != NULL && strncmp(word, table[i].keyword, - strlen(word)) == 0) { - match++; - t = &table[i]; - if (t->value) - res->action = t->value; - } - break; - case ENDTOKEN: - break; - } - } - - if (match != 1) { - if (word == NULL) - fprintf(stderr, "missing argument:\n"); - else if (match > 1) - fprintf(stderr, "ambiuous argument: %s\n", word); - else if (match < 1) - fprintf(stderr, "unknown argument: %s\n", word); - return NULL; - } - - return t; -} - -static void -show_valid_args(const struct token *table) -{ - int i; - - for (i = 0; table[i].type != ENDTOKEN; i++) { - switch (table[i].type) { - case NOTOKEN: - fprintf(stderr, " \n"); - break; - case KEYWORD: - fprintf(stderr, " %s\n", table[i].keyword); - break; - case ENDTOKEN: - break; - } - } -} blob - 1c41496f316a505b67099be11f5f5d3deec51a7b (mode 644) blob + /dev/null --- ctl_parser.h +++ /dev/null @@ -1,34 +0,0 @@ -/* - * Copyright (c) 2004 Esben Norby - * Copyright (c) 2003, 2004 Henning Brauer - * - * Permission to use, copy, modify, and distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. - * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - */ - -#ifndef CTL_PARSER_H -#define CTL_PARSER_H - -enum actions { - NONE, - LOG_VERBOSE, - LOG_BRIEF, - RELOAD, -}; - -struct parse_result { - enum actions action; -}; - -struct parse_result *parse(int, char **); - -#endif blob - 3a202598199dd05601178ee98af2b1bd2a8df942 (mode 644) blob + /dev/null --- ftp.c +++ /dev/null @@ -1,1011 +0,0 @@ -/* - * Copyright (c) 2021 Omar Polo - * - * Permission to use, copy, modify, and distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. - * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - */ - -#include "compat.h" - -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#if HAVE_LIBREADLINE -#include -#include -#endif - -#ifndef nitems -#define nitems(_a) (sizeof((_a)) / sizeof((_a)[0])) -#endif - -#include "9pclib.h" -#include "kamid.h" -#include "utils.h" -#include "log.h" - -/* flags */ -int tls; -const char *crtpath; -const char *keypath; - -/* state */ -struct tls_config *tlsconf; -struct tls *ctx; -int sock; -struct evbuffer *buf; -struct evbuffer *dirbuf; -uint32_t msize; -int bell; - -volatile sig_atomic_t resized; -int tty_p; -int tty_width; - -struct np_stat { - uint16_t type; - uint32_t dev; - struct qid qid; - uint32_t mode; - uint32_t atime; - uint32_t mtime; - uint64_t length; - char *name; - char *uid; - char *gid; - char *muid; -}; - -struct progress { - uint64_t max; - uint64_t done; -}; - -int pwdfid; - -#define ASSERT_EMPTYBUF() assert(EVBUFFER_LENGTH(buf) == 0) - -#if HAVE_LIBREADLINE -static char * -read_line(const char *prompt) -{ - char *line; - -again: - if ((line = readline(prompt)) == NULL) - return NULL; - /* XXX: trim spaces? */ - if (*line == '\0') { - free(line); - goto again; - } - - add_history(line); - return line; -} -#else -static char * -read_line(const char *prompt) -{ - char *ch, *line = NULL; - size_t linesize = 0; - ssize_t linelen; - - printf("%s", prompt); - fflush(stdout); - - linelen = getline(&line, &linesize, stdin); - if (linelen == -1) - return NULL; - - if ((ch = strchr(line, '\n')) != NULL) - *ch = '\0'; - return line; -} -#endif - -static void -tty_resized(int signo) -{ - resized = 1; -} - -static void __dead -usage(int ret) -{ - fprintf(stderr, "usage: %s [-c] host[:port] [path]\n", - getprogname()); - fprintf(stderr, PACKAGE_NAME " suite version " PACKAGE VERSION "\n"); - exit(ret); -} - -static void -do_send(void) -{ - const void *buf; - size_t nbytes; - ssize_t r; - - while (EVBUFFER_LENGTH(evb) != 0) { - buf = EVBUFFER_DATA(evb); - nbytes = EVBUFFER_LENGTH(evb); - - if (ctx == NULL) { - r = write(sock, buf, nbytes); - if (r == 0 || r == -1) - errx(1, "EOF"); - } else { - r = tls_write(ctx, buf, nbytes); - if (r == TLS_WANT_POLLIN || r == TLS_WANT_POLLOUT) - continue; - if (r == -1) - errx(1, "tls: %s", tls_error(ctx)); - } - - evbuffer_drain(evb, r); - } -} - -static void -mustread(void *d, size_t len) -{ - ssize_t r; - - while (len != 0) { - if (ctx == NULL) { - r = read(sock, d, len); - if (r == 0 || r == -1) - errx(1, "EOF"); - } else { - r = tls_read(ctx, d, len); - if (r == TLS_WANT_POLLIN || r == TLS_WANT_POLLOUT) - continue; - if (r == -1) - errx(1, "tls: %s", tls_error(ctx)); - } - - d += r; - len -= r; - } -} - -static void -recv_msg(void) -{ - uint32_t len, l; - char tmp[BUFSIZ]; - - mustread(&len, sizeof(len)); - len = le32toh(len); - if (len < HEADERSIZE) - errx(1, "read message of invalid length %d", len); - - len -= 4; /* skip the length just read */ - - while (len != 0) { - l = MIN(len, sizeof(tmp)); - mustread(tmp, l); - len -= l; - evbuffer_add(buf, tmp, l); - } -} - -static uint64_t -np_read64(struct evbuffer *buf) -{ - uint64_t n; - - evbuffer_remove(buf, &n, sizeof(n)); - return le64toh(n); -} - -static uint32_t -np_read32(struct evbuffer *buf) -{ - uint32_t n; - - evbuffer_remove(buf, &n, sizeof(n)); - return le32toh(n); -} - -static uint16_t -np_read16(struct evbuffer *buf) -{ - uint16_t n; - - evbuffer_remove(buf, &n, sizeof(n)); - return le16toh(n); -} - -static uint16_t -np_read8(struct evbuffer *buf) -{ - uint8_t n; - - evbuffer_remove(buf, &n, sizeof(n)); - return n; -} - -static char * -np_readstr(struct evbuffer *buf) -{ - uint16_t len; - char *str; - - len = np_read16(buf); - assert(EVBUFFER_LENGTH(buf) >= len); - - if ((str = calloc(1, len+1)) == NULL) - err(1, "calloc"); - evbuffer_remove(buf, str, len); - return str; -} - -static void -np_read_qid(struct evbuffer *buf, struct qid *qid) -{ - assert(EVBUFFER_LENGTH(buf) >= QIDSIZE); - - qid->type = np_read8(buf); - qid->vers = np_read32(buf); - qid->path = np_read64(buf); -} - -static int -np_read_stat(struct evbuffer *buf, struct np_stat *st) -{ - uint16_t size; - - memset(st, 0, sizeof(*st)); - - size = np_read16(buf); - if (size > EVBUFFER_LENGTH(buf)) - return -1; - - st->type = np_read16(buf); - st->dev = np_read32(buf); - np_read_qid(buf, &st->qid); - st->mode = np_read32(buf); - st->atime = np_read32(buf); - st->mtime = np_read32(buf); - st->length = np_read64(buf); - st->name = np_readstr(buf); - st->uid = np_readstr(buf); - st->gid = np_readstr(buf); - st->muid = np_readstr(buf); - - return 0; -} - -static void -expect(uint8_t type) -{ - uint8_t t; - - t = np_read8(buf); - if (t == type) - return; - - if (t == Rerror) { - char *err; - - /* skip tag */ - np_read16(buf); - - err = np_readstr(buf); - errx(1, "expected %s, got error %s", - pp_msg_type(type), err); - } - - errx(1, "expected %s, got msg type %s", - pp_msg_type(type), pp_msg_type(t)); -} - -static void -expect2(uint8_t type, uint16_t tag) -{ - uint16_t t; - - expect(type); - - t = np_read16(buf); - if (t == tag) - return; - - errx(1, "expected tag 0x%x, got 0x%x", tag, t); -} - -static void -do_version(void) -{ - char *version; - - tversion(VERSION9P, MSIZE9P); - do_send(); - recv_msg(); - expect2(Rversion, NOTAG); - - msize = np_read32(buf); - version = np_readstr(buf); - - if (msize > MSIZE9P) - errx(1, "got unexpected msize: %d", msize); - if (strcmp(version, VERSION9P)) - errx(1, "unexpected 9p version: %s", version); - - free(version); - ASSERT_EMPTYBUF(); -} - -static void -do_attach(const char *path) -{ - const char *user; - struct qid qid; - - if (path == NULL) - path = "/"; - if ((user = getenv("USER")) == NULL) - user = "flan"; - - tattach(pwdfid, NOFID, user, path); - do_send(); - recv_msg(); - expect2(Rattach, iota_tag); - np_read_qid(buf, &qid); - - ASSERT_EMPTYBUF(); -} - -static uint32_t -do_open(uint32_t fid, uint8_t mode) -{ - struct qid qid; - uint32_t iounit; - - topen(fid, mode); - do_send(); - recv_msg(); - expect2(Ropen, iota_tag); - - np_read_qid(buf, &qid); - iounit = np_read32(buf); - - ASSERT_EMPTYBUF(); - - return iounit; -} - -static void -do_clunk(uint32_t fid) -{ - tclunk(fid); - do_send(); - recv_msg(); - expect2(Rclunk, iota_tag); - - ASSERT_EMPTYBUF(); -} - -static void -dup_fid(int fid, int nfid) -{ - uint16_t nwqid; - - twalk(fid, nfid, NULL, 0); - do_send(); - recv_msg(); - expect2(Rwalk, iota_tag); - - nwqid = np_read16(buf); - assert(nwqid == 0); - - ASSERT_EMPTYBUF(); -} - -static int -walk_path(int fid, int newfid, const char *path, struct qid *qid) -{ - char *wnames[MAXWELEM], *p, *t; - size_t nwname, i; - uint16_t nwqid; - - if ((p = strdup(path)) == NULL) - err(1, "strdup"); - t = p; - - /* strip initial ./ */ - if (t[0] == '.' && t[1] == '/') - t += 2; - - for (nwname = 0; nwname < nitems(wnames) && - (wnames[nwname] = strsep(&t, "/")) != NULL;) { - if (*wnames[nwname] != '\0') - nwname++; - } - - twalk(fid, newfid, (const char **)wnames, nwname); - do_send(); - recv_msg(); - expect2(Rwalk, iota_tag); - - nwqid = np_read16(buf); - assert(nwqid <= nwname); - - /* consume all qids */ - for (i = 0; i < nwname; ++i) - np_read_qid(buf, qid); - - free(p); - - return nwqid == nwname; -} - -static void -do_stat(int fid, struct np_stat *st) -{ - tstat(fid); - do_send(); - recv_msg(); - expect2(Rstat, iota_tag); - - if (np_read_stat(buf, st) == -1) - errx(1, "invalid stat struct read"); - - ASSERT_EMPTYBUF(); -} - -static size_t -do_read(int fid, uint64_t off, uint32_t count, void *data) -{ - uint32_t r; - - tread(fid, off, count); - do_send(); - recv_msg(); - expect2(Rread, iota_tag); - - r = np_read32(buf); - assert(r == EVBUFFER_LENGTH(buf)); - assert(r <= count); - evbuffer_remove(buf, data, r); - - ASSERT_EMPTYBUF(); - - return r; -} - -static void -draw_progress(const char *pre, const struct progress *p) -{ - struct winsize ws; - int i, l, w; - double perc; - - perc = 100.0 * p->done / p->max; - if (!tty_p) { - fprintf(stderr, "%s: %d%%\n", pre, (int)perc); - return; - } - - if (resized) { - resized = 0; - - if (ioctl(0, TIOCGWINSZ, &ws) == -1) - return; - tty_width = ws.ws_col; - } - w = tty_width; - - if (pre == NULL || - ((l = printf("\r%s ", pre)) == -1 || l >= w)) - return; - - w -= l + 2 + 5; /* 2 for |, 5 for percentage + \n */ - if (w < 0) { - printf("%4d%%\n", (int)perc); - return; - } - - printf("|"); - - l = w * MIN(100.0, perc) / 100.0; - for (i = 0; i < l; i++) - printf("*"); - for (; i < w; i++) - printf(" "); - printf("|%4d%%", (int)perc); - - fflush(stdout); -} - -static int -fetch_fid(int fid, const char *path) -{ - struct progress p = {0}; - struct np_stat st; - size_t r; - int fd; - char buf[BUFSIZ]; - - do_stat(fid, &st); - do_open(fid, KOREAD); - - if ((fd = open(path, O_WRONLY|O_CREAT|O_TRUNC, 0644)) == -1) { - warn("can't open %s", path); - return -1; - } - - p.max = st.length; - for (;;) { - size_t siz, off; - ssize_t nw; - - r = do_read(fid, p.done, sizeof(buf), buf); - if (r == 0) - break; - - siz = sizeof(buf); - for (off = 0; off < siz; off += nw) - if ((nw = write(fd, buf + off, siz - off)) == 0 || - nw == -1) - err(1, "write"); - - p.done += r; - draw_progress(path, &p); - -#if 0 - /* throttle, for debugging purpose */ - { - struct timespec ts = { 0, 500000000 }; - nanosleep(&ts, NULL); - } -#endif - } - - putchar('\n'); - - close(fd); - do_clunk(fid); - return 0; -} - -static void -do_tls_connect(const char *host, const char *port) -{ - int handshake; - - if ((tlsconf = tls_config_new()) == NULL) - fatalx("tls_config_new"); - tls_config_insecure_noverifycert(tlsconf); - tls_config_insecure_noverifyname(tlsconf); - if (tls_config_set_keypair_file(tlsconf, crtpath, keypath) == -1) - fatalx("can't load certs (%s, %s)", crtpath, keypath); - - if ((ctx = tls_client()) == NULL) - fatal("tls_client"); - if (tls_configure(ctx, tlsconf) == -1) - fatalx("tls_configure: %s", tls_error(ctx)); - - if (tls_connect(ctx, host, port) == -1) - fatalx("can't connect to %s:%s: %s", host, port, - tls_error(ctx)); - - for (handshake = 0; !handshake;) { - switch (tls_handshake(ctx)) { - case -1: - fatalx("tls_handshake: %s", tls_error(ctx)); - case 0: - handshake = 1; - break; - } - } -} - -static void -do_ctxt_connect(const char *host, const char *port) -{ - struct addrinfo hints, *res, *res0; - int error, saved_errno; - const char *cause = NULL; - - memset(&hints, 0, sizeof(hints)); - hints.ai_family = AF_UNSPEC; - hints.ai_socktype = SOCK_STREAM; - error = getaddrinfo(host, port, &hints, &res0); - if (error) - errx(1, "%s", gai_strerror(error)); - - sock = -1; - for (res = res0; res != NULL; res = res->ai_next) { - sock = socket(res->ai_family, res->ai_socktype, - res->ai_protocol); - if (sock == -1) { - cause = "socket"; - continue; - } - - if (connect(sock, res->ai_addr, res->ai_addrlen) == -1) { - cause = "connect"; - saved_errno = errno; - close(sock); - errno = saved_errno; - sock = -1; - continue; - } - - break; - } - - if (sock == -1) - err(1, "%s", cause); - freeaddrinfo(res0); -} - -static void -do_connect(const char *connspec, const char *path) -{ - char *host, *colon; - const char *port; - - host = xstrdup(connspec); - if ((colon = strchr(host, ':')) != NULL) { - *colon = '\0'; - port = ++colon; - } else - port = "1337"; - - printf("connecting to %s:%s...", host, port); - fflush(stdout); - - if (tls) - do_tls_connect(host, port); - else - do_ctxt_connect(host, port); - - printf(" done!\n"); - - do_version(); - do_attach(path); - - free(host); -} - -static void -cmd_bell(int argc, const char **argv) -{ - if (argc == 0) { - bell = !bell; - if (bell) - puts("bell mode enabled"); - else - puts("bell mode disabled"); - return; - } - - if (argc != 1) - goto usage; - - if (!strcmp(*argv, "on")) { - bell = 1; - puts("bell mode enabled"); - return; - } - - if (!strcmp(*argv, "off")) { - bell = 0; - puts("bell mode disabled"); - return; - } - -usage: - printf("bell [on | off]\n"); -} - -static void -cmd_bye(int argc, const char **argv) -{ - log_warnx("bye\n"); - exit(0); -} - -static void -cmd_cd(int argc, const char **argv) -{ - struct qid qid; - int nfid; - - if (argc != 1) { - printf("usage: cd remote-path\n"); - return; - } - - nfid = pwdfid+1; - if (walk_path(pwdfid, nfid, argv[0], &qid) == -1 || - !(qid.type & QTDIR)) { - printf("can't cd %s\n", argv[0]); - do_clunk(nfid); - } else { - do_clunk(pwdfid); - pwdfid = nfid; - } -} - -static void -cmd_get(int argc, const char **argv) -{ - struct qid qid; - const char *l; - int nfid; - - if (argc != 1 && argc != 2) { - printf("usage: get remote-file [local-file]\n"); - return; - } - - if (argc == 2) - l = argv[1]; - else if ((l = strrchr(argv[0], '/')) != NULL) - l++; /* skip / */ - else - l = argv[1]; - - nfid = pwdfid+1; - if (walk_path(pwdfid, nfid, argv[0], &qid) == -1) { - printf("can't fetch %s\n", argv[0]); - return; - } - - if (qid.type != 0) { - printf("can't fetch %s\n", argv[0]); - do_clunk(nfid); - return; - } - - fetch_fid(nfid, l); -} - -static void -cmd_lcd(int argc, const char **argv) -{ - const char *dir; - - if (argc > 1) { - printf("lcd takes only one argument\n"); - return; - } - - if (argc == 1) - dir = *argv; - - if (argc == 0 && (dir = getenv("HOME")) == NULL) { - printf("HOME is not defined\n"); - return; - } - - if (chdir(dir) == -1) - printf("cd: %s: %s\n", dir, strerror(errno)); -} - -static void -cmd_lpwd(int argc, const char **argv) -{ - char path[PATH_MAX]; - - if (getcwd(path, sizeof(path)) == NULL) { - printf("lpwd: %s\n", strerror(errno)); - return; - } - - printf("%s\n", path); -} - -static void -cmd_ls(int argc, const char **argv) -{ - struct np_stat st; - uint64_t off = 0; - uint32_t len; - char fmt[FMT_SCALED_STRSIZE]; - - if (argc != 0) { - printf("ls don't take arguments (yet)\n"); - return; - } - - dup_fid(pwdfid, 1); - do_open(1, KOREAD); - - evbuffer_drain(dirbuf, EVBUFFER_LENGTH(dirbuf)); - - for (;;) { - tread(1, off, BUFSIZ); - do_send(); - recv_msg(); - expect2(Rread, iota_tag); - - len = np_read32(buf); - if (len == 0) - break; - - evbuffer_add_buffer(dirbuf, buf); - off += len; - - ASSERT_EMPTYBUF(); - } - - while (EVBUFFER_LENGTH(dirbuf) != 0) { - if (np_read_stat(dirbuf, &st) == -1) - errx(1, "invalid stat struct read"); - - if (fmt_scaled(st.length, fmt) == -1) - strlcpy(fmt, "xxx", sizeof(fmt)); - - printf("%4s %8s %s\n", pp_qid_type(st.qid.type), fmt, st.name); - - free(st.name); - free(st.uid); - free(st.gid); - free(st.muid); - } - - do_clunk(1); -} - -static void -cmd_verbose(int argc, const char **argv) -{ - if (argc == 0) { - log_setverbose(!log_getverbose()); - if (log_getverbose()) - puts("verbose mode enabled"); - else - puts("verbose mode disabled"); - return; - } - - if (argc != 1) - goto usage; - - if (!strcmp(*argv, "on")) { - log_setverbose(1); - puts("verbose mode enabled"); - return; - } - - if (!strcmp(*argv, "off")) { - log_setverbose(0); - puts("verbose mode disabled"); - return; - } - -usage: - printf("verbose [on | off]\n"); -} - -static void -excmd(int argc, const char **argv) -{ - struct cmd { - const char *name; - void (*fn)(int, const char **); - } cmds[] = { - {"bell", cmd_bell}, - {"bye", cmd_bye}, - {"cd", cmd_cd}, - {"get", cmd_get}, - {"lcd", cmd_lcd}, - {"lpwd", cmd_lpwd}, - {"ls", cmd_ls}, - {"quit", cmd_bye}, - {"verbose", cmd_verbose}, - }; - size_t i; - - if (argc == 0) - return; - for (i = 0; i < nitems(cmds); ++i) { - if (!strcmp(cmds[i].name, *argv)) { - cmds[i].fn(argc-1, argv+1); - return; - } - } - - log_warnx("unknown command %s", *argv); -} - -int -main(int argc, char **argv) -{ - int ch; - - log_init(1, LOG_DAEMON); - log_setverbose(0); - log_procinit(getprogname()); - - while ((ch = getopt(argc, argv, "C:cK:")) != -1) { - switch (ch) { - case 'C': - crtpath = optarg; - break; - case 'c': - tls = 1; - break; - case 'K': - keypath = optarg; - break; - default: - usage(1); - } - } - argc -= optind; - argv += optind; - - if (argc == 0) - usage(1); - - if (isatty(1)) { - tty_p = 1; - resized = 1; - signal(SIGWINCH, tty_resized); - } - - if ((evb = evbuffer_new()) == NULL) - fatal("evbuffer_new"); - - if ((buf = evbuffer_new()) == NULL) - fatal("evbuffer_new"); - - if ((dirbuf = evbuffer_new()) == NULL) - fatal("evbuferr_new"); - - do_connect(argv[0], argv[1]); - - for (;;) { - int argc = 0; - char *line, *argv[16] = {0}, **ap; - - if ((line = read_line("kamiftp> ")) == NULL) - break; - - for (argc = 0, ap = argv; ap < &argv[15] && - (*ap = strsep(&line, " \t")) != NULL;) { - if (**ap != '\0') - ap++, argc++; - } - excmd(argc, (const char **)argv); - - if (bell) { - printf("\a"); - fflush(stdout); - } - - free(line); - } - - printf("\n"); -} blob - b263ba9f41d13275d28d6a215aa1419302259a89 (mode 644) blob + /dev/null --- kamictl.8 +++ /dev/null @@ -1,61 +0,0 @@ -.\" Copyright (c) 2021 Omar Polo -.\" -.\" Permission to use, copy, modify, and distribute this software for any -.\" purpose with or without fee is hereby granted, provided that the above -.\" copyright notice and this permission notice appear in all copies. -.\" -.\" THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES -.\" WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF -.\" MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR -.\" ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES -.\" WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN -.\" ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF -.\" OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. -.\" -.Dd $Mdocdate: July 07 2021 $ -.Dt KAMICTL 8 -.Os -.Sh NAME -.Nm kamictl -.Nd control the kamid daemon -.Sh SYNOPSIS -.Nm -.Op Fl s Ar socket -.Ar command -.Op Ar argument ... -.Sh DESCRIPTION -The -.Nm -program controls the -.Xr kamid 8 -daemon. -.Pp -The following options are available: -.Bl -tag -width Ds -.It Fl s Ar socket -Use -.Ar socket -instead of the default -.Pa /var/run/kamid.sock -to communicate with -.Xr kamid 8 . -.El -.Pp -The following commands are available: -.Bl -tag -width Ds -.It Cm log brief -Disable verbose debug logging. -.It Cm log verbose -Enable verbose debug logging. -.It Cm reload -Reload the configuration file. -.El -.Sh FILES -.Bl -tag -width "/var/run/kamid.sockXX" -compact -.It Pa /var/run/kamid.sock -UNIX-domain socket used for communication with -.Xr kamid 8 . -.El -.Sh SEE ALSO -.Xr kamid.conf 5 , -.Xr kamid 8 blob - 1a0172f56920d0407171fc51a81795eb6bef5233 (mode 644) blob + /dev/null --- kamictl.c +++ /dev/null @@ -1,142 +0,0 @@ -/* - * Copyright (c) 2021 Omar Polo - * - * Permission to use, copy, modify, and distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. - * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - */ - -#include "compat.h" - -#include -#include - -#include -#include -#include -#include -#include -#include - -#include "ctl_parser.h" -#include "kamid.h" -#include "log.h" - -__dead void usage(void); - -struct imsgbuf *ibuf; - -__dead void -usage(void) -{ - /* - * XXX: this will print `kamid' if compat/getprogname.c is - * used. - */ - fprintf(stderr, "usage: %s [-s socket] command [argument ...]\n", - getprogname()); - exit(1); -} - -int -main(int argc, char **argv) -{ - struct sockaddr_un sun; - struct parse_result *res; - struct imsg imsg; - int ctl_sock; - int done = 0; - int n, verbose = 0; - int ch; - const char *sockname; - - log_init(1, LOG_DAEMON); /* Log to stderr. */ - - sockname = KD_SOCKET; - while ((ch = getopt(argc, argv, "s:")) != -1) { - switch (ch) { - case 's': - sockname = optarg; - break; - default: - usage(); - } - } - argc -= optind; - argv += optind; - - /* parse command line */ - if ((res = parse(argc, argv)) == NULL) - exit(1); - - /* connect to control socket */ - if ((ctl_sock = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) - err(1, "socket"); - - memset(&sun, 0, sizeof(sun)); - sun.sun_family = AF_UNIX; - strlcpy(sun.sun_path, sockname, sizeof(sun.sun_path)); - - if (connect(ctl_sock, (struct sockaddr*)&sun, sizeof(sun)) == -1) - err(1, "connect: %s", sockname); - -#ifdef __OpenBSD__ - if (pledge("stdio", NULL) == -1) - err(1, "pledge"); -#endif - - if ((ibuf = calloc(1, sizeof(*ibuf))) == NULL) - err(1, NULL); - imsg_init(ibuf, ctl_sock); - done = 0; - - /* process user request */ - switch (res->action) { - case LOG_VERBOSE: - verbose = 1; - /* fallthrough */ - case LOG_BRIEF: - imsg_compose(ibuf, IMSG_CTL_LOG_VERBOSE, 0, 0, -1, - &verbose, sizeof(verbose)); - puts("logging request sent."); - done = 1; - break; - case RELOAD: - imsg_compose(ibuf, IMSG_CTL_RELOAD, 0, 0, -1, NULL, 0); - puts("reload request sent."); - done = 1; - break; - default: - usage(); - } - - imsg_flush(ibuf); - - /* - * Later we may add commands which requires a response. - */ - while (!done) { - if ((n = imsg_get(ibuf, &imsg)) == -1) - errx(1, "imsg_get error"); - if (n == 0) - break; - - switch (res->action) { - default: - break; - } - imsg_free(&imsg); - } - close(ctl_sock); - free(ibuf); - - return 0; -} blob - e0f398aaf1fb243d4b11fbef4b3e08442fd9ea46 (mode 644) blob + /dev/null --- kamid.8 +++ /dev/null @@ -1,81 +0,0 @@ -.\" Copyright (c) 2021 Omar Polo -.\" -.\" Permission to use, copy, modify, and distribute this software for any -.\" purpose with or without fee is hereby granted, provided that the above -.\" copyright notice and this permission notice appear in all copies. -.\" -.\" THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES -.\" WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF -.\" MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR -.\" ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES -.\" WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN -.\" ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF -.\" OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. -.\" -.Dd $Mdocdate: July 07 2021 $ -.Dt KAMID 8 -.Os -.Sh NAME -.Nm kamid -.Nd 9p file server daemon -.Sh SYNOPSIS -.Nm -.Op Fl dnv -.Op Fl D Ar macro Ns = Ns Ar value -.Op Fl f Pa file -.Op Fl s Pa socket -.Sh DESCRIPTION -.Nm -is a 9p file server daemon. -.Pp -The options are as follows: -.Bl -tag -width Ds -.It Fl D Ar macro Ns = Ns Ar value -Set a -.Ar macro -to a -.Ar value . -Macros can be referenced in the configuration files. -.It Fl d -Do not daemonize. -If this option is specified, -.Nm -will run in the foreground and log to -.Em stderr . -.It Fl f Ar file -specify an alternative configuration file. -.It Fl n -Configtest mode. -Only check the configuration file for validity. -.It Fl s Ar socket -Use an alternate location for the default control socket. -.It Fl v -Produce more verbose output. -.El -.Sh FILES -.Bl -tag -width "/var/run/kamid.sockXX" -compact -.It Pa /etc/kamid.conf -Default -.Nm -configuration file. -.It Pa /var/run/kamid.sock -UNIX-domain socket used for communication with -.Xr kamictl 8 . -.El -.Sh SEE ALSO -.Xr kami.conf 5 , -.Xr kamictl 8 -.Sh AUTHORS -.An -nosplit -The -.Nm -program was written by -.An Omar Polo Aq Mt op@omarpolo.com . -.Sh CAVEATS -.Nm -doesn't handle very well when a user directory is over multiple -devices since it uses the inode of files to build the QID path field. -.Pp -Opening/creating a file with the -.Dv OEXEC -result in an error. blob - 05f753529776da3a4945281f04bec846131da94e (mode 644) blob + /dev/null --- kamid.c +++ /dev/null @@ -1,626 +0,0 @@ -/* - * Copyright (c) 2021 Omar Polo - * Copyright (c) 2018 Florian Obser - * Copyright (c) 2005 Claudio Jeker - * Copyright (c) 2004 Esben Norby - * Copyright (c) 2003, 2004 Henning Brauer - * - * Permission to use, copy, modify, and distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. - * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - */ - -#include -#include - -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "client.h" -#include "control.h" -#include "kamid.h" -#include "listener.h" -#include "log.h" -#include "sandbox.h" -#include "table.h" -#include "utils.h" - -enum kd_process { - PROC_MAIN, - PROC_LISTENER, - PROC_CLIENTCONN, -}; - -const char *saved_argv0; -static int debug, nflag; -int verbose; - -__dead void usage(void); - -void main_sig_handler(int, short, void *); -void main_dispatch_listener(int, short, void *); -int main_reload(void); -int main_imsg_send_config(struct kd_conf *); -void main_dispatch_listener(int, short, void *); -__dead void main_shutdown(void); - -static pid_t start_child(enum kd_process, int, int, int); - -struct kd_conf *main_conf; -static struct imsgev *iev_listener; -const char *conffile; -pid_t listener_pid; -uint32_t cmd_opts; - -__dead void -usage(void) -{ - fprintf(stderr, "usage: %s [-dnv] [-f file] [-s socket]\n", - getprogname()); - exit(1); -} - -int -main(int argc, char **argv) -{ - struct event ev_sigint, ev_sigterm, ev_sighup; - int ch; - int listener_flag = 0, client_flag = 0; - int pipe_main2listener[2]; - int control_fd; - const char *csock; - - conffile = KD_CONF_FILE; - csock = KD_SOCKET; - - log_init(1, LOG_DAEMON); /* Log to stderr until deamonized. */ - log_setverbose(1); - - saved_argv0 = argv[0]; - if (saved_argv0 == NULL) - saved_argv0 = "kamid"; - - while ((ch = getopt(argc, argv, "D:df:nsT:v")) != -1) { - switch (ch) { - case 'D': - if (cmdline_symset(optarg) == -1) - log_warnx("could not parse macro definition %s", - optarg); - break; - case 'd': - debug = 1; - break; - case 'f': - conffile = optarg; - break; - case 'n': - nflag = 1; - break; - case 's': - csock = optarg; - break; - case 'T': - switch (*optarg) { - case 'c': - client_flag = 1; - break; - case 'l': - listener_flag = 1; - break; - default: - fatalx("invalid process spec %c", *optarg); - } - break; - case 'v': - verbose = 1; - break; - default: - usage(); - } - } - - argc -= optind; - argv += optind; - if (argc > 0 || (listener_flag && client_flag)) - usage(); - - if (client_flag) - client(debug, verbose); - else if (listener_flag) - listener(debug, verbose); - - if ((main_conf = parse_config(conffile)) == NULL) - exit(1); - - if (nflag) { - fprintf(stderr, "configuration OK\n"); - exit(0); - } - - /* Check for root privileges. */ - if (geteuid()) - fatalx("need root privileges"); - - /* Check for assigned daemon user. */ - if (getpwnam(KD_USER) == NULL) - fatalx("unknown user %s", KD_USER); - - log_init(debug, LOG_DAEMON); - log_setverbose(verbose); - - if (!debug) - daemon(1, 0); - - log_info("startup"); - - if (socketpair(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC | SOCK_NONBLOCK, - PF_UNSPEC, pipe_main2listener) == -1) - fatal("main2listener socketpair"); - - /* Start children. */ - listener_pid = start_child(PROC_LISTENER, pipe_main2listener[1], - debug, verbose); - - log_procinit("main"); - - event_init(); - - /* Setup signal handler */ - signal_set(&ev_sigint, SIGINT, main_sig_handler, NULL); - signal_set(&ev_sigterm, SIGTERM, main_sig_handler, NULL); - signal_set(&ev_sighup, SIGHUP, main_sig_handler, NULL); - - signal_add(&ev_sigint, NULL); - signal_add(&ev_sigterm, NULL); - signal_add(&ev_sighup, NULL); - - signal(SIGCHLD, SIG_IGN); - signal(SIGPIPE, SIG_IGN); - - if ((iev_listener = malloc(sizeof(*iev_listener))) == NULL) - fatal(NULL); - imsg_init(&iev_listener->ibuf, pipe_main2listener[0]); - iev_listener->handler = main_dispatch_listener; - - /* Setup event handlers for pipes to listener. */ - iev_listener->events = EV_READ; - event_set(&iev_listener->ev, iev_listener->ibuf.fd, - iev_listener->events, iev_listener->handler, iev_listener); - event_add(&iev_listener->ev, NULL); - - if ((control_fd = control_init(csock)) == -1) - fatalx("control socket setup failed"); - - main_imsg_compose_listener(IMSG_CONTROLFD, control_fd, 0, - NULL, 0); - main_imsg_send_config(main_conf); - - sandbox_main(); - - event_dispatch(); - - main_shutdown(); - return 0; -} - -void -main_sig_handler(int sig, short event, void *arg) -{ - /* - * Normal signal handler rules don't apply because libevent - * decouples for us. - */ - - switch (sig) { - case SIGTERM: - case SIGINT: - main_shutdown(); - break; - case SIGHUP: - if (main_reload() == -1) - log_warnx("configuration reload failed"); - else - log_debug("configuration reloaded"); - break; - default: - fatalx("unexpected signal %d", sig); - } -} - -static inline struct table * -auth_table_by_id(uint32_t id) -{ - struct kd_listen_conf *listen; - - STAILQ_FOREACH(listen, &main_conf->listen_head, entry) { - if (listen->id == id) - return listen->auth_table; - } - - return NULL; -} - -static inline struct table * -virtual_table_by_id(uint32_t id) -{ - struct kd_listen_conf *listen; - - STAILQ_FOREACH(listen, &main_conf->listen_head, entry) { - if (listen->id == id) - return listen->virtual_table; - } - - return NULL; -} - -static inline struct table * -userdata_table_by_id(uint32_t id) -{ - struct kd_listen_conf *listen; - - STAILQ_FOREACH(listen, &main_conf->listen_head, entry) { - if (listen->id == id) - return listen->userdata_table; - } - - return NULL; -} - -static inline void -do_auth_tls(struct imsg *imsg) -{ - char *username = NULL, *user = NULL, *home = NULL, *local_user; - struct passwd *pw; - struct table *auth, *virt, *userdata; - struct kd_auth_req kauth; - int p[2], free_home = 1; - - if (sizeof(kauth) != IMSG_DATA_SIZE(*imsg)) - fatal("wrong size for IMSG_AUTH_TLS: " - "got %lu; want %lu", IMSG_DATA_SIZE(*imsg), - sizeof(kauth)); - memcpy(&kauth, imsg->data, sizeof(kauth)); - - if (memmem(kauth.hash, sizeof(kauth.hash), "", 1) == NULL) - fatal("non NUL-terminated hash received"); - - log_debug("tls id=%u hash=%s", kauth.listen_id, kauth.hash); - - if ((auth = auth_table_by_id(kauth.listen_id)) == NULL) - fatal("request for invalid listener id %d", imsg->hdr.pid); - - virt = virtual_table_by_id(kauth.listen_id); - userdata = userdata_table_by_id(kauth.listen_id); - - if (table_lookup(auth, kauth.hash, &username) == -1) { - log_warnx("login failed for hash %s", kauth.hash); - goto err; - } - - if (virt != NULL && table_lookup(virt, username, &user) == -1) { - log_warnx("virtual lookup failed for user %s", username); - goto err; - } - - /* the local user */ - local_user = user != NULL ? user : username; - - if (user != NULL) - log_debug("virtual user %s matched local user %s", - username, user); - else - log_debug("matched local user %s", username); - - if (userdata != NULL && table_lookup(userdata, username, &home) - == -1) { - log_warnx("userdata lookup failed for user %s", username); - goto err; - } else if (userdata == NULL) { - if ((pw = getpwnam(local_user)) == NULL) { - log_warnx("getpwnam(%s) failed", local_user); - goto err; - } - - free_home = 0; - home = pw->pw_dir; - } - - if (user != NULL) - log_debug("matched home %s for virtual user %s", - home, username); - else - log_debug("matched home %s for local user %s", - home, username); - - if (socketpair(AF_UNIX, SOCK_STREAM|SOCK_CLOEXEC|SOCK_NONBLOCK, - PF_UNSPEC, p) == -1) - fatal("socketpair"); - - start_child(PROC_CLIENTCONN, p[1], debug, verbose); - - main_imsg_compose_listener(IMSG_AUTH, p[0], imsg->hdr.peerid, - local_user, strlen(local_user)+1); - main_imsg_compose_listener(IMSG_AUTH_DIR, -1, imsg->hdr.peerid, - home, strlen(home)+1); - - free(username); - free(user); - if (free_home) - free(home); - return; - -err: - free(username); - free(user); - if (free_home) - free(home); - main_imsg_compose_listener(IMSG_AUTH, -1, imsg->hdr.peerid, - NULL, 0); -} - -void -main_dispatch_listener(int fd, short event, void *d) -{ - struct imsgev *iev = d; - struct imsgbuf *ibuf; - struct imsg imsg; - ssize_t n; - int shut = 0; - - ibuf = &iev->ibuf; - - if (event & EV_READ) { - if ((n = imsg_read(ibuf)) == -1 && errno != EAGAIN) - fatal("imsg_read error"); - if (n == 0) /* Connection closed. */ - shut = 1; - } - if (event & EV_WRITE) { - if ((n = msgbuf_write(&ibuf->w)) == -1 && errno != EAGAIN) - fatal("msgbuf_write"); - if (n == 0) /* Connection closed. */ - shut = 1; - } - - for (;;) { - if ((n = imsg_get(ibuf, &imsg)) == -1) - fatal("imsg_get"); - if (n == 0) /* No more messages. */ - break; - - switch (imsg.hdr.type) { - case IMSG_AUTH_TLS: - do_auth_tls(&imsg); - break; - default: - log_debug("%s: error handling imsg %d", __func__, - imsg.hdr.type); - break; - } - imsg_free(&imsg); - } - if (!shut) - imsg_event_add(iev); - else { - /* This pipe is dead. Remove its event handler. */ - event_del(&iev->ev); - event_loopexit(NULL); - } -} - -int -main_reload(void) -{ - struct kd_conf *xconf; - - if ((xconf = parse_config(conffile)) == NULL) - return -1; - - if (main_imsg_send_config(xconf) == -1) - return -1; - - merge_config(main_conf, xconf); - - return 0; -} - -static inline int -make_socket_for(struct kd_listen_conf *l) -{ - struct sockaddr_in addr4; - size_t len; - int fd, v; - - memset(&addr4, 0, sizeof(addr4)); - addr4.sin_family = AF_INET; - addr4.sin_port = htons(l->port); - addr4.sin_addr.s_addr = INADDR_ANY; - - if ((fd = socket(AF_INET, SOCK_STREAM, 0)) == -1) - fatal("socket"); - - v = 1; - if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &v, sizeof(v)) == -1) - fatal("setsockopt(SO_REUSEADDR)"); - - v = 1; - if (setsockopt(fd, SOL_SOCKET, SO_REUSEPORT, &v, sizeof(v)) == -1) - fatal("setsockopt(SO_REUSEPORT)"); - - len = sizeof(addr4); - if (bind(fd, (struct sockaddr *)&addr4, len) == -1) - fatal("bind(%s, %d)", l->iface, l->port); - - if (listen(fd, 16) == -1) - fatal("l(%s, %d)", l->iface, l->port); - - return fd; -} - -int -main_imsg_send_config(struct kd_conf *xconf) -{ - struct kd_pki_conf *pki; - struct kd_listen_conf *listen; - -#define SEND(type, fd, data, len) do { \ - if (main_imsg_compose_listener(type, fd, 0, data, len) \ - == -1) \ - return -1; \ - } while (0) - - /* Send fixed part of config to children. */ - SEND(IMSG_RECONF_CONF, -1, xconf, sizeof(*xconf)); - - STAILQ_FOREACH(pki, &xconf->pki_head, entry) { - log_debug("sending pki %s", pki->name); - SEND(IMSG_RECONF_PKI, -1, pki->name, sizeof(pki->name)); - SEND(IMSG_RECONF_PKI_CERT, -1, pki->cert, pki->certlen); - SEND(IMSG_RECONF_PKI_KEY, -1, pki->key, pki->keylen); - } - - STAILQ_FOREACH(listen, &xconf->listen_head, entry) { - log_debug("sending listen on port %d", listen->port); - SEND(IMSG_RECONF_LISTEN, make_socket_for(listen), listen, - sizeof(*listen)); - } - - SEND(IMSG_RECONF_END, -1, NULL, 0); - return 0; - -#undef SEND -} - -void -merge_config(struct kd_conf *conf, struct kd_conf *xconf) -{ - /* do stuff... */ - - free(xconf); -} - -struct kd_conf * -config_new_empty(void) -{ - struct kd_conf *xconf; - - if ((xconf = calloc(1, sizeof(*xconf))) == NULL) - fatal(NULL); - - /* set default values */ - - return xconf; -} - -void -config_clear(struct kd_conf *conf) -{ - struct kd_conf *xconf; - - /* Merge current config with an empty one. */ - xconf = config_new_empty(); - merge_config(conf, xconf); - - free(conf); -} - -__dead void -main_shutdown(void) -{ - pid_t pid; - int status; - - /* close pipes. */ - config_clear(main_conf); - - log_debug("waiting for children to terminate"); - do { - pid = wait(&status); - if (pid == -1) { - if (errno != EINTR && errno != ECHILD) - fatal("wait"); - } else if (WIFSIGNALED(status)) - log_warnx("%s terminated; signal %d", - (pid == listener_pid) ? "logger" : "clientconn", - WTERMSIG(status)); - } while (pid != -1 || (pid == -1 && errno == EINTR)); - - free(iev_listener); - - log_info("terminating"); - exit(0); -} - -static pid_t -start_child(enum kd_process p, int fd, int debug, int verbose) -{ - const char *argv[5]; - int argc = 0; - pid_t pid; - - switch (pid = fork()) { - case -1: - fatal("cannot fork"); - case 0: - break; - default: - close(fd); - return pid; - } - - if (fd != 3) { - if (dup2(fd, 3) == -1) - fatal("cannot setup imsg fd"); - } else if (fcntl(F_SETFD, 0) == -1) - fatal("cannot setup imsg fd"); - - argv[argc++] = saved_argv0; - switch (p) { - case PROC_MAIN: - fatalx("Can not start main process"); - case PROC_LISTENER: - argv[argc++] = "-Tl"; - break; - case PROC_CLIENTCONN: - argv[argc++] = "-Tc"; - break; - } - if (debug) - argv[argc++] = "-d"; - if (verbose) - argv[argc++] = "-v"; - argv[argc++] = NULL; - - /* really? */ - execvp(saved_argv0, (char *const *)argv); - fatal("execvp"); -} - -int -main_imsg_compose_listener(int type, int fd, uint32_t peerid, - const void *data, uint16_t datalen) -{ - if (iev_listener) - return imsg_compose_event(iev_listener, type, peerid, 0, - fd, data, datalen); - else - return -1; -} blob - fd14ab7c7c727a8d7d30270434c294902e08c6e2 (mode 644) blob + /dev/null --- kamid.conf.5 +++ /dev/null @@ -1,131 +0,0 @@ -.\" Copyright (c) 2021 Omar Polo -.\" -.\" Permission to use, copy, modify, and distribute this software for any -.\" purpose with or without fee is hereby granted, provided that the above -.\" copyright notice and this permission notice appear in all copies. -.\" -.\" THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES -.\" WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF -.\" MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR -.\" ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES -.\" WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN -.\" ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF -.\" OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. -.\" -.Dd $Mdocdate: December 14 2021 $ -.Dt KAMID.CONF 5 -.Os -.Sh NAME -.Nm kamid.conf -.Nd 9p file server daemon configuration file -.Sh DESCRIPTION -.Nm -is the configuration file for the 9p file server daemon -.Xr kamid 8 . -.Pp -The format of the configuration file is fairly flexible. -The current line can be extended over multiple lines using a backslash -.Pq Sq \e . -Comments can be put anywhere in the file using a hash mark -.Pq Sq # , -and extend to the end of the current line. -Care should be taken when commenting out multi-line text: the comment is -effective until the end of the entire block. -Arguments names not beginning with a letter, digit, or underscore, as -well as reserved words -(such as -.Ic listen , -.Ic pki -and -.Ic table ) -must be quoted. -Arguments containing whitespace should be surrounded by double quotes -.Pq \&" . -.Pp -Macros can be defined that are later expanded in context. -Macro names must start with a letter, digit, or underscore, and may -contain any of those characters, but may not be reserved words. -Macros are not expanded inside quotes. -For example: -.Bd -literal -offset indent -lan_addr = "192.168.0.1" -listen on $lan_addr -listen on $lan_addr tls auth -.Ed -.Pp -Additional configuration files can be included with the -.Ic include -keyword, for example: -.Bd -literal -offset indent -include "/etc/kamid.conf.local" -.Ed -.Pp -The syntax of -.Nm -is described below. -.Bl -tag -width Ds -.It Ic listen Op Ar options... -The options are as follows: -.Bl -tag -width Ds -.It Ic on Ar address Ic port Ar number -Listen on the -.Ar address -for incoming connection on the given port -.Ar number . -.Ar address -can be an IP address or a domain name. -.It Ic tls Ic pki Ar name -Use the tls certificate -.Ar name -previously defined with the -.Ic pki -rule. -.It Ic auth Pf < Ar table Ns > -Use the given authentication -.Ar table -to authorize the clients. -.It Ic userdata Pf < Ar table Ns > -Maps user -.Pq virtuals or not -to their exported tree. -By default the user home directory obtained with -.Xr getpwnam 3 -is used. -.It Ic virtual Pf < Ar table Ns > -Maps virtual users to local user. -.El -.It Ic pki Ar pkiname Ic cert Ar certfile -Associate certificate file -.Ar certfile -with pki entry -.Ar pkiname . -The pki entry defines a keypair configuration that can be referenced in -listener rules. -.It Ic pki Ar pkiname Ic key Ar keyfile -Associate the key located in -.Ar keyfile -with pki entry -.Ar pkiname . -.\" TODO: document the other syntax for the table -.It Ic table Ar name Brq Ar value Cm => Ar value Oo , Ar ... Oc -Tables provide additional configuration information for -.Xr kamid 8 -in the form of key-value mappings. -.Pp -Declare a mapping table containing the given static -.Ar key Ns Pf - Ar value -pairs. -.El -.Sh EXAMPLES -A sample configuration file: -.Bd -literal -offset indent -pki localhost cert "/etc/ssl/localhost.crt" -pki localhost key "/etc/ssl/private/localhost.key" - -table users { "SHA256:..." => "op" } - -listen on localhost port 1337 tls pki localhost auth -.Ed -.Sh SEE ALSO -.Xr kamictl 8 , -.Xr kamid 8 blob - 9675e6ac175f820b3eec23853740698a562b017b (mode 644) blob + /dev/null --- kamid.h +++ /dev/null @@ -1,249 +0,0 @@ -/* - * Copyright (c) 2021 Omar Polo - * - * Permission to use, copy, modify, and distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. - * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - */ - -#ifndef KAMID_H -#define KAMID_H - -#include "compat.h" - -#include -#include -#include - -/* TODO: make these customizable */ -#define KD_CONF_FILE "/etc/kamid.conf" -#define KD_USER "_kamid" -#define KD_SOCKET "/var/run/kamid.sock" - -#define IMSG_DATA_SIZE(imsg) ((imsg).hdr.len - IMSG_HEADER_SIZE) - -#define MIN(a, b) ((a) < (b) ? (a) : (b)) - -struct imsgev { - struct imsgbuf ibuf; - void (*handler)(int, short, void *); - struct event ev; - short events; -}; - -enum imsg_type { - IMSG_NONE, - IMSG_CTL_LOG_VERBOSE, - IMSG_CTL_RELOAD, - IMSG_CONTROLFD, - IMSG_STARTUP, - IMSG_RECONF_CONF, - IMSG_RECONF_PKI, - IMSG_RECONF_PKI_CERT, - IMSG_RECONF_PKI_KEY, - IMSG_RECONF_LISTEN, - IMSG_RECONF_END, - IMSG_AUTH, - IMSG_AUTH_DIR, - IMSG_AUTH_TLS, - IMSG_CONN_GONE, - IMSG_BUF, - IMSG_MSIZE, - IMSG_CLOSE, -}; - -struct kd_options_conf { - /* ... */ -}; - -enum table_type { - T_NONE = 0, - T_HASH = 0x01, -}; - -struct table { - char t_name[LINE_MAX]; - enum table_type t_type; - char t_path[PATH_MAX]; - void *t_handle; - struct table_backend *t_backend; -}; - -struct table_backend { - const char *name; - int (*open)(struct table *); - int (*add)(struct table *, const char *, const char *); - int (*lookup)(struct table *, const char *, char **); - void (*close)(struct table *); -}; - -/* table_static.c */ -extern struct table_backend table_static; - -#define L_NONE 0x0 -#define L_TLS 0x1 -struct kd_listen_conf { - STAILQ_ENTRY(kd_listen_conf) entry; - uint32_t id; - uint32_t flags; - int fd; - char iface[LINE_MAX]; - uint16_t port; - - /* certificate hash => (virtual) user */ - struct table *auth_table; - - /* virtual user => local user */ - struct table *virtual_table; - - /* (virtual) user => export directory */ - struct table *userdata_table; - - char pki[LINE_MAX]; - struct event ev; - struct tls *ctx; -}; - -struct kd_pki_conf { - STAILQ_ENTRY(kd_pki_conf) entry; - char name[LINE_MAX]; - uint8_t *cert; - size_t certlen; - uint8_t *key; - size_t keylen; - struct tls_config *tlsconf; -}; - -struct kd_tables_conf { - STAILQ_ENTRY(kd_tables_conf) entry; - struct table *table; -}; - -struct kd_conf { - struct kd_options_conf kd_options; - STAILQ_HEAD(kd_pki_conf_head, kd_pki_conf) pki_head; - STAILQ_HEAD(kd_tables_conf_head, kd_tables_conf) table_head; - STAILQ_HEAD(kd_listen_conf_head, kd_listen_conf) listen_head; -}; - -struct kd_auth_req { - uint32_t listen_id; - char hash[128+1]; -}; - -/* - * 9p message header. - * - * The message itself is len bytes long (counting the whole header - * too.) - */ -struct np_msg_header { - uint32_t len; - uint8_t type; - uint16_t tag; -}; - -struct qid { - uint64_t path; - uint32_t vers; - uint8_t type; -}; - -/* useful constants */ -#define HEADERSIZE (4 + 1 + 2) -#define VERSION9P "9P2000" -#define MSIZE9P ((uint32_t)4*1024*1024) -#define NOTAG ((uint16_t)~0U) -#define NOFID ((uint32_t)~0U) -#define NOUID (-1) -#define QIDSIZE 13 -#define MAXWELEM 16 - -#define NPSTATSIZ(namlen, uidnam, gidnam, unam) \ - (6 + QIDSIZE + 20 + 2 + namlen + 2 + uidnam + 2 + gidnam + 2 + unam) - -/* bits in Qid.type */ -#define QTDIR 0x80 /* type bit for directories */ -#define QTAPPEND 0x40 /* type bit for append only files */ -#define QTEXCL 0x20 /* type bit for exclusive use files */ -#define QTMOUNT 0x10 /* type bit for mounted channel */ -#define QTAUTH 0x08 /* type bit for authentication file */ -#define QTTMP 0x04 /* type bit for non-backed-up file */ -#define QTSYMLINK 0x02 /* type bit for symbolic link */ -#define QTFILE 0x00 /* type bits for plain file */ - -/* Topen mode/flags */ -#define KOREAD 0x00 -#define KOWRITE 0x01 -#define KORDWR 0x02 -#define KOEXEC 0x03 -#define KOTRUNC 0x10 -#define KORCLOSE 0x40 - -/* 9p message types */ -enum { - Treaddir = 40, /* .L */ - Rreaddir, - - Tversion = 100, - Rversion, - Tauth = 102, - Rauth, - Tattach = 104, - Rattach, - Terror = 106, /* illegal */ - Rerror, - Tflush = 108, - Rflush, - Twalk = 110, - Rwalk, - Topen = 112, - Ropen, - Tcreate = 114, - Rcreate, - Tread = 116, - Rread, - Twrite = 118, - Rwrite, - Tclunk = 120, - Rclunk, - Tremove = 122, - Rremove, - Tstat = 124, - Rstat, - Twstat = 126, - Rwstat, - Tmax, - - /* - * plan9ports' include/fcall.h also has a - * - * Topenfd = 98, - * Ropenfd, - * - * which it's not mentioned in the 9p "rfc" over at - * 9p.cat-v.org. Ignoring that for now. - */ -}; - -/* kamid.c */ -extern int verbose; -int main_imsg_compose_listener(int, int, uint32_t, const void *, uint16_t); -void merge_config(struct kd_conf *, struct kd_conf *); - -struct kd_conf *config_new_empty(void); -void config_clear(struct kd_conf *); - -/* parse.y */ -struct kd_conf *parse_config(const char *); -int cmdline_symset(char *); - -#endif blob - bbb82ddbd18431c0c4cc0cc05d9e9273e5bf4d26 (mode 644) blob + /dev/null --- kamiftp.1 +++ /dev/null @@ -1,80 +0,0 @@ -.\" Copyright (c) 2021 Omar Polo -.\" -.\" Permission to use, copy, modify, and distribute this software for any -.\" purpose with or without fee is hereby granted, provided that the above -.\" copyright notice and this permission notice appear in all copies. -.\" -.\" THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES -.\" WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF -.\" MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR -.\" ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES -.\" WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN -.\" ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF -.\" OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. -.\" -.Dd $Mdocdate: December 14 2021 $ -.Dt KAMIFTP 1 -.Os -.Sh NAME -.Nm kamiftp -.Nd 9p client -.Sh SYNOPSIS -.Nm -.Op Fl c -.Op Fl C Ar cert -.Op Fl K Ar key -.Ar host Op Ar path -.Sh DESCRIPTION -.Nm -is a -.Xr 9p 7 -client. -.Pp -The options are as follows: -.Bl -tag -width Ds -.It Fl c -Use TLS for the connection. -.Fl K -and -.Fl C -are mandatory if -.Fl c -is used. -.It Fl C Ar certificate -Specify the path to the client -.Ar certificate -to be use during the TLS handsahke. -.It Fl K Ar key -Specify the path to the client certificate -.Ar key -to be used during the TLS handshake. -.El -.Pp -The following commands are recognized by -.Nm : -.Bl -tag -width Ds -.It Ic bell Oo Cm on | off Oc -Request terminal to sound a bell after each command. -Without arguments toggle the current state. -.It Ic bye -Terminate the session. -Synomym of -.Ic quit . -.It Ic ls -List the file in the current directory -.It Ic quit -Terminate the session. -Synomym of -.Ic bye . -.It Ic verbose Oo Cm on | off Oc -Print verbose information. -Without arguments toggle the current state. -.El -.Sh SEE ALSO -.Xr 9p 7 , -.Xr kamid 8 -.Sh AUTHORS -The -.Nm -utility was written by -.An Omar Polo Aq Mt op@omarpolo.com . blob - fb0c86530732d462858c57359e8a544d794d6b06 (mode 644) blob + /dev/null --- kamirepl.1 +++ /dev/null @@ -1,152 +0,0 @@ -.\" Copyright (c) 2021 Omar Polo -.\" -.\" Permission to use, copy, modify, and distribute this software for any -.\" purpose with or without fee is hereby granted, provided that the above -.\" copyright notice and this permission notice appear in all copies. -.\" -.\" THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES -.\" WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF -.\" MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR -.\" ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES -.\" WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN -.\" ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF -.\" OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. -.\" -.Dd $Mdocdate: December 16 2021 $ -.Dt KAMIREPL 1 -.Os -.Sh NAME -.Nm kamirepl -.Nd 9p repl client -.Sh SYNOPSIS -.Nm -.Op Fl chv -.Op Fl C Ar cert -.Op Fl H Ar host -.Op Fl K Ar key -.Op Fl P Ar port -.Sh DESCRIPTION -.Nm -is a -.Xr 9p 7 -repl client. -.Pp -The optinos are as follows: -.Bl -tag -width tenletters -.It Fl C Ar cert -Path to the TLS client certificate to use. -.It Fl c -Use TLS for the connection. -.Fl C -and -.Fl K -are mandatory if used. -.It Fl H Ar host -Hostname of the file server. -.It Fl h -Display usage and exit. -.It Fl K Ar key -Path to the TLS client certificate private key. -.It Fl P Ar port -Port number to connect to. -.It Fl v -Verbose logging. -.El -.Pp -The interactive commands are -.Bl -tag -width Ds -.It Ic version Op Ar version-string -.Ar version-string -is -.Dq 9P2000 -by default. -.It Ic attach Ar fid Ar uname Ar aname -Request the file server to attach the file tree identified by -.Ar aname -to the specified -.Ar fid -number. -.Ar aname -is the identifier for the user. -The afid used is implicitly NOFID. -.It Ic clunk Ar fid -Closes -.Ar fid. -.It Ic flush Ar oldtag -Require the server to flush -.Ar oldtag . -.It Ic walk Ar fid Ar newfid Ar wnames... -Do a walk from -.Ar fid -following -.Ar wnames -component and associating the reached file to -.Ar newfid . -.It Ic open Ar fid Ar mode Op Ar flag -Prepare -.Ar fid -for I/O. -.Ar mode -can be one of -.Sq read -or -.Sq r , -.Sq write -or -.Sq w , -.Sq readwrite -or -.Sq rdwr . -Optionally, -.Ar flag -can be on of -.Sq trunc -to truncate the file or -.Sq rclose -to remove the file upon -.Ic clunk . -.It Ic create Ar fid Ar name Ar perm Ar mode -Create the file -.Ar name -and open it with -.Ar mode -as the given -.Ar fid. -.Ar perm -should be used to select the permissions of the file, but is currently -unused. -.It Ic read Ar fid Ar offset Ar count -Issue a read request for the given -.Ar fid , -which must have been prepared for I/O with -.Ic open , -at -.Ar offset -and for -.Ar count -bytes. -.It Ic write Ar fid Ar offset Ar content -Writes -.Ar content -to -.Ar fid -starting at -.Ar offset . -.It Ic remove Ar fid -Delete the file identified by -.Ar fid -and close it. -Even in case of error, -.Ar fid -is clunked. -.El -.Sh SEE ALSO -.Xr kamiftp 1 -.Xr 9p 7 -.Xr kamid 8 -.Sh AUTHORS -.An -nosplit -The -.Nm -utility was written by -.An Omar Polo Aq Mt op@omarpolo.com . blob - 504329156629b6374f7a18342d97b66bfbb16659 (mode 644) blob + /dev/null --- kamirepl.c +++ /dev/null @@ -1,1073 +0,0 @@ -/* - * Copyright (c) 2021 Omar Polo - * - * Permission to use, copy, modify, and distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. - * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - */ - -#include "compat.h" - -#include -#include - -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "9pclib.h" -#include "kamid.h" -#include "log.h" -#include "utils.h" - -#define DEBUG_PACKETS 0 - -#define PROMPT "=% " - -/* flags */ -int verbose; -int tls; -const char *keypath; -const char *crtpath; -const char *host; -const char *port; - -/* state */ -struct tls_config *tlsconf; -struct tls *ctx; -struct bufferevent *bev, *inbev; - -static void __dead usage(int); - -static void sig_handler(int, short, void *); - -static int openconn(void); -static void mark_nonblock(int); - -static void tls_readcb(int, short, void *); -static void tls_writecb(int, short, void *); - -static void client_read(struct bufferevent *, void *); -static void client_write(struct bufferevent *, void *); -static void client_error(struct bufferevent *, short, void *); - -static void repl_read(struct bufferevent *, void *); -static void repl_error(struct bufferevent *, short, void *); - -static void excmd_version(const char **, int); -static void excmd_attach(const char **, int); -static void excmd_clunk(const char **, int); -static void excmd_flush(const char **, int); -static void excmd_walk(const char ** , int); -static void excmd_open(const char ** , int); -static void excmd_create(const char ** , int); -static void excmd_read(const char ** , int); -static void excmd_write(const char **, int); -static void excmd(const char **, int); - -static void pp_qid(const uint8_t *, uint32_t); -static void pp_msg(uint32_t, uint8_t, uint16_t, const uint8_t *); -static void handle_9p(const uint8_t *, size_t); -static void clr(void); -static void prompt(void); - -static void __dead -usage(int ret) -{ - fprintf(stderr, - "usage: %s [-chv] [-C crt] [-K key] [-H host] [-P port]\n", - getprogname()); - fprintf(stderr, PACKAGE_NAME " suite version " PACKAGE_VERSION "\n"); - exit(ret); -} - -static void -sig_handler(int sig, short event, void *d) -{ - /* - * Normal signal handler rules don't apply because libevent - * decouples for us. - */ - - switch (sig) { - case SIGINT: - case SIGTERM: - clr(); - log_warnx("Shutting down..."); - event_loopbreak(); - return; - default: - fatalx("unexpected signal %d", sig); - } -} - -static int -openconn(void) -{ - struct addrinfo hints, *res, *res0; - int error; - int save_errno; - int s; - const char *cause = NULL; - - memset(&hints, 0, sizeof(hints)); - hints.ai_family = AF_UNSPEC; - hints.ai_socktype = SOCK_STREAM; - if ((error = getaddrinfo(host, port, &hints, &res0))) { - warnx("%s", gai_strerror(error)); - return -1; - } - - s = -1; - for (res = res0; res; res = res->ai_next) { - s = socket(res->ai_family, res->ai_socktype, - res->ai_protocol); - if (s == -1) { - cause = "socket"; - continue; - } - - if (connect(s, res->ai_addr, res->ai_addrlen) == -1) { - cause = "connect"; - save_errno = errno; - close(s); - errno = save_errno; - s = -1; - continue; - } - - break; - } - - freeaddrinfo(res0); - - if (s == -1) - warn("%s", cause); - - return s; -} - -static void -mark_nonblock(int fd) -{ - int flags; - - if ((flags = fcntl(fd, F_GETFL)) == -1) - fatal("fcntl(F_GETFL)"); - if (fcntl(fd, F_SETFL, flags | O_NONBLOCK) == -1) - fatal("fcntl(F_SETFL)"); -} - -static void -tls_readcb(int fd, short event, void *d) -{ - struct bufferevent *bufev = d; - char buf[IBUF_READ_SIZE]; - int what = EVBUFFER_READ; - int howmuch = IBUF_READ_SIZE; - ssize_t ret; - size_t len; - - if (event == EV_TIMEOUT) { - what |= EVBUFFER_TIMEOUT; - goto err; - } - - if (bufev->wm_read.high != 0) - howmuch = MIN(sizeof(buf), bufev->wm_read.high); - - switch (ret = tls_read(ctx, buf, howmuch)) { - case TLS_WANT_POLLIN: - case TLS_WANT_POLLOUT: - goto retry; - case -1: - what |= EVBUFFER_ERROR; - goto err; - } - len = ret; - - if (len == 0) { - what |= EVBUFFER_EOF; - goto err; - } - - if (evbuffer_add(bufev->input, buf, len) == -1) { - what |= EVBUFFER_ERROR; - goto err; - } - - event_add(&bufev->ev_read, NULL); - - len = EVBUFFER_LENGTH(bufev->input); - if (bufev->wm_read.low != 0 && len < bufev->wm_read.low) - return; - if (bufev->readcb != NULL) - (*bufev->readcb)(bufev, bufev->cbarg); - return; - -retry: - event_add(&bufev->ev_read, NULL); - return; - -err: - (*bufev->errorcb)(bufev, what, bufev->cbarg); -} - -static void -tls_writecb(int fd, short event, void *d) -{ - struct bufferevent *bufev = d; - ssize_t ret; - size_t len; - short what = EVBUFFER_WRITE; - void *data; - - if (event == EV_TIMEOUT) { - what |= EVBUFFER_TIMEOUT; - goto err; - } - - len = EVBUFFER_LENGTH(bufev->output); - if (len != 0) { - data = EVBUFFER_DATA(bufev->output); - -#if DEBUG_PACKETS - hexdump("outgoing msg", data, len); -#endif - - switch (ret = tls_write(ctx, data, len)) { - case TLS_WANT_POLLIN: - case TLS_WANT_POLLOUT: - goto retry; - case -1: - what |= EVBUFFER_ERROR; - goto err; - } - evbuffer_drain(bufev->output, ret); - } - - if (EVBUFFER_LENGTH(bufev->output) != 0) - event_add(&bufev->ev_write, NULL); - - if (bufev->writecb != NULL && - EVBUFFER_LENGTH(bufev->output) <= bufev->wm_write.low) - (*bufev->writecb)(bufev, bufev->cbarg); - return; - -retry: - event_add(&bufev->ev_write, NULL); - return; -err: - (*bufev->errorcb)(bufev, what, bufev->cbarg); -} - -static void -client_read(struct bufferevent *bev, void *d) -{ - struct evbuffer *src = EVBUFFER_INPUT(bev); - uint32_t len; - uint8_t *data; - - for (;;) { - if (EVBUFFER_LENGTH(src) < sizeof(len)) - return; - - data = EVBUFFER_DATA(src); - - memcpy(&len, data, sizeof(len)); - len = le32toh(len); - - if (len < HEADERSIZE) - fatal("incoming message is too small! (%d bytes)", - len); - - if (len > EVBUFFER_LENGTH(src)) - return; - -#if DEBUG_PACKETS - hexdump("incoming msg", data, len); -#endif - - handle_9p(data, len); - evbuffer_drain(src, len); - } -} - -static void -client_write(struct bufferevent *bev, void *data) -{ - return; /* nothing to do */ -} - -static void -client_error(struct bufferevent *bev, short err, void *data) -{ - if (err & EVBUFFER_ERROR) - fatal("buffer event error"); - - if (err & EVBUFFER_EOF) { - clr(); - log_info("EOF"); - event_loopbreak(); - return; - } - - clr(); - log_warnx("unknown event error"); - event_loopbreak(); -} - -static void -repl_read(struct bufferevent *bev, void *d) -{ - size_t len; - int argc; - const char *argv[10], **ap; - char *line; - - line = evbuffer_readln(bev->input, &len, EVBUFFER_EOL_LF); - if (line == NULL) - return; - - for (argc = 0, ap = argv; ap < &argv[9] && - (*ap = strsep(&line, " \t")) != NULL;) { - if (**ap != '\0') - ap++, argc++; - } - - clr(); - excmd(argv, argc); - prompt(); - - free(line); -} - -static void -repl_error(struct bufferevent *bev, short error, void *d) -{ - fatalx("an error occurred"); -} - -static inline void -do_send(void) -{ - bufferevent_write_buffer(bev, evb); -} - -/* version [version-str] */ -static void -excmd_version(const char **argv, int argc) -{ - const char *s; - - s = VERSION9P; - if (argc == 2) - s = argv[1]; - - tversion(s, MSIZE9P); - do_send(); -} - -/* attach fid uname aname */ -static void -excmd_attach(const char **argv, int argc) -{ - uint32_t fid; - const char *errstr; - - if (argc != 4) - goto usage; - - fid = strtonum(argv[1], 0, UINT32_MAX, &errstr); - if (errstr != NULL) { - log_warnx("fid is %s: %s", errstr, argv[1]); - return; - } - - tattach(fid, NOFID, argv[2], argv[3]); - do_send(); - return; - -usage: - log_warnx("usage: attach fid uname aname"); -} - -/* clunk fid */ -static void -excmd_clunk(const char **argv, int argc) -{ - uint32_t fid; - const char *errstr; - - if (argc != 2) - goto usage; - - fid = strtonum(argv[1], 0, UINT32_MAX, &errstr); - if (errstr != NULL) { - log_warnx("fid is %s: %s", errstr, argv[1]); - return; - } - - tclunk(fid); - do_send(); - return; - -usage: - log_warnx("usage: clunk fid"); -} - -/* flush oldtag */ -static void -excmd_flush(const char **argv, int argc) -{ - uint16_t oldtag; - const char *errstr; - - if (argc != 2) - goto usage; - - oldtag = strtonum(argv[1], 0, UINT16_MAX, &errstr); - if (errstr != NULL) { - log_warnx("oldtag is %s: %s", errstr, argv[1]); - return; - } - - tflush(oldtag); - do_send(); - return; - -usage: - log_warnx("usage: flush oldtag"); -} - -/* walk fid newfid wnames... */ -static void -excmd_walk(const char **argv, int argc) -{ - uint32_t fid, newfid; - const char *errstr; - - if (argc < 3) - goto usage; - - fid = strtonum(argv[1], 0, UINT32_MAX, &errstr); - if (errstr != NULL) { - log_warnx("fid is %s: %s", errstr, argv[1]); - return; - } - - newfid = strtonum(argv[2], 0, UINT32_MAX, &errstr); - if (errstr != NULL) { - log_warnx("newfid is %s: %s", errstr, argv[1]); - return; - } - - twalk(fid, newfid, argv + 3, argc - 3); - do_send(); - return; - -usage: - log_warnx("usage: walk fid newfid wnames..."); -} - -/* open fid mode [flag] */ -static void -excmd_open(const char **argv, int argc) -{ - const char *errstr; - uint32_t fid; - uint8_t mode = 0; - - if (argc != 3 && argc != 4) - goto usage; - - fid = strtonum(argv[1], 0, UINT32_MAX, &errstr); - if (errstr != NULL) { - log_warnx("fid is %s: %s", errstr, argv[1]); - return; - } - - /* parse mode */ - if (!strcmp("read", argv[2]) || !strcmp("r", argv[2])) - mode = KOREAD; - else if (!strcmp("write", argv[2]) || !strcmp("w", argv[2])) - mode = KOWRITE; - else if (!strcmp("readwrite", argv[2]) || !strcmp("rw", argv[2])) - mode = KORDWR; - else { - log_warnx("invalid mode %s", argv[2]); - return; - } - - /* parse flag */ - if (argv[3] != NULL) { - if (!strcmp("trunc", argv[3])) - mode |= KOTRUNC; - else if (!strcmp("rclose", argv[3])) - mode |= KORCLOSE; - else { - log_warnx("invalid flag %s", argv[3]); - return; - } - } - - topen(fid, mode); - do_send(); - return; - -usage: - log_warnx("usage: open fid mode [flag]"); -} - -/* create fid path perm mode */ -static void -excmd_create(const char **argv, int argc) -{ - const char *errstr; - uint32_t fid; - uint8_t mode = 0; - - if (argc != 5) - goto usage; - - fid = strtonum(argv[1], 0, UINT32_MAX, &errstr); - if (errstr != NULL) { - log_warnx("fid is %s: %s", errstr, argv[1]); - return; - } - - /* parse mode */ - if (!strcmp("write", argv[4]) || !strcmp("w", argv[4])) - mode = KOWRITE; - else if (!strcmp("readwrite", argv[4]) || !strcmp("rw", argv[4])) - mode = KORDWR; - else { - log_warnx("invalid mode %s for create", argv[4]); - return; - } - - tcreate(fid, argv[2], 0, mode); - do_send(); - return; - -usage: - log_warnx("usage: create fid path perm mode ; perm is unused"); -} - - -/* read fid offset count */ -static void -excmd_read(const char **argv, int argc) -{ - uint64_t off; - uint32_t fid, count; - const char *errstr; - - if (argc != 4) - goto usage; - - fid = strtonum(argv[1], 0, UINT32_MAX, &errstr); - if (errstr != NULL) { - log_warnx("fid is %s: %s", errstr, argv[1]); - return; - } - - /* should really be UNT64_MAX but it fails... */ - off = strtonum(argv[2], -1, UINT32_MAX, &errstr); - if (errstr != NULL) { - log_warnx("offset is %s: %s", errstr, argv[2]); - return; - } - - count = strtonum(argv[3], 0, UINT32_MAX, &errstr); - if (errstr != NULL) { - log_warnx("count is %s: %s", errstr, argv[3]); - return; - } - - tread(fid, off, count); - do_send(); - return; - -usage: - log_warnx("usage: read fid offset count"); -} - -/* write fid offset content */ -static void -excmd_write(const char **argv, int argc) -{ - uint64_t off; - uint32_t fid, count; - const char *errstr; - - if (argc != 4) - goto usage; - - fid = strtonum(argv[1], 0, UINT32_MAX, &errstr); - if (errstr != NULL) { - log_warnx("fid is %s: %s", errstr, argv[1]); - return; - } - - /* should really be UINT64_MAX but... */ - off = strtonum(argv[2], 0, UINT32_MAX, &errstr); - if (errstr != NULL) { - log_warnx("offset is %s: %s", errstr, argv[2]); - return; - } - - count = strlen(argv[3]); - twrite(fid, off, argv[3], count); - do_send(); - return; - -usage: - log_warnx("usage: write fid offset content"); -} - -/* remove fid */ -static void -excmd_remove(const char **argv, int argc) -{ - const char *errstr; - uint32_t fid; - - if (argc != 2) - goto usage; - - fid = strtonum(argv[1], 0, UINT32_MAX, &errstr); - if (errstr != NULL) { - log_warnx("fid is %s: %s", errstr, argv[1]); - return; - } - - tremove(fid); - do_send(); - return; - -usage: - log_warnx("usage: remove fid"); -} - -static void -excmd(const char **argv, int argc) -{ - struct cmd { - const char *name; - void (*fn)(const char **, int); - } cmds[] = { - {"version", excmd_version}, - {"attach", excmd_attach}, - {"clunk", excmd_clunk}, - {"flush", excmd_flush}, - {"walk", excmd_walk}, - {"open", excmd_open}, - {"create", excmd_create}, - {"read", excmd_read}, - {"write", excmd_write}, - /* TODO: stat */ - {"remove", excmd_remove}, - }; - size_t i; - - if (argc == 0) - return; - - for (i = 0; i < sizeof(cmds)/sizeof(cmds[0]); ++i) { - if (!strcmp(cmds[i].name, argv[0])) { - cmds[i].fn(argv, argc); - return; - } - } - - log_warnx("Unknown command %s", *argv); -} - -static void -pp_qid(const uint8_t *d, uint32_t len) -{ - uint64_t path; - uint32_t vers; - uint8_t type; - - if (len < 13) { - printf("invalid"); - return; - } - - type = *d++; - - memcpy(&vers, d, sizeof(vers)); - d += sizeof(vers); - vers = le64toh(vers); - - memcpy(&path, d, sizeof(path)); - d += sizeof(path); - path = le64toh(path); - - printf("qid{path=%"PRIu64" version=%"PRIu32" type=0x%x\"%s\"}", - path, vers, type, pp_qid_type(type)); -} - -static void -pp_msg(uint32_t len, uint8_t type, uint16_t tag, const uint8_t *d) -{ - uint32_t msize, iounit, count; - uint16_t slen; - char *v; - - printf("len=%"PRIu32" type=%d[%s] tag=0x%x[%d] ", len, - type, pp_msg_type(type), tag, tag); - - len -= HEADERSIZE; - - switch (type) { - case Rversion: - if (len < 6) { - printf("invalid: not enough space for msize " - "and version provided."); - break; - } - - memcpy(&msize, d, sizeof(msize)); - d += sizeof(msize); - len -= sizeof(msize); - msize = le32toh(msize); - - memcpy(&slen, d, sizeof(slen)); - d += sizeof(slen); - len -= sizeof(slen); - slen = le16toh(slen); - - if (len != slen) { - printf("invalid: version string length doesn't " - "match. Got %d; want %d", slen, len); - break; - } - - printf("msize=%"PRIu32" version[%"PRIu16"]=\"", - msize, slen); - fwrite(d, 1, slen, stdout); - printf("\""); - - break; - - case Rattach: - pp_qid(d, len); - break; - - case Rclunk: - case Rflush: - case Rremove: - if (len != 0) - printf("invalid %s: %"PRIu32" extra bytes", - pp_msg_type(type), len); - break; - - case Rwalk: - if (len < 2) { - printf("invaild Rwalk: less than two bytes (%d)", - (int)len); - break; - } - - memcpy(&slen, d, sizeof(slen)); - d += sizeof(slen); - len -= sizeof(slen); - slen = le16toh(slen); - - if (len != QIDSIZE * slen) { - printf("invalid Rwalk: wanted %d bytes for %d qids " - "but got %"PRIu32" bytes instead", - QIDSIZE*slen, slen, len); - break; - } - - printf("nwqid=%"PRIu16, slen); - - for (; slen != 0; slen--) { - printf(" "); - pp_qid(d, len); - d += QIDSIZE; - len -= QIDSIZE; - } - - break; - - case Ropen: - case Rcreate: - if (len != QIDSIZE + 4) { - printf("invalid %s: expected %d bytes; " - "got %u\n", pp_msg_type(type), QIDSIZE + 4, len); - break; - } - - pp_qid(d, len); - d += QIDSIZE; - len -= QIDSIZE; - - memcpy(&iounit, d, sizeof(iounit)); - d += sizeof(iounit); - len -= sizeof(iounit); - iounit = le32toh(iounit); - printf(" iounit=%"PRIu32, iounit); - break; - - case Rread: - if (len < sizeof(count)) { - printf("invalid Rread: expected %zu bytes at least; " - "got %u\n", sizeof(count), len); - break; - } - - memcpy(&count, d, sizeof(count)); - d += sizeof(count); - len -= sizeof(count); - count = le32toh(count); - - if (len != count) { - printf("invalid Rread: expected %d data bytes; " - "got %u\n", count, len); - break; - } - - /* allocates three extra bytes, oh well... */ - if ((v = calloc(count + 1, 4)) == NULL) - fatal("calloc"); - strvisx(v, d, count, VIS_SAFE | VIS_TAB | VIS_NL | VIS_CSTYLE); - printf("data=%s", v); - free(v); - - break; - - case Rwrite: - if (len != sizeof(count)) { - printf("invalid Rwrite: expected %zu data bytes; " - "got %u\n", sizeof(count), len); - break; - } - - memcpy(&count, d, sizeof(count)); - d += sizeof(count); - len -= sizeof(count); - count = le32toh(count); - - printf("count=%d", count); - break; - - case Rerror: - memcpy(&slen, d, sizeof(slen)); - d += sizeof(slen); - len -= sizeof(slen); - slen = le16toh(slen); - - if (slen != len) { - printf("invalid: error string length doesn't " - "match. Got %d; want %d", slen, len); - break; - } - - printf("error=\""); - fwrite(d, 1, slen, stdout); - printf("\""); - - break; - - default: - if ((v = calloc(len + 1, 4)) == NULL) - fatal("calloc"); - strvisx(v, d, len, VIS_SAFE | VIS_TAB | VIS_NL | VIS_CSTYLE); - printf("body=%s", v); - free(v); - } - - printf("\n"); -} - -static void -handle_9p(const uint8_t *data, size_t size) -{ - uint32_t len; - uint16_t tag; - uint8_t type; - - assert(size >= HEADERSIZE); - - memcpy(&len, data, sizeof(len)); - data += sizeof(len); - - memcpy(&type, data, sizeof(type)); - data += sizeof(type); - - memcpy(&tag, data, sizeof(tag)); - data += sizeof(tag); - - len = le32toh(len); - /* type is one byte long, no endianness issues */ - tag = le16toh(tag); - - clr(); - pp_msg(len, type, tag, data); - prompt(); -} - -static void -clr(void) -{ - printf("\r"); - fflush(stdout); -} - -static void -prompt(void) -{ - printf("%s", PROMPT); - fflush(stdout); -} - -int -main(int argc, char **argv) -{ - int ch, sock, handshake; - struct event ev_sigint, ev_sigterm; - - signal(SIGPIPE, SIG_IGN); - - while ((ch = getopt(argc, argv, "C:cH:hK:P:v")) != -1) { - switch (ch) { - case 'C': - crtpath = optarg; - break; - case 'c': - tls = 1; - break; - case 'H': - host = optarg; - break; - case 'h': - usage(0); - break; - case 'K': - keypath = optarg; - break; - case 'P': - port = optarg; - break; - case 'v': - verbose = 1; - break; - default: - usage(1); - } - } - - if (host == NULL) - host = "localhost"; - if (port == NULL) - port = "1337"; - - argc -= optind; - argv += optind; - - if (argc != 0) - usage(1); - /* if (!tls || (crtpath != NULL || keypath != NULL)) */ - /* usage(1); */ - if (!tls) - errx(1, "must enable tls (for now)"); - - log_init(1, LOG_DAEMON); - log_setverbose(verbose); - log_procinit(getprogname()); - - if ((tlsconf = tls_config_new()) == NULL) - fatalx("tls_config_new"); - tls_config_insecure_noverifycert(tlsconf); - tls_config_insecure_noverifyname(tlsconf); - if (tls_config_set_keypair_file(tlsconf, crtpath, keypath) == -1) - fatalx("can't load certs (%s, %s)", crtpath, keypath); - - if ((ctx = tls_client()) == NULL) - fatal("tls_client"); - if (tls_configure(ctx, tlsconf) == -1) - fatalx("tls_configure: %s", tls_error(ctx)); - - log_info("connecting to %s:%s...", host, port); - - if ((sock = openconn()) == -1) - fatalx("can't connect to %s:%s", host, port); - - if (tls_connect_socket(ctx, sock, host) == -1) - fatalx("tls_connect_socket: %s", tls_error(ctx)); - - for (handshake = 0; !handshake;) { - switch (tls_handshake(ctx)) { - case -1: - fatalx("tls_handshake: %s", tls_error(ctx)); - case 0: - handshake = 1; - break; - } - } - - log_info("connected!"); - - mark_nonblock(sock); - - event_init(); - - /* initialize global evb */ - if ((evb = evbuffer_new()) == NULL) - fatal("evbuffer_new"); - - signal_set(&ev_sigint, SIGINT, sig_handler, NULL); - signal_set(&ev_sigterm, SIGINT, sig_handler, NULL); - - signal_add(&ev_sigint, NULL); - signal_add(&ev_sigterm, NULL); - - bev = bufferevent_new(sock, client_read, client_write, client_error, - NULL); - if (bev == NULL) - fatal("bufferevent_new"); - - /* setup tls/io */ - event_set(&bev->ev_read, sock, EV_READ, tls_readcb, bev); - event_set(&bev->ev_write, sock, EV_WRITE, tls_writecb, bev); - - bufferevent_enable(bev, EV_READ|EV_WRITE); - - mark_nonblock(0); - inbev = bufferevent_new(0, repl_read, NULL, repl_error, NULL); - bufferevent_enable(inbev, EV_READ); - - prompt(); - event_dispatch(); - - bufferevent_free(bev); - tls_free(ctx); - tls_config_free(tlsconf); - close(sock); - - return 0; -} blob - 196a2a9f84052ad4fef72faea5dc79a91530ccb7 (mode 644) blob + /dev/null --- listener.c +++ /dev/null @@ -1,933 +0,0 @@ -/* - * Copyright (c) 2021 Omar Polo - * Copyright (c) 2018 Florian Obser - * Copyright (c) 2004, 2005 Claudio Jeker - * Copyright (c) 2004 Esben Norby - * Copyright (c) 2003, 2004 Henning Brauer - * - * Permission to use, copy, modify, and distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. - * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - */ - -#include "compat.h" - -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "control.h" -#include "kamid.h" -#include "listener.h" -#include "log.h" -#include "sandbox.h" -#include "utils.h" - -static struct kd_conf *listener_conf; -static struct imsgev *iev_main; - -static void listener_sig_handler(int, short, void *); -__dead void listener_shutdown(void); - -SPLAY_HEAD(clients_tree_id, client) clients; - -struct client { - uint32_t id; - uint32_t lid; - uint32_t msize; - int fd; - int done; - struct tls *ctx; - struct event event; - struct imsgev iev; - struct bufferevent *bev; - SPLAY_ENTRY(client) sp_entry; -}; - -static void listener_imsg_event_add(struct imsgev *, void *); -static void listener_dispatch_client(int, short, void *); -static int listener_imsg_compose_client(struct client *, int, - uint32_t, const void *, uint16_t); - -static void apply_config(struct kd_conf *); -static void handle_accept(int, short, void *); - -static void handle_handshake(int, short, void *); -static void client_read(struct bufferevent *, void *); -static void client_write(struct bufferevent *, void *); -static void client_error(struct bufferevent *, short, void *); -static void client_tls_readcb(int, short, void *); -static void client_tls_writecb(int, short, void *); -static void close_conn(struct client *); -static void handle_close(int, short, void *); - -static inline int -clients_tree_cmp(struct client *a, struct client *b) -{ - if (a->id == b->id) - return 0; - else if (a->id < b->id) - return -1; - else - return +1; -} - -SPLAY_PROTOTYPE(clients_tree_id, client, sp_entry, clients_tree_cmp); -SPLAY_GENERATE(clients_tree_id, client, sp_entry, clients_tree_cmp) - -static void -listener_sig_handler(int sig, short event, void *d) -{ - /* - * Normal signal handler rules don't apply because libevent - * decouples for us. - */ - - switch (sig) { - case SIGINT: - case SIGTERM: - listener_shutdown(); - default: - fatalx("unexpected signal %d", sig); - } -} - -void -listener(int debug, int verbose) -{ - struct event ev_sigint, ev_sigterm; - struct passwd *pw; - - /* listener_conf = config_new_empty(); */ - - log_init(debug, LOG_DAEMON); - log_setverbose(verbose); - - if ((pw = getpwnam(KD_USER)) == NULL) - fatal("getpwnam"); - - if (chroot(pw->pw_dir) == -1) - fatal("chroot"); - if (chdir("/") == -1) - fatal("chdir(\"/\")"); - - setproctitle("listener"); - log_procinit("listener"); - - if (setgroups(1, &pw->pw_gid) || - setresgid(pw->pw_gid, pw->pw_gid, pw->pw_gid) || - setresuid(pw->pw_uid, pw->pw_uid, pw->pw_uid)) - fatal("can't drop privileges"); - - event_init(); - - /* Setup signal handlers(s). */ - signal_set(&ev_sigint, SIGINT, listener_sig_handler, NULL); - signal_set(&ev_sigterm, SIGTERM, listener_sig_handler, NULL); - - signal_add(&ev_sigint, NULL); - signal_add(&ev_sigterm, NULL); - - signal(SIGPIPE, SIG_IGN); - signal(SIGHUP, SIG_IGN); - - /* Setup pipe and event handler to the main process. */ - if ((iev_main = malloc(sizeof(*iev_main))) == NULL) - fatal(NULL); - - imsg_init(&iev_main->ibuf, 3); - iev_main->handler = listener_dispatch_main; - - /* Setup event handlers. */ - iev_main->events = EV_READ; - event_set(&iev_main->ev, iev_main->ibuf.fd, iev_main->events, - iev_main->handler, iev_main); - event_add(&iev_main->ev, NULL); - - sandbox_listener(); - event_dispatch(); - listener_shutdown(); -} - -__dead void -listener_shutdown(void) -{ - msgbuf_clear(&iev_main->ibuf.w); - close(iev_main->ibuf.fd); - - config_clear(listener_conf); - - free(iev_main); - - log_info("listener exiting"); - exit(0); -} - -static void -listener_receive_config(struct imsg *imsg, struct kd_conf **nconf, - struct kd_pki_conf **pki) -{ - struct kd_listen_conf *listen; - char *t; - - switch (imsg->hdr.type) { - case IMSG_RECONF_CONF: - if (*nconf != NULL) - fatalx("%s: IMSG_RECONF_CONF already in " - "progress", __func__); - - if (listener_conf != NULL) - fatalx("%s: don't know how reload the " - "configuration yet", __func__); - - if (IMSG_DATA_SIZE(*imsg) != sizeof(struct kd_conf)) - fatalx("%s: IMSG_RECONF_CONF wrong length: %lu", - __func__, IMSG_DATA_SIZE(*imsg)); - if ((*nconf = malloc(sizeof(**nconf))) == NULL) - fatal(NULL); - memcpy(*nconf, imsg->data, sizeof(**nconf)); - memset(&(*nconf)->pki_head, 0, sizeof((*nconf)->pki_head)); - memset(&(*nconf)->table_head, 0, sizeof((*nconf)->table_head)); - memset(&(*nconf)->listen_head, 0, sizeof((*nconf)->listen_head)); - break; - case IMSG_RECONF_PKI: - if (*nconf == NULL) - fatalx("%s: IMSG_RECONF_PKI without " - "IMSG_RECONF_CONF", __func__); - *pki = xcalloc(1, sizeof(**pki)); - t = imsg->data; - t[IMSG_DATA_SIZE(*imsg)-1] = '\0'; - strlcpy((*pki)->name, t, sizeof((*pki)->name)); - break; - case IMSG_RECONF_PKI_CERT: - if (*pki == NULL) - fatalx("%s: IMSG_RECONF_PKI_CERT without " - "IMSG_RECONF_PKI", __func__); - (*pki)->certlen = IMSG_DATA_SIZE(*imsg); - (*pki)->cert = xmemdup(imsg->data, (*pki)->certlen); - break; - case IMSG_RECONF_PKI_KEY: - if (*pki == NULL) - fatalx("%s: IMSG_RECONF_PKI_KEY without " - "IMSG_RECONF_PKI", __func__); - (*pki)->keylen = IMSG_DATA_SIZE(*imsg); - (*pki)->key = xmemdup(imsg->data, (*pki)->keylen); - STAILQ_INSERT_HEAD(&(*nconf)->pki_head, *pki, entry); - pki = NULL; - break; - case IMSG_RECONF_LISTEN: - if (*nconf == NULL) - fatalx("%s: IMSG_RECONF_LISTEN without " - "IMSG_RECONF_CONF", __func__); - if (IMSG_DATA_SIZE(*imsg) != sizeof(*listen)) - fatalx("%s: IMSG_RECONF_LISTEN wrong length: %lu", - __func__, IMSG_DATA_SIZE(*imsg)); - listen = xcalloc(1, sizeof(*listen)); - memcpy(listen, imsg->data, sizeof(*listen)); - memset(&listen->entry, 0, sizeof(listen->entry)); - if ((listen->fd = imsg->fd) == -1) - fatalx("%s: IMSG_RECONF_LISTEN no fd", - __func__); - listen->auth_table = NULL; - memset(&listen->ev, 0, sizeof(listen->ev)); - STAILQ_INSERT_HEAD(&(*nconf)->listen_head, listen, entry); - break; - case IMSG_RECONF_END: - if (*nconf == NULL) - fatalx("%s: IMSG_RECONF_END without " - "IMSG_RECONF_CONF", __func__); - /* merge_config(listener_conf, nconf); */ - apply_config(*nconf); - *nconf = NULL; - break; - } -} - -static inline struct kd_listen_conf * -listen_by_id(uint32_t id) -{ - struct kd_listen_conf *l; - - STAILQ_FOREACH(l, &listener_conf->listen_head, entry) { - if (l->id == id) - return l; - } - return NULL; -} - -void -listener_dispatch_main(int fd, short event, void *d) -{ - static struct kd_conf *nconf; - static struct kd_pki_conf *pki; - struct kd_listen_conf *listen; - struct client *client, find; - struct imsg imsg; - struct imsgev *iev = d; - struct imsgbuf *ibuf; - ssize_t n; - int shut = 0; - - ibuf = &iev->ibuf; - - if (event & EV_READ) { - if ((n = imsg_read(ibuf)) == -1 && errno != EAGAIN) - fatal("imsg_read error"); - if (n == 0) /* Connection closed. */ - shut = 1; - } - if (event & EV_WRITE) { - if ((n = msgbuf_write(&ibuf->w)) == -1 && errno != EAGAIN) - fatal("msgbuf_write"); - if (n == 0) /* Connection closed. */ - shut = 1; - } - - for (;;) { - if ((n = imsg_get(ibuf, &imsg)) == -1) - fatal("%s: imsg_get error", __func__); - if (n == 0) /* No more messages. */ - break; - - switch (imsg.hdr.type) { - case IMSG_CONTROLFD: - if ((fd = imsg.fd) == -1) - fatalx("%s: expected to receive imsg " - "control fd but didn't receive any", - __func__); - /* Listen on control socket. */ - control_listen(fd); - break; - case IMSG_RECONF_CONF: - case IMSG_RECONF_PKI: - case IMSG_RECONF_PKI_CERT: - case IMSG_RECONF_PKI_KEY: - case IMSG_RECONF_LISTEN: - case IMSG_RECONF_END: - listener_receive_config(&imsg, &nconf, &pki); - break; - case IMSG_AUTH: - find.id = imsg.hdr.peerid; - client = SPLAY_FIND(clients_tree_id, &clients, &find); - if (client == NULL) { - if (imsg.fd != -1) - close(imsg.fd); - break; - } - if (imsg.fd == -1) { - log_info("got fd = -1, auth failed?"); - close_conn(client); - break; - } - imsg_init(&client->iev.ibuf, imsg.fd); - client->iev.events = EV_READ; - client->iev.handler = listener_dispatch_client; - event_set(&client->iev.ev, client->iev.ibuf.fd, - client->iev.events, client->iev.handler, client); - listener_imsg_compose_client(client, IMSG_AUTH, - client->id, imsg.data, IMSG_DATA_SIZE(imsg)); - break; - case IMSG_AUTH_DIR: - find.id = imsg.hdr.peerid; - client = SPLAY_FIND(clients_tree_id, &clients, &find); - if (client == NULL) { - log_info("got AUTH_DIR but client gone"); - break; - } - - listener_imsg_compose_client(client, IMSG_AUTH_DIR, - 0, imsg.data, IMSG_DATA_SIZE(imsg)); - - client->bev = bufferevent_new(client->fd, - client_read, client_write, client_error, - client); - if (client->bev == NULL) { - log_info("failed to allocate client buffer"); - close_conn(client); - return; - } - -#if HAVE_EVENT2 - evbuffer_unfreeze(client->bev->input, 0); - evbuffer_unfreeze(client->bev->output, 1); -#endif - - listen = listen_by_id(client->lid); - if (listen->flags & L_TLS) { - event_set(&client->bev->ev_read, client->fd, - EV_READ, client_tls_readcb, client->bev); - event_set(&client->bev->ev_write, client->fd, - EV_WRITE, client_tls_writecb, client->bev); - } - - /* - * Read or write at least a header before - * firing the callbacks. High watermark of 0 - * to never stop reading/writing; probably to - * be revisited. - */ - /* bufferevent_setwatermark(client->bev, EV_READ|EV_WRITE, */ - /* sizeof(struct np_msg_header), 0); */ - bufferevent_enable(client->bev, EV_READ|EV_WRITE); - break; - - default: - log_debug("%s: unexpected imsg %d", __func__, - imsg.hdr.type); - break; - } - imsg_free(&imsg); - } - - if (!shut) - listener_imsg_event_add(iev, d); - else { - /* This pipe is dead. Remove its event handler. */ - event_del(&iev->ev); - log_warnx("pipe closed, shutting down..."); - event_loopexit(NULL); - } -} - -int -listener_imsg_compose_main(int type, uint32_t peerid, const void *data, - uint16_t datalen) -{ - return imsg_compose_event(iev_main, type, peerid, 0, -1, data, - datalen); -} - -static void -listener_imsg_event_add(struct imsgev *iev, void *d) -{ - iev->events = EV_READ; - if (iev->ibuf.w.queued) - iev->events |= EV_WRITE; - - event_del(&iev->ev); - event_set(&iev->ev, iev->ibuf.fd, iev->events, iev->handler, d); - event_add(&iev->ev, NULL); -} - -static void -listener_dispatch_client(int fd, short event, void *d) -{ - struct client find, *client = d; - struct imsg imsg; - struct imsgev *iev; - struct imsgbuf *ibuf; - ssize_t n; - int r, shut = 0; - - iev = &client->iev; - ibuf = &iev->ibuf; - - if (event & EV_READ) { - if ((n = imsg_read(ibuf)) == -1 && errno != EAGAIN) - fatal("imsg_read error"); - if (n == 0) /* Connection closed */ - shut = 1; - } - - if (event & EV_WRITE) { - if ((n = msgbuf_write(&ibuf->w)) == -1 && errno != EAGAIN) - fatal("msgbuf_write"); - if (n == 0) /* Connection closed. */ - shut = 1; - } - - for (;;) { - if ((n = imsg_get(ibuf, &imsg)) == -1) - fatal("%s: imsg_get error", __func__); - if (n == 0) /* No more messages. */ - break; - - switch (imsg.hdr.type) { - case IMSG_BUF: - find.id = imsg.hdr.peerid; - client = SPLAY_FIND(clients_tree_id, &clients, &find); - if (client == NULL) { - log_info("got IMSG_BUF but client (%d) gone", - imsg.hdr.peerid); - break; - } - r = bufferevent_write(client->bev, imsg.data, - IMSG_DATA_SIZE(imsg)); - if (r == -1) { - log_warn("%s: bufferevent_write failed", - __func__); - close_conn(client); - break; - } - break; - - case IMSG_MSIZE: - if (IMSG_DATA_SIZE(imsg) != sizeof(client->msize)) - fatal("IMSG_MSIZE size mismatch: " - "got %zu want %zu", IMSG_DATA_SIZE(imsg), - sizeof(client->msize)); - - memcpy(&client->msize, imsg.data, - sizeof(client->msize)); - - if (client->msize == 0) - fatal("IMSG_MSIZE got msize = 0"); - - break; - - case IMSG_CLOSE: - /* - * Both EVBUFFER_READ or EVBUFFER_WRITE should - * be fine. - */ - client_error(client->bev, EVBUFFER_READ, client); - break; - - default: - log_debug("%s: unexpected imsg %d", __func__, - imsg.hdr.type); - break; - } - imsg_free(&imsg); - } - - if (!shut) - listener_imsg_event_add(iev, d); - else { - /* This pipe is dead. Remove its handler */ - log_debug("client proc vanished"); - close_conn(client); - } -} - -static int -listener_imsg_compose_client(struct client *client, int type, - uint32_t peerid, const void *data, uint16_t len) -{ - int ret; - - if ((ret = imsg_compose(&client->iev.ibuf, type, peerid, 0, -1, - data, len)) != -1) - listener_imsg_event_add(&client->iev, client); - - return ret; -} - -static inline struct kd_pki_conf * -pki_by_name(const char *name) -{ - struct kd_pki_conf *pki; - - STAILQ_FOREACH(pki, &listener_conf->pki_head, entry) { - if (!strcmp(name, pki->name)) - return pki; - } - - return NULL; -} - -static void -apply_config(struct kd_conf *conf) -{ - struct kd_pki_conf *pki; - struct kd_listen_conf *listen; - - listener_conf = conf; - - /* prepare the various tls_config */ - STAILQ_FOREACH(pki, &listener_conf->pki_head, entry) { - if ((pki->tlsconf = tls_config_new()) == NULL) - fatal("tls_config_new"); - tls_config_verify_client_optional(pki->tlsconf); - tls_config_insecure_noverifycert(pki->tlsconf); - if (tls_config_set_keypair_mem(pki->tlsconf, - pki->cert, pki->certlen, - pki->key, pki->keylen) == -1) - fatalx("tls_config_set_keypair_mem: %s", - tls_config_error(pki->tlsconf)); - } - - /* prepare and kickoff the listeners */ - STAILQ_FOREACH(listen, &listener_conf->listen_head, entry) { - if ((listen->ctx = tls_server()) == NULL) - fatal("tls_server"); - - pki = pki_by_name(listen->pki); - if (tls_configure(listen->ctx, pki->tlsconf) == -1) - fatalx("tls_configure: %s", - tls_config_error(pki->tlsconf)); - - event_set(&listen->ev, listen->fd, EV_READ|EV_PERSIST, - handle_accept, listen); - event_add(&listen->ev, NULL); - } -} - -static inline void -yield_r(struct client *c, void (*fn)(int, short, void *)) -{ - if (event_pending(&c->event, EV_WRITE|EV_READ, NULL)) - event_del(&c->event); - event_set(&c->event, c->fd, EV_READ, fn, c); - event_add(&c->event, NULL); -} - -static inline void -yield_w(struct client *c, void (*fn)(int, short, void *)) -{ - if (event_pending(&c->event, EV_WRITE|EV_READ, NULL)) - event_del(&c->event); - event_set(&c->event, c->fd, EV_WRITE, fn, c); - event_add(&c->event, NULL); -} - -static inline uint32_t -random_id(void) -{ -#if HAVE_ARC4RANDOM -# define RANDID() arc4random() -#else - /* not as pretty as a random id */ - static uint32_t counter = 0; -# define RANDID() counter++ -#endif - - struct client find, *res; - - for (;;) { - find.id = RANDID(); - res = SPLAY_FIND(clients_tree_id, &clients, &find); - if (res == NULL) - return find.id; - } - -#undef RANDID -} - -static void -handle_accept(int fd, short ev, void *data) -{ - struct kd_listen_conf *listen = data; - struct client *c; - int s; - - if ((s = accept(fd, NULL, NULL)) == -1) { - log_warn("accept"); - return; - } - - c = xcalloc(1, sizeof(*c)); - c->msize = MSIZE9P; - c->lid = listen->id; - c->iev.ibuf.fd = -1; - - if (tls_accept_socket(listen->ctx, &c->ctx, s) == -1) { - log_warnx("tls_accept_socket: %s", - tls_error(listen->ctx)); - free(c); - close(s); - return; - } - - c->fd = s; - c->id = random_id(); - - SPLAY_INSERT(clients_tree_id, &clients, c); - - /* initialize the event */ - event_set(&c->event, c->fd, EV_READ, NULL, NULL); - - yield_r(c, handle_handshake); -} - -static void -handle_handshake(int fd, short ev, void *data) -{ - struct client *c = data; - struct kd_auth_req auth; - ssize_t r; - const char *hash; - - switch (r = tls_handshake(c->ctx)) { - case TLS_WANT_POLLIN: - yield_r(c, handle_handshake); - return; - case TLS_WANT_POLLOUT: - yield_w(c, handle_handshake); - return; - case -1: - log_debug("handhsake failed: %s", tls_error(c->ctx)); - close_conn(c); - return; - } - - if ((hash = tls_peer_cert_hash(c->ctx)) == NULL) { - log_warnx("client didn't provide certificate"); - close_conn(c); - return; - } - - memset(&auth, 0, sizeof(auth)); - auth.listen_id = c->lid; - strlcpy(auth.hash, hash, sizeof(auth.hash)); - log_debug("sending hash %s", auth.hash); - - listener_imsg_compose_main(IMSG_AUTH_TLS, c->id, - &auth, sizeof(auth)); -} - -static void -client_read(struct bufferevent *bev, void *d) -{ - struct client *client = d; - struct evbuffer *src = EVBUFFER_INPUT(bev); - uint32_t len; - - for (;;) { - if (EVBUFFER_LENGTH(src) < 4) - return; - - memcpy(&len, EVBUFFER_DATA(src), sizeof(len)); - len = le32toh(len); - log_debug("expecting a message %"PRIu32" bytes long " - "(of wich %zu already read)", - len, EVBUFFER_LENGTH(src)); - - if (len < HEADERSIZE) { - log_warnx("invalid message size %d (too low)", len); - client_error(bev, EVBUFFER_READ, client); - return; - } - - if (len > client->msize) { - log_warnx("incoming message bigger than msize " - "(%"PRIu32" vs %"PRIu32")", len, client->msize); - client_error(bev, EVBUFFER_READ, client); - return; - } - - if (len > EVBUFFER_LENGTH(src)) - return; - - listener_imsg_compose_client(client, IMSG_BUF, client->id, - EVBUFFER_DATA(src), len); - evbuffer_drain(src, len); - } -} - -static void -client_write(struct bufferevent *bev, void *d) -{ - /* - * here we can do some fancy logic like deciding when to call - * - * (*bev->errorcb)(bev, EVBUFFER_WRITE, bev->cbarg) - * - * to signal the end of the transaction. - */ - - return; -} - -static void -client_error(struct bufferevent *bev, short err, void *d) -{ - struct client *client = d; - struct evbuffer *buf; - - if (err & EVBUFFER_ERROR) { - if (errno == EFBIG) { - bufferevent_enable(bev, EV_READ); - return; - } - log_debug("buffer event error"); - close_conn(client); - return; - } - - if (err & EVBUFFER_EOF) { - close_conn(client); - return; - } - - if (err & (EVBUFFER_READ|EVBUFFER_WRITE)) { - bufferevent_disable(bev, EV_READ|EV_WRITE); - client->done = 1; - - buf = EVBUFFER_OUTPUT(client->bev); - if (EVBUFFER_LENGTH(buf) != 0) { - /* finish writing all the data first */ - bufferevent_enable(client->bev, EV_WRITE); - return; - } - - close_conn(client); - return; - } - - log_warnx("unknown event error, closing client connection"); - close_conn(client); -} - -static void -client_tls_readcb(int fd, short event, void *d) -{ - struct bufferevent *bufev = d; - struct client *client = bufev->cbarg; - char buf[IBUF_READ_SIZE]; - int what = EVBUFFER_READ; - int howmuch = IBUF_READ_SIZE; - ssize_t ret; - size_t len; - - if (event == EV_TIMEOUT) { - what |= EVBUFFER_TIMEOUT; - goto err; - } - - if (bufev->wm_read.high != 0) - howmuch = MIN(sizeof(buf), bufev->wm_read.high); - - switch (ret = tls_read(client->ctx, buf, howmuch)) { - case TLS_WANT_POLLIN: - case TLS_WANT_POLLOUT: - goto retry; - case -1: - what |= EVBUFFER_ERROR; - goto err; - } - len = ret; - - if (len == 0) { - what |= EVBUFFER_EOF; - goto err; - } - - if (evbuffer_add(bufev->input, buf, len) == -1) { - what |= EVBUFFER_ERROR; - goto err; - } - - event_add(&bufev->ev_read, NULL); - - len = EVBUFFER_LENGTH(bufev->input); - if (bufev->wm_read.low != 0 && len < bufev->wm_read.low) - return; - if (bufev->wm_read.high != 0 && len > bufev->wm_read.high) { - /* - * here we could implement some read pressure - * mechanism. - */ - } - - if (bufev->readcb != NULL) - (*bufev->readcb)(bufev, bufev->cbarg); - - return; - -retry: - event_add(&bufev->ev_read, NULL); - return; - -err: - (*bufev->errorcb)(bufev, what, bufev->cbarg); -} - -static void -client_tls_writecb(int fd, short event, void *d) -{ - struct bufferevent *bufev = d; - struct client *client = bufev->cbarg; - ssize_t ret; - size_t len; - short what = EVBUFFER_WRITE; - - if (event == EV_TIMEOUT) { - what |= EVBUFFER_TIMEOUT; - goto err; - } - - if (EVBUFFER_LENGTH(bufev->output) != 0) { - ret = tls_write(client->ctx, - EVBUFFER_DATA(bufev->output), - EVBUFFER_LENGTH(bufev->output)); - switch (ret) { - case TLS_WANT_POLLIN: - case TLS_WANT_POLLOUT: - goto retry; - case -1: - what |= EVBUFFER_ERROR; - goto err; - } - len = ret; - evbuffer_drain(bufev->output, len); - } - - if (EVBUFFER_LENGTH(bufev->output) != 0) - event_add(&bufev->ev_write, NULL); - - if (bufev->writecb != NULL && - EVBUFFER_LENGTH(bufev->output) <= bufev->wm_write.low) - (*bufev->writecb)(bufev, bufev->cbarg); - return; - -retry: - event_add(&bufev->ev_write, NULL); - return; - -err: - (*bufev->errorcb)(bufev, what, bufev->cbarg); -} - -static void -close_conn(struct client *c) -{ - log_debug("closing connection"); - - if (c->iev.ibuf.fd != -1) { - listener_imsg_compose_client(c, IMSG_CONN_GONE, 0, NULL, 0); - imsg_flush(&c->iev.ibuf); - msgbuf_clear(&c->iev.ibuf.w); - event_del(&c->iev.ev); - close(c->iev.ibuf.fd); - } - - handle_close(c->fd, 0, c); -} - -static void -handle_close(int fd, short ev, void *d) -{ - struct client *c = d; - - switch (tls_close(c->ctx)) { - case TLS_WANT_POLLIN: - yield_r(c, handle_close); - return; - case TLS_WANT_POLLOUT: - yield_w(c, handle_close); - return; - } - - event_del(&c->event); - tls_free(c->ctx); - close(c->fd); - free(c); -} blob - 4869c415ac1237dc481e958ab418713eb08d313b (mode 644) blob + /dev/null --- listener.h +++ /dev/null @@ -1,26 +0,0 @@ -/* - * Copyright (c) 2021 Omar Polo - * - * Permission to use, copy, modify, and distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. - * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - */ - -#ifndef LISTENER_H -#define LISTENER_H - -#include "compat.h" - -void listener(int, int); -void listener_dispatch_main(int, short, void *); -int listener_imsg_compose_main(int, uint32_t, const void *, uint16_t); - -#endif blob - 859eb2b7dc29fb815517252d515596e8a2c801f3 (mode 644) blob + /dev/null --- log.c +++ /dev/null @@ -1,198 +0,0 @@ -/* - * Copyright (c) 2003, 2004 Henning Brauer - * - * Permission to use, copy, modify, and distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. - * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - */ - -#include "compat.h" - -#include -#include -#include -#include -#include -#include -#include - -#include "log.h" - -static int debug; -static int verbose; -static const char *log_procname; - -void -log_init(int n_debug, int facility) -{ - debug = n_debug; - verbose = n_debug; - log_procinit(getprogname()); - - if (!debug) - openlog(getprogname(), LOG_PID | LOG_NDELAY, facility); - - tzset(); -} - -void -log_procinit(const char *procname) -{ - if (procname != NULL) - log_procname = procname; -} - -void -log_setverbose(int v) -{ - verbose = v; -} - -int -log_getverbose(void) -{ - return (verbose); -} - -void -logit(int pri, const char *fmt, ...) -{ - va_list ap; - - va_start(ap, fmt); - vlog(pri, fmt, ap); - va_end(ap); -} - -void -vlog(int pri, const char *fmt, va_list ap) -{ - char *nfmt; - int saved_errno = errno; - - if (debug) { - /* best effort in out of mem situations */ - if (asprintf(&nfmt, "%s: %s\n", log_procname, fmt) == -1) { - fprintf(stderr, "%s: ", log_procname); - vfprintf(stderr, fmt, ap); - fprintf(stderr, "\n"); - } else { - vfprintf(stderr, nfmt, ap); - free(nfmt); - } - fflush(stderr); - } else - vsyslog(pri, fmt, ap); - - errno = saved_errno; -} - -void -log_warn(const char *emsg, ...) -{ - char *nfmt; - va_list ap; - int saved_errno = errno; - - /* best effort to even work in out of memory situations */ - if (emsg == NULL) - logit(LOG_ERR, "%s", strerror(saved_errno)); - else { - va_start(ap, emsg); - - if (asprintf(&nfmt, "%s: %s", emsg, - strerror(saved_errno)) == -1) { - /* we tried it... */ - vlog(LOG_ERR, emsg, ap); - logit(LOG_ERR, "%s", strerror(saved_errno)); - } else { - vlog(LOG_ERR, nfmt, ap); - free(nfmt); - } - va_end(ap); - } - - errno = saved_errno; -} - -void -log_warnx(const char *emsg, ...) -{ - va_list ap; - - va_start(ap, emsg); - vlog(LOG_ERR, emsg, ap); - va_end(ap); -} - -void -log_info(const char *emsg, ...) -{ - va_list ap; - - va_start(ap, emsg); - vlog(LOG_INFO, emsg, ap); - va_end(ap); -} - -void -log_debug(const char *emsg, ...) -{ - va_list ap; - - if (verbose) { - va_start(ap, emsg); - vlog(LOG_DEBUG, emsg, ap); - va_end(ap); - } -} - -static void -vfatalc(int code, const char *emsg, va_list ap) -{ - static char s[BUFSIZ]; - const char *sep; - - if (emsg != NULL) { - (void)vsnprintf(s, sizeof(s), emsg, ap); - sep = ": "; - } else { - s[0] = '\0'; - sep = ""; - } - if (code) - logit(LOG_CRIT, "fatal in %s: %s%s%s", - log_procname, s, sep, strerror(code)); - else - logit(LOG_CRIT, "fatal in %s%s%s", log_procname, sep, s); -} - -void -fatal(const char *emsg, ...) -{ - va_list ap; - - va_start(ap, emsg); - vfatalc(errno, emsg, ap); - va_end(ap); - exit(1); -} - -void -fatalx(const char *emsg, ...) -{ - va_list ap; - - va_start(ap, emsg); - vfatalc(0, emsg, ap); - va_end(ap); - exit(1); -} blob - 06d92fcce2b042403dc0d18cc578cf59e8fbc9d8 (mode 644) blob + /dev/null --- log.h +++ /dev/null @@ -1,43 +0,0 @@ -/* - * Copyright (c) 2003, 2004 Henning Brauer - * - * Permission to use, copy, modify, and distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. - * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - */ - -#ifndef LOG_H -#define LOG_H - -#include "compat.h" - -void log_init(int, int); -void log_procinit(const char *); -void log_setverbose(int); -int log_getverbose(void); -void log_warn(const char *, ...) - __attribute__((__format__ (printf, 1, 2))); -void log_warnx(const char *, ...) - __attribute__((__format__ (printf, 1, 2))); -void log_info(const char *, ...) - __attribute__((__format__ (printf, 1, 2))); -void log_debug(const char *, ...) - __attribute__((__format__ (printf, 1, 2))); -void logit(int, const char *, ...) - __attribute__((__format__ (printf, 2, 3))); -void vlog(int, const char *, va_list) - __attribute__((__format__ (printf, 2, 0))); -__dead void fatal(const char *, ...) - __attribute__((__format__ (printf, 1, 2))); -__dead void fatalx(const char *, ...) - __attribute__((__format__ (printf, 1, 2))); - -#endif /* LOG_H */ blob - 6da049b0758a48894158408f942e91823baa3ade (mode 644) blob + /dev/null --- ninepscript.5 +++ /dev/null @@ -1,265 +0,0 @@ -.\" Copyright (c) 2021 Omar Polo -.\" -.\" Permission to use, copy, modify, and distribute this software for any -.\" purpose with or without fee is hereby granted, provided that the above -.\" copyright notice and this permission notice appear in all copies. -.\" -.\" THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES -.\" WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF -.\" MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR -.\" ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES -.\" WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN -.\" ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF -.\" OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. -.\" -.Dd $Mdocdate: December 02 2021$ -.Dt NINEPSCRIPT 5 -.Os -.Sh NAME -.Nm ninepscript -.Nd kamid regress test scripting language -.Sh DESCRIPTION -.Nm -is a custom DSL -.Pq domain specific language -used to write the regression suite of -.Xr kamid 8 . -it has a fairly simple and regular syntax that features constant -declarations, routines, test cases. -It does not support conditional or loops. -.Pp -Additional files can be included with the -.Ic include -keyword, for example -.Bd -literal -offset Ds -include "lib.9ps" -.Ed -.Pp -Comments can be placed anywhere, start with the # character and extend -until the end of the line. -.Pp -An expression is a fundamental building block. -It is something that yields a value. -An expression may be either a: -.Bl -tag -width variable_reference -.It literal -a bare number or string. -A string is a sequence of characters enclosed in single or double quotes -.Sq like this -or -.Dq like this . -.It routine call -Evaluate the routine code and return the value computed by it. -The syntax is -.Bd -literal -offset Ds -.Ar routine Ns Po Ar arguments... Pc -.Ed -.Pp -The -.Ql ... -special syntax expands to the list of variable arguments of the -current routine. -Be aware that the implementation of the variable arguments is quirky -and has a lot of corner cases, use with care! -.It variable reference -a variable -.Pq or constant -reference is the name of a previously defined variable or constant. -It evaluates to the value of the variable or constant in the current -scope. -.It comparison -The syntax is -.Bd -literal -offset Ds -.Ar expression Cm == Ar expression -.Ar expression Cm <= Ar expression -.Ed -.Pp -and yields a true value if the two expressions are considered to be -respectively -.Sq equal -or -.Sq lesser equal , -a false value otherwise. -Two values are equal if they are both number and represent the same -value -.Pq regardless of the size -or if they're both the same string. -.It cast -convert one value to another type. -The syntax is -.Ql Ar expression : Ns Ar type -where type is one of -.Sq u8 , -.Sq u16 , -.Sq u32 -or -.Sq str . -.It field access -Access a field of a complex object. -The syntax is -.Ql Ar object . Ns Ar field . -See the -.Sx OBJECTS AND FIELDS -section for the description of objects types and fields allowed. -.El -.Pp -An expression is considered to be -.Dq false -if evaluates to a number and its value is zero. -Otherwise, it's considered to be -.Dq true . -.Pp -The top-level declarations are: -.Bl -tag -width Ds -.It Ic const Ar identifier No = Ar value -Declare -.Ar identifier -to be a constant that evaluates to -.Ar value . -.Ar value -must be a literal or a cast from a literal. -Multiple constant can be declared at the same time using the following -syntax: -.Bd -literal -offset Ds -.Ic const ( - foo = 5 - bar = 7 -) -.Ed -.Pp -Note that newlines are mandatory after an -.Ar identifier No = Ar value -line in this case. -.It Ic proc Ar name Ns Po Ar arguments ... Pc Brq code ... -Define a routine called -.Ar name -that accepts the comma-separated list of -.Ar arguments . -When a routine is called, its -.Ar code -gets evaluated in a lexical scope where -.Ar arguments -are defined to the value passed by the caller. -A routine may be called only within another routine body or inside a -.Ic testing -body. -.It Ic testing Ar reason Ic dir Ar path Brq code ... -Define a test case. -.Ar reason -is what the test block is about and -.Ar path -is the path to the root directory where the test will be executed. -.Ar reason -and -.Ar path -must be string literals. -.El -.Pp -Inside a -.Ic proc -or -.Ic testing -code block the following instructions are allowed: -.Bl -tag -width Ds -.It Ar variable Cm = Ar expression -Set a local lexical -.Ar variable -to the value yielded by -.Ar expression . -The -.Ar variable -lifetime last from this declaration until the end of the current -block. -.It Ar procedure Ns Pq Ar arguments ... -Execute -.Ar procedure -with the given -.Ar arguments . -.It Ic assert Ar comparison -Evaluate -.Ar comparison -and if it not yields a true-ish value terminate the current running -test and mark it as failed. -Multiple assertion can be done in one single -.Ic assert -block using the following syntax: -.Bd -literal -offset Ds -.Ic assert ( - comparison_1 - comparison_2 - ... - comparison_n -) -.Ed -.Pp -Note that newlines are mandatory after every -.Ar comparison -in this case. -.It Ic should-fail Ar expression Op : Ar reason -Evaluate -.Ar expression -and continue only if the evaluation produced an error. -If the execution of -.Ar expression -is successful, terminate the current test. -.Ar reason -is optional and, if present, must be a literal string. -It is similar to the -.Sq try-catch -statement of other programming languages. -.El -.Sh BUILT IN FUNCTIONS -These functions are built into the language and provided by the -interpreter: -.Bl -tag -width Ds -.It Ic debug Ns Po Ar arg, ... Pc -Print the argument list separated by a space and followed by a newline -if the interpreter runs with the verbose flag set. -.It Ic iota Ns Pq -Return distinct u16 integer every time it's called. -Starts at zero and goes up to 254 to then wrap around to zero again. -255 is skipped because -.Ic iota -is intended to be used to provide the tag for -.Ic send -and 255 is the special -.Sq NOTAG -value in 9P200. -.It Ic print Ns Po Ar arg, ... Pc -Print the argument list separated by a space and followed by a -newline. -.It Ic recv Ns Pq -Receive a message from the server and return it as an object. -A -.Dv Terror -doesn't stop the execution of the test, rather, an error object is -returned. -See -.Sx OBJECTS AND FIELDS -for the complete list of objects. -.It Ic send Ns Po Ar type, tag, ... Pc -Send a 9P message with the given -.Ar type -and -.Ar tag . -Other arguments, if given, are packed into the message and sent as -well, respecting the given order. -The overall length of the message is computed automatically. -.It Ic skip Ns Pq -Terminate the execution of the current test suite immediately. -The test won't be counted as passed nor failed, but as skipped. -.El -.Sh OBJECTS AND FIELDS -List of objects and fields... -.Sh SEE ALSO -.Xr 9p 7 , -.Xr kamid 8 , -.Xr ninepscript 8 -.Sh AUTHORS -.An -nosplit -.Nm -was designed and implemented by -.An Omar Polo Aq Mt op@omarpolo.com -for the -.Xr kamid 8 -daemon regression suite. blob - 2df71001f2c0ddff2c4217feba1820fecb17df36 (mode 644) blob + /dev/null --- ninepscript.8 +++ /dev/null @@ -1,54 +0,0 @@ -.\" Copyright (c) 2021 Omar Polo -.\" -.\" Permission to use, copy, modify, and distribute this software for any -.\" purpose with or without fee is hereby granted, provided that the above -.\" copyright notice and this permission notice appear in all copies. -.\" -.\" THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES -.\" WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF -.\" MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR -.\" ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES -.\" WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN -.\" ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF -.\" OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. -.\" -.Dd $Mdocdate: December 02 2021$ -.Dt NINEPSCRIPT 8 -.Os -.Sh NAME -.Nm ninepscript -.Nd scripting language for the kamid regress suite -.Sh SYNOPSIS -.Nm -.Op Fl nv -.Op Fl x Ar pattern -.Ar -.Sh DESCRIPTION -.Nm -is an interpreter for a custom DSL -.Pq domain sppecific language -used to write the regression suite of -.Xr kamid 8 . -The test themselves are written in -.Xr ninepscript 5 . -.Pp -The options are as follows: -.Bl -tag -width Ds -.It Fl n -don't run the test, check only the syntax of the provided -.Ar files . -.It Fl v -verbose logging, print more information during the tests execution. -.It Fl x Ar pattern -Run only the tests that match the given -.Ar pattern . -.El -.Pp -.Nm -first loads all the given -.Ar files -then proceeds to execute each defined test. -.Pp -See -.Xr ninepscript 5 -for the description of the ninepscript language. blob - 3b22364359c6f834886ffda7912b580339df0524 (mode 644) blob + /dev/null --- np.y +++ /dev/null @@ -1,686 +0,0 @@ -/* - * Copyright (c) 2021 Omar Polo - * Copyright (c) 2018 Florian Obser - * Copyright (c) 2004, 2005 Esben Norby - * Copyright (c) 2004 Ryan McBride - * Copyright (c) 2002, 2003, 2004 Henning Brauer - * Copyright (c) 2001 Markus Friedl. All rights reserved. - * Copyright (c) 2001 Daniel Hartmeier. All rights reserved. - * Copyright (c) 2001 Theo de Raadt. All rights reserved. - * - * Permission to use, copy, modify, and distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. - * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - */ - -%{ - -#include "compat.h" - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "log.h" -#include "kamid.h" -#include "utils.h" - -#include "script.h" - -TAILQ_HEAD(files, file) files = TAILQ_HEAD_INITIALIZER(files); -static struct file { - TAILQ_ENTRY(file) entry; - FILE *stream; - char *name; - size_t ungetpos; - size_t ungetsize; - u_char *ungetbuf; - int eof_reached; - int lineno; - int errors; -} *file, *topfile; -struct file *pushfile(const char *); -int popfile(void); -int yyparse(void); -int yylex(void); -int yyerror(const char *, ...) - __attribute__((__format__ (printf, 1, 2))) - __attribute__((__nonnull__ (1))); -int kw_cmp(const void *, const void *); -int lookup(char *); -int igetc(void); -int lgetc(int); -void lungetc(int); -int findeol(void); - -static int shouldfail; - -typedef struct { - union { - struct op *op; - struct proc *proc; - char *str; - int64_t num; - } v; - int lineno; -} YYSTYPE; - -%} - -/* - * for bison: - * %define parse.error verbose - */ - -%token ASSERT -%token CONST -%token DIR -%token ERROR -%token INCLUDE -%token PROC -%token REPEAT -%token SHOULD_FAIL STR -%token TESTING -%token U8 U16 U32 -%token VARGS - -%token STRING SYMBOL -%token NUMBER - -%type cast cexpr check expr faccess funcall -%type literal sfail var varref vargs - -%type procname - -%% - -program : /* empty */ - | program '\n' - | program include '\n' - | program const '\n' - | program proc '\n' - | program test '\n' - ; - -optnl : '\n' optnl /* zero or more newlines */ - | /*empty*/ - ; - -nl : '\n' optnl ; - -include : INCLUDE STRING { - struct file *nfile; - - if ((nfile = pushfile($2)) == NULL) { - yyerror("failed to include file %s", $2); - free($2); - YYERROR; - } - free($2); - - file = nfile; - lungetc('\n'); - } - ; - -const : CONST consti - | CONST '(' optnl mconst ')' - ; - -mconst : consti nl | mconst consti nl ; - -consti : SYMBOL '=' expr { - if (!global_set($1, $3)) { - yyerror("can't set %s: illegal expression", $1); - free($1); - free_op($3); - YYERROR; - } - } - ; - -var : SYMBOL '=' expr { $$ = op_assign($1, $3); } ; -varref : SYMBOL { $$ = op_var($1); } ; -literal : STRING { $$ = op_lit_str($1); } - | NUMBER { $$ = op_lit_num($1); } ; - -/* - * `expr '=' '=' expr` is ambiguous. furthermore, we're not - * interested in checking all the possibilities here. - */ -cexpr : literal | varref | funcall | faccess ; -check : cexpr '=' '=' cexpr { $$ = op_cmp_eq($1, $4); } - | cexpr '<' '=' cexpr { $$ = op_cmp_leq($1, $4); } - ; - -expr : literal | funcall | varref | check | cast | faccess | vargs ; - -vargs : VARGS { $$ = op_vargs(); } ; - -cast : expr ':' U8 { $$ = op_cast($1, V_U8); } - | expr ':' U16 { $$ = op_cast($1, V_U16); } - | expr ':' U32 { $$ = op_cast($1, V_U32); } - | expr ':' STR { $$ = op_cast($1, V_STR); } - ; - -faccess : varref '.' SYMBOL { $$ = op_faccess($1, $3); } - | faccess '.' SYMBOL { $$ = op_faccess($1, $3); } - ; - -procname: SYMBOL { - if (($$ = proc_by_name($1)) == NULL) { - yyerror("unknown proc %s", $1); - free($1); - YYERROR; - } - free($1); - } - ; - -funcall : procname { - prepare_funcall(); - } '(' args optcomma ')' { - struct proc *proc; - int argc; - - $$ = op_funcall($1); - proc = $$->v.funcall.proc; - argc = $$->v.funcall.argc; - - if (argc != proc->minargs && !proc->vararg) { - yyerror("invalid arity for `%s': want %d arguments " - "but %d given.", $1->name, proc->minargs, argc); - /* TODO: recursively free $$ */ - YYERROR; - } - - if (argc < proc->minargs && proc->vararg) { - yyerror("invalid arity for `%s': want at least %d " - "arguments but %d given.", $1->name, proc->minargs, - argc); - /* TODO: recursively free $$ */ - YYERROR; - } - } - ; - -optcomma: /* empty */ | ',' ; - -dots : '.' '.' '.' ; - -args : /* empty */ - | args ',' expr { push_arg($3); } - | args ',' dots { push_arg(op_rest()); } - | expr { push_arg($1); } - | dots { push_arg(op_rest()); } - ; - -proc : PROC SYMBOL { - prepare_proc(); - } '(' args ')' { - if (!proc_setup_body()) { - yyerror("invalid argument in proc `%s' definition", - $2); - free($2); - YYERROR; - } - } '{' optnl block '}' { - proc_done($2); - } - ; - -block : /* empty */ - | block var nl { block_push($2); } - | block funcall nl { block_push($2); } - | block assert nl - | block sfail nl { block_push($2); } - ; - -sfail : SHOULD_FAIL expr { $$ = op_sfail($2, NULL); } - | SHOULD_FAIL expr ':' STRING { $$ = op_sfail($2, $4); } - ; - -assert : ASSERT asserti - | ASSERT '(' optnl massert ')' - ; - -massert : asserti nl | massert asserti nl ; - -asserti : check { block_push(op_assert($1)); } - ; - -test : TESTING STRING DIR STRING { - prepare_test(); - } testopt '{' optnl block '}' { - test_done(shouldfail, $2, $4); - shouldfail = 0; - } - ; - -testopt : /* empty */ - | SHOULD_FAIL { shouldfail = 1; } - ; - -%% - -struct keywords { - const char *k_name; - int k_val; -}; - -int -yyerror(const char *fmt, ...) -{ - va_list ap; - char *msg; - - file->errors++; - va_start(ap, fmt); - if (vasprintf(&msg, fmt, ap) == -1) - fatalx("yyerror vasprintf"); - va_end(ap); - logit(LOG_CRIT, "%s:%d: %s", file->name, yylval.lineno, msg); - free(msg); - return 0; -} - -int -kw_cmp(const void *k, const void *e) -{ - return strcmp(k, ((const struct keywords *)e)->k_name); -} - -int -lookup(char *s) -{ - /* This has to be sorted always. */ - static const struct keywords keywords[] = { - {"assert", ASSERT}, - {"const", CONST}, - {"dir", DIR}, - {"include", INCLUDE}, - {"proc", PROC}, - {"repeat", REPEAT}, - {"should-fail", SHOULD_FAIL}, - {"str", STR}, - {"testing", TESTING}, - {"u16", U16}, - {"u32", U32}, - {"u8", U8}, - {"vargs", VARGS}, - }; - const struct keywords *p; - - p = bsearch(s, keywords, sizeof(keywords)/sizeof(keywords[0]), - sizeof(keywords[0]), kw_cmp); - - if (p) - return p->k_val; - else - return SYMBOL; -} - -#define START_EXPAND 1 -#define DONE_EXPAND 2 - -static int expanding; - -int -igetc(void) -{ - int c; - - while (1) { - if (file->ungetpos > 0) - c = file->ungetbuf[--file->ungetpos]; - else - c = getc(file->stream); - - if (c == START_EXPAND) - expanding = 1; - else if (c == DONE_EXPAND) - expanding = 0; - else - break; - } - return c; -} - -int -lgetc(int quotec) -{ - int c, next; - - if (quotec) { - if ((c = igetc()) == EOF) { - yyerror("reached end of file while parsing " - "quoted string"); - if (file == topfile || popfile() == EOF) - return EOF; - return quotec; - } - return c; - } - - while ((c = igetc()) == '\\') { - next = igetc(); - if (next != '\n') { - c = next; - break; - } - yylval.lineno = file->lineno; - file->lineno++; - } - - if (c == EOF) { - /* - * Fake EOL when hit EOF for the first time. This gets line - * count right if last line in included file is syntactically - * invalid and has no newline. - */ - if (file->eof_reached == 0) { - file->eof_reached = 1; - return '\n'; - } - while (c == EOF) { - if (file == topfile || popfile() == EOF) - return EOF; - c = igetc(); - } - } - return c; -} - -void -lungetc(int c) -{ - if (c == EOF) - return; - - if (file->ungetpos >= file->ungetsize) { - void *p = reallocarray(file->ungetbuf, file->ungetsize, 2); - if (p == NULL) - err(1, "lungetc"); - file->ungetbuf = p; - file->ungetsize *= 2; - } - file->ungetbuf[file->ungetpos++] = c; -} - -int -findeol(void) -{ - int c; - - /* Skip to either EOF or the first real EOL. */ - while (1) { - c = lgetc(0); - if (c == '\n') { - file->lineno++; - break; - } - if (c == EOF) - break; - } - return ERROR; -} - - -#if 0 -int my_yylex(void); - -int -yylex(void) -{ - int x; - - switch (x = my_yylex()) { - case ASSERT: puts("assert"); break; - case CONST: puts("const"); break; - case DIR: puts("dir"); break; - case ERROR: puts("error"); break; - case INCLUDE: puts("include"); break; - case PROC: puts("proc"); break; - case REPEAT: puts("repeat"); break; - case STR: puts(":str"); break; - case TESTING: puts("testing"); break; - case U8: puts(":u8"); break; - case U16: puts(":u16"); break; - case U32: puts(":u32"); break; - - case STRING: printf("string \"%s\"\n", yylval.v.str); break; - case SYMBOL: printf("symbol %s\n", yylval.v.str); break; - case NUMBER: printf("number %"PRIu64"\n", yylval.v.num); break; - - default: - printf("character "); - if (x == '\n') - printf("\\n"); - else - printf("%c", x); - printf(" [0x%x]", x); - printf("\n"); - break; - } - - return x; -} - -int -my_yylex(void) -#else -int -yylex(void) -#endif -{ - unsigned char buf[8096]; - unsigned char *p; - int quotec, next, c; - int token; - - p = buf; - while ((c = lgetc(0)) == ' ' || c == '\t' || c == '\f') - ; /* nop */ - - yylval.lineno = file->lineno; - if (c == '#') - while ((c = lgetc(0)) != '\n' && c != EOF) - ; /* nop */ - - switch (c) { - case ':': - return c; - break; - case '\'': - case '\"': - quotec = c; - while (1) { - if ((c = lgetc(quotec)) == EOF) - return 0; - if (c == '\n') { - file->lineno++; - continue; - } else if (c == '\\') { - if ((next = lgetc(quotec)) == EOF) - return 0; - if (next == quotec || next == ' ' || - next == '\t') - c = next; - else if (next == '\n') { - file->lineno++; - continue; - } else - lungetc(next); - } else if (c == quotec) { - *p = '\0'; - break; - } else if (c == '\0') { - yyerror("syntax error"); - return findeol(); - } - - if (p + 1 >= buf + sizeof(buf) - 1) { - yyerror("string too long"); - return findeol(); - } - - *p++ = c; - } - - yylval.v.str = xstrdup(buf); - return STRING; - } - -#define allowed_to_end_number(x) \ - (isspace(x) || x == ')' || x == ',' || x == '/' || x == '}' \ - || x == '=' || x == ':') - - if (c == '-' || isdigit(c)) { - do { - *p++ = c; - if ((size_t)(p-buf) >= sizeof(buf)) { - yyerror("string too long"); - return findeol(); - } - } while ((c = lgetc(0)) != EOF && (isdigit(c) || c == 'x')); - lungetc(c); - if (p == buf + 1 && buf[0] == '-') - goto nodigits; - if (c == EOF || allowed_to_end_number(c)) { - char *ep; - - *p = '\0'; - errno = 0; - yylval.v.num = strtoll(buf, &ep, 0); - if (*ep != '\0' || (errno == ERANGE && - (yylval.v.num == LONG_MAX || - yylval.v.num == LONG_MIN))) { - yyerror("\"%s\" invalid number or out of range", - buf); - return findeol(); - } - - return NUMBER; - } else { -nodigits: - while (p > buf + 1) - lungetc(*--p); - c = *--p; - if (c == '-') - return c; - } - } - -#define allowed_in_symbol(x) \ - (isalnum(x) || (ispunct(x) && x != '(' && x != ')' && \ - x != '{' && x != '}' && \ - x != '!' && x != '=' && \ - x != '#' && x != ',' && \ - x != '.' && x != ':')) - - if (isalnum(c) || c == ':' || c == '_') { - do { - *p++ = c; - if ((size_t)(p-buf) >= sizeof(buf)) { - yyerror("string too long"); - return findeol(); - } - } while ((c = lgetc(0)) != EOF && (allowed_in_symbol(c))); - lungetc(c); - *p = '\0'; - if ((token = lookup(buf)) == SYMBOL) - yylval.v.str = xstrdup(buf); - return token; - } - - if (c == '\n') { - yylval.lineno = file->lineno; - file->lineno++; - } - if (c == EOF) - return 0; - return c; -} - -struct file * -pushfile(const char *name) -{ - struct file *nfile; - - if ((nfile = calloc(1, sizeof(struct file))) == NULL) { - log_warn("calloc"); - return NULL; - } - if ((nfile->name = strdup(name)) == NULL) { - log_warn("strdup"); - free(nfile); - return NULL; - } - if ((nfile->stream = fopen(nfile->name, "r")) == NULL) { - log_warn("%s", nfile->name); - free(nfile->name); - free(nfile); - return NULL; - } - nfile->lineno = TAILQ_EMPTY(&files) ? 1 : 0; - nfile->ungetsize = 16; - nfile->ungetbuf = malloc(nfile->ungetsize); - if (nfile->ungetbuf == NULL) { - log_warn("malloc"); - fclose(nfile->stream); - free(nfile->name); - free(nfile); - return NULL; - } - TAILQ_INSERT_TAIL(&files, nfile, entry); - return nfile; -} - -int -popfile(void) -{ - struct file *prev; - - if ((prev = TAILQ_PREV(file, files, entry)) != NULL) - prev->errors += file->errors; - - TAILQ_REMOVE(&files, file, entry); - fclose(file->stream); - free(file->name); - free(file->ungetbuf); - free(file); - file = prev; - return file ? 0 : EOF; -} - -void -loadfile(const char *path) -{ - int errors; - - file = pushfile(path); - if (file == NULL) - err(1, "pushfile"); - topfile = file; - - yyparse(); - errors = file->errors; - popfile(); - - if (errors) - errx(1, "can't load %s because of errors", path); -} blob - 946f6669b45b632adfcbc3bd1920d2554b3ebc5e (mode 644) blob + /dev/null --- parse.y +++ /dev/null @@ -1,987 +0,0 @@ -/* - * Copyright (c) 2021 Omar Polo - * Copyright (c) 2018 Florian Obser - * Copyright (c) 2004, 2005 Esben Norby - * Copyright (c) 2004 Ryan McBride - * Copyright (c) 2002, 2003, 2004 Henning Brauer - * Copyright (c) 2001 Markus Friedl. All rights reserved. - * Copyright (c) 2001 Daniel Hartmeier. All rights reserved. - * Copyright (c) 2001 Theo de Raadt. All rights reserved. - * - * Permission to use, copy, modify, and distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. - * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - */ - -%{ -#include "compat.h" - -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "log.h" -#include "kamid.h" -#include "table.h" -#include "utils.h" - -TAILQ_HEAD(files, file) files = TAILQ_HEAD_INITIALIZER(files); -static struct file { - TAILQ_ENTRY(file) entry; - FILE *stream; - char *name; - size_t ungetpos; - size_t ungetsize; - u_char *ungetbuf; - int eof_reached; - int lineno; - int errors; -} *file, *topfile; -struct file *pushfile(const char *, int); -int popfile(void); -int check_file_secrecy(int, const char *); -int yyparse(void); -int yylex(void); -int yyerror(const char *, ...) - __attribute__((__format__ (printf, 1, 2))) - __attribute__((__nonnull__ (1))); -int kw_cmp(const void *, const void *); -int lookup(char *); -int igetc(void); -int lgetc(int); -void lungetc(int); -int findeol(void); - -TAILQ_HEAD(symhead, sym) symhead = TAILQ_HEAD_INITIALIZER(symhead); -struct sym { - TAILQ_ENTRY(sym) entry; - int used; - int persist; - char *nam; - char *val; -}; - -int symset(const char *, const char *, int); -char *symget(const char *); - -void clear_config(struct kd_conf *xconf); - -static void add_table(const char *, const char *, const char *); -static struct table *findtable(const char *name); -static void add_cert(const char *, const char *); -static void add_key(const char *, const char *); -static struct kd_listen_conf *listen_new(void); - -static uint32_t counter; -static struct table *table; -static struct kd_listen_conf *listener; -static struct kd_conf *conf; -static int errors; - -typedef struct { - union { - int64_t number; - char *string; - struct table *table; - } v; - int lineno; -} YYSTYPE; - -%} - -%token AUTH -%token CERT -%token ERROR -%token INCLUDE -%token KEY -%token LISTEN -%token NO -%token ON -%token PKI PORT -%token TABLE TLS -%token USERDATA -%token VIRTUAL -%token YES - -%token STRING -%token NUMBER -%type yesno -%type string -%type tableref - -%% - -grammar : /* empty */ - | grammar include '\n' - | grammar '\n' - | grammar table '\n' - | grammar pki '\n' - | grammar listen '\n' - | grammar varset '\n' - | grammar error '\n' { file->errors++; } - ; - -include : INCLUDE STRING { - struct file *nfile; - - if ((nfile = pushfile($2, 0)) == NULL) { - yyerror("failed to include file %s", $2); - free($2); - YYERROR; - } - free($2); - - file = nfile; - lungetc('\n'); - } - ; - -string : string STRING { - if (asprintf(&$$, "%s %s", $1, $2) == -1) { - free($1); - free($2); - yyerror("string: asprintf"); - YYERROR; - } - free($1); - free($2); - } - | STRING - ; - -yesno : YES { $$ = 1; } - | NO { $$ = 0; } - ; - -optnl : '\n' optnl /* zero or more newlines */ - | /*empty*/ - ; - -nl : '\n' optnl /* one or more newlines */ - ; - -arrow : '=' '>' ; - -comma : ',' optnl - ; - -varset : STRING '=' string { - char *s = $1; - if (verbose) - printf("%s = \"%s\"\n", $1, $3); - while (*s++) { - if (isspace((unsigned char)*s)) { - yyerror("macro name cannot contain " - "whitespace"); - free($1); - free($3); - YYERROR; - } - } - if (symset($1, $3, 0) == -1) - fatal("cannot store variable"); - free($1); - free($3); - } - ; - -pki : PKI STRING CERT STRING { add_cert($2, $4); } - | PKI STRING KEY STRING { add_key($2, $4); } - ; - -table_kp : string arrow string optnl { - if (table_add(table, $1, $3) == -1) - yyerror("can't add to table %s", - table->t_name); - free($1); - free($3); - } - ; - -table_kps : table_kp - | table_kp comma table_kps - ; - -stringel : STRING { - if (table_add(table, $1, NULL) == -1) - yyerror("can't add to table %s", - table->t_name); - free($1); - } - ; - -string_list : stringel - | stringel comma string_list - ; - -table_vals : table_kps - | string_list - ; - -table : TABLE STRING STRING { - char *p; - - if ((p = strchr($3, ':')) == NULL) { - yyerror("invalid table %s", $2); - YYERROR; - } - - *p = '\0'; - add_table($2, $3, p+1); - free($2); - free($3); - } - | TABLE STRING { - add_table($2, "static", NULL); - } '{' optnl table_vals '}' { - table = NULL; - } - ; - -tableref : '<' STRING '>' { - struct table *t; - - t = findtable($2); - free($2); - if (t == NULL) - YYERROR; - $$ = t; - } - ; - -listen : LISTEN { listener = listen_new(); } - listen_opts { - if (listener->auth_table == NULL) - yyerror("missing auth table"); - if (!(listener->flags & L_TLS)) - yyerror("can't define a non-tls listener"); - listener = NULL; - } - ; - -listen_opts : listen_opt - | listen_opt listen_opts - ; - -listen_opt : ON STRING PORT NUMBER { - if (*listener->iface != '\0') - yyerror("listen address and port already" - " defined"); - strlcpy(listener->iface, $2, sizeof(listener->iface)); - listener->port = $4; - } - | TLS PKI STRING { - if (*listener->pki != '\0') - yyerror("listen tls pki already defined"); - listener->flags |= L_TLS; - strlcpy(listener->pki, $3, sizeof(listener->pki)); - } - | AUTH tableref { - if (listener->auth_table != NULL) - yyerror("listen auth already defined"); - listener->auth_table = $2; - } - | USERDATA tableref { - if (listener->userdata_table != NULL) - yyerror("userdata table already defined"); - listener->userdata_table = $2; - } - | VIRTUAL tableref { - if (listener->virtual_table != NULL) - yyerror("virtual table already defined"); - listener->virtual_table = $2; - } - ; - -%% - -struct keywords { - const char *k_name; - int k_val; -}; - -int -yyerror(const char *fmt, ...) -{ - va_list ap; - char *msg; - - file->errors++; - va_start(ap, fmt); - if (vasprintf(&msg, fmt, ap) == -1) - fatalx("yyerror vasprintf"); - va_end(ap); - logit(LOG_CRIT, "%s:%d: %s", file->name, yylval.lineno, msg); - free(msg); - return 0; -} - -int -kw_cmp(const void *k, const void *e) -{ - return strcmp(k, ((const struct keywords *)e)->k_name); -} - -int -lookup(char *s) -{ - /* This has to be sorted always. */ - static const struct keywords keywords[] = { - {"auth", AUTH}, - {"cert", CERT}, - {"include", INCLUDE}, - {"key", KEY}, - {"listen", LISTEN}, - {"no", NO}, - {"on", ON}, - {"pki", PKI}, - {"port", PORT}, - {"table", TABLE}, - {"tls", TLS}, - {"userdata", USERDATA}, - {"virtual", VIRTUAL}, - {"yes", YES}, - }; - const struct keywords *p; - - p = bsearch(s, keywords, sizeof(keywords)/sizeof(keywords[0]), - sizeof(keywords[0]), kw_cmp); - - if (p) - return p->k_val; - else - return STRING; -} - -#define START_EXPAND 1 -#define DONE_EXPAND 2 - -static int expanding; - -int -igetc(void) -{ - int c; - - while (1) { - if (file->ungetpos > 0) - c = file->ungetbuf[--file->ungetpos]; - else - c = getc(file->stream); - - if (c == START_EXPAND) - expanding = 1; - else if (c == DONE_EXPAND) - expanding = 0; - else - break; - } - return c; -} - -int -lgetc(int quotec) -{ - int c, next; - - if (quotec) { - if ((c = igetc()) == EOF) { - yyerror("reached end of file while parsing " - "quoted string"); - if (file == topfile || popfile() == EOF) - return EOF; - return quotec; - } - return c; - } - - while ((c = igetc()) == '\\') { - next = igetc(); - if (next != '\n') { - c = next; - break; - } - yylval.lineno = file->lineno; - file->lineno++; - } - - if (c == EOF) { - /* - * Fake EOL when hit EOF for the first time. This gets line - * count right if last line in included file is syntactically - * invalid and has no newline. - */ - if (file->eof_reached == 0) { - file->eof_reached = 1; - return '\n'; - } - while (c == EOF) { - if (file == topfile || popfile() == EOF) - return EOF; - c = igetc(); - } - } - return c; -} - -void -lungetc(int c) -{ - if (c == EOF) - return; - - if (file->ungetpos >= file->ungetsize) { - void *p = reallocarray(file->ungetbuf, file->ungetsize, 2); - if (p == NULL) - err(1, "lungetc"); - file->ungetbuf = p; - file->ungetsize *= 2; - } - file->ungetbuf[file->ungetpos++] = c; -} - -int -findeol(void) -{ - int c; - - /* Skip to either EOF or the first real EOL. */ - while (1) { - c = lgetc(0); - if (c == '\n') { - file->lineno++; - break; - } - if (c == EOF) - break; - } - return ERROR; -} - -#if 0 -int my_yylex(void); - -int -yylex(void) -{ - int x; - - switch (x = my_yylex()) { - case AUTH: - puts("auth"); - break; - case CERT: - puts("cert"); - break; - case ERROR: - puts("error"); - break; - case INCLUDE: - puts("include"); - break; - case KEY: - puts("key"); - break; - case LISTEN: - puts("listen"); - break; - case NO: - puts("no"); - break; - case ON: - puts("on"); - break; - case PKI: - puts("pki"); - break; - case PORT: - puts("port"); - break; - case TABLE: - puts("table"); - break; - case TLS: - puts("tls"); - break; - case YES: - puts("yes"); - break; - case STRING: - printf("string \"%s\"\n", yylval.v.string); - break; - case NUMBER: - printf("number %"PRIi64"\n", yylval.v.number); - default: - printf("character "); - if (x == '\n') - printf("\\n"); - else - printf("%c", x); - printf(" [0x%x]", x); - printf("\n"); - break; - } - - return x; -} - -int -my_yylex(void) -#else -int -yylex(void) -#endif -{ - char buf[8096]; - char *p, *val; - int quotec, next, c; - int token; - -top: - p = buf; - while ((c = lgetc(0)) == ' ' || c == '\t') - ; /* nothing */ - - yylval.lineno = file->lineno; - if (c == '#') - while ((c = lgetc(0)) != '\n' && c != EOF) - ; /* nothing */ - if (c == '$' && !expanding) { - while (1) { - if ((c = lgetc(0)) == EOF) - return 0; - - if (p + 1 >= buf + sizeof(buf) - 1) { - yyerror("string too long"); - return findeol(); - } - if (isalnum(c) || c == '_') { - *p++ = c; - continue; - } - *p = '\0'; - lungetc(c); - break; - } - val = symget(buf); - if (val == NULL) { - yyerror("macro '%s' not defined", buf); - return findeol(); - } - p = val + strlen(val) - 1; - lungetc(DONE_EXPAND); - while (p >= val) { - lungetc((unsigned char)*p); - p--; - } - lungetc(START_EXPAND); - goto top; - } - - switch (c) { - case '\'': - case '"': - quotec = c; - while (1) { - if ((c = lgetc(quotec)) == EOF) - return 0; - if (c == '\n') { - file->lineno++; - continue; - } else if (c == '\\') { - if ((next = lgetc(quotec)) == EOF) - return (0); - if (next == quotec || next == ' ' || - next == '\t') - c = next; - else if (next == '\n') { - file->lineno++; - continue; - } else - lungetc(next); - } else if (c == quotec) { - *p = '\0'; - break; - } else if (c == '\0') { - yyerror("syntax error"); - return findeol(); - } - if (p + 1 >= buf + sizeof(buf) - 1) { - yyerror("string too long"); - return findeol(); - } - *p++ = c; - } - yylval.v.string = strdup(buf); - if (yylval.v.string == NULL) - err(1, "yylex: strdup"); - return STRING; - } - -#define allowed_to_end_number(x) \ - (isspace(x) || x == ')' || x ==',' || x == '/' || x == '}' || x == '=') - - if (c == '-' || isdigit(c)) { - do { - *p++ = c; - if ((size_t)(p-buf) >= sizeof(buf)) { - yyerror("string too long"); - return findeol(); - } - } while ((c = lgetc(0)) != EOF && isdigit(c)); - lungetc(c); - if (p == buf + 1 && buf[0] == '-') - goto nodigits; - if (c == EOF || allowed_to_end_number(c)) { - const char *errstr = NULL; - - *p = '\0'; - yylval.v.number = strtonum(buf, LLONG_MIN, - LLONG_MAX, &errstr); - if (errstr) { - yyerror("\"%s\" invalid number: %s", - buf, errstr); - return findeol(); - } - return NUMBER; - } else { -nodigits: - while (p > buf + 1) - lungetc((unsigned char)*--p); - c = (unsigned char)*--p; - if (c == '-') - return c; - } - } - -#define allowed_in_string(x) \ - (isalnum(x) || (ispunct(x) && x != '(' && x != ')' && \ - x != '{' && x != '}' && \ - x != '!' && x != '=' && x != '#' && \ - x != ',' && x != '>')) - - if (isalnum(c) || c == ':' || c == '_') { - do { - *p++ = c; - if ((size_t)(p-buf) >= sizeof(buf)) { - yyerror("string too long"); - return findeol(); - } - } while ((c = lgetc(0)) != EOF && (allowed_in_string(c))); - lungetc(c); - *p = '\0'; - if ((token = lookup(buf)) == STRING) - if ((yylval.v.string = strdup(buf)) == NULL) - err(1, "yylex: strdup"); - return token; - } - if (c == '\n') { - yylval.lineno = file->lineno; - file->lineno++; - } - if (c == EOF) - return 0; - return c; -} - -int -check_file_secrecy(int fd, const char *fname) -{ - struct stat st; - - if (fstat(fd, &st)) { - log_warn("cannot stat %s", fname); - return -1; - } - if (st.st_uid != 0 && st.st_uid != getuid()) { - log_warnx("%s: owner not root or current user", fname); - return -1; - } - if (st.st_mode & (S_IWGRP | S_IXGRP | S_IRWXO)) { - log_warnx("%s: group writable or world read/writable", fname); - return -1; - } - return 0; -} - -struct file * -pushfile(const char *name, int secret) -{ - struct file *nfile; - - if ((nfile = calloc(1, sizeof(struct file))) == NULL) { - log_warn("calloc"); - return NULL; - } - if ((nfile->name = strdup(name)) == NULL) { - log_warn("strdup"); - free(nfile); - return NULL; - } - if ((nfile->stream = fopen(nfile->name, "r")) == NULL) { - log_warn("%s", nfile->name); - free(nfile->name); - free(nfile); - return NULL; - } else if (secret && - check_file_secrecy(fileno(nfile->stream), nfile->name)) { - fclose(nfile->stream); - free(nfile->name); - free(nfile); - return NULL; - } - nfile->lineno = TAILQ_EMPTY(&files) ? 1 : 0; - nfile->ungetsize = 16; - nfile->ungetbuf = malloc(nfile->ungetsize); - if (nfile->ungetbuf == NULL) { - log_warn("malloc"); - fclose(nfile->stream); - free(nfile->name); - free(nfile); - return NULL; - } - TAILQ_INSERT_TAIL(&files, nfile, entry); - return nfile; -} - -int -popfile(void) -{ - struct file *prev; - - if ((prev = TAILQ_PREV(file, files, entry)) != NULL) - prev->errors += file->errors; - - TAILQ_REMOVE(&files, file, entry); - fclose(file->stream); - free(file->name); - free(file->ungetbuf); - free(file); - file = prev; - return file ? 0 : EOF; -} - -struct kd_conf * -parse_config(const char *filename) -{ - struct sym *sym, *next; - - counter = 0; - conf = config_new_empty(); - - file = pushfile(filename, 0); - if (file == NULL) { - free(conf); - return NULL; - } - topfile = file; - - yyparse(); - errors = file->errors; - popfile(); - - /* Free macros and check which have not been used. */ - TAILQ_FOREACH_SAFE(sym, &symhead, entry, next) { - if (verbose && !sym->used) - fprintf(stderr, "warning: macro '%s' not used\n", - sym->nam); - if (!sym->persist) { - free(sym->nam); - free(sym->val); - TAILQ_REMOVE(&symhead, sym, entry); - free(sym); - } - } - - if (errors) { - clear_config(conf); - return NULL; - } - - return conf; -} - -int -symset(const char *nam, const char *val, int persist) -{ - struct sym *sym; - - TAILQ_FOREACH(sym, &symhead, entry) { - if (strcmp(nam, sym->nam) == 0) - break; - } - - if (sym != NULL) { - if (sym->persist == 1) - return 0; - else { - free(sym->nam); - free(sym->val); - TAILQ_REMOVE(&symhead, sym, entry); - free(sym); - } - } - if ((sym = calloc(1, sizeof(*sym))) == NULL) - return -1; - - sym->nam = strdup(nam); - if (sym->nam == NULL) { - free(sym); - return -1; - } - sym->val = strdup(val); - if (sym->val == NULL) { - free(sym->nam); - free(sym); - return -1; - } - sym->used = 0; - sym->persist = persist; - TAILQ_INSERT_TAIL(&symhead, sym, entry); - return 0; -} - -int -cmdline_symset(char *s) -{ - char *sym, *val; - int ret; - - if ((val = strrchr(s, '=')) == NULL) - return -1; - sym = strndup(s, val - s); - if (sym == NULL) - errx(1, "%s: strndup", __func__); - ret = symset(sym, val + 1, 1); - free(sym); - - return ret; -} - -char * -symget(const char *nam) -{ - struct sym *sym; - - TAILQ_FOREACH(sym, &symhead, entry) { - if (strcmp(nam, sym->nam) == 0) { - sym->used = 1; - return sym->val; - } - } - return NULL; -} - -void -clear_config(struct kd_conf *xconf) -{ - /* free stuff? */ - - free(xconf); -} - -static void -add_table(const char *name, const char *type, const char *path) -{ - if (table_open(conf, name, type, path) == -1) - yyerror("can't initialize table %s", name); - table = STAILQ_FIRST(&conf->table_head)->table; -} - -static struct table * -findtable(const char *name) -{ - struct kd_tables_conf *i; - - STAILQ_FOREACH(i, &conf->table_head, entry) { - if (!strcmp(i->table->t_name, name)) - return i->table; - } - - yyerror("unknown table %s", name); - return NULL; -} - -static void -add_cert(const char *name, const char *path) -{ - struct kd_pki_conf *pki; - - STAILQ_FOREACH(pki, &conf->pki_head, entry) { - if (strcmp(name, pki->name) != 0) - continue; - - if (pki->cert != NULL) { - yyerror("duplicate `pki %s cert'", name); - return; - } - - goto set; - } - - pki = xcalloc(1, sizeof(*pki)); - strlcpy(pki->name, name, sizeof(pki->name)); - STAILQ_INSERT_HEAD(&conf->pki_head, pki, entry); - -set: - if ((pki->cert = tls_load_file(path, &pki->certlen, NULL)) == NULL) - fatal(NULL); -} - -static void -add_key(const char *name, const char *path) -{ - struct kd_pki_conf *pki; - - STAILQ_FOREACH(pki, &conf->pki_head, entry) { - if (strcmp(name, pki->name) != 0) - continue; - - if (pki->key != NULL) { - yyerror("duplicate `pki %s key'", name); - return; - } - - goto set; - } - - pki = xcalloc(1, sizeof(*pki)); - strlcpy(pki->name, name, sizeof(pki->name)); - STAILQ_INSERT_HEAD(&conf->pki_head, pki, entry); - -set: - if ((pki->key = tls_load_file(path, &pki->keylen, NULL)) == NULL) - fatal(NULL); -} - -static struct kd_listen_conf * -listen_new(void) -{ - struct kd_listen_conf *l; - - l = xcalloc(1, sizeof(*l)); - l->id = counter++; - l->fd = -1; - - STAILQ_INSERT_HEAD(&conf->listen_head, l, entry); - return l; -} blob - 5f149ef222c04f85943c9bcd2d100590e8f68d31 (mode 644) blob + /dev/null --- regress/consts.9ps +++ /dev/null @@ -1,46 +0,0 @@ -const ( - np2000 = "9P2000" - msize = 4194304:u32 # 4*1024*1024 - notag = -1:u16 - nofid = -1:u32 - - QTDIR = 0x80 - # ... - QTFILE = 0x0 - - OREAD = 0 - OWRITE = 1 - ORDWR = 2 - OEXEC = 3 - OTRUNC = 16 - ORCLOSE = 64 - - Tversion = 100:u8 - Rversion = 101:u8 - Tauth = 102:u8 - Rauth = 103:u8 - Tattach = 104:u8 - Rattach = 105:u8 - Terror = 106:u8 # illegal - Rerror = 107:u8 - Tflush = 108:u8 - Rflush = 109:u8 - Twalk = 110:u8 - Rwalk = 111:u8 - Topen = 112:u8 - Ropen = 113:u8 - Tcreate = 114:u8 - Rcreate = 115:u8 - Tread = 116:u8 - Rread = 117:u8 - Twrite = 118:u8 - Rwrite = 119:u8 - Tclunk = 120:u8 - Rclunk = 121:u8 - Tremove = 122:u8 - Rremove = 123:u8 - Tstat = 124:u8 - Rstat = 125:u8 - Twstat = 126:u8 - Rwstat = 127:u8 -) blob - /dev/null blob + c0aa58ac4c6f1883a9f4d63cc216253cbf5ff1a0 (mode 644) --- /dev/null +++ regress/Makefile @@ -0,0 +1,9 @@ +HAVE_LISP ?= No + +SUBDIR = ninepscript + +.if ${HAVE_LISP:L} == yes +SUBDIR += lisp +.endif + +.include blob - aca3357c55180745ed17f5a5fb9eb5ef56bd64e0 (mode 644) blob + /dev/null --- regress/io-suite.9ps +++ /dev/null @@ -1,41 +0,0 @@ -include "lib.9ps" - -testing "open + clunk works" dir "./root" { - mount(0, "/") - walk(0, 1, "dir", "subdir", "file") - expect(Rwalk) - - open(1, OREAD) - m = recv() - assert m.type == Ropen - - clunk(1) - m = recv() - assert m.type == Rclunk -} - -testing "can open directories" dir "./root" { - mount(0, "/") - walk(0, 1, "dir", "subdir") - expect(Rwalk) - - open(1, OREAD) - m = recv() - assert m.type == Ropen - - clunk(1) - m = recv() - assert m.type == Rclunk -} - -testing "can't open directories for writing" dir "./root" { - mount(0, "/") - walk(0, 1, "dir") - expect(Rwalk) - - open(1, OWRITE) - expect-error() - - open(1, ORDWR) - expect-error() -} blob - /dev/null blob + d3581899e6d17bbfede4fae297d8f51b738879db (mode 644) --- /dev/null +++ regress/Makefile.inc @@ -0,0 +1,2 @@ +.include "../kamid-version.mk" +.include "../Makefile.inc" blob - c5b25c870e154f06143d64be5bab83c66694c253 (mode 644) blob + /dev/null --- regress/lib.9ps +++ /dev/null @@ -1,58 +0,0 @@ -include "consts.9ps" - -# 9p protocol - -proc version(msize, version) { - send(Tversion, notag, msize:u32, version:str) -} - -proc attach(fid, afid, uname, aname) { - send(Tattach, iota(), fid:u32, afid:u32, uname:str, aname:str) -} - -proc walk(fid, newfid, ...) { - send(Twalk, iota(), fid:u32, newfid:u32, vargs:u16, ...) -} - -proc open(fid, mode) { - send(Topen, iota(), fid:u32, mode:u8) -} - -proc clunk(fid) { - send(Tclunk, iota(), fid:u32) -} - - - -# useful functions - -proc mount(fid, path) { - version(msize, np2000) - - m = recv() - assert ( - m.type == Rversion - m.tag == notag - m.msize <= msize - # m.version == version - ) - - attach(fid, nofid, "op", path) - - m = recv() - assert ( - m.type == Rattach - m.qid.type == QTDIR - ) -} - -proc expect(t) { - m = recv() - assert m.type == t -} - -proc expect-error() { - m = recv() - assert m.type == Rerror - debug("got expected error", m) -} blob - 598ecedc0ac2742c734e6bcf87aeb4388ea95b15 (mode 755) blob + /dev/null --- regress/lisp/9p-test/run-tests.sh +++ /dev/null @@ -1,8 +0,0 @@ -#!/bin/sh - -#export REGRESS_CERT="$HOME/lisp/kamid.cert" -#export REGRESS_KEY="$HOME/lisp/kamid.key" -#export REGRESS_HOSTNAME="localhost" -#export REGRESS_PORT=10564 - -sbcl --eval "(require 'asdf)" --eval "(push \"$(pwd)/\" asdf:*central-registry*)" --eval "(asdf:make \"9p-test\")" --eval "(all-tests:run-all-tests)" blob - /dev/null blob + e14bb17cce917914d9bb94daa608f41e808a31c0 (mode 644) --- /dev/null +++ regress/lisp/Makefile @@ -0,0 +1,7 @@ +REGRESS_TARGETS= lisp +NOOBJ=Yes + +lisp: + SUDO=${SUDO} ./run.sh + +.include blob - /dev/null blob + a2038af9311ed5befcd1c0324c6442a59b5976f2 (mode 755) --- /dev/null +++ regress/lisp/run.sh @@ -0,0 +1,105 @@ +#!/bin/sh +# +# Run external tests, requires a common lisp interpreter (sbcl by +# default) to be available. + +SUDO=${SUDO:-doas} +USER=${USER?:user not set} +SBCL=${SBCL:-sbcl} + +set -e + +if ! which kamid 2>/dev/null >/dev/null; then + echo "can't find kamid in PATH" >&2 + exit 1 +fi + +# gencerts name +gencerts() { + echo "generating keypairs for $1..." + openssl req -x509 \ + -newkey rsa:4096 \ + -out "$1.pem" \ + -keyout "$1.key" \ + -days 365 \ + -nodes \ + -subj "/CN=$1" +} + +# h cert +h() { + printf "SHA256:" + openssl x509 -in "$1" -noout -fingerprint -sha256 | \ + sed -e 's/^.*=//' -e 's/://g' | \ + tr A-Z a-z +} + +if [ ! -f client.pem -o ! -f client.key ]; then + gencerts client +fi + +if [ ! -f kamid.pem -o ! -f kamid.key ]; then + gencerts kamid +fi + +kamid_hash="$(h client.pem)" +tmpdir="$(mktemp -d -t kamid-regress.XXXXXXXXXX)" +testroot="$tmpdir/root" + +cp -R ../root/ "$tmpdir" + +cat > regress.conf < "flan" } +table virt { "flan" => "$USER" } +table data { "flan" => "$testroot" } + +listen on localhost port 1337 tls pki localhost \ + auth \ + virtual \ + userdata +EOF + +logfile="$tmpdir/$(date +%Y-%m-%d-%H-%M).log" + +export REGRESS_CERT="$PWD/client.pem" +export REGRESS_KEY="$PWD/client.key" +export REGRESS_HOSTNAME=localhost +export REGRESS_PORT=1337 +export REGRESS_ROOT="$testroot" + +echo "REGRESS_CERT: $REGRESS_CERT" +echo "REGRESS_KEY: $REGRESS_KEY" +echo "REGRESS_HOSTNAME: $REGRESS_HOSTNAME" +echo "REGRESS_PORT: $REGRESS_PORT" +echo "REGRESS_ROOT: $REGRESS_ROOT" +echo + +echo "logging on $logfile" +${SUDO} "$(which kamid)" -d -vvv -f regress.conf > "$logfile" 2>&1 & + +ret=0 + +set +e +cd 9p-test/ && \ + ${SBCL} --noinform \ + --disable-debugger \ + --eval "(require 'asdf)" \ + --eval "(push \"$(pwd)/\" asdf:*central-registry*)" \ + --eval "(asdf:make \"9p-test\")" \ + --eval "(all-tests:run-all-tests)" + +ret=$? +if [ $ret -ne 0 ]; then + echo + echo "Test failed, leaving root at $testroot" + sleep 1 +else + rm -rf "$testroot" +fi + +${SUDO} pkill kamid + +exit $ret blob - /dev/null blob + ddd9794ed92d5c469e202ee924479a18e6c352b1 (mode 644) --- /dev/null +++ regress/ninepscript/Makefile @@ -0,0 +1,7 @@ +REGRESS_TARGETS=suite +NOOBJ=Yes + +suite: + ${SUDO} ${PREFIX}/bin/ninepscript *-suite.9ps + +.include blob - /dev/null blob + 5f149ef222c04f85943c9bcd2d100590e8f68d31 (mode 644) --- /dev/null +++ regress/ninepscript/consts.9ps @@ -0,0 +1,46 @@ +const ( + np2000 = "9P2000" + msize = 4194304:u32 # 4*1024*1024 + notag = -1:u16 + nofid = -1:u32 + + QTDIR = 0x80 + # ... + QTFILE = 0x0 + + OREAD = 0 + OWRITE = 1 + ORDWR = 2 + OEXEC = 3 + OTRUNC = 16 + ORCLOSE = 64 + + Tversion = 100:u8 + Rversion = 101:u8 + Tauth = 102:u8 + Rauth = 103:u8 + Tattach = 104:u8 + Rattach = 105:u8 + Terror = 106:u8 # illegal + Rerror = 107:u8 + Tflush = 108:u8 + Rflush = 109:u8 + Twalk = 110:u8 + Rwalk = 111:u8 + Topen = 112:u8 + Ropen = 113:u8 + Tcreate = 114:u8 + Rcreate = 115:u8 + Tread = 116:u8 + Rread = 117:u8 + Twrite = 118:u8 + Rwrite = 119:u8 + Tclunk = 120:u8 + Rclunk = 121:u8 + Tremove = 122:u8 + Rremove = 123:u8 + Tstat = 124:u8 + Rstat = 125:u8 + Twstat = 126:u8 + Rwstat = 127:u8 +) blob - /dev/null blob + dd15078e8b5ab344f1694f8e354a235160b15ee7 (mode 644) --- /dev/null +++ regress/ninepscript/io-suite.9ps @@ -0,0 +1,41 @@ +include "lib.9ps" + +testing "open + clunk works" dir "./../root" { + mount(0, "/") + walk(0, 1, "dir", "subdir", "file") + expect(Rwalk) + + open(1, OREAD) + m = recv() + assert m.type == Ropen + + clunk(1) + m = recv() + assert m.type == Rclunk +} + +testing "can open directories" dir "./../root" { + mount(0, "/") + walk(0, 1, "dir", "subdir") + expect(Rwalk) + + open(1, OREAD) + m = recv() + assert m.type == Ropen + + clunk(1) + m = recv() + assert m.type == Rclunk +} + +testing "can't open directories for writing" dir "./../root" { + mount(0, "/") + walk(0, 1, "dir") + expect(Rwalk) + + open(1, OWRITE) + expect-error() + + open(1, ORDWR) + expect-error() +} blob - /dev/null blob + c5b25c870e154f06143d64be5bab83c66694c253 (mode 644) --- /dev/null +++ regress/ninepscript/lib.9ps @@ -0,0 +1,58 @@ +include "consts.9ps" + +# 9p protocol + +proc version(msize, version) { + send(Tversion, notag, msize:u32, version:str) +} + +proc attach(fid, afid, uname, aname) { + send(Tattach, iota(), fid:u32, afid:u32, uname:str, aname:str) +} + +proc walk(fid, newfid, ...) { + send(Twalk, iota(), fid:u32, newfid:u32, vargs:u16, ...) +} + +proc open(fid, mode) { + send(Topen, iota(), fid:u32, mode:u8) +} + +proc clunk(fid) { + send(Tclunk, iota(), fid:u32) +} + + + +# useful functions + +proc mount(fid, path) { + version(msize, np2000) + + m = recv() + assert ( + m.type == Rversion + m.tag == notag + m.msize <= msize + # m.version == version + ) + + attach(fid, nofid, "op", path) + + m = recv() + assert ( + m.type == Rattach + m.qid.type == QTDIR + ) +} + +proc expect(t) { + m = recv() + assert m.type == t +} + +proc expect-error() { + m = recv() + assert m.type == Rerror + debug("got expected error", m) +} blob - /dev/null blob + ab1b801eb99c1408f60afdd75fc9e706cf425010 (mode 644) --- /dev/null +++ regress/ninepscript/misc-suite.9ps @@ -0,0 +1,61 @@ +include "lib.9ps" + +testing "if version works" dir "./../root" { + send(Tversion, notag, msize, np2000) + m = recv() + assert m.type == Rversion +} + +testing "fails when sending a R-message" dir "./../root" { + send(Rversion, notag, msize, np2000) + should-fail recv() : "the connection should have been closed" +} + +testing "multiple attach" dir "./../root" { + version(msize, np2000) + + m = recv() + assert ( + m.type == Rversion + m.tag == notag + m.msize <= msize + ) + + fid1 = 0 + fid2 = 1 + + # attach the first fid + attach(fid1, nofid, "op", "/") + m = recv() + assert ( + m.type == Rattach + m.qid.type == QTDIR + ) + + # attach the second fid + attach(fid2, nofid, "op", "/") + m = recv() + assert ( + m.type == Rattach + m.qid.type == QTDIR + ) +} + +testing "don't close used qids" dir "./../root" { + mount(0, "/") + + walk(0, 2, "dir") + expect(Rwalk) + + clunk(0) + expect(Rclunk) + + walk(2, 3, "a-file") + expect(Rwalk) + + clunk(2) + expect(Rclunk) + + open(3, OREAD) + expect(Ropen) +} blob - /dev/null blob + feb818b939bbabc724d41d26f1fa77479299fe14 (mode 644) --- /dev/null +++ regress/ninepscript/sample.9ps @@ -0,0 +1,54 @@ +# default protocol version +const npversion = "9P2000" + +# some constants +const ( + one = 1:u8 + two = 2 + + notag = -1:u8 + + n = 4 +) + +proc unreachable(qid) { # and useless + type = qid.type + assert ( + type == qid.type + ) +} + +proc test(x) { + skip() +} + +proc myrealprint(...) { + print(...) +} + +proc myprint(...) { + myrealprint(...) +} + +testing "skip called in proc" dir "./root" { + foo = 5:u8 + myprint("hello", "foo is", foo) + test(3:u32) + assert 1 == 0 + + assert ( + 5 == 7 + 7 == 9 + 8 == 0 + ) +} + +testing "casts" dir "./root" { + foo = 300:u8 +} + +proc empty() { +} + +testing "foobar" dir "./root" { +} blob - /dev/null blob + 8a0a8ca13b19b78d916dc6137b301ea2a0dfa2a2 (mode 644) --- /dev/null +++ regress/ninepscript/walk-suite.9ps @@ -0,0 +1,74 @@ +include "lib.9ps" + +# TODO: add a test that tries to do a walk after opening a fid for i/o. + +testing "walk to a directory" dir "./../root" { + mount(0, "/") + walk(0, 1, "dir", "subdir") + + m = recv() + assert ( + m.type == Rwalk + m.nwqid == 2 + m.wqid.0.type == QTDIR + m.wqid.1.type == QTDIR + ) +} + +testing "walk to a file" dir "./../root" { + mount(0, "/") + walk(0, 1, "dir", "subdir", "file") + + m = recv() + assert ( + m.type == Rwalk + m.nwqid == 3 + m.wqid.0.type == QTDIR + m.wqid.1.type == QTDIR + m.wqid.2.type == QTFILE + ) +} + +testing "can't walk from a file" dir "./../root" { + mount(0, "/") + + walk(0, 1, "dir", "a-file") + expect(Rwalk) + + walk(1, 2, "subdir", "file") + expect-error() +} + +testing "walk with invalid fid" dir "./../root" { + mount(0, "/") + walk(1, 2) + expect-error() +} + +testing "walk with empty string" dir "./../root" { + mount(0, "/") + walk(0, 1, "") + expect-error() +} + +testing "walk to a non-existant file" dir "./../root" { + mount(0, "/") + walk(0, 1, "non-exists") + expect-error() +} + +testing "walk with an invalid component" dir "./../root" { + mount(0, "/") + walk(0, 1, "/non-exists") + expect-error() +} + +testing "zero-path walk don't reply with a qid" dir "./../root" { + mount(0, "/") + walk(0, 1) + m = recv() + assert ( + m.type == Rwalk + m.nwqid == 0 + ) +} blob - 8c80fde28a266bf7c9947cfd555b284fd9d5f801 (mode 644) blob + /dev/null --- regress/misc-suite.9ps +++ /dev/null @@ -1,61 +0,0 @@ -include "lib.9ps" - -testing "if version works" dir "./root" { - send(Tversion, notag, msize, np2000) - m = recv() - assert m.type == Rversion -} - -testing "fails when sending a R-message" dir "./root" { - send(Rversion, notag, msize, np2000) - should-fail recv() : "the connection should have been closed" -} - -testing "multiple attach" dir "./root" { - version(msize, np2000) - - m = recv() - assert ( - m.type == Rversion - m.tag == notag - m.msize <= msize - ) - - fid1 = 0 - fid2 = 1 - - # attach the first fid - attach(fid1, nofid, "op", "/") - m = recv() - assert ( - m.type == Rattach - m.qid.type == QTDIR - ) - - # attach the second fid - attach(fid2, nofid, "op", "/") - m = recv() - assert ( - m.type == Rattach - m.qid.type == QTDIR - ) -} - -testing "don't close used qids" dir "./root" { - mount(0, "/") - - walk(0, 2, "dir") - expect(Rwalk) - - clunk(0) - expect(Rclunk) - - walk(2, 3, "a-file") - expect(Rwalk) - - clunk(2) - expect(Rclunk) - - open(3, OREAD) - expect(Ropen) -} blob - /dev/null blob + e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 (mode 644) blob - feb818b939bbabc724d41d26f1fa77479299fe14 (mode 644) blob + /dev/null --- regress/sample.9ps +++ /dev/null @@ -1,54 +0,0 @@ -# default protocol version -const npversion = "9P2000" - -# some constants -const ( - one = 1:u8 - two = 2 - - notag = -1:u8 - - n = 4 -) - -proc unreachable(qid) { # and useless - type = qid.type - assert ( - type == qid.type - ) -} - -proc test(x) { - skip() -} - -proc myrealprint(...) { - print(...) -} - -proc myprint(...) { - myrealprint(...) -} - -testing "skip called in proc" dir "./root" { - foo = 5:u8 - myprint("hello", "foo is", foo) - test(3:u32) - assert 1 == 0 - - assert ( - 5 == 7 - 7 == 9 - 8 == 0 - ) -} - -testing "casts" dir "./root" { - foo = 300:u8 -} - -proc empty() { -} - -testing "foobar" dir "./root" { -} blob - f35b4b10b72ba797360354489331a55ead1c9bb0 (mode 644) blob + /dev/null --- regress/walk-suite.9ps +++ /dev/null @@ -1,74 +0,0 @@ -include "lib.9ps" - -# TODO: add a test that tries to do a walk after opening a fid for i/o. - -testing "walk to a directory" dir "./root" { - mount(0, "/") - walk(0, 1, "dir", "subdir") - - m = recv() - assert ( - m.type == Rwalk - m.nwqid == 2 - m.wqid.0.type == QTDIR - m.wqid.1.type == QTDIR - ) -} - -testing "walk to a file" dir "./root" { - mount(0, "/") - walk(0, 1, "dir", "subdir", "file") - - m = recv() - assert ( - m.type == Rwalk - m.nwqid == 3 - m.wqid.0.type == QTDIR - m.wqid.1.type == QTDIR - m.wqid.2.type == QTFILE - ) -} - -testing "can't walk from a file" dir "./root" { - mount(0, "/") - - walk(0, 1, "dir", "a-file") - expect(Rwalk) - - walk(1, 2, "subdir", "file") - expect-error() -} - -testing "walk with invalid fid" dir "./root" { - mount(0, "/") - walk(1, 2) - expect-error() -} - -testing "walk with empty string" dir "./root" { - mount(0, "/") - walk(0, 1, "") - expect-error() -} - -testing "walk to a non-existant file" dir "./root" { - mount(0, "/") - walk(0, 1, "non-exists") - expect-error() -} - -testing "walk with an invalid component" dir "./root" { - mount(0, "/") - walk(0, 1, "/non-exists") - expect-error() -} - -testing "zero-path walk don't reply with a qid" dir "./root" { - mount(0, "/") - walk(0, 1) - m = recv() - assert ( - m.type == Rwalk - m.nwqid == 0 - ) -} blob - baec1e893829b2d9016a0e589066c0d5a60725dc (mode 755) blob + /dev/null --- run-extra-tests.sh +++ /dev/null @@ -1,100 +0,0 @@ -#!/bin/sh -# -# Run external tests, requires a common lisp interpreter (sbcl by -# default) to be available. - -DOAS=${DOAS:-doas} -USER=${USER?:user not set} -SBCL=${SBCL:-sbcl} - -set -e - -# gencerts name -gencerts() { - echo "generating keypairs for $1..." - openssl req -x509 \ - -newkey rsa:4096 \ - -out "$1.pem" \ - -keyout "$1.key" \ - -days 365 \ - -nodes \ - -subj "/CN=$1" -} - -# h cert -h() { - printf "SHA256:" - openssl x509 -in "$1" -noout -fingerprint -sha256 | \ - sed -e 's/^.*=//' -e 's/://g' | \ - tr A-Z a-z -} - -if [ ! -f client.pem -o ! -f client.key ]; then - gencerts client -fi - -if [ ! -f kamid.pem -o ! -f kamid.key ]; then - gencerts kamid -fi - -kamid_hash="$(h client.pem)" -tmpdir="$(mktemp -d -t kamid-regress.XXXXXXXXXX)" -testroot="$tmpdir/root" - -cp -R regress/root/ "$tmpdir" - -cat > regress.conf < "flan" } -table virt { "flan" => "$USER" } -table data { "flan" => "$testroot" } - -listen on localhost port 1337 tls pki localhost \ - auth \ - virtual \ - userdata -EOF - -logfile="$tmpdir/$(date +%Y-%m-%d-%H-%M).log" - -export REGRESS_CERT="$PWD/client.pem" -export REGRESS_KEY="$PWD/client.key" -export REGRESS_HOSTNAME=localhost -export REGRESS_PORT=1337 -export REGRESS_ROOT="$testroot" - -echo "REGRESS_CERT: $REGRESS_CERT" -echo "REGRESS_KEY: $REGRESS_KEY" -echo "REGRESS_HOSTNAME: $REGRESS_HOSTNAME" -echo "REGRESS_PORT: $REGRESS_PORT" -echo "REGRESS_ROOT: $REGRESS_ROOT" -echo - -echo "logging on $logfile" -${DOAS} ./kamid -d -vvv -f regress.conf > "$logfile" 2>&1 & - -ret=0 - -set +e -cd regress/lisp/9p-test/ && \ - ${SBCL} --noinform \ - --disable-debugger \ - --eval "(require 'asdf)" \ - --eval "(push \"$(pwd)/\" asdf:*central-registry*)" \ - --eval "(asdf:make \"9p-test\")" \ - --eval "(all-tests:run-all-tests)" - -ret=$? -if [ $ret -ne 0 ]; then - echo - echo "Test failed, leaving root at $testroot" - sleep 1 -else - rm -rf "$testroot" -fi - -${DOAS} pkill kamid - -exit $ret blob - 391e5ae2869fe2556407765b51278e546d8ffdb1 (mode 755) blob + /dev/null --- run-tests.sh +++ /dev/null @@ -1,9 +0,0 @@ -#!/bin/sh -# -# Test runner for kamid - -set -e - -cd regress - -./../ninepscript "$@" *-suite.9ps blob - cf1ac0e9438622950da2943dd9476e842bc8531c (mode 644) blob + /dev/null --- sandbox.c +++ /dev/null @@ -1,64 +0,0 @@ -/* - * Copyright (c) 2021 Omar Polo - * - * Permission to use, copy, modify, and distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. - * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - */ - -#include "log.h" -#include "sandbox.h" - -#ifdef __OpenBSD__ - -#include - -void -sandbox_main(void) -{ - return; -} - -void -sandbox_listener(void) -{ - return; -} - -void -sandbox_client(void) -{ - return; -} - -#else -#warning "No sandbox available for this OS" - -void -sandbox_main(void) -{ - log_warnx("No sandbox available for this os"); - return; -} - -void -sandbox_listener(void) -{ - return; -} - -void -sandbox_client(void) -{ - return; -} - -#endif blob - c964b983ea10e3af850adc627f269bd1051a2619 (mode 644) blob + /dev/null --- sandbox.h +++ /dev/null @@ -1,24 +0,0 @@ -/* - * Copyright (c) 2021 Omar Polo - * - * Permission to use, copy, modify, and distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. - * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - */ - -#ifndef SANDBOX_H -#define SANDBOX_H - -void sandbox_main(void); -void sandbox_listener(void); -void sandbox_client(void); - -#endif blob - 8b480c2ff8bd568c4adadc410eed846e8f7696d2 (mode 644) blob + /dev/null --- script.c +++ /dev/null @@ -1,1768 +0,0 @@ -/* - * Copyright (c) 2021 Omar Polo - * - * Permission to use, copy, modify, and distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. - * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - */ - -#include "compat.h" - -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "client.h" -#include "log.h" -#include "script.h" -#include "utils.h" - -#define DEBUG 0 - -#ifndef INFTIM -#define INFTIM -1 -#endif - -static const char *argv0; - -static uint8_t *lastmsg; - -static struct imsgbuf ibuf; -static int ibuf_inuse; -static int child_out = -1; - -static struct procs procs = TAILQ_HEAD_INITIALIZER(procs); -static struct tests tests = TAILQ_HEAD_INITIALIZER(tests); - -static int ntests; - -static struct opstacks blocks = TAILQ_HEAD_INITIALIZER(blocks); -static struct opstacks args = TAILQ_HEAD_INITIALIZER(args); - -#define STACK_HEIGHT 64 -static struct value vstack[STACK_HEIGHT]; -static int stackh; - -static struct envs envs = TAILQ_HEAD_INITIALIZER(envs); - -static struct value v_false = {.type = V_NUM, .v = {.num = 0}}; -static struct value v_true = {.type = V_NUM, .v = {.num = 1}}; - -static uint8_t lasttag; - -static int debug; -static int syntaxcheck; - -static const char *filler; - -static inline void -before_printing(void) -{ - if (filler != NULL) { - printf("%s", filler); - filler = NULL; - } -} - -static inline void -check_for_output(void) -{ - static char buf[BUFSIZ]; - struct pollfd pfd; - ssize_t r; - - pfd.fd = child_out; - pfd.events = POLLIN; - if (poll(&pfd, 1, 0) == -1) - fatal("poll"); - - if (!(pfd.revents & POLLIN)) - return; - - for (;;) { - if ((r = read(child_out, buf, sizeof(buf))) == -1) { - if (errno == EAGAIN) - break; - fatal("read"); - } - if (r == 0) - break; - before_printing(); - fwrite(buf, 1, r, stdout); - } -} - -static inline void -peekn(int depth, struct value *v) -{ - if (depth > stackh) - errx(1, "can't peek the stack at %d: underflow", - depth); - memcpy(v, &vstack[stackh - depth], sizeof(*v)); - -#if DEBUG - printf("peeking(%d) ", depth); pp_val(v); printf("\n"); -#endif -} - -static inline void -popv(struct value *v) -{ - if (stackh == 0) - errx(1, "can't pop the stack: underflow"); - memcpy(v, &vstack[--stackh], sizeof(*v)); - -#if DEBUG - printf("popping "); pp_val(v); printf("\n"); -#endif -} - -static inline void -popvn(int n) -{ - struct value v; - - while (n-- > 0) - popv(&v); -} - -static inline void -pushv(struct value *v) -{ - if (stackh == STACK_HEIGHT) - errx(1, "can't push the stack: overflow"); - -#if DEBUG - printf("pushing "); pp_val(v); printf("\n"); -#endif - - memcpy(&vstack[stackh++], v, sizeof(*v)); -} - -static inline void -pushbool(int n) -{ - pushv(n ? &v_true : &v_false); -} - -static inline void -pushnum(int64_t n) -{ - struct value v; - - v.type = V_NUM; - v.v.num = n; - pushv(&v); -} - -static inline struct opstack * -pushstack(struct opstacks *stack) -{ - struct opstack *ops; - - ops = xcalloc(1, sizeof(*ops)); - TAILQ_INSERT_HEAD(stack, ops, entry); - return ops; -} - -static inline struct opstack * -peek(struct opstacks *stack) -{ - if (TAILQ_EMPTY(stack)) - errx(1, "%s: args underflow", __func__); - - return TAILQ_FIRST(stack); -} - -static inline struct op * -finalize(struct opstacks *stack, int *argc) -{ - struct opstack *ops; - struct op *op; - - if (TAILQ_EMPTY(stack)) - errx(1, "%s: args underflow", __func__); - - ops = peek(stack); - TAILQ_REMOVE(&args, ops, entry); - op = ops->base.next; - - if (argc != NULL) - *argc = ops->counter; - - free(ops); - return op; -} - -static inline void -push(struct opstacks *stack, struct op *op) -{ - struct opstack *ops; - - ops = peek(stack); - if (ops->last == NULL) { - ops->base.next = op; - ops->last = op; - } else { - ops->last->next = op; - ops->last = op; - } - - ops->counter++; -} - -static inline void -pushenv(void) -{ - struct env *e; - - e = xcalloc(1, sizeof(*e)); - TAILQ_INSERT_HEAD(&envs, e, entry); -} - -static inline struct env * -currentenv(void) -{ - assert(!TAILQ_EMPTY(&envs)); - return TAILQ_FIRST(&envs); -} - -static void -popenv(void) -{ - struct env *e; - struct binding *b, *tb; - - e = currentenv(); - TAILQ_REMOVE(&envs, e, entry); - - TAILQ_FOREACH_SAFE(b, &e->bindings, entry, tb) - free(b); - - free(e); -} - -static inline int -setvar(char *sym, struct op *op) -{ - struct binding *b; - struct env *e; - int ret, height; - - height = stackh; - if ((ret = eval(op)) != EVAL_OK) - return ret; - - if (stackh != height + 1) { - before_printing(); - printf("trying to assign to `%s' a void value: ", sym); - pp_op(op); - printf("\n"); - return EVAL_ERR; - } - - b = xcalloc(1, sizeof(*b)); - b->name = sym; - popv(&b->val); - - e = TAILQ_FIRST(&envs); - TAILQ_INSERT_HEAD(&e->bindings, b, entry); - - return EVAL_OK; -} - -static inline void -setvar_raw(char *sym, struct op *op) -{ - struct binding *b; - struct env *e; - - b = xcalloc(1, sizeof(*b)); - b->name = sym; - b->raw = op; - - e = TAILQ_FIRST(&envs); - TAILQ_INSERT_HEAD(&e->bindings, b, entry); -} - -static inline int -getvar(const char *sym, struct value *v) -{ - struct env *e; - struct binding *b; - - TAILQ_FOREACH(e, &envs, entry) { - TAILQ_FOREACH(b, &e->bindings, entry) { - if (!strcmp(sym, b->name)) { - memcpy(v, &b->val, sizeof(*v)); - return EVAL_OK; - } - } - } - - before_printing(); - fprintf(stderr, "unbound variable %s\n", sym); - return EVAL_ERR; -} - -static inline int -getvar_raw(const char *sym, struct op **raw) -{ - struct env *e; - struct binding *b; - - TAILQ_FOREACH(e, &envs, entry) { - TAILQ_FOREACH(b, &e->bindings, entry) { - if (!strcmp(sym, b->name)) { - *raw = b->raw; - return EVAL_OK; - } - } - } - - return EVAL_ERR; -} - -int -global_set(char *sym, struct op *op) -{ - struct binding *b; - struct env *e; - - /* TODO: check for duplicates */ - - if (op->type != OP_LITERAL && - (op->type == OP_CAST && op->v.cast.expr->type != OP_LITERAL)) - return 0; - - b = xcalloc(1, sizeof(*b)); - b->name = sym; - - /* it's only a cast on a literal! */ - if (op->type == OP_CAST) { - if (eval(op) != EVAL_OK) { - free(b); - return 0; - } - popv(&b->val); - } else - memcpy(&b->val, &op->v.literal, sizeof(b->val)); - - e = TAILQ_LAST(&envs, envs); - TAILQ_INSERT_HEAD(&e->bindings, b, entry); - - return 1; -} - -struct op * -newop(int type) -{ - struct op *op; - - op = xcalloc(1, sizeof(*op)); - op->type = type; - - return op; -} - -void -free_op_rec(struct op *op) -{ - struct op *n; - - while (op != NULL) { - n = op->next; - free_op(op); - op = n; - } -} - -void -free_op(struct op *op) -{ - if (op == NULL) - return; - - switch (op->type) { - case OP_REST: - case OP_LITERAL: - case OP_VARGS: - break; - case OP_ASSIGN: - free(op->v.assign.name); - free_op_rec(op->v.assign.expr); - break; - case OP_ASSERT: - free_op_rec(op->v.assert); - break; - case OP_FUNCALL: - free_op_rec(op->v.funcall.argv); - break; - case OP_VAR: - free(op->v.var); - break; - case OP_CAST: - free_op_rec(op->v.cast.expr); - break; - case OP_CMP_EQ: - case OP_CMP_LEQ: - free_op_rec(op->v.bin_cmp.a); - free_op_rec(op->v.bin_cmp.b); - break; - case OP_FACCESS: - free_op_rec(op->v.faccess.expr); - free(op->v.faccess.field); - break; - case OP_SFAIL: - free(op->v.sfail.msg); - free_op_rec(op->v.sfail.expr); - break; - default: - /* unreachable */ - abort(); - } - - free(op); -} - -struct op * -op_rest(void) -{ - return newop(OP_REST); -} - -struct op * -op_assign(char *sym, struct op *expr) -{ - struct op *op; - - op = newop(OP_ASSIGN); - op->v.assign.name = sym; - op->v.assign.expr = expr; - - return op; -} - -struct op * -op_assert(struct op *expr) -{ - struct op *op; - - op = newop(OP_ASSERT); - op->v.assert = expr; - - return op; -} - -struct op * -op_var(char *sym) -{ - struct op *op; - - op = newop(OP_VAR); - op->v.var = sym; - - return op; -} - -struct op * -op_lit_str(char *str) -{ - struct op *op; - - op = newop(OP_LITERAL); - op->v.literal.type = V_STR; - op->v.literal.v.str = str; - - return op; -} - -struct op * -op_lit_num(uint64_t n) -{ - struct op *op; - - op = newop(OP_LITERAL); - op->v.literal.type = V_NUM; - op->v.literal.v.num = n; - - return op; -} - -struct op * -op_cmp_eq(struct op *a, struct op *b) -{ - struct op *op; - - op = newop(OP_CMP_EQ); - op->v.bin_cmp.a = a; - op->v.bin_cmp.b = b; - - return op; -} - -struct op * -op_cmp_leq(struct op *a, struct op *b) -{ - struct op *op; - - op = newop(OP_CMP_LEQ); - op->v.bin_cmp.a = a; - op->v.bin_cmp.b = b; - - return op; -} - -struct op * -op_cast(struct op *expr, int totype) -{ - struct op *op; - - op = newop(OP_CAST); - op->v.cast.expr = expr; - op->v.cast.totype = totype; - - return op; -} - -struct op * -op_faccess(struct op *expr, char *field) -{ - struct op *op; - - op = newop(OP_FACCESS); - op->v.faccess.expr = expr; - op->v.faccess.field = field; - - return op; -} - -struct op * -op_sfail(struct op *expr, char *msg) -{ - struct op *op; - - op = newop(OP_SFAIL); - op->v.sfail.expr = expr; - op->v.sfail.msg = msg; - - return op; -} - -struct op * -op_vargs(void) -{ - struct op *op; - - op = newop(OP_VARGS); - - return op; -} - -void -ppf_val(FILE *f, struct value *val) -{ - size_t i; - - switch (val->type) { - case V_SYM: - fprintf(f, "%s", val->v.str); - break; - case V_STR: - fprintf(f, "\"%s\"", val->v.str); - break; - case V_NUM: - fprintf(f, "%"PRIi64, val->v.num); - break; - case V_U8: - fprintf(f, "%"PRIu8, val->v.u8); - break; - case V_U16: - fprintf(f, "%"PRIu16, val->v.u16); - break; - case V_U32: - fprintf(f, "%"PRIu32, val->v.u32); - break; - case V_MSG: - fprintf(f, "("); - for (i = 0; i < val->v.msg.len; ++i) - fprintf(f, "%x%s", val->v.msg.msg[i], - i == val->v.msg.len-1 ? "" : " "); - fprintf(f, ")"); - break; - case V_QIDVEC: - fprintf(f, "qids[n=%zu]", val->v.qidvec.len); - break; - default: - fprintf(f, ""); - break; - } -} - -void -pp_val(struct value *val) -{ - ppf_val(stdout, val); -} - -const char * -val_type(struct value *v) -{ - switch (v->type) { - case V_SYM: return "symbol"; - case V_STR: return "string"; - case V_NUM: return "number"; - case V_MSG: return "message"; - case V_QID: return "qid"; - case V_U8: return "u8"; - case V_U16: return "u16"; - case V_U32: return "u32"; - default: return "unknown"; - } -} - -int -val_trueish(struct value *a) -{ - if (val_isnum(a)) - return val_tonum(a); - return 1; -} - -int -val_isnum(struct value *a) -{ - return a->type == V_NUM - || a->type == V_U8 - || a->type == V_U16 - || a->type == V_U32; -} - -int64_t -val_tonum(struct value *a) -{ - switch (a->type) { - case V_NUM: return a->v.num; - case V_U8: return a->v.u8; - case V_U16: return a->v.u16; - case V_U32: return a->v.u32; - default: - before_printing(); - fprintf(stderr, "%s: given value is not a number\n", __func__); - abort(); - } -} - -int -val_eq(struct value *a, struct value *b) -{ - if (val_isnum(a) && val_isnum(b)) - return val_tonum(a) == val_tonum(b); - - if (a->type != b->type) - return 0; - - switch (a->type) { - case V_STR: - case V_SYM: - return !strcmp(a->v.str, b->v.str); - } - - return 0; -} - -int -val_leq(struct value *a, struct value *b) -{ - if (val_isnum(a) && val_isnum(b)) - return val_tonum(a) <= val_tonum(b); - return 0; -} - -static inline const char * -pp_totype(int totype) -{ - /* - * Not all of these are valid cast type thought, including - * every possibility only to aid debugging. - */ - switch (totype) { - case V_STR: return "str"; - case V_SYM: return "sym"; - case V_NUM: return "num"; - case V_QID: return "qid"; - case V_U8: return "u8"; - case V_U16: return "u16"; - case V_U32: return "u32"; - default: return "unknown"; - } -} - -int -val_cast(struct value *a, int totype) -{ - int64_t v; - -#define NUMCAST(val, t, c, totype, max) do { \ - if (val > max) { \ - before_printing(); \ - fprintf(stderr, "can't cast %"PRIu64 \ - " to %s\n", val, pp_totype(totype)); \ - return EVAL_ERR; \ - } \ - a->type = totype; \ - a->v.t = (c)val; \ - return EVAL_OK; \ - } while (0) - - if (a->type == totype) - return EVAL_OK; - - if (!val_isnum(a)) { - before_printing(); - fprintf(stderr, "can't cast "); - ppf_val(stderr, a); - fprintf(stderr, " to type %s\n", pp_totype(totype)); - return EVAL_ERR; - } - - v = a->v.num; - switch (totype) { - case V_U8: NUMCAST(v, u8, uint8_t, totype, UINT8_MAX); - case V_U16: NUMCAST(v, u16, uint16_t, totype, UINT16_MAX); - case V_U32: NUMCAST(v, u32, uint32_t, totype, UINT32_MAX); - default: - before_printing(); - fprintf(stderr, "can't cast %"PRIu64" to %s\n", - v, pp_totype(totype)); - return EVAL_ERR; - } - -#undef NUMCAST -} - -int -val_faccess(struct value *a, const char *field, struct value *ret) -{ - uint8_t mtype; - uint16_t len; - const char *errstr; - -#define MSGTYPE(m) *(m.msg + 4) /* skip the length */ - - switch (a->type) { - case V_QID: - /* TODO: add path. needs uint64_t values thought! */ - if (!strcmp(field, "vers")) { - ret->type = V_U32; - memcpy(&ret->v.u32, a->v.qid+1, 4); - return EVAL_OK; - } else if (!strcmp(field, "type")) { - ret->type = V_U8; - ret->v.u8 = *a->v.qid; - return EVAL_OK; - } - break; - - case V_MSG: - mtype = MSGTYPE(a->v.msg); - if (!strcmp(field, "type")) { - ret->type = V_U8; - ret->v.u8 = MSGTYPE(a->v.msg); - return EVAL_OK; - } else if (!strcmp(field, "tag")) { - ret->type = V_U16; - memcpy(&ret->v.u16, &a->v.msg.msg[5], 2); - ret->v.u16 = le16toh(ret->v.u16); - return EVAL_OK; - } else if (!strcmp(field, "msize") && mtype == Rversion) { - ret->type = V_U32; - memcpy(&ret->v.u32, &a->v.msg.msg[7], 4); - ret->v.u32 = le32toh(ret->v.u32); - return EVAL_OK; - } else if (!strcmp(field, "qid") && mtype == Rattach) { - ret->type = V_QID; - memcpy(&ret->v.qid, &a->v.msg.msg[7], QIDSIZE); - return EVAL_OK; - } else if (!strcmp(field, "nwqid") && mtype == Rwalk) { - ret->type = V_U16; - memcpy(&ret->v.u16, &a->v.msg.msg[7], 2); - ret->v.u16 = le16toh(ret->v.u16); - return EVAL_OK; - } else if (!strcmp(field, "wqid") && mtype == Rwalk) { - ret->type = V_QIDVEC; - ret->v.qidvec.start = &a->v.msg.msg[9]; - memcpy(&len, &a->v.msg.msg[7], 2); - len = le16toh(len); - ret->v.qidvec.len = len; - return EVAL_OK; - } - break; - - case V_QIDVEC: - len = strtonum(field, 0, MAXWELEM, &errstr); - if (errstr != NULL) { - before_printing(); - printf("can't access qid #%s: %s\n", field, errstr); - return EVAL_ERR; - } - - if (len >= a->v.qidvec.len) { - before_printing(); - printf("can't access qid #%d: out-of-bound " - "(max %zu)\n", len, a->v.qidvec.len); - return EVAL_ERR; - } - - ret->type = V_QID; - memcpy(&ret->v.qid, a->v.qidvec.start + len * QIDSIZE, - QIDSIZE); - - return EVAL_OK; - - default: - break; - } - - before_printing(); - printf("can't access field `%s' on type %s (", field, val_type(a)); - pp_val(a); - printf(")\n"); - return EVAL_ERR; - -#undef MSGTYPE -} - -void -pp_op(struct op *op) -{ - struct op *aux; - - switch (op->type) { - case OP_REST: - printf("..."); - break; - case OP_ASSIGN: - printf("%s = ", op->v.assign.name); - pp_op(op->v.assign.expr); - break; - case OP_ASSERT: - printf("assert "); - pp_op(op->v.assert); - break; - case OP_FUNCALL: - printf("funcall %s(", op->v.funcall.proc->name); - for (aux = op->v.funcall.argv; aux != NULL; aux = aux->next) { - pp_op(aux); - if (aux->next != NULL) - printf(", "); - } - printf(")"); - break; - case OP_LITERAL: - pp_val(&op->v.literal); - break; - case OP_VAR: - printf("%s", op->v.var); - break; - case OP_CAST: - pp_op(op->v.cast.expr); - printf(":"); - switch (op->v.cast.totype) { - case V_U8: printf("u8"); break; - case V_U16: printf("u16"); break; - case V_U32: printf("u32"); break; - case V_STR: printf("str"); break; - default: printf("???"); break; - } - break; - case OP_CMP_EQ: - pp_op(op->v.bin_cmp.a); - printf(" == "); - pp_op(op->v.bin_cmp.b); - break; - case OP_CMP_LEQ: - pp_op(op->v.bin_cmp.a); - printf(" <= "); - pp_op(op->v.bin_cmp.b); - break; - case OP_FACCESS: - pp_op(op->v.faccess.expr); - printf(".%s", op->v.faccess.field); - break; - case OP_SFAIL: - printf("should-fail "); - pp_op(op->v.sfail.expr); - if (op->v.sfail.msg != NULL) - printf(": \"%s\"", op->v.sfail.msg); - break; - case OP_VARGS: - printf("vargs"); - break; - default: - printf(" ???[%d] ", op->type); - } -} - -void -pp_block(struct op *op) -{ - while (op != NULL) { - printf("> "); - pp_op(op); - printf("\n"); - - op = op->next; - } -} - -int -eval(struct op *op) -{ - struct value a, b; - struct proc *proc; - struct op *t, *tnext; - int i, ret; - -#if DEBUG - pp_op(op); - printf("\n"); -#endif - - switch (op->type) { - case OP_REST: - /* - * Try to load the rest argument. Note that it can be - * empty! - */ - if ((ret = getvar_raw("...", &t)) == EVAL_OK) - if ((ret = eval(t)) != EVAL_OK) - return ret; - break; - - case OP_ASSIGN: - ret = setvar(op->v.assign.name, op->v.assign.expr); - if (ret != EVAL_OK) - return ret; - break; - - case OP_ASSERT: - if ((ret = eval(op->v.assert)) != EVAL_OK) - return ret; - popv(&a); - if (!val_trueish(&a)) { - before_printing(); - printf("assertion failed: "); - pp_op(op->v.assert); - printf("\n"); - return EVAL_ERR; - } - break; - - case OP_FUNCALL: - /* assume airity matches */ - - proc = op->v.funcall.proc; - if (proc->nativefn != NULL) { - /* - * Push arguments on the stack for builtin - * functions. Counting the height of the - * stack is done to compute the correct number - * in the vararg case. argc only counts the - * "syntactical" arguments, i.e. foo(x, ...) - * has argc == 2, but at runtime argc may be - * 1, 2 or a greater number! - */ - - i = stackh; - t = op->v.funcall.argv; - if (t != NULL && (ret = eval(t)) != EVAL_OK) - return ret; - i = stackh - i; - - assert(i >= 0); - - if ((ret = proc->nativefn(i)) - != EVAL_OK) - return ret; - } else { - if (proc->body == NULL) { - before_printing(); - printf("warn: calling the empty proc `%s'\n", - proc->name); - break; - } - - pushenv(); - - for (t = op->v.funcall.argv, i = 0; - t != NULL; - t = t->next, i++) { - /* - * Push a pseudo variable `...' (and - * don't evaluate it) in the vararg - * case. A special case is when the - * variable is itself `...'. - */ - if (proc->vararg && i == proc->minargs) { - if (t->type != OP_REST) - setvar_raw(xstrdup("..."), t); - break; - } - - /* - * The arguments are a linked list of - * ops. Setvar will call eval that - * will evaluate *all* the arguments. - * The dance here that sets next to - * NULL and then restores it is to - * avoid this behaviour. - */ - tnext = t->next; - t->next = NULL; - ret = setvar(proc->args[i], t); - t->next = tnext; - - if (ret != EVAL_OK) - return ret; - } - - if ((ret = eval(proc->body)) != EVAL_OK) - return ret; - - popenv(); - } - - break; - - case OP_LITERAL: - pushv(&op->v.literal); - break; - - case OP_VAR: - if ((ret = getvar(op->v.var, &a)) != EVAL_OK) - return ret; - pushv(&a); - break; - - case OP_CAST: - if ((ret = eval(op->v.cast.expr)) != EVAL_OK) - return ret; - popv(&a); - if ((ret = val_cast(&a, op->v.cast.totype)) != EVAL_OK) - return ret; - pushv(&a); - break; - - case OP_CMP_EQ: - if ((ret = eval(op->v.bin_cmp.a)) != EVAL_OK) - return ret; - if ((ret = eval(op->v.bin_cmp.b)) != EVAL_OK) - return ret; - - popv(&b); - popv(&a); - pushbool(val_eq(&a, &b)); - break; - - case OP_CMP_LEQ: - if ((ret = eval(op->v.bin_cmp.a)) != EVAL_OK) - return ret; - if ((ret = eval(op->v.bin_cmp.b)) != EVAL_OK) - return ret; - - popv(&b); - popv(&a); - pushbool(val_leq(&a, &b)); - break; - - case OP_FACCESS: - if ((ret = eval(op->v.faccess.expr)) != EVAL_OK) - return ret; - popv(&a); - if ((ret = val_faccess(&a, op->v.faccess.field, &b)) - != EVAL_OK) - return ret; - pushv(&b); - break; - - case OP_SFAIL: - if ((ret = eval(op->v.sfail.expr)) == EVAL_OK) { - before_printing(); - printf("expecting failure"); - if (op->v.sfail.msg != NULL) - printf(" \"%s\"", op->v.sfail.msg); - printf("\n"); - printf("expression: "); - pp_op(op->v.sfail.expr); - printf("\n"); - return EVAL_ERR; - } - if (ret == EVAL_SKIP) - return ret; - break; - - case OP_VARGS: - if ((ret = getvar_raw("...", &t)) == EVAL_OK) { - for (i = 0; t != NULL; t = t->next) - i++; - pushnum(i); - } else - pushnum(0); - break; - - default: - before_printing(); - fprintf(stderr, "invalid op, aborting.\n"); - abort(); - } - - if (op->next) - return eval(op->next); - return EVAL_OK; -} - -void -prepare_funcall(void) -{ - pushstack(&args); -} - -void -push_arg(struct op *op) -{ - push(&args, op); -} - -struct op * -op_funcall(struct proc *proc) -{ - struct op *op, *argv; - int argc; - - argv = finalize(&args, &argc); - - op = newop(OP_FUNCALL); - op->v.funcall.proc = proc; - op->v.funcall.argv = argv; - op->v.funcall.argc = argc; - - return op; -} - -void -add_builtin_proc(const char *name, int (*fn)(int), int argc, int vararg) -{ - struct proc *proc; - - proc = xcalloc(1, sizeof(*proc)); - proc->name = xstrdup(name); - proc->nativefn = fn; - proc->minargs = argc; - proc->vararg = vararg; - - TAILQ_INSERT_HEAD(&procs, proc, entry); -} - -void -prepare_proc(void) -{ - pushstack(&args); -} - -int -proc_setup_body(void) -{ - struct opstack *argv; - struct op *op; - int i; - - argv = peek(&args); - for (i = 0, op = argv->base.next; op != NULL; i++) { - /* - * TODO: should free the whole list on error but.., - * we're gonna exit real soon(tm)! - */ - if (op->type != OP_VAR && op->type != OP_REST) - return 0; - - op = op->next; - } - - assert(i == argv->counter); - pushstack(&blocks); - return 1; -} - -void -proc_done(char *name) -{ - struct proc *proc; - struct op *op, *next, *argv, *body; - int i, argc; - - argv = finalize(&args, &argc); - body = finalize(&blocks, NULL); - - proc = xcalloc(1, sizeof(*proc)); - proc->name = name; - proc->minargs = argc; - - for (i = 0, op = argv; op != NULL; ++i) { - if (op->type == OP_REST) { - proc->vararg = 1; - proc->minargs = i; - break; - } - - proc->args[i] = xstrdup(op->v.var); - - next = op->next; - free_op(op); - op = next; - } - assert(i == argc || (proc->vararg && i == proc->minargs)); - - proc->body = body; - - TAILQ_INSERT_HEAD(&procs, proc, entry); -} - -void -block_push(struct op *op) -{ - push(&blocks, op); -} - -struct proc * -proc_by_name(const char *name) -{ - struct proc *p; - - TAILQ_FOREACH(p, &procs, entry) { - if (!strcmp(p->name, name)) - return p; - } - - return NULL; -} - -void -prepare_test(void) -{ - pushstack(&blocks); -} - -void -test_done(int shouldfail, char *name, char *dir) -{ - struct test *test; - - test = xcalloc(1, sizeof(*test)); - test->shouldfail = shouldfail; - test->name = name; - test->dir = dir; - test->body = finalize(&blocks, NULL); - - if (TAILQ_EMPTY(&tests)) - TAILQ_INSERT_HEAD(&tests, test, entry); - else - TAILQ_INSERT_TAIL(&tests, test, entry); - - ntests++; -} - -static int -builtin_print(int argc) -{ - struct value v; - int i; - - before_printing(); - - for (i = argc; i > 0; --i) { - peekn(i, &v); - if (v.type == V_STR) - printf("%s", v.v.str); - else - pp_val(&v); - printf(" "); - } - - printf("\n"); - - popvn(argc); - - return EVAL_OK; -} - -static int -builtin_debug(int argc) -{ - if (debug) - return builtin_print(argc); - - popvn(argc); - return EVAL_OK; -} - -static int -builtin_skip(int argc) -{ - return EVAL_SKIP; -} - -static int -builtin_iota(int argc) -{ - struct value v; - - v.type = V_U16; - if ((v.v.u16 = ++lasttag) == 255) - v.v.u16 = ++lasttag; - - pushv(&v); - return EVAL_OK; -} - -static int -builtin_send(int argc) -{ - struct ibuf *buf; - struct value v; - uint32_t len; - uint16_t slen; - int i; - - check_for_output(); - - /* - * Compute the length of the packet. 4 is for the initial - * length field - */ - len = 4; - - for (i = argc; i > 0; --i) { - peekn(i, &v); - switch (v.type) { - case V_STR: - len += 2; /* count */ - len += strlen(v.v.str); - break; - - case V_U8: - len += 1; - break; - - case V_U16: - len += 2; - break; - - case V_U32: - len += 4; - break; - - default: - before_printing(); - printf("%s: can't serialize ", __func__); - pp_val(&v); - printf("\n"); - return EVAL_ERR; - } - } - - if (len > UINT16_MAX) { - before_printing(); - printf("%s: message size too long: got %d when max is %d\n", - __func__, len, UINT16_MAX); - return EVAL_ERR; - } - - if ((buf = imsg_create(&ibuf, IMSG_BUF, 0, 0, len)) == NULL) - fatal("imsg_create(%d)", len); - - len = htole32(len); - imsg_add(buf, &len, sizeof(len)); - - for (i = argc; i > 0; --i) { - peekn(i, &v); - switch (v.type) { - case V_STR: - slen = strlen(v.v.str); - slen = htole16(slen); - imsg_add(buf, &slen, sizeof(slen)); - imsg_add(buf, v.v.str, strlen(v.v.str)); - break; - - case V_U8: - imsg_add(buf, &v.v.u8, 1); - break; - - case V_U16: - v.v.u16 = htole16(v.v.u16); - imsg_add(buf, &v.v.u16, 2); - break; - - case V_U32: - v.v.u32 = htole32(v.v.u32); - imsg_add(buf, &v.v.u32, 4); - break; - } - } - - imsg_close(&ibuf, buf); - - if (imsg_flush(&ibuf) == -1) { - i = errno; - before_printing(); - printf("%s: imsg_flush failed: %s\n", __func__, strerror(i)); - return EVAL_ERR; - } - - check_for_output(); - return EVAL_OK; -} - -static int -builtin_recv(int argc) -{ - struct pollfd pfd; - struct value v; - struct imsg imsg; - ssize_t n, datalen; - int serrno; - - if (lastmsg != NULL) { - free(lastmsg); - lastmsg = NULL; - } - - pfd.fd = ibuf.fd; - pfd.events = POLLIN; - if (poll(&pfd, 1, INFTIM) == -1) { - serrno = errno; - before_printing(); - printf("%s: poll failed: %s\n", __func__, strerror(serrno)); - return EVAL_ERR; - } - -again: - if ((n = imsg_read(&ibuf)) == -1) { - if (errno == EAGAIN) - goto again; - fatal("imsg_read"); - } - if (n == 0) { -disconnect: - before_printing(); - printf("child disconnected\n"); - return EVAL_ERR; - } - -nextmessage: - check_for_output(); - - /* read only one message */ - if ((n = imsg_get(&ibuf, &imsg)) == -1) - fatal("imsg_get"); - if (n == 0) - goto disconnect; - - datalen = imsg.hdr.len - IMSG_HEADER_SIZE; - switch (imsg.hdr.type) { - case IMSG_BUF: - v.type = V_MSG; - if ((v.v.msg.msg = malloc(datalen)) == NULL) - fatal("malloc"); - memcpy(v.v.msg.msg, imsg.data, datalen); - v.v.msg.len = datalen; - pushv(&v); - imsg_free(&imsg); - return EVAL_OK; - - case IMSG_CLOSE: - before_printing(); - printf("subprocess closed the connection\n"); - imsg_free(&imsg); - return EVAL_ERR; - - case IMSG_MSIZE: - imsg_free(&imsg); - goto nextmessage; - - default: - before_printing(); - printf("got unknown message from subprocess: %d\n", - imsg.hdr.type); - imsg_free(&imsg); - return EVAL_ERR; - } -} - -static pid_t -spawn_client_proc(void) -{ - const char *argv[4]; - int p[2], out[2], argc = 0; - pid_t pid; - - if (child_out != -1) - close(child_out); - - if (socketpair(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC | SOCK_NONBLOCK, - PF_UNSPEC, p) == -1) - fatal("socketpair"); - - if (socketpair(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC | SOCK_NONBLOCK, - PF_UNSPEC, out) == -1) - fatal("socketpair"); - - switch (pid = fork()) { - case -1: - fatal("cannot fork"); - case 0: - break; - default: - close(p[1]); - close(out[1]); - child_out = out[0]; - if (ibuf_inuse) { - msgbuf_clear(&ibuf.w); - close(ibuf.fd); - } - imsg_init(&ibuf, p[0]); - ibuf_inuse = 1; - return pid; - } - - close(p[0]); - close(out[0]); - - if (dup2(out[1], 1) == -1 || - dup2(out[1], 2) == -1) - fatal("dup2"); - - if (p[1] != 3) { - if (dup2(p[1], 3) == -1) - fatal("cannot setup imsg fd"); - } else if (fcntl(F_SETFD, 0) == -1) - fatal("cannot setup imsg fd"); - - argv[argc++] = argv0; - argv[argc++] = "-Tc"; - -#if DEBUG - argv[argc++] = "-v"; -#endif - - argv[argc++] = NULL; - - execvp(argv0, (char *const *)argv); - fatal("execvp"); -} - -static void -prepare_child_for_test(struct test *t) -{ - struct passwd *pw; - struct stat sb; - - if (stat(t->dir, &sb) == -1) - fatal("stat(\"%s\")", t->dir); - - if ((pw = getpwuid(sb.st_uid)) == NULL) - fatal("getpwuid(%d)", sb.st_uid); - - imsg_compose(&ibuf, IMSG_AUTH, 0, 0, -1, - pw->pw_name, strlen(pw->pw_name)+1); - imsg_compose(&ibuf, IMSG_AUTH_DIR, 0, 0, -1, - t->dir, strlen(t->dir)+1); - - if (imsg_flush(&ibuf) == -1) - fatal("imsg_flush"); -} - -static int -run_test(struct test *t) -{ - pid_t pid; - int ret; - -#if DEBUG - before_printing(); - puts("====================="); - pp_block(t->body); - puts("====================="); -#endif - - if (stackh != 0) - popvn(stackh); - - if (t->body == NULL) { - before_printing(); - printf("no instructions, skipping...\n"); - return EVAL_SKIP; - } - - pid = spawn_client_proc(); - prepare_child_for_test(t); - ret = eval(t->body); - - imsg_compose(&ibuf, IMSG_CONN_GONE, 0, 0, -1, NULL, 0); - imsg_flush(&ibuf); - - while (waitpid(pid, NULL, 0) != pid) - ; /* nop */ - - check_for_output(); - - if (t->shouldfail) { - if (ret == EVAL_OK) { - before_printing(); - printf("test was expected to fail\n"); - return EVAL_ERR; - } else if (ret == EVAL_ERR) - return EVAL_OK; - } - - return ret; -} - -int -main(int argc, char **argv) -{ - struct test *t; - int ch, i, r, passed = 0, failed = 0, skipped = 0; - int runclient = 0; - const char *pat = NULL; - regex_t reg; - - assert(argv0 = argv[0]); - - signal(SIGPIPE, SIG_IGN); - - log_init(1, LOG_DAEMON); - log_setverbose(1); - - /* prepare the global env */ - pushenv(); - - add_builtin_proc("print", builtin_print, 1, 1); - add_builtin_proc("debug", builtin_debug, 1, 1); - add_builtin_proc("skip", builtin_skip, 0, 0); - add_builtin_proc("iota", builtin_iota, 0, 0); - add_builtin_proc("send", builtin_send, 2, 1); - add_builtin_proc("recv", builtin_recv, 0, 0); - - while ((ch = getopt(argc, argv, "nT:vx:")) != -1) { - switch (ch) { - case 'n': - syntaxcheck = 1; - break; - case 'T': - assert(*optarg == 'c'); - runclient = 1; - break; - case 'v': - debug = 1; - break; - case 'x': - pat = optarg; - break; - default: - fprintf(stderr, "Usage: %s [-nv] [files...]\n", - *argv); - exit(1); - } - } - argc -= optind; - argv += optind; - - if (runclient) - client(1, debug); - - if (pat == NULL) - pat = ".*"; - - if (regcomp(®, pat, REG_BASIC | REG_ICASE | REG_NOSUB) != 0) - fatalx("invalid regexp: %s", pat); - - for (i = 0; i < argc; ++i) - loadfile(argv[i]); - - if (syntaxcheck) { - fprintf(stderr, "files OK\n"); - return 0; - } - - /* Check for root privileges. */ - if (geteuid()) - fatalx("need root privileges"); - - i = 0; - TAILQ_FOREACH(t, &tests, entry) { - if (regexec(®, t->name, 0, NULL, 0) != 0) - continue; - - printf("===> [%d/%d] running test \"%s\"... ", i+1, ntests, - t->name); - fflush(stdout); - - filler = "\n"; - r = run_test(t); - if (filler == NULL) - printf("=> test "); - - switch (r) { - case EVAL_OK: - printf("passed\n"); - passed++; - break; - case EVAL_ERR: - failed++; - printf("failed\n"); - break; - case EVAL_SKIP: - printf("skipped\n"); - skipped++; - break; - } - - if (filler == NULL) - printf("\n"); - i++; - } - - printf("\n"); - printf("%d/%d passed (%d skipped and %d failed)\n", - passed, i, skipped, failed); - - popenv(); - free(lastmsg); - regfree(®); - - return failed != 0; -} blob - f7b6a07ae2b5f8ecc35bea64130cf0b6af2af2c0 (mode 644) blob + /dev/null --- script.h +++ /dev/null @@ -1,223 +0,0 @@ -/* - * Copyright (c) 2021 Omar Polo - * - * Permission to use, copy, modify, and distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. - * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - */ - -#ifndef SCRIPT_H -#define SCRIPT_H - -#include "compat.h" - -#include - -#include "kamid.h" - -enum { - /* literals */ - V_SYM, - V_STR, - V_NUM, - - /* foreign */ - V_MSG, - V_QIDVEC, - V_QID, - - /* casted */ - V_U8, - V_U16, - V_U32, -}; - -struct value { - int type; - union { - char *str; - int64_t num; - uint8_t u8; - uint16_t u16; - uint32_t u32; - struct { - uint8_t *msg; - size_t len; - } msg; - struct { - uint8_t *start; - size_t len; - } qidvec; - uint8_t qid[QIDSIZE]; - } v; -}; - -enum { - OP_REST, - OP_ASSIGN, - OP_ASSERT, - OP_FUNCALL, - OP_LITERAL, - OP_VAR, - OP_CAST, - OP_CMP_EQ, - OP_CMP_LEQ, - OP_FACCESS, - OP_SFAIL, - OP_VARGS, -}; - -struct proc; - -struct op { - struct op *next; - int type; - union { - struct { - char *name; - struct op *expr; - } assign; - struct op *assert; - struct { - struct proc *proc; - struct op *argv; - int argc; - } funcall; - struct value literal; - char *var; - struct { - struct op *expr; - int totype; - } cast; - struct { - struct op *a; - struct op *b; - } bin_cmp; - struct { - struct op *expr; - char *field; - } faccess; - struct { - char *msg; - struct op *expr; - } sfail; - } v; -}; - -TAILQ_HEAD(bindings, binding); -struct binding { - TAILQ_ENTRY(binding) entry; - char *name; - struct value val; - - /* - * Hack to support varargs. We set a special variable named - * "..." that contains the list of ops that will evaluate to - * the arguments. - */ - struct op *raw; -}; - -TAILQ_HEAD(envs, env); -struct env { - TAILQ_ENTRY(env) entry; - struct bindings bindings; -}; - -TAILQ_HEAD(opstacks, opstack); -struct opstack { - TAILQ_ENTRY(opstack) entry; - struct op base; - struct op *last; - int counter; -}; - -TAILQ_HEAD(procs, proc); -struct proc { - TAILQ_ENTRY(proc) entry; - char *name; - int minargs; - int vararg; - char *args[MAXWELEM]; - struct op *body; - int (*nativefn)(int); -}; - -TAILQ_HEAD(tests, test); -struct test { - TAILQ_ENTRY(test) entry; - int shouldfail; - char *name; - char *dir; - struct op *body; -}; - -enum { - EVAL_OK, - EVAL_ERR, - EVAL_SKIP, -}; - -int global_set(char *, struct op *); - -struct op *newop(int); -void free_op_rec(struct op *); -void free_op(struct op *); -struct op *op_rest(void); -struct op *op_assign(char *, struct op *); -struct op *op_assert(struct op *); -struct op *op_var(char *); -struct op *op_lit_str(char *); -struct op *op_lit_num(uint64_t); -struct op *op_cmp_eq(struct op *, struct op *); -struct op *op_cmp_leq(struct op *, struct op *); -struct op *op_cast(struct op *, int); -struct op *op_faccess(struct op *, char *); -struct op *op_sfail(struct op *, char *); -struct op *op_vargs(void); - -void ppf_val(FILE *, struct value *); -void pp_val(struct value *); -void pp_val(struct value *); -const char *val_type(struct value *); -int val_trueish(struct value *); -int val_isnum(struct value *); -int64_t val_tonum(struct value *); -int val_eq(struct value *, struct value *); -int val_leq(struct value *, struct value *); -int val_cast(struct value *, int); -int val_faccess(struct value *, const char *, struct value *); -void pp_op(struct op *); -void pp_block(struct op *); -int eval(struct op *); - -/* funcall */ -void prepare_funcall(void); -void push_arg(struct op *); -struct op *op_funcall(struct proc *); - -/* proc */ -void add_builtin_proc(const char *name, int (*)(int), int, int); -void prepare_proc(void); -/* push_arg works on procs too */ -int proc_setup_body(void); -void proc_done(char *name); -void block_push(struct op *); -struct proc *proc_by_name(const char *); - -/* testing */ -void prepare_test(void); -void test_done(int, char *, char *); - -/* np.y */ -void loadfile(const char *); - -#endif blob - 801209c5402fa8ad9a51028d4aa0e06848279093 (mode 644) blob + /dev/null --- table.c +++ /dev/null @@ -1,98 +0,0 @@ -/* - * Copyright (c) 2021 Omar Polo - * - * Permission to use, copy, modify, and distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. - * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - */ - -#include "compat.h" - -#include -#include - -#include "log.h" -#include "table.h" -#include "utils.h" - -int -table_open(struct kd_conf *conf, const char *name, const char *type, - const char *path) -{ - struct table *t; - struct kd_tables_conf *entry; - struct table_backend *backends[] = { - &table_static, - NULL, - }, *b; - size_t i; - - for (i = 0; backends[i] != NULL; ++i) { - b = backends[i]; - if (!strcmp(type, b->name)) - goto found; - } - log_warn("unknown table type %s", type); - return -1; - -found: - if (b->open == NULL) { - log_warn("can't open table %s (type %s)", - name, b->name); - return -1; - } - - t = xcalloc(1, sizeof(*t)); - strlcpy(t->t_name, name, sizeof(t->t_name)); - if (path != NULL) - strlcpy(t->t_path, path, sizeof(t->t_path)); - t->t_backend = b; - - if (t->t_backend->open(t) == -1) - fatal("can't open table %s (type %s)", - name, path); - - entry = xcalloc(1, sizeof(*entry)); - entry->table = t; - STAILQ_INSERT_HEAD(&conf->table_head, entry, entry); - return 0; -} - -int -table_add(struct table *t, const char *key, const char *val) -{ - if (t->t_backend->add == NULL) { - log_warn("can't add to table %s (type %s)", - t->t_name, t->t_backend->name); - return -1; - } - - return t->t_backend->add(t, key, val); -} - -int -table_lookup(struct table *t, const char *key, char **ret_val) -{ - if (t->t_backend->lookup == NULL) { - log_warn("can't lookup table %s (type %s)", - t->t_name, t->t_backend->name); - return -1; - } - - return t->t_backend->lookup(t, key, ret_val); -} - -void -table_close(struct table *t) -{ - if (t->t_backend->close != NULL) - t->t_backend->close(t); -} blob - 5380d45279b042b87c616571f4f34c053c0cf217 (mode 644) blob + /dev/null --- table.h +++ /dev/null @@ -1,27 +0,0 @@ -/* - * Copyright (c) 2021 Omar Polo - * - * Permission to use, copy, modify, and distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. - * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - */ - -#ifndef TABLE_H -#define TABLE_H - -#include "kamid.h" - -int table_open(struct kd_conf *, const char *, const char *, const char *); -int table_add(struct table *, const char *, const char *); -int table_lookup(struct table *, const char *, char **); -void table_close(struct table *); - -#endif blob - cb42223006577b8071076b8753890c9fe56df50e (mode 644) blob + /dev/null --- table_static.c +++ /dev/null @@ -1,130 +0,0 @@ -/* - * Copyright (c) 2021 Omar Polo - * - * Permission to use, copy, modify, and distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. - * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - */ - -#include "compat.h" - -#include -#include - -#include "kamid.h" -#include "utils.h" - -static void *hash_alloc(size_t, void *); -static void *hash_calloc(size_t, size_t, void *); -static void hash_free(void *, void *); - -static int table_static_open(struct table *); -static int table_static_add(struct table *, const char *, const char *); -static int table_static_lookup(struct table *, const char *, char **); -static void table_static_close(struct table *); - -struct table_backend table_static = { - "static", - table_static_open, - table_static_add, - table_static_lookup, - table_static_close, -}; - -struct kp { - char *val; - char key[]; -}; - -static void * -hash_alloc(size_t len, void *d) -{ - return xmalloc(len); -} - -static void * -hash_calloc(size_t nmemb, size_t size, void *d) -{ - return xcalloc(nmemb, size); -} - -static void -hash_free(void *ptr, void *d) -{ - free(ptr); -} - -static int -table_static_open(struct table *t) -{ - struct ohash_info info = { - .key_offset = offsetof(struct kp, key), - .calloc = hash_calloc, - .free = hash_free, - .alloc = hash_alloc, - }; - - t->t_handle = xmalloc(sizeof(struct ohash)); - ohash_init(t->t_handle, 5, &info); - return 0; -} - -int -table_static_add(struct table *t, const char *key, const char *val) -{ - struct kp *kp; - unsigned int slot; - - if (key == NULL) - return -1; - - kp = xcalloc(1, sizeof(*kp) + strlen(key) + 1); - strcpy(kp->key, key); - if (val != NULL) - kp->val = xstrdup(val); - - slot = ohash_qlookup(t->t_handle, kp->key); - ohash_insert(t->t_handle, slot, kp); - - return 0; -} - -int -table_static_lookup(struct table *t, const char *key, char **ret_val) -{ - struct kp *kp; - unsigned int slot; - - slot = ohash_qlookup(t->t_handle, key); - if ((kp = ohash_find(t->t_handle, slot)) == NULL) - return -1; - - *ret_val = xstrdup(kp->val); - return 0; -} - -static void -table_static_close(struct table *t) -{ - struct kp *kp; - unsigned int i; - - for (kp = ohash_first(t->t_handle, &i); - kp != NULL; - kp = ohash_next(t->t_handle, &i)) { - ohash_remove(t->t_handle, i); - free(kp->key); - free(kp->val); - free(kp); - } - - free(t->t_handle); -} blob - 3315f4365f5644798ab3fc243eccdc05c228926f (mode 644) blob + /dev/null --- utils.c +++ /dev/null @@ -1,201 +0,0 @@ -/* - * Copyright (c) 2021 Omar Polo - * - * Permission to use, copy, modify, and distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. - * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - */ - -#include "compat.h" - -#include -#include -#include -#include - -#include "kamid.h" -#include "log.h" -#include "utils.h" - -void * -xmalloc(size_t size) -{ - void *r; - - if ((r = malloc(size)) == NULL) - fatal("malloc"); - return r; -} - -void * -xcalloc(size_t nmemb, size_t size) -{ - void *r; - - if ((r = calloc(nmemb, size)) == NULL) - fatal("calloc"); - return r; -} - -char * -xstrdup(const char *s) -{ - char *r; - - if ((r = strdup(s)) == NULL) - fatal("strdup"); - return r; -} - -void * -xmemdup(const void *d, size_t len) -{ - void *r; - - if ((r = malloc(len)) == NULL) - fatal("malloc"); - memcpy(r, d, len); - return r; -} - -const char * -pp_msg_type(uint8_t type) -{ - switch (type) { - case Tversion: return "Tversion"; - case Rversion: return "Rversion"; - case Tauth: return "Tauth"; - case Rauth: return "Rauth"; - case Tattach: return "Tattach"; - case Rattach: return "Rattach"; - case Terror: return "Terror"; /* illegal */ - case Rerror: return "Rerror"; - case Tflush: return "Tflush"; - case Rflush: return "Rflush"; - case Twalk: return "Twalk"; - case Rwalk: return "Rwalk"; - case Topen: return "Topen"; - case Ropen: return "Ropen"; - case Tcreate: return "Tcreate"; - case Rcreate: return "Rcreate"; - case Tread: return "Tread"; - case Rread: return "Rread"; - case Twrite: return "Twrite"; - case Rwrite: return "Rwrite"; - case Tclunk: return "Tclunk"; - case Rclunk: return "Rclunk"; - case Tremove: return "Tremove"; - case Rremove: return "Rremove"; - case Tstat: return "Tstat"; - case Rstat: return "Rstat"; - case Twstat: return "Twstat"; - case Rwstat: return "Rwstat"; - default: return "unknown"; - } -} - -const char * -pp_qid_type(uint8_t type) -{ - switch (type) { - case QTDIR: return "dir"; - case QTAPPEND: return "append-only"; - case QTEXCL: return "exclusive"; - case QTMOUNT: return "mounted-channel"; - case QTAUTH: return "authentication"; - case QTTMP: return "non-backed-up"; - case QTSYMLINK: return "symlink"; - case QTFILE: return "file"; - } - - return "unknown"; -} - -static void -hexdump_ppline(int x, uint8_t *data, size_t len) -{ - for (; x < 50; x++) - printf(" "); - - printf("|"); - - for (x = 0; x < (int)len; ++x) { - if (isgraph(data[x])) - printf("%c", data[x]); - else - printf("."); - } - - printf("|\n"); -} - -void -hexdump(const char *label, uint8_t *data, size_t len) -{ - size_t i; - int x, n; - - /* - * Layout: - * === first block === == second block == |........|\n - * first and second block are 8 bytes long (for a total of 48 - * columns), plus two separator plus two | plus 16 chars, for - * a total of 68 characters. - */ - - printf("\nhexdump \"%s\": (%zu bytes)\n", label, len); - for (x = 0, n = 0, i = 0; i < len; ++i) { - if (i != 0 && i % 8 == 0) { - printf(" "); - x++; - } - - if (n == 16) { - hexdump_ppline(x, &data[i - 16], 16); - x = 0; - n = 0; - } - - printf("%02x ", data[i]); - x += 3; - n++; - } - - if (n != 0) - hexdump_ppline(x, &data[i - n], n); - - printf("\n"); -} - -void -imsg_event_add(struct imsgev *iev) -{ - iev->events = EV_READ; - if (iev->ibuf.w.queued) - iev->events |= EV_WRITE; - - event_del(&iev->ev); - event_set(&iev->ev, iev->ibuf.fd, iev->events, iev->handler, iev); - event_add(&iev->ev, NULL); -} - -int -imsg_compose_event(struct imsgev *iev, uint16_t type, uint32_t peerid, - pid_t pid, int fd, const void *data, uint16_t datalen) -{ - int ret; - - if ((ret = imsg_compose(&iev->ibuf, type, peerid, pid, fd, data, - datalen) != -1)) - imsg_event_add(iev); - - return ret; -} blob - 55b0bbe929a564d5e891da783db89f48371265d8 (mode 644) blob + /dev/null --- utils.h +++ /dev/null @@ -1,38 +0,0 @@ -/* - * Copyright (c) 2021 Omar Polo - * - * Permission to use, copy, modify, and distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. - * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - */ - -#ifndef UTILS_H -#define UTILS_H - -#include "compat.h" - -struct imsgev; - -void *xmalloc(size_t); -void *xcalloc(size_t, size_t); -char *xstrdup(const char *); -void *xmemdup(const void *, size_t); - -const char *pp_msg_type(uint8_t); -const char *pp_qid_type(uint8_t); - -void hexdump(const char *, uint8_t *data, size_t len); - -void imsg_event_add(struct imsgev *); -int imsg_compose_event(struct imsgev *, uint16_t, uint32_t, pid_t, - int, const void *, uint16_t); - -#endif