author | Sebastian Harl <sh@teamix.net> | |
Sun, 9 Sep 2012 11:48:31 +0000 (13:48 +0200) | ||
committer | Sebastian 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
- 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] | patch | blob |
COPYING | [new file with mode: 0644] | patch | blob |
INSTALL | [new file with mode: 0644] | patch | blob |
Makefile.am | [new file with mode: 0644] | patch | blob |
README | [new file with mode: 0644] | patch | blob |
autogen.sh | [new file with mode: 0755] | patch | blob |
configure.ac | [new file with mode: 0644] | patch | blob |
doc/Makefile.am | [new file with mode: 0644] | patch | blob |
doc/junosc.txt | [new file with mode: 0644] | patch | blob |
src/Makefile.am | [new file with mode: 0644] | patch | blob |
src/access_ssh.c | [new file with mode: 0644] | patch | blob |
src/junos.c | [new file with mode: 0644] | patch | blob |
src/junos.h | [new file with mode: 0644] | patch | blob |
src/junosc.c | [new file with mode: 0644] | patch | blob |
src/libjunos_features.h.in | [new file with mode: 0644] | patch | blob |
src/netrc.c | [new file with mode: 0644] | patch | blob |
version-gen.sh | [new file with mode: 0755] | patch | blob |
diff --git a/.gitignore b/.gitignore
--- /dev/null
+++ b/.gitignore
@@ -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
--- /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
--- /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
--- /dev/null
+++ b/Makefile.am
@@ -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
--- /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
--- /dev/null
+++ b/autogen.sh
@@ -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
--- /dev/null
+++ b/configure.ac
@@ -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
--- /dev/null
+++ b/doc/Makefile.am
@@ -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
--- /dev/null
+++ b/doc/junosc.txt
@@ -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
--- /dev/null
+++ b/src/Makefile.am
@@ -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
--- /dev/null
+++ b/src/access_ssh.c
@@ -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
--- /dev/null
+++ b/src/junos.c
@@ -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
--- /dev/null
+++ b/src/junos.h
@@ -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
--- /dev/null
+++ b/src/junosc.c
@@ -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
--- /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
--- /dev/null
+++ b/src/netrc.c
@@ -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
--- /dev/null
+++ b/version-gen.sh
@@ -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
+