Code

Initial commit. libjunos-0.0.0
authorSebastian Harl <sh@teamix.net>
Sun, 9 Sep 2012 11:48:31 +0000 (13:48 +0200)
committerSebastian Harl <sh@teamix.net>
Sun, 9 Sep 2012 11:48:31 +0000 (13:48 +0200)
 * libJUNOS:
   - support for .netrc files
   - support for handling SSH connections to JUNOS devices
   - support for submitting simple commands (no arguments)
   - unique interface for accessing error information of all subsystems (SSH,
     XML, system errors)
 * junosc:
   - simple interface to libJUNOS
   - support for submitting a single command and dumping the reply

17 files changed:
.gitignore [new file with mode: 0644]
COPYING [new file with mode: 0644]
INSTALL [new file with mode: 0644]
Makefile.am [new file with mode: 0644]
README [new file with mode: 0644]
autogen.sh [new file with mode: 0755]
configure.ac [new file with mode: 0644]
doc/Makefile.am [new file with mode: 0644]
doc/junosc.txt [new file with mode: 0644]
src/Makefile.am [new file with mode: 0644]
src/access_ssh.c [new file with mode: 0644]
src/junos.c [new file with mode: 0644]
src/junos.h [new file with mode: 0644]
src/junosc.c [new file with mode: 0644]
src/libjunos_features.h.in [new file with mode: 0644]
src/netrc.c [new file with mode: 0644]
version-gen.sh [new file with mode: 0755]

