diff --git a/.gitignore b/.gitignore index f2d1c8137..e6b87b0b9 100644 --- a/.gitignore +++ b/.gitignore @@ -30,4 +30,3 @@ node_modules/ # ?? /inst cscope.* -build/ diff --git a/.gitmodules b/.gitmodules deleted file mode 100644 index b56a2d94e..000000000 --- a/.gitmodules +++ /dev/null @@ -1,18 +0,0 @@ -[submodule "subprojects/softfloat3"] - path = subprojects/softfloat3 - url = https://github.com/urbit/berkeley-softfloat-3.git -[submodule "subprojects/commonmark-legacy"] - path = subprojects/commonmark-legacy - url = https://github.com/urbit/commonmark-legacy.git -[submodule "subprojects/http-parser-legacy"] - path = subprojects/http-parser-legacy - url = https://github.com/urbit/http-parser-legacy.git -[submodule "subprojects/ed25519"] - path = subprojects/ed25519 - url = https://github.com/urbit/ed25519.git -[submodule "subprojects/libscrypt"] - path = subprojects/libscrypt - url = https://github.com/urbit/libscrypt.git -[submodule "subprojects/murmur3"] - path = subprojects/murmur3 - url = https://github.com/urbit/murmur3.git diff --git a/.travis.yml b/.travis.yml index 1147a2ee2..8063ec07f 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,44 +1,30 @@ language: c -script: meson build && cd ./build && ninja +script: make && make test # no ./configure # Uncomment me if this gets annoying # -# notifications: -# email: false - -before_install: - - wget https://github.com/ninja-build/ninja/releases/download/v1.8.2/ninja-linux.zip - - unzip ninja-linux.zip - - sudo mv ninja /usr/bin/ - - wget https://github.com/libuv/libuv/archive/v1.19.2.tar.gz - - tar -xvf v1.19.2.tar.gz - - cd libuv-1.19.2 - - sh autogen.sh - - ./configure --prefix=/usr - - make - - sudo make install - - cd .. -install: - - pip3 install --user -I meson==0.44.1 +# notifications: +# email: false addons: apt: packages: - - python3 - - python3-pip - libgmp3-dev - libsigsegv-dev - openssl - libssl-dev - libncurses5-dev + - make + - exuberant-ctags - automake - autoconf - - make - libtool - g++ + - ragel + - cmake - re2c - libcurl4-gnutls-dev - - unzip + - python # before_deploy: "make deb" # TODO deploy: skip_cleanup: true @@ -46,7 +32,7 @@ deploy: prerelease: true # turn this off for official releases api_key: secure: V4E7784ECSS3MO6ZIRtang9XwibDyvDYGb0MoSaP2CTlmzIAhdokr4KJFM0qM4KRaaajCdQuqi0lojgOjwdxs7e0GkAwScb33LFxQ7Chj/QkFOY7V1AnSRLR5OsXnazB0nur5aSwvcvnggQ2XW3OeF7zIvGfs9aR97SEz/xCrVE= - file: ./build/urbit # TODO upload package from before_deploy + file: bin/urbit # TODO upload package from before_deploy on: repo: urbit/urbit tags: true diff --git a/Makefile b/Makefile new file mode 100644 index 000000000..33b0853c9 --- /dev/null +++ b/Makefile @@ -0,0 +1,552 @@ +# A simple makefile. +# + +default: all +-include .make.conf + +CORE=.MAKEFILE-VERSION + +# Pick one of: +# linux +# osx + +UNAME=$(shell uname) +ifeq ($(UNAME),Darwin) + OS=osx +else ifeq ($(UNAME),Linux) + OS=linux +else ifeq ($(UNAME),FreeBSD) + OS=bsd +else ifeq ($(UNAME),OpenBSD) + OS=bsd +else + $(error unknown unix) +endif + +# Pick one of: +# little +# big +# +ENDIAN=little + +# Binary directory - not in quotes. +# +BIN=bin + +# Only include/link with this if it exists. +# (Mac OS X El Capitan clean install does not have /opt) +ifneq (,$(wildcard /opt/local/.)) + OPTLOCALINC?=/opt/local/include + OPTLOCALLIB?=/opt/local/lib +endif + +# Only include/link with this if it exists. +# (`brew install openssl` on Mac OS X El Capitan puts openssl here) +ifneq (,$(wildcard /usr/local/opt/openssl/.)) + OPENSSLINC?=/usr/local/opt/openssl/include + OPENSSLLIB?=/usr/local/opt/openssl/lib +endif + +# can't have empty -I or -L options due to whitespace sensitivity +ifdef OPTLOCALINC + OPTLOCALIFLAGS=-I$(OPTLOCALINC) +endif +ifdef OPTLOCALLIB + OPTLOCALLFLAGS=-L$(OPTLOCALLIB) +endif +ifdef OPENSSLINC + OPENSSLIFLAGS=-I$(OPENSSLINC) +endif +ifdef OPENSSLLIB + OPENSSLLFLAGS=-L$(OPENSSLLIB) +endif + +CURLINC=$(shell curl-config --cflags) +CURLLIB=$(shell curl-config --libs) + +RM=rm -f +CC=cc +CXX=c++ +CXXFLAGS=$(CFLAGS) +CLD=c++ $(CFLAGS) -L/usr/local/lib $(OPTLOCALLFLAGS) $(OPENSSLLFLAGS) + +ifeq ($(OS),osx) + CLDOSFLAGS=-bind_at_load + OSLIBS=-framework CoreServices -framework CoreFoundation +endif +ifeq ($(OS),linux) + OSLIBS=-lpthread -lrt -lcurses + DEFINES=-D_FILE_OFFSET_BITS=64 -D_LARGEFILE64_SOURCE +endif +ifeq ($(OS),bsd) + OSLIBS=-lpthread -lncurses -lkvm +endif + +ifeq ($(STATIC),yes) +LIBS=-lssl -lcrypto -lncurses /usr/local/lib/libsigsegv.a /usr/local/lib/libgmp.a $(CURLLIB) $(OSLIBS) +else +LIBS=-lssl -lcrypto -lgmp -lncurses -lsigsegv $(CURLLIB) $(OSLIBS) +endif + +INCLUDE=include +MDEFINES=-DU3_OS_$(OS) -DU3_OS_ENDIAN_$(ENDIAN) + +DEBUG=no + +ifeq ($(DEBUG),yes) +CFLAGS=-g +else +CFLAGS?=-O3 +endif + +LIBUV_VER=libuv-v1.7.5 + +LIBUV_CONFIGURE_OPTIONS=CC=$(CC) + +# NOTFORCHECKIN - restore -O3 +# -DGHETTO \ +# -DHUSH +CFLAGS+= $(COSFLAGS) -ffast-math \ + -funsigned-char \ + -I/usr/local/include \ + $(OPTLOCALIFLAGS) \ + $(OPENSSLIFLAGS) \ + $(CURLINC) \ + -I$(INCLUDE) \ + -Ioutside/$(LIBUV_VER)/include \ + -Ioutside/anachronism/include \ + -Ioutside/ed25519/src \ + -Ioutside/commonmark/src \ + -Ioutside/commonmark/build/src \ + -Ioutside/scrypt \ + -Ioutside/softfloat-3/source/include \ + -Ioutside/murmur3 \ + $(DEFINES) \ + $(MDEFINES) + +# TODO remove -Wno-* +CWFLAGS=-Wall \ + -Wextra \ + -Wno-sign-compare \ + -Wno-unused-parameter \ + -Wno-missing-field-initializers \ + -Wno-strict-aliasing \ + -Wno-error +ifneq ($(OS),bsd) + CWFLAGS+=-Wno-error=unused-result +endif + +# glibc 2.24 deprecates readdir_r; iff glibc >=2.24, +# don't upgrade 'deprecated declarations' warnings to errors +# dependency: `getconf`, which comes w/glibc +GLIBC := $(lastword $(shell getconf GNU_LIBC_VERSION 2>/dev/null)) +# dependency: none, uses make's native functions +GLIBC_MAJ := $(word 1, $(subst ., ,$(GLIBC))) +GLIBC_MIN := $(word 2, $(subst ., ,$(GLIBC))) +# dependency: `expr` shell built-in +GLIBC_GE_2_24 := $(shell expr $(GLIBC_MAJ) ">" 2 "|" \ + $(GLIBC_MAJ) "=" 2 "&" $(GLIBC_MIN) ">=" 24 2>/dev/null) +ifeq (1,$(GLIBC_GE_2_24)) + CWFLAGS+=-Wno-error=deprecated-declarations +endif + +ifdef NO_SILENT_RULES +%.o: %.c $(CORE) + $(CC) -c $(CWFLAGS) $(CFLAGS) -o $@ $< + @$(CC) -MM -MP $(CWFLAGS) $(CFLAGS) -MT $@ $< -MF .d/$*.d +else +%.o: %.c $(CORE) + @echo " CC $@" + @$(CC) -c $(CWFLAGS) $(CFLAGS) -o $@ $< + @$(CC) -MM -MP $(CWFLAGS) $(CFLAGS) -MT $@ $< -MF .d/$*.d +endif + +N_OFILES=\ + noun/allocate.o \ + noun/events.o \ + noun/hashtable.o \ + noun/imprison.o \ + noun/jets.o \ + noun/manage.o \ + noun/nock.o \ + noun/retrieve.o \ + noun/trace.o \ + noun/xtract.o \ + noun/vortex.o \ + noun/zave.o + +J_A_OFILES=\ + jets/a/add.o \ + jets/a/dec.o \ + jets/a/div.o \ + jets/a/gte.o \ + jets/a/gth.o \ + jets/a/lte.o \ + jets/a/lth.o \ + jets/a/mod.o \ + jets/a/mul.o \ + jets/a/sub.o + +J_B_OFILES=\ + jets/b/bind.o \ + jets/b/clap.o \ + jets/b/drop.o \ + jets/b/flop.o \ + jets/b/lent.o \ + jets/b/levy.o \ + jets/b/lien.o \ + jets/b/murn.o \ + jets/b/need.o \ + jets/b/reap.o \ + jets/b/reel.o \ + jets/b/roll.o \ + jets/b/skid.o \ + jets/b/skim.o \ + jets/b/skip.o \ + jets/b/scag.o \ + jets/b/slag.o \ + jets/b/snag.o \ + jets/b/sort.o \ + jets/b/turn.o \ + jets/b/weld.o + +J_C_OFILES=\ + jets/c/bex.o \ + jets/c/xeb.o \ + jets/c/can.o \ + jets/c/cap.o \ + jets/c/cat.o \ + jets/c/con.o \ + jets/c/cut.o \ + jets/c/dor.o \ + jets/c/dvr.o \ + jets/c/dis.o \ + jets/c/end.o \ + jets/c/gor.o \ + jets/c/hor.o \ + jets/c/lsh.o \ + jets/c/mas.o \ + jets/c/met.o \ + jets/c/mix.o \ + jets/c/mug.o \ + jets/c/muk.o \ + jets/c/peg.o \ + jets/c/po.o \ + jets/c/pow.o \ + jets/c/rap.o \ + jets/c/rep.o \ + jets/c/rip.o \ + jets/c/rsh.o \ + jets/c/sqt.o \ + jets/c/vor.o + +J_D_OFILES=\ + jets/d/in_has.o \ + jets/d/in_int.o \ + jets/d/in_gas.o \ + jets/d/in_mer.o \ + jets/d/in_put.o \ + jets/d/in_tap.o \ + jets/d/in_uni.o \ + jets/d/in_wyt.o \ + jets/d/in_bif.o \ + jets/d/in_dif.o \ + jets/d/by_gas.o \ + jets/d/by_get.o \ + jets/d/by_has.o \ + jets/d/by_int.o \ + jets/d/by_put.o \ + jets/d/by_uni.o \ + jets/d/by_bif.o \ + jets/d/by_dif.o + +J_E_OFILES=\ + jets/e/aes_ecb.o \ + jets/e/aes_cbc.o \ + jets/e/aesc.o \ + jets/e/cue.o \ + jets/e/fl.o \ + jets/e/jam.o \ + jets/e/mat.o \ + jets/e/mink.o \ + jets/e/mule.o \ + jets/e/parse.o \ + jets/e/rd.o \ + jets/e/rq.o \ + jets/e/rs.o \ + jets/e/rh.o \ + jets/e/rub.o \ + jets/e/scr.o \ + jets/e/shax.o \ + jets/e/lore.o \ + jets/e/loss.o \ + jets/e/lune.o \ + jets/e/trip.o + +J_E_OFILES_ED=\ + jets/e/ed_puck.o \ + jets/e/ed_sign.o \ + jets/e/ed_veri.o \ + jets/e/ed_shar.o + +J_F_OFILES=\ + jets/f/ap.o \ + jets/f/cell.o \ + jets/f/comb.o \ + jets/f/cons.o \ + jets/f/core.o \ + jets/f/face.o \ + jets/f/fitz.o \ + jets/f/flan.o \ + jets/f/flip.o \ + jets/f/flor.o \ + jets/f/fork.o \ + jets/f/help.o \ + jets/f/hike.o \ + jets/f/look.o \ + jets/f/loot.o + +J_F_OFILES_UT=\ + jets/f/ut.o \ + jets/f/ut_burn.o \ + jets/f/ut_buss.o \ + jets/f/ut_conk.o \ + jets/f/ut_crop.o \ + jets/f/ut_find.o \ + jets/f/ut_fire.o \ + jets/f/ut_fish.o \ + jets/f/ut_fuse.o \ + jets/f/ut_gain.o \ + jets/f/ut_lose.o \ + jets/f/ut_mint.o \ + jets/f/ut_mull.o \ + jets/f/ut_nest.o \ + jets/f/ut_peek.o \ + jets/f/ut_peel.o \ + jets/f/ut_play.o \ + jets/f/ut_repo.o \ + jets/f/ut_rest.o \ + jets/f/ut_tack.o \ + jets/f/ut_toss.o \ + jets/f/ut_wrap.o + +J_G_OFILES=\ + jets/g/down.o + +J_OFILES=\ + $(J_A_OFILES) \ + $(J_B_OFILES) \ + $(J_C_OFILES) \ + $(J_D_OFILES) \ + $(J_E_OFILES) \ + $(J_E_OFILES_ED) \ + $(J_F_OFILES) \ + $(J_F_OFILES_UT) \ + $(J_G_OFILES) \ + jets/tree.o + +BASE_OFILES=$(N_OFILES) $(J_OFILES) + +OUT_OFILES=\ + outside/jhttp/http_parser.o \ + outside/murmur3/MurmurHash3.o + +V_OFILES=\ + vere/ames.o \ + vere/behn.o \ + vere/cttp.o \ + vere/http.o \ + vere/loop.o \ + vere/raft.o \ + vere/reck.o \ + vere/sist.o \ + vere/term.o \ + vere/time.o \ + vere/unix.o \ + vere/save.o \ + vere/walk.o + +MAIN_FILE =\ + vere/main.o + +VERE_OFILES=\ + $(OUT_OFILES) \ + $(BASE_OFILES) \ + $(MAIN_FILE) \ + $(V_OFILES) + +VERE_DFILES=$(VERE_OFILES:%.o=.d/%.d) + +-include $(VERE_DFILES) + +TEST_HASH_MAIN_FILE =\ + tests/hashtable_tests.o + +TEST_HASH_OFILES=\ + $(OUT_OFILES) \ + $(BASE_OFILES) \ + $(TEST_HASH_MAIN_FILE) \ + $(V_OFILES) + +TEST_HASH_DFILES=$(TEST_HASH_OFILES:%.o=.d/%.d) + +-include $(TEST_HASH_DFILES) + +# This is a silly hack necessitated by the fact that libuv uses configure +# +# * Making 'all' obviously requires outside/libuv, +# which requires the libuv Makefile to be created. +# * Making distclean on outside/libuv destroys the makefile. +# * ...so configuring outside/libuv is parodoxically required +# in order to distclean it! +# * But what if developer types 'make distclean all' ? +# * first target makes libuv Makefile, then destroys it...and +# second target knows that it was made. +# * ...so second target borks. +# * Solution: make libuv not only depend on its own Makefile, +# but on a side effect of creating its own makefile. +# +LIBUV_MAKEFILE=outside/$(LIBUV_VER)/Makefile +LIBUV_MAKEFILE2=outside/$(LIBUV_VER)/config.log + +LIBUV=outside/$(LIBUV_VER)/.libs/libuv.a + +LIBED25519=outside/ed25519/ed25519.a + +LIBANACHRONISM=outside/anachronism/build/libanachronism.a + +LIBCOMMONMARK=outside/commonmark/build/src/libcmark.a + +LIBSCRYPT=outside/scrypt/scrypt.a + +LIBSOFTFLOAT=outside/softfloat-3/build/Linux-x86_64-GCC/softfloat.a + +TAGS=\ + .tags \ + .etags \ + GPATH GTAGS GRTAGS \ + cscope.in.out cscope.po.out cscope.out + +all: urbit + +.MAKEFILE-VERSION: Makefile .make.conf + @echo "Makefile update." + @touch .MAKEFILE-VERSION + +.make.conf: + @echo "# Set custom configuration here, please!" > ".make.conf" + +urbit: $(BIN)/urbit + +$(LIBUV_MAKEFILE) $(LIBUV_MAKEFILE2): + cd outside/$(LIBUV_VER) ; sh autogen.sh ; ./configure $(LIBUV_CONFIGURE_OPTIONS) + +# [h]act II: the plot thickens +# +# * Specifying two targets that each configure libuv works +# when the rules are executed sequentially, +# * but when attempting a parallel build, it is likely Make +# will try to configure libuv simultaneously. +# * We can specify a dependency between the two targets so +# that execution of their rule(s) is serialized. +# * Further, libuv does not seem to be friendly towards +# parallel builds either. A true fix is out of scope here +# * ...so we must instruct Make to only use one job when it +# attempts to build libuv. +# +$(LIBUV_MAKEFILE2): $(LIBUV_MAKEFILE) + +$(LIBUV): $(LIBUV_MAKEFILE) $(LIBUV_MAKEFILE2) + $(MAKE) -C outside/$(LIBUV_VER) all-am -j1 + +$(LIBED25519): + $(MAKE) -C outside/ed25519 + +$(LIBANACHRONISM): + $(MAKE) -C outside/anachronism static + +$(LIBCOMMONMARK): + $(MAKE) -C outside/commonmark + +$(LIBSCRYPT): + $(MAKE) -C outside/scrypt MDEFINES="$(MDEFINES)" + +$(LIBSOFTFLOAT): + $(MAKE) -C outside/softfloat-3/build/Linux-x86_64-GCC + +$(V_OFILES): include/vere/vere.h + +ifdef NO_SILENT_RULES +$(BIN)/urbit: $(LIBCOMMONMARK) $(VERE_OFILES) $(LIBUV) $(LIBED25519) $(LIBANACHRONISM) $(LIBSCRYPT) $(LIBSOFTFLOAT) + mkdir -p $(BIN) + $(CLD) $(CLDOSFLAGS) -o $(BIN)/urbit $(VERE_OFILES) $(LIBUV) $(LIBED25519) $(LIBANACHRONISM) $(LIBS) $(LIBCOMMONMARK) $(LIBSCRYPT) $(LIBSOFTFLOAT) +else +$(BIN)/urbit: $(LIBCOMMONMARK) $(VERE_OFILES) $(LIBUV) $(LIBED25519) $(LIBANACHRONISM) $(LIBSCRYPT) $(LIBSOFTFLOAT) + @echo " CCLD $(BIN)/urbit" + @mkdir -p $(BIN) + @$(CLD) $(CLDOSFLAGS) -o $(BIN)/urbit $(VERE_OFILES) $(LIBUV) $(LIBED25519) $(LIBANACHRONISM) $(LIBS) $(LIBCOMMONMARK) $(LIBSCRYPT) $(LIBSOFTFLOAT) +endif + +# This should start a comet or something +test: + @echo "FIXME no tests defined" + +test_hash: $(BIN)/test_hash + +ifdef NO_SILENT_RULES +$(BIN)/test_hash: $(LIBCOMMONMARK) $(TEST_HASH_OFILES) $(LIBUV) $(LIBED25519) $(LIBANACHRONISM) $(LIBSCRYPT) $(LIBSOFTFLOAT) + mkdir -p $(BIN) + $(CLD) $(CLDOSFLAGS) -o $(BIN)/test_hash $(TEST_HASH_OFILES) $(LIBUV) $(LIBED25519) $(LIBANACHRONISM) $(LIBS) $(LIBCOMMONMARK) $(LIBSCRYPT) $(LIBSOFTFLOAT) +else +$(BIN)/test_hash: $(LIBCOMMONMARK) $(TEST_HASH_OFILES) $(LIBUV) $(LIBED25519) $(LIBANACHRONISM) $(LIBSCRYPT) $(LIBSOFTFLOAT) + @echo "VERE_DFILES=$(VERE_DFILES)" + @echo " CCLD $(BIN)/test_hash" + @mkdir -p $(BIN) + @$(CLD) $(CLDOSFLAGS) -o $(BIN)/test_hash $(TEST_HASH_OFILES) $(LIBUV) $(LIBED25519) $(LIBANACHRONISM) $(LIBS) $(LIBCOMMONMARK) $(LIBSCRYPT) $(LIBSOFTFLOAT) +endif + +tags: ctags etags gtags cscope + +ctags: + @ctags -R -f .tags --exclude=root || true + +etags: + @etags -f .etags $$(find . -name '*.c' -or -name '*.h') || true + +gtags: + @gtags || true + +cscope: + @cscope -b -q -R || true + +osxpackage: + $(RM) -r inst + $(MAKE) distclean + $(MAKE) $(BIN)/urbit LIB=/usr/local/lib/urb STATIC=yes + mkdir -p inst/usr/local/lib/urb inst/usr/local/bin + cp $(BIN)/urbit inst/usr/local/bin + cp urb/urbit.pill inst/usr/local/lib/urb + pkgbuild --root inst --identifier org.urbit.urbit --version 0.2 urbit.pkg + +debbuild: + $(MAKE) $(BIN)/urbit LIB=/usr/share/urb + +debinstall: + mkdir -p $(DESTDIR)/usr/bin $(DESTDIR)/usr/share/urb + install -m755 $(BIN)/urbit $(DESTDIR)/usr/bin + cp urb/urbit.pill $(DESTDIR)/usr/share/urb + +clean: + $(RM) $(VERE_OFILES) $(BIN)/urbit urbit.pkg $(VERE_DFILES) $(TAGS) + $(RM) -r debian/files debian/urbit* + +# 'make distclean all -jn' ∀ n>1 still does not work because it is possible +# Make will attempt to build urbit while it is also cleaning urbit.. +distclean: clean $(LIBUV_MAKEFILE) + $(MAKE) -C outside/$(LIBUV_VER) distclean + $(MAKE) -C outside/ed25519 clean + $(MAKE) -C outside/anachronism clean + $(MAKE) -C outside/scrypt clean + $(MAKE) -C outside/softfloat-3/build/Linux-x86_64-GCC clean + +.PHONY: clean debbuild debinstalldistclean etags osxpackage tags test diff --git a/README.md b/README.md index 0fd49ed84..c1ed536da 100644 --- a/README.md +++ b/README.md @@ -16,13 +16,15 @@ If you're doing development on Urbit, keep reading. `vere`, the Urbit virtual machine, depends on the following: - C compiler ([gcc](https://gcc.gnu.org) or [clang](http://clang.llvm.org)) -- [Meson](http://mesonbuild.com/) +- [GNU Make](https://www.gnu.org/software/make/) - [GMP](https://gmplib.org) +- [CMake](https://cmake.org) +- automake, autoconf, and libtool - [OpenSSL](https://www.openssl.org) - [libsigsegv](https://www.gnu.org/software/libsigsegv/) - [libcurl](https://curl.haxx.se/libcurl/) -- [libuv](http://libuv.org) - curses implementation (ncurses on Linux distributions, OS curses otherwise) +- [Ragel](https://www.colm.net/open-source/ragel/) - [re2c](http://re2c.org) Most of these dependencies are unfortunate; we aim to drastically shrink the @@ -32,52 +34,16 @@ for future unbundling or removal wherever possible. ## Building -Urbit uses Meson build system. +Our Makefile should handle the build smoothly on all supported platforms. It's +just a simple Makefile, written by hand for GNU Make, and the most complicated +parts of its internal machinery have to do with the varied build systems of the +bundled libraries. -Some libraries which are not found in major distributions: -- ed25519 -- http-parser legacy version 0.1.0 -- murmur3 -- softfloat3 -- urbit-scrypt -- commonmark legacy version 0.12.0 +Useful targets are the default `all`, `clean`, and `distclean`. The last may not +produce an entirely clean distribution directory, and runs a bundled library's +configure script; `git clean` may be a better option. -are included as git submodules. To build urbit from source, perform the following steps: - -## MacOS specifics -On macos, you need to make sure `pkg-config` uses the correct homebrew path. - The `export PKG_CONFIG_PATH=/usr/local/opt/openssl/lib/pkgconfig/:$PKG_CONFIG_PATH` - should setup the `pkg-config` path correctly, solving errors with homebrew package discovery (notably with `openssl` paths). - -## Configuration & compilation -(For instructions for legacy meson, also see below) - -1. Install all required dependencies. -2. `git submodule init` in the urbit repository -3. `git submodule update` -4. `meson ./build` -5. If the last step was successful, type `cd ./build` followed by `ninja` - to compile urbit. -6. The executable should appear in `./build` directory. - -### Using meson & ninja -To configure project, enter the build directory and enter -`meson configure`. Without any arguments this command will display available -options. For example, to compile debug build of urbit, use -`meson configure -Ddebug=true`. -To set the prefix for installation use -`meson configure -Dprefix=/usr`, and so on. - -## Configuration & compilation for legacy meson - -The syntax for legacy meson (Version `0.29`) is a bit different. -1. Manually create `build` directory and invoke meson as `meson . ./build` -2. If you want to set options, this is done in one step. - Use `meson -D [options] . ./build` to prepare customized build. - -Once the project is configured, use `ninja` to build it. -To install it into the default prefix, use `ninja install`. -If you want to specify custom `DESTDIR`, use `DESTDIR=... ninja install`. +The `vere` binary is produced in `bin/urbit`. ## Building the Debian Package diff --git a/debian/control b/debian/control index 430ff65e3..ee8711ca0 100644 --- a/debian/control +++ b/debian/control @@ -2,7 +2,7 @@ Source: urbit Section: net Priority: extra Maintainer: Ted Blackman -Build-Depends: debhelper (>= 9), libgmp3-dev, libsigsegv-dev, openssl, libssl-dev,libtool, meson, re2c, libcurl4-gnutls-dev +Build-Depends: debhelper (>= 9), libgmp3-dev, libsigsegv-dev, openssl, libssl-dev, automake, autoconf, libtool, g++, ragel, cmake, re2c, libcurl4-gnutls-dev Standards-Version: 3.9.5 Homepage: http://urbit.org diff --git a/include/all.h b/include/all.h index 4f77916b1..ed8608967 100644 --- a/include/all.h +++ b/include/all.h @@ -2,7 +2,7 @@ ** ** This file is in the public domain. */ -# include "config.h" +# include "version.h" /** c3: C environment. **/ # include "c/portable.h" // C and OS portability @@ -12,7 +12,7 @@ /** u3: noun environment. **/ -# include "noun/aliases.h" // general u3 +# include "noun/aliases.h" // general u3 # include "noun/allocate.h" // u3a: allocation # include "noun/events.h" // u3e: persistence @@ -24,7 +24,7 @@ # include "noun/options.h" // u3o: config options # include "noun/retrieve.h" // u3r: noun access (error returns) # include "noun/trace.h" // u3t: profiling / tracing -# include "noun/xtract.h" // u3x: noun access (error crashes) +# include "noun/xtract.h" // u3x: noun access (error crashes) # include "noun/vortex.h" // u3v: arvo kernel # include "noun/zave.h" // u3z: memoization @@ -52,3 +52,4 @@ */ # define uH u3_term_io_hija() # define uL(x) u3_term_io_loja(x) + diff --git a/include/c/portable.h b/include/c/portable.h index 0ef146026..7ed2db664 100644 --- a/include/c/portable.h +++ b/include/c/portable.h @@ -4,15 +4,10 @@ */ /** Must be compiled on gcc with C99 support. **/ - -#include "config.h" - # ifndef __GNUC__ # error "port me" # endif -# ifndef _GNU_SOURCE # define _GNU_SOURCE -# endif /** System include files. diff --git a/include/config.h.in b/include/config.h.in deleted file mode 100644 index 66f778309..000000000 --- a/include/config.h.in +++ /dev/null @@ -1,13 +0,0 @@ -#ifndef CONFIG_H -#define CONFIG_H - -#mesondefine URBIT_VERSION - -#mesondefine U3_OS_linux -#mesondefine U3_OS_bsd -#mesondefine U3_OS_osx - -#mesondefine U3_OS_ENDIAN_little -#mesondefine U3_OS_ENDIAN_big - -#endif /*CONFIG_H*/ diff --git a/include/version.h b/include/version.h new file mode 100644 index 000000000..8fcd4d24b --- /dev/null +++ b/include/version.h @@ -0,0 +1 @@ +#define URBIT_VERSION "0.5.1" diff --git a/jets/c/muk.c b/jets/c/muk.c index 6d71aed1c..60e0a6b41 100644 --- a/jets/c/muk.c +++ b/jets/c/muk.c @@ -2,7 +2,7 @@ ** */ #include "all.h" -#include +#include /* functions */ diff --git a/jets/e/rd.c b/jets/e/rd.c index d4b44f778..04281147b 100644 --- a/jets/e/rd.c +++ b/jets/e/rd.c @@ -2,7 +2,7 @@ ** */ #include "all.h" -#include +#include "softfloat.h" #define DOUBNAN 0x7ff8000000000000 diff --git a/jets/e/rh.c b/jets/e/rh.c index 472de374a..96c2f2f74 100644 --- a/jets/e/rh.c +++ b/jets/e/rh.c @@ -2,7 +2,7 @@ ** */ #include "all.h" -#include +#include "softfloat.h" #define HALFNAN 0x7e00 @@ -151,8 +151,8 @@ /* div */ u3_noun - u3qes_div(u3_atom a, - u3_atom b, + u3qes_div(u3_atom a, + u3_atom b, u3_atom r) { union half c, d, e; @@ -183,7 +183,7 @@ /* sqt */ u3_noun - u3qes_sqt(u3_atom a, + u3qes_sqt(u3_atom a, u3_atom r) { union half c, d; diff --git a/jets/e/rq.c b/jets/e/rq.c index 19af6acc6..f4c506c9a 100644 --- a/jets/e/rq.c +++ b/jets/e/rq.c @@ -2,7 +2,7 @@ ** */ #include "all.h" -#include +#include "softfloat.h" #define QUADNAN 0x7fff800000000000 diff --git a/jets/e/rs.c b/jets/e/rs.c index b0f2a4574..d4ee29d7e 100644 --- a/jets/e/rs.c +++ b/jets/e/rs.c @@ -2,7 +2,7 @@ ** */ #include "all.h" -#include +#include "softfloat.h" #define SINGNAN 0x7fc00000 @@ -151,8 +151,8 @@ /* div */ u3_noun - u3qet_div(u3_atom a, - u3_atom b, + u3qet_div(u3_atom a, + u3_atom b, u3_atom r) { union sing c, d, e; @@ -183,7 +183,7 @@ /* sqt */ u3_noun - u3qet_sqt(u3_atom a, + u3qet_sqt(u3_atom a, u3_atom r) { union sing c, d; diff --git a/jets/e/scr.c b/jets/e/scr.c index 69d1cdecd..9111f8a6c 100644 --- a/jets/e/scr.c +++ b/jets/e/scr.c @@ -5,9 +5,7 @@ #include #include - -#include -#include +#include static int _crypto_scrypt(const uint8_t *, size_t, const uint8_t *, size_t, uint64_t, uint32_t, uint32_t, uint8_t *, size_t); @@ -19,8 +17,8 @@ static int _crypto_scrypt(const uint8_t *, size_t, const uint8_t *, size_t, u3qes_hsl(u3_atom p, u3_atom pl, u3_atom s, u3_atom sl, u3_atom n, - u3_atom r, - u3_atom z, + u3_atom r, + u3_atom z, u3_atom d) { // asserting that n is power of 2 in _crypto_scrypt @@ -32,7 +30,7 @@ static int _crypto_scrypt(const uint8_t *, size_t, const uint8_t *, size_t, (((c3_d)r * 128 * ((c3_d)n + z - 1)) <= (1 << 30)))) return u3m_bail(c3__exit); - c3_y* b_p = u3a_malloc(pl + 1); c3_y* b_s= u3a_malloc(sl + 1); + c3_y* b_p = u3a_malloc(pl + 1); c3_y* b_s= u3a_malloc(sl + 1); u3r_bytes(0, pl, b_p, p); u3r_bytes(0, sl, b_s, s); b_p[pl] = 0; b_s[sl]=0; c3_y* buf = u3a_malloc(d); @@ -75,7 +73,7 @@ static int _crypto_scrypt(const uint8_t *, size_t, const uint8_t *, size_t, return u3m_bail(c3__exit); c3_w pl = u3r_met(3, p); c3_w sl = u3r_met(3, s); - c3_y* b_p = u3a_malloc(pl + 1); c3_y* b_s= u3a_malloc(sl + 1); + c3_y* b_p = u3a_malloc(pl + 1); c3_y* b_s= u3a_malloc(sl + 1); u3r_bytes(0, pl, b_p, p); u3r_bytes(0, sl, b_s, s); b_p[pl] = 0; b_s[sl]=0; c3_y* buf = u3a_malloc(d); @@ -114,12 +112,12 @@ static int _crypto_scrypt(const uint8_t *, size_t, const uint8_t *, size_t, (c != 0))) return u3m_bail(c3__exit); - c3_y* b_p = u3a_malloc(pl + 1); c3_y* b_s= u3a_malloc(pl + 1); + c3_y* b_p = u3a_malloc(pl + 1); c3_y* b_s= u3a_malloc(pl + 1); u3r_bytes(0, pl, b_p, p); u3r_bytes(0, sl, b_s, s); b_p[pl] = 0; b_s[sl]=0; c3_y* buf = u3a_malloc(d); - libscrypt_PBKDF2_SHA256(b_p, pl, b_s, sl, c, buf, d); + PBKDF2_SHA256(b_p, pl, b_s, sl, c, buf, d); u3_noun res = u3i_bytes(d, buf); u3a_free(b_p); u3a_free(b_s); u3a_free(buf); @@ -149,12 +147,12 @@ static int _crypto_scrypt(const uint8_t *, size_t, const uint8_t *, size_t, return u3m_bail(c3__exit); c3_w pl = u3r_met(3, p); c3_w sl = u3r_met(3, s); - c3_y* b_p = u3a_malloc(pl + 1); c3_y* b_s= u3a_malloc(pl + 1); + c3_y* b_p = u3a_malloc(pl + 1); c3_y* b_s= u3a_malloc(pl + 1); u3r_bytes(0, pl, b_p, p); u3r_bytes(0, sl, b_s, s); b_p[pl] = 0; b_s[sl]=0; c3_y* buf = u3a_malloc(d); - libscrypt_PBKDF2_SHA256(b_p, pl, b_s, sl, c, buf, d); + PBKDF2_SHA256(b_p, pl, b_s, sl, c, buf, d); u3_noun res = u3i_bytes(d, buf); u3a_free(b_p); u3a_free(b_s); u3a_free(buf); @@ -172,6 +170,35 @@ static int _crypto_scrypt(const uint8_t *, size_t, const uint8_t *, size_t, return u3qes_pbk(p, s, c, d); } +/*- + * Copyright 2009 Colin Percival + * 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 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 AUTHOR 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. + * + * This file was originally written by Colin Percival as part of the Tarsnap + * online backup system. + */ + /** * crypto_scrypt(passwd, passwdlen, salt, saltlen, N, r, p, buf, buflen): * Compute scrypt(passwd[0 .. passwdlen - 1], salt[0 .. saltlen - 1], N, r, @@ -186,5 +213,77 @@ _crypto_scrypt(const uint8_t * passwd, size_t passwdlen, const uint8_t * salt, size_t saltlen, uint64_t N, uint32_t r, uint32_t p, uint8_t * buf, size_t buflen) { - return libscrypt_scrypt(passwd, passwdlen, salt, saltlen, N, r, p, buf, buflen); + void * B0, * V0, * XY0; + uint8_t * B; + uint32_t * V; + uint32_t * XY; + uint32_t i; + + if (((N & (N-1)) != 0) || N == 0) + goto err0; + + /* Sanity-check parameters. */ +#if SIZE_MAX > UINT32_MAX + if (buflen > (((uint64_t)(1) << 32) - 1) * 32) { + errno = EFBIG; + goto err0; + } +#endif + if ((uint64_t)(r) * (uint64_t)(p) >= (1 << 30)) { + errno = EFBIG; + goto err0; + } + if (((N & (N - 1)) != 0) || (N == 0)) { + errno = EINVAL; + goto err0; + } + int test_size_max = (r > SIZE_MAX / 128 / p) || (N > SIZE_MAX / 128 / r); + +#if SIZE_MAX / 256 <= UINT32_MAX + test_size_max = (r > (SIZE_MAX - 64) / 256) || test_size_max; +#endif + if(test_size_max) { + errno = ENOMEM; + goto err0; + } + + /* Allocate memory. */ + if ((B0 = u3a_malloc(128 * r * p + 63)) == NULL) + goto err0; + B = (uint8_t *)(((uintptr_t)(B0) + 63) & ~ (uintptr_t)(63)); + if ((XY0 = u3a_malloc(256 * r + 64 + 63)) == NULL) + goto err1; + XY = (uint32_t *)(((uintptr_t)(XY0) + 63) & ~ (uintptr_t)(63)); + if ((V0 = u3a_malloc(128 * r * N + 63)) == NULL) + goto err2; + V = (uint32_t *)(((uintptr_t)(V0) + 63) & ~ (uintptr_t)(63)); + + /* 1: (B_0 ... B_{p-1}) <-- PBKDF2(P, S, 1, p * MFLen) */ + PBKDF2_SHA256(passwd, passwdlen, salt, saltlen, 1, B, p * 128 * r); + + /* 2: for i = 0 to p - 1 do */ + for (i = 0; i < p; i++) { + /* 3: B_i <-- MF(B_i, N) */ + smix(&B[i * 128 * r], r, N, V, XY); + } + + /* 5: DK <-- PBKDF2(P, B, 1, dkLen) */ + PBKDF2_SHA256(passwd, passwdlen, B, p * 128 * r, 1, buf, buflen); + + /* Free memory. */ + + u3a_free(V0); + u3a_free(XY0); + u3a_free(B0); + + /* Success! */ + return (0); + +err2: + u3a_free(XY0); +err1: + u3a_free(B0); +err0: + /* Failure! */ + return (-1); } diff --git a/meson.build b/meson.build deleted file mode 100644 index 4f8011b23..000000000 --- a/meson.build +++ /dev/null @@ -1,326 +0,0 @@ -project('urbit', 'c', meson_version: '>=0.29.0') - -legacy_meson = false - -detect_meson_version = run_command('meson', '--version') -meson_ver = detect_meson_version.stdout() - -if(meson_ver == '0.29.0\n') - legacy_meson = true -elif(not meson.version().version_compare('>=0.40.0')) - error('Meson 0.29.0 is last legacy version supported. Otherwise please upgrade to 0.40.0 or higher.') -endif - -jets_a_src = [ -'jets/a/add.c', -'jets/a/dec.c', -'jets/a/div.c', -'jets/a/gte.c', -'jets/a/gth.c', -'jets/a/lte.c', -'jets/a/lth.c', -'jets/a/mod.c', -'jets/a/mul.c', -'jets/a/sub.c',] - -jets_b_src = [ -'jets/b/bind.c', -'jets/b/clap.c', -'jets/b/drop.c', -'jets/b/flop.c', -'jets/b/lent.c', -'jets/b/levy.c', -'jets/b/lien.c', -'jets/b/murn.c', -'jets/b/need.c', -'jets/b/reap.c', -'jets/b/reel.c', -'jets/b/roll.c', -'jets/b/skid.c', -'jets/b/skim.c', -'jets/b/skip.c', -'jets/b/scag.c', -'jets/b/slag.c', -'jets/b/snag.c', -'jets/b/sort.c', -'jets/b/turn.c', - 'jets/b/weld.c' ] - -jets_c_src = [ -'jets/c/bex.c', -'jets/c/xeb.c', -'jets/c/can.c', -'jets/c/cap.c', -'jets/c/cat.c', -'jets/c/con.c', -'jets/c/cut.c', -'jets/c/dor.c', -'jets/c/dvr.c', -'jets/c/dis.c', -'jets/c/end.c', -'jets/c/gor.c', -'jets/c/hor.c', -'jets/c/lsh.c', -'jets/c/mas.c', -'jets/c/met.c', -'jets/c/mix.c', -'jets/c/mug.c', -'jets/c/muk.c', -'jets/c/peg.c', -'jets/c/po.c', -'jets/c/pow.c', -'jets/c/rap.c', -'jets/c/rep.c', -'jets/c/rip.c', -'jets/c/rsh.c', -'jets/c/sqt.c', -'jets/c/vor.c', -] - -jets_d_src = [ -'jets/d/in_has.c', -'jets/d/in_int.c', -'jets/d/in_gas.c', -'jets/d/in_mer.c', -'jets/d/in_put.c', -'jets/d/in_tap.c', -'jets/d/in_uni.c', -'jets/d/in_wyt.c', -'jets/d/in_bif.c', -'jets/d/in_dif.c', -'jets/d/by_gas.c', -'jets/d/by_get.c', -'jets/d/by_has.c', -'jets/d/by_int.c', -'jets/d/by_put.c', -'jets/d/by_uni.c', -'jets/d/by_bif.c', -'jets/d/by_dif.c' -] - -jets_e_src = [ -'jets/e/aes_ecb.c', -'jets/e/aes_cbc.c', -'jets/e/aesc.c', -'jets/e/cue.c', -'jets/e/fl.c', -'jets/e/jam.c', -'jets/e/mat.c', -'jets/e/mink.c', -'jets/e/mule.c', -'jets/e/parse.c', -'jets/e/rd.c', -'jets/e/rq.c', -'jets/e/rs.c', -'jets/e/rh.c', -'jets/e/rub.c', -'jets/e/scr.c', -'jets/e/shax.c', -'jets/e/lore.c', -'jets/e/loss.c', -'jets/e/lune.c', -'jets/e/trip.c' -] - -jets_e_ed_src = [ -'jets/e/ed_puck.c', -'jets/e/ed_sign.c', -'jets/e/ed_veri.c', -'jets/e/ed_shar.c' - -] -jets_f_src = [ -'jets/f/ap.c', -'jets/f/cell.c', -'jets/f/comb.c', -'jets/f/cons.c', -'jets/f/core.c', -'jets/f/face.c', -'jets/f/fitz.c', -'jets/f/flan.c', -'jets/f/flip.c', -'jets/f/flor.c', -'jets/f/fork.c', -'jets/f/help.c', -'jets/f/hike.c', -'jets/f/look.c', -'jets/f/loot.c' -] - -jets_f_ut_src = [ -'jets/f/ut.c', -'jets/f/ut_burn.c', -'jets/f/ut_buss.c', -'jets/f/ut_conk.c', -'jets/f/ut_crop.c', -'jets/f/ut_find.c', -'jets/f/ut_fire.c', -'jets/f/ut_fish.c', -'jets/f/ut_fuse.c', -'jets/f/ut_gain.c', -'jets/f/ut_lose.c', -'jets/f/ut_mint.c', -'jets/f/ut_mull.c', -'jets/f/ut_nest.c', -'jets/f/ut_peek.c', -'jets/f/ut_peel.c', -'jets/f/ut_play.c', -'jets/f/ut_repo.c', -'jets/f/ut_rest.c', -'jets/f/ut_tack.c', -'jets/f/ut_toss.c', -'jets/f/ut_wrap.c' -] - -jets_g_src = [ -'jets/g/down.c' -] - -jets_src = [ -'jets/tree.c' -] -noun_src = ['noun/allocate.c', - 'noun/events.c', - 'noun/hashtable.c', - 'noun/imprison.c', - 'noun/jets.c', - 'noun/manage.c', - 'noun/nock.c', - 'noun/retrieve.c', - 'noun/trace.c', - 'noun/vortex.c', - 'noun/xtract.c', - 'noun/zave.c'] - -vere_src = ['vere/ames.c', - 'vere/behn.c', - 'vere/cttp.c', - 'vere/http.c', - 'vere/loop.c', - 'vere/main.c', - 'vere/raft.c', - 'vere/reck.c', - 'vere/save.c', - 'vere/sist.c', - 'vere/term.c', - 'vere/time.c', - 'vere/unix.c', - 'vere/walk.c'] - -src_list = [ -vere_src, noun_src, -jets_a_src, jets_b_src, -jets_c_src, jets_d_src, -jets_e_src, jets_e_ed_src, jets_f_src, jets_f_ut_src, -jets_g_src, jets_src] - -sources = [] -foreach s : src_list - sources += s -endforeach - -incdir = include_directories('include/') - -conf_data = configuration_data() -conf_data.set('URBIT_VERSION', '"0.5.1"') - -osdet = build_machine.system() -os_c_flags = [] -os_deps = [] -os_link_flags = [] - -if osdet == 'linux' - conf_data.set('U3_OS_linux', true) - - if(legacy_meson) - pthread_dep = find_library('pthread') - else - pthread_dep = meson.get_compiler('c').find_library('pthread') - endif - - ncurses_dep = dependency('ncurses') - os_deps = os_deps + [pthread_dep, ncurses_dep] - -elif osdet == 'darwin' - conf_data.set('U3_OS_osx', true) - - os_c_flags = os_c_flags + ['-bind_at_load'] - # os_link_flags = ['-framework CoreServices', '-framework CoreFoundation'] - if(legacy_meson) - ncurses_dep = find_library('ncurses') - else - ncurses_dep = meson.get_compiler('c').find_library('ncurses') - endif - - os_deps = os_deps + [ncurses_dep] - -elif osdet == 'bsd' - conf_data.set('U3_OS_bsd', true) - - pthread_dep = meson.get_compiler('c').find_library('pthread') - kvm_dep = meson.get_compiler('c').find_library('kvm') - ncurses_dep = dependency('ncurses') - os_deps = os_deps + [kvm_dep, pthread_dep, ncurses_dep] -else - error('Unsupported OS detected:' + osdet) -endif - -endian = build_machine.endian() - -if endian == 'little' - conf_data.set('U3_OS_ENDIAN_little', true) -else - error('Little endian encoding required') -endif - -configure_file(input : 'include/config.h.in', - output : 'config.h', - configuration : conf_data) - -# We expect these libs to supplied with the distribution -openssl_dep = dependency('openssl', version: '>=1.0.0') -curl_dep = dependency('libcurl', version: '>=7.35.0') -libuv_dep = dependency('libuv', version: '>=1.8.0') - -if(legacy_meson) - gmp_dep = find_library('gmp') - sigsegv_dep = find_library('sigsegv') -else - gmp_dep = meson.get_compiler('c').find_library('gmp') - sigsegv_dep = meson.get_compiler('c').find_library('sigsegv') -endif - -# For these libs we provide fallback bundle -cmark_dep = dependency('libcmark', version: '0.12.0', fallback: ['commonmark-legacy', 'cmark_dep']) -urbitscrypt_dep = dependency('libscrypt', version: '>=0.1.21', fallback: ['libscrypt', 'libscrypt_dep']) - -ed25519_dep = dependency('ed25519', version: '>=0.1.0', fallback: ['ed25519', 'ed25519_dep']) -murmur3_dep = dependency('murmur3', version: '>=0.1.0', fallback: ['murmur3', 'murmur3_dep']) -http_parser_dep = dependency('http-parser', version: '0.1.0', fallback: ['http-parser-legacy', 'http_parser_dep']) -softfloat3_dep = dependency('softfloat3', version: '>=3.0.0', fallback: ['softfloat3', 'softfloat3_dep']) - - -opt_flags = [] -if get_option('debug') - opt_flags = ['-g'] -else - opt_flags = ['-O3'] -endif - -executable('urbit', -sources : sources, -include_directories : incdir, -c_args : opt_flags + os_c_flags, -link_args: os_link_flags, -dependencies: [openssl_dep, - curl_dep, - libuv_dep, - cmark_dep, - gmp_dep, - sigsegv_dep, - urbitscrypt_dep, - ed25519_dep, - murmur3_dep, - http_parser_dep, - softfloat3_dep] + os_deps, -install: true) diff --git a/meson_options.txt b/meson_options.txt deleted file mode 100644 index 378902f8d..000000000 --- a/meson_options.txt +++ /dev/null @@ -1 +0,0 @@ -option('debug', type:'boolean', value: false) diff --git a/outside/anachronism/.gitignore b/outside/anachronism/.gitignore new file mode 100644 index 000000000..567609b12 --- /dev/null +++ b/outside/anachronism/.gitignore @@ -0,0 +1 @@ +build/ diff --git a/outside/anachronism/LICENSE b/outside/anachronism/LICENSE new file mode 100644 index 000000000..72d4b706e --- /dev/null +++ b/outside/anachronism/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2010 Jonathan Castello + +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. diff --git a/outside/anachronism/Makefile b/outside/anachronism/Makefile new file mode 100644 index 000000000..123576f13 --- /dev/null +++ b/outside/anachronism/Makefile @@ -0,0 +1,70 @@ +SHELL = sh +UNAME = $(shell uname) + +ifneq ($(UNAME),FreeBSD) +CC = gcc +else +CC = cc +endif +FLAGS = -c -fPIC -Iinclude/ +CFLAGS = --pedantic -Wall -Wextra -march=native -std=gnu99 +INCLUDE = include/anachronism + +VERSION_MAJOR = 0 +VERSION = $(VERSION_MAJOR).3.1 + +SO = libanachronism.so +SOFILE = $(SO).$(VERSION) +SONAME = $(SO).$(VERSION_MAJOR) + + +all: static shared +shared: build/ build/$(SOFILE) +static: build/ build/libanachronism.a + +build/: + mkdir build + +build/$(SOFILE): build/nvt.o build/parser.o + $(CC) -shared -Wl,-soname,$(SONAME) -o build/$(SOFILE) build/nvt.o build/parser.o + +build/libanachronism.a: build/nvt.o build/parser.o + ar rcs build/libanachronism.a build/nvt.o build/parser.o + +build/nvt.o: src/nvt.c $(INCLUDE)/nvt.h $(INCLUDE)/common.h + $(CC) $(FLAGS) $(CFLAGS) src/nvt.c -o build/nvt.o + +build/parser.o: src/parser.c $(INCLUDE)/parser.h $(INCLUDE)/common.h + $(CC) $(FLAGS) $(CFLAGS) src/parser.c -o build/parser.o + +src/parser.c: src/parser.rl src/parser_common.rl + ragel -C -G2 src/parser.rl -o src/parser.c + + +graph: doc/parser.png + +doc/parser.png: src/parser.rl src/parser_common.rl + ragel -V -p src/parser.rl | dot -Tpng > doc/parser.png + +install: all + install -D -d /usr/local/include/anachronism/ /usr/local/lib + install -D include/anachronism/* /usr/local/include/anachronism/ + install -D build/$(SOFILE) /usr/local/lib/$(SOFILE) + install -D build/libanachronism.a /usr/local/lib/libanachronism.a + ln -s -f /usr/local/lib/$(SOFILE) /usr/local/lib/$(SONAME) + ln -s -f /usr/local/lib/$(SOFILE) /usr/local/lib/$(SO) + +uninstall: + -rm -rf /usr/local/include/anachronism + -rm /usr/local/lib/libanachronism.a + -rm /usr/local/lib/$(SOFILE) + -rm /usr/local/lib/$(SONAME) + -rm /usr/local/lib/$(SO) + +clean: + -rm -f build/nvt.o build/router.o build/parser.o + +distclean: clean + -rm -f build/libanachronism.a build/$(SOFILE) + +.PHONY: all static shared clean distclean install uninstall diff --git a/outside/anachronism/README.md b/outside/anachronism/README.md new file mode 100644 index 000000000..894825e04 --- /dev/null +++ b/outside/anachronism/README.md @@ -0,0 +1,158 @@ +# Anachronism +Anachronism is a fully-compliant implementation of [the Telnet protocol][wiki-telnet]. Fallen +out of favor in this day and age, most people only know it as a command-line +tool for debugging HTTP. Today, Telnet is most commonly used in the realm of +[MUDs][wiki-muds], though there are still a few other niches filled by Telnet. + +Anachronism offers a simple API for translating between streams of data and +events, and is completely network-agnostic. Anachronism also offers **channels**, an +abstraction layer which treats Telnet as a data multiplexer. Channels make it +extremely easy to build reusable modules for Telnet sub-protocols such +as MCCP (MUD Client Compression Protocol), which can be written once and plugged +into any application that wants to include support. + +[wiki-telnet]: http://en.wikipedia.org/wiki/Telnet (Telnet at Wikipedia) +[wiki-muds]: http://en.wikipedia.org/wiki/MUD (MUDs at Wikipedia) + +## Installation +While Anachronism has no dependencies and is theoretically cross-platform, I've +only written a Makefile for Linux. Help would be appreciated for making this +work across more platforms. + + make + sudo make install + +This will install Anachronism's shared and static libraries to /usr/local/lib, +and its header files to /usr/local/include/anachronism/. You may also need to +run `ldconfig` to make Anachronism available to your project's compiler/linker. + +## Usage +The anachronism/nvt.h header can be consulted for more complete documentation. + +### Basic usage +The core type exposed by Anachronism is the telnet\_nvt, which represents the +Telnet RFC's "Network Virtual Terminal". An NVT is created using +telnet\_nvt\_new(). When creating an NVT, you must provide it with a set of +callbacks to send events to, and an optional void\* to store as the event +handler's context. You can use telnet\_recv() to process incoming data, and +the telnet\_send\_\*() set of functions to emit outgoing data. + + #include + #include + + void on_event(telnet_nvt* nvt, telnet_event* event) + { + switch (event->type) + { + // A data event (normal text received) + case TELNET_EV_DATA: + { + telnet_data_event* ev = (telnet_data_event*)event; + printf("[IN]: %.*s\n", ev->length, ev->data); + break; + } + + // Outgoing data emitted by the NVT + case TELNET_EV_SEND: + { + telnet_send_event* ev = (telnet_send_event*)event; + printf("[OUT]: %.*s\n", ev->length, ev->data); + break; + } + } + } + + int main() + { + // Create an NVT + telnet_nvt* nvt = telnet_nvt_new(NULL, &on_event, NULL, NULL); + + // Process some incoming data + const char* data = "foo bar baz"; + telnet_receive(nvt, (const telnet_byte*)data, strlen(data), NULL); + + // Free the NVT + telnet_nvt_free(nvt); + return 0; + } + +### Telopts +Anachronism provides an easy-to-use interface to Telnet's "telopt" functionality +via the telnet\_telopt\_*() set of functions. As telopts are negotiated and +utilized, events are sent to the telopt callback provided to telnet_nvt_new(). + + #include + #include + + void on_event(telnet_nvt* nvt, telnet_event* event) + { + switch (event->type) + { + // Outgoing data emitted by the NVT + case TELNET_EV_SEND: + { + telnet_send_event* ev = (telnet_send_event*)event; + printf("[OUT]: %.*s\n", ev->length, ev->data); + break; + } + } + } + + void on_telopt_event(telnet_nvt* nvt, telnet_byte telopt, telnet_telopt_event* event) + { + // telopt is the telopt this event was triggered for + + switch (event->type) + { + case TELNET_EV_TELOPT_TOGGLE: + telnet_telopt_toggle_event* ev = (telnet_telopt_toggle_event*)event; + // ev->where is TELNET_TELOPT_LOCAL or TELNET_TELOPT_REMOTE, + // corresponding to Telnet's WILL/WONT and DO/DONT commands. + // ev->status is TELNET_TELOPT_ON or TELNET_TELOPT_OFF. + break; + case TELNET_EV_TELOPT_FOCUS: + telnet_telopt_focus_event* ev = (telnet_telopt_focus_event*)event; + // ev->focus is 1 or 0 depending on if a subnegotiation packet has + // begun or ended. + break; + case TELNET_EV_TELOPT_DATA: + telnet_telopt_data_event* ev = (telnet_telopt_data_event*)event; + // ev->data is a pointer to the received data. + // ev->length is the length of the data buffer. + break; + } + } + + int main() + { + // Create an NVT + telnet_nvt* nvt = telnet_nvt_new(NULL, &on_event, &on_telopt_event, NULL); + + // Ask to enable a telopt locally (a WILL command) + telnet_request_enable(nvt, 230, TELNET_LOCAL); + + // Process some incoming data + const char* data = "\xFF\xFD\xE6" // IAC DO 230 (turn channel on) + "\xFF\xFA\xE6" // IAC SB 230 (switch to channel) + "foo bar baz" (send data) + "\xFF\xF0"; // IAC SE (switch to main) + telnet_receive(nvt, (const telnet_byte*)data, strlen(data), NULL); + + // Free the NVT + telnet_nvt_free(nvt); + return 0; + } + +### Interrupting + TODO: Explain how to interrupt the parser. + +## Alternatives +* [libtelnet][github-libtelnet], by Elanthis
+ It incorporates a number of (rather MUD-specific) protocols by default, + though its API is quite different. + +[github-libtelnet]: https://github.com/elanthis/libtelnet (libtelnet on GitHub) + +## Credits +Someone from #startups on Freenode IRC suggested the name (I'm sure as a joke). +If you read this, remind me who you are so I can credit you properly! diff --git a/outside/anachronism/doc/channels.md b/outside/anachronism/doc/channels.md new file mode 100644 index 000000000..bfcbf838c --- /dev/null +++ b/outside/anachronism/doc/channels.md @@ -0,0 +1,50 @@ +# Telnet + +## Channels +Telnet supports data multiplexing by way of 256 built-in sub-channels, each +identified by a byte in the interval [\x00-\xFF]. By switching between +channels, you can send completely separate streams of data through the same +connection. + +All channels start out closed by default. To open a channel, one host must +request or offer a channel using IAC WILL <id> or IAC DO <id>. The remote host +then responds with IAC DO <id> or IAC WILL <id>, respectively. Alternatively, +the request may be denied using IAC DONT <id> or IAC WONT <id>, respectively. + +In order to switch to a specific channel, the IAC SB <id> sequence must +be used. All data sent afterwards will be routed through that specific channel. +To switch back to the main channel, IAC SE must be used. Note that subchannels +do not support any IAC sequences except IAC IAC (an escaped \xFF byte) and +IAC SE (return to the main channel). In particular, you cannot switch directly +from one subchannel to another: you must revert to the main channel first. + +Due to the unbiased nature of Telnet, neither side of the connection is +automatically recognized as the server or the client. However, a host may either +request a channel (as a client) or offer a channel (as a server). The WILL/WONT +commands are used in the role of server ("I will", "I wont"), while DO/DONT +are used in the role of client ("You do", "You do not"). As such, a channel +may be opened twice (even simultaneously). + +As an example, lets assume a terminal is connected to a server using Telnet. The +server offers MCCP (data compression), but wants to know what the terminal's +window size is. The following communication might occur: + + IAC DO NAWS + IAC WILL MCCP + IAC WILL NAWS + IAC SB NAWS \x50 \x00 \x50 \x00 IAC SE + IAC DO MCCP + IAC SB MCCP IAC SE + (compressed data) + +Notice that MCCP was negotiated such that the server offers the compression. +Only the server-to-client flow of data is compressed; the client would not +compress its data unless the channel was negotiated in the other direction as +well. + +In general, a specific subchannel is tied to a specific Telnet subprotocol. For +example, the EXOPL subprotocol is assigned to channel 255, so that channel +should be avoided for any other purpose. A full list of registered subprotocols +can be found on the [IANA website][1]. + +[1]: http://www.iana.org/assignments/telnet-options diff --git a/outside/anachronism/doc/parser.png b/outside/anachronism/doc/parser.png new file mode 100644 index 000000000..b6a5e05a9 Binary files /dev/null and b/outside/anachronism/doc/parser.png differ diff --git a/outside/anachronism/include/anachronism/common.h b/outside/anachronism/include/anachronism/common.h new file mode 100644 index 000000000..9dcc157a8 --- /dev/null +++ b/outside/anachronism/include/anachronism/common.h @@ -0,0 +1,24 @@ +#ifndef ANACHRONISM_COMMON_H +#define ANACHRONISM_COMMON_H + +#include /* for size_t */ + +// Telnet bytes must be unsigned +typedef unsigned char telnet_byte; + +// Error codes returned from API functions +// Positive codes are success/notice codes. +// Nonpositive codes are errors. +// ALLOC is 0 for parity with the NULL result from malloc(). +typedef enum telnet_error +{ + TELNET_E_NOT_SUBNEGOTIABLE = -4, // The telopt is not open for subnegotiation. + TELNET_E_BAD_PARSER = -3, // The telnet_parser* passed is NULL + TELNET_E_BAD_NVT = -2, // The telnet_nvt* passed is NULL + TELNET_E_INVALID_COMMAND = -1, // The telnet_byte passed is not an allowed command in this API method + TELNET_E_ALLOC = 0, // Not enough memory to allocate essential library structures + TELNET_E_OK = 1, // Huge Success! + TELNET_E_INTERRUPT = 2, // Parser interrupted by user code. +} telnet_error; + +#endif // ANACHRONISM_COMMON_H diff --git a/outside/anachronism/include/anachronism/nvt.h b/outside/anachronism/include/anachronism/nvt.h new file mode 100644 index 000000000..3ab5d5a57 --- /dev/null +++ b/outside/anachronism/include/anachronism/nvt.h @@ -0,0 +1,214 @@ +#ifndef ANACHRONISM_ANACHRONISM_H +#define ANACHRONISM_ANACHRONISM_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include + +// predefined Telnet commands from 240-255 +enum +{ + IAC_SE = 240, + IAC_NOP, + IAC_DM, + IAC_BRK, + IAC_IP, + IAC_AO, + IAC_AYT, + IAC_EC, + IAC_EL, + IAC_GA, + IAC_SB, + IAC_WILL, + IAC_WONT, + IAC_DO, + IAC_DONT, + IAC_IAC, +}; + +typedef enum telnet_telopt_location +{ + TELNET_LOCAL, + TELNET_REMOTE, +} telnet_telopt_location; + + +/** + * NVT Events + */ + +typedef enum telnet_event_type +{ + TELNET_EV_DATA, /* A stretch of plain data was received. (data, length) */ + TELNET_EV_COMMAND, /* A simple IAC comamnd was recevied. (command) */ + TELNET_EV_WARNING, /* A non-fatal invalid sequence was received. (message, position) */ + TELNET_EV_SEND, /* Outgoing data to be sent. (data, length) */ +} telnet_event_type; + +typedef struct telnet_event +{ + telnet_event_type type; +} telnet_event; + +typedef struct telnet_data_event +{ + telnet_event SUPER_; + const telnet_byte* data; + size_t length; +} telnet_data_event; + +typedef struct telnet_command_event +{ + telnet_event SUPER_; + telnet_byte command; +} telnet_command_event; + +typedef struct telnet_warning_event +{ + telnet_event SUPER_; + const char* message; + size_t position; +} telnet_warning_event; + +typedef struct telnet_send_event +{ + telnet_event SUPER_; + const telnet_byte* data; + size_t length; +} telnet_send_event; + + +/** + * Telopt Events + */ + +typedef enum telnet_telopt_event_type +{ + TELNET_EV_TELOPT_TOGGLE, + TELNET_EV_TELOPT_FOCUS, + TELNET_EV_TELOPT_DATA, +} telnet_telopt_event_type; + +typedef struct telnet_telopt_event +{ + telnet_telopt_event_type type; +} telnet_telopt_event; + +typedef struct telnet_telopt_toggle_event +{ + telnet_telopt_event SUPER_; + telnet_telopt_location where; + unsigned char status; +} telnet_telopt_toggle_event; + +typedef struct telnet_telopt_focus_event +{ + telnet_telopt_event SUPER_; + unsigned char focus; +} telnet_telopt_focus_event; + +typedef struct telnet_telopt_data_event +{ + telnet_telopt_event SUPER_; + const telnet_byte* data; + size_t length; +} telnet_telopt_data_event; + + + +typedef struct telnet_nvt telnet_nvt; + + +typedef void (*telnet_nvt_event_callback)(telnet_nvt* nvt, telnet_event* event); +typedef void (*telnet_telopt_event_callback)(telnet_nvt* nvt, telnet_byte telopt, telnet_telopt_event* event); +typedef unsigned char (*telnet_negotiate_event_callback)(telnet_nvt* nvt, telnet_byte telopt, telnet_telopt_location where); + +/** + Creates a new Telnet NVT. + + Errors: + TELNET_E_ALLOC - Unable to allocate enough memory for the NVT. + */ +telnet_nvt* telnet_nvt_new(void* userdata, + telnet_nvt_event_callback nvt_callback, + telnet_telopt_event_callback telopt_callback, + telnet_negotiate_event_callback negotiate_callback); + +void telnet_nvt_free(telnet_nvt* nvt); + +/** + Every NVT can have some user-specific data attached, such as a user-defined struct. + This can be accessed (primarily by event callbacks) to differentiate between NVTs. + + Errors: + TELNET_E_BAD_NVT - Invalid telnet_nvt* parameter. + + Example: + // assuming a FILE was passed to telnet_nvt_new(): + FILE out = NULL; + telnet_get_userdata(nvt, (void**)&out); + */ +telnet_error telnet_get_userdata(telnet_nvt* nvt, void** udata); + +/** + Processes incoming data. + If `bytes_used` is non-NULL, it will be set to the length of the string that + was read. This is generally only useful if you use telnet_halt() in a callback. + + Errors: + TELNET_E_BAD_NVT - Invalid telnet_nvt* parameter. + TELNET_E_ALLOC - Unable to allocate destination buffer for incoming text. + TELNET_E_INTERRUPT - User code interrupted the parser. + */ +telnet_error telnet_receive(telnet_nvt* nvt, const telnet_byte* data, size_t length, size_t* bytes_used); + +/** + If currently parsing (i.e. telnet_recv() is running), interrupts the parser. + This is useful for things such as MCCP, where a Telnet sequence hails the start of + data that must be decompressed before being parsed. + + Errors: + TELNET_E_BAD_NVT - Invalid telnet_nvt* parameter. + */ +telnet_error telnet_interrupt(telnet_nvt* nvt); + + +/** + Sends a string as a stream of escaped Telnet data. + + Errors: + TELNET_E_BAD_NVT - Invalid telnet_nvt* parameter. + TELNET_E_ALLOC - Unable to allocate destination buffer for outgoing text. + */ +telnet_error telnet_send_data(telnet_nvt* nvt, const telnet_byte* data, const size_t length); + +/** + Sends a Telnet command. + + Errors: + TELNET_E_BAD_NVT - Invalid telnet_nvt* parameter. + TELNET_E_INVALID_COMMAND - The command cannot be WILL, WONT, DO, DONT, SB, or SE. + */ +telnet_error telnet_send_command(telnet_nvt* nvt, const telnet_byte command); + +/** + Sends a subnegotiation packet. + + Errors: + TELNET_E_BAD_NVT - Invalid telnet_nvt* parameter. + TELNET_E_ALLOC - Unable to allocate destination buffer for outgoing text. + */ +telnet_error telnet_send_subnegotiation(telnet_nvt* nvt, const telnet_byte option, const telnet_byte* data, const size_t length); + + +telnet_error telnet_telopt_enable(telnet_nvt* nvt, const telnet_byte telopt, telnet_telopt_location where); +telnet_error telnet_telopt_disable(telnet_nvt* nvt, const telnet_byte telopt, telnet_telopt_location where); +telnet_error telnet_telopt_status(telnet_nvt* nvt, const telnet_byte telopt, telnet_telopt_location where, unsigned char* status); + +#ifdef __cplusplus +} +#endif + +#endif // ANACHRONISM_ANACHRONISM_H diff --git a/outside/anachronism/include/anachronism/parser.h b/outside/anachronism/include/anachronism/parser.h new file mode 100644 index 000000000..8309ee5ae --- /dev/null +++ b/outside/anachronism/include/anachronism/parser.h @@ -0,0 +1,73 @@ +#ifndef ANACHRONISM_PARSER_H +#define ANACHRONISM_PARSER_H + +#include + +typedef enum telnet_parser_event_type +{ + TELNET_EV_PARSER_DATA, + TELNET_EV_PARSER_COMMAND, + TELNET_EV_PARSER_OPTION, + TELNET_EV_PARSER_SUBNEGOTIATION, + TELNET_EV_PARSER_WARNING, +} telnet_parser_event_type; + +typedef struct telnet_parser_event +{ + telnet_parser_event_type type; +} telnet_parser_event; + +typedef struct telnet_parser_data_event +{ + telnet_parser_event SUPER_; + const telnet_byte* data; + size_t length; +} telnet_parser_data_event; + +typedef struct telnet_parser_command_event +{ + telnet_parser_event SUPER_; + telnet_byte command; +} telnet_parser_command_event; + +typedef struct telnet_parser_option_event +{ + telnet_parser_event SUPER_; + telnet_byte command; + telnet_byte option; +} telnet_parser_option_event; + +typedef struct telnet_parser_subnegotiation_event +{ + telnet_parser_event SUPER_; + int active; + telnet_byte option; +} telnet_parser_subnegotiation_event; + +typedef struct telnet_parser_warning_event +{ + telnet_parser_event SUPER_; + const char* message; + size_t position; +} telnet_parser_warning_event; + + + +typedef struct telnet_parser telnet_parser; + +typedef void (*telnet_parser_callback)(telnet_parser* parser, telnet_parser_event* event); + + +telnet_parser* telnet_parser_new(void* userdata, telnet_parser_callback callback); +void telnet_parser_free(telnet_parser* parser); + +telnet_error telnet_parser_get_userdata(telnet_parser* parser, void** userdata); + +telnet_error telnet_parser_parse(telnet_parser* parser, + const telnet_byte* data, + size_t length, + size_t* bytes_used); + +telnet_error telnet_parser_interrupt(telnet_parser* parser); + +#endif // ANACHRONISM_PARSER_H diff --git a/outside/anachronism/src/README.md b/outside/anachronism/src/README.md new file mode 100644 index 000000000..8ee680823 --- /dev/null +++ b/outside/anachronism/src/README.md @@ -0,0 +1,6 @@ +* parser_common.rl +
The language-agnostic Ragel grammar for the Telnet protocol. +* parser.rl +
The C implementation of the Ragel grammar. Compiled to parser.c by Ragel. +* nvt.c +
The core implementation of Anachronism's NVT and Channel constructs. diff --git a/outside/anachronism/src/nvt.c b/outside/anachronism/src/nvt.c new file mode 100644 index 000000000..f33d93f75 --- /dev/null +++ b/outside/anachronism/src/nvt.c @@ -0,0 +1,631 @@ +#include +#include +#include +#include + + +#define TELOPT_TOGGLE_CALLBACK(nvt, telopt, where_, status_) do { \ + if ((nvt)->telopt_callback) { \ + telnet_telopt_toggle_event ev; \ + ev.SUPER_.type = TELNET_EV_TELOPT_TOGGLE; \ + ev.where = (where_); \ + ev.status = (status_); \ + \ + (nvt)->telopt_callback((nvt), (telopt), (telnet_telopt_event*)&ev); \ + } \ +} while (0) + +#define TELOPT_FOCUS_CALLBACK(nvt, telopt, status_) do { \ + if ((nvt)->telopt_callback) { \ + telnet_telopt_focus_event ev; \ + ev.SUPER_.type = TELNET_EV_TELOPT_FOCUS; \ + ev.status = (status_); \ + \ + (nvt)->telopt_callback((nvt), (telopt), (telnet_telopt_event*)&ev); \ + } \ +} while (0) + +#define TELOPT_DATA_CALLBACK(nvt, telopt, data_, length_) do { \ + if ((nvt)->telopt_callback) { \ + telnet_telopt_data_event ev; \ + ev.SUPER_.type = TELNET_EV_TELOPT_DATA; \ + ev.data = (data_); \ + ev.length = (length_); \ + \ + (nvt)->telopt_callback((nvt), (telopt), (telnet_telopt_event*)&ev); \ + } \ +} while (0) + +#define SEND_CALLBACK(nvt, data_, length_) do { \ + if ((nvt)->callback) { \ + telnet_send_event ev; \ + ev.SUPER_.type = TELNET_EV_SEND; \ + ev.data = (data_); \ + ev.length = (length_); \ + \ + (nvt)->callback((nvt), (telnet_event*)&ev); \ + } \ +} while (0) + + +// Q Method of Implementing TELNET Option Negotiation +// ftp://ftp.rfc-editor.org/in-notes/rfc1143.txt +typedef enum qstate { + Q_NO = 0, Q_WANTYES, Q_WANTYESNO, + Q_YES, Q_WANTNO, Q_WANTNOYES, +} qstate; + +typedef struct telnet_qstate +{ + unsigned remote : 3; + unsigned local : 3; +} telnet_qstate; + +struct telnet_nvt +{ + telnet_parser* parser; + telnet_qstate options[256]; // track the state of each subnegotiation option + short current_remote; + + telnet_nvt_event_callback callback; + telnet_telopt_event_callback telopt_callback; + telnet_negotiate_event_callback negotiate_callback; + + void* userdata; +}; + +static unsigned char telopt_status(telnet_nvt* nvt, + telnet_byte telopt, + telnet_telopt_location where) +{ + unsigned int qval = (where == TELNET_LOCAL) ? + nvt->options[telopt].local : + nvt->options[telopt].remote; + + switch (qval) { + case Q_YES: case Q_WANTNO: case Q_WANTNOYES: + return 1; + default: + return 0; + } +} +#define telopt_subnegotiable(nvt, telopt) (telopt_status((nvt), (telopt), TELNET_REMOTE) || telopt_status((nvt), (telopt), TELNET_LOCAL)) + + +static void send_option(telnet_nvt* nvt, telnet_byte command, telnet_byte telopt) +{ + const telnet_byte buf[] = {IAC_IAC, command, telopt}; + SEND_CALLBACK(nvt, buf, 3); +} + +static void process_option_event(telnet_nvt* nvt, + telnet_byte command, + telnet_byte telopt) +{ + telnet_qstate* q = &nvt->options[telopt]; + // Every qstate begins zeroed-out, and Q_NO is 0. + + switch (command) + { + case IAC_WILL: + switch (q->remote) + { + case Q_NO: + if (nvt->negotiate_callback && nvt->negotiate_callback(nvt, telopt, TELNET_REMOTE)) { + send_option(nvt, IAC_DO, telopt); + q->remote = Q_YES; + TELOPT_TOGGLE_CALLBACK(nvt, telopt, TELNET_REMOTE, 1); + } else { + send_option(nvt, IAC_DONT, telopt); + } + break; + case Q_WANTNO: + // error + q->remote = Q_NO; + break; + case Q_WANTNOYES: + // error + q->remote = Q_YES; + TELOPT_TOGGLE_CALLBACK(nvt, telopt, TELNET_REMOTE, 1); + break; + case Q_WANTYES: + q->remote = Q_YES; + TELOPT_TOGGLE_CALLBACK(nvt, telopt, TELNET_REMOTE, 1); + break; + case Q_WANTYESNO: + send_option(nvt, IAC_DONT, telopt); + q->remote = Q_WANTNO; + TELOPT_TOGGLE_CALLBACK(nvt, telopt, TELNET_REMOTE, 1); + break; + } + break; + case IAC_WONT: + switch (q->remote) + { + case Q_YES: + send_option(nvt, IAC_DONT, telopt); + q->remote = Q_NO; + TELOPT_TOGGLE_CALLBACK(nvt, telopt, TELNET_REMOTE, 0); + break; + case Q_WANTNO: + q->remote = Q_NO; + break; + case Q_WANTNOYES: + send_option(nvt, IAC_DO, telopt); + q->remote = Q_WANTYES; + break; + case Q_WANTYES: + q->remote = Q_NO; + break; + case Q_WANTYESNO: + q->remote = Q_NO; + break; + } + break; + case IAC_DO: + switch (q->local) + { + case Q_NO: + if (nvt->negotiate_callback && nvt->negotiate_callback(nvt, telopt, TELNET_LOCAL)) { + send_option(nvt, IAC_WILL, telopt); + q->local = Q_YES; + TELOPT_TOGGLE_CALLBACK(nvt, telopt, TELNET_LOCAL, 1); + } else { + send_option(nvt, IAC_WONT, telopt); + } + break; + case Q_WANTNO: + // error + q->local = Q_NO; + break; + case Q_WANTNOYES: + // error + q->local = Q_YES; + TELOPT_TOGGLE_CALLBACK(nvt, telopt, TELNET_LOCAL, 1); + break; + case Q_WANTYES: + q->local = Q_YES; + TELOPT_TOGGLE_CALLBACK(nvt, telopt, TELNET_LOCAL, 1); + break; + case Q_WANTYESNO: + send_option(nvt, IAC_WONT, telopt); + q->local = Q_WANTNO; + TELOPT_TOGGLE_CALLBACK(nvt, telopt, TELNET_LOCAL, 1); + break; + } + break; + case IAC_DONT: + switch (q->local) + { + case Q_YES: + send_option(nvt, IAC_DONT, telopt); + q->local = Q_NO; + TELOPT_TOGGLE_CALLBACK(nvt, telopt, TELNET_LOCAL, 0); + break; + case Q_WANTNO: + q->local = Q_NO; + break; + case Q_WANTNOYES: + send_option(nvt, IAC_WILL, telopt); + q->local = Q_WANTYES; + break; + case Q_WANTYES: + q->local = Q_NO; + break; + case Q_WANTYESNO: + q->local = Q_NO; + break; + } + break; + } +} + +static void process_data_event(telnet_nvt* nvt, + const telnet_byte* data, + size_t length) +{ + if (nvt->current_remote == -1) { + // Main-line data + if (nvt->callback) { + telnet_data_event ev; + ev.SUPER_.type = TELNET_EV_DATA; + ev.data = data; + ev.length = length; + nvt->callback(nvt, (telnet_event*)&ev); + } + } else { + // Telopt data + telnet_byte telopt = (telnet_byte)nvt->current_remote; + + if (nvt->telopt_callback) { + // Make sure the telopt is enabled + if (telopt_subnegotiable(nvt, telopt)) { + telnet_telopt_data_event ev; + ev.SUPER_.type = TELNET_EV_TELOPT_DATA; + ev.data = data; + ev.length = length; + nvt->telopt_callback(nvt, telopt, (telnet_telopt_event*)&ev); + } + } + } +} + +static void process_subnegotiation_event(telnet_nvt* nvt, + int open, + telnet_byte telopt) +{ + if (open) { + nvt->current_remote = telopt; + } else { + nvt->current_remote = -1; + } + + if (nvt->telopt_callback) { + // Make sure the telopt is enabled + if (telopt_subnegotiable(nvt, telopt)) { + telnet_telopt_focus_event ev; + ev.SUPER_.type = TELNET_EV_TELOPT_FOCUS; + ev.focus = open; + nvt->telopt_callback(nvt, telopt, (telnet_telopt_event*)&ev); + } + } +} + +static void process_event(telnet_parser* parser, telnet_parser_event* event) +{ + telnet_nvt* nvt = NULL; + telnet_parser_get_userdata(parser, (void*)&nvt); + + switch (event->type) + { + case TELNET_EV_PARSER_DATA: + { + telnet_parser_data_event* ev = (telnet_parser_data_event*)event; + process_data_event(nvt, ev->data, ev->length); + break; + } + + case TELNET_EV_PARSER_OPTION: + { + telnet_parser_option_event* ev = (telnet_parser_option_event*)event; + process_option_event(nvt, ev->command, ev->option); + break; + } + + case TELNET_EV_PARSER_SUBNEGOTIATION: + { + telnet_parser_subnegotiation_event* ev = (telnet_parser_subnegotiation_event*)event; + process_subnegotiation_event(nvt, ev->active, ev->option); + break; + } + + case TELNET_EV_PARSER_COMMAND: + { + if (nvt->callback) { + telnet_parser_command_event* parser_ev = (telnet_parser_command_event*) event; + + telnet_command_event ev; + ev.SUPER_.type = TELNET_EV_COMMAND; + ev.command = parser_ev->command; + nvt->callback(nvt, (telnet_event*)&ev); + } + break; + } + + case TELNET_EV_PARSER_WARNING: + { + if (nvt->callback) { + telnet_parser_warning_event* parser_ev = (telnet_parser_warning_event*) event; + + telnet_warning_event ev; + ev.SUPER_.type = TELNET_EV_WARNING; + ev.message = parser_ev->message; + ev.position = parser_ev->position; + nvt->callback(nvt, (telnet_event*)&ev); + } + break; + } + + default: + break; + } +} + + +telnet_nvt* telnet_nvt_new(void* userdata, + telnet_nvt_event_callback nvt_callback, + telnet_telopt_event_callback telopt_callback, + telnet_negotiate_event_callback negotiate_callback) +{ + telnet_nvt* nvt = malloc(sizeof(telnet_nvt)); + if (nvt) + { + telnet_parser* parser = telnet_parser_new((void*)nvt, &process_event); + if (parser) + { + memset(nvt, 0, sizeof(*nvt)); + nvt->parser = parser; + nvt->callback = nvt_callback; + nvt->telopt_callback = telopt_callback; + nvt->negotiate_callback = negotiate_callback; + nvt->userdata = userdata; + nvt->current_remote = -1; + } + else + { + free(nvt); + nvt = NULL; + } + } + return nvt; +} + +void telnet_nvt_free(telnet_nvt* nvt) +{ + if (nvt) + { + telnet_parser_free(nvt->parser); + free(nvt); + } +} + +telnet_error telnet_get_userdata(telnet_nvt* nvt, void** userdata) +{ + if (!nvt) + return TELNET_E_BAD_NVT; + + *userdata = nvt->userdata; + return TELNET_E_OK; +} + +telnet_error telnet_receive(telnet_nvt* nvt, const telnet_byte* data, size_t length, size_t* bytes_used) +{ + if (!nvt) + return TELNET_E_BAD_NVT; + + return telnet_parser_parse(nvt->parser, data, length, bytes_used); +} + +telnet_error telnet_interrupt(telnet_nvt* nvt) +{ + if (!nvt) + return TELNET_E_BAD_NVT; + + return telnet_parser_interrupt(nvt->parser); +} + + +static int safe_concat(const telnet_byte* in, size_t inlen, telnet_byte* out, size_t outlen) +{ + // Copy as much as possible into the buffer. + memcpy(out, in, (outlen < inlen) ? outlen : inlen); + + // true if everything could be copied, false otherwise + return outlen >= inlen; +} + +// Escapes any special characters in data, writing the result data to out. +// Returns -1 if not everything could be copied (and out is full). +// Otherwise returns the length of the data in out. +// +// To avoid potential -1 return values, pass in an out buffer double the length of the data buffer. +static size_t telnet_escape(const telnet_byte* data, size_t length, telnet_byte* out, size_t outsize) +{ + if (data == NULL || out == NULL) + return 0; + + size_t outlen = 0; + size_t left = 0; + size_t right = 0; + const char* seq = NULL; + for (; right < length; ++right) + { + switch (data[right]) + { + case IAC_IAC: + seq = "\xFF\xFF"; + break; + case '\r': + // Only escape \r if it doesn't immediately precede \n. + if (right + 1 >= length || data[right+1] != '\n') + { + seq = "\r\0"; + break; + } + // !!FALLTHROUGH!! + default: + continue; // Move to the next character + } + + // Add any normal data that hasn't been added yet. + if (safe_concat(data+left, right-left, out+outlen, outsize-outlen) == 0) + return -1; + outlen += right - left; + left = right + 1; + + // Add the escape sequence. + if (safe_concat((const telnet_byte*)seq, 2, out+outlen, outsize-outlen) == 0) + return -1; + outlen += 2; + } + + // Add any leftover normal data. + if (left < right) + { + if (safe_concat(data+left, right-left, out+outlen, outsize-outlen) == 0) + return -1; + outlen += right - left; + } + + return outlen; +} + +telnet_error telnet_send_data(telnet_nvt* nvt, const telnet_byte* data, const size_t length) +{ + if (!nvt) + return TELNET_E_BAD_NVT; + else if (!nvt->callback) + return TELNET_E_OK; // immediate success since they apparently don't want the data to go anywhere + + // Due to the nature of the protocol, the most any one byte can be encoded as is two bytes. + // Hence, the smallest buffer guaranteed to contain any input is double the length of the source. + size_t bufsize = sizeof(telnet_byte) * length * 2; + telnet_byte* buf = malloc(bufsize); + if (!buf) + return TELNET_E_ALLOC; + + bufsize = telnet_escape(data, length, buf, bufsize); + + SEND_CALLBACK(nvt, buf, bufsize); + + free(buf); + buf = NULL; + + return TELNET_E_OK; +} + +telnet_error telnet_send_command(telnet_nvt* nvt, const telnet_byte command) +{ + if (!nvt) + return TELNET_E_BAD_NVT; + else if (command >= IAC_SB || command == IAC_SE) + return TELNET_E_INVALID_COMMAND; // Invalid command + + const telnet_byte buf[] = {IAC_IAC, command}; + SEND_CALLBACK(nvt, buf, 2); + + return TELNET_E_OK; +} + +telnet_error telnet_send_subnegotiation(telnet_nvt* nvt, const telnet_byte option, const telnet_byte* data, const size_t length) +{ + if (!nvt) + return TELNET_E_BAD_NVT; + else if (!telopt_subnegotiable(nvt, option)) + return TELNET_E_NOT_SUBNEGOTIABLE; + else if (!nvt->callback) + return TELNET_E_OK; + + // length*2 is the maximum buffer size needed for an escaped string. + // The extra five bytes are for the IAC, SB,