From: Sebastian Harl Date: Sun, 9 Sep 2012 11:48:31 +0000 (+0200) Subject: Initial commit. X-Git-Tag: libjunos-0.0.0^0 X-Git-Url: https://git.tokkee.org/?a=commitdiff_plain;h=b54725f6d2bb1a9be12e023211d5e5c14fa306bb;p=libjunos.git Initial commit. * 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 --- b54725f6d2bb1a9be12e023211d5e5c14fa306bb diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..3f060a3 --- /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 new file mode 100644 index 0000000..b90bd54 --- /dev/null +++ b/COPYING @@ -0,0 +1,24 @@ +Copyright (c) 2012 Sebastian 'tokkee' Harl +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 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 index 0000000..3fab3fa --- /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 new file mode 100644 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= to `make install', will be + prefixed to all installation directories. This might be useful when creating + packages for libJUNOS. + +Author +------ + + Sebastian "tokkee" Harl + diff --git a/autogen.sh b/autogen.sh new file mode 100755 index 0000000..7c7c72f --- /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 new file mode 100644 index 0000000..a223f6d --- /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 +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 " +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 index 0000000..b9a7f7d --- /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 new file mode 100644 index 0000000..b22c5a5 --- /dev/null +++ b/doc/junosc.txt @@ -0,0 +1,54 @@ +JUNOSC(1) +========= +Sebastian "tokkee" Harl +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 . + +COPYRIGHT +--------- +Copyright (C) 2012 Sebastian "tokkee" Harl + +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 index 0000000..ffee374 --- /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 new file mode 100644 index 0000000..5e66599 --- /dev/null +++ b/src/access_ssh.c @@ -0,0 +1,571 @@ +/* + * libJUNOS - src/access_ssh.c + * Copyright (C) 2012 Sebastian 'tokkee' Harl + * 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 + +#include + +#include +#include + +#include +#include + +/* + * 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 index 0000000..6a0fea6 --- /dev/null +++ b/src/junos.c @@ -0,0 +1,503 @@ +/* + * libJUNOS - src/junos.c + * Copyright (C) 2012 Sebastian 'tokkee' Harl + * 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 + +#include +#include + +#include +#include +#include + +#include + +#include +#include + +#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[] = "" + ""; + + 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, "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), + "<%s/>", 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, "")) + 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 index 0000000..a88ad76 --- /dev/null +++ b/src/junos.h @@ -0,0 +1,181 @@ +/* + * libJUNOS - src/junos.h + * Copyright (C) 2012 Sebastian 'tokkee' Harl + * 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 + +#include + +#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 index 0000000..3bbb4c7 --- /dev/null +++ b/src/junosc.c @@ -0,0 +1,208 @@ +/* + * libJUNOS - src/junosc.c + * Copyright (C) 2012 Sebastian 'tokkee' Harl + * 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 +#else /* HAVE_LIBGEN_H */ +# define basename(path) (path) +#endif /* ! HAVE_LIBGEN_H */ + +#include +#include + +#include + +static void +exit_usage(char *name, int status) +{ + printf( +"Usage: %s -H -u [] \n" + +"\nOptions:\n" +" -H hostname to connect to\n" +" -u username to connect with\n" +" -p 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 index 0000000..c08aacd --- /dev/null +++ b/src/libjunos_features.h.in @@ -0,0 +1,69 @@ +/* + * libJUNOS - src/libjunos_features.h + * Copyright (C) 2012 Sebastian 'tokkee' Harl + * 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 index 0000000..34605b7 --- /dev/null +++ b/src/netrc.c @@ -0,0 +1,268 @@ +/* + * libJUNOS - src/netrc.c + * Copyright (C) 2012 Sebastian 'tokkee' Harl + * 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 +#include + +#include +#include + +/* + * 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 index 0000000..be9b446 --- /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 +