diff --git a/.gitignore b/.gitignore
new file mode 100644 (file)
index 0000000..3f060a3
--- /dev/null
@@ -0,0 +1,28 @@
+# build system stuff
+.deps
+Makefile
+Makefile.in
+aclocal.m4
+autom4te.cache
+compile
+config.*
+configure
+depcomp
+install-sh
+missing
+stamp-h1
+version
+
+# ltdl stuff
+libtool
+ltmain.sh
+
+# build output
+.libs
+*.la
+*.lo
+*.o
+junosc
+junosc.1
+libjunos_features.h
+
diff --git a/COPYING b/COPYING
new file mode 100644 (file)
index 0000000..b90bd54
--- /dev/null
+++ b/COPYING
@@ -0,0 +1,24 @@
+Copyright (c) 2012 Sebastian 'tokkee' Harl <sh@tokkee.org>
+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 COPYRIGHT HOLDERS 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 COPYRIGHT HOLDERS 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.
+
diff --git a/INSTALL b/INSTALL
new file mode 100644 (file)
index 0000000..23e5f25
--- /dev/null
+++ b/INSTALL
@@ -0,0 +1,236 @@
+Installation Instructions
+*************************
+
+Copyright (C) 1994, 1995, 1996, 1999, 2000, 2001, 2002, 2004, 2005 Free
+Software Foundation, Inc.
+
+This file is free documentation; the Free Software Foundation gives
+unlimited permission to copy, distribute and modify it.
+
+Basic Installation
+==================
+
+These are generic installation instructions.
+
+   The `configure' shell script attempts to guess correct values for
+various system-dependent variables used during compilation.  It uses
+those values to create a `Makefile' in each directory of the package.
+It may also create one or more `.h' files containing system-dependent
+definitions.  Finally, it creates a shell script `config.status' that
+you can run in the future to recreate the current configuration, and a
+file `config.log' containing compiler output (useful mainly for
+debugging `configure').
+
+   It can also use an optional file (typically called `config.cache'
+and enabled with `--cache-file=config.cache' or simply `-C') that saves
+the results of its tests to speed up reconfiguring.  (Caching is
+disabled by default to prevent problems with accidental use of stale
+cache files.)
+
+   If you need to do unusual things to compile the package, please try
+to figure out how `configure' could check whether to do them, and mail
+diffs or instructions to the address given in the `README' so they can
+be considered for the next release.  If you are using the cache, and at
+some point `config.cache' contains results you don't want to keep, you
+may remove or edit it.
+
+   The file `configure.ac' (or `configure.in') is used to create
+`configure' by a program called `autoconf'.  You only need
+`configure.ac' if you want to change it or regenerate `configure' using
+a newer version of `autoconf'.
+
+The simplest way to compile this package is:
+
+  1. `cd' to the directory containing the package's source code and type
+     `./configure' to configure the package for your system.  If you're
+     using `csh' on an old version of System V, you might need to type
+     `sh ./configure' instead to prevent `csh' from trying to execute
+     `configure' itself.
+
+     Running `configure' takes awhile.  While running, it prints some
+     messages telling which features it is checking for.
+
+  2. Type `make' to compile the package.
+
+  3. Optionally, type `make check' to run any self-tests that come with
+     the package.
+
+  4. Type `make install' to install the programs and any data files and
+     documentation.
+
+  5. You can remove the program binaries and object files from the
+     source code directory by typing `make clean'.  To also remove the
+     files that `configure' created (so you can compile the package for
+     a different kind of computer), type `make distclean'.  There is
+     also a `make maintainer-clean' target, but that is intended mainly
+     for the package's developers.  If you use it, you may have to get
+     all sorts of other programs in order to regenerate files that came
+     with the distribution.
+
+Compilers and Options
+=====================
+
+Some systems require unusual options for compilation or linking that the
+`configure' script does not know about.  Run `./configure --help' for
+details on some of the pertinent environment variables.
+
+   You can give `configure' initial values for configuration parameters
+by setting variables in the command line or in the environment.  Here
+is an example:
+
+     ./configure CC=c89 CFLAGS=-O2 LIBS=-lposix
+
+   *Note Defining Variables::, for more details.
+
+Compiling For Multiple Architectures
+====================================
+
+You can compile the package for more than one kind of computer at the
+same time, by placing the object files for each architecture in their
+own directory.  To do this, you must use a version of `make' that
+supports the `VPATH' variable, such as GNU `make'.  `cd' to the
+directory where you want the object files and executables to go and run
+the `configure' script.  `configure' automatically checks for the
+source code in the directory that `configure' is in and in `..'.
+
+   If you have to use a `make' that does not support the `VPATH'
+variable, you have to compile the package for one architecture at a
+time in the source code directory.  After you have installed the
+package for one architecture, use `make distclean' before reconfiguring
+for another architecture.
+
+Installation Names
+==================
+
+By default, `make install' installs the package's commands under
+`/usr/local/bin', include files under `/usr/local/include', etc.  You
+can specify an installation prefix other than `/usr/local' by giving
+`configure' the option `--prefix=PREFIX'.
+
+   You can specify separate installation prefixes for
+architecture-specific files and architecture-independent files.  If you
+pass the option `--exec-prefix=PREFIX' to `configure', the package uses
+PREFIX as the prefix for installing programs and libraries.
+Documentation and other data files still use the regular prefix.
+
+   In addition, if you use an unusual directory layout you can give
+options like `--bindir=DIR' to specify different values for particular
+kinds of files.  Run `configure --help' for a list of the directories
+you can set and what kinds of files go in them.
+
+   If the package supports it, you can cause programs to be installed
+with an extra prefix or suffix on their names by giving `configure' the
+option `--program-prefix=PREFIX' or `--program-suffix=SUFFIX'.
+
+Optional Features
+=================
+
+Some packages pay attention to `--enable-FEATURE' options to
+`configure', where FEATURE indicates an optional part of the package.
+They may also pay attention to `--with-PACKAGE' options, where PACKAGE
+is something like `gnu-as' or `x' (for the X Window System).  The
+`README' should mention any `--enable-' and `--with-' options that the
+package recognizes.
+
+   For packages that use the X Window System, `configure' can usually
+find the X include and library files automatically, but if it doesn't,
+you can use the `configure' options `--x-includes=DIR' and
+`--x-libraries=DIR' to specify their locations.
+
+Specifying the System Type
+==========================
+
+There may be some features `configure' cannot figure out automatically,
+but needs to determine by the type of machine the package will run on.
+Usually, assuming the package is built to be run on the _same_
+architectures, `configure' can figure that out, but if it prints a
+message saying it cannot guess the machine type, give it the
+`--build=TYPE' option.  TYPE can either be a short name for the system
+type, such as `sun4', or a canonical name which has the form:
+
+     CPU-COMPANY-SYSTEM
+
+where SYSTEM can have one of these forms:
+
+     OS KERNEL-OS
+
+   See the file `config.sub' for the possible values of each field.  If
+`config.sub' isn't included in this package, then this package doesn't
+need to know the machine type.
+
+   If you are _building_ compiler tools for cross-compiling, you should
+use the option `--target=TYPE' to select the type of system they will
+produce code for.
+
+   If you want to _use_ a cross compiler, that generates code for a
+platform different from the build platform, you should specify the
+"host" platform (i.e., that on which the generated programs will
+eventually be run) with `--host=TYPE'.
+
+Sharing Defaults
+================
+
+If you want to set default values for `configure' scripts to share, you
+can create a site shell script called `config.site' that gives default
+values for variables like `CC', `cache_file', and `prefix'.
+`configure' looks for `PREFIX/share/config.site' if it exists, then
+`PREFIX/etc/config.site' if it exists.  Or, you can set the
+`CONFIG_SITE' environment variable to the location of the site script.
+A warning: not all `configure' scripts look for a site script.
+
+Defining Variables
+==================
+
+Variables not defined in a site shell script can be set in the
+environment passed to `configure'.  However, some packages may run
+configure again during the build, and the customized values of these
+variables may be lost.  In order to avoid this problem, you should set
+them in the `configure' command line, using `VAR=value'.  For example:
+
+     ./configure CC=/usr/local2/bin/gcc
+
+causes the specified `gcc' to be used as the C compiler (unless it is
+overridden in the site shell script).  Here is a another example:
+
+     /bin/bash ./configure CONFIG_SHELL=/bin/bash
+
+Here the `CONFIG_SHELL=/bin/bash' operand causes subsequent
+configuration-related scripts to be executed by `/bin/bash'.
+
+`configure' Invocation
+======================
+
+`configure' recognizes the following options to control how it operates.
+
+`--help'
+`-h'
+     Print a summary of the options to `configure', and exit.
+
+`--version'
+`-V'
+     Print the version of Autoconf used to generate the `configure'
+     script, and exit.
+
+`--cache-file=FILE'
+     Enable the cache: use and save the results of the tests in FILE,
+     traditionally `config.cache'.  FILE defaults to `/dev/null' to
+     disable caching.
+
+`--config-cache'
+`-C'
+     Alias for `--cache-file=config.cache'.
+
+`--quiet'
+`--silent'
+`-q'
+     Do not print messages saying which checks are being made.  To
+     suppress all normal output, redirect it to `/dev/null' (any error
+     messages will still be shown).
+
+`--srcdir=DIR'
+     Look for the package's source code in directory DIR.  Usually
+     `configure' can determine that directory automatically.
+
+`configure' also accepts some other, not widely useful, options.  Run
+`configure --help' for more details.
+
diff --git a/Makefile.am b/Makefile.am
new file mode 100644 (file)
index 0000000..3fab3fa
--- /dev/null
@@ -0,0 +1,11 @@
+SUBDIRS = src
+if BUILD_DOCUMENTATION
+SUBDIRS += doc
+endif
+
+EXTRA_DIST = autogen.sh version-gen.sh
+
+version: FORCE
+       @# As a side-effect, this updates version.
+       @echo Building $(PACKAGE_NAME) version $$( cd .. && ./version-gen.sh )
+
diff --git a/README b/README
new file mode 100644 (file)
index 0000000..8bcd9d3
--- /dev/null
+++ b/README
@@ -0,0 +1,46 @@
+libJUNOS
+========
+
+  This is a C client library to access routers running JUNOS software. It
+  offeres an API to connect to the device and issue JUNOScript commands.
+
+  This is free and open source software, licensed under the 2-clause BSD
+  license. See COPYING for details.
+
+Prerequisites
+-------------
+
+  To compile the libJUNOS package from source you need:
+
+  * A build environment: autotools, libtool, C compiler, ...
+
+  * A POSIX + Single UNIX Specification compatible C library.
+
+  * libssh2:
+    SSH client library used to connect to the router.
+
+  * libxml2:
+    GNOME XML library used to parse responses from the router.
+
+  * asciidoc, xsltproc:
+    The AsciiDoc text document format is used to write the manpages.
+
+Configuring / Compiling / Installing
+------------------------------------
+
+  To configure, build and install libJUNOS with the default settings, run
+  `./configure && make && make install'. For detailed, generic instructions
+  see INSTALL. For a complete list of configure options and their description,
+  run `./configure --help'.
+
+  By default, libJUNOS will be installed into `/opt/libjunos'. You can adjust
+  this setting by specifying the `--prefix' configure option - see INSTALL for
+  details. If you pass DESTDIR=<path> to `make install', <path> will be
+  prefixed to all installation directories. This might be useful when creating
+  packages for libJUNOS.
+
+Author
+------
+
+  Sebastian "tokkee" Harl <sh@tokkee.org>
+
diff --git a/autogen.sh b/autogen.sh
new file mode 100755 (executable)
index 0000000..7c7c72f
--- /dev/null
@@ -0,0 +1,17 @@
+#! /bin/sh
+
+libtoolize=libtoolize
+
+if which glibtoolize > /dev/null 2>&1; then
+       libtoolize=glibtoolize
+fi
+
+set -ex
+
+aclocal --force --warnings=all
+$libtoolize --automake --copy --force
+aclocal
+autoconf --force --warnings=all
+autoheader --force --warnings=all
+automake --add-missing --copy --foreign --warnings=all
+
diff --git a/configure.ac b/configure.ac
new file mode 100644 (file)
index 0000000..a223f6d
--- /dev/null
@@ -0,0 +1,259 @@
+dnl Process this file with autoconf to produce a configure script.
+dnl
+dnl This is the libJUNOS configure script.
+dnl
+dnl Copyright (C) 2012 Sebastian 'tokkee' Harl <sh@tokkee.org>
+dnl All rights reserved.
+dnl
+dnl Redistribution and use in source and binary forms, with or without
+dnl modification, are permitted provided that the following conditions
+dnl are met:
+dnl 1. Redistributions of source code must retain the above copyright
+dnl    notice, this list of conditions and the following disclaimer.
+dnl 2. Redistributions in binary form must reproduce the above copyright
+dnl    notice, this list of conditions and the following disclaimer in the
+dnl    documentation and/or other materials provided with the distribution.
+dnl
+dnl THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+dnl ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+dnl TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+dnl PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR
+dnl CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+dnl EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+dnl PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+dnl OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+dnl WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+dnl OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+dnl ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+AC_INIT([JUNOScript client library],[m4_esyscmd(./version-gen.sh)],
+               [sh@tokkee.org],
+               [libJUNOS],
+               [http://git.tokkee.org/?p=libjunos.git])
+PACKAGE_MAINTAINER="Sebastian 'tokkee' Harl <sh@tokkee.org>"
+AC_DEFINE_UNQUOTED([PACKAGE_MAINTAINER], ["$PACKAGE_MAINTAINER"],
+               [Define to the name of the maintainer of this package.])
+AC_CONFIG_SRCDIR([src/junosc.c])
+AC_CONFIG_HEADERS([src/config.h])
+AC_PREFIX_DEFAULT([/opt/libjunos])
+
+AM_INIT_AUTOMAKE([foreign -Wall])
+
+AC_LANG(C)
+
+AC_SYS_LARGEFILE
+
+AC_PROG_CC
+AC_PROG_CPP
+AC_PROG_INSTALL
+AC_PROG_LN_S
+AC_PROG_MAKE_SET
+
+AM_PROG_CC_C_O
+
+AC_FUNC_STRERROR_R
+
+m4_ifdef([LT_INIT],
+       [
+        LT_INIT
+       ],
+       # else
+       # (older libtools)
+       [
+        AC_PROG_LIBTOOL
+       ]
+)
+
+PKG_PROG_PKG_CONFIG
+
+test_cc_flags() {
+       AC_LANG_CONFTEST([AC_LANG_PROGRAM([[ ]], [[ ]])])
+       $CC -c conftest.c $CFLAGS $@ > /dev/null 2> /dev/null
+       ret=$?
+       rm -f conftest.o
+       return $ret
+}
+
+m4_divert_once([HELP_ENABLE], [
+Build options:])
+
+dnl Optionally stick to standard C99 and POSIX:2001 as close as possible.
+AC_ARG_ENABLE([standards],
+               AS_HELP_STRING([--enable-standards],
+                               [C99 / POSIX standards compliance mode @<:@default=no@:>@]),
+               [enable_standards="$enableval"],
+               [enable_standards="no"])
+
+if test "x$enable_standards" = "xyes"; then
+       AC_DEFINE([_ISOC99_SOURCE], 1,
+                       [Define to enforce ISO/IEC 9899:1999 (C99) compliance.])
+       AC_DEFINE([_POSIX_C_SOURCE], 200112L,
+                       [Define to enforce IEEE 1003.1-2001 (POSIX:2001) compliance.])
+       AC_DEFINE([_XOPEN_SOURCE], 600,
+                       [Define to enforce X/Open 6 (XSI) compliance.])
+       AC_DEFINE([_REENTRANT], 1,
+                       [Define to enable reentrant interfaces.])
+       AC_DEFINE([_THREAD_SAFE], 1,
+                       [Define to enable reentrant interfaces.])
+
+       for flag in -std=c99 -pedantic; do
+               AC_MSG_CHECKING([whether $CC accepts $flag])
+
+               if test_cc_flags $flag; then
+                       CFLAGS="$CFLAGS $flag"
+                       AC_MSG_RESULT([yes])
+               else
+                       AC_MSG_RESULT([no])
+               fi
+       done
+fi
+
+dnl Hardening (see e.g. http://wiki.debian.org/Hardening for a motivation).
+AC_DEFINE([_FORTIFY_SOURCE], 2,
+               [Define to enable protection against static sized buffer overflows.])
+AC_ARG_ENABLE([hardening],
+               AS_HELP_STRING([--disable-hardening],
+                               [hardening options @<:@default=yes@:>@]),
+               [enable_hardening="$enableval"],
+               [enable_hardening="yes"])
+
+if test "x$enable_hardening" = "xyes"; then
+       hardening=0
+       hardening_tests=0
+       for flag in -Wformat -Wformat-security; do
+               hardening_tests=$(($hardening_tests + 1))
+               AC_MSG_CHECKING([whether $CC accepts $flag])
+
+               if test_cc_flags $flag; then
+                       CFLAGS="$CFLAGS $flag"
+                       hardening=$(($hardening + 1))
+                       AC_MSG_RESULT([yes])
+               else
+                       AC_MSG_RESULT([no])
+               fi
+       done
+       if test $hardening -ne $hardening_tests; then
+               AC_MSG_WARN(
+                               [Some hardening options are not supported by your compiler!])
+       fi
+fi
+
+dnl Strict checking for potential problems.
+AC_ARG_ENABLE([strict-checks],
+               AS_HELP_STRING([--disable-strict-checks],
+                               [strict compiler checks @<:@default=yes@:>@]),
+               [enable_strict_checks="$enableval"],
+               [enable_strict_checks="yes"])
+
+STRICT_CFLAGS=""
+for flag in -Wall -Werror; do
+       AC_MSG_CHECKING([whether $CC accepts $flag])
+
+       if test_cc_flags $flag; then
+               STRICT_CFLAGS="$STRICT_CFLAGS $flag"
+               AC_MSG_RESULT([yes])
+       else
+               AC_MSG_RESULT([no])
+       fi
+done
+
+if test "x$enable_strict_checks" = "xyes"; then
+       for flag in -Wextra \
+                       -Wbad-function-cast \
+                       -Wcast-align \
+                       -Wcast-qual \
+                       -Wconversion \
+                       -Wdeclaration-after-statement \
+                       -Wmissing-prototypes \
+                       -Wpointer-arith \
+                       -Wshadow \
+                       -Wstrict-prototypes \
+                       -Wunreachable-code; do
+               AC_MSG_CHECKING([whether $CC accepts $flag])
+
+               if test_cc_flags $flag; then
+                       STRICT_CFLAGS="$STRICT_CFLAGS $flag"
+                       AC_MSG_RESULT([yes])
+               else
+                       AC_MSG_RESULT([no])
+               fi
+       done
+fi
+AC_SUBST([STRICT_CFLAGS])
+
+AC_CHECK_HEADERS(libgen.h)
+
+dnl Check for dependencies.
+build_documentation="yes"
+
+have_xsltproc="yes"
+AC_PATH_PROG([XSLTPROC], [xsltproc])
+if test "x$XSLTPROC" = "x"; then
+       have_xsltproc="no"
+       build_documentation="no (missing xsltproc)"
+fi
+
+have_a2x="yes"
+AC_PATH_PROG([A2X], [a2x])
+if test "x$A2X" = "x"; then
+       have_a2x="no"
+       build_documentation="no (missing a2x)"
+fi
+AC_SUBST([A2X])
+
+AM_CONDITIONAL([BUILD_DOCUMENTATION], test "x$build_documentation" = "xyes")
+
+PKG_CHECK_MODULES([LIBSSH2], [libssh2])
+LIBSSH2_VERSION=`$PKG_CONFIG --modversion libssh2`
+LIBSSH2_IDIRS=`$PKG_CONFIG --cflags-only-I libssh2`
+
+PKG_CHECK_MODULES([LIBXML2], [libxml-2.0])
+LIBXML2_VERSION=`$PKG_CONFIG --modversion libxml-2.0`
+LIBXML2_IDIRS=`$PKG_CONFIG --cflags-only-I libxml-2.0`
+
+dnl Try to ignore compiler warnings caused by third-party headers
+for flag in $LIBSSH2_IDIRS $LIBXML2_IDIRS; do
+       I_dir=${flag#-I}
+
+       flag="-isystem $I_dir"
+       AC_MSG_CHECKING([whether $CC accepts $flag])
+       if test_cc_flags "$flag"; then
+               STRICT_CFLAGS="$STRICT_CFLAGS $flag"
+               AC_MSG_RESULT([yes])
+       else
+               AC_MSG_RESULT([no])
+       fi
+done
+
+AC_CONFIG_FILES([Makefile doc/Makefile src/Makefile])
+AC_OUTPUT
+
+BUILD_DATE="`date --utc '+%F %T'` (UTC)"
+
+AC_MSG_RESULT()
+AC_MSG_RESULT([$PACKAGE_NAME has been configured successfully.])
+AC_MSG_RESULT()
+AC_MSG_RESULT([Run 'make' to compile the software and use 'make install' to])
+AC_MSG_RESULT([install the package into $prefix.])
+AC_MSG_RESULT()
+AC_MSG_RESULT([Configuration summary:])
+AC_MSG_RESULT()
+AC_MSG_RESULT([  package version: $PACKAGE_VERSION])
+AC_MSG_RESULT([  build date: $BUILD_DATE])
+AC_MSG_RESULT()
+AC_MSG_RESULT([  Tools:])
+AC_MSG_RESULT([    AsciiDoc (a2x): . . . . . . $have_a2x])
+AC_MSG_RESULT([    xsltproc: . . . . . . . . . $have_xsltproc])
+AC_MSG_RESULT()
+AC_MSG_RESULT([  Libraries:])
+AC_MSG_RESULT([    libssh2:  . . . . . . . . . $LIBSSH2_VERSION])
+AC_MSG_RESULT([    libxml2:  . . . . . . . . . $LIBXML2_VERSION])
+AC_MSG_RESULT()
+AC_MSG_RESULT([  Features:])
+AC_MSG_RESULT([    documentation:  . . . . . . $build_documentation])
+AC_MSG_RESULT([    SSH access: . . . . . . . . yes])
+AC_MSG_RESULT()
+AC_MSG_RESULT([This package is maintained by $PACKAGE_MAINTAINER.])
+AC_MSG_RESULT([Please report bugs to $PACKAGE_BUGREPORT.])
+AC_MSG_RESULT()
+
diff --git a/doc/Makefile.am b/doc/Makefile.am
new file mode 100644 (file)
index 0000000..b9a7f7d
--- /dev/null
@@ -0,0 +1,13 @@
+EXTRA_DIST = junosc.txt
+CLEANFILES = junosc.1
+
+man_MANS = junosc.1
+
+junosc.1: junosc.txt ../version
+
+.txt.1:
+       @A2X@ -d manpage -f manpage \
+               -apackage_version=$(PACKAGE_VERSION) \
+               -abuild_date="$$( date --utc '+%F' )" \
+               $<
+
diff --git a/doc/junosc.txt b/doc/junosc.txt
new file mode 100644 (file)
index 0000000..b22c5a5
--- /dev/null
@@ -0,0 +1,54 @@
+JUNOSC(1)
+=========
+Sebastian "tokkee" Harl <sh@tokkee.org>
+version {package_version}, {build_date}
+:doctype: manpage
+
+NAME
+----
+junosc - a JUNOScript client application
+
+SYNOPSIS
+--------
+*junosc* ['options']
+
+DESCRIPTION
+-----------
+*junosc* is a client application to connect to routers running JUNOS software.
+It may be used to issue JUNOScript commands and display the response to the
+user.
+
+OPTIONS
+-------
+*-h*::
+       Display a usage and help summary and exit.
+
+*-V*::
+       Display the version number and copyright information.
+
+EXIT CODES
+----------
+*0*::
+       Success.
+
+*1*::
+       Failure (syntax or usage error).
+
+BUGS
+----
+None known.
+
+AUTHOR
+------
+junosc was written by Sebastian "tokkee" Harl <sh@tokkee.org>.
+
+COPYRIGHT
+---------
+Copyright (C) 2012 Sebastian "tokkee" Harl <sh@tokkee.org>
+
+This is free software under the terms of the BSD license, see the source for
+copying conditions. There is NO WARRANTY; not even for MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE.
+
+// vim: set tw=78 sw=4 ts=4 noexpandtab spell spelllang=en_us :
+
diff --git a/src/Makefile.am b/src/Makefile.am
new file mode 100644 (file)
index 0000000..ffee374
--- /dev/null
@@ -0,0 +1,35 @@
+AM_CFLAGS = @STRICT_CFLAGS@
+
+include_HEADERS = junos.h libjunos_features.h
+lib_LTLIBRARIES = libjunos.la
+
+BUILT_SOURCES = libjunos_features.h
+
+libjunos_la_SOURCES = junos.c junos.h \
+               libjunos_features.h \
+               access_ssh.c \
+               netrc.c
+libjunos_la_CFLAGS = $(AM_CFLAGS) \
+               @LIBSSH2_CFLAGS@ \
+               @LIBXML2_CFLAGS@
+libjunos_la_LDFLAGS = $(AM_LDFLAGS) -version-info 0:0:0 \
+               @LIBSSH2_LIBS@ \
+               @LIBXML2_LIBS@
+
+bin_PROGRAMS = junosc
+
+junosc_SOURCES = junosc.c junos.h
+junosc_CFLAGS = $(AM_CFLAGS) -DBUILD_DATE="\"$$( date --utc '+%F %T' ) (UTC)\""
+junosc_LDADD = libjunos.la
+
+libjunos_features.h: libjunos_features.h.in ../version
+       source ../version; sed \
+           -e "s/@LIBJUNOS_VERSION_MAJOR@/$$VERSION_MAJOR/g" \
+           -e "s/@LIBJUNOS_VERSION_MINOR@/$$VERSION_MINOR/g" \
+           -e "s/@LIBJUNOS_VERSION_PATCH@/$$VERSION_PATCH/g" \
+           -e "s/@LIBJUNOS_VERSION_EXTRA@/$$VERSION_EXTRA/g" \
+           -e "s/@LIBJUNOS_VERSION_STRING@/$$VERSION_STRING/g" \
+           libjunos_features.h.in > libjunos_features.h
+
+.PHONY: FORCE
+
diff --git a/src/access_ssh.c b/src/access_ssh.c
new file mode 100644 (file)
index 0000000..5e66599
--- /dev/null
@@ -0,0 +1,571 @@
+/*
+ * libJUNOS - src/access_ssh.c
+ * Copyright (C) 2012 Sebastian 'tokkee' Harl <sh@tokkee.org>
+ * 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 COPYRIGHT HOLDERS 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 COPYRIGHT HOLDERS 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.
+ */
+
+/*
+ * SSH access backend for the JUNOS object.
+ */
+
+#include "junos.h"
+
+#include <errno.h>
+
+#include <libssh2.h>
+
+#include <sys/socket.h>
+#include <netdb.h>
+
+#include <stdlib.h>
+#include <unistd.h>
+
+/*
+ * private data structures
+ */
+
+struct junos_ssh_access {
+       junos_t *junos;
+
+       char *hostname;
+       char *username;
+       char *password;
+
+       struct addrinfo ai;
+       int sock_fd;
+
+       LIBSSH2_SESSION *session;
+       LIBSSH2_CHANNEL *channel;
+
+       char *banner;
+};
+
+/*
+ * private helper functions
+ */
+
+static void
+ssh_set_error(junos_ssh_access_t *ssh, int type, int error,
+               char *msg_prefix, ...)
+{
+       va_list ap;
+
+       if (! ssh)
+               return;
+
+       va_start(ap, msg_prefix);
+       junos_set_verror(ssh->junos, type, error, msg_prefix, ap);
+       va_end(ap);
+} /* ssh_set_error */
+
+static int
+ssh_select(junos_ssh_access_t *ssh)
+{
+       int direction;
+
+       fd_set fd;
+       fd_set *read_fds  = NULL;
+       fd_set *write_fds = NULL;
+
+       struct timeval timeout;
+
+       timeout.tv_sec  = 10;
+       timeout.tv_usec = 0;
+
+       FD_ZERO(&fd);
+       FD_SET(ssh->sock_fd, &fd);
+
+       direction = libssh2_session_block_directions(ssh->session);
+
+       if (direction & LIBSSH2_SESSION_BLOCK_INBOUND)
+               read_fds = &fd;
+
+       if (direction & LIBSSH2_SESSION_BLOCK_OUTBOUND)
+               write_fds = &fd;
+
+       return select(ssh->sock_fd + 1, read_fds, write_fds,
+                       /* error_fds = */ NULL, &timeout);
+} /* ssh_select */
+
+static int
+ssh_connect(junos_ssh_access_t *ssh)
+{
+       int status;
+
+       struct addrinfo *ai;
+       struct addrinfo *ai_ptr;
+       struct addrinfo  ai_hints;
+
+       int have_error = 0;
+
+       memset(&ai_hints, 0, sizeof(ai_hints));
+       ai_hints.ai_flags    = 0;
+       ai_hints.ai_family   = AF_UNSPEC;
+       ai_hints.ai_socktype = SOCK_STREAM;
+       ai_hints.ai_protocol = IPPROTO_TCP;
+
+       status = getaddrinfo(ssh->hostname, /* servname = */ "22",
+                       &ai_hints, &ai);
+       if (status) {
+               ssh_set_error(ssh, JUNOS_GAI_ERROR, status,
+                               "Failed to resolve hostname '%s'", ssh->hostname);
+               return -1;
+       }
+
+       for (ai_ptr = ai; ai_ptr != NULL; ai_ptr = ai_ptr->ai_next) {
+               ssh->sock_fd = socket(ai_ptr->ai_family, ai_ptr->ai_socktype,
+                               ai_ptr->ai_protocol);
+               if (ssh->sock_fd < 0) {
+                       ssh_set_error(ssh, JUNOS_SYS_ERROR, errno,
+                                       "Failed to open socket");
+                       have_error = 1;
+                       continue;
+               }
+
+               status = connect(ssh->sock_fd, (struct sockaddr *)ai_ptr->ai_addr,
+                               ai_ptr->ai_addrlen);
+               if (status) {
+                       ssh_set_error(ssh, JUNOS_SYS_ERROR, errno,
+                                       "Failed to connect to host '%s'", ssh->hostname);
+                       shutdown(ssh->sock_fd, SHUT_RDWR);
+                       close(ssh->sock_fd);
+                       ssh->sock_fd = -1;
+                       have_error = 1;
+                       continue;
+               }
+
+               /* succeeded to connect */
+               ssh->ai = *ai_ptr;
+               break;
+       }
+
+       if (ai_ptr && have_error)
+               junos_clear_error(ssh->junos);
+       else if (! ai_ptr) /* use error set above */
+               return -1;
+
+       freeaddrinfo(ai);
+       return 0;
+} /* ssh_connect */
+
+static int
+ssh_authenticate_password(junos_ssh_access_t *ssh)
+{
+       int status;
+
+       while ((status = libssh2_userauth_password_ex(ssh->session,
+                                       ssh->username, (unsigned int)strlen(ssh->username),
+                                       ssh->password, (unsigned int)strlen(ssh->password),
+                                       /* passwd_change_cb = */ NULL)) == LIBSSH2_ERROR_EAGAIN)
+               /* retry */;
+
+       if (status) {
+               ssh_set_error(ssh, JUNOS_ACCESS_ERROR, status,
+                               "Password authentication failed for user '%s'",
+                               ssh->username);
+               return -1;
+       }
+       return 0;
+} /* ssh_authenticate_password */
+
+static int
+ssh_authenticate_pubkey(junos_ssh_access_t *ssh)
+{
+       int status;
+
+       char  ssh_pub_key[256];
+       char  ssh_priv_key[256];
+
+       char *home_dir;
+
+       home_dir = getenv("HOME");
+       if (! home_dir) {
+               ssh_set_error(ssh, JUNOS_SYS_ERROR, errno,
+                               "Failed to determine home directory");
+               return -1;
+       }
+
+       snprintf(ssh_pub_key, sizeof(ssh_pub_key) - 1,
+                       "%s/.ssh/id_rsa.pub", home_dir);
+       ssh_pub_key[sizeof(ssh_pub_key) - 1] = '\0';
+       snprintf(ssh_priv_key, sizeof(ssh_priv_key) - 1,
+                       "%s/.ssh/id_rsa", home_dir);
+       ssh_priv_key[sizeof(ssh_priv_key) - 1] = '\0';
+
+       while ((status = libssh2_userauth_publickey_fromfile_ex(ssh->session,
+                                       ssh->username, (unsigned int)strlen(ssh->username),
+                                       ssh_pub_key, ssh_priv_key,
+                                       /* passphrase = */ "")) == LIBSSH2_ERROR_EAGAIN)
+               /* retry */;
+
+       if (status) {
+               ssh_set_error(ssh, JUNOS_ACCESS_ERROR, status,
+                               "Public key authentication failed for user '%s'",
+                               ssh->username);
+               return -1;
+       }
+       return 0;
+} /* ssh_authenticate_pubkey */
+
+static int
+ssh_authenticate(junos_ssh_access_t *ssh)
+{
+       if (ssh->password && strlen(ssh->password))
+               return ssh_authenticate_password(ssh);
+       else
+               return ssh_authenticate_pubkey(ssh);
+       return -1;
+} /* ssh_authenticate */
+
+static void
+ssh_access_disconnect(junos_ssh_access_t *ssh, char *msg)
+{
+       int status;
+
+       if (! ssh)
+               return;
+
+       if (! msg)
+               msg = "SSH session terminated";
+
+       if (ssh->channel) {
+               while ((status = libssh2_channel_close(ssh->channel)) == LIBSSH2_ERROR_EAGAIN)
+                       ssh_select(ssh);
+
+               libssh2_channel_free(ssh->channel);
+               ssh->channel = NULL;
+       }
+
+       if (ssh->session) {
+               libssh2_session_disconnect(ssh->session, msg);
+               libssh2_session_free(ssh->session);
+               ssh->session = NULL;
+       }
+
+       if (ssh->sock_fd >= 0) {
+               shutdown(ssh->sock_fd, SHUT_RDWR);
+               close(ssh->sock_fd);
+               ssh->sock_fd = -1;
+       }
+
+       if (ssh->banner)
+               free(ssh->banner);
+       ssh->banner = NULL;
+} /* ssh_access_disconnect */
+
+static void
+ssh_access_free(junos_ssh_access_t *ssh, char *msg)
+{
+       ssh_access_disconnect(ssh, msg);
+
+       if (ssh->hostname)
+               free(ssh->hostname);
+       if (ssh->username)
+               free(ssh->username);
+       if (ssh->password)
+               free(ssh->password);
+       ssh->hostname = ssh->username = ssh->password = NULL;
+
+       free(ssh);
+} /* ssh_access_free */
+
+/*
+ * public API
+ */
+
+junos_ssh_access_t *
+junos_ssh_new(junos_t *junos)
+{
+       junos_ssh_access_t *ssh;
+
+       if (! junos) {
+               dprintf("SSH: Missing JUNOS object\n");
+               return NULL;
+       }
+
+       ssh = calloc(1, sizeof(*ssh));
+       if (! ssh) {
+               junos_set_error(junos, JUNOS_SYS_ERROR, errno,
+                               "Failed to allocate a new JUNOS SSH object");
+               return NULL;
+       }
+
+       ssh->junos = junos;
+
+       ssh->hostname = junos_get_hostname(junos);
+       if (ssh->hostname)
+               ssh->hostname = strdup(ssh->hostname);
+       ssh->username = junos_get_username(junos);
+       if (ssh->username)
+               ssh->username = strdup(ssh->username);
+       ssh->password = junos_get_password(junos);
+       if (ssh->password)
+               ssh->password = strdup(ssh->password);
+
+       if ((! ssh->hostname) || (! ssh->username)) {
+               ssh_set_error(ssh, JUNOS_SYS_ERROR, errno,
+                               "Failed to duplicate hostname/username strings");
+               ssh_access_free(ssh, "Internal error: strdup failed");
+               return NULL;
+       }
+
+       ssh->sock_fd = -1;
+
+       ssh->session = NULL;
+       ssh->channel = NULL;
+
+       ssh->banner  = NULL;
+
+       return ssh;
+} /* junos_ssh_new */
+
+int
+junos_ssh_connect(junos_ssh_access_t *ssh)
+{
+       int status;
+       const char *ssh_banner;
+
+       if (! ssh)
+               return -1;
+
+       if (ssh_connect(ssh)) {
+               ssh_access_disconnect(ssh, "Failed to connect to host");
+               return -1;
+       }
+
+       ssh->session = libssh2_session_init();
+       if (! ssh->session) {
+               ssh_set_error(ssh, JUNOS_ACCESS_ERROR,
+                               libssh2_session_last_error(ssh->session, NULL, NULL, 0),
+                               "Failed to create libssh2 session object");
+               ssh_access_disconnect(ssh, "Failed to create libssh2 session object");
+               return -1;
+       }
+
+       /* do non-blocking I/O */
+       libssh2_session_set_blocking(ssh->session, 0);
+
+       while ((status = libssh2_session_handshake(ssh->session,
+                                       ssh->sock_fd)) == LIBSSH2_ERROR_EAGAIN)
+               /* retry */;
+
+       if (status) {
+               ssh_set_error(ssh, JUNOS_ACCESS_ERROR, status,
+                               "Failed to establish libssh2 session");
+               ssh_access_disconnect(ssh, "Failed to establish libssh2 session");
+               return -1;
+       }
+
+       /* XXX: verify host key */
+
+       if (ssh_authenticate(ssh)) {
+               ssh_access_disconnect(ssh, "Authentication failed");
+               return -1;
+       }
+
+       while ((! (ssh->channel = libssh2_channel_open_session(ssh->session)))
+                       && (libssh2_session_last_error(ssh->session,
+                                       NULL, NULL, 0) == LIBSSH2_ERROR_EAGAIN))
+               ssh_select(ssh);
+
+       if (! ssh->channel) {
+               ssh_set_error(ssh, JUNOS_ACCESS_ERROR,
+                               libssh2_session_last_error(ssh->session, NULL, NULL, 0),
+                               "Failed to open libssh2 session");
+               ssh_access_disconnect(ssh, "Failed to open libssh2 session");
+               return -1;
+       }
+
+       ssh_banner = libssh2_session_banner_get(ssh->session);
+       if (ssh_banner) {
+               dprintf("SSH: Successfully connected to host '%s': %s\n",
+                               ssh->hostname, ssh_banner);
+               ssh->banner = strdup(ssh_banner);
+       }
+
+       while ((status = libssh2_channel_exec(ssh->channel,
+                                       "junoscript")) == LIBSSH2_ERROR_EAGAIN)
+               ssh_select(ssh);
+
+       if (status) {
+               ssh_set_error(ssh, JUNOS_ACCESS_ERROR, status,
+                               "Failed to start 'junoscript'");
+               ssh_access_disconnect(ssh, "Failed to start 'junoscript'");
+               return -1;
+       }
+
+       return 0;
+} /* junos_ssh_connect */
+
+int
+junos_ssh_disconnect(junos_ssh_access_t *ssh)
+{
+       ssh_access_disconnect(ssh, NULL);
+       return 0;
+} /* junos_ssh_disconnect */
+
+int
+junos_ssh_free(junos_ssh_access_t *ssh)
+{
+       ssh_access_disconnect(ssh, NULL);
+       ssh_access_free(ssh, NULL);
+       return 0;
+} /* junos_ssh_free */
+
+ssize_t
+junos_ssh_recv(junos_ssh_access_t *ssh, char *buf, size_t buf_len)
+{
+       ssize_t status;
+       ssize_t count = 0;
+
+       if ((! ssh) || (! buf)) {
+               ssh_set_error(ssh, JUNOS_SYS_ERROR, EINVAL,
+                               "junos_ssh_recv() requires the 'ssh' and 'buf' arguments");
+               return -1;
+       }
+
+       if (buf_len <= 1) {
+               ssh_set_error(ssh, JUNOS_SYS_ERROR, EINVAL,
+                               "junos_ssh_recv() requires buffer >= 2 bytes");
+               return -1;
+       }
+
+       while (42) {
+               if ((size_t)count >= buf_len - 1)
+                       break;
+
+               status = libssh2_channel_read(ssh->channel,
+                               buf + count, buf_len - (size_t)count - 1);
+
+               if (! status)
+                       break;
+               else if (status == LIBSSH2_ERROR_EAGAIN) {
+                       if (! count) {
+                               ssh_select(ssh);
+                               continue;
+                       }
+                       break;
+               }
+               else if (status < 0) {
+                       ssh_set_error(ssh, JUNOS_ACCESS_ERROR, (int)status,
+                                       "Failed to read from remote host");
+                       if (! count)
+                               count = -1;
+                       break;
+               }
+
+               count += status;
+       }
+
+       if (count >= 0)
+               buf[count] = '\0';
+
+       return count;
+} /* junos_ssh_recv */
+
+ssize_t
+junos_ssh_send(junos_ssh_access_t *ssh, char *buf, size_t buf_len)
+{
+       ssize_t status;
+       ssize_t count = 0;
+
+       if ((! ssh) || (! buf)) {
+               ssh_set_error(ssh, JUNOS_SYS_ERROR, EINVAL,
+                               "junos_ssh_send() requires the 'ssh' and 'buf' arguments");
+               return -1;
+       }
+
+       while (42) {
+               if ((size_t)count >= buf_len)
+                       break;
+
+               status = libssh2_channel_write(ssh->channel,
+                               buf + count, buf_len - (size_t)count);
+
+               if (status == LIBSSH2_ERROR_EAGAIN)
+                       continue;
+               else if (status < 0) {
+                       ssh_set_error(ssh, JUNOS_ACCESS_ERROR, (int)status,
+                                       "Failed to write to remote host");
+                       if (! count)
+                               count = -1;
+                       break;
+               }
+
+               count += status;
+       }
+
+       return count;
+} /* junos_ssh_send */
+
+int
+junos_set_ssh_error(junos_error_t *err, junos_ssh_access_t *ssh,
+               char *msg_prefix, ...)
+{
+       va_list ap;
+       int status;
+
+       va_start(ap, msg_prefix);
+       status = junos_set_ssh_verror(err, ssh, msg_prefix, ap);
+       va_end(ap);
+
+       return status;
+} /* junos_set_ssh_error */
+
+int
+junos_set_ssh_verror(junos_error_t *err, junos_ssh_access_t *ssh,
+               char *msg_prefix, va_list ap)
+{
+       char *err_msg = NULL;
+
+       char prefix[1024];
+
+       vsnprintf(prefix, sizeof(prefix), msg_prefix, ap);
+       prefix[sizeof(prefix) - 1] = '\0';
+
+       if (! ssh->session) {
+               err->type  = JUNOS_ACCESS_ERROR;
+               err->error = -1;
+               snprintf(err->errmsg, sizeof(err->errmsg),
+                               "%s: SSH session not initialized", prefix);
+               return 0;
+       }
+
+       err->type  = JUNOS_ACCESS_ERROR;
+       err->error = libssh2_session_last_error(ssh->session, &err_msg,
+                       /* errmsg_len = */ NULL, /* want_buf = */ 0);
+
+       if (! err->error) {
+               junos_clear_error(ssh->junos);
+               return 0;
+       }
+
+       snprintf(err->errmsg, sizeof(err->errmsg), "%s: %s", prefix, err_msg);
+       return 0;
+} /* ssh_set_verror */
+
+/* error handling */
+
+/* vim: set tw=78 sw=4 ts=4 noexpandtab : */
+
diff --git a/src/junos.c b/src/junos.c
new file mode 100644 (file)
index 0000000..6a0fea6
--- /dev/null
@@ -0,0 +1,503 @@
+/*
+ * libJUNOS - src/junos.c
+ * Copyright (C) 2012 Sebastian 'tokkee' Harl <sh@tokkee.org>
+ * 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 COPYRIGHT HOLDERS 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 COPYRIGHT HOLDERS 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.
+ */
+
+/*
+ * Base object used to manage the connection to a JUNOS device.
+ */
+
+#include "junos.h"
+
+#include "libjunos_features.h"
+
+#include <errno.h>
+
+#include <stdlib.h>
+#include <string.h>
+
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <netdb.h>
+
+#include <libssh2.h>
+
+#include <libxml/tree.h>
+#include <libxml/parser.h>
+
+#ifndef LIBXML_PUSH_ENABLED
+#      error "libxml has not been compiled with push parser support"
+#endif
+
+/*
+ * private data structures
+ */
+
+struct junos {
+       char *hostname;
+       char *username;
+       char *password;
+
+       void *access;
+
+       xmlParserCtxtPtr xml_ctx;
+
+       junos_error_t err;
+};
+
+/*
+ * private helper functions
+ */
+
+static ssize_t
+read_lines(junos_t *junos, char *buf, size_t buf_len)
+{
+       ssize_t count = 0;
+
+       while (42) {
+               ssize_t status;
+
+               /* junos_ssh_recv requires at least two bytes */
+               if (buf_len - 2 < (size_t)count) {
+                       dprintf("Receive buffer too small\n");
+                       break;
+               }
+
+               status = junos_ssh_recv(junos->access,
+                               buf + count, buf_len - (size_t)count);
+               if (status < 0) {
+                       count = -1;
+                       break;
+               }
+
+               if (! status)
+                       if (count)
+                               break;
+                       /* else: retry */
+
+               count += status;
+
+               if (buf[count - 1] == '\n')
+                       break;
+       }
+       return count;
+} /* read_line */
+
+/*
+ * public API
+ */
+
+int
+junos_init(void)
+{
+       int status;
+
+       status = libssh2_init(/* flags = */ 0);
+       if (status < 0) {
+               dprintf("Failed to initialize libssh2 (status %d)\n", status);
+               return status;
+       }
+
+       LIBXML_TEST_VERSION;
+       return 0;
+} /* junos_init */
+
+junos_t *
+junos_new(char *hostname, char *username, char *password)
+{
+       junos_t *junos;
+
+       if ((! hostname) || (! username))
+               return NULL;
+
+       junos = calloc(1, sizeof(*junos));
+       if (! junos)
+               return NULL;
+
+       junos->hostname = strdup(hostname);
+       junos->username = strdup(username);
+       if (password)
+               junos->password = strdup(password);
+
+       if ((! junos->hostname) || (! junos->username)
+                       || (password && (! junos->password))) {
+               junos_free(junos);
+               return NULL;
+       }
+
+       junos->access  = NULL;
+       junos->xml_ctx = NULL;
+
+       junos_clear_error(junos);
+       return junos;
+} /* junos_new */
+
+char *
+junos_get_hostname(junos_t *junos)
+{
+       if (! junos)
+               return NULL;
+       return junos->hostname;
+} /* junos_get_hostname */
+
+char *
+junos_get_username(junos_t *junos)
+{
+       if (! junos)
+               return NULL;
+       return junos->username;
+} /* junos_get_username */
+
+char *
+junos_get_password(junos_t *junos)
+{
+       if (! junos)
+               return NULL;
+       return junos->password;
+} /* junos_get_password */
+
+void
+junos_free(junos_t *junos)
+{
+       if (! junos)
+               return;
+
+       junos_disconnect(junos);
+
+       if (junos->hostname)
+               free(junos->hostname);
+       if (junos->username)
+               free(junos->username);
+       if (junos->password)
+               free(junos->password);
+
+       free(junos);
+} /* junos_free */
+
+int
+junos_connect(junos_t *junos)
+{
+       char recv_buf[4096];
+       ssize_t count = 0;
+       ssize_t status;
+
+       char *tmp;
+
+       char js_handshake[] = "<?xml version=\"1.0\" encoding=\"us-ascii\"?>"
+               "<junoscript version=\"1.0\" os=\"libJUNOS\">";
+
+       if (! junos)
+               return -1;
+
+       junos->access = junos_ssh_new(junos);
+       if (! junos->access)
+               return -1;
+
+       if (junos_ssh_connect(junos->access))
+               return -1;
+
+       while (42) {
+               status = read_lines(junos, recv_buf + count,
+                               sizeof(recv_buf) - (size_t)count);
+               if (status < 0)
+                       break;
+
+               count += status;
+
+               if ((tmp = strstr(recv_buf, "<?xml"))
+                               && strstr(tmp, "<junoscript"))
+                       break;
+       }
+
+       dprintf("Header: %s", recv_buf);
+
+       /* don't send the trailing null byte */
+       status = junos_ssh_send(junos->access,
+                       js_handshake, sizeof(js_handshake) - 1);
+       if (status != (ssize_t)sizeof(js_handshake) - 1) {
+               dprintf("Failed to send JUNOScript handshake (status %d)\n",
+                               (int)status);
+               return -1;
+       }
+
+       read_lines(junos, recv_buf, sizeof(recv_buf));
+       dprintf(" ->  %s", recv_buf);
+       return 0;
+} /* junos_connect */
+
+int
+junos_disconnect(junos_t *junos)
+{
+       if (! junos)
+               return -1;
+
+       if (junos->access)
+               junos_ssh_free(junos->access);
+       junos->access = NULL;
+
+       return 0;
+} /* junos_disconnect */
+
+xmlDocPtr
+junos_simple_command(junos_t *junos, const char *cmd)
+{
+       char cmd_string[1024];
+       char recv_buf[4096];
+       ssize_t status;
+
+       int xml_status;
+
+       xmlDocPtr doc;
+
+       if ((! junos) || (! cmd)) {
+               junos_set_error(junos, JUNOS_SYS_ERROR, EINVAL,
+                               "junos_simple_command() requires the "
+                               "'junos' and 'cmd' arguments");
+               return NULL;
+       }
+
+       if (! junos->access) {
+               junos_set_error(junos, JUNOS_SYS_ERROR, EINVAL,
+                               "Please call junos_connect() before submitting commands");
+               return NULL;
+       }
+
+       snprintf(cmd_string, sizeof(cmd_string),
+                       "<rpc><%s/></rpc>", cmd);
+       status = junos_ssh_send(junos->access, cmd_string, strlen(cmd_string));
+       if (status != (ssize_t)strlen(cmd_string)) {
+               dprintf("Failed to send cmd '%s' (status %d)\n",
+                               cmd_string, (int)status);
+               return NULL;
+       }
+
+       errno = 0;
+       junos->xml_ctx = xmlCreatePushParserCtxt(/* sax = */ NULL,
+                       /* user_data = */ NULL,
+                       /* chunk = */ NULL, /* size = */ 0,
+                       /* filename = */ NULL);
+       if (! junos->xml_ctx) {
+               junos_set_error(junos, JUNOS_SYS_ERROR, errno,
+                               "Failed to create XML parser context");
+               return NULL;
+       }
+
+       while (42) {
+               status = read_lines(junos, recv_buf, sizeof(recv_buf));
+               if (status < 0)
+                       break;
+
+               dprintf(" ->  %s", recv_buf);
+
+               xml_status = xmlParseChunk(junos->xml_ctx, recv_buf, (int)status,
+                               /* terminate = */ 0);
+               if (xml_status) {
+                       junos_set_error(junos, JUNOS_XML_ERROR, xml_status,
+                                       "XML parsing failed");
+                       break;
+               }
+
+               if (strstr(recv_buf, "</rpc-reply>"))
+                       break;
+       }
+
+       /* finish parser */
+       xmlParseChunk(junos->xml_ctx, "", 0, /* terminate = */ 1);
+
+       doc = junos->xml_ctx->myDoc;
+       if (xml_status || (! junos->xml_ctx->wellFormed)) {
+               if ((! xml_status) && (! status))
+                       junos_set_error(junos, JUNOS_XML_ERROR, -1,
+                                       "XML validation failed");
+               if (status >= 0)
+                       status = -1;
+       }
+
+       xmlFreeParserCtxt(junos->xml_ctx);
+       junos->xml_ctx = NULL;
+
+       if (status < 0) {
+               xmlFreeDoc(doc);
+               return NULL;
+       }
+
+       return doc;
+} /* junos_simple_command */
+
+/* error handling */
+
+const char *
+junos_get_errstr(junos_t *junos)
+{
+       if (! junos)
+               return NULL;
+       return junos->err.errmsg;
+} /* junos_get_errstr */
+
+void
+junos_clear_error(junos_t *junos)
+{
+       junos_error_t no_error = JUNOS_NO_ERROR;
+
+       if (! junos)
+               return;
+
+       junos->err = no_error;
+} /* junos_clear_error */
+
+int
+junos_set_error(junos_t *junos, int type, int error,
+               char *msg_prefix, ...)
+{
+       va_list ap;
+       int status;
+
+       va_start(ap, msg_prefix);
+       status = junos_set_verror(junos, type, error, msg_prefix, ap);
+       va_end(ap);
+
+       return status;
+} /* junos_set_error */
+
+int
+junos_set_verror(junos_t *junos, int type, int error,
+               char *msg_prefix, va_list ap)
+{
+       junos_error_t *err;
+
+       char errbuf[1024];
+       const char *err_msg;
+
+       char prefix[1024];
+
+       int status = 0;
+
+       if (! junos)
+               return -1;
+
+       err = &junos->err;
+
+       err->type  = type;
+       err->error = error;
+
+       vsnprintf(prefix, sizeof(prefix), msg_prefix, ap);
+       prefix[sizeof(prefix) - 1] = '\0';
+
+       switch (type) {
+               case JUNOS_OK:
+                       snprintf(err->errmsg, sizeof(err->errmsg),
+                                       "i%s: success", prefix);
+                       break;
+               case JUNOS_SYS_ERROR:
+                       {
+                               int failed = 0;
+
+#if STRERROR_R_CHAR_P
+                               errbuf[0] = '\0';
+                               err_msg = strerror_r(error, errbuf, sizeof(errbuf));
+                               if (! err_msg)
+                                       err_msg = errbuf;
+                               if (! err_msg[0])
+                                       failed = 1;
+#else /* STRERROR_R_CHAR_P */
+                               failed = strerror_r(error, errbuf, sizeof(errbuf));
+                               err_msg = errbuf;
+#endif /* STRERROR_R_CHAR_P */
+
+                               if (failed)
+                                       snprintf(err->errmsg, sizeof(err->errmsg),
+                                                       "%s: system error #%i", prefix, error);
+                               else
+                                       snprintf(err->errmsg, sizeof(err->errmsg),
+                                                       "%s: %s", prefix, err_msg);
+                       }
+                       break;
+               case JUNOS_GAI_ERROR:
+                       if (error == EAI_SYSTEM)
+                               return junos_set_error(junos, JUNOS_SYS_ERROR, error,
+                                               "%s: network address translation failed", prefix);
+
+                       err_msg = gai_strerror(error);
+                       if (err_msg)
+                               snprintf(err->errmsg, sizeof(err->errmsg),
+                                               "%s: %s", prefix, err_msg);
+                       else
+                               snprintf(err->errmsg, sizeof(err->errmsg),
+                                               "%s: network address translation error #%i",
+                                               prefix, error);
+                       break;
+               case JUNOS_XML_ERROR:
+                       {
+                               xmlErrorPtr xml_err;
+
+                               if (! junos->xml_ctx) /* don't touch any error information */
+                                       return 0;
+
+                               xml_err = xmlCtxtGetLastError(junos->xml_ctx);
+                               if (! xml_err)
+                                       return 0;
+
+                               err->error = xml_err->code;
+                               snprintf(err->errmsg, sizeof(err->errmsg),
+                                               "%s: %s", prefix, xml_err->message);
+                       }
+                       break;
+               case JUNOS_ACCESS_ERROR:
+                       status = junos_set_ssh_error(err, junos->access,
+                                       "%s", prefix);
+                       break;
+               default:
+                       return -1;
+                       break;
+       }
+
+       err->errmsg[sizeof(err->errmsg) - 1] = '\0';
+       dprintf("ERROR: %s\n", err->errmsg);
+
+       return status;
+} /* junos_set_verror */
+
+/* features */
+
+unsigned int
+libjunos_version(void)
+{
+       return LIBJUNOS_VERSION;
+} /* libjunos_version */
+
+const char *
+libjunos_version_string(void)
+{
+       return LIBJUNOS_VERSION_STRING;
+} /* libjunos_version_string */
+
+const char *
+libjunos_version_extra(void)
+{
+       return LIBJUNOS_VERSION_EXTRA;
+} /* libjunos_version_extra */
+
+/* vim: set tw=78 sw=4 ts=4 noexpandtab : */
+
diff --git a/src/junos.h b/src/junos.h
new file mode 100644 (file)
index 0000000..a88ad76
--- /dev/null
@@ -0,0 +1,181 @@
+/*
+ * libJUNOS - src/junos.h
+ * Copyright (C) 2012 Sebastian 'tokkee' Harl <sh@tokkee.org>
+ * 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 COPYRIGHT HOLDERS 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 COPYRIGHT HOLDERS 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.
+ */
+
+/*
+ * Base object used to manage the connection to a JUNOS device.
+ */
+
+#ifndef JUNOS_H
+#define JUNOS_H 1
+
+#include <stdio.h>
+
+#include <libxml/tree.h>
+
+#if defined(DEBUG) && (! defined(NDEBUG))
+#      define dprintf(...) fprintf(stderr, "LIBJUNOS DEBUG: "__VA_ARGS__)
+#else
+#      define dprintf(...) /* noop */
+#endif
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/*
+ * data types
+ */
+
+typedef struct junos junos_t;
+
+typedef struct {
+       char *machine;
+       char *login;
+       char *password;
+       /* we don't care for 'account' or 'macdef' */
+} junos_netrc_entry_t;
+
+typedef struct junos_netrc junos_netrc_t;
+
+/* access types */
+
+typedef struct junos_ssh_access junos_ssh_access_t;
+
+/* error information */
+
+enum {
+       JUNOS_OK = 0,
+       JUNOS_SYS_ERROR,
+       JUNOS_GAI_ERROR,
+       JUNOS_XML_ERROR,
+       JUNOS_ACCESS_ERROR
+};
+
+typedef struct {
+       int  type;
+       int  error;
+       char errmsg[1024];
+} junos_error_t;
+
+#define JUNOS_NO_ERROR { JUNOS_OK, 0, "" }
+
+/*
+ * JUNOS object
+ */
+
+int
+junos_init(void);
+
+junos_t *
+junos_new(char *hostname, char *username, char *password);
+
+char *
+junos_get_hostname(junos_t *junos);
+char *
+junos_get_username(junos_t *junos);
+char *
+junos_get_password(junos_t *junos);
+
+int
+junos_connect(junos_t *junos);
+
+xmlDocPtr
+junos_simple_command(junos_t *junos, const char *cmd);
+
+int
+junos_disconnect(junos_t *junos);
+
+void
+junos_free(junos_t *junos);
+
+/*
+ * netrc
+ */
+
+junos_netrc_t *
+junos_netrc_read(char *filename);
+
+void
+junos_netrc_free(junos_netrc_t *netrc);
+
+const junos_netrc_entry_t *
+junos_netrc_lookup(junos_netrc_t *netrc, char *hostname);
+
+/*
+ * error handling
+ */
+
+const char *
+junos_get_errstr(junos_t *junos);
+
+int
+junos_set_error(junos_t *junos, int type, int error,
+               char *msg_prefix, ...);
+int
+junos_set_verror(junos_t *junos, int type, int error,
+               char *msg_prefix, va_list ap);
+
+int
+junos_set_ssh_error(junos_error_t *err, junos_ssh_access_t *ssh,
+               char *msg_prefix, ...);
+int
+junos_set_ssh_verror(junos_error_t *err, junos_ssh_access_t *ssh,
+               char *msg_prefix, va_list ap);
+
+void
+junos_clear_error(junos_t *junos);
+
+/*
+ * SSH
+ */
+
+junos_ssh_access_t *
+junos_ssh_new(junos_t *junos);
+
+int
+junos_ssh_connect(junos_ssh_access_t *ssh);
+
+ssize_t
+junos_ssh_recv(junos_ssh_access_t *ssh, char *buf, size_t buf_len);
+
+ssize_t
+junos_ssh_send(junos_ssh_access_t *ssh, char *buf, size_t buf_len);
+
+int
+junos_ssh_disconnect(junos_ssh_access_t *ssh);
+
+int
+junos_ssh_free(junos_ssh_access_t *ssh);
+
+#ifdef __cplusplus
+} /* extern "C" */
+#endif
+
+#endif /* ! JUNOS_H */
+
+/* vim: set tw=78 sw=4 ts=4 noexpandtab : */
+
diff --git a/src/junosc.c b/src/junosc.c
new file mode 100644 (file)
index 0000000..3bbb4c7
--- /dev/null
@@ -0,0 +1,208 @@
+/*
+ * libJUNOS - src/junosc.c
+ * Copyright (C) 2012 Sebastian 'tokkee' Harl <sh@tokkee.org>
+ * 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 COPYRIGHT HOLDERS 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 COPYRIGHT HOLDERS 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.
+ */
+
+/*
+ * A JUNOScript client application.
+ */
+
+#if HAVE_CONFIG_H
+#      include "config.h"
+#endif /* HAVE_CONFIG_H */
+
+#include "junos.h"
+#include "libjunos_features.h"
+
+#if HAVE_LIBGEN_H
+#      include <libgen.h>
+#else /* HAVE_LIBGEN_H */
+#      define basename(path) (path)
+#endif /* ! HAVE_LIBGEN_H */
+
+#include <stdio.h>
+#include <stdlib.h>
+
+#include <unistd.h>
+
+static void
+exit_usage(char *name, int status)
+{
+       printf(
+"Usage: %s -H <host> -u <user> [<options>] <command>\n"
+
+"\nOptions:\n"
+"  -H <host>    hostname to connect to\n"
+"  -u <user>    username to connect with\n"
+"  -p <pass>    password to connect with\n"
+"  -n           do not use .netrc for username/password lookup\n"
+"\n"
+"  -h           display this help and exit\n"
+"  -V           display the version number and copyright\n"
+
+"\njunosc "LIBJUNOS_VERSION_STRING LIBJUNOS_VERSION_EXTRA", "PACKAGE_URL"\n",
+basename(name));
+       exit(status);
+} /* exit_usage */
+
+static void
+exit_version(void)
+{
+       printf("junosc version "LIBJUNOS_VERSION_STRING LIBJUNOS_VERSION_EXTRA", "
+                       "built "BUILD_DATE"\n"
+                       "Copyright (C) 2012 "PACKAGE_MAINTAINER"\n"
+
+                       "\nThis is free software under the terms of the BSD license, see "
+                       "the source for\ncopying conditions. There is NO WARRANTY; not "
+                       "even for MERCHANTABILITY or\nFITNESS FOR A PARTICULAR "
+                       "PURPOSE.\n");
+       exit(0);
+} /* exit_version */
+
+int
+main(int argc, char **argv)
+{
+       junos_netrc_t *netrc;
+
+       int use_netrc  = 1;
+
+       char *hostname = NULL;
+       char *username = NULL;
+       char *password = NULL;
+
+       char *command  = NULL;
+
+       junos_t *junos;
+       xmlDocPtr doc;
+
+       while (42) {
+               int opt = getopt(argc, argv, "H:u:p:nhV");
+
+               if (-1 == opt)
+                       break;
+
+               switch (opt) {
+                       case 'H':
+                               hostname = optarg;
+                               break;
+                       case 'u':
+                               username = optarg;
+                               break;
+                       case 'p':
+                               password = optarg;
+                               break;
+                       case 'n':
+                               use_netrc = 0;
+                               break;
+
+                       case 'h':
+                               exit_usage(argv[0], 0);
+                               break;
+                       case 'V':
+                               exit_version();
+                               break;
+                       default:
+                               exit_usage(argv[0], 1);
+               }
+       }
+
+       if (! hostname) {
+               fprintf(stderr, "Missing hostname\n");
+               exit_usage(argv[0], 1);
+       }
+
+       if (optind != argc - 1) {
+               fprintf(stderr, "Missing command name\n");
+               exit_usage(argv[0], 1);
+       }
+
+       command = argv[optind];
+       ++optind;
+
+       if (use_netrc
+                       && (netrc = junos_netrc_read(/* filename = default */ NULL))) {
+               const junos_netrc_entry_t *entry;
+
+               entry = junos_netrc_lookup(netrc, hostname);
+               if (entry) {
+                       if ((! username) && (entry->login)) {
+                               dprintf("Using username '%s' from netrc\n", entry->login);
+                               username = entry->login;
+                       }
+
+                       if ((! password) && (entry->password)) {
+                               dprintf("Using password from netrc\n");
+                               password = entry->password;
+                       }
+               }
+       }
+
+       if (! username)
+               username = getlogin();
+
+       if (! username) {
+               fprintf(stderr, "Missing username\n");
+               if (netrc)
+                       junos_netrc_free(netrc);
+               exit_usage(argv[0], 1);
+       }
+
+       if (junos_init()) {
+               fprintf(stderr, "FATAL: Failed to initialize libJUNOS. Aborting.\n");
+               exit(1);
+       }
+
+       junos = junos_new(hostname, username, password);
+       if (! junos) {
+               fprintf(stderr, "FATAL: Failed to create JUNOS object!\n");
+               exit(1);
+       }
+
+       if (junos_connect(junos)) {
+               fprintf(stderr, "Failed to connect: %s\n", junos_get_errstr(junos));
+               junos_free(junos);
+               exit(1);
+       }
+
+       junos_clear_error(junos);
+       doc = junos_simple_command(junos, command);
+       if (doc) {
+               xmlDocFormatDump(stderr, doc, /* format = */ 1);
+               xmlFreeDoc(doc);
+       }
+       else {
+               fprintf(stderr, "Command failed: %s\n", junos_get_errstr(junos));
+       }
+
+       junos_disconnect(junos);
+       junos_free(junos);
+
+       if (netrc)
+               junos_netrc_free(netrc);
+       return 0;
+} /* main */
+
+/* vim: set tw=78 sw=4 ts=4 noexpandtab : */
+
diff --git a/src/libjunos_features.h.in b/src/libjunos_features.h.in
new file mode 100644 (file)
index 0000000..c08aacd
--- /dev/null
@@ -0,0 +1,69 @@
+/*
+ * libJUNOS - src/libjunos_features.h
+ * Copyright (C) 2012 Sebastian 'tokkee' Harl <sh@tokkee.org>
+ * 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 COPYRIGHT HOLDERS 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 COPYRIGHT HOLDERS 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.
+ */
+
+/*
+ * JUNOScript client library.
+ */
+
+#ifndef LIBJUNOS_FEATURES_H
+#define LIBJUNOS_FEATURES_H 1
+
+#define LIBJUNOS_VERSION_MAJOR @LIBJUNOS_VERSION_MAJOR@
+#define LIBJUNOS_VERSION_MINOR @LIBJUNOS_VERSION_MINOR@
+#define LIBJUNOS_VERSION_PATCH @LIBJUNOS_VERSION_PATCH@
+
+#define LIBJUNOS_VERSION_EXTRA "@LIBJUNOS_VERSION_EXTRA@"
+
+#define LIBJUNOS_VERSION_STRING "@LIBJUNOS_VERSION_STRING@"
+
+#define LIBJUNOS_VERSION_ENCODE(major, minor, patch) \
+       ((major) * 10000 + (minor) * 100 + (patch))
+
+#define LIBJUNOS_VERSION LIBJUNOS_VERSION_ENCODE(LIBJUNOS_VERSION_MAJOR, \
+               LIBJUNOS_VERSION_MINOR, LIBJUNOS_VERSION_PATCH)
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+unsigned int
+libjunos_version(void);
+
+const char *
+libjunos_version_string(void);
+
+const char *
+libjunos_version_extra(void);
+
+#ifdef __cplusplus
+} /* extern "C" */
+#endif
+
+#endif /* ! LIBJUNOS_FEATURES_H */
+
+/* vim: set tw=78 sw=4 ts=4 noexpandtab : */
+
diff --git a/src/netrc.c b/src/netrc.c
new file mode 100644 (file)
index 0000000..34605b7
--- /dev/null
@@ -0,0 +1,268 @@
+/*
+ * libJUNOS - src/netrc.c
+ * Copyright (C) 2012 Sebastian 'tokkee' Harl <sh@tokkee.org>
+ * 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 COPYRIGHT HOLDERS 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 COPYRIGHT HOLDERS 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.
+ */
+
+/*
+ * Access .netrc information.
+ */
+
+#include "junos.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+
+#include <string.h>
+#include <strings.h>
+
+/*
+ * private data structures
+ */
+
+enum {
+       TOKEN_NONE = 0,
+       TOKEN_MACHINE,
+       TOKEN_LOGIN,
+       TOKEN_PASSWORD,
+       TOKEN_ACCOUNT,
+       TOKEN_MACDEF,
+};
+
+struct junos_netrc {
+       char *filename;
+
+       junos_netrc_entry_t *entries;
+       size_t               entries_num;
+};
+
+/*
+ * private helper functions
+ */
+
+static int
+netrc_add_machine(junos_netrc_t *netrc)
+{
+       junos_netrc_entry_t *tmp;
+
+       tmp = realloc(netrc->entries, netrc->entries_num + 1);
+       if (! tmp)
+               return -1;
+
+       netrc->entries = tmp;
+       ++netrc->entries_num;
+
+       memset(netrc->entries + (netrc->entries_num - 1), 0,
+                       sizeof(*netrc->entries));
+       return 0;
+} /* netrc_add_machine */
+
+static int
+netrc_set_value(junos_netrc_t *netrc, int token, char *value)
+{
+       junos_netrc_entry_t *entry;
+       char **target = NULL;
+
+       if (! netrc->entries_num)
+               return -1;
+
+       entry = netrc->entries + (netrc->entries_num - 1);
+
+       switch (token) {
+               case TOKEN_NONE: /* fall thru */
+               case TOKEN_MACDEF: /* fall thru */
+               case TOKEN_ACCOUNT:
+                       break;
+               case TOKEN_MACHINE:
+                       target = &entry->machine;
+                       break;
+               case TOKEN_LOGIN:
+                       target = &entry->login;
+                       break;
+               case TOKEN_PASSWORD:
+                       target = &entry->password;
+                       break;
+       }
+
+       if (target) {
+               if (*target)
+                       free(*target);
+               *target = strdup(value);
+       }
+       return 0;
+} /* netrc_set_value */
+
+static int
+netrc_parse_line(char *line, junos_netrc_t *netrc, int *last_token)
+{
+       char *lasts_ptr = NULL;
+       char *token;
+
+       if ((*last_token == TOKEN_MACDEF) && (line[0] != '\0'))
+               return 0; /* skip line */
+
+       while ((token = strtok_r(line, " \t\r\n", &lasts_ptr))) {
+               line = NULL;
+
+               if (*last_token != TOKEN_NONE) {
+                       netrc_set_value(netrc, *last_token, token);
+                       *last_token = TOKEN_NONE;
+               }
+               else if (! strcasecmp(token, "machine")) {
+                       *last_token = TOKEN_MACHINE;
+                       if (netrc_add_machine(netrc))
+                               return -1;
+               }
+               else if (! strcasecmp(token, "default")) {
+                       *last_token = TOKEN_NONE;
+                       if (netrc_add_machine(netrc))
+                               return -1;
+               }
+               else if (! strcasecmp(token, "login"))
+                       *last_token = TOKEN_LOGIN;
+               else if (! strcasecmp(token, "password"))
+                       *last_token = TOKEN_PASSWORD;
+               else if (! strcasecmp(token, "account"))
+                       *last_token = TOKEN_ACCOUNT;
+               else if (! strcasecmp(token, "macdef")) {
+                       *last_token = TOKEN_MACDEF;
+                       return 0;
+               }
+       }
+       return 0;
+} /* netrc_parse_line */
+
+static int
+netrc_read(FILE *fh, junos_netrc_t *netrc)
+{
+       char  line_buf[1024];
+       char *line;
+
+       int status;
+       int last_token = TOKEN_NONE;
+
+       while ((line = fgets(line_buf, sizeof(line_buf), fh)))
+               if ((status = netrc_parse_line(line, netrc, &last_token)))
+                       return status;
+
+       if (! feof(fh))
+               return -1;
+       return 0;
+} /* netrc_read */
+
+/*
+ * public API
+ */
+
+junos_netrc_t *
+junos_netrc_read(char *filename)
+{
+       char  buf[1024];
+       FILE *fh;
+
+       junos_netrc_t *netrc;
+
+       if (! filename) {
+               char *home_dir = getenv("HOME");
+               if (! home_dir) {
+                       dprintf("Failed to determine home directory\n");
+                       return NULL;
+               }
+
+               snprintf(buf, sizeof(buf), "%s/.netrc", home_dir);
+               filename = buf;
+       }
+
+       fh = fopen(filename, "r");
+       if (! fh) {
+               dprintf("Failed to open '%s'\n", filename);
+               return NULL;
+       }
+
+       netrc = calloc(1, sizeof(*netrc));
+       if (netrc)
+               netrc->filename = strdup(filename);
+
+       if ((! netrc) || (! netrc->filename)) {
+               dprintf("Failed to allocate libJUNOS netrc object\n");
+               fclose(fh);
+               junos_netrc_free(netrc);
+               return NULL;
+       }
+
+       if (netrc_read(fh, netrc)) {
+               dprintf("Failed to parse .netrc\n");
+               fclose(fh);
+               junos_netrc_free(netrc);
+               return NULL;
+       }
+
+       fclose(fh);
+       return netrc;
+} /* junos_netrc_read */
+
+void
+junos_netrc_free(junos_netrc_t *netrc)
+{
+       size_t i;
+
+       if (! netrc)
+               return;
+
+       if (netrc->filename)
+               free(netrc->filename);
+
+       for (i = 0; i < netrc->entries_num; ++i) {
+               junos_netrc_entry_t *entry = netrc->entries + i;
+
+               if (entry->machine)
+                       free(entry->machine);
+               if (entry->login)
+                       free(entry->login);
+               if (entry->password)
+                       free(entry->password);
+       }
+       free(netrc->entries);
+       free(netrc);
+} /* junos_netrc_free */
+
+const junos_netrc_entry_t *
+junos_netrc_lookup(junos_netrc_t *netrc, char *hostname)
+{
+       size_t i;
+
+       if ((! netrc) || (! hostname))
+               return NULL;
+
+       for (i = 0; i < netrc->entries_num; ++i) {
+               junos_netrc_entry_t *entry = netrc->entries + i;
+
+               if ((! entry->machine) || (! strcasecmp(hostname, entry->machine)))
+                       return entry;
+       }
+       return NULL;
+} /* junos_netrc_lookup */
+
+/* vim: set tw=78 sw=4 ts=4 noexpandtab : */
+
diff --git a/version-gen.sh b/version-gen.sh
new file mode 100755 (executable)
index 0000000..be9b446
--- /dev/null
@@ -0,0 +1,44 @@
+#! /bin/sh
+
+DEFAULT_VERSION="0.0.0.git"
+
+VERSION="$( git describe --tags 2> /dev/null \
+       | sed -e 's/libjunos-//' || true )"
+
+if test -z "$VERSION"; then
+       VERSION="$DEFAULT_VERSION"
+else
+       git update-index -q --refresh || true
+       if test -n "$( git diff-index --name-only HEAD || true )"; then
+               VERSION="$VERSION-dirty"
+       fi
+fi
+
+VERSION="$( echo "$VERSION" | sed -e 's/-/./g' )"
+if test "x`uname -s`" = "xAIX" || test "x`uname -s`" = "xSunOS" ; then
+       echo "$VERSION\c"
+else
+       echo -n "$VERSION"
+fi
+
+OLD_VERSION=""
+if test -e version; then
+       OLD_VERSION=$( sed -ne 's/^VERSION="\(.*\)"/\1/p' version )
+fi
+
+if test "$OLD_VERSION" != "$VERSION"; then
+       VERSION_MAJOR=$( echo $VERSION | cut -d'.' -f1 )
+       VERSION_MINOR=$( echo $VERSION | cut -d'.' -f2 )
+       VERSION_PATCH=$( echo $VERSION | cut -d'.' -f3 )
+       VERSION_EXTRA="\"$( echo $VERSION | cut -d'.' -f4- )\""
+       test -z "$VERSION_EXTRA" || VERSION_EXTRA=".$VERSION_EXTRA"
+       (
+        echo "VERSION=\"$VERSION\""
+        echo "VERSION_MAJOR=$VERSION_MAJOR"
+        echo "VERSION_MINOR=$VERSION_MINOR"
+        echo "VERSION_PATCH=$VERSION_PATCH"
+        echo "VERSION_EXTRA=\"$VERSION_EXTRA\""
+        echo "VERSION_STRING=\"$VERSION_MAJOR.$VERSION_MINOR.$VERSION_PATCH\""
+       ) > version
+fi
+