summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorQuentin Young <qlyoung@cumulusnetworks.com>2016-11-12 06:17:37 +0100
committerQuentin Young <qlyoung@cumulusnetworks.com>2016-11-12 06:17:37 +0100
commit07321a065d1126c00766ca3d6698e57936699f82 (patch)
treea7dac2bb2248d87f40b1c3c2af7440d9bea35421
parentlib, vtysh: Fix memory leaks, change cmd_element to const (diff)
parentMerge remote-tracking branch 'opensourcerouting/cmaster-next-vty2' into cmast... (diff)
downloadfrr-07321a065d1126c00766ca3d6698e57936699f82.tar.xz
frr-07321a065d1126c00766ca3d6698e57936699f82.zip
Merge branch 'cmaster-next' into vtysh-grammar
Signed-off-by: Quentin Young <qlyoung@cumulusnetworks.com> Conflicts: lib/command.c lib/vty.c
-rwxr-xr-xconfigure.ac12
-rw-r--r--doc/defines.texi.in1
-rw-r--r--doc/install.texi27
-rw-r--r--doc/ipv6.texi3
-rw-r--r--doc/vtysh.texi174
-rw-r--r--lib/command.c27
-rw-r--r--lib/command.h2
-rw-r--r--lib/log.c3
-rw-r--r--lib/log.h1
-rw-r--r--lib/network.c14
-rw-r--r--lib/network.h2
-rw-r--r--lib/pid_output.c3
-rw-r--r--lib/privs.c51
-rw-r--r--lib/vty.c17
-rwxr-xr-xtools/quagga14
-rw-r--r--vtysh/Makefile.am1
-rw-r--r--vtysh/vtysh.c164
-rw-r--r--vtysh/vtysh.h9
-rw-r--r--vtysh/vtysh_config.c1
-rw-r--r--vtysh/vtysh_main.c65
-rw-r--r--watchquagga/Makefile.am4
-rw-r--r--watchquagga/watchquagga.c57
-rw-r--r--watchquagga/watchquagga.h29
-rw-r--r--watchquagga/watchquagga_vty.c134
24 files changed, 611 insertions, 204 deletions
diff --git a/configure.ac b/configure.ac
index cfadfbc01..afb0731db 100755
--- a/configure.ac
+++ b/configure.ac
@@ -1746,8 +1746,20 @@ AC_DEFINE_UNQUOTED(OSPF6_VTYSH_PATH, "$quagga_statedir/ospf6d.vty",ospf6d vty so
AC_DEFINE_UNQUOTED(LDP_VTYSH_PATH, "$quagga_statedir/ldpd.vty",ldpd vty socket)
AC_DEFINE_UNQUOTED(ISIS_VTYSH_PATH, "$quagga_statedir/isisd.vty",isisd vty socket)
AC_DEFINE_UNQUOTED(PIM_VTYSH_PATH, "$quagga_statedir/pimd.vty",pimd vty socket)
+AC_DEFINE_UNQUOTED(WATCHQUAGGA_VTYSH_PATH, "$quagga_statedir/watchquagga.vty",watchquagga vty socket)
AC_DEFINE_UNQUOTED(DAEMON_VTY_DIR, "$quagga_statedir",daemon vty directory)
+dnl autoconf does this, but it does it too late...
+test "x$prefix" = xNONE && prefix=$ac_default_prefix
+test "x$exec_prefix" = xNONE && exec_prefix='${prefix}'
+
+dnl get the full path, recursing through variables...
+vtysh_bin="$bindir/vtysh"
+for I in 1 2 3 4 5 6 7 8 9 10; do
+ eval vtysh_bin="\"$vtysh_bin\""
+done
+AC_DEFINE_UNQUOTED(VTYSH_BIN_PATH, "$vtysh_bin",path to vtysh binary)
+
dnl -------------------------------
dnl Quagga sources should always be
dnl current wrt interfaces. Dont
diff --git a/doc/defines.texi.in b/doc/defines.texi.in
index a4badef0e..5436f20c3 100644
--- a/doc/defines.texi.in
+++ b/doc/defines.texi.in
@@ -13,3 +13,4 @@
@set INSTALL_PREFIX_ETC /etc/quagga
@set INSTALL_PREFIX_SBIN /usr/sbin
@set INSTALL_PREFIX_STATE /var/run/quagga
+@set INSTALL_VTY_GROUP @enable_vty_group@
diff --git a/doc/install.texi b/doc/install.texi
index 811ad9ae8..3ace7d6a0 100644
--- a/doc/install.texi
+++ b/doc/install.texi
@@ -44,15 +44,10 @@ commands:
@cindex Options to @code{./configure}
Quagga has an excellent configure script which automatically detects most
-host configurations. There are several additional configure options you can
-use to turn off IPv6 support, to disable the compilation of specific
-daemons, and to enable SNMP support.
+host configurations. There are several additional configure options to
+customize the build to include or exclude specific features and dependencies.
@table @option
-@item --disable-ipv6
-Turn off IPv6 related features and daemons. Quagga configure script
-automatically detects IPv6 stack. But sometimes you might want to
-disable IPv6 support of Quagga.
@item --disable-zebra
Do not build zebra daemon.
@item --disable-ripd
@@ -68,28 +63,18 @@ Do not build bgpd.
@item --disable-bgp-announce
Make @command{bgpd} which does not make bgp announcements at all. This
feature is good for using @command{bgpd} as a BGP announcement listener.
-@item --enable-netlink
-Force to enable @sc{gnu}/Linux netlink interface. Quagga configure
-script detects netlink interface by checking a header file. When the header
-file does not match to the current running kernel, configure script will
-not turn on netlink support.
@item --enable-snmp
Enable SNMP support. By default, SNMP support is disabled.
-@item --disable-opaque-lsa
-Disable support for Opaque LSAs (RFC2370) in ospfd.
@item --disable-ospfapi
Disable support for OSPF-API, an API to interface directly with ospfd.
OSPF-API is enabled if --enable-opaque-lsa is set.
@item --disable-ospfclient
Disable building of the example OSPF-API client.
-@item --disable-ospf-te
-Disable support for OSPF Traffic Engineering Extension (RFC3630) this
-requires support for Opaque LSAs.
@item --disable-ospf-ri
Disable support for OSPF Router Information (RFC4970 & RFC5088) this
requires support for Opaque LSAs and Traffic Engineering.
-@item --enable-isisd
-Build isisd.
+@item --disable-isisd
+Do not build isisd.
@item --enable-isis-topology
Enable IS-IS topology generator.
@item --enable-isis-te
@@ -104,7 +89,7 @@ Pass the @command{-rdynamic} option to the linker driver. This is in most
cases neccessary for getting usable backtraces. This option defaults to on
if the compiler is detected as gcc, but giving an explicit enable/disable is
suggested.
-@item --enable-backtrace
+@item --disable-backtrace
Controls backtrace support for the crash handlers. This is autodetected by
default. Using the switch will enforce the requested behaviour, failing with
an error if support is requested but not available. On BSD systems, this
@@ -129,7 +114,7 @@ as pid files and unix sockets.
@end table
@example
-% ./configure --disable-ipv6
+% ./configure --disable-snmp
@end example
This command will configure zebra and the routing daemons.
diff --git a/doc/ipv6.texi b/doc/ipv6.texi
index 5be2babed..e08759ab0 100644
--- a/doc/ipv6.texi
+++ b/doc/ipv6.texi
@@ -8,6 +8,9 @@ automatic address configuration via a feature called @code{address
auto configuration}. To do it, the router must send router advertisement
messages to the all nodes that exist on the network.
+Previous versions of Quagga could be built without IPv6 support. This is
+no longer possible.
+
@menu
* Router Advertisement::
@end menu
diff --git a/doc/vtysh.texi b/doc/vtysh.texi
index 66562a96c..69b7acd3b 100644
--- a/doc/vtysh.texi
+++ b/doc/vtysh.texi
@@ -1,56 +1,105 @@
@node VTY shell
@chapter VTY shell
-@command{vtysh} is integrated shell of Quagga software.
+@menu
+* Integrated configuration mode::
+@end menu
-To use vtysh please specify ---enable-vtysh to configure script. To use
-PAM for authentication use ---with-libpam option to configure script.
+@command{vtysh} provides a combined frontend to all Quagga daemons in a
+single combined session. It is enabled by default at build time, but can
+be disabled through the @option{--disable-vtysh} option to
+@command{./configure}.
+
+@command{vtysh} has a configuration file, @file{vtysh.conf}. The location
+of that file cannot be changed from @file{@value{INSTALL_PREFIX_ETC}} since
+it contains options controlling authentication behavior. This file will
+also not be written by configuration-save commands, it is intended to be
+updated manually by an administrator with an external editor.
+
+@quotation Warning
+This also means the @command{hostname} and @command{banner motd} commands
+(which both do have effect for vtysh) need to be manually updated in
+@file{vtysh.conf}.
+@end quotation
+
+@section Permissions and setup requirements
+
+@command{vtysh} connects to running daemons through Unix sockets located in
+@file{@value{INSTALL_PREFIX_STATE}}. Running vtysh thus requires access to
+that directory, plus membership in the @emph{@value{INSTALL_VTY_GROUP}}
+group (which is the group that the daemons will change ownership of their
+sockets to).
+
+To restrict access to Quagga configuration, make sure no unauthorized users
+are members of the @emph{@value{INSTALL_VTY_GROUP}} group.
+
+@subsection PAM support (experimental)
+
+vtysh has working (but rather useless) PAM support. It will perform
+an "authenticate" PAM call using @emph{@value{PACKAGE_NAME}} as service
+name. No other (accounting, session, password change) calls will be
+performed by vtysh.
+
+Users using vtysh still need to have appropriate access to the daemons'
+VTY sockets, usually by being member of the @emph{@value{INSTALL_VTY_GROUP}}
+group. If they have this membership, PAM support is useless since they can
+connect to daemons and issue commands using some other tool. Alternatively,
+the @command{vtysh} binary could be made SGID (set group ID) to the
+@emph{@value{INSTALL_VTY_GROUP}} group. @strong{No security guarantees are
+made for this configuration}.
-vtysh only searches @value{INSTALL_PREFIX_ETC} path for vtysh.conf which
-is the vtysh configuration file. Vtysh does not search current
-directory for configuration file because the file includes user
-authentication settings.
+@deffn {Command} {username @var{username} nopassword} {}
-Currently, vtysh.conf has only two commands.
+If PAM support is enabled at build-time, this command allows disabling the
+use of PAM on a per-user basis. If vtysh finds that an user is trying to
+use vtysh and a "nopassword" entry is found, no calls to PAM will be made
+at all.
-@menu
-* VTY shell username::
-* VTY shell integrated configuration::
-@end menu
+@end deffn
-@node VTY shell username
-@section VTY shell username
+@node Integrated configuration mode
+@section Integrated configuration mode
-@deffn {Command} {username @var{username} nopassword} {}
+Integrated configuration mode uses a single configuration file,
+@file{Quagga.conf}, for all daemons. This replaces the individual files like
+@file{zebra.conf} or @file{bgpd.conf}.
-With this set, user foo does not need password authentication for user vtysh.
-With PAM vtysh uses PAM authentication mechanism.
+@file{Quagga.conf} is located in @file{@value{INSTALL_PREFIX_ETC}}. All
+daemons check for the existence of this file at startup, and if it exists
+will not load their individual configuration files. Instead,
+@command{vtysh -b} must be invoked to process @file{Quagga.conf} and apply
+its settings to the individual daemons.
-If vtysh is compiled without PAM authentication, every user can use vtysh
-without authentication. vtysh requires read/write permission
-to the various daemons vty sockets, this can be accomplished through use
-of unix groups and the --enable-vty-group configure option.
+@quotation Warning
+@command{vtysh -b} must also be executed after restarting any daemon.
+@end quotation
-@end deffn
+@subsection Configuration saving, file ownership and permissions
-@node VTY shell integrated configuration
-@section VTY shell integrated configuration
+The @file{Quagga.conf} file is not written by any of the daemons; instead
+@command{vtysh} contains the neccessary logic to collect configuration from
+all of the daemons, combine it and write it out.
-@deffn {Command} {service integrated-vtysh-config} {}
-Write out integrated Quagga.conf file when 'write file' is issued.
+@quotation Warning
+Daemons must be running for @command{vtysh} to be able to collect their
+configuration. Any configuration from non-running daemons is permanently
+lost after doing a configuration save.
+@end quotation
+
+Since the @command{vtysh} command may be running as ordinary user on the
+system, configuration writes will be tried through @command{watchquagga},
+using the @command{write integrated} command internally. Since
+@command{watchquagga} is running as superuser, @command{vtysh} is able to
+ensure correct ownership and permissions on @file{Quagga.conf}.
-This command controls the behaviour of vtysh when it is told to write out
-the configuration. Per default, vtysh will instruct each daemon to write
-out their own config files when @command{write file} is issued. However, if
-@command{service integrated-vtysh-config} is set, when @command{write file}
-is issued, vtysh will instruct the daemons will write out a Quagga.conf with
-all daemons' commands integrated into it.
+If @command{watchquagga} is not running or the configuration write fails,
+@command{vtysh} will attempt to directly write to the file. This is likely
+to fail if running as unprivileged user; alternatively it may leave the
+file with incorrect owner or permissions.
-Vtysh per default behaves as if @command{write-conf daemon} is set. Note
-that both may be set at same time if one wishes to have both Quagga.conf and
-daemon specific files written out. Further, note that the daemons are
-hard-coded to first look for the integrated Quagga.conf file before looking
-for their own file.
+Writing the configuration can be triggered directly by invoking
+@command{vtysh -w}. This may be useful for scripting. Note this command
+should be run as either the superuser or the Quagga user.
We recommend you do not mix the use of the two types of files. Further, it
is better not to use the integrated Quagga.conf file, as any syntax error in
@@ -58,4 +107,55 @@ it can lead to /all/ of your daemons being unable to start up. Per daemon
files are more robust as impact of errors in configuration are limited to
the daemon in whose file the error is made.
+@deffn {Command} {service integrated-vtysh-config} {}
+@deffnx {Command} {no service integrated-vtysh-config} {}
+
+Control whether integrated @file{Quagga.conf} file is written when
+'write file' is issued.
+
+These commands need to be placed in @file{vtysh.conf} to have any effect.
+Note that since @file{vtysh.conf} is not written by Quagga itself, they
+therefore need to be manually placed in that file.
+
+This command has 3 states:
+@itemize @bullet
+@item
+@command{service integrated-vtysh-config}
+
+@command{vtysh} will always write @file{Quagga.conf}.
+
+@item
+@command{no service integrated-vtysh-config}
+
+@command{vtysh} will never write @file{Quagga.conf}; instead it will ask
+daemons to write their individual configuration files.
+
+@item
+Neither option present (default)
+
+@command{vtysh} will check whether @file{Quagga.conf} exists. If it does,
+configuration writes will update that file. Otherwise, writes are performed
+through the individual daemons.
+@end itemize
+
+This command is primarily intended for packaging/distribution purposes, to
+preset one of the two operating modes and ensure consistent operation across
+installations.
@end deffn
+
+@deffn {Command} {write integrated} {}
+
+Unconditionally (regardless of @command{service integrated-vtysh-config}
+setting) write out integrated @file{Quagga.conf} file through
+@command{watchquagga}. If @command{watchquagga} is not running, this command
+is unavailable.
+
+@end deffn
+
+@section Caveats
+
+Configuration changes made while some daemon is not running will be invisible
+to that daemon. The daemon will start up with its saved configuration
+(either in its individual configuration file, or in @file{Quagga.conf}).
+This is particularly troublesome for route-maps and prefix lists, which would
+otherwise be synchronized between daemons.
diff --git a/lib/command.c b/lib/command.c
index 593822a66..bf7e269ac 100644
--- a/lib/command.c
+++ b/lib/command.c
@@ -1350,6 +1350,9 @@ DEFUN (config_write,
return CMD_SUCCESS;
}
+ if (host.noconfig)
+ return CMD_SUCCESS;
+
/* Check and see if we are operating under vtysh configuration */
if (host.config == NULL)
{
@@ -1478,6 +1481,11 @@ DEFUN (show_startup_config,
char buf[BUFSIZ];
FILE *confp;
+ if (host.noconfig)
+ return CMD_SUCCESS;
+ if (host.config == NULL)
+ return CMD_WARNING;
+
confp = fopen (host.config, "r");
if (confp == NULL)
{
@@ -2281,7 +2289,11 @@ install_default (enum node_type node)
install_element (node, &show_running_config_cmd);
}
-/* Initialize command interface. Install basic nodes and commands. */
+/* Initialize command interface. Install basic nodes and commands.
+ *
+ * terminal = 0 -- vtysh / no logging, no config control
+ * terminal = 1 -- normal daemon
+ * terminal = -1 -- watchquagga / no logging, but minimal config control */
void
cmd_init (int terminal)
{
@@ -2296,6 +2308,7 @@ cmd_init (int terminal)
host.enable = NULL;
host.logfile = NULL;
host.config = NULL;
+ host.noconfig = (terminal < 0);
host.lines = -1;
host.motd = default_motd;
host.motdfile = NULL;
@@ -2338,12 +2351,17 @@ cmd_init (int terminal)
{
install_element (ENABLE_NODE, &config_logmsg_cmd);
install_default (CONFIG_NODE);
+
+ install_element (VIEW_NODE, &show_thread_cpu_cmd);
+ install_element (ENABLE_NODE, &clear_thread_cpu_cmd);
+
+ install_element (VIEW_NODE, &show_work_queues_cmd);
}
install_element (CONFIG_NODE, &hostname_cmd);
install_element (CONFIG_NODE, &no_hostname_cmd);
- if (terminal)
+ if (terminal > 0)
{
install_element (CONFIG_NODE, &password_cmd);
install_element (CONFIG_NODE, &enable_password_cmd);
@@ -2373,11 +2391,6 @@ cmd_init (int terminal)
install_element (CONFIG_NODE, &service_terminal_length_cmd);
install_element (CONFIG_NODE, &no_service_terminal_length_cmd);
- install_element (VIEW_NODE, &show_thread_cpu_cmd);
-
- install_element (ENABLE_NODE, &clear_thread_cpu_cmd);
- install_element (VIEW_NODE, &show_work_queues_cmd);
-
vrf_install_commands ();
}
srandom(time(NULL));
diff --git a/lib/command.h b/lib/command.h
index 3bcd66468..50323d375 100644
--- a/lib/command.h
+++ b/lib/command.h
@@ -57,6 +57,7 @@ struct host
/* config file name of this host */
char *config;
+ int noconfig;
/* Flags for services */
int advanced;
@@ -226,6 +227,7 @@ struct cmd_element
#define CMD_COMPLETE_LIST_MATCH 9
#define CMD_SUCCESS_DAEMON 10
#define CMD_ERR_NO_FILE 11
+#define CMD_SUSPEND 12
/* Argc max counts. */
#define CMD_ARGC_MAX 25
diff --git a/lib/log.c b/lib/log.c
index cd1f0bb77..f9877300b 100644
--- a/lib/log.c
+++ b/lib/log.c
@@ -58,7 +58,8 @@ const char *zlog_proto_names[] =
"LDP",
"ISIS",
"PIM",
- "RFP",
+ "RFP",
+ "WATCHQUAGGA",
NULL,
};
diff --git a/lib/log.h b/lib/log.h
index cd4cd1495..b36e5fbc8 100644
--- a/lib/log.h
+++ b/lib/log.h
@@ -59,6 +59,7 @@ typedef enum
ZLOG_ISIS,
ZLOG_PIM,
ZLOG_RFP,
+ ZLOG_WATCHQUAGGA,
} zlog_proto_t;
/* If maxlvl is set to ZLOG_DISABLED, then no messages will be sent
diff --git a/lib/network.c b/lib/network.c
index 5379ecb5a..506e01913 100644
--- a/lib/network.c
+++ b/lib/network.c
@@ -94,6 +94,20 @@ set_nonblocking(int fd)
return 0;
}
+int
+set_cloexec(int fd)
+{
+ int flags;
+ flags = fcntl(fd, F_GETFD, 0);
+ if (flags == -1)
+ return -1;
+
+ flags |= FD_CLOEXEC;
+ if (fcntl(fd, F_SETFD, flags) == -1)
+ return -1;
+ return 0;
+}
+
float
htonf (float host)
{
diff --git a/lib/network.h b/lib/network.h
index 0fcb575d1..a9126caf7 100644
--- a/lib/network.h
+++ b/lib/network.h
@@ -33,6 +33,8 @@ extern int writen (int, const u_char *, int);
-1 on error. */
extern int set_nonblocking(int fd);
+extern int set_cloexec(int fd);
+
/* Does the I/O error indicate that the operation should be retried later? */
#define ERRNO_IO_RETRY(EN) \
(((EN) == EAGAIN) || ((EN) == EWOULDBLOCK) || ((EN) == EINTR))
diff --git a/lib/pid_output.c b/lib/pid_output.c
index 5261babc6..de4c2fba9 100644
--- a/lib/pid_output.c
+++ b/lib/pid_output.c
@@ -24,6 +24,7 @@
#include <fcntl.h>
#include <log.h>
#include "version.h"
+#include "network.h"
#define PIDFILE_MASK 0644
#ifndef HAVE_FCNTL
@@ -84,6 +85,8 @@ pid_output (const char *path)
umask(oldumask);
memset (&lock, 0, sizeof(lock));
+ set_cloexec(fd);
+
lock.l_type = F_WRLCK;
lock.l_whence = SEEK_SET;
diff --git a/lib/privs.c b/lib/privs.c
index 6cf87c18d..ac2a8454c 100644
--- a/lib/privs.c
+++ b/lib/privs.c
@@ -679,6 +679,15 @@ zprivs_init(struct zebra_privs_t *zprivs)
exit (1);
}
+ if (zprivs->vty_group)
+ {
+ /* in a "NULL" setup, this is allowed to fail too, but still try. */
+ if ((grentry = getgrnam (zprivs->vty_group)))
+ zprivs_state.vtygrp = grentry->gr_gid;
+ else
+ zprivs_state.vtygrp = (gid_t)-1;
+ }
+
/* NULL privs */
if (! (zprivs->user || zprivs->group
|| zprivs->cap_num_p || zprivs->cap_num_i) )
@@ -731,34 +740,30 @@ zprivs_init(struct zebra_privs_t *zprivs)
if (zprivs->vty_group)
/* Add the vty_group to the supplementary groups so it can be chowned to */
{
- if ( (grentry = getgrnam (zprivs->vty_group)) )
- {
- zprivs_state.vtygrp = grentry->gr_gid;
-
- for ( i = 0; i < ngroups; i++ )
- if ( groups[i] == zprivs_state.vtygrp )
- {
- found++;
- break;
- }
-
- if (!found)
- {
- fprintf (stderr, "privs_init: user(%s) is not part of vty group specified(%s)\n",
- zprivs->user, zprivs->vty_group);
- exit (1);
- }
- if ( i >= ngroups && ngroups < (int) ZEBRA_NUM_OF(groups) )
- {
- groups[i] = zprivs_state.vtygrp;
- }
- }
- else
+ if (zprivs_state.vtygrp == (gid_t)-1)
{
fprintf (stderr, "privs_init: could not lookup vty group %s\n",
zprivs->vty_group);
exit (1);
}
+
+ for ( i = 0; i < ngroups; i++ )
+ if ( groups[i] == zprivs_state.vtygrp )
+ {
+ found++;
+ break;
+ }
+
+ if (!found)
+ {
+ fprintf (stderr, "privs_init: user(%s) is not part of vty group specified(%s)\n",
+ zprivs->user, zprivs->vty_group);
+ exit (1);
+ }
+ if ( i >= ngroups && ngroups < (int) ZEBRA_NUM_OF(groups) )
+ {
+ groups[i] = zprivs_state.vtygrp;
+ }
}
if (ngroups)
diff --git a/lib/vty.c b/lib/vty.c
index 53a04851d..c1cc332d8 100644
--- a/lib/vty.c
+++ b/lib/vty.c
@@ -1839,6 +1839,7 @@ vty_accept (struct thread *thread)
return -1;
}
set_nonblocking(vty_sock);
+ set_cloexec(vty_sock);
sockunion2hostprefix (&su, &p);
@@ -1937,6 +1938,7 @@ vty_serv_sock_addrinfo (const char *hostname, unsigned short port)
sockopt_v6only (ainfo->ai_family, sock);
sockopt_reuseaddr (sock);
sockopt_reuseport (sock);
+ set_cloexec (sock);
ret = bind (sock, ainfo->ai_addr, ainfo->ai_addrlen);
if (ret < 0)
@@ -2004,6 +2006,7 @@ vty_serv_sock_family (const char* addr, unsigned short port, int family)
/* This is server, so reuse address. */
sockopt_reuseaddr (accept_sock);
sockopt_reuseport (accept_sock);
+ set_cloexec (accept_sock);
/* Bind socket to universal address and given port. */
ret = sockunion_bind (accept_sock, &su, port, naddr);
@@ -2066,6 +2069,8 @@ vty_serv_un (const char *path)
len = sizeof (serv.sun_family) + strlen (serv.sun_path);
#endif /* HAVE_STRUCT_SOCKADDR_UN_SUN_LEN */
+ set_cloexec (sock);
+
ret = bind (sock, (struct sockaddr *) &serv, len);
if (ret < 0)
{
@@ -2133,6 +2138,7 @@ vtysh_accept (struct thread *thread)
close (sock);
return -1;
}
+ set_cloexec(sock);
#ifdef VTYSH_DEBUG
printf ("VTY shell accept\n");
@@ -2227,8 +2233,15 @@ vtysh_read (struct thread *thread)
printf ("vtysh node: %d\n", vty->node);
#endif /* VTYSH_DEBUG */
- header[3] = ret;
- buffer_put(vty->obuf, header, 4);
+ /* hack for asynchronous "write integrated"
+ * - other commands in "buf" will be ditched
+ * - input during pending config-write is "unsupported" */
+ if (ret == CMD_SUSPEND)
+ break;
+
+ /* warning: watchquagga hardcodes this result write */
+ header[3] = ret;
+ buffer_put(vty->obuf, header, 4);
if (!vty->t_write && (vtysh_flush(vty) < 0))
/* Try to flush results; exit if a write error occurs. */
diff --git a/tools/quagga b/tools/quagga
index 83dfb6302..e8595d787 100755
--- a/tools/quagga
+++ b/tools/quagga
@@ -33,7 +33,6 @@ else
SSD=`which start-stop-daemon`
fi
-echo ${SSD}
# Print the name of the pidfile.
pidfile()
{
@@ -114,12 +113,23 @@ start()
echo -n " $1"
fi
+ if [ -e /var/run/quagga/watchquagga.started ] ; then
+ rm /var/run/quagga/watchquagga.started
+ fi
${SSD} \
--start \
--pidfile=`pidfile $1` \
--exec "$D_PATH/$1" \
-- \
"${watchquagga_options[@]}"
+ for i in `seq 1 10`;
+ do
+ if [ -e /var/run/quagga/watchquagga.started ] ; then
+ break
+ else
+ sleep 1
+ fi
+ done
elif [ -n "$2" ]; then
echo -n " $1-$2"
if ! check_daemon $1 $2 ; then
@@ -502,8 +512,8 @@ case "$1" in
if [ "$2" != "watchquagga" ]; then
start_prio 10 $dmn
fi
- vtysh_b
start_watchquagga
+ vtysh_b
;;
1|2|3|4|5|6|7|8|9|10)
diff --git a/vtysh/Makefile.am b/vtysh/Makefile.am
index 58ffdfca2..0ff2d738f 100644
--- a/vtysh/Makefile.am
+++ b/vtysh/Makefile.am
@@ -86,6 +86,7 @@ vtysh_cmd_FILES = $(vtysh_scan) \
$(top_srcdir)/zebra/zebra_fpm.c \
$(top_srcdir)/zebra/zebra_ptm.c \
$(top_srcdir)/zebra/zebra_mpls_vty.c \
+ $(top_srcdir)/watchquagga/watchquagga_vty.c \
$(BGP_VNC_RFAPI_SRC) $(BGP_VNC_RFP_SRC)
vtysh_cmd.c: $(vtysh_cmd_FILES) extract.pl
diff --git a/vtysh/vtysh.c b/vtysh/vtysh.c
index e8cf5aa23..85973bdb5 100644
--- a/vtysh/vtysh.c
+++ b/vtysh/vtysh.c
@@ -73,12 +73,11 @@ struct vtysh_client vtysh_client[] =
{ .fd = -1, .name = "bgpd", .flag = VTYSH_BGPD, .path = BGP_VTYSH_PATH, .next = NULL},
{ .fd = -1, .name = "isisd", .flag = VTYSH_ISISD, .path = ISIS_VTYSH_PATH, .next = NULL},
{ .fd = -1, .name = "pimd", .flag = VTYSH_PIMD, .path = PIM_VTYSH_PATH, .next = NULL},
+ { .fd = -1, .name = "watchquagga", .flag = VTYSH_WATCHQUAGGA, .path = WATCHQUAGGA_VTYSH_PATH, .next = NULL},
};
enum vtysh_write_integrated vtysh_write_integrated = WRITE_INTEGRATED_UNSPECIFIED;
-extern char config_default[];
-
static void
vclient_close (struct vtysh_client *vclient)
{
@@ -1068,7 +1067,7 @@ vtysh_end (void)
return CMD_SUCCESS;
}
-DEFUNSH (VTYSH_ALL,
+DEFUNSH (VTYSH_REALLYALL,
vtysh_end_all,
vtysh_end_all_cmd,
"end",
@@ -1410,8 +1409,8 @@ DEFUNSH (VTYSH_ALL,
return CMD_SUCCESS;
}
-DEFUNSH (VTYSH_ALL,
- vtysh_enable,
+DEFUNSH (VTYSH_REALLYALL,
+ vtysh_enable,
vtysh_enable_cmd,
"enable",
"Turn on privileged mode command\n")
@@ -1420,8 +1419,8 @@ DEFUNSH (VTYSH_ALL,
return CMD_SUCCESS;
}
-DEFUNSH (VTYSH_ALL,
- vtysh_disable,
+DEFUNSH (VTYSH_REALLYALL,
+ vtysh_disable,
vtysh_disable_cmd,
"disable",
"Turn off privileged mode command\n")
@@ -1431,7 +1430,7 @@ DEFUNSH (VTYSH_ALL,
return CMD_SUCCESS;
}
-DEFUNSH (VTYSH_ALL,
+DEFUNSH (VTYSH_REALLYALL,
vtysh_config_terminal,
vtysh_config_terminal_cmd,
"configure terminal",
@@ -1512,7 +1511,7 @@ vtysh_exit (struct vty *vty)
return CMD_SUCCESS;
}
-DEFUNSH (VTYSH_ALL,
+DEFUNSH (VTYSH_REALLYALL,
vtysh_exit_all,
vtysh_exit_all_cmd,
"exit",
@@ -2399,74 +2398,101 @@ backup_config_file (const char *fbackup)
free (integrate_sav);
}
-static int
-write_config_integrated(void)
+int
+vtysh_write_config_integrated(void)
{
u_int i;
char line[] = "write terminal\n";
- FILE *fp, *fp1;
+ FILE *fp;
+ int fd;
+ struct passwd *pwentry;
+ struct group *grentry;
+ uid_t uid = -1;
+ gid_t gid = -1;
+ struct stat st;
+ int err = 0;
fprintf (stdout,"Building Configuration...\n");
- backup_config_file(integrate_default);
- backup_config_file(host.config);
-
- fp = fopen (integrate_default, "w");
+ backup_config_file(quagga_config);
+ fp = fopen (quagga_config, "w");
if (fp == NULL)
{
- fprintf (stdout,"%% Can't open configuration file %s due to '%s'\n",
- integrate_default, safe_strerror(errno));
- return CMD_SUCCESS;
- }
-
- fp1 = fopen (host.config, "w");
- if (fp1 == NULL)
- {
- fprintf (stdout,"%% Can't open configuration file %s due to '%s'\n",
- host.config, safe_strerror(errno));
- return CMD_SUCCESS;
+ fprintf (stdout,"%% Error: failed to open configuration file %s: %s\n",
+ quagga_config, safe_strerror(errno));
+ return CMD_WARNING;
}
+ fd = fileno (fp);
- vtysh_config_write ();
- vtysh_config_dump (fp1);
-
- fclose (fp1);
for (i = 0; i < array_size(vtysh_client); i++)
vtysh_client_config (&vtysh_client[i], line);
vtysh_config_write ();
vtysh_config_dump (fp);
- fclose (fp);
+ if (fchmod (fd, CONFIGFILE_MASK) != 0)
+ {
+ printf ("%% Warning: can't chmod configuration file %s: %s\n",
+ quagga_config, safe_strerror(errno));
+ err++;
+ }
- if (chmod (integrate_default, CONFIGFILE_MASK) != 0)
+ pwentry = getpwnam (QUAGGA_USER);
+ if (pwentry)
+ uid = pwentry->pw_uid;
+ else
{
- fprintf (stdout,"%% Can't chmod configuration file %s: %s\n",
- integrate_default, safe_strerror(errno));
- return CMD_WARNING;
+ printf ("%% Warning: could not look up user \"%s\"\n", QUAGGA_USER);
+ err++;
}
- if (chmod (host.config, CONFIGFILE_MASK) != 0)
+ grentry = getgrnam (QUAGGA_GROUP);
+ if (grentry)
+ gid = grentry->gr_gid;
+ else
{
- fprintf (stdout,"%% Can't chmod configuration file %s: %s (%d)\n",
- integrate_default, safe_strerror(errno), errno);
- return CMD_WARNING;
+ printf ("%% Warning: could not look up group \"%s\"\n", QUAGGA_GROUP);
+ err++;
}
- fprintf(stdout,"Integrated configuration saved to %s\n",integrate_default);
- fprintf (stdout,"[OK]\n");
+ if (!fstat (fd, &st))
+ {
+ if (st.st_uid == uid)
+ uid = -1;
+ if (st.st_gid == gid)
+ gid = -1;
+ if ((uid != (uid_t)-1 || gid != (gid_t)-1) && fchown (fd, uid, gid))
+ {
+ printf ("%% Warning: can't chown configuration file %s: %s\n",
+ quagga_config, safe_strerror(errno));
+ err++;
+ }
+ }
+ else
+ {
+ printf ("%% Warning: stat() failed on %s: %s\n",
+ quagga_config, safe_strerror(errno));
+ err++;
+ }
+
+ fclose (fp);
+ printf ("Integrated configuration saved to %s\n", quagga_config);
+ if (err)
+ return CMD_WARNING;
+
+ printf ("[OK]\n");
return CMD_SUCCESS;
}
-static bool vtysh_writeconfig_integrated(void)
+static bool want_config_integrated(void)
{
struct stat s;
switch (vtysh_write_integrated)
{
case WRITE_INTEGRATED_UNSPECIFIED:
- if (stat(integrate_default, &s) && errno == ENOENT)
+ if (stat(quagga_config, &s) && errno == ENOENT)
return false;
return true;
case WRITE_INTEGRATED_NO:
@@ -2487,41 +2513,33 @@ DEFUN (vtysh_write_memory,
int ret = CMD_SUCCESS;
char line[] = "write memory\n";
u_int i;
- FILE *fp;
-
- /* If integrated Quagga.conf explicitely set. */
- if (vtysh_writeconfig_integrated())
- return write_config_integrated();
- else
- backup_config_file(integrate_default);
-
- fprintf (stdout,"Building Configuration...\n");
-
- for (i = 0; i < array_size(vtysh_client); i++)
- ret = vtysh_client_execute (&vtysh_client[i], line, stdout);
+ fprintf (stdout, "Note: this version of vtysh never writes vtysh.conf\n");
- fp = fopen(host.config, "w");
- if (fp == NULL)
+ /* If integrated Quagga.conf explicitely set. */
+ if (want_config_integrated())
{
- fprintf (stdout,"%% Can't open configuration file %s due to '%s'\n",
- host.config, safe_strerror(errno));
- return CMD_SUCCESS;
+ ret = CMD_WARNING;
+ for (i = 0; i < array_size(vtysh_client); i++)
+ if (vtysh_client[i].flag == VTYSH_WATCHQUAGGA)
+ break;
+ if (i < array_size(vtysh_client) && vtysh_client[i].fd != -1)
+ ret = vtysh_client_execute (&vtysh_client[i], "write integrated", stdout);
+
+ if (ret != CMD_SUCCESS)
+ {
+ printf("\nWarning: attempting direct configuration write without "
+ "watchquagga.\nFile permissions and ownership may be "
+ "incorrect, or write may fail.\n\n");
+ ret = vtysh_write_config_integrated();
+ }
+ return ret;
}
- vtysh_config_write ();
- vtysh_config_dump (fp);
-
- fclose (fp);
-
- if (chmod (host.config, CONFIGFILE_MASK) != 0)
- {
- fprintf (stdout,"%% Can't chmod configuration file %s: %s\n",
- integrate_default, safe_strerror(errno));
- return CMD_WARNING;
- }
+ fprintf (stdout,"Building Configuration...\n");
- fprintf (stdout,"[OK]\n");
+ for (i = 0; i < array_size(vtysh_client); i++)
+ ret = vtysh_client_execute (&vtysh_client[i], line, stdout);
return ret;
}
diff --git a/vtysh/vtysh.h b/vtysh/vtysh.h
index 7241b4c12..dade049ad 100644
--- a/vtysh/vtysh.h
+++ b/vtysh/vtysh.h
@@ -34,7 +34,13 @@ DECLARE_MGROUP(MVTYSH)
#define VTYSH_ISISD 0x40
#define VTYSH_PIMD 0x100
#define VTYSH_LDPD 0x200
+#define VTYSH_WATCHQUAGGA 0x400
+/* commands in REALLYALL are crucial to correct vtysh operation */
+#define VTYSH_REALLYALL ~0U
+/* watchquagga is not in ALL since library CLI functions should not be
+ * run on it (logging & co. should stay in a fixed/frozen config, and
+ * things like prefix lists are not even initialised) */
#define VTYSH_ALL VTYSH_ZEBRA|VTYSH_RIPD|VTYSH_RIPNGD|VTYSH_OSPFD|VTYSH_OSPF6D|VTYSH_LDPD|VTYSH_BGPD|VTYSH_ISISD|VTYSH_PIMD
#define VTYSH_RMAP VTYSH_ZEBRA|VTYSH_RIPD|VTYSH_RIPNGD|VTYSH_OSPFD|VTYSH_OSPF6D|VTYSH_BGPD|VTYSH_PIMD
#define VTYSH_INTERFACE VTYSH_ZEBRA|VTYSH_RIPD|VTYSH_RIPNGD|VTYSH_OSPFD|VTYSH_OSPF6D|VTYSH_LDPD|VTYSH_ISISD|VTYSH_PIMD
@@ -53,6 +59,8 @@ enum vtysh_write_integrated {
extern enum vtysh_write_integrated vtysh_write_integrated;
+extern char *quagga_config;
+
void vtysh_init_vty (void);
void vtysh_init_cmd (void);
extern int vtysh_connect_all (const char *optional_daemon_name);
@@ -73,6 +81,7 @@ void config_add_line (struct list *, const char *);
int vtysh_mark_file(const char *filename);
int vtysh_read_config (const char *);
+int vtysh_write_config_integrated (void);
void vtysh_config_parse_line (const char *);
diff --git a/vtysh/vtysh_config.c b/vtysh/vtysh_config.c
index 7ad457ee7..4ec0e0028 100644
--- a/vtysh/vtysh_config.c
+++ b/vtysh/vtysh_config.c
@@ -376,7 +376,6 @@ vtysh_read_config (const char *config_default_dir)
FILE *confp = NULL;
int ret;
- host_config_set (config_default_dir);
confp = fopen (config_default_dir, "r");
if (confp == NULL)
{
diff --git a/vtysh/vtysh_main.c b/vtysh/vtysh_main.c
index 751152e91..999d90ab2 100644
--- a/vtysh/vtysh_main.c
+++ b/vtysh/vtysh_main.c
@@ -35,7 +35,6 @@
#include "getopt.h"
#include "command.h"
#include "memory.h"
-#include "privs.h"
#include "linklist.h"
#include "memory_vty.h"
@@ -45,30 +44,10 @@
/* VTY shell program name. */
char *progname;
-static zebra_capabilities_t _caps_p [] =
-{
- ZCAP_BIND,
- ZCAP_NET_RAW,
- ZCAP_NET_ADMIN,
-};
-
-struct zebra_privs_t vtysh_privs =
-{
-#if defined(QUAGGA_USER) && defined(QUAGGA_GROUP)
- .user = QUAGGA_USER,
- .group = QUAGGA_GROUP,
-#endif
-#ifdef VTY_GROUP
- .vty_group = VTY_GROUP,
-#endif
- .caps_p = _caps_p,
- .cap_num_p = array_size(_caps_p),
- .cap_num_i = 0,
-};
-
/* Configuration file name and directory. */
-char config_default[] = SYSCONFDIR VTYSH_DEFAULT_CONFIG;
-char quagga_config_default[] = SYSCONFDIR QUAGGA_DEFAULT_CONFIG;
+static char vtysh_config_always[] = SYSCONFDIR VTYSH_DEFAULT_CONFIG;
+static char quagga_config_default[] = SYSCONFDIR QUAGGA_DEFAULT_CONFIG;
+char *quagga_config = quagga_config_default;
char history_file[MAXPATHLEN];
/* Flag for indicate executing child command. */
@@ -166,6 +145,7 @@ usage (int status)
"-E, --echo Echo prompt and command in -c mode\n" \
"-C, --dryrun Check configuration for validity and exit\n" \
"-m, --markfile Mark input file with context end\n"
+ "-w, --writeconfig Write integrated config (Quagga.conf) and exit\n"
"-h, --help Display this help and exit\n\n" \
"Note that multiple commands may be executed from the command\n" \
"line by passing multiple -c args, or by embedding linefeed\n" \
@@ -189,6 +169,7 @@ struct option longopts[] =
{ "help", no_argument, NULL, 'h'},
{ "noerror", no_argument, NULL, 'n'},
{ "mark", no_argument, NULL, 'm'},
+ { "writeconfig", no_argument, NULL, 'w'},
{ 0 }
};
@@ -289,6 +270,7 @@ main (int argc, char **argv, char **env)
int echo_command = 0;
int no_error = 0;
int markfile = 0;
+ int writeconfig = 0;
int ret = 0;
char *homedir = NULL;
@@ -302,7 +284,7 @@ main (int argc, char **argv, char **env)
/* Option handling. */
while (1)
{
- opt = getopt_long (argc, argv, "be:c:d:nf:mEhC", longopts, 0);
+ opt = getopt_long (argc, argv, "be:c:d:nf:mEhCw", longopts, 0);
if (opt == EOF)
break;
@@ -346,6 +328,9 @@ main (int argc, char **argv, char **env)
case 'C':
dryrun = 1;
break;
+ case 'w':
+ writeconfig = 1;
+ break;
case 'h':
usage (0);
break;
@@ -355,12 +340,22 @@ main (int argc, char **argv, char **env)
}
}
+ if (markfile + writeconfig + dryrun + boot_flag > 1)
+ {
+ fprintf (stderr, "Invalid combination of arguments. Please specify at "
+ "most one of:\n\t-b, -C, -m, -w\n");
+ return 1;
+ }
+ if (inputfile && (writeconfig || boot_flag))
+ {
+ fprintf (stderr, "WARNING: Combinining the -f option with -b or -w is "
+ "NOT SUPPORTED since its\nresults are inconsistent!\n");
+ }
+
/* Initialize user input buffer. */
line_read = NULL;
setlinebuf(stdout);
- zprivs_init (&vtysh_privs);
-
/* Signal and others. */
vtysh_signal_init ();
@@ -373,7 +368,7 @@ main (int argc, char **argv, char **env)
vty_init_vtysh ();
/* Read vtysh configuration file before connecting to daemons. */
- vtysh_read_config(config_default);
+ vtysh_read_config(vtysh_config_always);
if (markfile)
{
@@ -422,6 +417,12 @@ main (int argc, char **argv, char **env)
exit(1);
}
+ if (writeconfig)
+ {
+ vtysh_execute ("enable");
+ return vtysh_write_config_integrated ();
+ }
+
if (inputfile)
{
vtysh_flock_config (inputfile);
@@ -512,17 +513,17 @@ main (int argc, char **argv, char **env)
history_truncate_file(history_file,1000);
exit (0);
}
-
+
/* Boot startup configuration file. */
if (boot_flag)
{
- vtysh_flock_config (integrate_default);
- int ret = vtysh_read_config (integrate_default);
+ vtysh_flock_config (quagga_config);
+ int ret = vtysh_read_config (quagga_config);
vtysh_unflock_config ();
if (ret)
{
fprintf (stderr, "Configuration file[%s] processing failure: %d\n",
- integrate_default, ret);
+ quagga_config, ret);
if (no_error)
exit (0);
else
diff --git a/watchquagga/Makefile.am b/watchquagga/Makefile.am
index 1f05f26ce..43f743eba 100644
--- a/watchquagga/Makefile.am
+++ b/watchquagga/Makefile.am
@@ -7,5 +7,7 @@ AM_CFLAGS = $(WERROR)
sbin_PROGRAMS = watchquagga
-watchquagga_SOURCES = watchquagga.c
+noinst_HEADERS = watchquagga.h
+
+watchquagga_SOURCES = watchquagga.c watchquagga_vty.c
watchquagga_LDADD = ../lib/libzebra.la @LIBCAP@
diff --git a/watchquagga/watchquagga.c b/watchquagga/watchquagga.c
index e882653e3..16b398030 100644
--- a/watchquagga/watchquagga.c
+++ b/watchquagga/watchquagga.c
@@ -24,12 +24,17 @@
#include <network.h>
#include <sigevent.h>
#include <lib/version.h>
+#include "command.h"
+#include "memory_vty.h"
+
#include <getopt.h>
#include <sys/un.h>
#include <sys/wait.h>
#include <memory.h>
#include <systemd.h>
+#include "watchquagga.h"
+
#ifndef MIN
#define MIN(X,Y) (((X) <= (Y)) ? (X) : (Y))
#endif
@@ -437,6 +442,12 @@ sigchild(void)
return;
}
+ if (child == integrated_write_pid)
+ {
+ integrated_write_sigchld(status);
+ return;
+ }
+
if ((restart = find_child(child)) != NULL)
{
name = restart->name;
@@ -682,6 +693,28 @@ handle_read(struct thread *t_read)
return 0;
}
+/*
+ * Wait till we notice that all daemons are ready before
+ * we send we are ready to systemd
+ */
+static void
+daemon_send_ready (void)
+{
+ static int sent = 0;
+ if (!sent && gs.numdown == 0)
+ {
+#if defined (HAVE_CUMULUS)
+ FILE *fp;
+
+ fp = fopen("/var/run/quagga/watchquagga.started", "w");
+ fclose(fp);
+#endif
+ zlog_notice ("Watchquagga: Notifying Systemd we are up and running");
+ systemd_send_started(master, 0);
+ sent = 1;
+ }
+}
+
static void
daemon_up(struct daemon *dmn, const char *why)
{
@@ -689,6 +722,7 @@ daemon_up(struct daemon *dmn, const char *why)
gs.numdown--;
dmn->connect_tries = 0;
zlog_notice("%s state -> up : %s",dmn->name,why);
+ daemon_send_ready();
if (gs.do_ping)
SET_WAKEUP_ECHO(dmn);
phase_check();
@@ -775,9 +809,9 @@ try_connect(struct daemon *dmn)
return -1;
}
- if (set_nonblocking(sock) < 0)
+ if (set_nonblocking(sock) < 0 || set_cloexec(sock) < 0)
{
- zlog_err("%s(%s): set_nonblocking(%d) failed",
+ zlog_err("%s(%s): set_nonblocking/cloexec(%d) failed",
__func__, addr.sun_path, sock);
close(sock);
return -1;
@@ -1028,6 +1062,13 @@ translate_blanks(const char *cmd, const char *blankstr)
return res;
}
+struct zebra_privs_t watchquagga_privs =
+{
+#ifdef VTY_GROUP
+ .vty_group = VTY_GROUP,
+#endif
+};
+
int
main(int argc, char **argv)
{
@@ -1283,8 +1324,16 @@ main(int argc, char **argv)
}
gs.restart.interval = gs.min_restart_interval;
+
+ zprivs_init (&watchquagga_privs);
+
master = thread_master_create();
- systemd_send_started (master, 0);
+ cmd_init(-1);
+ memory_init();
+ vty_init(master);
+ watchquagga_vty_init();
+ vty_serv_sock(NULL, 0, WATCHQUAGGA_VTYSH_PATH);
+
signal_init (master, array_size(my_signals), my_signals);
srandom(time(NULL));
@@ -1335,7 +1384,7 @@ main(int argc, char **argv)
return usage(progname,1);
}
- zlog_default = openzlog(progname, ZLOG_NONE, 0,
+ zlog_default = openzlog(progname, ZLOG_WATCHQUAGGA, 0,
LOG_CONS|LOG_NDELAY|LOG_PID, LOG_DAEMON);
zlog_set_level(NULL, ZLOG_DEST_MONITOR, ZLOG_DISABLED);
if (daemon_mode)
diff --git a/watchquagga/watchquagga.h b/watchquagga/watchquagga.h
new file mode 100644
index 000000000..ecadf22c5
--- /dev/null
+++ b/watchquagga/watchquagga.h
@@ -0,0 +1,29 @@
+/*
+ Common definitions for watchquagga API socket.
+
+ Copyright (C) 2016 David Lamparter for NetDEF, Inc.
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#ifndef QUAGGA_WATCHQUAGGA_H
+#define QUAGGA_WATCHQUAGGA_H
+
+extern void watchquagga_vty_init(void);
+
+extern pid_t integrated_write_pid;
+extern void integrated_write_sigchld(int status);
+
+#endif /* QUAGGA_WATCHQUAGGA_H */
diff --git a/watchquagga/watchquagga_vty.c b/watchquagga/watchquagga_vty.c
new file mode 100644
index 000000000..b96011b76
--- /dev/null
+++ b/watchquagga/watchquagga_vty.c
@@ -0,0 +1,134 @@
+/*
+ watchquagga CLI functions.
+
+ Copyright (C) 2016 David Lamparter for NetDEF, Inc.
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#include <zebra.h>
+#include <sys/wait.h>
+
+#include "memory.h"
+#include "log.h"
+#include "vty.h"
+#include "command.h"
+
+#include "watchquagga.h"
+
+pid_t integrated_write_pid;
+static int integrated_result_fd;
+
+DEFUN (config_write_integrated,
+ config_write_integrated_cmd,
+ "write integrated",
+ "Write running configuration to memory, network, or terminal\n"
+ "Write integrated all-daemon Quagga.conf file\n")
+{
+ pid_t child;
+ sigset_t oldmask, sigmask;
+
+ if (integrated_write_pid != -1) {
+ vty_out(vty, "%% configuration write already in progress.%s",
+ VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ fflush(stdout);
+ fflush(stderr);
+
+ /* need to temporarily block SIGCHLD because it could arrive between
+ * fork() call and setting the integrated_write_pid variable. This
+ * would mean the completion call gets lost and this hangs forever.
+ */
+ sigemptyset(&oldmask);
+ sigemptyset(&sigmask);
+ sigaddset(&sigmask, SIGCHLD);
+ sigprocmask(SIG_BLOCK, &sigmask, &oldmask);
+
+ child = fork();
+ if (child == -1) {
+ vty_out(vty, "%% configuration write fork() failed: %s.%s",
+ safe_strerror(errno), VTY_NEWLINE);
+ sigprocmask(SIG_SETMASK, &oldmask, NULL);
+ return CMD_WARNING;
+ }
+ if (child != 0) {
+ /* note: the VTY won't write a command return value to vtysh; the
+ * session temporarily enters an intentional "hang" state. This is
+ * to make sure latency in vtysh doing the config write (several
+ * seconds is not rare to see) does not interfere with watchquagga's
+ * supervisor job.
+ *
+ * The fd is duplicated here so we don't need to hold a vty pointer
+ * (which could become invalid in the meantime).
+ */
+ integrated_write_pid = child;
+ integrated_result_fd = dup(vty->wfd);
+ sigprocmask(SIG_SETMASK, &oldmask, NULL);
+ return CMD_SUSPEND;
+ }
+
+ /* redirect stdout/stderr to vty session. Note vty->wfd is marked
+ * CLOEXEC, but dup2 will clear that flag. */
+ dup2(vty->wfd, 1);
+ dup2(vty->wfd, 2);
+
+ /* don't allow the user to pass parameters, we're root here!
+ * should probably harden vtysh at some point too... */
+ execl(VTYSH_BIN_PATH, "vtysh", "-w", NULL);
+
+ /* unbuffered write; we just messed with stdout... */
+ char msg[512];
+ snprintf(msg, sizeof(msg), "error executing %s: %s\n",
+ VTYSH_BIN_PATH, safe_strerror(errno));
+ write(1, msg, strlen(msg));
+ exit(1);
+}
+
+void integrated_write_sigchld(int status)
+{
+ uint8_t reply[4] = { 0, 0, 0, CMD_WARNING };
+
+ if (WIFEXITED(status)) {
+ zlog_info("configuration write completed with exit code %d",
+ WEXITSTATUS(status));
+ reply[3] = WEXITSTATUS(status);
+ } else if (WIFSIGNALED(status)) {
+ zlog_warn("configuration write terminated by signal %d",
+ WTERMSIG(status));
+ } else {
+ zlog_warn("configuration write terminated");
+ }
+
+ if (reply[3] != CMD_SUCCESS) {
+ /* failure might be silent in vtysh without this */
+ static const char msg[] = "% Configuration write failed.\n";
+ write(integrated_result_fd, msg, strlen(msg));
+ }
+
+ /* don't care about failures here, if the connection is broken the
+ * return value will just be lost. */
+ write(integrated_result_fd, reply, sizeof(reply));
+ close(integrated_result_fd);
+
+ integrated_write_pid = -1;
+}
+
+void watchquagga_vty_init(void)
+{
+ integrated_write_pid = -1;
+ install_element(ENABLE_NODE, &config_write_integrated_cmd);
+}