From 2c032d8f1c6292c1338a615e6ec40252889ba85c Mon Sep 17 00:00:00 2001 From: Michael Biebl Date: Thu, 22 Jan 2015 00:29:39 +0100 Subject: Imported Upstream version 1.0.0 --- clients/cli/Makefile.am | 72 + clients/cli/Makefile.in | 862 ++++ clients/cli/agent.c | 252 ++ clients/cli/agent.h | 29 + clients/cli/common.c | 1146 ++++++ clients/cli/common.h | 69 + clients/cli/connections.c | 9020 +++++++++++++++++++++++++++++++++++++++++ clients/cli/connections.h | 27 + clients/cli/devices.c | 2925 +++++++++++++ clients/cli/devices.h | 27 + clients/cli/general.c | 899 ++++ clients/cli/general.h | 29 + clients/cli/nmcli-completion | 1287 ++++++ clients/cli/nmcli.c | 621 +++ clients/cli/nmcli.h | 156 + clients/cli/polkit-agent.c | 166 + clients/cli/polkit-agent.h | 30 + clients/cli/settings-docs.c | 388 ++ clients/cli/settings-docs.xsl | 86 + clients/cli/settings.c | 7445 ++++++++++++++++++++++++++++++++++ clients/cli/settings.h | 66 + clients/cli/utils.c | 1151 ++++++ clients/cli/utils.h | 96 + 23 files changed, 26849 insertions(+) create mode 100644 clients/cli/Makefile.am create mode 100644 clients/cli/Makefile.in create mode 100644 clients/cli/agent.c create mode 100644 clients/cli/agent.h create mode 100644 clients/cli/common.c create mode 100644 clients/cli/common.h create mode 100644 clients/cli/connections.c create mode 100644 clients/cli/connections.h create mode 100644 clients/cli/devices.c create mode 100644 clients/cli/devices.h create mode 100644 clients/cli/general.c create mode 100644 clients/cli/general.h create mode 100644 clients/cli/nmcli-completion create mode 100644 clients/cli/nmcli.c create mode 100644 clients/cli/nmcli.h create mode 100644 clients/cli/polkit-agent.c create mode 100644 clients/cli/polkit-agent.h create mode 100644 clients/cli/settings-docs.c create mode 100644 clients/cli/settings-docs.xsl create mode 100644 clients/cli/settings.c create mode 100644 clients/cli/settings.h create mode 100644 clients/cli/utils.c create mode 100644 clients/cli/utils.h (limited to 'clients/cli') diff --git a/clients/cli/Makefile.am b/clients/cli/Makefile.am new file mode 100644 index 00000000..75c00a99 --- /dev/null +++ b/clients/cli/Makefile.am @@ -0,0 +1,72 @@ +bin_PROGRAMS = \ + nmcli + +AM_CPPFLAGS = \ + -I${top_srcdir} \ + -I${top_builddir} \ + -I${top_srcdir}/include \ + -I${top_srcdir}/libnm-core \ + -I${top_builddir}/libnm-core \ + -I${top_srcdir}/libnm \ + -I${top_builddir}/libnm \ + -I${top_srcdir}/clients/common \ + $(GLIB_CFLAGS) \ + -DG_LOG_DOMAIN=\""nmcli"\" \ + -DNM_VERSION_MAX_ALLOWED=NM_VERSION_NEXT_STABLE \ + -DNMCLI_LOCALEDIR=\"$(datadir)/locale\" + +nmcli_SOURCES = \ + agent.c \ + agent.h \ + common.c \ + common.h \ + connections.c \ + connections.h \ + devices.c \ + devices.h \ + general.c \ + general.h \ + settings.c \ + settings.h \ + nmcli.c \ + nmcli.h \ + utils.c \ + utils.h \ + polkit-agent.c \ + polkit-agent.h \ + \ + $(srcdir)/../common/nm-secret-agent-simple.c \ + $(srcdir)/../common/nm-secret-agent-simple.h \ + $(NULL) + +nmcli_LDADD = \ + $(GLIB_LIBS) \ + $(READLINE_LIBS) \ + $(top_builddir)/libnm/libnm.la + +if WITH_POLKIT_AGENT +AM_CPPFLAGS += $(POLKIT_CFLAGS) +nmcli_SOURCES += $(srcdir)/../common/nm-polkit-listener.c $(srcdir)/../common/nm-polkit-listener.h +nmcli_LDADD += $(POLKIT_LIBS) +endif + +if BUILD_SETTING_DOCS +settings-docs.c: settings-docs.xsl $(top_builddir)/libnm/nm-property-docs.xml + $(AM_V_GEN) xsltproc --output $@ $^ + +BUILT_SOURCES = settings-docs.c +CLEANFILES = settings-docs.c +endif + +EXTRA_DIST = settings-docs.c settings-docs.xsl + +completiondir = $(datadir)/bash-completion/completions + +install-data-hook: + $(mkinstalldirs) $(DESTDIR)$(completiondir) + $(INSTALL_DATA) $(srcdir)/nmcli-completion $(DESTDIR)$(completiondir)/nmcli + +uninstall-hook: + rm -f $(DESTDIR)$(completiondir)/nmcli + +EXTRA_DIST += nmcli-completion diff --git a/clients/cli/Makefile.in b/clients/cli/Makefile.in new file mode 100644 index 00000000..871b9525 --- /dev/null +++ b/clients/cli/Makefile.in @@ -0,0 +1,862 @@ +# Makefile.in generated by automake 1.13.4 from Makefile.am. +# @configure_input@ + +# Copyright (C) 1994-2013 Free Software Foundation, Inc. + +# This Makefile.in is free software; the Free Software Foundation +# gives unlimited permission to copy and/or distribute it, +# with or without modifications, as long as this notice is preserved. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY, to the extent permitted by law; without +# even the implied warranty of MERCHANTABILITY or FITNESS FOR A +# PARTICULAR PURPOSE. + +@SET_MAKE@ + +VPATH = @srcdir@ +am__is_gnu_make = test -n '$(MAKEFILE_LIST)' && test -n '$(MAKELEVEL)' +am__make_running_with_option = \ + case $${target_option-} in \ + ?) ;; \ + *) echo "am__make_running_with_option: internal error: invalid" \ + "target option '$${target_option-}' specified" >&2; \ + exit 1;; \ + esac; \ + has_opt=no; \ + sane_makeflags=$$MAKEFLAGS; \ + if $(am__is_gnu_make); then \ + sane_makeflags=$$MFLAGS; \ + else \ + case $$MAKEFLAGS in \ + *\\[\ \ ]*) \ + bs=\\; \ + sane_makeflags=`printf '%s\n' "$$MAKEFLAGS" \ + | sed "s/$$bs$$bs[$$bs $$bs ]*//g"`;; \ + esac; \ + fi; \ + skip_next=no; \ + strip_trailopt () \ + { \ + flg=`printf '%s\n' "$$flg" | sed "s/$$1.*$$//"`; \ + }; \ + for flg in $$sane_makeflags; do \ + test $$skip_next = yes && { skip_next=no; continue; }; \ + case $$flg in \ + *=*|--*) continue;; \ + -*I) strip_trailopt 'I'; skip_next=yes;; \ + -*I?*) strip_trailopt 'I';; \ + -*O) strip_trailopt 'O'; skip_next=yes;; \ + -*O?*) strip_trailopt 'O';; \ + -*l) strip_trailopt 'l'; skip_next=yes;; \ + -*l?*) strip_trailopt 'l';; \ + -[dEDm]) skip_next=yes;; \ + -[JT]) skip_next=yes;; \ + esac; \ + case $$flg in \ + *$$target_option*) has_opt=yes; break;; \ + esac; \ + done; \ + test $$has_opt = yes +am__make_dryrun = (target_option=n; $(am__make_running_with_option)) +am__make_keepgoing = (target_option=k; $(am__make_running_with_option)) +pkgdatadir = $(datadir)/@PACKAGE@ +pkgincludedir = $(includedir)/@PACKAGE@ +pkglibdir = $(libdir)/@PACKAGE@ +pkglibexecdir = $(libexecdir)/@PACKAGE@ +am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd +install_sh_DATA = $(install_sh) -c -m 644 +install_sh_PROGRAM = $(install_sh) -c +install_sh_SCRIPT = $(install_sh) -c +INSTALL_HEADER = $(INSTALL_DATA) +transform = $(program_transform_name) +NORMAL_INSTALL = : +PRE_INSTALL = : +POST_INSTALL = : +NORMAL_UNINSTALL = : +PRE_UNINSTALL = : +POST_UNINSTALL = : +build_triplet = @build@ +host_triplet = @host@ +bin_PROGRAMS = nmcli$(EXEEXT) +@WITH_POLKIT_AGENT_TRUE@am__append_1 = $(POLKIT_CFLAGS) +@WITH_POLKIT_AGENT_TRUE@am__append_2 = $(srcdir)/../common/nm-polkit-listener.c $(srcdir)/../common/nm-polkit-listener.h +@WITH_POLKIT_AGENT_TRUE@am__append_3 = $(POLKIT_LIBS) +subdir = clients/cli +DIST_COMMON = $(srcdir)/Makefile.in $(srcdir)/Makefile.am \ + $(top_srcdir)/build-aux/depcomp +ACLOCAL_M4 = $(top_srcdir)/aclocal.m4 +am__aclocal_m4_deps = $(top_srcdir)/m4/ax_lib_readline.m4 \ + $(top_srcdir)/m4/compiler_warnings.m4 \ + $(top_srcdir)/m4/gettext.m4 \ + $(top_srcdir)/m4/gnome-code-coverage.m4 \ + $(top_srcdir)/m4/gtk-doc.m4 $(top_srcdir)/m4/iconv.m4 \ + $(top_srcdir)/m4/intlmacosx.m4 $(top_srcdir)/m4/intltool.m4 \ + $(top_srcdir)/m4/introspection.m4 $(top_srcdir)/m4/lib-ld.m4 \ + $(top_srcdir)/m4/lib-link.m4 $(top_srcdir)/m4/lib-prefix.m4 \ + $(top_srcdir)/m4/libtool.m4 $(top_srcdir)/m4/ltoptions.m4 \ + $(top_srcdir)/m4/ltsugar.m4 $(top_srcdir)/m4/ltversion.m4 \ + $(top_srcdir)/m4/lt~obsolete.m4 $(top_srcdir)/m4/nls.m4 \ + $(top_srcdir)/m4/po.m4 $(top_srcdir)/m4/progtest.m4 \ + $(top_srcdir)/m4/vapigen.m4 $(top_srcdir)/configure.ac +am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \ + $(ACLOCAL_M4) +mkinstalldirs = $(install_sh) -d +CONFIG_HEADER = $(top_builddir)/config.h +CONFIG_CLEAN_FILES = +CONFIG_CLEAN_VPATH_FILES = +am__installdirs = "$(DESTDIR)$(bindir)" +PROGRAMS = $(bin_PROGRAMS) +am__nmcli_SOURCES_DIST = agent.c agent.h common.c common.h \ + connections.c connections.h devices.c devices.h general.c \ + general.h settings.c settings.h nmcli.c nmcli.h utils.c \ + utils.h polkit-agent.c polkit-agent.h \ + $(srcdir)/../common/nm-secret-agent-simple.c \ + $(srcdir)/../common/nm-secret-agent-simple.h \ + $(srcdir)/../common/nm-polkit-listener.c \ + $(srcdir)/../common/nm-polkit-listener.h +@WITH_POLKIT_AGENT_TRUE@am__objects_1 = nm-polkit-listener.$(OBJEXT) +am_nmcli_OBJECTS = agent.$(OBJEXT) common.$(OBJEXT) \ + connections.$(OBJEXT) devices.$(OBJEXT) general.$(OBJEXT) \ + settings.$(OBJEXT) nmcli.$(OBJEXT) utils.$(OBJEXT) \ + polkit-agent.$(OBJEXT) nm-secret-agent-simple.$(OBJEXT) \ + $(am__objects_1) +nmcli_OBJECTS = $(am_nmcli_OBJECTS) +am__DEPENDENCIES_1 = +@WITH_POLKIT_AGENT_TRUE@am__DEPENDENCIES_2 = $(am__DEPENDENCIES_1) +nmcli_DEPENDENCIES = $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1) \ + $(top_builddir)/libnm/libnm.la $(am__DEPENDENCIES_2) +AM_V_lt = $(am__v_lt_@AM_V@) +am__v_lt_ = $(am__v_lt_@AM_DEFAULT_V@) +am__v_lt_0 = --silent +am__v_lt_1 = +AM_V_P = $(am__v_P_@AM_V@) +am__v_P_ = $(am__v_P_@AM_DEFAULT_V@) +am__v_P_0 = false +am__v_P_1 = : +AM_V_GEN = $(am__v_GEN_@AM_V@) +am__v_GEN_ = $(am__v_GEN_@AM_DEFAULT_V@) +am__v_GEN_0 = @echo " GEN " $@; +am__v_GEN_1 = +AM_V_at = $(am__v_at_@AM_V@) +am__v_at_ = $(am__v_at_@AM_DEFAULT_V@) +am__v_at_0 = @ +am__v_at_1 = +DEFAULT_INCLUDES = -I.@am__isrc@ -I$(top_builddir) +depcomp = $(SHELL) $(top_srcdir)/build-aux/depcomp +am__depfiles_maybe = depfiles +am__mv = mv -f +COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \ + $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) +LTCOMPILE = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \ + $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) \ + $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) \ + $(AM_CFLAGS) $(CFLAGS) +AM_V_CC = $(am__v_CC_@AM_V@) +am__v_CC_ = $(am__v_CC_@AM_DEFAULT_V@) +am__v_CC_0 = @echo " CC " $@; +am__v_CC_1 = +CCLD = $(CC) +LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \ + $(LIBTOOLFLAGS) --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) \ + $(AM_LDFLAGS) $(LDFLAGS) -o $@ +AM_V_CCLD = $(am__v_CCLD_@AM_V@) +am__v_CCLD_ = $(am__v_CCLD_@AM_DEFAULT_V@) +am__v_CCLD_0 = @echo " CCLD " $@; +am__v_CCLD_1 = +SOURCES = $(nmcli_SOURCES) +DIST_SOURCES = $(am__nmcli_SOURCES_DIST) +am__can_run_installinfo = \ + case $$AM_UPDATE_INFO_DIR in \ + n|no|NO) false;; \ + *) (install-info --version) >/dev/null 2>&1;; \ + esac +am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP) +# Read a list of newline-separated strings from the standard input, +# and print each of them once, without duplicates. Input order is +# *not* preserved. +am__uniquify_input = $(AWK) '\ + BEGIN { nonempty = 0; } \ + { items[$$0] = 1; nonempty = 1; } \ + END { if (nonempty) { for (i in items) print i; }; } \ +' +# Make sure the list of sources is unique. This is necessary because, +# e.g., the same source file might be shared among _SOURCES variables +# for different programs/libraries. +am__define_uniq_tagged_files = \ + list='$(am__tagged_files)'; \ + unique=`for i in $$list; do \ + if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \ + done | $(am__uniquify_input)` +ETAGS = etags +CTAGS = ctags +DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST) +ACLOCAL = @ACLOCAL@ +ALL_LINGUAS = @ALL_LINGUAS@ +AMTAR = @AMTAR@ +AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@ +AR = @AR@ +AUTOCONF = @AUTOCONF@ +AUTOHEADER = @AUTOHEADER@ +AUTOMAKE = @AUTOMAKE@ +AWK = @AWK@ +BLUEZ5_CFLAGS = @BLUEZ5_CFLAGS@ +BLUEZ5_LIBS = @BLUEZ5_LIBS@ +CC = @CC@ +CCDEPMODE = @CCDEPMODE@ +CFLAGS = @CFLAGS@ +CKDB_PATH = @CKDB_PATH@ +CODE_COVERAGE_CFLAGS = @CODE_COVERAGE_CFLAGS@ +CODE_COVERAGE_ENABLED = @CODE_COVERAGE_ENABLED@ +CODE_COVERAGE_LDFLAGS = @CODE_COVERAGE_LDFLAGS@ +CPP = @CPP@ +CPPFLAGS = @CPPFLAGS@ +CXX = @CXX@ +CXXCPP = @CXXCPP@ +CXXDEPMODE = @CXXDEPMODE@ +CXXFLAGS = @CXXFLAGS@ +CYGPATH_W = @CYGPATH_W@ +DATADIRNAME = @DATADIRNAME@ +DBUS_CFLAGS = @DBUS_CFLAGS@ +DBUS_GLIB_100_CFLAGS = @DBUS_GLIB_100_CFLAGS@ +DBUS_GLIB_100_LIBS = @DBUS_GLIB_100_LIBS@ +DBUS_LIBS = @DBUS_LIBS@ +DBUS_SYS_DIR = @DBUS_SYS_DIR@ +DEFS = @DEFS@ +DEPDIR = @DEPDIR@ +DHCLIENT_PATH = @DHCLIENT_PATH@ +DHCPCD_PATH = @DHCPCD_PATH@ +DISTRO_NETWORK_SERVICE = @DISTRO_NETWORK_SERVICE@ +DLLTOOL = @DLLTOOL@ +DNSMASQ_PATH = @DNSMASQ_PATH@ +DSYMUTIL = @DSYMUTIL@ +DUMPBIN = @DUMPBIN@ +ECHO_C = @ECHO_C@ +ECHO_N = @ECHO_N@ +ECHO_T = @ECHO_T@ +EGREP = @EGREP@ +EXEEXT = @EXEEXT@ +FGREP = @FGREP@ +GENHTML = @GENHTML@ +GETTEXT_MACRO_VERSION = @GETTEXT_MACRO_VERSION@ +GETTEXT_PACKAGE = @GETTEXT_PACKAGE@ +GLIB_CFLAGS = @GLIB_CFLAGS@ +GLIB_GENMARSHAL = @GLIB_GENMARSHAL@ +GLIB_LIBS = @GLIB_LIBS@ +GLIB_MAKEFILE = @GLIB_MAKEFILE@ +GLIB_MKENUMS = @GLIB_MKENUMS@ +GMSGFMT = @GMSGFMT@ +GMSGFMT_015 = @GMSGFMT_015@ +GNUTLS_CFLAGS = @GNUTLS_CFLAGS@ +GNUTLS_LIBS = @GNUTLS_LIBS@ +GREP = @GREP@ +GTKDOC_CHECK = @GTKDOC_CHECK@ +GTKDOC_DEPS_CFLAGS = @GTKDOC_DEPS_CFLAGS@ +GTKDOC_DEPS_LIBS = @GTKDOC_DEPS_LIBS@ +GTKDOC_MKPDF = @GTKDOC_MKPDF@ +GTKDOC_REBASE = @GTKDOC_REBASE@ +GUDEV_CFLAGS = @GUDEV_CFLAGS@ +GUDEV_LIBS = @GUDEV_LIBS@ +HTML_DIR = @HTML_DIR@ +INSTALL = @INSTALL@ +INSTALL_DATA = @INSTALL_DATA@ +INSTALL_PROGRAM = @INSTALL_PROGRAM@ +INSTALL_SCRIPT = @INSTALL_SCRIPT@ +INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@ +INTLLIBS = @INTLLIBS@ +INTLTOOL_EXTRACT = @INTLTOOL_EXTRACT@ +INTLTOOL_MERGE = @INTLTOOL_MERGE@ +INTLTOOL_PERL = @INTLTOOL_PERL@ +INTLTOOL_UPDATE = @INTLTOOL_UPDATE@ +INTLTOOL_V_MERGE = @INTLTOOL_V_MERGE@ +INTLTOOL_V_MERGE_OPTIONS = @INTLTOOL_V_MERGE_OPTIONS@ +INTLTOOL__v_MERGE_ = @INTLTOOL__v_MERGE_@ +INTLTOOL__v_MERGE_0 = @INTLTOOL__v_MERGE_0@ +INTL_MACOSX_LIBS = @INTL_MACOSX_LIBS@ +INTROSPECTION_CFLAGS = @INTROSPECTION_CFLAGS@ +INTROSPECTION_COMPILER = @INTROSPECTION_COMPILER@ +INTROSPECTION_GENERATE = @INTROSPECTION_GENERATE@ +INTROSPECTION_GIRDIR = @INTROSPECTION_GIRDIR@ +INTROSPECTION_LIBS = @INTROSPECTION_LIBS@ +INTROSPECTION_MAKEFILE = @INTROSPECTION_MAKEFILE@ +INTROSPECTION_SCANNER = @INTROSPECTION_SCANNER@ +INTROSPECTION_TYPELIBDIR = @INTROSPECTION_TYPELIBDIR@ +IPTABLES_PATH = @IPTABLES_PATH@ +IWMX_SDK_CFLAGS = @IWMX_SDK_CFLAGS@ +IWMX_SDK_LIBS = @IWMX_SDK_LIBS@ +KERNEL_FIRMWARE_DIR = @KERNEL_FIRMWARE_DIR@ +LCOV = @LCOV@ +LD = @LD@ +LDFLAGS = @LDFLAGS@ +LIBDL = @LIBDL@ +LIBGCRYPT_CFLAGS = @LIBGCRYPT_CFLAGS@ +LIBGCRYPT_CONFIG = @LIBGCRYPT_CONFIG@ +LIBGCRYPT_LIBS = @LIBGCRYPT_LIBS@ +LIBICONV = @LIBICONV@ +LIBINTL = @LIBINTL@ +LIBM = @LIBM@ +LIBNDP_CFLAGS = @LIBNDP_CFLAGS@ +LIBNDP_LIBS = @LIBNDP_LIBS@ +LIBNL_CFLAGS = @LIBNL_CFLAGS@ +LIBNL_LIBS = @LIBNL_LIBS@ +LIBOBJS = @LIBOBJS@ +LIBS = @LIBS@ +LIBSOUP_CFLAGS = @LIBSOUP_CFLAGS@ +LIBSOUP_LIBS = @LIBSOUP_LIBS@ +LIBTEAMDCTL_CFLAGS = @LIBTEAMDCTL_CFLAGS@ +LIBTEAMDCTL_LIBS = @LIBTEAMDCTL_LIBS@ +LIBTOOL = @LIBTOOL@ +LIPO = @LIPO@ +LN_S = @LN_S@ +LTLIBICONV = @LTLIBICONV@ +LTLIBINTL = @LTLIBINTL@ +LTLIBOBJS = @LTLIBOBJS@ +MAINT = @MAINT@ +MAKEINFO = @MAKEINFO@ +MANIFEST_TOOL = @MANIFEST_TOOL@ +MKDIR_P = @MKDIR_P@ +MM_GLIB_CFLAGS = @MM_GLIB_CFLAGS@ +MM_GLIB_LIBS = @MM_GLIB_LIBS@ +MOC = @MOC@ +MSGFMT = @MSGFMT@ +MSGFMT_015 = @MSGFMT_015@ +MSGMERGE = @MSGMERGE@ +NEWT_CFLAGS = @NEWT_CFLAGS@ +NEWT_LIBS = @NEWT_LIBS@ +NM = @NM@ +NMEDIT = @NMEDIT@ +NM_CONFIG_DEFAULT_AUTH_POLKIT_TEXT = @NM_CONFIG_DEFAULT_AUTH_POLKIT_TEXT@ +NM_MAJOR_VERSION = @NM_MAJOR_VERSION@ +NM_MICRO_VERSION = @NM_MICRO_VERSION@ +NM_MINOR_VERSION = @NM_MINOR_VERSION@ +NM_MODIFY_SYSTEM_POLICY = @NM_MODIFY_SYSTEM_POLICY@ +NM_VERSION = @NM_VERSION@ +NSS_CFLAGS = @NSS_CFLAGS@ +NSS_LIBS = @NSS_LIBS@ +OBJDUMP = @OBJDUMP@ +OBJEXT = @OBJEXT@ +OTOOL = @OTOOL@ +OTOOL64 = @OTOOL64@ +PACKAGE = @PACKAGE@ +PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@ +PACKAGE_NAME = @PACKAGE_NAME@ +PACKAGE_STRING = @PACKAGE_STRING@ +PACKAGE_TARNAME = @PACKAGE_TARNAME@ +PACKAGE_URL = @PACKAGE_URL@ +PACKAGE_VERSION = @PACKAGE_VERSION@ +PATH_SEPARATOR = @PATH_SEPARATOR@ +PERL = @PERL@ +PKG_CONFIG = @PKG_CONFIG@ +PKG_CONFIG_LIBDIR = @PKG_CONFIG_LIBDIR@ +PKG_CONFIG_PATH = @PKG_CONFIG_PATH@ +POLKIT_CFLAGS = @POLKIT_CFLAGS@ +POLKIT_LIBS = @POLKIT_LIBS@ +POSUB = @POSUB@ +PPPD_PATH = @PPPD_PATH@ +PPPD_PLUGIN_DIR = @PPPD_PLUGIN_DIR@ +PPPOE_PATH = @PPPOE_PATH@ +QT_CFLAGS = @QT_CFLAGS@ +QT_LIBS = @QT_LIBS@ +RANLIB = @RANLIB@ +READLINE_LIBS = @READLINE_LIBS@ +SED = @SED@ +SELINUX_CFLAGS = @SELINUX_CFLAGS@ +SELINUX_LIBS = @SELINUX_LIBS@ +SET_MAKE = @SET_MAKE@ +SHELL = @SHELL@ +STRIP = @STRIP@ +SYSTEMD_200_CFLAGS = @SYSTEMD_200_CFLAGS@ +SYSTEMD_200_LIBS = @SYSTEMD_200_LIBS@ +SYSTEMD_INHIBIT_CFLAGS = @SYSTEMD_INHIBIT_CFLAGS@ +SYSTEMD_INHIBIT_LIBS = @SYSTEMD_INHIBIT_LIBS@ +SYSTEMD_LOGIN_CFLAGS = @SYSTEMD_LOGIN_CFLAGS@ +SYSTEMD_LOGIN_LIBS = @SYSTEMD_LOGIN_LIBS@ +SYSTEM_CA_PATH = @SYSTEM_CA_PATH@ +UDEV_DIR = @UDEV_DIR@ +USE_NLS = @USE_NLS@ +UUID_CFLAGS = @UUID_CFLAGS@ +UUID_LIBS = @UUID_LIBS@ +VALGRIND_RULES = @VALGRIND_RULES@ +VAPIGEN = @VAPIGEN@ +VAPIGEN_MAKEFILE = @VAPIGEN_MAKEFILE@ +VAPIGEN_VAPIDIR = @VAPIGEN_VAPIDIR@ +VERSION = @VERSION@ +XGETTEXT = @XGETTEXT@ +XGETTEXT_015 = @XGETTEXT_015@ +XGETTEXT_EXTRA_OPTIONS = @XGETTEXT_EXTRA_OPTIONS@ +abs_builddir = @abs_builddir@ +abs_srcdir = @abs_srcdir@ +abs_top_builddir = @abs_top_builddir@ +abs_top_srcdir = @abs_top_srcdir@ +ac_ct_AR = @ac_ct_AR@ +ac_ct_CC = @ac_ct_CC@ +ac_ct_CXX = @ac_ct_CXX@ +ac_ct_DUMPBIN = @ac_ct_DUMPBIN@ +am__include = @am__include@ +am__leading_dot = @am__leading_dot@ +am__quote = @am__quote@ +am__tar = @am__tar@ +am__untar = @am__untar@ +bindir = @bindir@ +build = @build@ +build_alias = @build_alias@ +build_cpu = @build_cpu@ +build_os = @build_os@ +build_vendor = @build_vendor@ +builddir = @builddir@ +datadir = @datadir@ +datarootdir = @datarootdir@ +docdir = @docdir@ +dvidir = @dvidir@ +exec_prefix = @exec_prefix@ +host = @host@ +host_alias = @host_alias@ +host_cpu = @host_cpu@ +host_os = @host_os@ +host_vendor = @host_vendor@ +htmldir = @htmldir@ +includedir = @includedir@ +infodir = @infodir@ +install_sh = @install_sh@ +intltool__v_merge_options_ = @intltool__v_merge_options_@ +intltool__v_merge_options_0 = @intltool__v_merge_options_0@ +libdir = @libdir@ +libexecdir = @libexecdir@ +localedir = @localedir@ +localstatedir = @localstatedir@ +mandir = @mandir@ +mkdir_p = @mkdir_p@ +nmbinary = @nmbinary@ +nmconfdir = @nmconfdir@ +nmdatadir = @nmdatadir@ +nmrundir = @nmrundir@ +nmstatedir = @nmstatedir@ +oldincludedir = @oldincludedir@ +pdfdir = @pdfdir@ +prefix = @prefix@ +program_transform_name = @program_transform_name@ +psdir = @psdir@ +sbindir = @sbindir@ +sharedstatedir = @sharedstatedir@ +srcdir = @srcdir@ +subdirs = @subdirs@ +sysconfdir = @sysconfdir@ +systemdsystemunitdir = @systemdsystemunitdir@ +target_alias = @target_alias@ +top_build_prefix = @top_build_prefix@ +top_builddir = @top_builddir@ +top_srcdir = @top_srcdir@ +with_dhclient = @with_dhclient@ +with_dhcpcd = @with_dhcpcd@ +with_netconfig = @with_netconfig@ +with_resolvconf = @with_resolvconf@ +with_valgrind = @with_valgrind@ +AM_CPPFLAGS = -I${top_srcdir} -I${top_builddir} \ + -I${top_srcdir}/include -I${top_srcdir}/libnm-core \ + -I${top_builddir}/libnm-core -I${top_srcdir}/libnm \ + -I${top_builddir}/libnm -I${top_srcdir}/clients/common \ + $(GLIB_CFLAGS) -DG_LOG_DOMAIN=\""nmcli"\" \ + -DNM_VERSION_MAX_ALLOWED=NM_VERSION_NEXT_STABLE \ + -DNMCLI_LOCALEDIR=\"$(datadir)/locale\" $(am__append_1) +nmcli_SOURCES = agent.c agent.h common.c common.h connections.c \ + connections.h devices.c devices.h general.c general.h \ + settings.c settings.h nmcli.c nmcli.h utils.c utils.h \ + polkit-agent.c polkit-agent.h \ + $(srcdir)/../common/nm-secret-agent-simple.c \ + $(srcdir)/../common/nm-secret-agent-simple.h $(NULL) \ + $(am__append_2) +nmcli_LDADD = $(GLIB_LIBS) $(READLINE_LIBS) \ + $(top_builddir)/libnm/libnm.la $(am__append_3) +@BUILD_SETTING_DOCS_TRUE@BUILT_SOURCES = settings-docs.c +@BUILD_SETTING_DOCS_TRUE@CLEANFILES = settings-docs.c +EXTRA_DIST = settings-docs.c settings-docs.xsl nmcli-completion +completiondir = $(datadir)/bash-completion/completions +all: $(BUILT_SOURCES) + $(MAKE) $(AM_MAKEFLAGS) all-am + +.SUFFIXES: +.SUFFIXES: .c .lo .o .obj +$(srcdir)/Makefile.in: @MAINTAINER_MODE_TRUE@ $(srcdir)/Makefile.am $(am__configure_deps) + @for dep in $?; do \ + case '$(am__configure_deps)' in \ + *$$dep*) \ + ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \ + && { if test -f $@; then exit 0; else break; fi; }; \ + exit 1;; \ + esac; \ + done; \ + echo ' cd $(top_srcdir) && $(AUTOMAKE) --gnu clients/cli/Makefile'; \ + $(am__cd) $(top_srcdir) && \ + $(AUTOMAKE) --gnu clients/cli/Makefile +.PRECIOUS: Makefile +Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status + @case '$?' in \ + *config.status*) \ + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \ + *) \ + echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__depfiles_maybe)'; \ + cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__depfiles_maybe);; \ + esac; + +$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh + +$(top_srcdir)/configure: @MAINTAINER_MODE_TRUE@ $(am__configure_deps) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh +$(ACLOCAL_M4): @MAINTAINER_MODE_TRUE@ $(am__aclocal_m4_deps) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh +$(am__aclocal_m4_deps): +install-binPROGRAMS: $(bin_PROGRAMS) + @$(NORMAL_INSTALL) + @list='$(bin_PROGRAMS)'; test -n "$(bindir)" || list=; \ + if test -n "$$list"; then \ + echo " $(MKDIR_P) '$(DESTDIR)$(bindir)'"; \ + $(MKDIR_P) "$(DESTDIR)$(bindir)" || exit 1; \ + fi; \ + for p in $$list; do echo "$$p $$p"; done | \ + sed 's/$(EXEEXT)$$//' | \ + while read p p1; do if test -f $$p \ + || test -f $$p1 \ + ; then echo "$$p"; echo "$$p"; else :; fi; \ + done | \ + sed -e 'p;s,.*/,,;n;h' \ + -e 's|.*|.|' \ + -e 'p;x;s,.*/,,;s/$(EXEEXT)$$//;$(transform);s/$$/$(EXEEXT)/' | \ + sed 'N;N;N;s,\n, ,g' | \ + $(AWK) 'BEGIN { files["."] = ""; dirs["."] = 1 } \ + { d=$$3; if (dirs[d] != 1) { print "d", d; dirs[d] = 1 } \ + if ($$2 == $$4) files[d] = files[d] " " $$1; \ + else { print "f", $$3 "/" $$4, $$1; } } \ + END { for (d in files) print "f", d, files[d] }' | \ + while read type dir files; do \ + if test "$$dir" = .; then dir=; else dir=/$$dir; fi; \ + test -z "$$files" || { \ + echo " $(INSTALL_PROGRAM_ENV) $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL_PROGRAM) $$files '$(DESTDIR)$(bindir)$$dir'"; \ + $(INSTALL_PROGRAM_ENV) $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL_PROGRAM) $$files "$(DESTDIR)$(bindir)$$dir" || exit $$?; \ + } \ + ; done + +uninstall-binPROGRAMS: + @$(NORMAL_UNINSTALL) + @list='$(bin_PROGRAMS)'; test -n "$(bindir)" || list=; \ + files=`for p in $$list; do echo "$$p"; done | \ + sed -e 'h;s,^.*/,,;s/$(EXEEXT)$$//;$(transform)' \ + -e 's/$$/$(EXEEXT)/' \ + `; \ + test -n "$$list" || exit 0; \ + echo " ( cd '$(DESTDIR)$(bindir)' && rm -f" $$files ")"; \ + cd "$(DESTDIR)$(bindir)" && rm -f $$files + +clean-binPROGRAMS: + @list='$(bin_PROGRAMS)'; test -n "$$list" || exit 0; \ + echo " rm -f" $$list; \ + rm -f $$list || exit $$?; \ + test -n "$(EXEEXT)" || exit 0; \ + list=`for p in $$list; do echo "$$p"; done | sed 's/$(EXEEXT)$$//'`; \ + echo " rm -f" $$list; \ + rm -f $$list + +nmcli$(EXEEXT): $(nmcli_OBJECTS) $(nmcli_DEPENDENCIES) $(EXTRA_nmcli_DEPENDENCIES) + @rm -f nmcli$(EXEEXT) + $(AM_V_CCLD)$(LINK) $(nmcli_OBJECTS) $(nmcli_LDADD) $(LIBS) + +mostlyclean-compile: + -rm -f *.$(OBJEXT) + +distclean-compile: + -rm -f *.tab.c + +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/agent.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/common.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/connections.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/devices.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/general.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/nm-polkit-listener.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/nm-secret-agent-simple.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/nmcli.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/polkit-agent.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/settings.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/utils.Po@am__quote@ + +.c.o: +@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $< +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c $< + +.c.obj: +@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ `$(CYGPATH_W) '$<'` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c `$(CYGPATH_W) '$<'` + +.c.lo: +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LTCOMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $< +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LTCOMPILE) -c -o $@ $< + +nm-secret-agent-simple.o: $(srcdir)/../common/nm-secret-agent-simple.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT nm-secret-agent-simple.o -MD -MP -MF $(DEPDIR)/nm-secret-agent-simple.Tpo -c -o nm-secret-agent-simple.o `test -f '$(srcdir)/../common/nm-secret-agent-simple.c' || echo '$(srcdir)/'`$(srcdir)/../common/nm-secret-agent-simple.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/nm-secret-agent-simple.Tpo $(DEPDIR)/nm-secret-agent-simple.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$(srcdir)/../common/nm-secret-agent-simple.c' object='nm-secret-agent-simple.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o nm-secret-agent-simple.o `test -f '$(srcdir)/../common/nm-secret-agent-simple.c' || echo '$(srcdir)/'`$(srcdir)/../common/nm-secret-agent-simple.c + +nm-secret-agent-simple.obj: $(srcdir)/../common/nm-secret-agent-simple.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT nm-secret-agent-simple.obj -MD -MP -MF $(DEPDIR)/nm-secret-agent-simple.Tpo -c -o nm-secret-agent-simple.obj `if test -f '$(srcdir)/../common/nm-secret-agent-simple.c'; then $(CYGPATH_W) '$(srcdir)/../common/nm-secret-agent-simple.c'; else $(CYGPATH_W) '$(srcdir)/$(srcdir)/../common/nm-secret-agent-simple.c'; fi` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/nm-secret-agent-simple.Tpo $(DEPDIR)/nm-secret-agent-simple.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$(srcdir)/../common/nm-secret-agent-simple.c' object='nm-secret-agent-simple.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o nm-secret-agent-simple.obj `if test -f '$(srcdir)/../common/nm-secret-agent-simple.c'; then $(CYGPATH_W) '$(srcdir)/../common/nm-secret-agent-simple.c'; else $(CYGPATH_W) '$(srcdir)/$(srcdir)/../common/nm-secret-agent-simple.c'; fi` + +nm-polkit-listener.o: $(srcdir)/../common/nm-polkit-listener.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT nm-polkit-listener.o -MD -MP -MF $(DEPDIR)/nm-polkit-listener.Tpo -c -o nm-polkit-listener.o `test -f '$(srcdir)/../common/nm-polkit-listener.c' || echo '$(srcdir)/'`$(srcdir)/../common/nm-polkit-listener.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/nm-polkit-listener.Tpo $(DEPDIR)/nm-polkit-listener.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$(srcdir)/../common/nm-polkit-listener.c' object='nm-polkit-listener.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o nm-polkit-listener.o `test -f '$(srcdir)/../common/nm-polkit-listener.c' || echo '$(srcdir)/'`$(srcdir)/../common/nm-polkit-listener.c + +nm-polkit-listener.obj: $(srcdir)/../common/nm-polkit-listener.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT nm-polkit-listener.obj -MD -MP -MF $(DEPDIR)/nm-polkit-listener.Tpo -c -o nm-polkit-listener.obj `if test -f '$(srcdir)/../common/nm-polkit-listener.c'; then $(CYGPATH_W) '$(srcdir)/../common/nm-polkit-listener.c'; else $(CYGPATH_W) '$(srcdir)/$(srcdir)/../common/nm-polkit-listener.c'; fi` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/nm-polkit-listener.Tpo $(DEPDIR)/nm-polkit-listener.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$(srcdir)/../common/nm-polkit-listener.c' object='nm-polkit-listener.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o nm-polkit-listener.obj `if test -f '$(srcdir)/../common/nm-polkit-listener.c'; then $(CYGPATH_W) '$(srcdir)/../common/nm-polkit-listener.c'; else $(CYGPATH_W) '$(srcdir)/$(srcdir)/../common/nm-polkit-listener.c'; fi` + +mostlyclean-libtool: + -rm -f *.lo + +clean-libtool: + -rm -rf .libs _libs + +ID: $(am__tagged_files) + $(am__define_uniq_tagged_files); mkid -fID $$unique +tags: tags-am +TAGS: tags + +tags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files) + set x; \ + here=`pwd`; \ + $(am__define_uniq_tagged_files); \ + shift; \ + if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \ + test -n "$$unique" || unique=$$empty_fix; \ + if test $$# -gt 0; then \ + $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ + "$$@" $$unique; \ + else \ + $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ + $$unique; \ + fi; \ + fi +ctags: ctags-am + +CTAGS: ctags +ctags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files) + $(am__define_uniq_tagged_files); \ + test -z "$(CTAGS_ARGS)$$unique" \ + || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \ + $$unique + +GTAGS: + here=`$(am__cd) $(top_builddir) && pwd` \ + && $(am__cd) $(top_srcdir) \ + && gtags -i $(GTAGS_ARGS) "$$here" +cscopelist: cscopelist-am + +cscopelist-am: $(am__tagged_files) + list='$(am__tagged_files)'; \ + case "$(srcdir)" in \ + [\\/]* | ?:[\\/]*) sdir="$(srcdir)" ;; \ + *) sdir=$(subdir)/$(srcdir) ;; \ + esac; \ + for i in $$list; do \ + if test -f "$$i"; then \ + echo "$(subdir)/$$i"; \ + else \ + echo "$$sdir/$$i"; \ + fi; \ + done >> $(top_builddir)/cscope.files + +distclean-tags: + -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags + +distdir: $(DISTFILES) + @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ + topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ + list='$(DISTFILES)'; \ + dist_files=`for file in $$list; do echo $$file; done | \ + sed -e "s|^$$srcdirstrip/||;t" \ + -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \ + case $$dist_files in \ + */*) $(MKDIR_P) `echo "$$dist_files" | \ + sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \ + sort -u` ;; \ + esac; \ + for file in $$dist_files; do \ + if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \ + if test -d $$d/$$file; then \ + dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \ + if test -d "$(distdir)/$$file"; then \ + find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ + fi; \ + if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \ + cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \ + find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ + fi; \ + cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \ + else \ + test -f "$(distdir)/$$file" \ + || cp -p $$d/$$file "$(distdir)/$$file" \ + || exit 1; \ + fi; \ + done +check-am: all-am +check: $(BUILT_SOURCES) + $(MAKE) $(AM_MAKEFLAGS) check-am +all-am: Makefile $(PROGRAMS) +installdirs: + for dir in "$(DESTDIR)$(bindir)"; do \ + test -z "$$dir" || $(MKDIR_P) "$$dir"; \ + done +install: $(BUILT_SOURCES) + $(MAKE) $(AM_MAKEFLAGS) install-am +install-exec: install-exec-am +install-data: install-data-am +uninstall: uninstall-am + +install-am: all-am + @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am + +installcheck: installcheck-am +install-strip: + if test -z '$(STRIP)'; then \ + $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ + install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ + install; \ + else \ + $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ + install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ + "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \ + fi +mostlyclean-generic: + +clean-generic: + -test -z "$(CLEANFILES)" || rm -f $(CLEANFILES) + +distclean-generic: + -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES) + -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES) + +maintainer-clean-generic: + @echo "This command is intended for maintainers to use" + @echo "it deletes files that may require special tools to rebuild." + -test -z "$(BUILT_SOURCES)" || rm -f $(BUILT_SOURCES) +clean: clean-am + +clean-am: clean-binPROGRAMS clean-generic clean-libtool mostlyclean-am + +distclean: distclean-am + -rm -rf ./$(DEPDIR) + -rm -f Makefile +distclean-am: clean-am distclean-compile distclean-generic \ + distclean-tags + +dvi: dvi-am + +dvi-am: + +html: html-am + +html-am: + +info: info-am + +info-am: + +install-data-am: + @$(NORMAL_INSTALL) + $(MAKE) $(AM_MAKEFLAGS) install-data-hook +install-dvi: install-dvi-am + +install-dvi-am: + +install-exec-am: install-binPROGRAMS + +install-html: install-html-am + +install-html-am: + +install-info: install-info-am + +install-info-am: + +install-man: + +install-pdf: install-pdf-am + +install-pdf-am: + +install-ps: install-ps-am + +install-ps-am: + +installcheck-am: + +maintainer-clean: maintainer-clean-am + -rm -rf ./$(DEPDIR) + -rm -f Makefile +maintainer-clean-am: distclean-am maintainer-clean-generic + +mostlyclean: mostlyclean-am + +mostlyclean-am: mostlyclean-compile mostlyclean-generic \ + mostlyclean-libtool + +pdf: pdf-am + +pdf-am: + +ps: ps-am + +ps-am: + +uninstall-am: uninstall-binPROGRAMS + @$(NORMAL_INSTALL) + $(MAKE) $(AM_MAKEFLAGS) uninstall-hook +.MAKE: all check install install-am install-data-am install-strip \ + uninstall-am + +.PHONY: CTAGS GTAGS TAGS all all-am check check-am clean \ + clean-binPROGRAMS clean-generic clean-libtool cscopelist-am \ + ctags ctags-am distclean distclean-compile distclean-generic \ + distclean-libtool distclean-tags distdir dvi dvi-am html \ + html-am info info-am install install-am install-binPROGRAMS \ + install-data install-data-am install-data-hook install-dvi \ + install-dvi-am install-exec install-exec-am install-html \ + install-html-am install-info install-info-am install-man \ + install-pdf install-pdf-am install-ps install-ps-am \ + install-strip installcheck installcheck-am installdirs \ + maintainer-clean maintainer-clean-generic mostlyclean \ + mostlyclean-compile mostlyclean-generic mostlyclean-libtool \ + pdf pdf-am ps ps-am tags tags-am uninstall uninstall-am \ + uninstall-binPROGRAMS uninstall-hook + + +@BUILD_SETTING_DOCS_TRUE@settings-docs.c: settings-docs.xsl $(top_builddir)/libnm/nm-property-docs.xml +@BUILD_SETTING_DOCS_TRUE@ $(AM_V_GEN) xsltproc --output $@ $^ + +install-data-hook: + $(mkinstalldirs) $(DESTDIR)$(completiondir) + $(INSTALL_DATA) $(srcdir)/nmcli-completion $(DESTDIR)$(completiondir)/nmcli + +uninstall-hook: + rm -f $(DESTDIR)$(completiondir)/nmcli + +# Tell versions [3.59,3.63) of GNU make to not export all variables. +# Otherwise a system limit (for SysV at least) may be exceeded. +.NOEXPORT: diff --git a/clients/cli/agent.c b/clients/cli/agent.c new file mode 100644 index 00000000..1695ed59 --- /dev/null +++ b/clients/cli/agent.c @@ -0,0 +1,252 @@ +/* + * nmcli - command-line tool for controlling NetworkManager + * Functions for running NM secret agent. + * + * 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., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Copyright 2014 Red Hat, Inc. + */ + +#include "config.h" + +#include +#include +#include +#include +#include + +#include +#include + +#include "common.h" +#include "utils.h" +#include "nm-secret-agent-simple.h" +#include "polkit-agent.h" +#include "agent.h" + +static void +usage (void) +{ + g_printerr (_("Usage: nmcli agent { COMMAND | help }\n\n" + "COMMAND := { secret | polkit | all }\n\n" + )); +} + +static void +usage_agent_secret (void) +{ + g_printerr (_("Usage: nmcli agent secret { help }\n" + "\n" + "Runs nmcli as NetworkManager secret agent. When NetworkManager requires\n" + "a password it asks registered agents for it. This command keeps nmcli running\n" + "and if a password is required asks the user for it.\n\n")); +} + +static void +usage_agent_polkit (void) +{ + g_printerr (_("Usage: nmcli agent polkit { help }\n" + "\n" + "Registers nmcli as a polkit action for the user session.\n" + "When a polkit daemon requires an authorization, nmcli asks the user and gives\n" + "the response back to polkit.\n\n")); +} + +static void +usage_agent_all (void) +{ + g_printerr (_("Usage: nmcli agent all { help }\n" + "\n" + "Runs nmcli as both NetworkManager secret and a polkit agent.\n\n")); +} + +/* for pre-filling a string to readline prompt */ +static char *pre_input_deftext; +static int +set_deftext (void) +{ + if (pre_input_deftext && rl_startup_hook) { + rl_insert_text (pre_input_deftext); + g_free (pre_input_deftext); + pre_input_deftext = NULL; + rl_startup_hook = NULL; + } + return 0; +} + +static gboolean +get_secrets_from_user (const char *request_id, + const char *title, + const char *msg, + GPtrArray *secrets) +{ + int i; + + for (i = 0; i < secrets->len; i++) { + NMSecretAgentSimpleSecret *secret = secrets->pdata[i]; + char *pwd = NULL; + + /* Ask user for the password */ + g_print ("%s\n", msg); + if (secret->value) { + /* Prefill the password if we have it. */ + rl_startup_hook = set_deftext; + pre_input_deftext = g_strdup (secret->value); + } + pwd = nmc_readline ("%s (%s): ", secret->name, secret->prop_name); + + /* No password provided, cancel the secrets. */ + if (!pwd) + return FALSE; + g_free (secret->value); + secret->value = pwd; + } + return TRUE; +} + +static void +secrets_requested (NMSecretAgentSimple *agent, + const char *request_id, + const char *title, + const char *msg, + GPtrArray *secrets, + gpointer user_data) +{ + NmCli *nmc = (NmCli *) user_data; + gboolean success = FALSE; + + if (nmc->print_output == NMC_PRINT_PRETTY) + nmc_terminal_erase_line (); + + success = get_secrets_from_user (request_id, title, msg, secrets); + if (success) + nm_secret_agent_simple_response (agent, request_id, secrets); + else + nm_secret_agent_simple_response (agent, request_id, NULL); +} + + +static NMCResultCode +do_agent_secret (NmCli *nmc, int argc, char **argv) +{ + /* Create secret agent */ + nmc->secret_agent = nm_secret_agent_simple_new ("nmcli-agent"); + if (nmc->secret_agent) { + /* We keep running */ + nmc->should_wait = TRUE; + + nm_secret_agent_simple_enable (NM_SECRET_AGENT_SIMPLE (nmc->secret_agent), NULL); + g_signal_connect (nmc->secret_agent, "request-secrets", G_CALLBACK (secrets_requested), nmc); + g_print (_("nmcli successfully registered as a NetworkManager's secret agent.\n")); + } else { + g_string_printf (nmc->return_text, _("Error: secret agent initialization failed")); + nmc->return_value = NMC_RESULT_ERROR_UNKNOWN; + } + + return nmc->return_value; +} + +static NMCResultCode +do_agent_polkit (NmCli *nmc, int argc, char **argv) +{ + GError *error = NULL; + + /* Initialize polkit agent */ + if (!nmc_polkit_agent_init (nmc, TRUE, &error)) { + g_dbus_error_strip_remote_error (error); + g_string_printf (nmc->return_text, _("Error: polkit agent initialization failed: %s"), + error->message); + nmc->return_value = NMC_RESULT_ERROR_UNKNOWN; + g_error_free (error); + } else { + /* We keep running */ + nmc->should_wait = TRUE; + + g_print (_("nmcli successfully registered as a polkit agent.\n")); + } + + return nmc->return_value; +} + +static NMCResultCode +do_agent_all (NmCli *nmc, int argc, char **argv) +{ + NMCResultCode secret_res; + + /* Run both secret and polkit agent */ + secret_res = do_agent_secret (nmc, argc, argv); + if (secret_res != NMC_RESULT_SUCCESS) + g_printerr ("%s\n", nmc->return_text->str); + + nmc->return_value = do_agent_polkit (nmc, argc, argv); + + if (nmc->return_value == NMC_RESULT_SUCCESS && secret_res != NMC_RESULT_SUCCESS) + nmc->return_value = secret_res; + + return nmc->return_value; +} + +NMCResultCode +do_agent (NmCli *nmc, int argc, char **argv) +{ + /* Get NMClient object */ + nmc->get_client (nmc); + + /* Check whether NetworkManager is running */ + if (!nm_client_get_nm_running (nmc->client)) { + g_string_printf (nmc->return_text, _("Error: NetworkManager is not running.")); + nmc->return_value = NMC_RESULT_ERROR_NM_NOT_RUNNING; + return nmc->return_value; + } + /* Compare NM and nmcli versions */ + if (!nmc_versions_match (nmc)) + return nmc->return_value; + + if (argc == 0) { + nmc->return_value = do_agent_all (nmc, 0, NULL); + } + + if (argc > 0) { + if (nmc_arg_is_help (*argv)) { + usage (); + goto usage_exit; + } else if (matches (*argv, "secret") == 0) { + if (nmc_arg_is_help (*(argv+1))) { + usage_agent_secret (); + goto usage_exit; + } + nmc->return_value = do_agent_secret (nmc, argc-1, argv+1); + } else if (matches (*argv, "polkit") == 0) { + if (nmc_arg_is_help (*(argv+1))) { + usage_agent_polkit (); + goto usage_exit; + } + nmc->return_value = do_agent_polkit (nmc, argc-1, argv+1); + } else if (matches (*argv, "all") == 0) { + if (nmc_arg_is_help (*(argv+1))) { + usage_agent_all (); + goto usage_exit; + } + nmc->return_value = do_agent_all (nmc, argc-1, argv+1); + } else { + usage (); + g_string_printf (nmc->return_text, _("Error: 'agent' command '%s' is not valid."), *argv); + nmc->return_value = NMC_RESULT_ERROR_USER_INPUT; + } + } + +usage_exit: + return nmc->return_value; +} diff --git a/clients/cli/agent.h b/clients/cli/agent.h new file mode 100644 index 00000000..70ea0d9b --- /dev/null +++ b/clients/cli/agent.h @@ -0,0 +1,29 @@ +/* + * nmcli - command-line tool for controlling NetworkManager + * Functions for running NM secret agent. + * + * 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., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Copyright 2014 Red Hat, Inc. + */ + +#ifndef __NMC_AGENT_H__ +#define __NMC_AGENT_H__ + +#include "nmcli.h" + +NMCResultCode do_agent (NmCli *nmc, int argc, char **argv); + +#endif /* __NMC_AGENT_H__ */ diff --git a/clients/cli/common.c b/clients/cli/common.c new file mode 100644 index 00000000..6b13f7f8 --- /dev/null +++ b/clients/cli/common.c @@ -0,0 +1,1146 @@ +/* + * nmcli - command-line tool for controlling NetworkManager + * Common functions and data shared between files. + * + * 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., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Copyright 2012 - 2014 Red Hat, Inc. + */ + +#include "config.h" + +#include +#include +#include +#include +#include + +#include +#include + +#include "nm-glib-compat.h" + +#include "common.h" +#include "utils.h" + +/* Available fields for IPv4 group */ +NmcOutputField nmc_fields_ip4_config[] = { + {"GROUP", N_("GROUP"), 15}, /* 0 */ + {"ADDRESS", N_("ADDRESS"), 68}, /* 1 */ + {"GATEWAY", N_("GATEWAY"), 0}, /* 2 */ + {"ROUTE", N_("ROUTE"), 68}, /* 3 */ + {"DNS", N_("DNS"), 35}, /* 4 */ + {"DOMAIN", N_("DOMAIN"), 35}, /* 5 */ + {"WINS", N_("WINS"), 20}, /* 6 */ + {NULL, NULL, 0} +}; +#define NMC_FIELDS_IP4_CONFIG_ALL "GROUP,ADDRESS,GATEWAY,ROUTE,DNS,DOMAIN,WINS" + +/* Available fields for DHCPv4 group */ +NmcOutputField nmc_fields_dhcp4_config[] = { + {"GROUP", N_("GROUP"), 15}, /* 0 */ + {"OPTION", N_("OPTION"), 80}, /* 1 */ + {NULL, NULL, 0} +}; +#define NMC_FIELDS_DHCP4_CONFIG_ALL "GROUP,OPTION" + +/* Available fields for IPv6 group */ +NmcOutputField nmc_fields_ip6_config[] = { + {"GROUP", N_("GROUP"), 15}, /* 0 */ + {"ADDRESS", N_("ADDRESS"), 95}, /* 1 */ + {"GATEWAY", N_("GATEWAY"), 0}, /* 2 */ + {"ROUTE", N_("ROUTE"), 95}, /* 3 */ + {"DNS", N_("DNS"), 60}, /* 4 */ + {"DOMAIN", N_("DOMAIN"), 35}, /* 5 */ + {NULL, NULL, 0} +}; +#define NMC_FIELDS_IP6_CONFIG_ALL "GROUP,ADDRESS,GATEWAY,ROUTE,DNS,DOMAIN" + +/* Available fields for DHCPv6 group */ +NmcOutputField nmc_fields_dhcp6_config[] = { + {"GROUP", N_("GROUP"), 15}, /* 0 */ + {"OPTION", N_("OPTION"), 80}, /* 1 */ + {NULL, NULL, 0} +}; +#define NMC_FIELDS_DHCP6_CONFIG_ALL "GROUP,OPTION" + + +gboolean +print_ip4_config (NMIPConfig *cfg4, + NmCli *nmc, + const char *group_prefix, + const char *one_field) +{ + GPtrArray *ptr_array; + char **addr_arr = NULL; + char **route_arr = NULL; + char **dns_arr = NULL; + char **domain_arr = NULL; + char **wins_arr = NULL; + int i = 0; + NmcOutputField *tmpl, *arr; + size_t tmpl_len; + + if (cfg4 == NULL) + return FALSE; + + tmpl = nmc_fields_ip4_config; + tmpl_len = sizeof (nmc_fields_ip4_config); + nmc->print_fields.indices = parse_output_fields (one_field ? one_field : NMC_FIELDS_IP4_CONFIG_ALL, + tmpl, FALSE, NULL, NULL); + arr = nmc_dup_fields_array (tmpl, tmpl_len, NMC_OF_FLAG_FIELD_NAMES); + g_ptr_array_add (nmc->output_data, arr); + + /* addresses */ + ptr_array = nm_ip_config_get_addresses (cfg4); + if (ptr_array) { + addr_arr = g_new (char *, ptr_array->len + 1); + for (i = 0; i < ptr_array->len; i++) { + NMIPAddress *addr = (NMIPAddress *) g_ptr_array_index (ptr_array, i); + + addr_arr[i] = g_strdup_printf ("%s/%u", + nm_ip_address_get_address (addr), + nm_ip_address_get_prefix (addr)); + } + addr_arr[i] = NULL; + } + + /* routes */ + ptr_array = nm_ip_config_get_routes (cfg4); + if (ptr_array) { + route_arr = g_new (char *, ptr_array->len + 1); + for (i = 0; i < ptr_array->len; i++) { + NMIPRoute *route = (NMIPRoute *) g_ptr_array_index (ptr_array, i); + const char *next_hop; + + next_hop = nm_ip_route_get_next_hop (route); + if (!next_hop) + next_hop = "0.0.0.0"; + + route_arr[i] = g_strdup_printf ("dst = %s/%u, nh = %s%c mt = %u", + nm_ip_route_get_dest (route), + nm_ip_route_get_prefix (route), + next_hop, + nm_ip_route_get_metric (route) == -1 ? '\0' : ',', + (guint32) nm_ip_route_get_metric (route)); + } + route_arr[i] = NULL; + } + + /* DNS */ + dns_arr = g_strdupv ((char **) nm_ip_config_get_nameservers (cfg4)); + + /* domains */ + domain_arr = g_strdupv ((char **) nm_ip_config_get_domains (cfg4)); + + /* WINS */ + wins_arr = g_strdupv ((char **) nm_ip_config_get_wins_servers (cfg4)); + + arr = nmc_dup_fields_array (tmpl, tmpl_len, NMC_OF_FLAG_SECTION_PREFIX); + set_val_strc (arr, 0, group_prefix); + set_val_arr (arr, 1, addr_arr); + set_val_strc (arr, 2, nm_ip_config_get_gateway (cfg4)); + set_val_arr (arr, 3, route_arr); + set_val_arr (arr, 4, dns_arr); + set_val_arr (arr, 5, domain_arr); + set_val_arr (arr, 6, wins_arr); + g_ptr_array_add (nmc->output_data, arr); + + print_data (nmc); /* Print all data */ + + /* Remove any previous data */ + nmc_empty_output_fields (nmc); + + return TRUE; +} + +gboolean +print_ip6_config (NMIPConfig *cfg6, + NmCli *nmc, + const char *group_prefix, + const char *one_field) +{ + GPtrArray *ptr_array; + char **addr_arr = NULL; + char **route_arr = NULL; + char **dns_arr = NULL; + char **domain_arr = NULL; + int i = 0; + NmcOutputField *tmpl, *arr; + size_t tmpl_len; + + if (cfg6 == NULL) + return FALSE; + + tmpl = nmc_fields_ip6_config; + tmpl_len = sizeof (nmc_fields_ip6_config); + nmc->print_fields.indices = parse_output_fields (one_field ? one_field : NMC_FIELDS_IP6_CONFIG_ALL, + tmpl, FALSE, NULL, NULL); + arr = nmc_dup_fields_array (tmpl, tmpl_len, NMC_OF_FLAG_FIELD_NAMES); + g_ptr_array_add (nmc->output_data, arr); + + /* addresses */ + ptr_array = nm_ip_config_get_addresses (cfg6); + if (ptr_array) { + addr_arr = g_new (char *, ptr_array->len + 1); + for (i = 0; i < ptr_array->len; i++) { + NMIPAddress *addr = (NMIPAddress *) g_ptr_array_index (ptr_array, i); + + addr_arr[i] = g_strdup_printf ("%s/%u", + nm_ip_address_get_address (addr), + nm_ip_address_get_prefix (addr)); + } + addr_arr[i] = NULL; + } + + /* routes */ + ptr_array = nm_ip_config_get_routes (cfg6); + if (ptr_array) { + route_arr = g_new (char *, ptr_array->len + 1); + for (i = 0; i < ptr_array->len; i++) { + NMIPRoute *route = (NMIPRoute *) g_ptr_array_index (ptr_array, i); + const char *next_hop; + + next_hop = nm_ip_route_get_next_hop (route); + if (!next_hop) + next_hop = "::"; + + route_arr[i] = g_strdup_printf ("dst = %s/%u, nh = %s%c mt = %u", + nm_ip_route_get_dest (route), + nm_ip_route_get_prefix (route), + next_hop, + nm_ip_route_get_metric (route) == -1 ? '\0' : ',', + (guint32) nm_ip_route_get_metric (route)); + } + route_arr[i] = NULL; + } + + /* DNS */ + dns_arr = g_strdupv ((char **) nm_ip_config_get_nameservers (cfg6)); + + /* domains */ + domain_arr = g_strdupv ((char **) nm_ip_config_get_domains (cfg6)); + + arr = nmc_dup_fields_array (tmpl, tmpl_len, NMC_OF_FLAG_SECTION_PREFIX); + set_val_strc (arr, 0, group_prefix); + set_val_arr (arr, 1, addr_arr); + set_val_strc (arr, 2, nm_ip_config_get_gateway (cfg6)); + set_val_arr (arr, 3, route_arr); + set_val_arr (arr, 4, dns_arr); + set_val_arr (arr, 5, domain_arr); + g_ptr_array_add (nmc->output_data, arr); + + print_data (nmc); /* Print all data */ + + /* Remove any previous data */ + nmc_empty_output_fields (nmc); + + return TRUE; +} + +gboolean +print_dhcp4_config (NMDhcpConfig *dhcp4, + NmCli *nmc, + const char *group_prefix, + const char *one_field) +{ + GHashTable *table; + NmcOutputField *tmpl, *arr; + size_t tmpl_len; + + if (dhcp4 == NULL) + return FALSE; + + table = nm_dhcp_config_get_options (dhcp4); + if (table) { + GHashTableIter table_iter; + gpointer key, value; + char **options_arr = NULL; + int i = 0; + + tmpl = nmc_fields_dhcp4_config; + tmpl_len = sizeof (nmc_fields_dhcp4_config); + nmc->print_fields.indices = parse_output_fields (one_field ? one_field : NMC_FIELDS_DHCP4_CONFIG_ALL, + tmpl, FALSE, NULL, NULL); + arr = nmc_dup_fields_array (tmpl, tmpl_len, NMC_OF_FLAG_FIELD_NAMES); + g_ptr_array_add (nmc->output_data, arr); + + options_arr = g_new (char *, g_hash_table_size (table) + 1); + g_hash_table_iter_init (&table_iter, table); + while (g_hash_table_iter_next (&table_iter, &key, &value)) + options_arr[i++] = g_strdup_printf ("%s = %s", (char *) key, (char *) value); + options_arr[i] = NULL; + + arr = nmc_dup_fields_array (tmpl, tmpl_len, NMC_OF_FLAG_SECTION_PREFIX); + set_val_strc (arr, 0, group_prefix); + set_val_arr (arr, 1, options_arr); + g_ptr_array_add (nmc->output_data, arr); + + print_data (nmc); /* Print all data */ + + /* Remove any previous data */ + nmc_empty_output_fields (nmc); + + return TRUE; + } + return FALSE; +} + +gboolean +print_dhcp6_config (NMDhcpConfig *dhcp6, + NmCli *nmc, + const char *group_prefix, + const char *one_field) +{ + GHashTable *table; + NmcOutputField *tmpl, *arr; + size_t tmpl_len; + + if (dhcp6 == NULL) + return FALSE; + + table = nm_dhcp_config_get_options (dhcp6); + if (table) { + GHashTableIter table_iter; + gpointer key, value; + char **options_arr = NULL; + int i = 0; + + tmpl = nmc_fields_dhcp6_config; + tmpl_len = sizeof (nmc_fields_dhcp6_config); + nmc->print_fields.indices = parse_output_fields (one_field ? one_field : NMC_FIELDS_DHCP6_CONFIG_ALL, + tmpl, FALSE, NULL, NULL); + arr = nmc_dup_fields_array (tmpl, tmpl_len, NMC_OF_FLAG_FIELD_NAMES); + g_ptr_array_add (nmc->output_data, arr); + + options_arr = g_new (char *, g_hash_table_size (table) + 1); + g_hash_table_iter_init (&table_iter, table); + while (g_hash_table_iter_next (&table_iter, &key, &value)) + options_arr[i++] = g_strdup_printf ("%s = %s", (char *) key, (char *) value); + options_arr[i] = NULL; + + arr = nmc_dup_fields_array (tmpl, tmpl_len, NMC_OF_FLAG_SECTION_PREFIX); + set_val_strc (arr, 0, group_prefix); + set_val_arr (arr, 1, options_arr); + g_ptr_array_add (nmc->output_data, arr); + + print_data (nmc); /* Print all data */ + + /* Remove any previous data */ + nmc_empty_output_fields (nmc); + + return TRUE; + } + return FALSE; +} + +/* + * Parse IP address from string to NMIPAddress stucture. + * ip_str is the IP address in the form address/prefix + */ +NMIPAddress * +nmc_parse_and_build_address (int family, const char *ip_str, GError **error) +{ + int max_prefix = (family == AF_INET) ? 32 : 128; + NMIPAddress *addr = NULL; + const char *ip; + char *tmp; + char *plen; + long int prefix; + GError *local = NULL; + + g_return_val_if_fail (ip_str != NULL, NULL); + g_return_val_if_fail (error == NULL || *error == NULL, NULL); + + tmp = g_strdup (ip_str); + plen = strchr (tmp, '/'); /* prefix delimiter */ + if (plen) + *plen++ = '\0'; + + ip = tmp; + + prefix = max_prefix; + if (plen) { + if (!nmc_string_to_int (plen, TRUE, 1, max_prefix, &prefix)) { + g_set_error (error, NMCLI_ERROR, NMC_RESULT_ERROR_USER_INPUT, + _("invalid prefix '%s'; <1-%d> allowed"), plen, max_prefix); + goto finish; + } + } + + addr = nm_ip_address_new (family, ip, (guint32) prefix, &local); + if (!addr) { + g_set_error (error, NMCLI_ERROR, NMC_RESULT_ERROR_USER_INPUT, + _("invalid IP address: %s"), local->message); + g_clear_error (&local); + } + +finish: + g_free (tmp); + return addr; +} + +/* + * nmc_parse_and_build_route: + * @family: AF_INET or AF_INET6 + * @first: the route destination in the form of "address/prefix" + (/prefix is optional) + * @second: (allow-none): next hop address, if third is not NULL. Otherwise it could be + either next hop address or metric. (It can be NULL when @third is NULL). + * @third: (allow-none): route metric + * @error: location to store GError + * + * Parse route from strings and return an #NMIPRoute + * + * Returns: %TRUE on success, %FALSE on failure + */ +NMIPRoute * +nmc_parse_and_build_route (int family, + const char *first, + const char *second, + const char *third, + GError **error) +{ + int max_prefix = (family == AF_INET) ? 32 : 128; + char *dest = NULL, *plen = NULL; + const char *next_hop = NULL; + const char *canon_dest; + long int prefix = max_prefix, metric = -1; + NMIPRoute *route = NULL; + gboolean success = FALSE; + GError *local = NULL; + + g_return_val_if_fail (family == AF_INET || family == AF_INET6, FALSE); + g_return_val_if_fail (first != NULL, FALSE); + g_return_val_if_fail (second || !third, FALSE); + g_return_val_if_fail (error == NULL || *error == NULL, FALSE); + + dest = g_strdup (first); + plen = strchr (dest, '/'); /* prefix delimiter */ + if (plen) + *plen++ = '\0'; + + if (plen) { + if (!nmc_string_to_int (plen, TRUE, 1, max_prefix, &prefix)) { + g_set_error (error, NMCLI_ERROR, NMC_RESULT_ERROR_USER_INPUT, + _("invalid prefix '%s'; <1-%d> allowed"), + plen, max_prefix); + goto finish; + } + } + + if (second) { + if (third || nm_utils_ipaddr_valid (family, second)) + next_hop = second; + else { + /* 'second' can be a metric */ + if (!nmc_string_to_int (second, TRUE, 0, G_MAXUINT32, &metric)) { + g_set_error (error, 1, 0, _("the second component of route ('%s') is neither " + "a next hop address nor a metric"), second); + goto finish; + } + } + } + + if (third) { + if (!nmc_string_to_int (third, TRUE, 0, G_MAXUINT32, &metric)) { + g_set_error (error, 1, 0, _("invalid metric '%s'"), third); + goto finish; + } + } + + route = nm_ip_route_new (family, dest, prefix, next_hop, metric, &local); + if (!route) { + g_set_error (error, NMCLI_ERROR, NMC_RESULT_ERROR_USER_INPUT, + _("invalid route: %s"), local->message); + g_clear_error (&local); + goto finish; + } + + /* We don't accept default routes as NetworkManager handles it + * itself. But we have to check this after @route has normalized the + * dest string. + */ + canon_dest = nm_ip_route_get_dest (route); + if (!strcmp (canon_dest, "0.0.0.0") || !strcmp (canon_dest, "::")) { + g_set_error_literal (error, NMCLI_ERROR, NMC_RESULT_ERROR_USER_INPUT, + _("default route cannot be added (NetworkManager handles it by itself)")); + g_clear_pointer (&route, nm_ip_route_unref); + goto finish; + } + + success = TRUE; + +finish: + g_free (dest); + return route; +} + +const char * +nmc_device_state_to_string (NMDeviceState state) +{ + switch (state) { + case NM_DEVICE_STATE_UNMANAGED: + return _("unmanaged"); + case NM_DEVICE_STATE_UNAVAILABLE: + return _("unavailable"); + case NM_DEVICE_STATE_DISCONNECTED: + return _("disconnected"); + case NM_DEVICE_STATE_PREPARE: + return _("connecting (prepare)"); + case NM_DEVICE_STATE_CONFIG: + return _("connecting (configuring)"); + case NM_DEVICE_STATE_NEED_AUTH: + return _("connecting (need authentication)"); + case NM_DEVICE_STATE_IP_CONFIG: + return _("connecting (getting IP configuration)"); + case NM_DEVICE_STATE_IP_CHECK: + return _("connecting (checking IP connectivity)"); + case NM_DEVICE_STATE_SECONDARIES: + return _("connecting (starting secondary connections)"); + case NM_DEVICE_STATE_ACTIVATED: + return _("connected"); + case NM_DEVICE_STATE_DEACTIVATING: + return _("deactivating"); + case NM_DEVICE_STATE_FAILED: + return _("connection failed"); + default: + return _("unknown"); + } +} + +const char * +nmc_device_reason_to_string (NMDeviceStateReason reason) +{ + switch (reason) { + case NM_DEVICE_STATE_REASON_NONE: + return _("No reason given"); + + case NM_DEVICE_STATE_REASON_UNKNOWN: + return _("Unknown error"); + + case NM_DEVICE_STATE_REASON_NOW_MANAGED: + return _("Device is now managed"); + + case NM_DEVICE_STATE_REASON_NOW_UNMANAGED: + return _("Device is now unmanaged"); + + case NM_DEVICE_STATE_REASON_CONFIG_FAILED: + return _("The device could not be readied for configuration"); + + case NM_DEVICE_STATE_REASON_IP_CONFIG_UNAVAILABLE: + return _("IP configuration could not be reserved (no available address, timeout, etc.)"); + + case NM_DEVICE_STATE_REASON_IP_CONFIG_EXPIRED: + return _("The IP configuration is no longer valid"); + + case NM_DEVICE_STATE_REASON_NO_SECRETS: + return _("Secrets were required, but not provided"); + + case NM_DEVICE_STATE_REASON_SUPPLICANT_DISCONNECT: + return _("802.1X supplicant disconnected"); + + case NM_DEVICE_STATE_REASON_SUPPLICANT_CONFIG_FAILED: + return _("802.1X supplicant configuration failed"); + + case NM_DEVICE_STATE_REASON_SUPPLICANT_FAILED: + return _("802.1X supplicant failed"); + + case NM_DEVICE_STATE_REASON_SUPPLICANT_TIMEOUT: + return _("802.1X supplicant took too long to authenticate"); + + case NM_DEVICE_STATE_REASON_PPP_START_FAILED: + return _("PPP service failed to start"); + + case NM_DEVICE_STATE_REASON_PPP_DISCONNECT: + return _("PPP service disconnected"); + + case NM_DEVICE_STATE_REASON_PPP_FAILED: + return _("PPP failed"); + + case NM_DEVICE_STATE_REASON_DHCP_START_FAILED: + return _("DHCP client failed to start"); + + case NM_DEVICE_STATE_REASON_DHCP_ERROR: + return _("DHCP client error"); + + case NM_DEVICE_STATE_REASON_DHCP_FAILED: + return _("DHCP client failed"); + + case NM_DEVICE_STATE_REASON_SHARED_START_FAILED: + return _("Shared connection service failed to start"); + + case NM_DEVICE_STATE_REASON_SHARED_FAILED: + return _("Shared connection service failed"); + + case NM_DEVICE_STATE_REASON_AUTOIP_START_FAILED: + return _("AutoIP service failed to start"); + + case NM_DEVICE_STATE_REASON_AUTOIP_ERROR: + return _("AutoIP service error"); + + case NM_DEVICE_STATE_REASON_AUTOIP_FAILED: + return _("AutoIP service failed"); + + case NM_DEVICE_STATE_REASON_MODEM_BUSY: + return _("The line is busy"); + + case NM_DEVICE_STATE_REASON_MODEM_NO_DIAL_TONE: + return _("No dial tone"); + + case NM_DEVICE_STATE_REASON_MODEM_NO_CARRIER: + return _("No carrier could be established"); + + case NM_DEVICE_STATE_REASON_MODEM_DIAL_TIMEOUT: + return _("The dialing request timed out"); + + case NM_DEVICE_STATE_REASON_MODEM_DIAL_FAILED: + return _("The dialing attempt failed"); + + case NM_DEVICE_STATE_REASON_MODEM_INIT_FAILED: + return _("Modem initialization failed"); + + case NM_DEVICE_STATE_REASON_GSM_APN_FAILED: + return _("Failed to select the specified APN"); + + case NM_DEVICE_STATE_REASON_GSM_REGISTRATION_NOT_SEARCHING: + return _("Not searching for networks"); + + case NM_DEVICE_STATE_REASON_GSM_REGISTRATION_DENIED: + return _("Network registration denied"); + + case NM_DEVICE_STATE_REASON_GSM_REGISTRATION_TIMEOUT: + return _("Network registration timed out"); + + case NM_DEVICE_STATE_REASON_GSM_REGISTRATION_FAILED: + return _("Failed to register with the requested network"); + + case NM_DEVICE_STATE_REASON_GSM_PIN_CHECK_FAILED: + return _("PIN check failed"); + + case NM_DEVICE_STATE_REASON_FIRMWARE_MISSING: + return _("Necessary firmware for the device may be missing"); + + case NM_DEVICE_STATE_REASON_REMOVED: + return _("The device was removed"); + + case NM_DEVICE_STATE_REASON_SLEEPING: + return _("NetworkManager went to sleep"); + + case NM_DEVICE_STATE_REASON_CONNECTION_REMOVED: + return _("The device's active connection disappeared"); + + case NM_DEVICE_STATE_REASON_USER_REQUESTED: + return _("Device disconnected by user or client"); + + case NM_DEVICE_STATE_REASON_CARRIER: + return _("Carrier/link changed"); + + case NM_DEVICE_STATE_REASON_CONNECTION_ASSUMED: + return _("The device's existing connection was assumed"); + + case NM_DEVICE_STATE_REASON_SUPPLICANT_AVAILABLE: + return _("The supplicant is now available"); + + case NM_DEVICE_STATE_REASON_MODEM_NOT_FOUND: + return _("The modem could not be found"); + + case NM_DEVICE_STATE_REASON_BT_FAILED: + return _("The Bluetooth connection failed or timed out"); + + case NM_DEVICE_STATE_REASON_GSM_SIM_NOT_INSERTED: + return _("GSM Modem's SIM card not inserted"); + + case NM_DEVICE_STATE_REASON_GSM_SIM_PIN_REQUIRED: + return _("GSM Modem's SIM PIN required"); + + case NM_DEVICE_STATE_REASON_GSM_SIM_PUK_REQUIRED: + return _("GSM Modem's SIM PUK required"); + + case NM_DEVICE_STATE_REASON_GSM_SIM_WRONG: + return _("GSM Modem's SIM wrong"); + + case NM_DEVICE_STATE_REASON_INFINIBAND_MODE: + return _("InfiniBand device does not support connected mode"); + + case NM_DEVICE_STATE_REASON_DEPENDENCY_FAILED: + return _("A dependency of the connection failed"); + + case NM_DEVICE_STATE_REASON_BR2684_FAILED: + return _("A problem with the RFC 2684 Ethernet over ADSL bridge"); + + case NM_DEVICE_STATE_REASON_MODEM_MANAGER_UNAVAILABLE: + return _("ModemManager is unavailable"); + + case NM_DEVICE_STATE_REASON_SSID_NOT_FOUND: + return _("The Wi-Fi network could not be found"); + + case NM_DEVICE_STATE_REASON_SECONDARY_CONNECTION_FAILED: + return _("A secondary connection of the base connection failed"); + + case NM_DEVICE_STATE_REASON_DCB_FCOE_FAILED: + return _("DCB or FCoE setup failed"); + + case NM_DEVICE_STATE_REASON_TEAMD_CONTROL_FAILED: + return _("teamd control failed"); + + case NM_DEVICE_STATE_REASON_MODEM_FAILED: + return _("Modem failed or no longer available"); + + case NM_DEVICE_STATE_REASON_MODEM_AVAILABLE: + return _("Modem now ready and available"); + + case NM_DEVICE_STATE_REASON_SIM_PIN_INCORRECT: + return _("SIM PIN was incorrect"); + + default: + /* TRANSLATORS: Unknown reason for a device state change (NMDeviceStateReason) */ + return _("Unknown"); + } +} + + +/* Max priority values from libnm-core/nm-setting-vlan.c */ +#define MAX_SKB_PRIO G_MAXUINT32 +#define MAX_8021P_PRIO 7 /* Max 802.1p priority */ + +/* + * Parse VLAN priority mappings from the following format: 2:1,3:4,7:3 + * and verify if the priority numbers are valid + * + * Return: string array with split maps, or NULL on error + * Caller is responsible for freeing the array. + */ +char ** +nmc_vlan_parse_priority_maps (const char *priority_map, + NMVlanPriorityMap map_type, + GError **error) +{ + char **mapping = NULL, **iter; + unsigned long from, to, from_max, to_max; + + g_return_val_if_fail (priority_map != NULL, NULL); + g_return_val_if_fail (error == NULL || *error == NULL, NULL); + + if (map_type == NM_VLAN_INGRESS_MAP) { + from_max = MAX_8021P_PRIO; + to_max = MAX_SKB_PRIO; + } else { + from_max = MAX_SKB_PRIO; + to_max = MAX_8021P_PRIO; + } + + mapping = g_strsplit (priority_map, ",", 0); + for (iter = mapping; iter && *iter; iter++) { + char *left, *right; + + left = g_strstrip (*iter); + right = strchr (left, ':'); + if (!right) { + g_set_error (error, 1, 0, _("invalid priority map '%s'"), *iter); + g_strfreev (mapping); + return NULL; + } + *right++ = '\0'; + + if (!nmc_string_to_uint (left, TRUE, 0, from_max, &from)) { + g_set_error (error, 1, 0, _("priority '%s' is not valid (<0-%ld>)"), + left, from_max); + g_strfreev (mapping); + return NULL; + } + if (!nmc_string_to_uint (right, TRUE, 0, to_max, &to)) { + g_set_error (error, 1, 0, _("priority '%s' is not valid (<0-%ld>)"), + right, to_max); + g_strfreev (mapping); + return NULL; + } + *(right-1) = ':'; /* Put back ':' */ + } + return mapping; +} + +const char * +nmc_bond_validate_mode (const char *mode, GError **error) +{ + unsigned long mode_int; + static const char *valid_modes[] = { "balance-rr", + "active-backup", + "balance-xor", + "broadcast", + "802.3ad", + "balance-tlb", + "balance-alb", + NULL }; + if (nmc_string_to_uint (mode, TRUE, 0, 6, &mode_int)) { + /* Translate bonding mode numbers to mode names: + * https://www.kernel.org/doc/Documentation/networking/bonding.txt + */ + return valid_modes[mode_int]; + } else + return nmc_string_is_valid (mode, valid_modes, error); +} + +/* + * nmc_team_check_config: + * @config: file name with team config, or raw team JSON config data + * @out_config: raw team JSON config data (with removed new-line characters) + * @error: location to store error, or %NUL + * + * Check team config from @config parameter and return the checked/sanitized + * config in @out_config. + * + * Returns: %TRUE if the config is valid, %FALSE if it is invalid + */ +gboolean +nmc_team_check_config (const char *config, char **out_config, GError **error) +{ + char *contents = NULL; + size_t c_len = 0; + + *out_config = NULL; + + if (!config || strlen (config) == strspn (config, " \t")) + return TRUE; + + /* 'config' can be either a file name or raw JSON config data */ + if (g_file_test (config, G_FILE_TEST_EXISTS)) + (void) g_file_get_contents (config, &contents, NULL, NULL); + else + contents = g_strdup (config); + + if (contents) { + g_strstrip (contents); + c_len = strlen (contents); + } + + /* Do a simple validity check */ + if (!contents || !contents[0] || c_len > 100000 || contents[0] != '{' || contents[c_len-1] != '}') { + g_set_error (error, NMCLI_ERROR, NMC_RESULT_ERROR_USER_INPUT, + _("'%s' is not a valid team configuration or file name."), config); + g_free (contents); + return FALSE; + } + *out_config = g_strdelimit (contents, "\r\n", ' '); + return TRUE; +} + +/* + * nmc_find_connection: + * @connections: array of NMConnections to search in + * @filter_type: "id", "uuid", "path" or %NULL + * @filter_val: connection to find (connection name, UUID or path) + * @start: where to start in @list. The location is updated so that the function + * can be called multiple times (for connections with the same name). + * + * Find a connection in @list according to @filter_val. @filter_type determines + * what property is used for comparison. When @filter_type is NULL, compare + * @filter_val against all types. Otherwise, only compare against the specified + * type. If 'path' filter type is specified, comparison against numeric index + * (in addition to the whole path) is allowed. + * + * Returns: found connection, or %NULL + */ +NMConnection * +nmc_find_connection (const GPtrArray *connections, + const char *filter_type, + const char *filter_val, + int *start) +{ + NMConnection *connection; + NMConnection *found = NULL; + int i; + const char *id; + const char *uuid; + const char *path, *path_num; + + for (i = start ? *start : 0; i < connections->len; i++) { + connection = NM_CONNECTION (connections->pdata[i]); + + id = nm_connection_get_id (connection); + uuid = nm_connection_get_uuid (connection); + path = nm_connection_get_path (connection); + path_num = path ? strrchr (path, '/') + 1 : NULL; + + /* When filter_type is NULL, compare connection ID (filter_val) + * against all types. Otherwise, only compare against the specific + * type. If 'path' filter type is specified, comparison against + * numeric index (in addition to the whole path) is allowed. + */ + if ( ( (!filter_type || strcmp (filter_type, "id") == 0) + && strcmp (filter_val, id) == 0) + || ( (!filter_type || strcmp (filter_type, "uuid") == 0) + && strcmp (filter_val, uuid) == 0) + || ( (!filter_type || strcmp (filter_type, "path") == 0) + && (g_strcmp0 (filter_val, path) == 0 || (filter_type && g_strcmp0 (filter_val, path_num) == 0)))) { + if (!start) + return connection; + if (found) { + *start = i; + return found; + } + found = connection; + } + } + + if (start) + *start = 0; + return found; +} + +static gboolean +get_secrets_from_user (const char *request_id, + const char *title, + const char *msg, + gboolean ask, + GHashTable *pwds_hash, + GPtrArray *secrets) +{ + int i; + + for (i = 0; i < secrets->len; i++) { + NMSecretAgentSimpleSecret *secret = secrets->pdata[i]; + char *pwd = NULL; + + /* First try to find the password in provided passwords file, + * then ask user. */ + if (pwds_hash && (pwd = g_hash_table_lookup (pwds_hash, secret->prop_name))) { + pwd = g_strdup (pwd); + } else { + g_print ("%s\n", msg); + if (ask) { + if (secret->value) { + /* Prefill the password if we have it. */ + rl_startup_hook = nmc_rl_set_deftext; + nmc_rl_pre_input_deftext = g_strdup (secret->value); + } + pwd = nmc_readline ("%s (%s): ", secret->name, secret->prop_name); + if (!pwd) + pwd = g_strdup (""); + } else { + g_printerr (_("Warning: password for '%s' not given in 'passwd-file' " + "and nmcli cannot ask without '--ask' option.\n"), + secret->prop_name); + } + } + /* No password provided, cancel the secrets. */ + if (!pwd) + return FALSE; + g_free (secret->value); + secret->value = pwd; + } + return TRUE; +} + +/** + * nmc_secrets_requested: + * @agent: the #NMSecretAgentSimple + * @request_id: request ID, to eventually pass to + * nm_secret_agent_simple_response() + * @title: a title for the password request + * @msg: a prompt message for the password request + * @secrets: (element-type #NMSecretAgentSimpleSecret): array of secrets + * being requested. + * @user_data: user data passed to the function + * + * This function is used as a callback for "request-secrets" signal of + * NMSecretAgentSimpleSecret. +*/ +void +nmc_secrets_requested (NMSecretAgentSimple *agent, + const char *request_id, + const char *title, + const char *msg, + GPtrArray *secrets, + gpointer user_data) +{ + NmCli *nmc = (NmCli *) user_data; + gboolean success = FALSE; + + if (nmc->print_output == NMC_PRINT_PRETTY) + nmc_terminal_erase_line (); + + success = get_secrets_from_user (request_id, title, msg, nmc->in_editor || nmc->ask, + nmc->pwds_hash, secrets); + if (success) + nm_secret_agent_simple_response (agent, request_id, secrets); + else { + /* Unregister our secret agent on failure, so that another agent + * may be tried */ + if (nmc->secret_agent) { + nm_secret_agent_old_unregister (nmc->secret_agent, NULL, NULL); + g_clear_object (&nmc->secret_agent); + } + } +} + + +/** + * nmc_cleanup_readline: + * + * Cleanup readline when nmcli is terminated with a signal. + * It makes sure the terminal is not garbled. + */ +void +nmc_cleanup_readline (void) +{ + rl_free_line_state (); + rl_cleanup_after_signal (); +} + + +static gboolean nmcli_in_readline = FALSE; +static pthread_mutex_t readline_mutex = PTHREAD_MUTEX_INITIALIZER; + +gboolean +nmc_get_in_readline (void) +{ + gboolean in_readline; + + pthread_mutex_lock (&readline_mutex); + in_readline = nmcli_in_readline; + pthread_mutex_unlock (&readline_mutex); + return in_readline; +} + +void +nmc_set_in_readline (gboolean in_readline) +{ + pthread_mutex_lock (&readline_mutex); + nmcli_in_readline = in_readline; + pthread_mutex_unlock (&readline_mutex); +} + +/* Global variable defined in nmcli.c */ +extern NmCli nm_cli; + +/** + * nmc_readline: + * @prompt_fmt: prompt to print (telling user what to enter). It is standard + * printf() format string + * @...: a list of arguments according to the @prompt_fmt format string + * + * Wrapper around libreadline's readline() function. + * If user pressed Ctrl-C, readline() is called again (if not in editor and + * line is empty, nmcli will quit). + * If user pressed Ctrl-D on empty line, nmcli will quit. + * + * Returns: the user provided string. In case the user entered empty string, + * this function returns NULL. + */ +char * +nmc_readline (const char *prompt_fmt, ...) +{ + va_list args; + char *prompt, *str; + + va_start (args, prompt_fmt); + prompt = g_strdup_vprintf (prompt_fmt, args); + va_end (args); + +readline_mark: + /* We are in readline -> Ctrl-C should not quit nmcli */ + nmc_set_in_readline (TRUE); + str = readline (prompt); + /* We are outside readline -> Ctrl-C should quit nmcli */ + nmc_set_in_readline (FALSE); + + /* Add string to the history */ + if (str && *str) + add_history (str); + + /*-- React on Ctrl-C and Ctrl-D --*/ + /* We quit on Ctrl-D when line is empty */ + if (str == NULL) { + /* Send SIGQUIT to itself */ + nmc_set_sigquit_internal (); + kill (getpid (), SIGQUIT); + /* Sleep in this thread so that we don't do anything else until exit */ + for (;;) + sleep (3); + } + /* Ctrl-C */ + if (nmc_seen_sigint ()) { + nmc_clear_sigint (); + if (nm_cli.in_editor || *str) { + /* In editor, or the line is not empty */ + /* Call readline again to get new prompt (repeat) */ + g_free (str); + goto readline_mark; + } else { + /* Not in editor and line is empty */ + /* Send SIGQUIT to itself */ + nmc_set_sigquit_internal (); + kill (getpid (), SIGQUIT); + /* Sleep in this thread so that we don't do anything else until exit */ + for (;;) + sleep (3); + } + } + g_free (prompt); + + /* Return NULL, not empty string */ + if (str && *str == '\0') { + g_free (str); + str = NULL; + } + return str; +} + +/** + * nmc_rl_gen_func_basic: + * @text: text to complete + * @state: readline state; says whether start from scratch (state == 0) + * @words: strings for completion + * + * Basic function generating list of completion strings for readline. + * See e.g. http://cnswww.cns.cwru.edu/php/chet/readline/readline.html#SEC49 + */ +char * +nmc_rl_gen_func_basic (const char *text, int state, const char **words) +{ + static int list_idx, len; + const char *name; + + if (!state) { + list_idx = 0; + len = strlen (text); + } + + /* Return the next name which partially matches one from the 'words' list. */ + while ((name = words[list_idx])) { + list_idx++; + + if (strncmp (name, text, len) == 0) + return g_strdup (name); + } + return NULL; +} + +/* for pre-filling a string to readline prompt */ +char *nmc_rl_pre_input_deftext; + +int +nmc_rl_set_deftext (void) +{ + if (nmc_rl_pre_input_deftext && rl_startup_hook) { + rl_insert_text (nmc_rl_pre_input_deftext); + g_free (nmc_rl_pre_input_deftext); + nmc_rl_pre_input_deftext = NULL; + rl_startup_hook = NULL; + } + return 0; +} + diff --git a/clients/cli/common.h b/clients/cli/common.h new file mode 100644 index 00000000..8bb043f4 --- /dev/null +++ b/clients/cli/common.h @@ -0,0 +1,69 @@ +/* + * nmcli - command-line tool for controlling NetworkManager + * Common functions and data shared between files. + * + * 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., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Copyright 2012 - 2014 Red Hat, Inc. + */ + +#ifndef NMC_COMMON_H +#define NMC_COMMON_H + +#include "nmcli.h" +#include "nm-secret-agent-simple.h" + +gboolean print_ip4_config (NMIPConfig *cfg4, NmCli *nmc, const char *group_prefix, const char *one_field); +gboolean print_ip6_config (NMIPConfig *cfg6, NmCli *nmc, const char *group_prefix, const char *one_field); +gboolean print_dhcp4_config (NMDhcpConfig *dhcp4, NmCli *nmc, const char *group_prefix, const char *one_field); +gboolean print_dhcp6_config (NMDhcpConfig *dhcp6, NmCli *nmc, const char *group_prefix, const char *one_field); + +NMIPAddress *nmc_parse_and_build_address (int family, const char *ip_str, GError **error); +NMIPRoute *nmc_parse_and_build_route (int family, const char *first, const char *second, const char *third, GError **error); + +const char * nmc_device_state_to_string (NMDeviceState state); +const char * nmc_device_reason_to_string (NMDeviceStateReason reason); + +char ** +nmc_vlan_parse_priority_maps (const char *priority_map, + NMVlanPriorityMap map_type, + GError **error); + +const char *nmc_bond_validate_mode (const char *mode, GError **error); +gboolean nmc_team_check_config (const char *config, char **out_config, GError **error); + +NMConnection *nmc_find_connection (const GPtrArray *connections, + const char *filter_type, + const char *filter_val, + int *start); + +void nmc_secrets_requested (NMSecretAgentSimple *agent, + const char *request_id, + const char *title, + const char *msg, + GPtrArray *secrets, + gpointer user_data); + +void nmc_cleanup_readline (void); +char *nmc_readline (const char *prompt_fmt, ...) G_GNUC_PRINTF (1, 2); +char *nmc_rl_gen_func_basic (const char *text, int state, const char **words); +gboolean nmc_get_in_readline (void); +void nmc_set_in_readline (gboolean in_readline); + +/* for pre-filling a string to readline prompt */ +extern char *nmc_rl_pre_input_deftext; +int nmc_rl_set_deftext (void); + +#endif /* NMC_COMMON_H */ diff --git a/clients/cli/connections.c b/clients/cli/connections.c new file mode 100644 index 00000000..5de9cc9d --- /dev/null +++ b/clients/cli/connections.c @@ -0,0 +1,9020 @@ +/* nmcli - command-line tool to control NetworkManager + * + * 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., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Copyright 2010 - 2014 Red Hat, Inc. + */ + +#include "config.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "utils.h" +#include "common.h" +#include "settings.h" +#include "connections.h" +#include "nm-secret-agent-simple.h" +#include "polkit-agent.h" + +/* define some prompts for connection editor */ +#define EDITOR_PROMPT_SETTING _("Setting name? ") +#define EDITOR_PROMPT_PROPERTY _("Property name? ") +#define EDITOR_PROMPT_CON_TYPE _("Enter connection type: ") + +/* define some other prompts */ +#define PROMPT_CON_TYPE _("Connection type: ") +#define PROMPT_VPN_TYPE _("VPN type: ") +#define PROMPT_BOND_MASTER _("Bond master: ") +#define PROMPT_TEAM_MASTER _("Team master: ") +#define PROMPT_BRIDGE_MASTER _("Bridge master: ") +#define PROMPT_CONNECTION _("Connection (name, UUID, or path): ") + +static const char *nmc_known_vpns[] = + { "openvpn", "vpnc", "pptp", "openconnect", "openswan", "libreswan", + "ssh", "l2tp", "iodine", NULL }; + +/* Available fields for 'connection show' */ +static NmcOutputField nmc_fields_con_show[] = { + {"NAME", N_("NAME"), 25}, /* 0 */ + {"UUID", N_("UUID"), 38}, /* 1 */ + {"TYPE", N_("TYPE"), 17}, /* 2 */ + {"TIMESTAMP", N_("TIMESTAMP"), 12}, /* 3 */ + {"TIMESTAMP-REAL", N_("TIMESTAMP-REAL"), 34}, /* 4 */ + {"AUTOCONNECT", N_("AUTOCONNECT"), 13}, /* 5 */ + {"AUTOCONNECT-PRIORITY", N_("AUTOCONNECT-PRIORITY"), 10}, /* 6 */ + {"READONLY", N_("READONLY"), 10}, /* 7 */ + {"DBUS-PATH", N_("DBUS-PATH"), 42}, /* 8 */ + {"ACTIVE", N_("ACTIVE"), 10}, /* 9 */ + {"DEVICE", N_("DEVICE"), 10}, /* 10 */ + {"STATE", N_("STATE"), 12}, /* 11 */ + {"ACTIVE-PATH", N_("ACTIVE-PATH"), 51}, /* 12 */ + {NULL, NULL, 0} +}; +#define NMC_FIELDS_CON_SHOW_ALL "NAME,UUID,TYPE,TIMESTAMP,TIMESTAMP-REAL,AUTOCONNECT,AUTOCONNECT-PRIORITY,READONLY,DBUS-PATH,"\ + "ACTIVE,DEVICE,STATE,ACTIVE-PATH" +#define NMC_FIELDS_CON_SHOW_COMMON "NAME,UUID,TYPE,DEVICE" + +/* Helper macro to define fields */ +#define SETTING_FIELD(setting, props) { setting, N_(setting), 0, props, NULL, FALSE, FALSE, 0 } + +/* defined in settings.c */ +extern NmcOutputField nmc_fields_setting_connection[]; +extern NmcOutputField nmc_fields_setting_wired[]; +extern NmcOutputField nmc_fields_setting_8021X[]; +extern NmcOutputField nmc_fields_setting_wireless[]; +extern NmcOutputField nmc_fields_setting_wireless_security[]; +extern NmcOutputField nmc_fields_setting_ip4_config[]; +extern NmcOutputField nmc_fields_setting_ip6_config[]; +extern NmcOutputField nmc_fields_setting_serial[]; +extern NmcOutputField nmc_fields_setting_ppp[]; +extern NmcOutputField nmc_fields_setting_pppoe[]; +extern NmcOutputField nmc_fields_setting_adsl[]; +extern NmcOutputField nmc_fields_setting_gsm[]; +extern NmcOutputField nmc_fields_setting_cdma[]; +extern NmcOutputField nmc_fields_setting_bluetooth[]; +extern NmcOutputField nmc_fields_setting_olpc_mesh[]; +extern NmcOutputField nmc_fields_setting_vpn[]; +extern NmcOutputField nmc_fields_setting_wimax[]; +extern NmcOutputField nmc_fields_setting_infiniband[]; +extern NmcOutputField nmc_fields_setting_bond[]; +extern NmcOutputField nmc_fields_setting_vlan[]; +extern NmcOutputField nmc_fields_setting_bridge[]; +extern NmcOutputField nmc_fields_setting_bridge_port[]; +extern NmcOutputField nmc_fields_setting_team[]; +extern NmcOutputField nmc_fields_setting_team_port[]; +extern NmcOutputField nmc_fields_setting_dcb[]; + +/* Available settings for 'connection show ' - profile part */ +static NmcOutputField nmc_fields_settings_names[] = { + SETTING_FIELD (NM_SETTING_CONNECTION_SETTING_NAME, nmc_fields_setting_connection + 1), /* 0 */ + SETTING_FIELD (NM_SETTING_WIRED_SETTING_NAME, nmc_fields_setting_wired + 1), /* 1 */ + SETTING_FIELD (NM_SETTING_802_1X_SETTING_NAME, nmc_fields_setting_8021X + 1), /* 2 */ + SETTING_FIELD (NM_SETTING_WIRELESS_SETTING_NAME, nmc_fields_setting_wireless + 1), /* 3 */ + SETTING_FIELD (NM_SETTING_WIRELESS_SECURITY_SETTING_NAME, nmc_fields_setting_wireless_security + 1), /* 4 */ + SETTING_FIELD (NM_SETTING_IP4_CONFIG_SETTING_NAME, nmc_fields_setting_ip4_config + 1), /* 5 */ + SETTING_FIELD (NM_SETTING_IP6_CONFIG_SETTING_NAME, nmc_fields_setting_ip6_config + 1), /* 6 */ + SETTING_FIELD (NM_SETTING_SERIAL_SETTING_NAME, nmc_fields_setting_serial + 1), /* 7 */ + SETTING_FIELD (NM_SETTING_PPP_SETTING_NAME, nmc_fields_setting_ppp + 1), /* 8 */ + SETTING_FIELD (NM_SETTING_PPPOE_SETTING_NAME, nmc_fields_setting_pppoe + 1), /* 9 */ + SETTING_FIELD (NM_SETTING_GSM_SETTING_NAME, nmc_fields_setting_gsm + 1), /* 10 */ + SETTING_FIELD (NM_SETTING_CDMA_SETTING_NAME, nmc_fields_setting_cdma + 1), /* 11 */ + SETTING_FIELD (NM_SETTING_BLUETOOTH_SETTING_NAME, nmc_fields_setting_bluetooth + 1), /* 12 */ + SETTING_FIELD (NM_SETTING_OLPC_MESH_SETTING_NAME, nmc_fields_setting_olpc_mesh + 1), /* 13 */ + SETTING_FIELD (NM_SETTING_VPN_SETTING_NAME, nmc_fields_setting_vpn + 1), /* 14 */ + SETTING_FIELD (NM_SETTING_WIMAX_SETTING_NAME, nmc_fields_setting_wimax + 1), /* 15 */ + SETTING_FIELD (NM_SETTING_INFINIBAND_SETTING_NAME, nmc_fields_setting_infiniband + 1), /* 16 */ + SETTING_FIELD (NM_SETTING_BOND_SETTING_NAME, nmc_fields_setting_bond + 1), /* 17 */ + SETTING_FIELD (NM_SETTING_VLAN_SETTING_NAME, nmc_fields_setting_vlan + 1), /* 18 */ + SETTING_FIELD (NM_SETTING_ADSL_SETTING_NAME, nmc_fields_setting_adsl + 1), /* 19 */ + SETTING_FIELD (NM_SETTING_BRIDGE_SETTING_NAME, nmc_fields_setting_bridge + 1), /* 20 */ + SETTING_FIELD (NM_SETTING_BRIDGE_PORT_SETTING_NAME, nmc_fields_setting_bridge_port + 1), /* 21 */ + SETTING_FIELD (NM_SETTING_TEAM_SETTING_NAME, nmc_fields_setting_team + 1), /* 22 */ + SETTING_FIELD (NM_SETTING_TEAM_PORT_SETTING_NAME, nmc_fields_setting_team_port + 1), /* 23 */ + SETTING_FIELD (NM_SETTING_DCB_SETTING_NAME, nmc_fields_setting_dcb + 1), /* 24 */ + {NULL, NULL, 0, NULL, NULL, FALSE, FALSE, 0} +}; +#define NMC_FIELDS_SETTINGS_NAMES_ALL_X NM_SETTING_CONNECTION_SETTING_NAME","\ + NM_SETTING_WIRED_SETTING_NAME","\ + NM_SETTING_802_1X_SETTING_NAME","\ + NM_SETTING_WIRELESS_SETTING_NAME","\ + NM_SETTING_WIRELESS_SECURITY_SETTING_NAME","\ + NM_SETTING_IP4_CONFIG_SETTING_NAME","\ + NM_SETTING_IP6_CONFIG_SETTING_NAME","\ + NM_SETTING_SERIAL_SETTING_NAME","\ + NM_SETTING_PPP_SETTING_NAME","\ + NM_SETTING_PPPOE_SETTING_NAME","\ + NM_SETTING_ADSL_SETTING_NAME","\ + NM_SETTING_GSM_SETTING_NAME","\ + NM_SETTING_CDMA_SETTING_NAME","\ + NM_SETTING_BLUETOOTH_SETTING_NAME","\ + NM_SETTING_OLPC_MESH_SETTING_NAME","\ + NM_SETTING_VPN_SETTING_NAME","\ + NM_SETTING_INFINIBAND_SETTING_NAME","\ + NM_SETTING_BOND_SETTING_NAME","\ + NM_SETTING_VLAN_SETTING_NAME","\ + NM_SETTING_BRIDGE_SETTING_NAME","\ + NM_SETTING_BRIDGE_PORT_SETTING_NAME","\ + NM_SETTING_TEAM_SETTING_NAME","\ + NM_SETTING_TEAM_PORT_SETTING_NAME"," \ + NM_SETTING_DCB_SETTING_NAME +#if WITH_WIMAX +#define NMC_FIELDS_SETTINGS_NAMES_ALL NMC_FIELDS_SETTINGS_NAMES_ALL_X","\ + NM_SETTING_WIMAX_SETTING_NAME +#else +#define NMC_FIELDS_SETTINGS_NAMES_ALL NMC_FIELDS_SETTINGS_NAMES_ALL_X +#endif + +/* Active connection data */ +/* Available fields for GENERAL group */ +static NmcOutputField nmc_fields_con_active_details_general[] = { + {"GROUP", N_("GROUP"), 9}, /* 0 */ + {"NAME", N_("NAME"), 25}, /* 1 */ + {"UUID", N_("UUID"), 38}, /* 2 */ + {"DEVICES", N_("DEVICES"), 10}, /* 3 */ + {"STATE", N_("STATE"), 12}, /* 4 */ + {"DEFAULT", N_("DEFAULT"), 8}, /* 5 */ + {"DEFAULT6", N_("DEFAULT6"), 9}, /* 6 */ + {"SPEC-OBJECT", N_("SPEC-OBJECT"), 10}, /* 7 */ + {"VPN", N_("VPN"), 5}, /* 8 */ + {"DBUS-PATH", N_("DBUS-PATH"), 51}, /* 9 */ + {"CON-PATH", N_("CON-PATH"), 44}, /* 10 */ + {"ZONE", N_("ZONE"), 15}, /* 11 */ + {"MASTER-PATH", N_("MASTER-PATH"), 44}, /* 12 */ + {NULL, NULL, 0} +}; +#define NMC_FIELDS_CON_ACTIVE_DETAILS_GENERAL_ALL "GROUP,NAME,UUID,DEVICES,STATE,DEFAULT,DEFAULT6,"\ + "VPN,ZONE,DBUS-PATH,CON-PATH,SPEC-OBJECT,MASTER-PATH" + +/* IP group is handled by common.c */ + +/* Available fields for VPN group */ +static NmcOutputField nmc_fields_con_active_details_vpn[] = { + {"GROUP", N_("GROUP"), 9}, /* 0 */ + {"TYPE", N_("TYPE"), 15}, /* 1 */ + {"USERNAME", N_("USERNAME"), 15}, /* 2 */ + {"GATEWAY", N_("GATEWAY"), 25}, /* 3 */ + {"BANNER", N_("BANNER"), 120}, /* 4 */ + {"VPN-STATE", N_("VPN-STATE"), 40}, /* 5 */ + {"CFG", N_("CFG"), 120}, /* 6 */ + {NULL, NULL, 0} +}; +#define NMC_FIELDS_CON_ACTIVE_DETAILS_VPN_ALL "GROUP,TYPE,USERNAME,GATEWAY,BANNER,VPN-STATE,CFG" + +/* defined in common.c */ +extern NmcOutputField nmc_fields_ip4_config[]; +extern NmcOutputField nmc_fields_ip6_config[]; +extern NmcOutputField nmc_fields_dhcp4_config[]; +extern NmcOutputField nmc_fields_dhcp6_config[]; + +/* Available fields for 'connection show ' - active part */ +static NmcOutputField nmc_fields_con_active_details_groups[] = { + {"GENERAL", N_("GENERAL"), 0, nmc_fields_con_active_details_general + 1}, /* 0 */ + {"IP4", N_("IP4"), 0, nmc_fields_ip4_config + 1 }, /* 1 */ + {"DHCP4", N_("DHCP4"), 0, nmc_fields_dhcp4_config + 1 }, /* 2 */ + {"IP6", N_("IP6"), 0, nmc_fields_ip6_config + 1 }, /* 3 */ + {"DHCP6", N_("DHCP6"), 0, nmc_fields_dhcp6_config + 1 }, /* 4 */ + {"VPN", N_("VPN"), 0, nmc_fields_con_active_details_vpn + 1 }, /* 5 */ + {NULL, NULL, 0, NULL} +}; +#define NMC_FIELDS_CON_ACTIVE_DETAILS_ALL "GENERAL,IP4,DHCP4,IP6,DHCP6,VPN" + +/* Pseudo group names for 'connection show ' */ +/* e.g.: nmcli -f profile con show my-eth0 */ +/* e.g.: nmcli -f active con show my-eth0 */ +#define CON_SHOW_DETAIL_GROUP_PROFILE "profile" +#define CON_SHOW_DETAIL_GROUP_ACTIVE "active" + +/* glib main loop variable - defined in nmcli.c */ +extern GMainLoop *loop; + +static guint progress_id = 0; /* ID of event source for displaying progress */ + +/* for readline TAB completion in editor */ +typedef struct { + NmCli *nmc; + char *con_type; + NMConnection *connection; + NMSetting *setting; +} TabCompletionInfo; +static TabCompletionInfo nmc_tab_completion = {NULL, NULL, NULL, NULL}; + +/* Global variable defined in nmcli.c - used for TAB completion */ +extern NmCli nm_cli; + +static char *gen_connection_types (const char *text, int state); + +static void +usage (void) +{ + g_printerr (_("Usage: nmcli connection { COMMAND | help }\n\n" + "COMMAND := { show | up | down | add | modify | edit | delete | reload | load }\n\n" + " show [--active] [[--show-secrets] [id | uuid | path | apath] ] ...\n\n" +#if WITH_WIMAX + " up [[id | uuid | path] ] [ifname ] [ap ] [nsp ] [passwd-file ]\n\n" +#else + " up [[id | uuid | path] ] [ifname ] [ap ] [passwd-file ]\n\n" +#endif + " down [id | uuid | path | apath] ...\n\n" + " add COMMON_OPTIONS TYPE_SPECIFIC_OPTIONS IP_OPTIONS\n\n" + " modify [--temporary] [id | uuid | path] ([+|-]. )+\n\n" + " edit [id | uuid | path] \n" + " edit [type ] [con-name ]\n\n" + " delete [id | uuid | path] \n\n" + " reload\n\n" + " load [ ... ]\n\n")); +} + +static void +usage_connection_show (void) +{ + g_printerr (_("Usage: nmcli connection show { ARGUMENTS | help }\n" + "\n" + "ARGUMENTS := [--active]\n" + "\n" + "List in-memory and on-disk connection profiles, some of which may also be\n" + "active if a device is using that connection profile. Without a parameter, all\n" + "profiles are listed. When --active option is specified, only the active\n" + "profiles are shown.\n" + "\n" + "ARGUMENTS := [--active] [--show-secrets] [id | uuid | path | apath] ...\n" + "\n" + "Show details for specified connections. By default, both static configuration\n" + "and active connection data are displayed. It is possible to filter the output\n" + "using global '--fields' option. Refer to the manual page for more information.\n" + "When --active option is specified, only the active profiles are taken into\n" + "account. --show-secrets option will reveal associated secrets as well.\n")); +} + +static void +usage_connection_up (void) +{ + g_printerr (_("Usage: nmcli connection up { ARGUMENTS | help }\n" + "\n" + "ARGUMENTS := [id | uuid | path] [ifname ] [ap ] [nsp ] [passwd-file ]\n" + "\n" + "Activate a connection on a device. The profile to activate is identified by its\n" + "name, UUID or D-Bus path.\n" + "\n" + "ARGUMENTS := ifname [ap ] [nsp ] [passwd-file ]\n" + "\n" + "Activate a device with a connection. The connection profile is selected\n" + "automatically by NetworkManager.\n" + "\n" + "ifname - specifies the device to active the connection on\n" + "ap - specifies AP to connect to (only valid for Wi-Fi)\n" + "nsp - specifies NSP to connect to (only valid for WiMAX)\n" + "passwd-file - file with password(s) required to activate the connection\n\n")); +} + +static void +usage_connection_down (void) +{ + g_printerr (_("Usage: nmcli connection down { ARGUMENTS | help }\n" + "\n" + "ARGUMENTS := [id | uuid | path | apath] ...\n" + "\n" + "Deactivate a connection from a device (without preventing the device from\n" + "further auto-activation). The profile to deactivate is identified by its name,\n" + "UUID or D-Bus path.\n\n")); +} + +static void +usage_connection_add (void) +{ + g_printerr (_("Usage: nmcli connection add { ARGUMENTS | help }\n" + "\n" + "ARGUMENTS := COMMON_OPTIONS TYPE_SPECIFIC_OPTIONS IP_OPTIONS\n\n" + " COMMON_OPTIONS:\n" + " type \n" + " ifname | \"*\"\n" + " [con-name ]\n" + " [autoconnect yes|no]\n\n" + " [save yes|no]\n\n" + " TYPE_SPECIFIC_OPTIONS:\n" + " ethernet: [mac ]\n" + " [cloned-mac ]\n" + " [mtu ]\n\n" + " wifi: ssid \n" + " [mac ]\n" + " [cloned-mac ]\n" + " [mtu ]\n" + " [mode infrastructure|ap|adhoc]\n\n" + " wimax: [mac ]\n" + " [nsp ]\n\n" + " pppoe: username \n" + " [password ]\n" + " [service ]\n" + " [mtu ]\n" + " [mac ]\n\n" + " gsm: apn \n" + " [user ]\n" + " [password ]\n\n" + " cdma: [user ]\n" + " [password ]\n\n" + " infiniband: [mac ]\n" + " [mtu ]\n" + " [transport-mode datagram | connected]\n" + " [parent ]\n" + " [p-key ]\n\n" + " bluetooth: [addr ]\n" + " [bt-type panu|dun-gsm|dun-cdma]\n\n" + " vlan: dev \n" + " id \n" + " [flags ]\n" + " [ingress ]\n" + " [egress ]\n" + " [mtu ]\n\n" + " bond: [mode balance-rr (0) | active-backup (1) | balance-xor (2) | broadcast (3) |\n" + " 802.3ad (4) | balance-tlb (5) | balance-alb (6)]\n" + " [primary ]\n" + " [miimon ]\n" + " [downdelay ]\n" + " [updelay ]\n" + " [arp-interval ]\n" + " [arp-ip-target ]\n" + " [lacp-rate slow (0) | fast (1)]\n\n" + " bond-slave: master \n\n" + " team: [config |]\n\n" + " team-slave: master \n" + " [config |]\n\n" + " bridge: [stp yes|no]\n" + " [priority ]\n" + " [forward-delay <2-30>]\n" + " [hello-time <1-10>]\n" + " [max-age <6-40>]\n" + " [ageing-time <0-1000000>]\n" + " [mac ]\n\n" + " bridge-slave: master \n" + " [priority <0-63>]\n" + " [path-cost <1-65535>]\n" + " [hairpin yes|no]\n\n" + " vpn: vpn-type vpnc|openvpn|pptp|openconnect|openswan|libreswan|ssh|l2tp|iodine|...\n" + " [user ]\n\n" + " olpc-mesh: ssid \n" + " [channel <1-13>]\n" + " [dhcp-anycast ]\n\n" + " IP_OPTIONS:\n" + " [ip4 ] [gw4 ]\n" + " [ip6 ] [gw6 ]\n\n")); +} + +static void +usage_connection_modify (void) +{ + g_printerr (_("Usage: nmcli connection modify { ARGUMENTS | help }\n" + "\n" + "ARGUMENTS := [id | uuid | path] ([+|-]. )+\n" + "\n" + "Modify one or more properties of the connection profile.\n" + "The profile is identified by its name, UUID or D-Bus path. For multi-valued\n" + "properties you can use optional '+' or '-' prefix to the property name.\n" + "The '+' sign allows appending items instead of overwriting the whole value.\n" + "The '-' sign allows removing selected items instead of the whole value.\n" + "\n" + "Examples:\n" + "nmcli con mod home-wifi wifi.ssid rakosnicek\n" + "nmcli con mod em1-1 ipv4.method manual ipv4.addr \"192.168.1.2/24, 10.10.1.5/8\"\n" + "nmcli con mod em1-1 +ipv4.dns 8.8.4.4\n" + "nmcli con mod em1-1 -ipv4.dns 1\n" + "nmcli con mod em1-1 -ipv6.addr \"abbe::cafe/56\"\n" + "nmcli con mod bond0 +bond.options mii=500\n" + "nmcli con mod bond0 -bond.options downdelay\n\n")); +} + +static void +usage_connection_edit (void) +{ + g_printerr (_("Usage: nmcli connection edit { ARGUMENTS | help }\n" + "\n" + "ARGUMENTS := [id | uuid | path] \n" + "\n" + "Edit an existing connection profile in an interactive editor.\n" + "The profile is identified by its name, UUID or D-Bus path\n" + "\n" + "ARGUMENTS := [type ] [con-name ]\n" + "\n" + "Add a new connection profile in an interactive editor.\n\n")); +} + +static void +usage_connection_delete (void) +{ + g_printerr (_("Usage: nmcli connection delete { ARGUMENTS | help }\n" + "\n" + "ARGUMENTS := [id | uuid | path] \n" + "\n" + "Delete a connection profile.\n" + "The profile is identified by its name, UUID or D-Bus path.\n\n")); +} + +static void +usage_connection_reload (void) +{ + g_printerr (_("Usage: nmcli connection reload { help }\n" + "\n" + "Reload all connection files from disk.\n\n")); +} + +static void +usage_connection_load (void) +{ + g_printerr (_("Usage: nmcli connection load { ARGUMENTS | help }\n" + "\n" + "ARGUMENTS := [...]\n" + "\n" + "Load/reload one or more connection files from disk. Use this after manually\n" + "editing a connection file to ensure that NetworkManager is aware of its latest\n" + "state.\n\n")); +} + +static gboolean +usage_connection_second_level (const char *cmd) +{ + gboolean ret = TRUE; + + if (matches (cmd, "show") == 0) + usage_connection_show (); + else if (matches (cmd, "up") == 0) + usage_connection_up (); + else if (matches (cmd, "down") == 0) + usage_connection_down (); + else if (matches (cmd, "add") == 0) + usage_connection_add (); + else if (matches (cmd, "modify") == 0) + usage_connection_modify (); + else if (matches (cmd, "edit") == 0) + usage_connection_edit (); + else if (matches (cmd, "delete") == 0) + usage_connection_delete (); + else if (matches (cmd, "reload") == 0) + usage_connection_reload (); + else if (matches (cmd, "load") == 0) + usage_connection_load (); + else + ret = FALSE; + return ret; +} + +/* quit main loop */ +static void +quit (void) +{ + if (progress_id) { + g_source_remove (progress_id); + progress_id = 0; + nmc_terminal_erase_line (); + } + + g_main_loop_quit (loop); /* quit main loop */ +} + +static const char * +construct_header_name (const char *base, const char *spec) +{ + static char header_name[128]; + + if (spec == NULL) + return base; + + g_strlcpy (header_name, base, sizeof (header_name)); + g_strlcat (header_name, " (", sizeof (header_name)); + g_strlcat (header_name, spec, sizeof (header_name)); + g_strlcat (header_name, ")", sizeof (header_name)); + + return header_name; +} + +static const char * +active_connection_state_to_string (NMActiveConnectionState state) +{ + switch (state) { + case NM_ACTIVE_CONNECTION_STATE_ACTIVATING: + return _("activating"); + case NM_ACTIVE_CONNECTION_STATE_ACTIVATED: + return _("activated"); + case NM_ACTIVE_CONNECTION_STATE_DEACTIVATING: + return _("deactivating"); + case NM_ACTIVE_CONNECTION_STATE_DEACTIVATED: + return _("deactivated"); + case NM_ACTIVE_CONNECTION_STATE_UNKNOWN: + default: + return _("unknown"); + } +} + +static const char * +vpn_connection_state_to_string (NMVpnConnectionState state) +{ + switch (state) { + case NM_VPN_CONNECTION_STATE_PREPARE: + return _("VPN connecting (prepare)"); + case NM_VPN_CONNECTION_STATE_NEED_AUTH: + return _("VPN connecting (need authentication)"); + case NM_VPN_CONNECTION_STATE_CONNECT: + return _("VPN connecting"); + case NM_VPN_CONNECTION_STATE_IP_CONFIG_GET: + return _("VPN connecting (getting IP configuration)"); + case NM_VPN_CONNECTION_STATE_ACTIVATED: + return _("VPN connected"); + case NM_VPN_CONNECTION_STATE_FAILED: + return _("VPN connection failed"); + case NM_VPN_CONNECTION_STATE_DISCONNECTED: + return _("VPN disconnected"); + default: + return _("unknown"); + } +} + +/* Caller has to free the returned string */ +static char * +get_ac_device_string (NMActiveConnection *active) +{ + GString *dev_str; + const GPtrArray *devices; + int i; + + if (!active) + return NULL; + + /* Get devices of the active connection */ + dev_str = g_string_new (NULL); + devices = nm_active_connection_get_devices (active); + for (i = 0; i < devices->len; i++) { + NMDevice *device = g_ptr_array_index (devices, i); + const char *dev_iface = nm_device_get_iface (device); + + if (dev_iface) { + g_string_append (dev_str, dev_iface); + g_string_append_c (dev_str, ','); + } + } + if (dev_str->len > 0) + g_string_truncate (dev_str, dev_str->len - 1); /* Cut off last ',' */ + + return g_string_free (dev_str, FALSE); +} + +static NMActiveConnection * +get_ac_for_connection (const GPtrArray *active_cons, NMConnection *connection) +{ + const char *con_path, *ac_con_path; + int i; + NMActiveConnection *ac = NULL; + + /* Is the connection active? */ + con_path = nm_connection_get_path (connection); + for (i = 0; i < active_cons->len; i++) { + NMActiveConnection *candidate = g_ptr_array_index (active_cons, i); + NMRemoteConnection *con; + + con = nm_active_connection_get_connection (candidate); + ac_con_path = con ? nm_connection_get_path (NM_CONNECTION (con)) : NULL; + if (!g_strcmp0 (ac_con_path, con_path)) { + ac = candidate; + break; + } + } + return ac; +} + +/* Put secrets into local connection. */ +static void +update_secrets_in_connection (NMRemoteConnection *remote, NMConnection *local) +{ + GVariant *secrets; + int i; + GError *error = NULL; + + for (i = 0; nmc_fields_settings_names[i].name; i++) { + secrets = nm_remote_connection_get_secrets (remote, nmc_fields_settings_names[i].name, NULL, NULL); + if (secrets) { + if (!nm_connection_update_secrets (local, NULL, secrets, &error) && error) { + g_printerr (_("Error updating secrets for %s: %s\n"), + nmc_fields_settings_names[i].name, + error->message); + g_clear_error (&error); + } + g_variant_unref (secrets); + } + } +} + +static gboolean +nmc_connection_profile_details (NMConnection *connection, NmCli *nmc, gboolean secrets) +{ + GError *error = NULL; + GArray *print_settings_array; + GPtrArray *prop_array = NULL; + int i; + char *fields_str; + char *fields_all = NMC_FIELDS_SETTINGS_NAMES_ALL; + char *fields_common = NMC_FIELDS_SETTINGS_NAMES_ALL; + const char *base_hdr = _("Connection profile details"); + gboolean was_output = FALSE; + + if (!nmc->required_fields || strcasecmp (nmc->required_fields, "common") == 0) + fields_str = fields_common; + else if (!nmc->required_fields || strcasecmp (nmc->required_fields, "all") == 0) + fields_str = fields_all; + else + fields_str = nmc->required_fields; + + print_settings_array = parse_output_fields (fields_str, nmc_fields_settings_names, TRUE, &prop_array, &error); + if (error) { + g_string_printf (nmc->return_text, _("Error: 'connection show': %s"), error->message); + g_error_free (error); + nmc->return_value = NMC_RESULT_ERROR_USER_INPUT; + return FALSE; + } + g_assert (print_settings_array); + + /* Main header */ + nmc->print_fields.header_name = (char *) construct_header_name (base_hdr, nm_connection_get_id (connection)); + nmc->print_fields.indices = parse_output_fields (NMC_FIELDS_SETTINGS_NAMES_ALL, + nmc_fields_settings_names, FALSE, NULL, NULL); + + nmc_fields_settings_names[0].flags = NMC_OF_FLAG_MAIN_HEADER_ONLY; + print_required_fields (nmc, nmc_fields_settings_names); + + /* Loop through the required settings and print them. */ + for (i = 0; i < print_settings_array->len; i++) { + NMSetting *setting; + int section_idx = g_array_index (print_settings_array, int, i); + const char *prop_name = (const char *) g_ptr_array_index (prop_array, i); + + if (nmc->print_output != NMC_PRINT_TERSE && !nmc->multiline_output && was_output) + g_print ("\n"); /* Empty line */ + + was_output = FALSE; + + /* Remove any previous data */ + nmc_empty_output_fields (nmc); + + setting = nm_connection_get_setting_by_name (connection, nmc_fields_settings_names[section_idx].name); + if (setting) { + setting_details (setting, nmc, prop_name, secrets); + was_output = TRUE; + continue; + } + } + + g_array_free (print_settings_array, TRUE); + if (prop_array) + g_ptr_array_free (prop_array, TRUE); + + return TRUE; +} + +static NMActiveConnection * +find_active_connection (const GPtrArray *active_cons, + const GPtrArray *cons, + const char *filter_type, + const char *filter_val, + int *idx) +{ + int i; + int start = (idx && *idx > 0) ? *idx : 0; + const char *path, *a_path, *path_num, *a_path_num; + const char *id; + const char *uuid; + NMRemoteConnection *con; + NMActiveConnection *found = NULL; + + for (i = start; i < active_cons->len; i++) { + NMActiveConnection *candidate = g_ptr_array_index (active_cons, i); + + con = nm_active_connection_get_connection (candidate); + + id = nm_active_connection_get_id (candidate); + uuid = nm_active_connection_get_uuid (candidate); + path = con ? nm_connection_get_path (NM_CONNECTION (con)) : NULL; + path_num = path ? strrchr (path, '/') + 1 : NULL; + a_path = nm_object_get_path (NM_OBJECT (candidate)); + a_path_num = a_path ? strrchr (a_path, '/') + 1 : NULL; + + /* When filter_type is NULL, compare connection ID (filter_val) + * against all types. Otherwise, only compare against the specific + * type. If 'path' or 'apath' filter types are specified, comparison + * against numeric index (in addition to the whole path) is allowed. + */ + if ( ( (!filter_type || strcmp (filter_type, "id") == 0) + && strcmp (filter_val, id) == 0) + || ( (!filter_type || strcmp (filter_type, "uuid") == 0) + && strcmp (filter_val, uuid) == 0) + || ( (!filter_type || strcmp (filter_type, "path") == 0) + && (g_strcmp0 (filter_val, path) == 0 || (filter_type && g_strcmp0 (filter_val, path_num) == 0))) + || ( (!filter_type || strcmp (filter_type, "apath") == 0) + && (g_strcmp0 (filter_val, a_path) == 0 || (filter_type && g_strcmp0 (filter_val, a_path_num) == 0)))) { + if (!idx) + return candidate; + if (found) { + *idx = i; + return found; + } + found = candidate; + } + } + + if (idx) + *idx = 0; + return found; +} + +static void +fill_output_connection (gpointer data, gpointer user_data, gboolean active_only) +{ + NMConnection *connection = (NMConnection *) data; + NmCli *nmc = (NmCli *) user_data; + NMSettingConnection *s_con; + guint64 timestamp; + time_t timestamp_real; + char *timestamp_str; + char *timestamp_real_str = ""; + char *prio_str; + NmcOutputField *arr; + NMActiveConnection *ac = NULL; + const char *ac_path = NULL; + const char *ac_state = NULL; + char *ac_dev = NULL; + + s_con = nm_connection_get_setting_connection (connection); + g_assert (s_con); + + ac = get_ac_for_connection (nm_client_get_active_connections (nmc->client), connection); + if (active_only && !ac) + return; + + if (ac) { + ac_path = nm_object_get_path (NM_OBJECT (ac)); + ac_state = active_connection_state_to_string (nm_active_connection_get_state (ac)); + ac_dev = get_ac_device_string (ac); + } + + /* Obtain field values */ + timestamp = nm_setting_connection_get_timestamp (s_con); + timestamp_str = g_strdup_printf ("%" G_GUINT64_FORMAT, timestamp); + if (timestamp) { + timestamp_real = timestamp; + timestamp_real_str = g_malloc0 (64); + strftime (timestamp_real_str, 64, "%c", localtime (×tamp_real)); + } + prio_str = g_strdup_printf ("%u", nm_setting_connection_get_autoconnect_priority (s_con)); + + arr = nmc_dup_fields_array (nmc_fields_con_show, + sizeof (nmc_fields_con_show), + 0); + set_val_strc (arr, 0, nm_setting_connection_get_id (s_con)); + set_val_strc (arr, 1, nm_setting_connection_get_uuid (s_con)); + set_val_strc (arr, 2, nm_setting_connection_get_connection_type (s_con)); + set_val_str (arr, 3, timestamp_str); + set_val_str (arr, 4, timestamp ? timestamp_real_str : g_strdup (_("never"))); + set_val_strc (arr, 5, nm_setting_connection_get_autoconnect (s_con) ? _("yes") : _("no")); + set_val_str (arr, 6, prio_str); + set_val_strc (arr, 7, nm_setting_connection_get_read_only (s_con) ? _("yes") : _("no")); + set_val_strc (arr, 8, nm_connection_get_path (connection)); + set_val_strc (arr, 9, ac ? _("yes") : _("no")); + set_val_str (arr, 10, ac_dev); + set_val_strc (arr, 11, ac_state); + set_val_strc (arr, 12, ac_path); + + g_ptr_array_add (nmc->output_data, arr); +} + +static void +fill_output_connection_for_invisible (NMActiveConnection *ac, NmCli *nmc) +{ + NmcOutputField *arr; + const char *ac_path = NULL; + const char *ac_state = NULL; + char *ac_dev = NULL; + + ac_path = nm_object_get_path (NM_OBJECT (ac)); + ac_state = active_connection_state_to_string (nm_active_connection_get_state (ac)); + ac_dev = get_ac_device_string (ac); + + arr = nmc_dup_fields_array (nmc_fields_con_show, + sizeof (nmc_fields_con_show), + 0); + set_val_strc (arr, 0, nm_active_connection_get_id (ac)); + set_val_strc (arr, 1, nm_active_connection_get_uuid (ac)); + set_val_strc (arr, 2, nm_active_connection_get_connection_type (ac)); + set_val_strc (arr, 3, NULL); + set_val_strc (arr, 4, NULL); + set_val_strc (arr, 5, NULL); + set_val_strc (arr, 6, NULL); + set_val_strc (arr, 7, NULL); + set_val_strc (arr, 8, NULL); + set_val_strc (arr, 9, _("yes")); + set_val_str (arr, 10, ac_dev); + set_val_strc (arr, 11, ac_state); + set_val_strc (arr, 12, ac_path); + + g_ptr_array_add (nmc->output_data, arr); +} + +static void +fill_output_active_connection (NMActiveConnection *active, + NmCli *nmc, + gboolean with_group, + guint32 o_flags) +{ + NMRemoteConnection *con; + NMSettingConnection *s_con; + const GPtrArray *devices; + GString *dev_str; + NMActiveConnectionState state; + NMDevice *master; + const char *con_path = NULL, *con_zone = NULL; + int i; + NmcOutputField *tmpl, *arr; + size_t tmpl_len; + int idx_start = with_group ? 0 : 1; + + con = nm_active_connection_get_connection (active); + if (con) { + con_path = nm_connection_get_path (NM_CONNECTION (con)); + s_con = nm_connection_get_setting_connection (NM_CONNECTION (con)); + g_assert (s_con != NULL); + con_zone = nm_setting_connection_get_zone (s_con); + } + + state = nm_active_connection_get_state (active); + master = nm_active_connection_get_master (active); + + /* Get devices of the active connection */ + dev_str = g_string_new (NULL); + devices = nm_active_connection_get_devices (active); + for (i = 0; i < devices->len; i++) { + NMDevice *device = g_ptr_array_index (devices, i); + const char *dev_iface = nm_device_get_iface (device); + + if (dev_iface) { + g_string_append (dev_str, dev_iface); + g_string_append_c (dev_str, ','); + } + } + if (dev_str->len > 0) + g_string_truncate (dev_str, dev_str->len - 1); /* Cut off last ',' */ + + tmpl = nmc_fields_con_active_details_general; + tmpl_len = sizeof (nmc_fields_con_active_details_general); + if (!with_group) { + tmpl++; + tmpl_len -= sizeof (NmcOutputField); + } + + /* Fill field values */ + arr = nmc_dup_fields_array (tmpl, tmpl_len, o_flags); + if (with_group) + set_val_strc (arr, 0, nmc_fields_con_active_details_groups[0].name); + set_val_strc (arr, 1-idx_start, nm_active_connection_get_id (active)); + set_val_strc (arr, 2-idx_start, nm_active_connection_get_uuid (active)); + set_val_str (arr, 3-idx_start, dev_str->str); + set_val_strc (arr, 4-idx_start, active_connection_state_to_string (state)); + set_val_strc (arr, 5-idx_start, nm_active_connection_get_default (active) ? _("yes") : _("no")); + set_val_strc (arr, 6-idx_start, nm_active_connection_get_default6 (active) ? _("yes") : _("no")); + set_val_strc (arr, 7-idx_start, nm_active_connection_get_specific_object_path (active)); + set_val_strc (arr, 8-idx_start, NM_IS_VPN_CONNECTION (active) ? _("yes") : _("no")); + set_val_strc (arr, 9-idx_start, nm_object_get_path (NM_OBJECT (active))); + set_val_strc (arr, 10-idx_start, con_path); + set_val_strc (arr, 11-idx_start, con_zone); + set_val_strc (arr, 12-idx_start, master ? nm_object_get_path (NM_OBJECT (master)) : NULL); + + g_ptr_array_add (nmc->output_data, arr); + + g_string_free (dev_str, FALSE); +} + +static void +fill_output_for_all_invisible (NmCli *nmc) +{ + const GPtrArray *acons; + int a, c; + + g_return_if_fail (nmc != NULL); + + acons = nm_client_get_active_connections (nmc->client); + for (a = 0; a < acons->len; a++) { + gboolean found = FALSE; + NMActiveConnection *acon = g_ptr_array_index (acons, a); + const char *a_uuid = nm_active_connection_get_uuid (acon); + + for (c = 0; c < nmc->connections->len; c++) { + NMConnection *con = g_ptr_array_index (nmc->connections, c); + const char *c_uuid = nm_connection_get_uuid (con); + + if (strcmp (a_uuid, c_uuid) == 0) { + found = TRUE; + break; + } + } + + /* Active connection is not in connections list */ + if (!found) + fill_output_connection_for_invisible (acon, nmc); + } +} + +typedef struct { + char **array; + guint32 idx; +} FillVPNDataInfo; + +static void +fill_vpn_data_item (const char *key, const char *value, gpointer user_data) +{ + FillVPNDataInfo *info = (FillVPNDataInfo *) user_data; + + info->array[info->idx++] = g_strdup_printf ("%s = %s", key, value); +} + +// FIXME: The same or similar code for VPN info appears also in nm-applet (applet-dialogs.c), +// and in gnome-control-center as well. It could probably be shared somehow. +static char * +get_vpn_connection_type (NMConnection *connection) +{ + const char *type, *p; + + /* The service type is in form of "org.freedesktop.NetworkManager.vpnc". + * Extract end part after last dot, e.g. "vpnc" + */ + type = nm_setting_vpn_get_service_type (nm_connection_get_setting_vpn (connection)); + p = strrchr (type, '.'); + return g_strdup (p ? p + 1 : type); +} + +/* VPN parameters can be found at: + * http://git.gnome.org/browse/network-manager-openvpn/tree/src/nm-openvpn-service.h + * http://git.gnome.org/browse/network-manager-vpnc/tree/src/nm-vpnc-service.h + * http://git.gnome.org/browse/network-manager-pptp/tree/src/nm-pptp-service.h + * http://git.gnome.org/browse/network-manager-openconnect/tree/src/nm-openconnect-service.h + * http://git.gnome.org/browse/network-manager-openswan/tree/src/nm-openswan-service.h + * See also 'properties' directory in these plugins. + */ +static const gchar * +find_vpn_gateway_key (const char *vpn_type) +{ + if (g_strcmp0 (vpn_type, "openvpn") == 0) return "remote"; + if (g_strcmp0 (vpn_type, "vpnc") == 0) return "IPSec gateway"; + if (g_strcmp0 (vpn_type, "pptp") == 0) return "gateway"; + if (g_strcmp0 (vpn_type, "openconnect") == 0) return "gateway"; + if (g_strcmp0 (vpn_type, "openswan") == 0) return "right"; + if (g_strcmp0 (vpn_type, "libreswan") == 0) return "right"; + if (g_strcmp0 (vpn_type, "ssh") == 0) return "remote"; + if (g_strcmp0 (vpn_type, "l2tp") == 0) return "gateway"; + return ""; +} + +static const gchar * +find_vpn_username_key (const char *vpn_type) +{ + if (g_strcmp0 (vpn_type, "openvpn") == 0) return "username"; + if (g_strcmp0 (vpn_type, "vpnc") == 0) return "Xauth username"; + if (g_strcmp0 (vpn_type, "pptp") == 0) return "user"; + if (g_strcmp0 (vpn_type, "openconnect") == 0) return "username"; + if (g_strcmp0 (vpn_type, "openswan") == 0) return "leftxauthusername"; + if (g_strcmp0 (vpn_type, "libreswan") == 0) return "leftxauthusername"; + if (g_strcmp0 (vpn_type, "l2tp") == 0) return "user"; + return ""; +} + +enum VpnDataItem { + VPN_DATA_ITEM_GATEWAY, + VPN_DATA_ITEM_USERNAME +}; + +static const gchar * +get_vpn_data_item (NMConnection *connection, enum VpnDataItem vpn_data_item) +{ + const char *key; + char *type = get_vpn_connection_type (connection); + + switch (vpn_data_item) { + case VPN_DATA_ITEM_GATEWAY: + key = find_vpn_gateway_key (type); + break; + case VPN_DATA_ITEM_USERNAME: + key = find_vpn_username_key (type); + break; + default: + key = ""; + break; + } + g_free (type); + + return nm_setting_vpn_get_data_item (nm_connection_get_setting_vpn (connection), key); +} +/* FIXME end */ + +static gboolean +nmc_active_connection_details (NMActiveConnection *acon, NmCli *nmc) +{ + GError *error = NULL; + GArray *print_groups; + GPtrArray *group_fields = NULL; + int i; + char *fields_str; + char *fields_all = NMC_FIELDS_CON_ACTIVE_DETAILS_ALL; + char *fields_common = NMC_FIELDS_CON_ACTIVE_DETAILS_ALL; + NmcOutputField *tmpl, *arr; + size_t tmpl_len; + const char *base_hdr = _("Activate connection details"); + gboolean was_output = FALSE; + + if (!nmc->required_fields || strcasecmp (nmc->required_fields, "common") == 0) + fields_str = fields_common; + else if (!nmc->required_fields || strcasecmp (nmc->required_fields, "all") == 0) + fields_str = fields_all; + else + fields_str = nmc->required_fields; + + print_groups = parse_output_fields (fields_str, nmc_fields_con_active_details_groups, TRUE, &group_fields, &error); + if (error) { + g_string_printf (nmc->return_text, _("Error: 'connection show': %s"), error->message); + g_error_free (error); + nmc->return_value = NMC_RESULT_ERROR_USER_INPUT; + return FALSE; + } + g_assert (print_groups); + + /* Main header */ + nmc->print_fields.header_name = (char *) construct_header_name (base_hdr, nm_active_connection_get_uuid (acon)); + nmc->print_fields.indices = parse_output_fields (NMC_FIELDS_CON_ACTIVE_DETAILS_ALL, + nmc_fields_con_active_details_groups, FALSE, NULL, NULL); + + nmc_fields_con_active_details_groups[0].flags = NMC_OF_FLAG_MAIN_HEADER_ONLY; + print_required_fields (nmc, nmc_fields_con_active_details_groups); + + /* Loop through the groups and print them. */ + for (i = 0; i < print_groups->len; i++) { + int group_idx = g_array_index (print_groups, int, i); + char *group_fld = (char *) g_ptr_array_index (group_fields, i); + + if (nmc->print_output != NMC_PRINT_TERSE && !nmc->multiline_output && was_output) + g_print ("\n"); /* Empty line */ + + was_output = FALSE; + + /* Remove any previous data */ + nmc_empty_output_fields (nmc); + + /* GENERAL */ + if (strcasecmp (nmc_fields_con_active_details_groups[group_idx].name, nmc_fields_con_active_details_groups[0].name) == 0) { + /* Add field names */ + tmpl = nmc_fields_con_active_details_general; + tmpl_len = sizeof (nmc_fields_con_active_details_general); + nmc->print_fields.indices = parse_output_fields (group_fld ? group_fld : NMC_FIELDS_CON_ACTIVE_DETAILS_GENERAL_ALL, + tmpl, FALSE, NULL, NULL); + arr = nmc_dup_fields_array (tmpl, tmpl_len, NMC_OF_FLAG_FIELD_NAMES); + g_ptr_array_add (nmc->output_data, arr); + + /* Fill in values */ + fill_output_active_connection (acon, nmc, TRUE, NMC_OF_FLAG_SECTION_PREFIX); + + print_data (nmc); /* Print all data */ + + was_output = TRUE; + } + + /* IP4 */ + if (strcasecmp (nmc_fields_con_active_details_groups[group_idx].name, nmc_fields_con_active_details_groups[1].name) == 0) { + gboolean b1 = FALSE; + NMIPConfig *cfg4 = nm_active_connection_get_ip4_config (acon); + + b1 = print_ip4_config (cfg4, nmc, "IP4", group_fld); + was_output = was_output || b1; + } + + /* DHCP4 */ + if (strcasecmp (nmc_fields_con_active_details_groups[group_idx].name, nmc_fields_con_active_details_groups[2].name) == 0) { + gboolean b1 = FALSE; + NMDhcpConfig *dhcp4 = nm_active_connection_get_dhcp4_config (acon); + + b1 = print_dhcp4_config (dhcp4, nmc, "DHCP4", group_fld); + was_output = was_output || b1; + } + + /* IP6 */ + if (strcasecmp (nmc_fields_con_active_details_groups[group_idx].name, nmc_fields_con_active_details_groups[3].name) == 0) { + gboolean b1 = FALSE; + NMIPConfig *cfg6 = nm_active_connection_get_ip6_config (acon); + + b1 = print_ip6_config (cfg6, nmc, "IP6", group_fld); + was_output = was_output || b1; + } + + /* DHCP6 */ + if (strcasecmp (nmc_fields_con_active_details_groups[group_idx].name, nmc_fields_con_active_details_groups[4].name) == 0) { + gboolean b1 = FALSE; + NMDhcpConfig *dhcp6 = nm_active_connection_get_dhcp6_config (acon); + + b1 = print_dhcp6_config (dhcp6, nmc, "DHCP6", group_fld); + was_output = was_output || b1; + } + + /* VPN */ + if (NM_IS_VPN_CONNECTION (acon) && + strcasecmp (nmc_fields_con_active_details_groups[group_idx].name, nmc_fields_con_active_details_groups[5].name) == 0) { + NMConnection *con; + NMSettingConnection *s_con; + NMSettingVpn *s_vpn; + NMVpnConnectionState vpn_state; + char *type_str, *banner_str = NULL, *vpn_state_str; + const char *banner; + const char *username = NULL; + char **vpn_data_array = NULL; + guint32 items_num; + + con = NM_CONNECTION (nm_active_connection_get_connection (acon)); + + s_con = nm_connection_get_setting_connection (con); + g_assert (s_con != NULL); + + tmpl = nmc_fields_con_active_details_vpn; + tmpl_len = sizeof (nmc_fields_con_active_details_vpn); + nmc->print_fields.indices = parse_output_fields (group_fld ? group_fld : NMC_FIELDS_CON_ACTIVE_DETAILS_VPN_ALL, + tmpl, FALSE, NULL, NULL); + arr = nmc_dup_fields_array (tmpl, tmpl_len, NMC_OF_FLAG_FIELD_NAMES); + g_ptr_array_add (nmc->output_data, arr); + + s_vpn = nm_connection_get_setting_vpn (con); + if (s_vpn) { + items_num = nm_setting_vpn_get_num_data_items (s_vpn); + if (items_num > 0) { + FillVPNDataInfo info; + + vpn_data_array = g_new (char *, items_num + 1); + info.array = vpn_data_array; + info.idx = 0; + nm_setting_vpn_foreach_data_item (s_vpn, &fill_vpn_data_item, &info); + vpn_data_array[items_num] = NULL; + } + username = nm_setting_vpn_get_user_name (s_vpn); + } + + type_str = get_vpn_connection_type (con); + banner = nm_vpn_connection_get_banner (NM_VPN_CONNECTION (acon)); + if (banner) + banner_str = g_strescape (banner, ""); + vpn_state = nm_vpn_connection_get_vpn_state (NM_VPN_CONNECTION (acon)); + vpn_state_str = g_strdup_printf ("%d - %s", vpn_state, vpn_connection_state_to_string (vpn_state)); + + /* Add values */ + arr = nmc_dup_fields_array (tmpl, tmpl_len, NMC_OF_FLAG_SECTION_PREFIX); + set_val_strc (arr, 0, nmc_fields_con_active_details_groups[5].name); + set_val_str (arr, 1, type_str); + set_val_strc (arr, 2, username ? username : get_vpn_data_item (con, VPN_DATA_ITEM_USERNAME)); + set_val_strc (arr, 3, get_vpn_data_item (con, VPN_DATA_ITEM_GATEWAY)); + set_val_str (arr, 4, banner_str); + set_val_str (arr, 5, vpn_state_str); + set_val_arr (arr, 6, vpn_data_array); + g_ptr_array_add (nmc->output_data, arr); + + print_data (nmc); /* Print all data */ + was_output = TRUE; + } + } + + g_array_free (print_groups, TRUE); + if (group_fields) + g_ptr_array_free (group_fields, TRUE); + + return TRUE; +} + +static gboolean +split_required_fields_for_con_show (const char *input, + char **profile_flds, + char **active_flds, + GError **error) +{ + char **fields, **iter; + char *dot; + GString *str1, *str2; + gboolean found; + gboolean group_profile = FALSE; + gboolean group_active = FALSE; + gboolean success = TRUE; + gboolean is_all, is_common; + int i; + + if (!input) { + *profile_flds = NULL; + *active_flds = NULL; + return TRUE; + } + + str1 = g_string_new (NULL); + str2 = g_string_new (NULL); + + /* Split supplied fields string */ + fields = g_strsplit_set (input, ",", -1); + for (iter = fields; iter && *iter; iter++) { + g_strstrip (*iter); + dot = strchr (*iter, '.'); + if (dot) + *dot = '\0'; + + is_all = !dot && strcasecmp (*iter, "all") == 0; + is_common = !dot && strcasecmp (*iter, "common") == 0; + + found = FALSE; + + for (i = 0; nmc_fields_settings_names[i].name; i++) { + if ( is_all || is_common + || !strcasecmp (*iter, nmc_fields_settings_names[i].name)) { + if (dot) + *dot = '.'; + g_string_append (str1, *iter); + g_string_append_c (str1, ','); + found = TRUE; + break; + } + } + if (found) + continue; + for (i = 0; nmc_fields_con_active_details_groups[i].name; i++) { + if ( is_all || is_common + || !strcasecmp (*iter, nmc_fields_con_active_details_groups[i].name)) { + if (dot) + *dot = '.'; + g_string_append (str2, *iter); + g_string_append_c (str2, ','); + found = TRUE; + break; + } + } + if (!found) { + if (dot) + *dot = '.'; + if (!strcasecmp (*iter, CON_SHOW_DETAIL_GROUP_PROFILE)) + group_profile = TRUE; + else if (!strcasecmp (*iter, CON_SHOW_DETAIL_GROUP_ACTIVE)) + group_active = TRUE; + else { + char *allowed1 = nmc_get_allowed_fields (nmc_fields_settings_names, -1); + char *allowed2 = nmc_get_allowed_fields (nmc_fields_con_active_details_groups, -1); + g_set_error (error, NMCLI_ERROR, 0, _("invalid field '%s'; allowed fields: %s and %s, or %s,%s"), + *iter, allowed1, allowed2, CON_SHOW_DETAIL_GROUP_PROFILE, CON_SHOW_DETAIL_GROUP_ACTIVE); + g_free (allowed1); + g_free (allowed2); + success = FALSE; + break; + } + } + } + if (fields) + g_strfreev (fields); + + /* Handle pseudo groups: profile, active */ + if (success && group_profile) { + if (str1->len > 0) { + g_set_error (error, NMCLI_ERROR, 0, _("'%s' has to be alone"), + CON_SHOW_DETAIL_GROUP_PROFILE); + success = FALSE; + } else + g_string_assign (str1, "all,"); + } + if (success && group_active) { + if (str2->len > 0) { + g_set_error (error, NMCLI_ERROR, 0, _("'%s' has to be alone"), + CON_SHOW_DETAIL_GROUP_ACTIVE); + success = FALSE; + } else + g_string_assign (str2, "all,"); + } + + if (success) { + if (str1->len > 0) + g_string_truncate (str1, str1->len - 1); + if (str2->len > 0) + g_string_truncate (str2, str2->len - 1); + *profile_flds = g_string_free (str1, str1->len == 0); + *active_flds = g_string_free (str2, str2->len == 0); + } else { + g_string_free (str1, TRUE); + g_string_free (str2, TRUE); + } + return success; +} + +static NMCResultCode +do_connections_show (NmCli *nmc, gboolean active_only, gboolean show_secrets, + int argc, char **argv) +{ + GError *err = NULL; + char *profile_flds = NULL, *active_flds = NULL; + + nmc->should_wait = FALSE; + + if (argc == 0) { + char *fields_str; + char *fields_all = NMC_FIELDS_CON_SHOW_ALL; + char *fields_common = NMC_FIELDS_CON_SHOW_COMMON; + NmcOutputField *tmpl, *arr; + size_t tmpl_len; + int i; + + if (!nmc->required_fields || strcasecmp (nmc->required_fields, "common") == 0) + fields_str = fields_common; + else if (!nmc->required_fields || strcasecmp (nmc->required_fields, "all") == 0) + fields_str = fields_all; + else + fields_str = nmc->required_fields; + + tmpl = nmc_fields_con_show; + tmpl_len = sizeof (nmc_fields_con_show); + nmc->print_fields.indices = parse_output_fields (fields_str, tmpl, FALSE, NULL, &err); + if (err) { + goto finish; + } + if (!nmc_terse_option_check (nmc->print_output, nmc->required_fields, &err)) + goto finish; + + /* Add headers */ + nmc->print_fields.header_name = active_only ? _("NetworkManager active profiles") : + _("NetworkManager connection profiles"); + arr = nmc_dup_fields_array (tmpl, tmpl_len, NMC_OF_FLAG_MAIN_HEADER_ADD | NMC_OF_FLAG_FIELD_NAMES); + g_ptr_array_add (nmc->output_data, arr); + + /* Add values */ + for (i = 0; i < nmc->connections->len; i++) { + NMConnection *con = NM_CONNECTION (nmc->connections->pdata[i]); + fill_output_connection (con, nmc, active_only); + } + /* Some active connections may not be in connection list, show them here. */ + fill_output_for_all_invisible (nmc); + print_data (nmc); /* Print all data */ + } else { + gboolean new_line = FALSE; + gboolean without_fields = (nmc->required_fields == NULL); + const GPtrArray *active_cons = nm_client_get_active_connections (nmc->client); + int pos = 0; + + /* multiline mode is default for 'connection show ' */ + if (!nmc->mode_specified) + nmc->multiline_output = TRUE; + + /* Split required fields into the settings and active ones. */ + if (!split_required_fields_for_con_show (nmc->required_fields, &profile_flds, &active_flds, &err)) + goto finish; + g_free (nmc->required_fields); + nmc->required_fields = NULL; + + while (argc > 0) { + gboolean res; + NMConnection *con; + NMActiveConnection *acon = NULL; + const char *selector = NULL; + + if ( strcmp (*argv, "id") == 0 + || strcmp (*argv, "uuid") == 0 + || strcmp (*argv, "path") == 0 + || strcmp (*argv, "apath") == 0) { + selector = *argv; + if (next_arg (&argc, &argv) != 0) { + g_string_printf (nmc->return_text, _("Error: %s argument is missing."), *(argv-1)); + nmc->return_value = NMC_RESULT_ERROR_USER_INPUT; + goto finish; + } + } + + /* Find connection by id, uuid, path or apath */ + con = nmc_find_connection (nmc->connections, selector, *argv, &pos); + if (!con) { + acon = find_active_connection (active_cons, nmc->connections, selector, *argv, NULL); + if (acon) + con = NM_CONNECTION (nm_active_connection_get_connection (acon)); + } + + if (!con && !acon) { + g_string_printf (nmc->return_text, _("Error: %s - no such connection profile."), *argv); + nmc->return_value = NMC_RESULT_ERROR_NOT_FOUND; + goto finish; + } + + /* Print connection details: + * Usually we have both static and active connection. + * But when a connection is private to a user, another user + * may see only the active connection. + */ + + /* Filter only active connections */ + if (!acon) + acon = get_ac_for_connection (active_cons, con); + if (active_only && !acon) { + next_arg (&argc, &argv); + continue; + } + + /* Show an empty line between connections */ + if (new_line) + g_print ("\n"); + + /* Show profile configuration */ + if (without_fields || profile_flds) { + if (con) { + nmc->required_fields = profile_flds; + if (show_secrets) + update_secrets_in_connection (NM_REMOTE_CONNECTION (con), con); + res = nmc_connection_profile_details (con, nmc, show_secrets); + nmc->required_fields = NULL; + if (!res) + goto finish; + } + } + + /* If the profile is active, print also active details */ + if (without_fields || active_flds) { + if (acon) { + nmc->required_fields = active_flds; + res = nmc_active_connection_details (acon, nmc); + nmc->required_fields = NULL; + if (!res) + goto finish; + } + } + new_line = TRUE; + + /* Take next argument. + * But for pos != NULL we have more connections of the same name, + * so process the same argument again. + */ + if (!pos) + next_arg (&argc, &argv); + } + } + +finish: + if (err) { + g_string_printf (nmc->return_text, _("Error: %s."), err->message); + nmc->return_value = NMC_RESULT_ERROR_USER_INPUT; + g_error_free (err); + } + g_free (profile_flds); + g_free (active_flds); + return nmc->return_value; +} + +static NMActiveConnection * +get_default_active_connection (NmCli *nmc, NMDevice **device) +{ + NMActiveConnection *default_ac = NULL; + NMDevice *non_default_device = NULL; + NMActiveConnection *non_default_ac = NULL; + const GPtrArray *connections; + int i; + + g_return_val_if_fail (nmc != NULL, NULL); + g_return_val_if_fail (device != NULL, NULL); + g_return_val_if_fail (*device == NULL, NULL); + + connections = nm_client_get_active_connections (nmc->client); + for (i = 0; i < connections->len; i++) { + NMActiveConnection *candidate = g_ptr_array_index (connections, i); + const GPtrArray *devices; + + devices = nm_active_connection_get_devices (candidate); + if (!devices->len) + continue; + + if (nm_active_connection_get_default (candidate)) { + if (!default_ac) { + *device = g_ptr_array_index (devices, 0); + default_ac = candidate; + } + } else { + if (!non_default_ac) { + non_default_device = g_ptr_array_index (devices, 0); + non_default_ac = candidate; + } + } + } + + /* Prefer the default connection if one exists, otherwise return the first + * non-default connection. + */ + if (!default_ac && non_default_ac) { + default_ac = non_default_ac; + *device = non_default_device; + } + return default_ac; +} + +/* Find a device to activate the connection on. + * IN: connection: connection to activate + * iface: device interface name to use (optional) + * ap: access point to use (optional; valid just for 802-11-wireless) + * nsp: Network Service Provider to use (option; valid only for wimax) + * OUT: device: found device + * spec_object: specific_object path of NMAccessPoint + * RETURNS: TRUE when a device is found, FALSE otherwise. + */ +static gboolean +find_device_for_connection (NmCli *nmc, + NMConnection *connection, + const char *iface, + const char *ap, + const char *nsp, + NMDevice **device, + const char **spec_object, + GError **error) +{ + NMSettingConnection *s_con; + const char *con_type; + int i, j; + + g_return_val_if_fail (nmc != NULL, FALSE); + g_return_val_if_fail (device != NULL && *device == NULL, FALSE); + g_return_val_if_fail (spec_object != NULL && *spec_object == NULL, FALSE); + g_return_val_if_fail (error == NULL || *error == NULL, FALSE); + + s_con = nm_connection_get_setting_connection (connection); + g_assert (s_con); + con_type = nm_setting_connection_get_connection_type (s_con); + + if (strcmp (con_type, NM_SETTING_VPN_SETTING_NAME) == 0) { + /* VPN connections */ + NMActiveConnection *active = NULL; + if (iface) { + *device = nm_client_get_device_by_iface (nmc->client, iface); + if (*device) + active = nm_device_get_active_connection (*device); + + if (!active) { + g_set_error (error, NMCLI_ERROR, 0, _("no active connection on device '%s'"), iface); + return FALSE; + } + *spec_object = nm_object_get_path (NM_OBJECT (active)); + return TRUE; + } else { + active = get_default_active_connection (nmc, device); + if (!active) { + g_set_error_literal (error, NMCLI_ERROR, 0, _("no active connection or device")); + return FALSE; + } + *spec_object = nm_object_get_path (NM_OBJECT (active)); + return TRUE; + } + } else { + /* Other connections */ + NMDevice *found_device = NULL; + const GPtrArray *devices = nm_client_get_devices (nmc->client); + + for (i = 0; i < devices->len && !found_device; i++) { + NMDevice *dev = g_ptr_array_index (devices, i); + + if (iface) { + const char *dev_iface = nm_device_get_iface (dev); + if ( !g_strcmp0 (dev_iface, iface) + && nm_device_connection_compatible (dev, connection, NULL)) { + found_device = dev; + } + } else { + if (nm_device_connection_compatible (dev, connection, NULL)) { + found_device = dev; + } + } + + if (found_device && ap && !strcmp (con_type, NM_SETTING_WIRELESS_SETTING_NAME) && NM_IS_DEVICE_WIFI (dev)) { + char *bssid_up = g_ascii_strup (ap, -1); + const GPtrArray *aps = nm_device_wifi_get_access_points (NM_DEVICE_WIFI (dev)); + found_device = NULL; /* Mark as not found; set to the device again later, only if AP matches */ + + for (j = 0; j < aps->len; j++) { + NMAccessPoint *candidate_ap = g_ptr_array_index (aps, j); + const char *candidate_bssid = nm_access_point_get_bssid (candidate_ap); + + if (!strcmp (bssid_up, candidate_bssid)) { + found_device = dev; + *spec_object = nm_object_get_path (NM_OBJECT (candidate_ap)); + break; + } + } + g_free (bssid_up); + } + +#if WITH_WIMAX + if ( found_device + && nsp + && !strcmp (con_type, NM_SETTING_WIMAX_SETTING_NAME) + && NM_IS_DEVICE_WIMAX (dev)) { + const GPtrArray *nsps = nm_device_wimax_get_nsps (NM_DEVICE_WIMAX (dev)); + found_device = NULL; /* Mark as not found; set to the device again later, only if NSP matches */ + + for (j = 0; j < nsps->len; j++) { + NMWimaxNsp *candidate_nsp = g_ptr_array_index (nsps, j); + const char *candidate_name = nm_wimax_nsp_get_name (candidate_nsp); + + if (!strcmp (nsp, candidate_name)) { + found_device = dev; + *spec_object = nm_object_get_path (NM_OBJECT (candidate_nsp)); + break; + } + } + } +#endif + } + + if (found_device) { + *device = found_device; + return TRUE; + } else { + if (iface) + g_set_error (error, NMCLI_ERROR, 0, _("device '%s' not compatible with connection '%s'"), + iface, nm_setting_connection_get_id (s_con)); + else + g_set_error (error, NMCLI_ERROR, 0, _("no device found for connection '%s'"), + nm_setting_connection_get_id (s_con)); + return FALSE; + } + } +} + +static const char * +vpn_connection_state_reason_to_string (NMVpnConnectionStateReason reason) +{ + switch (reason) { + case NM_VPN_CONNECTION_STATE_REASON_UNKNOWN: + return _("unknown reason"); + case NM_VPN_CONNECTION_STATE_REASON_NONE: + return _("none"); + case NM_VPN_CONNECTION_STATE_REASON_USER_DISCONNECTED: + return _("the user was disconnected"); + case NM_VPN_CONNECTION_STATE_REASON_DEVICE_DISCONNECTED: + return _("the base network connection was interrupted"); + case NM_VPN_CONNECTION_STATE_REASON_SERVICE_STOPPED: + return _("the VPN service stopped unexpectedly"); + case NM_VPN_CONNECTION_STATE_REASON_IP_CONFIG_INVALID: + return _("the VPN service returned invalid configuration"); + case NM_VPN_CONNECTION_STATE_REASON_CONNECT_TIMEOUT: + return _("the connection attempt timed out"); + case NM_VPN_CONNECTION_STATE_REASON_SERVICE_START_TIMEOUT: + return _("the VPN service did not start in time"); + case NM_VPN_CONNECTION_STATE_REASON_SERVICE_START_FAILED: + return _("the VPN service failed to start"); + case NM_VPN_CONNECTION_STATE_REASON_NO_SECRETS: + return _("no valid VPN secrets"); + case NM_VPN_CONNECTION_STATE_REASON_LOGIN_FAILED: + return _("invalid VPN secrets"); + case NM_VPN_CONNECTION_STATE_REASON_CONNECTION_REMOVED: + return _("the connection was removed"); + default: + return _("unknown"); + } +} + +static void +device_state_cb (NMDevice *device, GParamSpec *pspec, gpointer user_data) +{ + NmCli *nmc = (NmCli *) user_data; + NMActiveConnection *active; + NMDeviceState state; + NMActiveConnectionState ac_state; + + active = nm_device_get_active_connection (device); + state = nm_device_get_state (device); + + ac_state = active ? nm_active_connection_get_state (active) : NM_ACTIVE_CONNECTION_STATE_UNKNOWN; + + if (ac_state == NM_ACTIVE_CONNECTION_STATE_ACTIVATED) { + if (nmc->print_output == NMC_PRINT_PRETTY) + nmc_terminal_erase_line (); + g_print (_("Connection successfully activated (D-Bus active path: %s)\n"), + nm_object_get_path (NM_OBJECT (active))); + quit (); + } else if ( ac_state == NM_ACTIVE_CONNECTION_STATE_ACTIVATING + && state == NM_DEVICE_STATE_IP_CONFIG) { + if (nmc->print_output == NMC_PRINT_PRETTY) + nmc_terminal_erase_line (); + g_print (_("Connection successfully activated (master waiting for slaves) (D-Bus active path: %s)\n"), + nm_object_get_path (NM_OBJECT (active))); + quit (); + } else if (active && ac_state != NM_ACTIVE_CONNECTION_STATE_ACTIVATING) { + g_string_printf (nmc->return_text, _("Error: Connection activation failed.")); + nmc->return_value = NMC_RESULT_ERROR_CON_ACTIVATION; + quit (); + } +} + +static void +active_connection_state_cb (NMActiveConnection *active, GParamSpec *pspec, gpointer user_data) +{ + NmCli *nmc = (NmCli *) user_data; + NMActiveConnectionState state; + + state = nm_active_connection_get_state (active); + + if (state == NM_ACTIVE_CONNECTION_STATE_ACTIVATED) { + if (nmc->print_output == NMC_PRINT_PRETTY) + nmc_terminal_erase_line (); + g_print (_("Connection successfully activated (D-Bus active path: %s)\n"), + nm_object_get_path (NM_OBJECT (active))); + g_object_unref (active); + quit (); + } else if (state == NM_ACTIVE_CONNECTION_STATE_DEACTIVATED) { + g_string_printf (nmc->return_text, _("Error: Connection activation failed.")); + nmc->return_value = NMC_RESULT_ERROR_CON_ACTIVATION; + g_object_unref (active); + quit (); + } else if (state == NM_ACTIVE_CONNECTION_STATE_ACTIVATING) { + /* activating master connection does not automatically activate any slaves, so their + * active connection state will not progress beyond ACTIVATING state. + * Monitor the device instead. */ + const GPtrArray *devices; + NMDevice *device; + + if (nmc->secret_agent) { + NMRemoteConnection *connection = nm_active_connection_get_connection (active); + + nm_secret_agent_simple_enable (NM_SECRET_AGENT_SIMPLE (nmc->secret_agent), + nm_connection_get_path (NM_CONNECTION (connection))); + } + + devices = nm_active_connection_get_devices (active); + device = devices->len ? g_ptr_array_index (devices, 0) : NULL; + if ( device + && ( NM_IS_DEVICE_BOND (device) + || NM_IS_DEVICE_TEAM (device) + || NM_IS_DEVICE_BRIDGE (device))) { + g_signal_handlers_disconnect_by_func (active, G_CALLBACK (active_connection_state_cb), nmc); + g_signal_connect (device, "notify::" NM_DEVICE_STATE, G_CALLBACK (device_state_cb), nmc); + + device_state_cb (device, NULL, nmc); + } + } +} + +static void +vpn_connection_state_cb (NMVpnConnection *vpn, + NMVpnConnectionState state, + NMVpnConnectionStateReason reason, + gpointer user_data) +{ + NmCli *nmc = (NmCli *) user_data; + + switch (state) { + case NM_VPN_CONNECTION_STATE_PREPARE: + case NM_VPN_CONNECTION_STATE_NEED_AUTH: + case NM_VPN_CONNECTION_STATE_CONNECT: + case NM_VPN_CONNECTION_STATE_IP_CONFIG_GET: + /* no operation */ + break; + + case NM_VPN_CONNECTION_STATE_ACTIVATED: + if (nmc->print_output == NMC_PRINT_PRETTY) + nmc_terminal_erase_line (); + g_print (_("VPN connection successfully activated (D-Bus active path: %s)\n"), + nm_object_get_path (NM_OBJECT (vpn))); + g_object_unref (vpn); + quit (); + break; + + case NM_VPN_CONNECTION_STATE_FAILED: + case NM_VPN_CONNECTION_STATE_DISCONNECTED: + g_string_printf (nmc->return_text, _("Error: Connection activation failed: %s."), + vpn_connection_state_reason_to_string (reason)); + nmc->return_value = NMC_RESULT_ERROR_CON_ACTIVATION; + g_object_unref (vpn); + quit (); + break; + + default: + break; + } +} + +static gboolean +timeout_cb (gpointer user_data) +{ + /* Time expired -> exit nmcli */ + + NmCli *nmc = (NmCli *) user_data; + + g_string_printf (nmc->return_text, _("Error: Timeout %d sec expired."), nmc->timeout); + nmc->return_value = NMC_RESULT_ERROR_TIMEOUT_EXPIRED; + quit (); + return FALSE; +} + +static gboolean +progress_cb (gpointer user_data) +{ + const char *str = (const char *) user_data; + + nmc_terminal_show_progress (str); + + return TRUE; +} + +static gboolean +progress_device_cb (gpointer user_data) +{ + NMDevice *device = (NMDevice *) user_data; + + nmc_terminal_show_progress (device ? nmc_device_state_to_string (nm_device_get_state (device)) : ""); + + return TRUE; +} + +static gboolean +progress_vpn_cb (gpointer user_data) +{ + NMVpnConnection *vpn = (NMVpnConnection *) user_data; + const char *str; + + str = NM_IS_VPN_CONNECTION (vpn) ? + vpn_connection_state_to_string (nm_vpn_connection_get_vpn_state (vpn)) : + ""; + + nmc_terminal_show_progress (str); + + return TRUE; +} + +typedef struct { + NmCli *nmc; + NMDevice *device; +} ActivateConnectionInfo; + +static void +activate_connection_cb (GObject *client, GAsyncResult *result, gpointer user_data) +{ + ActivateConnectionInfo *info = (ActivateConnectionInfo *) user_data; + NmCli *nmc = info->nmc; + NMDevice *device = info->device; + NMActiveConnection *active; + NMActiveConnectionState state; + const GPtrArray *ac_devs; + GError *error = NULL; + + active = nm_client_activate_connection_finish (NM_CLIENT (client), result, &error); + + if (error) { + g_string_printf (nmc->return_text, _("Error: Connection activation failed: %s"), + error->message); + g_error_free (error); + nmc->return_value = NMC_RESULT_ERROR_CON_ACTIVATION; + quit (); + } else { + state = nm_active_connection_get_state (active); + if (!device) { + /* device could be NULL for virtual devices. Fill it here. */ + ac_devs = nm_active_connection_get_devices (active); + info->device = device = ac_devs->len > 0 ? g_ptr_array_index (ac_devs, 0) : NULL; + } + + if (nmc->nowait_flag || state == NM_ACTIVE_CONNECTION_STATE_ACTIVATED) { + /* User doesn't want to wait or already activated */ + if (state == NM_ACTIVE_CONNECTION_STATE_ACTIVATED) { + if (nmc->print_output == NMC_PRINT_PRETTY) + nmc_terminal_erase_line (); + g_print (_("Connection successfully activated (D-Bus active path: %s)\n"), + nm_object_get_path (NM_OBJECT (active))); + } + g_object_unref (active); + quit (); + } else { + if (NM_IS_VPN_CONNECTION (active)) { + /* Monitor VPN state */ + g_signal_connect (G_OBJECT (active), "vpn-state-changed", G_CALLBACK (vpn_connection_state_cb), nmc); + + /* Start progress indication showing VPN states */ + if (nmc->print_output == NMC_PRINT_PRETTY) { + if (progress_id) + g_source_remove (progress_id); + progress_id = g_timeout_add (120, progress_vpn_cb, NM_VPN_CONNECTION (active)); + } + } else { + g_signal_connect (active, "notify::state", G_CALLBACK (active_connection_state_cb), nmc); + active_connection_state_cb (active, NULL, nmc); + + /* Start progress indication showing device states */ + if (nmc->print_output == NMC_PRINT_PRETTY) { + if (progress_id) + g_source_remove (progress_id); + progress_id = g_timeout_add (120, progress_device_cb, device); + } + } + + /* Start timer not to loop forever when signals are not emitted */ + g_timeout_add_seconds (nmc->timeout, timeout_cb, nmc); + } + } + g_free (info); +} + +/** + * parse_passwords: + * @passwd_file: file with passwords to parse + * @error: location to store error, or %NULL + * + * Parse passwords given in @passwd_file and insert them into a hash table. + * Example of @passwd_file contents: + * wifi.psk:tajne heslo + * 802-1x.password:krakonos + * 802-11-wireless-security:leap-password:my leap password + * + * Returns: hash table with parsed passwords, or %NULL on an error + */ +static GHashTable * +parse_passwords (const char *passwd_file, GError **error) +{ + GHashTable *pwds_hash; + char *contents = NULL; + gsize len = 0; + GError *local_err = NULL; + char **lines, **iter; + char *pwd_spec, *pwd, *prop; + const char *setting; + + pwds_hash = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free); + + if (!passwd_file) + return pwds_hash; + + /* Read the passwords file */ + if (!g_file_get_contents (passwd_file, &contents, &len, &local_err)) { + g_set_error (error, NMCLI_ERROR, NMC_RESULT_ERROR_USER_INPUT, + _("failed to read passwd-file '%s': %s"), + passwd_file, local_err->message); + g_error_free (local_err); + g_hash_table_destroy (pwds_hash); + return NULL; + } + + lines = nmc_strsplit_set (contents, "\r\n", -1); + for (iter = lines; *iter; iter++) { + pwd = strchr (*iter, ':'); + if (!pwd) { + g_set_error (error, NMCLI_ERROR, NMC_RESULT_ERROR_USER_INPUT, + _("missing colon in 'password' entry '%s'"), *iter); + goto failure; + } + *(pwd++) = '\0'; + + prop = strchr (*iter, '.'); + if (!prop) { + g_set_error (error, NMCLI_ERROR, NMC_RESULT_ERROR_USER_INPUT, + _("missing dot in 'password' entry '%s'"), *iter); + goto failure; + } + *(prop++) = '\0'; + + setting = *iter; + while (g_ascii_isspace (*setting)) + setting++; + /* Accept wifi-sec or wifi instead of cumbersome '802-11-wireless-security' */ + if (!strcmp (setting, "wifi-sec") || !strcmp (setting, "wifi")) + setting = NM_SETTING_WIRELESS_SECURITY_SETTING_NAME; + if (nm_setting_lookup_type (setting) == G_TYPE_INVALID) { + g_set_error (error, NMCLI_ERROR, NMC_RESULT_ERROR_USER_INPUT, + _("invalid setting name in 'password' entry '%s'"), setting); + goto failure; + } + + pwd_spec = g_strdup_printf ("%s.%s", setting, prop); + g_hash_table_insert (pwds_hash, pwd_spec, g_strdup (pwd)); + } + g_strfreev (lines); + g_free (contents); + return pwds_hash; + +failure: + g_strfreev (lines); + g_free (contents); + g_hash_table_destroy (pwds_hash); + return NULL; +} + + + +static gboolean +nmc_activate_connection (NmCli *nmc, + NMConnection *connection, + const char *ifname, + const char *ap, + const char *nsp, + const char *pwds, + GAsyncReadyCallback callback, + GError **error) +{ + ActivateConnectionInfo *info; + + GHashTable *pwds_hash; + NMDevice *device = NULL; + const char *spec_object = NULL; + gboolean device_found; + GError *local = NULL; + + g_return_val_if_fail (nmc != NULL, FALSE); + g_return_val_if_fail (error == NULL || *error == NULL, FALSE); + + if (connection) { + device_found = find_device_for_connection (nmc, connection, ifname, ap, nsp, &device, &spec_object, &local); + + /* Virtual connection may not have their interfaces created yet */ + if (!device_found && !nm_connection_is_virtual (connection)) { + g_set_error (error, NMCLI_ERROR, NMC_RESULT_ERROR_CON_ACTIVATION, + "%s", local && local->message ? local->message : _("unknown error")); + g_clear_error (&local); + return FALSE; + } + g_clear_error (&local); + } else if (ifname) { + device = nm_client_get_device_by_iface (nmc->client, ifname); + if (!device) { + g_set_error (error, NMCLI_ERROR, NMC_RESULT_ERROR_NOT_FOUND, + _("unknown device '%s'."), ifname); + return FALSE; + } + } else { + g_set_error_literal (error, NMCLI_ERROR, NMC_RESULT_ERROR_NOT_FOUND, + _("neither a valid connection nor device given")); + return FALSE; + } + + /* Parse passwords given in passwords file */ + pwds_hash = parse_passwords (pwds, &local); + if (local) { + g_propagate_error (error, local); + return FALSE; + } + if (nmc->pwds_hash) + g_hash_table_destroy (nmc->pwds_hash); + nmc->pwds_hash = pwds_hash; + + /* Create secret agent */ + nmc->secret_agent = nm_secret_agent_simple_new ("nmcli-connect"); + if (nmc->secret_agent) { + g_signal_connect (nmc->secret_agent, "request-secrets", G_CALLBACK (nmc_secrets_requested), nmc); + if (connection) { + nm_secret_agent_simple_enable (NM_SECRET_AGENT_SIMPLE (nmc->secret_agent), + nm_object_get_path (NM_OBJECT (connection))); + } + } + + info = g_malloc0 (sizeof (ActivateConnectionInfo)); + info->nmc = nmc; + info->device = device; + + nm_client_activate_connection_async (nmc->client, + connection, + device, + spec_object, + NULL, + callback, + info); + return TRUE; +} + +static NMCResultCode +do_connection_up (NmCli *nmc, int argc, char **argv) +{ + NMConnection *connection = NULL; + const char *ifname = NULL; + const char *ap = NULL; + const char *nsp = NULL; + const char *pwds = NULL; + GError *error = NULL; + const char *selector = NULL; + const char *name = NULL; + char *line = NULL; + + /* + * Set default timeout for connection activation. + * Activation can take quite a long time, use 90 seconds. + */ + if (nmc->timeout == -1) + nmc->timeout = 90; + + if (argc == 0) { + if (nmc->ask) { + line = nmc_readline (PROMPT_CONNECTION); + name = line ? line : ""; + } + } else if (strcmp (*argv, "ifname") != 0) { + if ( strcmp (*argv, "id") == 0 + || strcmp (*argv, "uuid") == 0 + || strcmp (*argv, "path") == 0) { + + selector = *argv; + if (next_arg (&argc, &argv) != 0) { + g_string_printf (nmc->return_text, _("Error: %s argument is missing."), selector); + nmc->return_value = NMC_RESULT_ERROR_USER_INPUT; + goto error; + } + name = *argv; + } + name = *argv; + next_arg (&argc, &argv); + } + + if (name) { + connection = nmc_find_connection (nmc->connections, selector, name, NULL); + if (!connection) { + g_string_printf (nmc->return_text, _("Error: Connection '%s' does not exist."), name); + nmc->return_value = NMC_RESULT_ERROR_NOT_FOUND; + goto error; + } + } + + while (argc > 0) { + if (strcmp (*argv, "ifname") == 0) { + if (next_arg (&argc, &argv) != 0) { + g_string_printf (nmc->return_text, _("Error: %s argument is missing."), *(argv-1)); + nmc->return_value = NMC_RESULT_ERROR_USER_INPUT; + goto error; + } + + ifname = *argv; + } + else if (strcmp (*argv, "ap") == 0) { + if (next_arg (&argc, &argv) != 0) { + g_string_printf (nmc->return_text, _("Error: %s argument is missing."), *(argv-1)); + nmc->return_value = NMC_RESULT_ERROR_USER_INPUT; + goto error; + } + + ap = *argv; + } +#if WITH_WIMAX + else if (strcmp (*argv, "nsp") == 0) { + if (next_arg (&argc, &argv) != 0) { + g_string_printf (nmc->return_text, _("Error: %s argument is missing."), *(argv-1)); + nmc->return_value = NMC_RESULT_ERROR_USER_INPUT; + goto error; + } + + nsp = *argv; + } +#endif + else if (strcmp (*argv, "passwd-file") == 0) { + if (next_arg (&argc, &argv) != 0) { + g_string_printf (nmc->return_text, _("Error: %s argument is missing."), *(argv-1)); + nmc->return_value = NMC_RESULT_ERROR_USER_INPUT; + goto error; + } + + pwds = *argv; + } + else { + g_printerr (_("Unknown parameter: %s\n"), *argv); + } + + argc--; + argv++; + } + + /* Use nowait_flag instead of should_wait because exiting has to be postponed till + * active_connection_state_cb() is called. That gives NM time to check our permissions + * and we can follow activation progress. + */ + nmc->nowait_flag = (nmc->timeout == 0); + nmc->should_wait = TRUE; + + if (!nmc_activate_connection (nmc, connection, ifname, ap, nsp, pwds, activate_connection_cb, &error)) { + g_string_printf (nmc->return_text, _("Error: %s."), + error ? error->message : _("unknown error")); + nmc->return_value = error ? error->code : NMC_RESULT_ERROR_CON_ACTIVATION; + g_clear_error (&error); + goto error; + } + + /* Start progress indication */ + if (nmc->print_output == NMC_PRINT_PRETTY) + progress_id = g_timeout_add (120, progress_cb, _("preparing")); + + g_free (line); + return nmc->return_value; +error: + nmc->should_wait = FALSE; + g_free (line); + return nmc->return_value; +} + +typedef struct { + NmCli *nmc; + GSList *queue; + guint timeout_id; +} DeactivateConnectionInfo; + +static void deactivate_connection_info_finish (DeactivateConnectionInfo *info, + NMActiveConnection *active); + +static gboolean +down_timeout_cb (gpointer user_data) +{ + DeactivateConnectionInfo *info = user_data; + + timeout_cb (info->nmc); + deactivate_connection_info_finish (info, NULL); + return G_SOURCE_REMOVE; +} + +static void +down_active_connection_state_cb (NMActiveConnection *active, + GParamSpec *pspec, + DeactivateConnectionInfo *info) +{ + if (nm_active_connection_get_state (active) < NM_ACTIVE_CONNECTION_STATE_DEACTIVATED) + return; + + if (info->nmc->print_output == NMC_PRINT_PRETTY) + nmc_terminal_erase_line (); + g_print (_("Connection '%s' successfully deactivated (D-Bus active path: %s)\n"), + nm_active_connection_get_id (active), nm_object_get_path (NM_OBJECT (active))); + + deactivate_connection_info_finish (info, active); +} + +static void +destroy_queue_element (gpointer data) +{ + g_signal_handlers_disconnect_matched (data, G_SIGNAL_MATCH_FUNC, 0, 0, 0, + down_active_connection_state_cb, NULL); + g_object_unref (data); +} + +static void +deactivate_connection_info_finish (DeactivateConnectionInfo *info, + NMActiveConnection *active) +{ + if (active) { + info->queue = g_slist_remove (info->queue, active); + g_signal_handlers_disconnect_by_func (active, + down_active_connection_state_cb, + info); + g_object_unref (active); + } else { + g_slist_free_full (info->queue, destroy_queue_element); + info->queue = NULL; + } + + if (info->queue) + return; + + if (info->timeout_id) + g_source_remove (info->timeout_id); + g_slice_free (DeactivateConnectionInfo, info); + quit (); +} + +static NMCResultCode +do_connection_down (NmCli *nmc, int argc, char **argv) +{ + NMActiveConnection *active; + DeactivateConnectionInfo *info = NULL; + const GPtrArray *active_cons; + GSList *queue = NULL, *iter; + char *line = NULL; + char **arg_arr = NULL; + char **arg_ptr = argv; + int arg_num = argc; + int idx = 0; + + if (argc == 0) { + if (nmc->ask) { + line = nmc_readline (PROMPT_CONNECTION); + nmc_string_to_arg_array (line, "", &arg_arr, &arg_num); + arg_ptr = arg_arr; + } + if (arg_num == 0) { + g_string_printf (nmc->return_text, _("Error: No connection specified.")); + nmc->return_value = NMC_RESULT_ERROR_USER_INPUT; + goto error; + } + } + + if (nmc->timeout == -1) + nmc->timeout = 10; + + /* Get active connections */ + active_cons = nm_client_get_active_connections (nmc->client); + while (arg_num > 0) { + const char *selector = NULL; + + if ( strcmp (*arg_ptr, "id") == 0 + || strcmp (*arg_ptr, "uuid") == 0 + || strcmp (*arg_ptr, "path") == 0 + || strcmp (*arg_ptr, "apath") == 0) { + + selector = *arg_ptr; + if (next_arg (&arg_num, &arg_ptr) != 0) { + g_string_printf (nmc->return_text, _("Error: %s argument is missing."), selector); + nmc->return_value = NMC_RESULT_ERROR_USER_INPUT; + goto error; + } + } + + active = find_active_connection (active_cons, nmc->connections, selector, *arg_ptr, &idx); + if (active) + queue = g_slist_prepend (queue, active); + else { + g_printerr (_("Error: '%s' is not an active connection.\n"), *arg_ptr); + g_string_printf (nmc->return_text, _("Error: not all active connections found.")); + nmc->return_value = NMC_RESULT_ERROR_NOT_FOUND; + } + + if (idx == 0) + next_arg (&arg_num, &arg_ptr); + } + + if (!queue) { + g_string_printf (nmc->return_text, _("Error: no active connection provided.")); + nmc->return_value = NMC_RESULT_ERROR_NOT_FOUND; + goto error; + } + + queue = g_slist_reverse (queue); + + if (nmc->timeout > 0) { + nmc->should_wait = TRUE; + + info = g_slice_new0 (DeactivateConnectionInfo); + info->nmc = nmc; + info->timeout_id = g_timeout_add_seconds (nmc->timeout, down_timeout_cb, info); + } + + for (iter = queue; iter; iter = g_slist_next (iter)) { + active = iter->data; + + if (info) { + info->queue = g_slist_prepend (info->queue, g_object_ref (active)); + g_signal_connect (active, + "notify::" NM_ACTIVE_CONNECTION_STATE, + G_CALLBACK (down_active_connection_state_cb), + info); + } + + /* Now deactivate the connection */ + nm_client_deactivate_connection (nmc->client, active, NULL, NULL); + } + +error: + g_strfreev (arg_arr); + g_slist_free (queue); + return nmc->return_value; +} + +/*----------------------------------------------------------------------------*/ + +typedef struct NameItem { + const char *name; + const char *alias; + const struct NameItem *settings; + gboolean mandatory; +} NameItem; + +static const NameItem nmc_generic_settings [] = { + { NM_SETTING_CONNECTION_SETTING_NAME, NULL, NULL, TRUE }, + { NM_SETTING_IP4_CONFIG_SETTING_NAME, NULL, NULL, FALSE }, + { NM_SETTING_IP6_CONFIG_SETTING_NAME, NULL, NULL, FALSE }, + { NULL, NULL, NULL, FALSE } +}; + +static const NameItem nmc_ethernet_settings [] = { + { NM_SETTING_CONNECTION_SETTING_NAME, NULL, NULL, TRUE }, + { NM_SETTING_WIRED_SETTING_NAME, "ethernet", NULL, TRUE }, + { NM_SETTING_802_1X_SETTING_NAME, NULL, NULL, FALSE }, + { NM_SETTING_IP4_CONFIG_SETTING_NAME, NULL, NULL, FALSE }, + { NM_SETTING_IP6_CONFIG_SETTING_NAME, NULL, NULL, FALSE }, + { NM_SETTING_DCB_SETTING_NAME, NULL, NULL, FALSE }, + { NULL, NULL, NULL, FALSE } +}; + +static const NameItem nmc_infiniband_settings [] = { + { NM_SETTING_CONNECTION_SETTING_NAME, NULL, NULL, TRUE }, + { NM_SETTING_INFINIBAND_SETTING_NAME, NULL, NULL, TRUE }, + { NM_SETTING_IP4_CONFIG_SETTING_NAME, NULL, NULL, FALSE }, + { NM_SETTING_IP6_CONFIG_SETTING_NAME, NULL, NULL, FALSE }, + { NULL, NULL, NULL, FALSE } +}; + +static const NameItem nmc_wifi_settings [] = { + { NM_SETTING_CONNECTION_SETTING_NAME, NULL, NULL, TRUE }, + { NM_SETTING_WIRELESS_SETTING_NAME, "wifi", NULL, TRUE }, + { NM_SETTING_WIRELESS_SECURITY_SETTING_NAME, "wifi-sec", NULL, FALSE }, + { NM_SETTING_802_1X_SETTING_NAME, NULL, NULL, FALSE }, + { NM_SETTING_IP4_CONFIG_SETTING_NAME, NULL, NULL, FALSE }, + { NM_SETTING_IP6_CONFIG_SETTING_NAME, NULL, NULL, FALSE }, + { NULL, NULL, NULL, FALSE } +}; + +static const NameItem nmc_wimax_settings [] = { + { NM_SETTING_CONNECTION_SETTING_NAME, NULL, NULL, TRUE }, + { NM_SETTING_WIMAX_SETTING_NAME, NULL, NULL, TRUE }, + { NM_SETTING_IP4_CONFIG_SETTING_NAME, NULL, NULL, FALSE }, + { NM_SETTING_IP6_CONFIG_SETTING_NAME, NULL, NULL, FALSE }, + { NULL, NULL, NULL, FALSE } +}; + +static const NameItem nmc_gsm_settings [] = { + { NM_SETTING_CONNECTION_SETTING_NAME, NULL, NULL, TRUE }, + { NM_SETTING_GSM_SETTING_NAME, NULL, NULL, TRUE }, + { NM_SETTING_SERIAL_SETTING_NAME, NULL, NULL, FALSE }, + { NM_SETTING_IP4_CONFIG_SETTING_NAME, NULL, NULL, FALSE }, + { NULL, NULL, NULL, FALSE } +}; + +static const NameItem nmc_cdma_settings [] = { + { NM_SETTING_CONNECTION_SETTING_NAME, NULL, NULL, TRUE }, + { NM_SETTING_CDMA_SETTING_NAME, NULL, NULL, TRUE }, + { NM_SETTING_SERIAL_SETTING_NAME, NULL, NULL, FALSE }, + { NM_SETTING_IP4_CONFIG_SETTING_NAME, NULL, NULL, FALSE }, + { NULL, NULL, NULL, FALSE } +}; + +static const NameItem nmc_bluetooth_settings [] = { + { NM_SETTING_CONNECTION_SETTING_NAME, NULL, NULL, TRUE }, + { NM_SETTING_BLUETOOTH_SETTING_NAME, NULL, NULL, TRUE }, + { NM_SETTING_IP4_CONFIG_SETTING_NAME, NULL, NULL, FALSE }, + { NM_SETTING_IP6_CONFIG_SETTING_NAME, NULL, NULL, FALSE }, + { NULL, NULL, NULL, FALSE } +}; + +static const NameItem nmc_adsl_settings [] = { + { NM_SETTING_CONNECTION_SETTING_NAME, NULL, NULL, TRUE }, + { NM_SETTING_ADSL_SETTING_NAME, NULL, NULL, TRUE }, + { NM_SETTING_IP4_CONFIG_SETTING_NAME, NULL, NULL, FALSE }, + { NM_SETTING_IP6_CONFIG_SETTING_NAME, NULL, NULL, FALSE }, + { NULL, NULL, NULL, FALSE } +}; + +/* PPPoE is a base connection type from historical reasons. + * See libnm-core/nm-setting.c:_nm_setting_is_base_type() + */ +static const NameItem nmc_pppoe_settings [] = { + { NM_SETTING_CONNECTION_SETTING_NAME, NULL, NULL, TRUE }, + { NM_SETTING_WIRED_SETTING_NAME, "ethernet", NULL, TRUE }, + { NM_SETTING_PPPOE_SETTING_NAME, NULL, NULL, TRUE }, + { NM_SETTING_PPP_SETTING_NAME, NULL, NULL, FALSE }, + { NM_SETTING_802_1X_SETTING_NAME, NULL, NULL, FALSE }, + { NM_SETTING_IP4_CONFIG_SETTING_NAME, NULL, NULL, FALSE }, + { NM_SETTING_IP6_CONFIG_SETTING_NAME, NULL, NULL, FALSE }, + { NULL, NULL, NULL, FALSE } +}; + +static const NameItem nmc_olpc_mesh_settings [] = { + { NM_SETTING_CONNECTION_SETTING_NAME, NULL, NULL, TRUE }, + { NM_SETTING_OLPC_MESH_SETTING_NAME, "olpc-mesh", NULL, TRUE }, + { NM_SETTING_IP4_CONFIG_SETTING_NAME, NULL, NULL, FALSE }, + { NM_SETTING_IP6_CONFIG_SETTING_NAME, NULL, NULL, FALSE }, + { NULL, NULL, NULL, FALSE } +}; + +static const NameItem nmc_vpn_settings [] = { + { NM_SETTING_CONNECTION_SETTING_NAME, NULL, NULL, TRUE }, + { NM_SETTING_VPN_SETTING_NAME, NULL, NULL, TRUE }, + { NM_SETTING_IP4_CONFIG_SETTING_NAME, NULL, NULL, FALSE }, + { NM_SETTING_IP6_CONFIG_SETTING_NAME, NULL, NULL, FALSE }, + { NULL, NULL, NULL, FALSE } +}; + +static const NameItem nmc_vlan_settings [] = { + { NM_SETTING_CONNECTION_SETTING_NAME, NULL, NULL, TRUE }, + { NM_SETTING_WIRED_SETTING_NAME, "ethernet", NULL, FALSE }, + { NM_SETTING_VLAN_SETTING_NAME, NULL, NULL, TRUE }, + { NM_SETTING_IP4_CONFIG_SETTING_NAME, NULL, NULL, FALSE }, + { NM_SETTING_IP6_CONFIG_SETTING_NAME, NULL, NULL, FALSE }, + { NULL, NULL, NULL, FALSE } +}; + +static const NameItem nmc_bond_settings [] = { + { NM_SETTING_CONNECTION_SETTING_NAME, NULL, NULL, TRUE }, + { NM_SETTING_BOND_SETTING_NAME, NULL, NULL, TRUE }, + { NM_SETTING_WIRED_SETTING_NAME, "ethernet", NULL, FALSE }, + { NM_SETTING_IP4_CONFIG_SETTING_NAME, NULL, NULL, FALSE }, + { NM_SETTING_IP6_CONFIG_SETTING_NAME, NULL, NULL, FALSE }, + { NULL, NULL, NULL, FALSE } +}; + +static const NameItem nmc_team_settings [] = { + { NM_SETTING_CONNECTION_SETTING_NAME, NULL, NULL, TRUE }, + { NM_SETTING_TEAM_SETTING_NAME, NULL, NULL, TRUE }, + { NM_SETTING_WIRED_SETTING_NAME, "ethernet", NULL, FALSE }, + { NM_SETTING_IP4_CONFIG_SETTING_NAME, NULL, NULL, FALSE }, + { NM_SETTING_IP6_CONFIG_SETTING_NAME, NULL, NULL, FALSE }, + { NULL, NULL, NULL, FALSE } +}; + +static const NameItem nmc_bridge_settings [] = { + { NM_SETTING_CONNECTION_SETTING_NAME, NULL, NULL, TRUE }, + { NM_SETTING_BRIDGE_SETTING_NAME, NULL, NULL, TRUE }, + { NM_SETTING_WIRED_SETTING_NAME, "ethernet", NULL, FALSE }, + { NM_SETTING_IP4_CONFIG_SETTING_NAME, NULL, NULL, FALSE }, + { NM_SETTING_IP6_CONFIG_SETTING_NAME, NULL, NULL, FALSE }, + { NULL, NULL, NULL, FALSE } +}; + +static const NameItem nmc_bond_slave_settings [] = { + { NM_SETTING_CONNECTION_SETTING_NAME, NULL, NULL, TRUE }, + { NM_SETTING_WIRED_SETTING_NAME, "ethernet", NULL, TRUE }, + { NM_SETTING_802_1X_SETTING_NAME, NULL, NULL, FALSE }, + { NULL, NULL, NULL, FALSE } +}; + +static const NameItem nmc_team_slave_settings [] = { + { NM_SETTING_CONNECTION_SETTING_NAME, NULL, NULL, TRUE }, + { NM_SETTING_WIRED_SETTING_NAME, "ethernet", NULL, TRUE }, + { NM_SETTING_TEAM_PORT_SETTING_NAME, NULL, NULL, TRUE }, + { NM_SETTING_802_1X_SETTING_NAME, NULL, NULL, FALSE }, + { NULL, NULL, NULL, FALSE } +}; + +static const NameItem nmc_bridge_slave_settings [] = { + { NM_SETTING_CONNECTION_SETTING_NAME, NULL, NULL, TRUE }, + { NM_SETTING_BRIDGE_PORT_SETTING_NAME, NULL, NULL, TRUE }, + { NM_SETTING_WIRED_SETTING_NAME, "ethernet", NULL, TRUE }, + { NM_SETTING_802_1X_SETTING_NAME, NULL, NULL, FALSE }, + { NULL, NULL, NULL, FALSE } +}; + + +/* Available connection types */ +static const NameItem nmc_valid_connection_types[] = { + { NM_SETTING_GENERIC_SETTING_NAME, NULL, nmc_generic_settings }, + { NM_SETTING_WIRED_SETTING_NAME, "ethernet", nmc_ethernet_settings }, + { NM_SETTING_PPPOE_SETTING_NAME, NULL, nmc_pppoe_settings }, + { NM_SETTING_WIRELESS_SETTING_NAME, "wifi", nmc_wifi_settings }, + { NM_SETTING_WIMAX_SETTING_NAME, NULL, nmc_wimax_settings }, + { NM_SETTING_GSM_SETTING_NAME, NULL, nmc_gsm_settings }, + { NM_SETTING_CDMA_SETTING_NAME, NULL, nmc_cdma_settings }, + { NM_SETTING_INFINIBAND_SETTING_NAME, NULL, nmc_infiniband_settings }, + { NM_SETTING_ADSL_SETTING_NAME, NULL, nmc_adsl_settings }, + { NM_SETTING_BLUETOOTH_SETTING_NAME, NULL, nmc_bluetooth_settings }, + { NM_SETTING_VPN_SETTING_NAME, NULL, nmc_vpn_settings }, + { NM_SETTING_OLPC_MESH_SETTING_NAME, "olpc-mesh", nmc_olpc_mesh_settings }, + { NM_SETTING_VLAN_SETTING_NAME, NULL, nmc_vlan_settings }, + { NM_SETTING_BOND_SETTING_NAME, NULL, nmc_bond_settings }, + { NM_SETTING_TEAM_SETTING_NAME, NULL, nmc_team_settings }, + { NM_SETTING_BRIDGE_SETTING_NAME, NULL, nmc_bridge_settings }, + { "bond-slave", NULL, nmc_bond_slave_settings }, + { "team-slave", NULL, nmc_team_slave_settings }, + { "bridge-slave", NULL, nmc_bridge_slave_settings }, + { NULL, NULL, NULL } +}; + +/* + * Return an alias for the 'name' if exists, else return the 'name'. + * The returned string must not be freed. + */ +static const char * +get_name_alias (const char *name, const NameItem array[]) +{ + const NameItem *iter = &array[0]; + + if (!name) + return NULL; + + while (iter && iter->name) { + if (!strcmp (name, iter->name)) { + if (iter->alias) + return iter->alias; + else + return iter->name; + } + iter++; + } + return name; +} + +/* + * Construct a string with names and aliases from the array formatted as: + * "name (alias), name, name (alias), name, name" + * + * Returns: string; the caller is responsible for freeing it. + */ +static char * +get_valid_options_string (const NameItem array[]) +{ + const NameItem *iter = &array[0]; + GString *str; + + str = g_string_sized_new (150); + while (iter && iter->name) { + if (str->len) + g_string_append (str, ", "); + if (iter->alias) + g_string_append_printf (str, "%s (%s)", iter->name, iter->alias); + else + g_string_append (str, iter->name); + iter++; + } + return g_string_free (str, FALSE); +} + +/* + * Check if 'val' is valid string in either array->name or array->alias. + * It accepts shorter string provided they are not ambiguous. + * 'val' == NULL doesn't hurt. + * + * Returns: pointer to array->name string or NULL on failure. + * The returned string must not be freed. + */ +static const char * +check_valid_name (const char *val, const NameItem array[], GError **error) +{ + const NameItem *iter; + GPtrArray *tmp_arr; + const char *str; + GError *tmp_err = NULL; + + /* Create a temporary array that can be used in nmc_string_is_valid() */ + tmp_arr = g_ptr_array_sized_new (30); + iter = &array[0]; + while (iter && iter->name) { + g_ptr_array_add (tmp_arr, (gpointer) iter->name); + if (iter->alias) + g_ptr_array_add (tmp_arr, (gpointer) iter->alias); + iter++; + } + g_ptr_array_add (tmp_arr, (gpointer) NULL); + + /* Check string validity */ + str = nmc_string_is_valid (val, (const char **) tmp_arr->pdata, &tmp_err); + if (!str) { + if (tmp_err->code == 1) + g_propagate_error (error, tmp_err); + else { + /* We want to handle aliases, so construct own error message */ + char *err_str = get_valid_options_string (array); + g_set_error (error, 1, 0, _("'%s' not among [%s]"), + val ? val : "", err_str); + g_free (err_str); + g_clear_error (&tmp_err); + } + g_ptr_array_free (tmp_arr, TRUE); + return NULL; + } + + /* Return a pointer to the found string in passed 'array' */ + iter = &array[0]; + while (iter && iter->name) { + if ( (iter->name && g_strcmp0 (iter->name, str) == 0) + || (iter->alias && g_strcmp0 (iter->alias, str) == 0)) { + g_ptr_array_free (tmp_arr, TRUE); + return iter->name; + } + iter++; + } + /* We should not really come here */ + g_ptr_array_free (tmp_arr, TRUE); + g_set_error (error, 1, 0, _("Unknown error")); + return NULL; +} + +static const NameItem * +get_valid_settings_array (const char *con_type) +{ + guint i, num; + + if (!con_type) + return NULL; + + num = G_N_ELEMENTS (nmc_valid_connection_types); + for (i = 0; i < num; i++) { + if (!g_strcmp0 (con_type, nmc_valid_connection_types[i].name)) + return nmc_valid_connection_types[i].settings; + } + return NULL; +} + +static gboolean +is_setting_mandatory (NMConnection *connection, NMSetting *setting) +{ + NMSettingConnection *s_con; + const char *c_type; + const NameItem *item; + const char *name; + + s_con = nm_connection_get_setting_connection (connection); + g_assert (s_con); + c_type = nm_setting_connection_get_connection_type (s_con); + + name = nm_setting_get_name (setting); + + item = get_valid_settings_array (c_type); + while (item && item->name) { + if (!strcmp (name, item->name)) + return item->mandatory; + item++; + } + return FALSE; +} + +/*----------------------------------------------------------------------------*/ + +static gboolean +check_mac (const char *mac, + int type, + const char *keyword, + GError **error) +{ + g_return_val_if_fail (type == ARPHRD_ETHER || type == ARPHRD_INFINIBAND, FALSE); + + if (!mac) + return TRUE; + + if (!nm_utils_hwaddr_valid (mac, nm_utils_hwaddr_len (type))) { + g_set_error (error, NMCLI_ERROR, NMC_RESULT_ERROR_USER_INPUT, + _("Error: '%s': '%s' is not a valid %s MAC address."), + keyword, mac, type == ARPHRD_INFINIBAND ? _("InfiniBand") : _("Ethernet")); + return FALSE; + } + + return TRUE; +} + +static gboolean +check_and_convert_mtu (const char *mtu, guint32 *mtu_int, GError **error) +{ + unsigned long local_mtu_int; + + if (mtu_int) + *mtu_int = 0; + + if (!mtu) + return TRUE; + + if (!nmc_string_to_uint (mtu, TRUE, 0, G_MAXUINT32, &local_mtu_int)) { + g_set_error (error, NMCLI_ERROR, NMC_RESULT_ERROR_USER_INPUT, + _("Error: 'mtu': '%s' is not a valid MTU."), mtu); + return FALSE; + } + if (mtu_int) + *mtu_int = (guint32) local_mtu_int; + return TRUE; +} + +static gboolean +check_infiniband_parent (const char *parent, GError **error) +{ + if (!parent) + return TRUE; + + if (!nm_utils_iface_valid_name (parent)) { + g_set_error (error, NMCLI_ERROR, NMC_RESULT_ERROR_USER_INPUT, + _("Error: 'parent': '%s' is not a valid interface name."), parent); + return FALSE; + } + return TRUE; +} + + +static gboolean +check_infiniband_p_key (const char *p_key, guint32 *p_key_int, GError **error) +{ + unsigned long local_p_key_int; + gboolean p_key_valid = FALSE; + if (!p_key) + return TRUE; + + if (!strncmp (p_key, "0x", 2)) + p_key_valid = nmc_string_to_uint_base (p_key + 2, 16, TRUE, 0, G_MAXUINT16, &local_p_key_int); + else + p_key_valid = nmc_string_to_uint (p_key, TRUE, 0, G_MAXUINT16, &local_p_key_int); + if (!p_key_valid) { + g_set_error (error, NMCLI_ERROR, NMC_RESULT_ERROR_USER_INPUT, + _("Error: 'p-key': '%s' is not a valid InfiniBand P_KEY."), p_key); + return FALSE; + } + if (p_key_int) + *p_key_int = (guint32) local_p_key_int; + return TRUE; +} + +/** + * check_valid_enumeration: + * @str: string to check against string array @strings + * @strings: string array to check @str againt + * @what: what parameter @str belongs to (used in error message) + * @what_desc: longer description of @what parameter (used in error message) + * @error: location to store an error, or %NULL + * + * Check whether @str is one of the string of @strings array. It accepts + * shortcuts and normalizes them (@str argument is modified on success). + * + * Returns: %TRUE on success, %FALSE on failure + */ +static gboolean +check_valid_enumeration (char **str, + const char *strings[], + const char *what, + const char *what_desc, + GError **error) +{ + char *tmp; + const char *checked_str; + + if (!str || !*str) + return TRUE; + + tmp = g_strstrip (g_strdup (*str)); + checked_str = nmc_string_is_valid (tmp, strings, NULL); + g_free (tmp); + if (checked_str) { + g_free (*str); + *str = g_strdup (checked_str); + } else { + char *options; + + options = nmc_util_strv_for_display (strings); + g_set_error (error, NMCLI_ERROR, NMC_RESULT_ERROR_USER_INPUT, + _("Error: '%s': '%s' is not a valid %s %s."), + what, *str, what_desc, options); + g_free (options); + } + return !!checked_str; +} + +/* Checks Wi-Fi mode. */ +static gboolean +check_wifi_mode (char **mode, GError **error) +{ + const char *modes[] = { "infrastructure", "ap", "adhoc", NULL }; + + return check_valid_enumeration (mode, modes, "mode", _("Wi-Fi mode"), error); +} + +/* Checks InfiniBand mode. */ +static gboolean +check_infiniband_mode (char **mode, GError **error) +{ + const char *modes[] = { "datagram", "connected", NULL }; + + return check_valid_enumeration (mode, modes, "mode", _("InfiniBand transport mode"), error); +} + +static gboolean +check_and_convert_vlan_flags (const char *flags, guint32 *flags_int, GError **error) +{ + unsigned long local_flags_int; + + if (!flags) + return TRUE; + + if (!nmc_string_to_uint (flags, TRUE, 0, 7, &local_flags_int)) { + g_set_error (error, NMCLI_ERROR, NMC_RESULT_ERROR_USER_INPUT, + _("Error: 'flags': '%s' is not valid; use <0-7>."), flags); + return FALSE; + } + if (flags_int) + *flags_int = (guint32) local_flags_int; + return TRUE; +} + +static gboolean +check_and_convert_vlan_prio_maps (const char *prio_map, + NMVlanPriorityMap type, + char ***prio_map_arr, + GError **error) +{ + char **local_prio_map_arr; + GError *local_err = NULL; + + if (!prio_map) + return TRUE; + + if (!(local_prio_map_arr = nmc_vlan_parse_priority_maps (prio_map, type, &local_err))) { + g_set_error (error, NMCLI_ERROR, NMC_RESULT_ERROR_USER_INPUT, + _("Error: '%s': '%s' is not valid; %s "), + type == NM_VLAN_INGRESS_MAP ? "ingress" : "egress", + prio_map, local_err->message); + return FALSE; + } + + if (prio_map_arr) + *prio_map_arr = local_prio_map_arr; + return TRUE; +} + +static gboolean +add_ip4_address_to_connection (NMIPAddress *ip4addr, NMConnection *connection) +{ + NMSettingIPConfig *s_ip4; + gboolean ret; + + if (!ip4addr) + return TRUE; + + s_ip4 = nm_connection_get_setting_ip4_config (connection); + if (!s_ip4) { + s_ip4 = (NMSettingIPConfig *) nm_setting_ip4_config_new (); + nm_connection_add_setting (connection, NM_SETTING (s_ip4)); + g_object_set (s_ip4, + NM_SETTING_IP_CONFIG_METHOD, NM_SETTING_IP4_CONFIG_METHOD_MANUAL, + NULL); + } + ret = nm_setting_ip_config_add_address (s_ip4, ip4addr); + nm_ip_address_unref (ip4addr); + + return ret; +} + +static gboolean +add_ip6_address_to_connection (NMIPAddress *ip6addr, NMConnection *connection) +{ + NMSettingIPConfig *s_ip6; + gboolean ret; + + if (!ip6addr) + return TRUE; + + s_ip6 = nm_connection_get_setting_ip6_config (connection); + if (!s_ip6) { + s_ip6 = (NMSettingIPConfig *) nm_setting_ip6_config_new (); + nm_connection_add_setting (connection, NM_SETTING (s_ip6)); + g_object_set (s_ip6, + NM_SETTING_IP_CONFIG_METHOD, NM_SETTING_IP6_CONFIG_METHOD_MANUAL, + NULL); + } + ret = nm_setting_ip_config_add_address (s_ip6, ip6addr); + nm_ip_address_unref (ip6addr); + + return ret; +} + +static char * +unique_master_iface_ifname (const GPtrArray *connections, + const char *try_name) +{ + NMConnection *connection; + char *new_name; + unsigned int num = 1; + int i = 0; + const char *ifname = NULL; + + new_name = g_strdup (try_name); + while (i < connections->len) { + connection = NM_CONNECTION (connections->pdata[i]); + ifname = nm_connection_get_interface_name (connection); + if (g_strcmp0 (new_name, ifname) == 0) { + g_free (new_name); + new_name = g_strdup_printf ("%s%d", try_name, num++); + i = 0; + } else + i++; + } + return new_name; +} + +static const char * +_strip_master_prefix (const char *master, const char *(**func)(NMConnection *)) +{ + if (!master) + return NULL; + + if (g_str_has_prefix (master, "ifname/")) { + master = master + strlen ("ifname/"); + if (func) + *func = nm_connection_get_interface_name; + } else if (g_str_has_prefix (master, "uuid/")) { + master = master + strlen ("uuid/"); + if (func) + *func = nm_connection_get_uuid; + } else if (g_str_has_prefix (master, "id/")) { + master = master + strlen ("id/"); + if (func) + *func = nm_connection_get_id; + } + return master; +} + +/* verify_master_for_slave: + * @connections: list af all connections + * @master: UUID, ifname or ID of the master connection + * @type: virtual connection type (bond, team, bridge, ...) + * + * Check whether master is a valid interface name, UUID or ID of some @type connection. + * First UUID and ifname are checked. If they don't match, ID is checked + * and replaced by UUID on a match. + * + * Returns: identifier of master connection if found, %NULL otherwise + */ +static const char * +verify_master_for_slave (const GPtrArray *connections, + const char *master, + const char *type) +{ + NMConnection *connection; + NMSettingConnection *s_con; + const char *con_type, *id, *uuid, *ifname; + int i; + const char *found_by_id = NULL; + const char *out_master = NULL; + const char *(*func) (NMConnection *) = NULL; + + if (!master) + return NULL; + + master = _strip_master_prefix (master, &func); + for (i = 0; i < connections->len; i++) { + connection = NM_CONNECTION (connections->pdata[i]); + s_con = nm_connection_get_setting_connection (connection); + g_assert (s_con); + con_type = nm_setting_connection_get_connection_type (s_con); + if (g_strcmp0 (con_type, type) != 0) + continue; + if (func) { + /* There was a prefix; only compare to that type. */ + if (g_strcmp0 (master, func (connection)) == 0) { + if (func == nm_connection_get_id) + out_master = nm_connection_get_uuid (connection); + else + out_master = master; + break; + } + } else { + id = nm_connection_get_id (connection); + uuid = nm_connection_get_uuid (connection); + ifname = nm_connection_get_interface_name (connection); + if ( g_strcmp0 (master, uuid) == 0 + || g_strcmp0 (master, ifname) == 0) { + out_master = master; + break; + } + if (!found_by_id && g_strcmp0 (master, id) == 0) + found_by_id = uuid; + } + } + return out_master ? out_master : found_by_id; +} + +static gboolean +bridge_prop_string_to_uint (const char *str, + const char *nmc_arg, + GType bridge_type, + const char *propname, + unsigned long *out_val, + GError **error) +{ + GParamSpecUInt *pspec; + + pspec = (GParamSpecUInt *) g_object_class_find_property (g_type_class_peek (bridge_type), + propname); + g_assert (G_IS_PARAM_SPEC_UINT (pspec)); + + if (!nmc_string_to_uint (str, TRUE, pspec->minimum, pspec->maximum, out_val)) { + g_set_error (error, NMCLI_ERROR, NMC_RESULT_ERROR_USER_INPUT, + _("Error: '%s': '%s' is not valid; use <%u-%u>."), + nmc_arg, str, pspec->minimum, pspec->maximum); + return FALSE; + } + return TRUE; +} + +#define WORD_YES "yes" +#define WORD_NO "no" +#define WORD_LOC_YES _("yes") +#define WORD_LOC_NO _("no") +static const char * +prompt_yes_no (gboolean default_yes, char *delim) +{ + static char prompt[128] = { 0 }; + + if (!delim) + delim = ""; + + snprintf (prompt, sizeof (prompt), "(%s/%s) [%s]%s ", + WORD_LOC_YES, WORD_LOC_NO, + default_yes ? WORD_LOC_YES : WORD_LOC_NO, delim); + + return prompt; +} + +static gboolean +normalize_yes_no (char **yes_no) +{ + char *tmp; + const char *checked_yes_no; + const char *strv[] = { WORD_LOC_YES, WORD_LOC_NO, NULL }; + + if (!yes_no || !*yes_no) + return FALSE; + + tmp = g_strstrip (g_strdup (*yes_no)); + checked_yes_no = nmc_string_is_valid (tmp, strv, NULL); + g_free (tmp); + if (g_strcmp0 (checked_yes_no, WORD_LOC_YES) == 0) { + g_free (*yes_no); + *yes_no = g_strdup (WORD_YES); + } else if (g_strcmp0 (checked_yes_no, WORD_LOC_NO) == 0) { + g_free (*yes_no); + *yes_no = g_strdup (WORD_NO); + } + return !!checked_yes_no; +} + +static gboolean +want_provide_opt_args (const char *type, int num) +{ + char *answer; + gboolean ret = TRUE; + + /* Ask for optional arguments. */ + g_print (ngettext ("There is %d optional argument for '%s' connection type.\n", + "There are %d optional arguments for '%s' connection type.\n", num), + num, type); + answer = nmc_readline (ngettext ("Do you want to provide it? %s", + "Do you want to provide them? %s", num), + prompt_yes_no (TRUE, NULL)); + answer = answer ? g_strstrip (answer) : NULL; + if (answer && matches (answer, WORD_LOC_YES) != 0) + ret = FALSE; + g_free (answer); + return ret; +} + +static void +do_questionnaire_ethernet (gboolean ethernet, char **mtu, char **mac, char **cloned_mac) +{ + gboolean once_more; + GError *error = NULL; + + /* Ask for optional arguments */ + if (ethernet && !want_provide_opt_args (_("ethernet"), 3)) + return; + + if (!*mtu) { + do { + *mtu = nmc_readline (_("MTU [auto]: ")); + once_more = !check_and_convert_mtu (*mtu, NULL, &error); + if (once_more) { + g_print ("%s\n", error->message); + g_clear_error (&error); + g_free (*mtu); + } + } while (once_more); + } + if (!*mac) { + do { + *mac = nmc_readline (_("MAC [none]: ")); + once_more = !check_mac (*mac, ARPHRD_ETHER, "mac", &error); + if (once_more) { + g_print ("%s\n", error->message); + g_clear_error (&error); + g_free (*mac); + } + } while (once_more); + } + if (!*cloned_mac) { + do { + *cloned_mac = nmc_readline (_("Cloned MAC [none]: ")); + once_more = !check_mac (*cloned_mac, ARPHRD_ETHER, "cloned-mac", &error); + if (once_more) { + g_print ("%s\n", error->message); + g_clear_error (&error); + g_free (*cloned_mac); + } + } while (once_more); + } +} + +#define WORD_DATAGRAM "datagram" +#define WORD_CONNECTED "connected" +#define PROMPT_IB_MODE "(" WORD_DATAGRAM "/" WORD_CONNECTED ") [" WORD_DATAGRAM "]: " +static void +do_questionnaire_infiniband (char **mtu, char **mac, char **mode, char **parent, char **p_key) +{ + gboolean once_more; + GError *error = NULL; + + /* Ask for optional arguments */ + if (!want_provide_opt_args (_("InfiniBand"), 5)) + return; + + if (!*mtu) { + do { + *mtu = nmc_readline (_("MTU [auto]: ")); + once_more = !check_and_convert_mtu (*mtu, NULL, &error); + if (once_more) { + g_print ("%s\n", error->message); + g_clear_error (&error); + g_free (*mtu); + } + } while (once_more); + } + if (!*mac) { + do { + *mac = nmc_readline (_("MAC [none]: ")); + once_more = !check_mac (*mac, ARPHRD_INFINIBAND, "mac", &error); + if (once_more) { + g_print ("%s\n", error->message); + g_clear_error (&error); + g_free (*mac); + } + } while (once_more); + } + if (!*mode) { + do { + *mode = nmc_readline (_("Transport mode %s"), PROMPT_IB_MODE); + if (!*mode) + *mode = g_strdup ("datagram"); + once_more = !check_infiniband_mode (mode, &error); + if (once_more) { + g_print ("%s\n", error->message); + g_clear_error (&error); + g_free (*mode); + } + } while (once_more); + } + if (!*parent) { + do { + *parent = nmc_readline (_("Parent interface [none]: ")); + once_more = !check_infiniband_parent (*parent, &error); + if (once_more) { + g_print ("%s\n", error->message); + g_clear_error (&error); + g_free (*parent); + } + } while (once_more); + } + if (!*p_key) { + do { + *p_key = nmc_readline (_("P_KEY [none]: ")); + once_more = !check_infiniband_p_key (*p_key, NULL, &error); + if (once_more) { + g_print ("%s\n", error->message); + g_clear_error (&error); + g_free (*p_key); + } + /* If parent is specified, so has to be P_KEY */ + if (!once_more && *parent && !*p_key) { + once_more = TRUE; + g_print (_("Error: 'p-key' is mandatory when 'parent' is specified.\n")); + } + } while (once_more); + } +} + +#define WORD_INFRA "infrastructure" +#define WORD_AP "ap" +#define WORD_ADHOC "adhoc" +#define PROMPT_WIFI_MODE "(" WORD_INFRA "/" WORD_AP "/" WORD_ADHOC ") [" WORD_INFRA "]: " +static void +do_questionnaire_wifi (char **mtu, char **mac, char **cloned_mac, char **mode) +{ + gboolean once_more; + GError *error = NULL; + + /* Ask for optional arguments */ + if (!want_provide_opt_args (_("Wi-Fi"), 4)) + return; + + /* Most optional Wi-Fi arguments are the same as for ethernet. */ + do_questionnaire_ethernet (FALSE, mtu, mac, cloned_mac); + + if (!*mode) { + do { + *mode = nmc_readline (_("Mode %s"), PROMPT_WIFI_MODE); + if (!*mode) + *mode = g_strdup ("infrastructure"); + once_more = !check_wifi_mode (mode, &error); + if (once_more) { + g_print ("%s\n", error->message); + g_clear_error (&error); + g_free (*mode); + } + } while (once_more); + } +} + +static void +do_questionnaire_wimax (char **mac) +{ + gboolean once_more; + GError *error = NULL; + + /* Ask for optional 'wimax' arguments. */ + if (!want_provide_opt_args (_("WiMAX"), 1)) + return; + + if (!*mac) { + do { + *mac = nmc_readline (_("MAC [none]: ")); + once_more = !check_mac (*mac, ARPHRD_ETHER, "mac", &error); + if (once_more) { + g_print ("%s\n", error->message); + g_clear_error (&error); + g_free (*mac); + } + } while (once_more); + } +} + +static void +do_questionnaire_pppoe (char **password, char **service, char **mtu, char **mac) +{ + gboolean once_more; + GError *error = NULL; + + /* Ask for optional 'pppoe' arguments. */ + if (!want_provide_opt_args (_("PPPoE"), 4)) + return; + + if (!*password) + *password = nmc_readline (_("Password [none]: ")); + if (!*service) + *service = nmc_readline (_("Service [none]: ")); + + if (!*mtu) { + do { + *mtu = nmc_readline (_("MTU [auto]: ")); + once_more = !check_and_convert_mtu (*mtu, NULL, &error); + if (once_more) { + g_print ("%s\n", error->message); + g_clear_error (&error); + g_free (*mtu); + } + } while (once_more); + } + if (!*mac) { + do { + *mac = nmc_readline (_("MAC [none]: ")); + once_more = !check_mac (*mac, ARPHRD_ETHER, "mac", &error); + if (once_more) { + g_print ("%s\n", error->message); + g_clear_error (&error); + g_free (*mac); + } + } while (once_more); + } +} + +static void +do_questionnaire_mobile (char **user, char **password) +{ + /* Ask for optional 'gsm' or 'cdma' arguments. */ + if (!want_provide_opt_args (_("mobile broadband"), 2)) + return; + + if (!*user) + *user = nmc_readline (_("Username [none]: ")); + if (!*password) + *password = nmc_readline (_("Password [none]: ")); +} + +#define WORD_PANU "panu" +#define WORD_DUN_GSM "dun-gsm" +#define WORD_DUN_CDMA "dun-cdma" +#define PROMPT_BT_TYPE "(" WORD_PANU "/" WORD_DUN_GSM "/" WORD_DUN_CDMA ") [" WORD_PANU "]: " +static void +do_questionnaire_bluetooth (char **bt_type) +{ + gboolean once_more; + + /* Ask for optional 'bluetooth' arguments. */ + if (!want_provide_opt_args (_("bluetooth"), 1)) + return; + + if (!*bt_type) { + const char *types[] = { "dun", "dun-gsm", "dun-cdma", "panu", NULL }; + const char *tmp; + do { + *bt_type = nmc_readline (_("Bluetooth type %s"), PROMPT_BT_TYPE); + if (!*bt_type) + *bt_type = g_strdup ("panu"); + tmp = nmc_string_is_valid (*bt_type, types, NULL); + once_more = !tmp; + if (once_more) { + g_print (_("Error: 'bt-type': '%s' is not a valid bluetooth type.\n"), *bt_type); + g_free (*bt_type); + } + } while (once_more); + g_free (*bt_type); + *bt_type = g_strdup (tmp); + } +} + +static void +do_questionnaire_vlan (char **mtu, char **flags, char **ingress, char **egress) +{ + gboolean once_more; + GError *error = NULL; + + /* Ask for optional 'vlan' arguments. */ + if (!want_provide_opt_args (_("VLAN"), 4)) + return; + + if (!*mtu) { + do { + *mtu = nmc_readline (_("MTU [auto]: ")); + once_more = !check_and_convert_mtu (*mtu, NULL, &error); + if (once_more) { + g_print ("%s\n", error->message); + g_clear_error (&error); + g_free (*mtu); + } + } while (once_more); + } + if (!*flags) { + do { + *flags = nmc_readline (_("VLAN flags (<0-7>) [none]: ")); + once_more = !check_and_convert_vlan_flags (*flags, NULL, &error); + if (once_more) { + g_print ("%s\n", error->message); + g_clear_error (&error); + g_free (*flags); + } + } while (once_more); + } + if (!*ingress) { + do { + *ingress = nmc_readline (_("Ingress priority maps [none]: ")); + once_more = !check_and_convert_vlan_prio_maps (*ingress, NM_VLAN_INGRESS_MAP, NULL, &error); + if (once_more) { + g_print ("%s\n", error->message); + g_clear_error (&error); + g_free (*ingress); + } + } while (once_more); + } + if (!*egress) { + do { + *egress = nmc_readline (_("Egress priority maps [none]: ")); + once_more = !check_and_convert_vlan_prio_maps (*egress, NM_VLAN_EGRESS_MAP, NULL, &error); + if (once_more) { + g_print ("%s\n", error->message); + g_clear_error (&error); + g_free (*egress); + } + } while (once_more); + } +} + +#define PROMPT_BOND_MODE _("Bonding mode [balance-rr]: ") +#define WORD_MIIMON "miimon" +#define WORD_ARP "arp" +#define PROMPT_BOND_MON_MODE "(" WORD_MIIMON "/" WORD_ARP ") [" WORD_MIIMON "]: " +static void +do_questionnaire_bond (char **mode, char **primary, char **miimon, + char **downdelay, char **updelay, + char **arpinterval, char **arpiptarget, + char **lacp_rate) +{ + char *monitor_mode; + unsigned long tmp; + gboolean once_more; + GError *error = NULL; + + /* Ask for optional 'bond' arguments. */ + if (!want_provide_opt_args (_("bond"), 7)) + return; + + if (!*mode) { + const char *mode_tmp; + do { + *mode = nmc_readline (PROMPT_BOND_MODE); + if (!*mode) + *mode = g_strdup ("balance-rr"); + mode_tmp = nmc_bond_validate_mode (*mode, &error); + g_free (*mode); + if (mode_tmp) { + *mode = g_strdup (mode_tmp); + } else { + g_print ("%s\n", error->message); + g_clear_error (&error); + } + } while (!mode_tmp); + } + + if (g_strcmp0 (*mode, "active-backup") == 0 && !*primary) { + do { + *primary = nmc_readline (_("Bonding primary interface [none]: ")); + once_more = *primary && !nm_utils_iface_valid_name (*primary); + if (once_more) { + g_print (_("Error: 'primary': '%s' is not a valid interface name.\n"), + *primary); + g_free (*primary); + } + } while (once_more); + } + + do { + monitor_mode = nmc_readline (_("Bonding monitoring mode %s"), PROMPT_BOND_MON_MODE); + if (!monitor_mode) + monitor_mode = g_strdup (WORD_MIIMON); + g_strstrip (monitor_mode); + once_more = matches (monitor_mode, WORD_MIIMON) != 0 && matches (monitor_mode, WORD_ARP) != 0; + if (once_more) { + g_print (_("Error: '%s' is not a valid monitoring mode; use '%s' or '%s'.\n"), + monitor_mode, WORD_MIIMON, WORD_ARP); + g_free (monitor_mode); + } + } while (once_more); + + if (matches (monitor_mode, WORD_MIIMON) == 0) { + if (!*miimon) { + do { + *miimon = nmc_readline (_("Bonding miimon [100]: ")); + once_more = *miimon && !nmc_string_to_uint (*miimon, TRUE, 0, G_MAXUINT32, &tmp); + if (once_more) { + g_print (_("Error: 'miimon': '%s' is not a valid number <0-%u>.\n"), + *miimon, G_MAXUINT32); + g_free (*miimon); + } + } while (once_more); + } + if (!*downdelay) { + do { + *downdelay = nmc_readline (_("Bonding downdelay [0]: ")); + once_more = *downdelay && !nmc_string_to_uint (*downdelay, TRUE, 0, G_MAXUINT32, &tmp); + if (once_more) { + g_print (_("Error: 'downdelay': '%s' is not a valid number <0-%u>.\n"), + *downdelay, G_MAXUINT32); + g_free (*downdelay); + } + } while (once_more); + } + if (!*updelay) { + do { + *updelay = nmc_readline (_("Bonding updelay [0]: ")); + once_more = *updelay && !nmc_string_to_uint (*updelay, TRUE, 0, G_MAXUINT32, &tmp); + if (once_more) { + g_print (_("Error: 'updelay': '%s' is not a valid number <0-%u>.\n"), + *updelay, G_MAXUINT32); + g_free (*updelay); + } + } while (once_more); + } + } else { + if (!*arpinterval) { + do { + *arpinterval = nmc_readline (_("Bonding arp-interval [0]: ")); + once_more = *arpinterval && !nmc_string_to_uint (*arpinterval, TRUE, 0, G_MAXUINT32, &tmp); + if (once_more) { + g_print (_("Error: 'arp-interval': '%s' is not a valid number <0-%u>.\n"), + *arpinterval, G_MAXUINT32); + g_free (*arpinterval); + } + } while (once_more); + } + if (!*arpiptarget) { + //FIXME: verify the string + *arpiptarget = nmc_readline (_("Bonding arp-ip-target [none]: ")); + } + } + + if ( !*lacp_rate + && (g_strcmp0 (*mode, "802.3ad") == 0 || g_strcmp0 (*mode, "4") == 0)) { + do { + *lacp_rate = nmc_readline (_("LACP rate ('slow' or 'fast') [slow]: ")); + once_more = *lacp_rate && (strcmp (*lacp_rate, "slow") && + strcmp (*lacp_rate, "0") && + strcmp (*lacp_rate, "fast") && + strcmp (*lacp_rate, "1")); + if (once_more) { + printf (_("Error: 'lacp_rate': '%s' is invalid ('slow' or 'fast').\n"), + *lacp_rate); + g_free (*lacp_rate); + } + } while (once_more); + } + + g_free (monitor_mode); +} + +static void +do_questionnaire_team_common (const char *type_name, char **config) +{ + gboolean once_more; + char *json = NULL; + GError *error = NULL; + + /* Ask for optional arguments. */ + if (!want_provide_opt_args (type_name, 1)) + return; + + if (!*config) { + do { + *config = nmc_readline (_("Team JSON configuration [none]: ")); + once_more = !nmc_team_check_config (*config, &json, &error); + if (once_more) { + g_print ("Error: %s\n", error->message); + g_clear_error (&error); + g_free (*config); + } + } while (once_more); + } + + *config = json; +} + +/* Both team and team-slave curently have just ithe same one optional argument */ +static void +do_questionnaire_team (char **config) +{ + do_questionnaire_team_common (_("team"), config); +} + +static void +do_questionnaire_team_slave (char **config) +{ + do_questionnaire_team_common (_("team-slave"), config); +} + +static void +do_questionnaire_bridge (char **stp, char **priority, char **fwd_delay, char **hello_time, + char **max_age, char **ageing_time, char **mac) +{ + unsigned long tmp; + gboolean once_more; + GError *error = NULL; + + /* Ask for optional 'bridge' arguments. */ + if (!want_provide_opt_args (_("bridge"), 7)) + return; + + if (!*stp) { + gboolean stp_bool; + do { + *stp = nmc_readline (_("Enable STP %s"), prompt_yes_no (TRUE, ":")); + *stp = *stp ? *stp : g_strdup ("yes"); + normalize_yes_no (stp); + once_more = !nmc_string_to_bool (*stp, &stp_bool, &error); + if (once_more) { + g_print (_("Error: 'stp': %s.\n"), error->message); + g_clear_error (&error); + g_free (*stp); + } + } while (once_more); + } + if (!*priority) { + do { + *priority = nmc_readline (_("STP priority [32768]: ")); + *priority = *priority ? *priority : g_strdup ("32768"); + once_more = !nmc_string_to_uint (*priority, TRUE, 0, G_MAXUINT16, &tmp); + if (once_more) { + g_print (_("Error: 'priority': '%s' is not a valid number <0-%d>.\n"), + *priority, G_MAXUINT16); + g_free (*priority); + } + } while (once_more); + } + if (!*fwd_delay) { + do { + *fwd_delay = nmc_readline (_("Forward delay [15]: ")); + *fwd_delay = *fwd_delay ? *fwd_delay : g_strdup ("15"); + once_more = !nmc_string_to_uint (*fwd_delay, TRUE, 2, 30, &tmp); + if (once_more) { + g_print (_("Error: 'forward-delay': '%s' is not a valid number <2-30>.\n"), + *fwd_delay); + g_free (*fwd_delay); + } + } while (once_more); + } + + if (!*hello_time) { + do { + *hello_time = nmc_readline (_("Hello time [2]: ")); + *hello_time = *hello_time ? *hello_time : g_strdup ("2"); + once_more = !nmc_string_to_uint (*hello_time, TRUE, 1, 10, &tmp); + if (once_more) { + g_print (_("Error: 'hello-time': '%s' is not a valid number <1-10>.\n"), + *hello_time); + g_free (*hello_time); + } + } while (once_more); + } + if (!*max_age) { + do { + *max_age = nmc_readline (_("Max age [20]: ")); + *max_age = *max_age ? *max_age : g_strdup ("20"); + once_more = !nmc_string_to_uint (*max_age, TRUE, 6, 40, &tmp); + if (once_more) { + g_print (_("Error: 'max-age': '%s' is not a valid number <6-40>.\n"), + *max_age); + g_free (*max_age); + } + } while (once_more); + } + if (!*ageing_time) { + do { + *ageing_time = nmc_readline (_("MAC address ageing time [300]: ")); + *ageing_time = *ageing_time ? *ageing_time : g_strdup ("300"); + once_more = !nmc_string_to_uint (*ageing_time, TRUE, 0, 1000000, &tmp); + if (once_more) { + g_print (_("Error: 'ageing-time': '%s' is not a valid number <0-1000000>.\n"), + *ageing_time); + g_free (*ageing_time); + } + } while (once_more); + } + if (!*mac) { + do { + *mac = nmc_get_user_input (_("MAC [none]: ")); + once_more = !check_mac (*mac, ARPHRD_ETHER, "mac", &error); + if (once_more) { + g_print ("%s\n", error->message); + g_clear_error (&error); + g_free (*mac); + } + } while (once_more); + } +} + +static void +do_questionnaire_bridge_slave (char **priority, char **path_cost, char **hairpin) +{ + unsigned long tmp; + gboolean once_more; + GError *error = NULL; + + /* Ask for optional 'bridge-slave' arguments. */ + if (!want_provide_opt_args (_("bridge-slave"), 3)) + return; + + if (!*priority) { + do { + *priority = nmc_readline (_("Bridge port priority [32]: ")); + *priority = *priority ? *priority : g_strdup ("32"); + once_more = !bridge_prop_string_to_uint (*priority, "priority", NM_TYPE_SETTING_BRIDGE_PORT, + NM_SETTING_BRIDGE_PORT_PRIORITY, &tmp, &error); + if (once_more) { + g_print ("%s\n", error->message); + g_clear_error (&error); + g_free (*priority); + } + } while (once_more); + } + if (!*path_cost) { + do { + *path_cost = nmc_readline (_("Bridge port STP path cost [100]: ")); + *path_cost = *path_cost ? *path_cost : g_strdup ("100"); + once_more = !bridge_prop_string_to_uint (*path_cost, "path-cost", NM_TYPE_SETTING_BRIDGE_PORT, + NM_SETTING_BRIDGE_PORT_PATH_COST, &tmp, &error); + if (once_more) { + g_print ("%s\n", error->message); + g_clear_error (&error); + g_free (*path_cost); + } + } while (once_more); + } + if (!*hairpin) { + gboolean hairpin_bool; + do { + *hairpin = nmc_readline (_("Hairpin %s"), prompt_yes_no (TRUE, ":")); + *hairpin = *hairpin ? *hairpin : g_strdup ("yes"); + normalize_yes_no (hairpin); + once_more = !nmc_string_to_bool (*hairpin, &hairpin_bool, &error); + if (once_more) { + g_print (_("Error: 'hairpin': %s.\n"), error->message); + g_clear_error (&error); + g_free (*hairpin); + } + } while (once_more); + } +} + +static void +do_questionnaire_vpn (char **user) +{ + /* Ask for optional 'vpn' arguments. */ + if (!want_provide_opt_args (_("VPN"), 1)) + return; + + if (!*user) + *user = nmc_readline (_("Username [none]: ")); +} + +static void +do_questionnaire_olpc (char **channel, char **dhcp_anycast) +{ + unsigned long tmp; + gboolean once_more; + GError *error = NULL; + + /* Ask for optional 'olpc' arguments. */ + if (!want_provide_opt_args (_("OLPC Mesh"), 2)) + return; + + if (!*channel) { + do { + *channel = nmc_readline (_("OLPC Mesh channel [1]: ")); + once_more = *channel && !nmc_string_to_uint (*channel, TRUE, 1, 13, &tmp); + if (once_more) { + g_print (_("Error: 'channel': '%s' is not a valid number <1-13>.\n"), + *channel); + g_free (*channel); + } + } while (once_more); + } + if (!*dhcp_anycast) { + do { + *dhcp_anycast = nmc_readline (_("DHCP anycast MAC address [none]: ")); + once_more = !check_mac (*dhcp_anycast, ARPHRD_ETHER, "dhcp-anycast", &error); + if (once_more) { + g_print ("%s\n", error->message); + g_clear_error (&error); + g_free (*dhcp_anycast); + } + } while (once_more); + } +} + +static gboolean +split_address (char* str, char **ip, char **rest) +{ + size_t n1, n2, n3; + + *ip = *rest = NULL; + if (!str) + return FALSE; + + n1 = strspn (str, " \t"); + n2 = strcspn (str+n1, " \t\0") + n1; + n3 = strspn (str+n2, " \t") + n2; + + str[n2] = '\0'; + *ip = str[n1] ? str + n1 : NULL; + *rest = str[n3] ? str + n3 : NULL; + + return TRUE; +} + +static void +ask_for_ip_addresses (NMConnection *connection, int family) +{ + gboolean ip_loop; + GError *error = NULL; + char *str, *ip, *rest; + const char *prompt; + gboolean added; + NMIPAddress *ipaddr; + + if (family == AF_INET) + prompt =_("IPv4 address (IP[/plen]) [none]: "); + else + prompt =_("IPv6 address (IP[/plen]) [none]: "); + + ip_loop = TRUE; + do { + str = nmc_readline ("%s", prompt); + split_address (str, &ip, &rest); + if (ip) { + ipaddr = nmc_parse_and_build_address (family, ip, &error); + if (ipaddr) { + if (family == AF_INET) + added = add_ip4_address_to_connection (ipaddr, connection); + else + added = add_ip6_address_to_connection (ipaddr, connection); + if (added) + g_print (_(" Address successfully added: %s\n"), ip); + else + g_print (_(" Warning: address already present: %s\n"), ip); + if (rest) + g_print (_(" Warning: ignoring garbage at the end: '%s'\n"), rest); + } else { + g_prefix_error (&error, _("Error: ")); + g_print ("%s\n", error->message); + g_clear_error (&error); + } + } else + ip_loop = FALSE; + + g_free (str); + } while (ip_loop); +} + +static void +maybe_ask_for_gateway (NMConnection *connection, int family) +{ + gboolean gw_loop; + char *str, *gw, *rest; + const char *prompt; + NMSettingIPConfig *s_ip; + + if (family == AF_INET) { + prompt =_("IPv4 gateway [none]: "); + s_ip = nm_connection_get_setting_ip4_config (connection); + } else { + prompt =_("IPv6 gateway [none]: "); + s_ip = nm_connection_get_setting_ip6_config (connection); + } + if (s_ip == NULL) + return; + if ( nm_setting_ip_config_get_num_addresses (s_ip) == 0 + || nm_setting_ip_config_get_gateway (s_ip) != NULL) + return; + + gw_loop = TRUE; + do { + str = nmc_readline ("%s", prompt); + split_address (str, &gw, &rest); + if (gw) { + if (nm_utils_ipaddr_valid (family, gw)) { + g_object_set (s_ip, + NM_SETTING_IP_CONFIG_GATEWAY, gw, + NULL); + gw_loop = FALSE; + } else + g_print (_("Error: invalid gateway address '%s'\n"), gw); + } else + gw_loop = FALSE; + g_free (str); + } while (gw_loop); +} + +static void +do_questionnaire_ip (NMConnection *connection) +{ + char *answer; + + /* Ask for IP addresses */ + answer = nmc_readline (_("Do you want to add IP addresses? %s"), prompt_yes_no (TRUE, NULL)); + answer = answer ? g_strstrip (answer) : NULL; + if (answer && matches (answer, WORD_LOC_YES) != 0) { + g_free (answer); + return; + } + g_free (answer); + + g_print (_("Press to finish adding addresses.\n")); + + ask_for_ip_addresses (connection, AF_INET); + maybe_ask_for_gateway (connection, AF_INET); + ask_for_ip_addresses (connection, AF_INET6); + maybe_ask_for_gateway (connection, AF_INET6); +} + +static gboolean +complete_connection_by_type (NMConnection *connection, + const char *con_type, + const GPtrArray *all_connections, + gboolean ask, + int argc, + char **argv, + GError **error) +{ + NMSettingConnection *s_con; + NMSettingWired *s_wired; + NMSettingInfiniband *s_infiniband; + NMSettingWireless *s_wifi; + NMSettingWimax *s_wimax; + NMSettingPppoe *s_pppoe; + NMSettingGsm *s_gsm; + NMSettingCdma *s_cdma; + NMSettingBluetooth *s_bt; + NMSettingVlan *s_vlan; + NMSettingBond *s_bond; + NMSettingTeam *s_team; + NMSettingTeamPort *s_team_port; + NMSettingBridge *s_bridge; + NMSettingBridgePort *s_bridge_port; + NMSettingVpn *s_vpn; + NMSettingOlpcMesh *s_olpc_mesh; + + g_return_val_if_fail (error == NULL || *error == NULL, FALSE); + + s_con = nm_connection_get_setting_connection (connection); + g_assert (s_con); + + if (!strcmp (con_type, NM_SETTING_WIRED_SETTING_NAME)) { + /* Build up the settings required for 'ethernet' */ + gboolean success = FALSE; + const char *mtu_c = NULL; + char *mtu = NULL; + guint32 mtu_int = 0; + const char *mac_c = NULL; + char *mac = NULL; + const char *cloned_mac_c = NULL; + char *cloned_mac = NULL; + nmc_arg_t exp_args[] = { {"mtu", TRUE, &mtu_c, FALSE}, + {"mac", TRUE, &mac_c, FALSE}, + {"cloned-mac", TRUE, &cloned_mac_c, FALSE}, + {NULL} }; + + if (!nmc_parse_args (exp_args, FALSE, &argc, &argv, error)) + return FALSE; + + /* Also ask for all optional arguments if '--ask' is specified. */ + mtu = g_strdup (mtu_c); + mac = g_strdup (mac_c); + cloned_mac = g_strdup (cloned_mac_c); + if (ask) + do_questionnaire_ethernet (TRUE, &mtu, &mac, &cloned_mac); + + if (!check_and_convert_mtu (mtu, &mtu_int, error)) + goto cleanup_wired; + if (!check_mac (mac, ARPHRD_ETHER, "mac", error)) + goto cleanup_wired; + if (!check_mac (cloned_mac, ARPHRD_ETHER, "cloned-mac", error)) + goto cleanup_wired; + + /* Add ethernet setting */ + s_wired = (NMSettingWired *) nm_setting_wired_new (); + nm_connection_add_setting (connection, NM_SETTING (s_wired)); + + if (mtu) + g_object_set (s_wired, NM_SETTING_WIRED_MTU, mtu_int, NULL); + if (mac) + g_object_set (s_wired, NM_SETTING_WIRED_MAC_ADDRESS, mac, NULL); + if (cloned_mac) + g_object_set (s_wired, NM_SETTING_WIRED_CLONED_MAC_ADDRESS, cloned_mac, NULL); + + success = TRUE; +cleanup_wired: + g_free (mtu); + g_free (mac); + g_free (cloned_mac); + if (!success) + return FALSE; + + } else if (!strcmp (con_type, NM_SETTING_INFINIBAND_SETTING_NAME)) { + /* Build up the settings required for 'infiniband' */ + gboolean success = FALSE; + const char *mtu_c = NULL; + char *mtu = NULL; + guint32 mtu_int = 0; + const char *mac_c = NULL; + char *mac = NULL; + const char *mode_c = NULL; + char *mode = NULL; + const char *parent_c = NULL; + char *parent = NULL; + const char *p_key_c = NULL; + char *p_key = NULL; + guint32 p_key_int = 0; + nmc_arg_t exp_args[] = { {"mtu", TRUE, &mtu_c, FALSE}, + {"mac", TRUE, &mac_c, FALSE}, + {"transport-mode", TRUE, &mode_c, FALSE}, + {"parent", TRUE, &parent_c, FALSE}, + {"p-key", TRUE, &p_key_c, FALSE}, + {NULL} }; + + if (!nmc_parse_args (exp_args, FALSE, &argc, &argv, error)) + return FALSE; + + /* Also ask for all optional arguments if '--ask' is specified. */ + mtu = g_strdup (mtu_c); + mac = g_strdup (mac_c); + mode = g_strdup (mode_c); + parent = g_strdup (parent_c); + p_key = g_strdup (p_key_c); + if (ask) + do_questionnaire_infiniband (&mtu, &mac, &mode, &parent, &p_key); + + if (!check_and_convert_mtu (mtu, &mtu_int, error)) + goto cleanup_ib; + if (!check_mac (mac, ARPHRD_INFINIBAND, "mac", error)) + goto cleanup_ib; + if (!check_infiniband_mode (&mode, error)) + goto cleanup_ib; + if (p_key) { + if (!check_infiniband_p_key (p_key, &p_key_int, error)) + goto cleanup_ib; + if (!check_infiniband_parent (parent, error)) + goto cleanup_ib; + } else if (parent) { + g_set_error (error, NMCLI_ERROR, NMC_RESULT_ERROR_USER_INPUT, + _("Error: 'parent': not valid without 'p-key'.")); + goto cleanup_ib; + } + + /* Add 'infiniband' setting */ + s_infiniband = (NMSettingInfiniband *) nm_setting_infiniband_new (); + nm_connection_add_setting (connection, NM_SETTING (s_infiniband)); + + g_object_set (s_infiniband, NM_SETTING_INFINIBAND_TRANSPORT_MODE, mode ? mode : "datagram", NULL); + if (mtu) + g_object_set (s_infiniband, NM_SETTING_INFINIBAND_MTU, mtu_int, NULL); + if (mac) + g_object_set (s_infiniband, NM_SETTING_INFINIBAND_MAC_ADDRESS, mac, NULL); + if (p_key) + g_object_set (s_infiniband, NM_SETTING_INFINIBAND_P_KEY, p_key_int, NULL); + if (parent) + g_object_set (s_infiniband, NM_SETTING_INFINIBAND_PARENT, parent, NULL); + + + success = TRUE; +cleanup_ib: + g_free (mtu); + g_free (mac); + g_free (mode); + g_free (parent); + g_free (p_key); + if (!success) + return FALSE; + + } else if (!strcmp (con_type, NM_SETTING_WIRELESS_SETTING_NAME)) { + /* Build up the settings required for 'wifi' */ + gboolean success = FALSE; + char *ssid_ask = NULL; + const char *ssid = NULL; + GBytes *ssid_bytes; + const char *mtu_c = NULL; + char *mtu = NULL; + guint32 mtu_int = 0; + const char *mac_c = NULL; + char *mac = NULL; + const char *cloned_mac_c = NULL; + char *cloned_mac = NULL; + const char *mode_c = NULL; + char *mode = NULL; + nmc_arg_t exp_args[] = { {"ssid", TRUE, &ssid, !ask}, + {"mtu", TRUE, &mtu_c, FALSE}, + {"mac", TRUE, &mac_c, FALSE}, + {"cloned-mac", TRUE, &cloned_mac_c, FALSE}, + {"mode", TRUE, &mode_c, FALSE}, + {NULL} }; + + if (!nmc_parse_args (exp_args, FALSE, &argc, &argv, error)) + return FALSE; + + if (!ssid && ask) + ssid = ssid_ask = nmc_readline (_("SSID: ")); + if (!ssid) { + g_set_error_literal (error, NMCLI_ERROR, NMC_RESULT_ERROR_USER_INPUT, + _("Error: 'ssid' is required.")); + return FALSE; + } + + /* Also ask for all optional arguments if '--ask' is specified. */ + mtu = g_strdup (mtu_c); + mac = g_strdup (mac_c); + cloned_mac = g_strdup (cloned_mac_c); + mode = g_strdup (mode_c); + if (ask) + do_questionnaire_wifi (&mtu, &mac, &cloned_mac, &mode); + + if (!check_and_convert_mtu (mtu, &mtu_int, error)) + goto cleanup_wifi; + if (!check_mac (mac, ARPHRD_ETHER, "mac", error)) + goto cleanup_wifi; + if (!check_mac (cloned_mac, ARPHRD_ETHER, "cloned-mac", error)) + goto cleanup_wifi; + if (!check_wifi_mode (&mode, error)) + goto cleanup_wifi; + + /* Add wifi setting */ + s_wifi = (NMSettingWireless *) nm_setting_wireless_new (); + nm_connection_add_setting (connection, NM_SETTING (s_wifi)); + + ssid_bytes = g_bytes_new (ssid, strlen (ssid)); + g_object_set (s_wifi, NM_SETTING_WIRELESS_SSID, ssid_bytes, NULL); + + if (mtu) + g_object_set (s_wifi, NM_SETTING_WIRELESS_MTU, mtu_int, NULL); + if (mac) + g_object_set (s_wifi, NM_SETTING_WIRELESS_MAC_ADDRESS, mac, NULL); + if (cloned_mac) + g_object_set (s_wifi, NM_SETTING_WIRELESS_CLONED_MAC_ADDRESS, cloned_mac, NULL); + if (mode) + g_object_set (s_wifi, NM_SETTING_WIRELESS_MODE, mode, NULL); + + g_bytes_unref (ssid_bytes); + + success = TRUE; +cleanup_wifi: + g_free (ssid_ask); + g_free (mtu); + g_free (mac); + g_free (cloned_mac); + g_free (mode); + if (!success) + return FALSE; + + } else if (!strcmp (con_type, NM_SETTING_WIMAX_SETTING_NAME)) { + /* Build up the settings required for 'wimax' */ + gboolean success = FALSE; + const char *nsp_name = NULL; + char *nsp_name_ask = NULL; + const char *mac_c = NULL; + char *mac = NULL; + nmc_arg_t exp_args[] = { {"nsp", TRUE, &nsp_name, !ask}, + {"mac", TRUE, &mac_c, FALSE}, + {NULL} }; + + if (!nmc_parse_args (exp_args, FALSE, &argc, &argv, error)) + return FALSE; + + if (!nsp_name && ask) + nsp_name = nsp_name_ask = nmc_readline (_("WiMAX NSP name: ")); + if (!nsp_name) { + g_set_error_literal (error, NMCLI_ERROR, NMC_RESULT_ERROR_USER_INPUT, + _("Error: 'nsp' is required.")); + goto cleanup_wimax; + } + + /* Also ask for all optional arguments if '--ask' is specified. */ + mac = g_strdup (mac_c); + if (ask) + do_questionnaire_wimax (&mac); + + if (!check_mac (mac, ARPHRD_ETHER, "mac", error)) + goto cleanup_wimax; + + /* Add 'wimax' setting */ + s_wimax = (NMSettingWimax *) nm_setting_wimax_new (); + nm_connection_add_setting (connection, NM_SETTING (s_wimax)); + g_object_set (s_wimax, NM_SETTING_WIMAX_NETWORK_NAME, nsp_name, NULL); + + if (mac) + g_object_set (s_wimax, NM_SETTING_WIMAX_MAC_ADDRESS, mac, NULL); + + success = TRUE; +cleanup_wimax: + g_free (nsp_name_ask); + g_free (mac); + if (!success) + return FALSE; + + } else if (!strcmp (con_type, NM_SETTING_PPPOE_SETTING_NAME)) { + /* Build up the settings required for 'pppoe' */ + gboolean success = FALSE; + const char *username = NULL; + char *username_ask = NULL; + const char *password_c = NULL; + char *password = NULL; + const char *service_c = NULL; + char *service = NULL; + const char *mtu_c = NULL; + char *mtu = NULL; + guint32 mtu_int = 0; + const char *mac_c = NULL; + char *mac = NULL; + nmc_arg_t exp_args[] = { {"username", TRUE, &username, !ask}, + {"password", TRUE, &password_c, FALSE}, + {"service", TRUE, &service_c, FALSE}, + {"mtu", TRUE, &mtu_c, FALSE}, + {"mac", TRUE, &mac_c, FALSE}, + {NULL} }; + + if (!nmc_parse_args (exp_args, FALSE, &argc, &argv, error)) + return FALSE; + + if (!username && ask) + username = username_ask = nmc_readline (_("PPPoE username: ")); + if (!username) { + g_set_error_literal (error, NMCLI_ERROR, NMC_RESULT_ERROR_USER_INPUT, + _("Error: 'username' is required.")); + goto cleanup_pppoe; + } + + /* Also ask for all optional arguments if '--ask' is specified. */ + password = g_strdup (password_c); + service = g_strdup (service_c); + mtu = g_strdup (mtu_c); + mac = g_strdup (mac_c); + if (ask) + do_questionnaire_pppoe (&password, &service, &mtu, &mac); + + if (!check_and_convert_mtu (mtu, &mtu_int, error)) + goto cleanup_pppoe; + if (!check_mac (mac, ARPHRD_ETHER, "mac", error)) + goto cleanup_pppoe; + + /* Add 'pppoe' setting */ + s_pppoe = (NMSettingPppoe *) nm_setting_pppoe_new (); + nm_connection_add_setting (connection, NM_SETTING (s_pppoe)); + g_object_set (s_pppoe, NM_SETTING_PPPOE_USERNAME, username, NULL); + g_object_set (s_pppoe, NM_SETTING_PPPOE_PASSWORD, password, NULL); + g_object_set (s_pppoe, NM_SETTING_PPPOE_SERVICE, service, NULL); + + /* Add ethernet setting */ + s_wired = (NMSettingWired *) nm_setting_wired_new (); + nm_connection_add_setting (connection, NM_SETTING (s_wired)); + if (mtu) + g_object_set (s_wired, NM_SETTING_WIRED_MTU, mtu_int, NULL); + if (mac) + g_object_set (s_wired, NM_SETTING_WIRED_MAC_ADDRESS, mac, NULL); + + success = TRUE; +cleanup_pppoe: + g_free (username_ask); + g_free (password); + g_free (service); + g_free (mtu); + g_free (mac); + if (!success) + return FALSE; + + } else if ( !strcmp (con_type, NM_SETTING_GSM_SETTING_NAME) + || !strcmp (con_type, NM_SETTING_CDMA_SETTING_NAME)) { + /* Build up the settings required for 'gsm' or 'cdma' mobile broadband */ + gboolean success = FALSE; + const char *apn = NULL; + char *apn_ask = NULL; + const char *user_c = NULL; + char *user = NULL; + const char *password_c = NULL; + char *password = NULL; + gboolean is_gsm; + int i = 0; + nmc_arg_t gsm_args[] = { {NULL}, {NULL}, {NULL}, /* placeholders */ + {NULL} }; + + is_gsm = !strcmp (con_type, NM_SETTING_GSM_SETTING_NAME); + + if (is_gsm) + gsm_args[i++] = (nmc_arg_t) {"apn", TRUE, &apn, !ask}; + gsm_args[i++] = (nmc_arg_t) {"user", TRUE, &user_c, FALSE}; + gsm_args[i++] = (nmc_arg_t) {"password", TRUE, &password_c, FALSE}; + gsm_args[i++] = (nmc_arg_t) {NULL}; + + if (!nmc_parse_args (gsm_args, FALSE, &argc, &argv, error)) + return FALSE; + + if (!apn && ask && is_gsm) + apn = apn_ask = nmc_readline (_("APN: ")); + if (!apn && is_gsm) { + g_set_error_literal (error, NMCLI_ERROR, NMC_RESULT_ERROR_USER_INPUT, + _("Error: 'apn' is required.")); + goto cleanup_mobile; + } + + /* Also ask for all optional arguments if '--ask' is specified. */ + user = g_strdup (user_c); + password = g_strdup (password_c); + if (ask) + do_questionnaire_mobile (&user, &password); + + if (is_gsm) { + g_object_set (s_con, NM_SETTING_CONNECTION_TYPE, NM_SETTING_GSM_SETTING_NAME, NULL); + + /* Add 'gsm' setting */ + s_gsm = (NMSettingGsm *) nm_setting_gsm_new (); + nm_connection_add_setting (connection, NM_SETTING (s_gsm)); + g_object_set (s_gsm, + NM_SETTING_GSM_NUMBER, "*99#", + NM_SETTING_GSM_APN, apn, + NM_SETTING_GSM_USERNAME, user, + NM_SETTING_GSM_PASSWORD, password, + NULL); + g_free (apn_ask); + } else { + g_object_set (s_con, NM_SETTING_CONNECTION_TYPE, NM_SETTING_CDMA_SETTING_NAME, NULL); + + /* Add 'cdma' setting */ + s_cdma = (NMSettingCdma *) nm_setting_cdma_new (); + nm_connection_add_setting (connection, NM_SETTING (s_cdma)); + g_object_set (s_cdma, + NM_SETTING_CDMA_NUMBER, "#777", + NM_SETTING_CDMA_USERNAME, user, + NM_SETTING_CDMA_PASSWORD, password, + NULL); + } + + success = TRUE; +cleanup_mobile: + g_free (user); + g_free (password); + if (!success) + return FALSE; + + } else if (!strcmp (con_type, NM_SETTING_BLUETOOTH_SETTING_NAME)) { + /* Build up the settings required for 'bluetooth' */ + gboolean success = FALSE; + const char *addr = NULL; + char *addr_ask = NULL; + const char *bt_type_c = NULL; + char *bt_type = NULL; + nmc_arg_t exp_args[] = { {"addr", TRUE, &addr, !ask}, + {"bt-type", TRUE, &bt_type_c, FALSE}, + {NULL} }; + + if (!nmc_parse_args (exp_args, FALSE, &argc, &argv, error)) + return FALSE; + + if (!addr && ask) + addr = addr_ask = nmc_readline (_("Bluetooth device address: ")); + if (!addr) { + g_set_error_literal (error, NMCLI_ERROR, NMC_RESULT_ERROR_USER_INPUT, + _("Error: 'addr' is required.")); + return FALSE; + } + if (!check_mac (addr, ARPHRD_ETHER, "addr", error)) + goto cleanup_bt; + + /* Also ask for all optional arguments if '--ask' is specified. */ + bt_type = g_strdup (bt_type_c); + if (ask) + do_questionnaire_bluetooth (&bt_type); + + /* Default to 'panu' if bt-type is not provided. */ + if (!bt_type) + bt_type = g_strdup (NM_SETTING_BLUETOOTH_TYPE_PANU); + + /* Add 'bluetooth' setting */ + s_bt = (NMSettingBluetooth *) nm_setting_bluetooth_new (); + nm_connection_add_setting (connection, NM_SETTING (s_bt)); + + if (addr) + g_object_set (s_bt, NM_SETTING_BLUETOOTH_BDADDR, addr, NULL); + + /* 'dun' type requires adding 'gsm' or 'cdma' setting */ + if ( !strcmp (bt_type, NM_SETTING_BLUETOOTH_TYPE_DUN) + || !strcmp (bt_type, NM_SETTING_BLUETOOTH_TYPE_DUN"-gsm")) { + bt_type = g_strdup (NM_SETTING_BLUETOOTH_TYPE_DUN); + s_gsm = (NMSettingGsm *) nm_setting_gsm_new (); + nm_connection_add_setting (connection, NM_SETTING (s_gsm)); + g_object_set (s_gsm, NM_SETTING_GSM_NUMBER, "*99#", NULL); +// g_object_set (s_gsm, NM_SETTING_GSM_APN, "FIXME", NULL; + + } else if (!strcmp (bt_type, NM_SETTING_BLUETOOTH_TYPE_DUN"-cdma")) { + bt_type = g_strdup (NM_SETTING_BLUETOOTH_TYPE_DUN); + s_cdma = (NMSettingCdma *) nm_setting_cdma_new (); + nm_connection_add_setting (connection, NM_SETTING (s_cdma)); + g_object_set (s_cdma, NM_SETTING_CDMA_NUMBER, "#777", NULL); + + } else if (!strcmp (bt_type, NM_SETTING_BLUETOOTH_TYPE_PANU)) { + /* no op */ + } else { + g_set_error (error, NMCLI_ERROR, NMC_RESULT_ERROR_USER_INPUT, + _("Error: 'bt-type': '%s' not valid; use [%s, %s (%s), %s]."), + bt_type, NM_SETTING_BLUETOOTH_TYPE_PANU, NM_SETTING_BLUETOOTH_TYPE_DUN, + NM_SETTING_BLUETOOTH_TYPE_DUN"-gsm", NM_SETTING_BLUETOOTH_TYPE_DUN"-cdma"); + goto cleanup_bt; + } + g_object_set (s_bt, NM_SETTING_BLUETOOTH_TYPE, bt_type, NULL); + + success = TRUE; +cleanup_bt: + g_free (addr_ask); + g_free (bt_type); + if (!success) + return FALSE; + + } else if (!strcmp (con_type, NM_SETTING_VLAN_SETTING_NAME)) { + /* Build up the settings required for 'vlan' */ + gboolean success = FALSE; + const char *parent = NULL; + char *parent_ask = NULL; + const char *vlan_id = NULL; + char *vlan_id_ask = NULL; + unsigned long id = 0; + const char *flags_c = NULL; + char *flags = NULL; + guint32 flags_int = 0; + const char *ingress_c = NULL, *egress_c = NULL; + char *ingress = NULL, *egress = NULL; + char **ingress_arr = NULL, **egress_arr = NULL, **p; + const char *mtu_c = NULL; + char *mtu = NULL; + guint32 mtu_int; + GByteArray *addr_array = NULL; + nmc_arg_t exp_args[] = { {"dev", TRUE, &parent, !ask}, + {"id", TRUE, &vlan_id, !ask}, + {"flags", TRUE, &flags_c, FALSE}, + {"ingress", TRUE, &ingress_c, FALSE}, + {"egress", TRUE, &egress_c, FALSE}, + {"mtu", TRUE, &mtu_c, FALSE}, + {NULL} }; + + if (!nmc_parse_args (exp_args, FALSE, &argc, &argv, error)) + return FALSE; + + if (!parent && ask) + parent = parent_ask = nmc_readline (_("VLAN parent device or connection UUID: ")); + if (!parent) { + g_set_error_literal (error, NMCLI_ERROR, NMC_RESULT_ERROR_USER_INPUT, + _("Error: 'dev' is required.")); + return FALSE; + } + if (!vlan_id && ask) + vlan_id = vlan_id_ask = nmc_readline (_("VLAN ID <0-4095>: ")); + if (!vlan_id) { + g_set_error_literal (error, NMCLI_ERROR, NMC_RESULT_ERROR_USER_INPUT, + _("Error: 'id' is required.")); + goto cleanup_vlan; + } + if (vlan_id) { + if (!nmc_string_to_uint (vlan_id, TRUE, 0, 4095, &id)) { + g_set_error (error, NMCLI_ERROR, NMC_RESULT_ERROR_USER_INPUT, + _("Error: 'id': '%s' is not valid; use <0-4095>."), + vlan_id); + goto cleanup_vlan; + } + } + + if ( !(addr_array = nm_utils_hwaddr_atoba (parent, ETH_ALEN)) + && !nm_utils_is_uuid (parent) + && !nm_utils_iface_valid_name (parent)) { + g_set_error (error, NMCLI_ERROR, NMC_RESULT_ERROR_USER_INPUT, + _("Error: 'dev': '%s' is neither UUID, interface name, nor MAC."), + parent); + goto cleanup_vlan; + } + + /* Also ask for all optional arguments if '--ask' is specified. */ + mtu = g_strdup (mtu_c); + flags = g_strdup (flags_c); + ingress = g_strdup (ingress_c); + egress = g_strdup (egress_c); + if (ask) + do_questionnaire_vlan (&mtu, &flags, &ingress, &egress); + + if (!check_and_convert_mtu (mtu, &mtu_int, error)) + goto cleanup_vlan; + if (!check_and_convert_vlan_flags (flags, &flags_int, error)) + goto cleanup_vlan; + if (!check_and_convert_vlan_prio_maps (ingress, NM_VLAN_INGRESS_MAP, &ingress_arr, error)) + goto cleanup_vlan; + if (!check_and_convert_vlan_prio_maps (egress, NM_VLAN_EGRESS_MAP, &egress_arr, error)) + goto cleanup_vlan; + + /* Add 'vlan' setting */ + s_vlan = (NMSettingVlan *) nm_setting_vlan_new (); + nm_connection_add_setting (connection, NM_SETTING (s_vlan)); + + /* Add 'wired' setting if necessary */ + if (mtu || addr_array) { + s_wired = (NMSettingWired *) nm_setting_wired_new (); + nm_connection_add_setting (connection, NM_SETTING (s_wired)); + + if (mtu) + g_object_set (s_wired, NM_SETTING_WIRED_MTU, mtu_int, NULL); + if (addr_array) + g_object_set (s_wired, NM_SETTING_WIRED_MAC_ADDRESS, addr_array, NULL); + } + + /* Set 'vlan' properties */ + if (!addr_array) + g_object_set (s_vlan, NM_SETTING_VLAN_PARENT, parent, NULL); + + g_object_set (s_vlan, NM_SETTING_VLAN_ID, id, NULL); + + if (flags) + g_object_set (s_vlan, NM_SETTING_VLAN_FLAGS, flags_int, NULL); + for (p = ingress_arr; p && *p; p++) + nm_setting_vlan_add_priority_str (s_vlan, NM_VLAN_INGRESS_MAP, *p); + for (p = egress_arr; p && *p; p++) + nm_setting_vlan_add_priority_str (s_vlan, NM_VLAN_EGRESS_MAP, *p); + + success = TRUE; +cleanup_vlan: + g_free (mtu); + g_free (flags); + g_free (ingress); + g_free (egress); + if (addr_array) + g_byte_array_free (addr_array, TRUE); + g_free (parent_ask); + g_free (vlan_id_ask); + g_strfreev (ingress_arr); + g_strfreev (egress_arr); + if (!success) + return FALSE; + + } else if (!strcmp (con_type, NM_SETTING_BOND_SETTING_NAME)) { + /* Build up the settings required for 'bond' */ + gboolean success = FALSE; + const char *ifname = NULL; + const char *bond_mode_c = NULL; + char *bond_mode = NULL; + const char *bond_primary_c = NULL; + char *bond_primary = NULL; + const char *bond_miimon_c = NULL; + char *bond_miimon = NULL; + const char *bond_downdelay_c = NULL; + char *bond_downdelay = NULL; + const char *bond_updelay_c = NULL; + char *bond_updelay = NULL; + const char *bond_arpinterval_c = NULL; + char *bond_arpinterval = NULL; + const char *bond_arpiptarget_c = NULL; + char *bond_arpiptarget = NULL; + const char *bond_lacp_rate_c = NULL; + char *bond_lacp_rate = NULL; + nmc_arg_t exp_args[] = { {"mode", TRUE, &bond_mode_c, FALSE}, + {"primary", TRUE, &bond_primary_c, FALSE}, + {"miimon", TRUE, &bond_miimon_c, FALSE}, + {"downdelay", TRUE, &bond_downdelay_c, FALSE}, + {"updelay", TRUE, &bond_updelay_c, FALSE}, + {"arp-interval", TRUE, &bond_arpinterval_c, FALSE}, + {"arp-ip-target", TRUE, &bond_arpiptarget_c, FALSE}, + {"lacp-rate", TRUE, &bond_lacp_rate_c, FALSE}, + {NULL} }; + + if (!nmc_parse_args (exp_args, FALSE, &argc, &argv, error)) + return FALSE; + + /* Also ask for all optional arguments if '--ask' is specified. */ + bond_mode = g_strdup (bond_mode_c); + bond_primary = g_strdup (bond_primary_c); + bond_miimon = g_strdup (bond_miimon_c); + bond_downdelay = g_strdup (bond_downdelay_c); + bond_updelay = g_strdup (bond_updelay_c); + bond_arpinterval = g_strdup (bond_arpinterval_c); + bond_arpiptarget = g_strdup (bond_arpiptarget_c); + bond_lacp_rate = g_strdup (bond_lacp_rate_c); + if (ask) + do_questionnaire_bond (&bond_mode, &bond_primary, &bond_miimon, + &bond_downdelay, &bond_updelay, + &bond_arpinterval, &bond_arpiptarget, + &bond_lacp_rate); + + /* Generate ifname if connection doesn't have one */ + ifname = nm_setting_connection_get_interface_name (s_con); + if (!ifname) { + char *bond_ifname = unique_master_iface_ifname (all_connections, "nm-bond"); + + g_object_set (s_con, + NM_SETTING_CONNECTION_INTERFACE_NAME, bond_ifname, + NULL); + g_free (bond_ifname); + } + + /* Add 'bond' setting */ + s_bond = (NMSettingBond *) nm_setting_bond_new (); + nm_connection_add_setting (connection, NM_SETTING (s_bond)); + + /* Set bond options */ + if (bond_mode) { + GError *err = NULL; + const char *bm; + if (!(bm = nmc_bond_validate_mode (bond_mode, &err))) { + g_set_error (error, NMCLI_ERROR, NMC_RESULT_ERROR_USER_INPUT, + _("Error: 'mode': %s."), err->message); + g_clear_error (&err); + goto cleanup_bond; + } + nm_setting_bond_add_option (s_bond, NM_SETTING_BOND_OPTION_MODE, bm); + } + if (bond_primary) { + if (!nm_utils_iface_valid_name (bond_primary)) { + g_set_error (error, NMCLI_ERROR, NMC_RESULT_ERROR_USER_INPUT, + _("Error: 'primary': '%s' is not a valid interface name."), + bond_primary); + goto cleanup_bond; + } + nm_setting_bond_add_option (s_bond, NM_SETTING_BOND_OPTION_PRIMARY, bond_primary); + } + if (bond_miimon) + nm_setting_bond_add_option (s_bond, NM_SETTING_BOND_OPTION_MIIMON, bond_miimon); + if (bond_downdelay && strcmp (bond_downdelay, "0") != 0) + nm_setting_bond_add_option (s_bond, NM_SETTING_BOND_OPTION_DOWNDELAY, bond_downdelay); + if (bond_updelay && strcmp (bond_updelay, "0") != 0) + nm_setting_bond_add_option (s_bond, NM_SETTING_BOND_OPTION_UPDELAY, bond_updelay); + if (bond_arpinterval && strcmp (bond_arpinterval, "0") != 0) + nm_setting_bond_add_option (s_bond, NM_SETTING_BOND_OPTION_ARP_INTERVAL, bond_arpinterval); + if (bond_arpiptarget) + nm_setting_bond_add_option (s_bond, NM_SETTING_BOND_OPTION_ARP_IP_TARGET, bond_arpiptarget); + if (bond_lacp_rate) + nm_setting_bond_add_option (s_bond, NM_SETTING_BOND_OPTION_LACP_RATE, bond_lacp_rate); + + success = TRUE; +cleanup_bond: + g_free (bond_mode); + g_free (bond_primary); + g_free (bond_miimon); + g_free (bond_downdelay); + g_free (bond_updelay); + g_free (bond_arpinterval); + g_free (bond_arpiptarget); + g_free (bond_lacp_rate); + if (!success) + return FALSE; + + } else if (!strcmp (con_type, "bond-slave")) { + /* Build up the settings required for 'bond-slave' */ + const char *master = NULL; + char *master_ask = NULL; + const char *checked_master = NULL; + const char *type = NULL; + nmc_arg_t exp_args[] = { {"master", TRUE, &master, !ask}, + {"type", TRUE, &type, FALSE}, + {NULL} }; + + /* Set global variables for use in TAB completion */ + nmc_tab_completion.con_type = NM_SETTING_BOND_SETTING_NAME; + + if (!nmc_parse_args (exp_args, TRUE, &argc, &argv, error)) + return FALSE; + + if (!master && ask) + master = master_ask = nmc_readline (PROMPT_BOND_MASTER); + if (!master) { + g_set_error_literal (error, NMCLI_ERROR, NMC_RESULT_ERROR_USER_INPUT, + _("Error: 'master' is required.")); + return FALSE; + } + /* Verify master argument */ + checked_master = verify_master_for_slave (all_connections, master, NM_SETTING_BOND_SETTING_NAME); + if (!checked_master) + g_print (_("Warning: master='%s' doesn't refer to any existing profile.\n"), master); + + if (type) + g_print (_("Warning: 'type' is currently ignored. " + "We only support ethernet slaves for now.\n")); + + /* Change properties in 'connection' setting */ + g_object_set (s_con, + NM_SETTING_CONNECTION_TYPE, NM_SETTING_WIRED_SETTING_NAME, + NM_SETTING_CONNECTION_MASTER, checked_master ? checked_master : _strip_master_prefix (master, NULL), + NM_SETTING_CONNECTION_SLAVE_TYPE, NM_SETTING_BOND_SETTING_NAME, + NULL); + + /* Add ethernet setting */ + s_wired = (NMSettingWired *) nm_setting_wired_new (); + nm_connection_add_setting (connection, NM_SETTING (s_wired)); + + g_free (master_ask); + + } else if (!strcmp (con_type, NM_SETTING_TEAM_SETTING_NAME)) { + /* Build up the settings required for 'team' */ + gboolean success = FALSE; + const char *ifname = NULL; + const char *config_c = NULL; + char *config = NULL; + char *json = NULL; + nmc_arg_t exp_args[] = { {"config", TRUE, &config_c, FALSE}, + {NULL} }; + + if (!nmc_parse_args (exp_args, FALSE, &argc, &argv, error)) + return FALSE; + + /* Also ask for all optional arguments if '--ask' is specified. */ + config = g_strdup (config_c); + if (ask) + do_questionnaire_team (&config); + + /* Generate ifname if conneciton doesn't have one */ + ifname = nm_setting_connection_get_interface_name (s_con); + if (!ifname) { + char *team_ifname = unique_master_iface_ifname (all_connections, "nm-team"); + + g_object_set (s_con, + NM_SETTING_CONNECTION_INTERFACE_NAME, team_ifname, + NULL); + g_free (team_ifname); + } + + /* Add 'team' setting */ + s_team = (NMSettingTeam *) nm_setting_team_new (); + nm_connection_add_setting (connection, NM_SETTING (s_team)); + + if (!nmc_team_check_config (config, &json, error)) { + g_prefix_error (error, _("Error: ")); + goto cleanup_team; + } + + /* Set team options */ + g_object_set (s_team, NM_SETTING_TEAM_CONFIG, json, NULL); + + success = TRUE; +cleanup_team: + g_free (config); + g_free (json); + if (!success) + return FALSE; + + } else if (!strcmp (con_type, "team-slave")) { + /* Build up the settings required for 'team-slave' */ + gboolean success = FALSE; + const char *master = NULL; + char *master_ask = NULL; + const char *checked_master = NULL; + const char *type = NULL; + const char *config_c = NULL; + char *config = NULL; + char *json = NULL; + nmc_arg_t exp_args[] = { {"master", TRUE, &master, !ask}, + {"type", TRUE, &type, FALSE}, + {"config", TRUE, &config_c, FALSE}, + {NULL} }; + + /* Set global variables for use in TAB completion */ + nmc_tab_completion.con_type = NM_SETTING_TEAM_SETTING_NAME; + + if (!nmc_parse_args (exp_args, TRUE, &argc, &argv, error)) + return FALSE; + + if (!master && ask) + master = master_ask = nmc_readline (PROMPT_TEAM_MASTER); + if (!master) { + g_set_error_literal (error, NMCLI_ERROR, NMC_RESULT_ERROR_USER_INPUT, + _("Error: 'master' is required.")); + return FALSE; + } + /* Verify master argument */ + checked_master = verify_master_for_slave (all_connections, master, NM_SETTING_TEAM_SETTING_NAME); + if (!checked_master) + g_print (_("Warning: master='%s' doesn't refer to any existing profile.\n"), master); + + /* Also ask for all optional arguments if '--ask' is specified. */ + config = g_strdup (config_c); + if (ask) + do_questionnaire_team_slave (&config); + + if (type) + g_print (_("Warning: 'type' is currently ignored. " + "We only support ethernet slaves for now.\n")); + + /* Add 'team-port' setting */ + s_team_port = (NMSettingTeamPort *) nm_setting_team_port_new (); + nm_connection_add_setting (connection, NM_SETTING (s_team_port)); + + if (!nmc_team_check_config (config, &json, error)) { + g_prefix_error (error, _("Error: ")); + goto cleanup_team_slave; + } + + /* Set team-port options */ + g_object_set (s_team_port, NM_SETTING_TEAM_PORT_CONFIG, json, NULL); + + /* Change properties in 'connection' setting */ + g_object_set (s_con, + NM_SETTING_CONNECTION_TYPE, NM_SETTING_WIRED_SETTING_NAME, + NM_SETTING_CONNECTION_MASTER, checked_master ? checked_master : _strip_master_prefix (master, NULL), + NM_SETTING_CONNECTION_SLAVE_TYPE, NM_SETTING_TEAM_SETTING_NAME, + NULL); + + /* Add ethernet setting */ + s_wired = (NMSettingWired *) nm_setting_wired_new (); + nm_connection_add_setting (connection, NM_SETTING (s_wired)); + + success = TRUE; +cleanup_team_slave: + g_free (master_ask); + g_free (config); + g_free (json); + if (!success) + return FALSE; + + } else if (!strcmp (con_type, NM_SETTING_BRIDGE_SETTING_NAME)) { + /* Build up the settings required for 'bridge' */ + gboolean success = FALSE; + const char *ifname = NULL; + const char *stp_c = NULL; + char *stp = NULL; + const char *priority_c = NULL; + char *priority = NULL; + const char *fwd_delay_c = NULL; + char *fwd_delay = NULL; + const char *hello_time_c = NULL; + char *hello_time = NULL; + const char *max_age_c = NULL; + char *max_age = NULL; + const char *ageing_time_c = NULL; + char *ageing_time = NULL; + gboolean stp_bool; + unsigned long stp_prio_int, fwd_delay_int, hello_time_int, + max_age_int, ageing_time_int; + const char *mac_c = NULL; + char *mac = NULL; + nmc_arg_t exp_args[] = { {"stp", TRUE, &stp_c, FALSE}, + {"priority", TRUE, &priority_c, FALSE}, + {"forward-delay", TRUE, &fwd_delay_c, FALSE}, + {"hello-time", TRUE, &hello_time_c, FALSE}, + {"max-age", TRUE, &max_age_c, FALSE}, + {"ageing-time", TRUE, &ageing_time_c, FALSE}, + {"mac", TRUE, &mac_c, FALSE}, + {NULL} }; + + if (!nmc_parse_args (exp_args, FALSE, &argc, &argv, error)) + return FALSE; + + /* Also ask for all optional arguments if '--ask' is specified. */ + stp = g_strdup (stp_c); + priority = g_strdup (priority_c); + fwd_delay = g_strdup (fwd_delay_c); + hello_time = g_strdup (hello_time_c); + max_age = g_strdup (max_age_c); + ageing_time = g_strdup (ageing_time_c); + mac = g_strdup (mac_c); + if (ask) + do_questionnaire_bridge (&stp, &priority, &fwd_delay, &hello_time, + &max_age, &ageing_time, &mac); + + /* Generate ifname if conneciton doesn't have one */ + ifname = nm_setting_connection_get_interface_name (s_con); + if (!ifname) { + char *bridge_ifname = unique_master_iface_ifname (all_connections, "nm-bridge"); + + g_object_set (s_con, + NM_SETTING_CONNECTION_INTERFACE_NAME, bridge_ifname, + NULL); + g_free (bridge_ifname); + } + + if (stp) { + GError *tmp_err = NULL; + if (!nmc_string_to_bool (stp, &stp_bool, &tmp_err)) { + g_set_error (error, NMCLI_ERROR, NMC_RESULT_ERROR_USER_INPUT, + _("Error: 'stp': %s."), tmp_err->message); + g_clear_error (&tmp_err); + goto cleanup_bridge; + } + } + + /* Add 'bond' setting */ + /* Must be done *before* bridge_prop_string_to_uint() so that the type is known */ + s_bridge = (NMSettingBridge *) nm_setting_bridge_new (); + nm_connection_add_setting (connection, NM_SETTING (s_bridge)); + + if (priority) + if (!bridge_prop_string_to_uint (priority, "priority", NM_TYPE_SETTING_BRIDGE, + NM_SETTING_BRIDGE_PRIORITY, &stp_prio_int, error)) + goto cleanup_bridge; + if (fwd_delay) + if (!bridge_prop_string_to_uint (fwd_delay, "forward-delay", NM_TYPE_SETTING_BRIDGE, + NM_SETTING_BRIDGE_FORWARD_DELAY, &fwd_delay_int, error)) + goto cleanup_bridge; + if (hello_time) + if (!bridge_prop_string_to_uint (hello_time, "hello-time", NM_TYPE_SETTING_BRIDGE, + NM_SETTING_BRIDGE_HELLO_TIME, &hello_time_int, error)) + goto cleanup_bridge; + if (max_age) + if (!bridge_prop_string_to_uint (max_age, "max-age", NM_TYPE_SETTING_BRIDGE, + NM_SETTING_BRIDGE_MAX_AGE, &max_age_int, error)) + goto cleanup_bridge; + if (ageing_time) + if (!bridge_prop_string_to_uint (ageing_time, "ageing-time", NM_TYPE_SETTING_BRIDGE, + NM_SETTING_BRIDGE_AGEING_TIME, &ageing_time_int, error)) + goto cleanup_bridge; + if (!check_mac (mac, ARPHRD_ETHER, "mac", error)) + goto cleanup_bridge; + + /* Set bridge options */ + if (stp) + g_object_set (s_bridge, NM_SETTING_BRIDGE_STP, stp_bool, NULL); + if (priority) + g_object_set (s_bridge, NM_SETTING_BRIDGE_PRIORITY, stp_prio_int, NULL); + if (fwd_delay) + g_object_set (s_bridge, NM_SETTING_BRIDGE_FORWARD_DELAY, fwd_delay_int, NULL); + if (hello_time) + g_object_set (s_bridge, NM_SETTING_BRIDGE_HELLO_TIME, hello_time_int, NULL); + if (max_age) + g_object_set (s_bridge, NM_SETTING_BRIDGE_MAX_AGE, max_age_int, NULL); + if (ageing_time) + g_object_set (s_bridge, NM_SETTING_BRIDGE_AGEING_TIME, ageing_time_int, NULL); + if (mac) + g_object_set (s_bridge, NM_SETTING_BRIDGE_MAC_ADDRESS, mac, NULL); + + success = TRUE; +cleanup_bridge: + g_free (stp); + g_free (priority); + g_free (fwd_delay); + g_free (hello_time); + g_free (max_age); + g_free (ageing_time); + g_free (mac); + if (!success) + return FALSE; + + } else if (!strcmp (con_type, "bridge-slave")) { + /* Build up the settings required for 'bridge-slave' */ + gboolean success = FALSE; + const char *master = NULL; + char *master_ask = NULL; + const char *checked_master = NULL; + const char *type = NULL; + const char *priority_c = NULL; + char *priority = NULL; + const char *path_cost_c = NULL; + char *path_cost = NULL; + const char *hairpin_c = NULL; + char *hairpin = NULL; + unsigned long prio_int, path_cost_int; + gboolean hairpin_bool; + nmc_arg_t exp_args[] = { {"master", TRUE, &master, !ask}, + {"type", TRUE, &type, FALSE}, + {"priority", TRUE, &priority_c, FALSE}, + {"path-cost", TRUE, &path_cost_c, FALSE}, + {"hairpin", TRUE, &hairpin_c, FALSE}, + {NULL} }; + + /* Set global variables for use in TAB completion */ + nmc_tab_completion.con_type = NM_SETTING_BRIDGE_SETTING_NAME; + + if (!nmc_parse_args (exp_args, TRUE, &argc, &argv, error)) + return FALSE; + + if (!master && ask) + master = master_ask = nmc_readline (PROMPT_BRIDGE_MASTER); + if (!master) { + g_set_error_literal (error, NMCLI_ERROR, NMC_RESULT_ERROR_USER_INPUT, + _("Error: 'master' is required.")); + return FALSE; + } + /* Verify master argument */ + checked_master = verify_master_for_slave (all_connections, master, NM_SETTING_BRIDGE_SETTING_NAME); + if (!checked_master) + g_print (_("Warning: master='%s' doesn't refer to any existing profile.\n"), master); + + if (type) + g_print (_("Warning: 'type' is currently ignored. " + "We only support ethernet slaves for now.\n")); + + /* Add 'bridge-port' setting */ + /* Must be done *before* bridge_prop_string_to_uint() so that the type is known */ + s_bridge_port = (NMSettingBridgePort *) nm_setting_bridge_port_new (); + nm_connection_add_setting (connection, NM_SETTING (s_bridge_port)); + + /* Also ask for all optional arguments if '--ask' is specified. */ + priority = g_strdup (priority_c); + path_cost = g_strdup (path_cost_c); + hairpin = g_strdup (hairpin_c); + if (ask) + do_questionnaire_bridge_slave (&priority, &path_cost, &hairpin); + + if (priority) + if (!bridge_prop_string_to_uint (priority, "priority", NM_TYPE_SETTING_BRIDGE_PORT, + NM_SETTING_BRIDGE_PORT_PRIORITY, &prio_int, error)) + goto cleanup_bridge_slave; + if (path_cost) + if (!bridge_prop_string_to_uint (path_cost, "path-cost", NM_TYPE_SETTING_BRIDGE_PORT, + NM_SETTING_BRIDGE_PORT_PATH_COST, &path_cost_int, error)) + goto cleanup_bridge_slave; + if (hairpin) { + GError *tmp_err = NULL; + if (!nmc_string_to_bool (hairpin, &hairpin_bool, &tmp_err)) { + g_set_error (error, NMCLI_ERROR, NMC_RESULT_ERROR_USER_INPUT, + _("Error: 'hairpin': %s."), tmp_err->message); + g_clear_error (&tmp_err); + goto cleanup_bridge_slave; + } + } + + /* Change properties in 'connection' setting */ + g_object_set (s_con, + NM_SETTING_CONNECTION_TYPE, NM_SETTING_WIRED_SETTING_NAME, + NM_SETTING_CONNECTION_MASTER, checked_master ? checked_master : _strip_master_prefix (master, NULL), + NM_SETTING_CONNECTION_SLAVE_TYPE, NM_SETTING_BRIDGE_SETTING_NAME, + NULL); + + /* Add ethernet setting */ + s_wired = (NMSettingWired *) nm_setting_wired_new (); + nm_connection_add_setting (connection, NM_SETTING (s_wired)); + + if (priority) + g_object_set (s_bridge_port, NM_SETTING_BRIDGE_PORT_PRIORITY, prio_int, NULL); + if (path_cost) + g_object_set (s_bridge_port, NM_SETTING_BRIDGE_PORT_PATH_COST, path_cost_int, NULL); + if (hairpin) + g_object_set (s_bridge_port, NM_SETTING_BRIDGE_PORT_HAIRPIN_MODE, hairpin_bool, NULL); + + success = TRUE; +cleanup_bridge_slave: + g_free (master_ask); + g_free (priority); + g_free (path_cost); + g_free (hairpin); + if (!success) + return FALSE; + + } else if (!strcmp (con_type, NM_SETTING_VPN_SETTING_NAME)) { + /* Build up the settings required for 'vpn' */ + gboolean success = FALSE; + const char *vpn_type = NULL; + char *vpn_type_ask = NULL; + const char *user_c = NULL; + char *user = NULL; + const char *st; + char *service_type = NULL; + nmc_arg_t exp_args[] = { {"vpn-type", TRUE, &vpn_type, !ask}, + {"user", TRUE, &user_c, FALSE}, + {NULL} }; + + if (!nmc_parse_args (exp_args, FALSE, &argc, &argv, error)) + return FALSE; + + if (!vpn_type && ask) + vpn_type = vpn_type_ask = nmc_readline (PROMPT_VPN_TYPE); + if (!vpn_type) { + g_set_error_literal (error, NMCLI_ERROR, NMC_RESULT_ERROR_USER_INPUT, + _("Error: 'vpn-type' is required.")); + goto cleanup_vpn; + } + if (vpn_type_ask) + vpn_type = g_strstrip (vpn_type_ask); + + if (!(st = nmc_string_is_valid (vpn_type, nmc_known_vpns, NULL))) { + g_print (_("Warning: 'vpn-type': %s not known.\n"), vpn_type); + st = vpn_type; + } + service_type = g_strdup_printf ("%s.%s", NM_DBUS_INTERFACE, st); + + /* Also ask for all optional arguments if '--ask' is specified. */ + user = g_strdup (user_c); + if (ask) + do_questionnaire_vpn (&user); + + /* Add 'vpn' setting */ + s_vpn = (NMSettingVpn *) nm_setting_vpn_new (); + nm_connection_add_setting (connection, NM_SETTING (s_vpn)); + + g_object_set (s_vpn, NM_SETTING_VPN_SERVICE_TYPE, service_type, NULL); + g_object_set (s_vpn, NM_SETTING_VPN_USER_NAME, user, NULL); + + success = TRUE; +cleanup_vpn: + g_free (vpn_type_ask); + g_free (service_type); + g_free (user); + if (!success) + return FALSE; + + } else if (!strcmp (con_type, NM_SETTING_OLPC_MESH_SETTING_NAME)) { + /* Build up the settings required for 'olpc' */ + gboolean success = FALSE; + char *ssid_ask = NULL; + const char *ssid = NULL; + GBytes *ssid_bytes; + const char *channel_c = NULL; + char *channel = NULL; + unsigned long chan; + const char *dhcp_anycast_c = NULL; + char *dhcp_anycast = NULL; + nmc_arg_t exp_args[] = { {"ssid", TRUE, &ssid, !ask}, + {"channel", TRUE, &channel_c, FALSE}, + {"dhcp-anycast", TRUE, &dhcp_anycast_c, FALSE}, + {NULL} }; + + if (!nmc_parse_args (exp_args, FALSE, &argc, &argv, error)) + return FALSE; + + if (!ssid && ask) + ssid = ssid_ask = nmc_readline (_("SSID: ")); + if (!ssid) { + g_set_error_literal (error, NMCLI_ERROR, NMC_RESULT_ERROR_USER_INPUT, + _("Error: 'ssid' is required.")); + goto cleanup_olpc; + } + + /* Also ask for all optional arguments if '--ask' is specified. */ + channel = g_strdup (channel_c); + dhcp_anycast = g_strdup (dhcp_anycast_c); + if (ask) + do_questionnaire_olpc (&channel, &dhcp_anycast); + + if (channel) { + if (!nmc_string_to_uint (channel, TRUE, 1, 13, &chan)) { + g_set_error (error, NMCLI_ERROR, NMC_RESULT_ERROR_USER_INPUT, + _("Error: 'channel': '%s' is not valid; use <1-13>."), + channel); + goto cleanup_olpc; + } + } + if (!check_mac (dhcp_anycast, ARPHRD_ETHER, "dhcp-anycast", error)) + goto cleanup_olpc; + + /* Add OLPC mesh setting */ + s_olpc_mesh = (NMSettingOlpcMesh *) nm_setting_olpc_mesh_new (); + nm_connection_add_setting (connection, NM_SETTING (s_olpc_mesh)); + + ssid_bytes = g_bytes_new (ssid, strlen (ssid)); + g_object_set (s_olpc_mesh, NM_SETTING_OLPC_MESH_SSID, ssid_bytes, NULL); + if (channel) + g_object_set (s_olpc_mesh, NM_SETTING_OLPC_MESH_CHANNEL, chan, NULL); + else + g_object_set (s_olpc_mesh, NM_SETTING_OLPC_MESH_CHANNEL, 1, NULL); + if (dhcp_anycast) + g_object_set (s_olpc_mesh, NM_SETTING_OLPC_MESH_DHCP_ANYCAST_ADDRESS, dhcp_anycast, NULL); + g_bytes_unref (ssid_bytes); + + success = TRUE; +cleanup_olpc: + g_free (ssid_ask); + g_free (channel); + g_free (dhcp_anycast); + if (!success) + return FALSE; + + } else { + g_set_error (error, NMCLI_ERROR, NMC_RESULT_ERROR_USER_INPUT, + _("Error: '%s' is not a valid connection type."), + con_type); + return FALSE; + } + + /* Read and add IP configuration */ + if ( strcmp (con_type, "bond-slave") != 0 + && strcmp (con_type, "team-slave") != 0 + && strcmp (con_type, "bridge-slave") != 0) { + + NMIPAddress *ip4addr = NULL, *ip6addr = NULL; + const char *ip4 = NULL, *gw4 = NULL, *ip6 = NULL, *gw6 = NULL; + nmc_arg_t exp_args[] = { {"ip4", TRUE, &ip4, FALSE}, {"gw4", TRUE, &gw4, FALSE}, + {"ip6", TRUE, &ip6, FALSE}, {"gw6", TRUE, &gw6, FALSE}, + {NULL} }; + + while (argc) { + nmc_arg_t *p; + + /* reset 'found' flag */ + for (p = exp_args; p->name; p++) + p->found = FALSE; + + ip4 = gw4 = ip6 = gw6 = NULL; + + if (!nmc_parse_args (exp_args, TRUE, &argc, &argv, error)) + return FALSE; + + /* coverity[dead_error_begin] */ + if (ip4) { + ip4addr = nmc_parse_and_build_address (AF_INET, ip4, error); + if (!ip4addr) { + g_prefix_error (error, _("Error: ")); + return FALSE; + } + add_ip4_address_to_connection (ip4addr, connection); + } + + /* coverity[dead_error_begin] */ + if (gw4) { + NMSettingIPConfig *s_ip = nm_connection_get_setting_ip4_config (connection); + + if (!s_ip) { + g_set_error (error, NMCLI_ERROR, NMC_RESULT_ERROR_USER_INPUT, + _("Error: IPv4 gateway specified without IPv4 addresses")); + return FALSE; + } else if (nm_setting_ip_config_get_gateway (s_ip)) { + g_set_error (error, NMCLI_ERROR, NMC_RESULT_ERROR_USER_INPUT, + _("Error: multiple IPv4 gateways specified")); + return FALSE; + } else if (!nm_utils_ipaddr_valid (AF_INET, gw4)) { + g_set_error (error, NMCLI_ERROR, NMC_RESULT_ERROR_USER_INPUT, + _("Error: Invalid IPv4 gateway '%s'"), + gw4); + } + + g_object_set (s_ip, + NM_SETTING_IP_CONFIG_GATEWAY, gw4, + NULL); + } + + /* coverity[dead_error_begin] */ + if (ip6) { + ip6addr = nmc_parse_and_build_address (AF_INET6, ip6, error); + if (!ip6addr) { + g_prefix_error (error, _("Error: ")); + return FALSE; + } + add_ip6_address_to_connection (ip6addr, connection); + } + + /* coverity[dead_error_begin] */ + if (gw6) { + NMSettingIPConfig *s_ip = nm_connection_get_setting_ip6_config (connection); + + if (!s_ip) { + g_set_error (error, NMCLI_ERROR, NMC_RESULT_ERROR_USER_INPUT, + _("Error: IPv6 gateway specified without IPv6 addresses")); + return FALSE; + } else if (nm_setting_ip_config_get_gateway (s_ip)) { + g_set_error (error, NMCLI_ERROR, NMC_RESULT_ERROR_USER_INPUT, + _("Error: multiple IPv6 gateways specified")); + return FALSE; + } else if (!nm_utils_ipaddr_valid (AF_INET, gw6)) { + g_set_error (error, NMCLI_ERROR, NMC_RESULT_ERROR_USER_INPUT, + _("Error: Invalid IPv6 gateway '%s'"), + gw6); + } + + g_object_set (s_ip, + NM_SETTING_IP_CONFIG_GATEWAY, gw6, + NULL); + } + } + + /* Ask for addresses if '--ask' is specified. */ + if (ask) + do_questionnaire_ip (connection); + } + + return TRUE; +} + +static char * +unique_connection_name (const GPtrArray *connections, const char *try_name) +{ + NMConnection *connection; + const char *name; + char *new_name; + unsigned int num = 1; + int i = 0; + + new_name = g_strdup (try_name); + while (i < connections->len) { + connection = NM_CONNECTION (connections->pdata[i]); + + name = nm_connection_get_id (connection); + if (g_strcmp0 (new_name, name) == 0) { + g_free (new_name); + new_name = g_strdup_printf ("%s-%d", try_name, num++); + i = 0; + } else + i++; + } + return new_name; +} + +typedef struct { + NmCli *nmc; + char *con_name; +} AddConnectionInfo; + +static void +add_connection_cb (GObject *client, + GAsyncResult *result, + gpointer user_data) +{ + AddConnectionInfo *info = (AddConnectionInfo *) user_data; + NmCli *nmc = info->nmc; + NMRemoteConnection *connection; + GError *error = NULL; + + connection = nm_client_add_connection_finish (NM_CLIENT (client), result, &error); + if (error) { + g_string_printf (nmc->return_text, + _("Error: Failed to add '%s' connection: %s"), + info->con_name, error->message); + g_error_free (error); + nmc->return_value = NMC_RESULT_ERROR_CON_ACTIVATION; + } else { + g_print (_("Connection '%s' (%s) successfully added.\n"), + nm_connection_get_id (NM_CONNECTION (connection)), + nm_connection_get_uuid (NM_CONNECTION (connection))); + g_object_unref (connection); + } + + g_free (info->con_name); + g_free (info); + quit (); +} + +static void +add_new_connection (gboolean persistent, + NMClient *client, + NMConnection *connection, + GAsyncReadyCallback callback, + gpointer user_data) +{ + nm_client_add_connection_async (client, connection, persistent, + NULL, callback, user_data); +} + +static void +update_connection (gboolean persistent, + NMRemoteConnection *connection, + GAsyncReadyCallback callback, + gpointer user_data) +{ + nm_remote_connection_commit_changes_async (connection, persistent, + NULL, callback, user_data); +} + +static char * +gen_func_vpn_types (const char *text, int state) +{ + return nmc_rl_gen_func_basic (text, state, nmc_known_vpns); +} + +static char * +gen_func_bool_values_l10n (const char *text, int state) +{ + const char *words[] = { WORD_LOC_YES, WORD_LOC_NO, NULL }; + return nmc_rl_gen_func_basic (text, state, words); +} + +static char * +gen_func_wifi_mode (const char *text, int state) +{ + const char *words[] = { "infrastructure", "ap", "adhoc", NULL }; + return nmc_rl_gen_func_basic (text, state, words); +} + +static char * +gen_func_ib_type (const char *text, int state) +{ + const char *words[] = { "datagram", "connected", NULL }; + return nmc_rl_gen_func_basic (text, state, words); +} + +static char * +gen_func_bt_type (const char *text, int state) +{ + const char *words[] = { "panu", "dun-gsm", "dun-cdma", NULL }; + return nmc_rl_gen_func_basic (text, state, words); +} + +static char * +gen_func_bond_mode (const char *text, int state) +{ + const char *words[] = { "balance-rr", "active-backup", "balance-xor", "broadcast", + "802.3ad", "balance-tlb", "balance-alb", NULL }; + return nmc_rl_gen_func_basic (text, state, words); +} +static char * +gen_func_bond_mon_mode (const char *text, int state) +{ + const char *words[] = { "miimon", "arp", NULL }; + return nmc_rl_gen_func_basic (text, state, words); +} + +static char * +gen_func_master_ifnames (const char *text, int state) +{ + int i; + GPtrArray *ifnames; + char *ret; + NMConnection *con; + NMSettingConnection *s_con; + const char *con_type, *ifname; + + if (!nm_cli.connections) + return NULL; + + /* Disable appending space after completion */ + rl_completion_append_character = '\0'; + + ifnames = g_ptr_array_sized_new (20); + for (i = 0; i < nm_cli.connections->len; i++) { + con = NM_CONNECTION (nm_cli.connections->pdata[i]); + s_con = nm_connection_get_setting_connection (con); + g_assert (s_con); + con_type = nm_setting_connection_get_connection_type (s_con); + if (g_strcmp0 (con_type, nmc_tab_completion.con_type) != 0) + continue; + ifname = nm_connection_get_interface_name (con); + g_ptr_array_add (ifnames, (gpointer) ifname); + } + g_ptr_array_add (ifnames, (gpointer) NULL); + + ret = nmc_rl_gen_func_basic (text, state, (const char **) ifnames->pdata); + + g_ptr_array_free (ifnames, TRUE); + return ret; +} + +static gboolean +is_single_word (const char* line) +{ + size_t n1, n2, n3; + + n1 = strspn (line, " \t"); + n2 = strcspn (line+n1, " \t\0") + n1; + n3 = strspn (line+n2, " \t"); + + if (n3 == 0) + return TRUE; + else + return FALSE; +} + +static char ** +nmcli_con_add_tab_completion (const char *text, int start, int end) +{ + char **match_array = NULL; + rl_compentry_func_t *generator_func = NULL; + + /* Disable readline's default filename completion */ + rl_attempted_completion_over = 1; + + /* Restore standard append character to space */ + rl_completion_append_character = ' '; + + if (!is_single_word (rl_line_buffer)) + return NULL; + + if (g_strcmp0 (rl_prompt, PROMPT_CON_TYPE) == 0) + generator_func = gen_connection_types; + else if (g_strcmp0 (rl_prompt, PROMPT_VPN_TYPE) == 0) + generator_func = gen_func_vpn_types; + else if ( g_strcmp0 (rl_prompt, PROMPT_BOND_MASTER) == 0 + || g_strcmp0 (rl_prompt, PROMPT_TEAM_MASTER) == 0 + || g_strcmp0 (rl_prompt, PROMPT_BRIDGE_MASTER) == 0) + generator_func = gen_func_master_ifnames; + else if ( g_str_has_suffix (rl_prompt, prompt_yes_no (TRUE, NULL)) + || g_str_has_suffix (rl_prompt, prompt_yes_no (TRUE, ":")) + || g_str_has_suffix (rl_prompt, prompt_yes_no (FALSE, NULL)) + || g_str_has_suffix (rl_prompt, prompt_yes_no (FALSE, ":"))) + generator_func = gen_func_bool_values_l10n; + else if (g_str_has_suffix (rl_prompt, PROMPT_WIFI_MODE)) + generator_func = gen_func_wifi_mode; + else if (g_str_has_suffix (rl_prompt, PROMPT_IB_MODE)) + generator_func = gen_func_ib_type; + else if (g_str_has_suffix (rl_prompt, PROMPT_BT_TYPE)) + generator_func = gen_func_bt_type; + else if (g_str_has_prefix (rl_prompt, PROMPT_BOND_MODE)) + generator_func = gen_func_bond_mode; + else if (g_str_has_suffix (rl_prompt, PROMPT_BOND_MON_MODE)) + generator_func = gen_func_bond_mon_mode; + + if (generator_func) + match_array = rl_completion_matches (text, generator_func); + + return match_array; +} + +static NMCResultCode +do_connection_add (NmCli *nmc, int argc, char **argv) +{ + NMConnection *connection = NULL; + NMSettingConnection *s_con; + char *uuid; + char *default_name = NULL; + const char *type = NULL; + char *type_ask = NULL; + const char *con_name = NULL; + const char *autoconnect = NULL; + gboolean auto_bool = TRUE; + const char *ifname = NULL; + char *ifname_ask = NULL; + gboolean ifname_mandatory = TRUE; + const char *save = NULL; + gboolean save_bool = TRUE; + AddConnectionInfo *info = NULL; + const char *setting_name; + GError *error = NULL; + nmc_arg_t exp_args[] = { {"type", TRUE, &type, !nmc->ask}, + {"con-name", TRUE, &con_name, FALSE}, + {"autoconnect", TRUE, &autoconnect, FALSE}, + {"ifname", TRUE, &ifname, FALSE}, + {"save", TRUE, &save, FALSE}, + {NULL} }; + + rl_attempted_completion_function = (rl_completion_func_t *) nmcli_con_add_tab_completion; + + nmc->return_value = NMC_RESULT_SUCCESS; + + if (!nmc_parse_args (exp_args, FALSE, &argc, &argv, &error)) { + g_string_assign (nmc->return_text, error->message); + nmc->return_value = error->code; + g_clear_error (&error); + goto error; + } + + if (!type && nmc->ask) { + char *types_tmp = get_valid_options_string (nmc_valid_connection_types); + g_print ("Valid types: [%s]\n", types_tmp); + type = type_ask = nmc_readline (PROMPT_CON_TYPE); + g_free (types_tmp); + } + if (!type) { + g_string_printf (nmc->return_text, _("Error: 'type' argument is required.")); + nmc->return_value = NMC_RESULT_ERROR_USER_INPUT; + goto error; + } + if (type_ask) + type = g_strstrip (type_ask); + + if (!(setting_name = check_valid_name (type, nmc_valid_connection_types, &error))) { + g_string_printf (nmc->return_text, _("Error: invalid connection type; %s."), + error->message); + nmc->return_value = NMC_RESULT_ERROR_USER_INPUT; + g_clear_error (&error); + goto error; + } + if (autoconnect) { + GError *tmp_err = NULL; + if (!nmc_string_to_bool (autoconnect, &auto_bool, &tmp_err)) { + g_string_printf (nmc->return_text, _("Error: 'autoconnect': %s."), + tmp_err->message); + nmc->return_value = NMC_RESULT_ERROR_USER_INPUT; + g_clear_error (&tmp_err); + goto error; + } + } + if (save) { + GError *tmp_err = NULL; + if (!nmc_string_to_bool (save, &save_bool, &tmp_err)) { + g_string_printf (nmc->return_text, _("Error: 'save': %s."), + tmp_err->message); + nmc->return_value = NMC_RESULT_ERROR_USER_INPUT; + g_clear_error (&tmp_err); + goto error; + } + } + + /* ifname is mandatory for all connection types except virtual ones (bond, team, bridge, vlan) */ + if ( strcmp (type, NM_SETTING_BOND_SETTING_NAME) == 0 + || strcmp (type, NM_SETTING_TEAM_SETTING_NAME) == 0 + || strcmp (type, NM_SETTING_BRIDGE_SETTING_NAME) == 0 + || strcmp (type, NM_SETTING_VLAN_SETTING_NAME) == 0) + ifname_mandatory = FALSE; + + if (!ifname && ifname_mandatory && nmc->ask) { + ifname = ifname_ask = nmc_readline (_("Interface name [*]: ")); + if (!ifname) + ifname = ifname_ask = g_strdup ("*"); + } + if (!ifname && ifname_mandatory) { + g_string_printf (nmc->return_text, _("Error: 'ifname' argument is required.")); + nmc->return_value = NMC_RESULT_ERROR_USER_INPUT; + goto error; + } + if (ifname) { + if (!nm_utils_iface_valid_name (ifname) && strcmp (ifname, "*") != 0) { + g_string_printf (nmc->return_text, + _("Error: 'ifname': '%s' is not a valid interface nor '*'."), + ifname); + nmc->return_value = NMC_RESULT_ERROR_USER_INPUT; + goto error; + } + /* Special value of '*' means no specific interface name */ + if (strcmp (ifname, "*") == 0) + ifname = NULL; + } + + /* Create a new connection object */ + connection = nm_simple_connection_new (); + + /* Build up the 'connection' setting */ + s_con = (NMSettingConnection *) nm_setting_connection_new (); + uuid = nm_utils_uuid_generate (); + if (con_name) + default_name = g_strdup (con_name); + else { + char *try_name = ifname ? + g_strdup_printf ("%s-%s", get_name_alias (setting_name, nmc_valid_connection_types), ifname) + : g_strdup (get_name_alias (setting_name, nmc_valid_connection_types)); + default_name = unique_connection_name (nmc->connections, try_name); + g_free (try_name); + } + g_object_set (s_con, + NM_SETTING_CONNECTION_ID, default_name, + NM_SETTING_CONNECTION_UUID, uuid, + NM_SETTING_CONNECTION_TYPE, setting_name, + NM_SETTING_CONNECTION_AUTOCONNECT, auto_bool, + NM_SETTING_CONNECTION_INTERFACE_NAME, ifname, + NULL); + g_free (uuid); + g_free (default_name); + nm_connection_add_setting (connection, NM_SETTING (s_con)); + + if (!complete_connection_by_type (connection, + setting_name, + nmc->connections, + nmc->ask, + argc, + argv, + &error)) { + g_string_assign (nmc->return_text, error->message); + nmc->return_value = error->code; + g_clear_error (&error); + goto error; + } + + nmc->should_wait = TRUE; + + info = g_malloc0 (sizeof (AddConnectionInfo)); + info->nmc = nmc; + info->con_name = g_strdup (nm_connection_get_id (connection)); + + /* Tell the settings service to add the new connection */ + add_new_connection (save_bool, + nmc->client, + connection, + add_connection_cb, + info); + + if (connection) + g_object_unref (connection); + + return nmc->return_value; + +error: + if (connection) + g_object_unref (connection); + g_free (type_ask); + g_free (ifname_ask); + + nmc->should_wait = FALSE; + return nmc->return_value; +} + + +/*----------------------------------------------------------------------------*/ +/* Functions for readline TAB completion in editor */ + +static void +uuid_display_hook (char **array, int len, int max_len) +{ + NMConnection *con; + int i, max = 0; + char *tmp; + const char *id; + + for (i = 1; i <= len; i++) { + con = nmc_find_connection (nmc_tab_completion.nmc->connections, "uuid", array[i], NULL); + id = con ? nm_connection_get_id (con) : NULL; + if (id) { + tmp = g_strdup_printf ("%s (%s)", array[i], id); + g_free (array[i]); + array[i] = tmp; + if (max < strlen (id)) + max = strlen (id); + } + } + rl_display_match_list (array, len, max_len + max + 3); + rl_forced_update_display (); +} + +static char * +gen_nmcli_cmds_menu (const char *text, int state) +{ + const char *words[] = { "goto", "set", "remove", "describe", "print", "verify", + "save", "activate", "back", "help", "quit", "nmcli", + NULL }; + return nmc_rl_gen_func_basic (text, state, words); +} + +static char * +gen_nmcli_cmds_submenu (const char *text, int state) +{ + const char *words[] = { "set", "add", "change", "remove", "describe", + "print", "back", "help", "quit", + NULL }; + return nmc_rl_gen_func_basic (text, state, words); +} + +static char * +gen_cmd_nmcli (const char *text, int state) +{ + const char *words[] = { "status-line", "save-confirmation", "show-secrets", "prompt-color", NULL }; + return nmc_rl_gen_func_basic (text, state, words); +} + +static char * +gen_cmd_nmcli_prompt_color (const char *text, int state) +{ + const char *words[] = { "0", "1", "2", "3", "4", "5", "6", "7", "8", NULL }; + return nmc_rl_gen_func_basic (text, state, words); +} + +static char * +gen_func_bool_values (const char *text, int state) +{ + const char *words[] = { "yes", "no", NULL }; + return nmc_rl_gen_func_basic (text, state, words); +} + +static char * +gen_cmd_verify0 (const char *text, int state) +{ + const char *words[] = { "all", "fix", NULL }; + return nmc_rl_gen_func_basic (text, state, words); +} + +static char * +gen_cmd_print0 (const char *text, int state) +{ + static char **words = NULL; + char *ret = NULL; + + if (!state) { + GVariant *settings; + GVariantIter iter; + const char *setting_name; + int i = 0; + + settings = nm_connection_to_dbus (nmc_tab_completion.connection, NM_CONNECTION_SERIALIZE_NO_SECRETS); + words = g_new (char *, g_variant_n_children (settings) + 2); + g_variant_iter_init (&iter, settings); + while (g_variant_iter_next (&iter, "{&s@a{sv}}", &setting_name, NULL)) + words [i++] = g_strdup (setting_name); + words[i++] = g_strdup ("all"); + words[i] = NULL; + g_variant_unref (settings); + } + + if (words) { + ret = nmc_rl_gen_func_basic (text, state, (const char **) words); + if (ret == NULL) { + g_strfreev (words); + words = NULL; + } + } + return ret; +} + +static char * +gen_cmd_print2 (const char *text, int state) +{ + const char *words[] = { "setting", "connection", "all", NULL }; + return nmc_rl_gen_func_basic (text, state, words); +} + +static char * +gen_cmd_save (const char *text, int state) +{ + const char *words[] = { "persistent", "temporary", NULL }; + return nmc_rl_gen_func_basic (text, state, words); +} + +static char * +gen_connection_types (const char *text, int state) +{ + static int list_idx, len; + const char *c_type, *a_type; + + if (!state) { + list_idx = 0; + len = strlen (text); + } + + while (nmc_valid_connection_types[list_idx].name) { + a_type = nmc_valid_connection_types[list_idx].alias; + c_type = nmc_valid_connection_types[list_idx].name; + list_idx++; + if (a_type && !strncmp (text, a_type, len)) + return g_strdup (a_type); + if (c_type && !strncmp (text, c_type, len)) + return g_strdup (c_type); + } + + return NULL; +} + +static char * +gen_setting_names (const char *text, int state) +{ + static int list_idx, len; + const char *s_name, *a_name; + const NameItem *valid_settings_arr; + + if (!state) { + list_idx = 0; + len = strlen (text); + } + + valid_settings_arr = get_valid_settings_array (nmc_tab_completion.con_type); + if (!valid_settings_arr) + return NULL; + while (valid_settings_arr[list_idx].name) { + a_name = valid_settings_arr[list_idx].alias; + s_name = valid_settings_arr[list_idx].name; + list_idx++; + if (len == 0 && a_name) + return g_strdup_printf ("%s (%s)", s_name, a_name); + if (a_name && !strncmp (text, a_name, len)) + return g_strdup (a_name); + if (s_name && !strncmp (text, s_name, len)) + return g_strdup (s_name); + } + return NULL; +} + +static char * +gen_property_names (const char *text, int state) +{ + NMSetting *setting = NULL; + char **valid_props = NULL; + char *ret = NULL; + const char *line = rl_line_buffer; + const char *setting_name; + char **strv = NULL; + const NameItem *valid_settings_arr; + const char *p1; + + /* Try to get the setting from 'line' - setting_name.property */ + p1 = strchr (line, '.'); + if (p1) { + while (p1 > line && !g_ascii_isspace (*p1)) + p1--; + + strv = g_strsplit (p1+1, ".", 2); + + valid_settings_arr = get_valid_settings_array (nmc_tab_completion.con_type); + setting_name = check_valid_name (strv[0], valid_settings_arr, NULL); + setting = nmc_setting_new_for_name (setting_name); + } else { + /* Else take the current setting, if any */ + setting = nmc_tab_completion.setting ? g_object_ref (nmc_tab_completion.setting) : NULL; + } + + if (setting) { + valid_props = nmc_setting_get_valid_properties (setting); + ret = nmc_rl_gen_func_basic (text, state, (const char **) valid_props); + } + + g_strfreev (strv); + g_strfreev (valid_props); + if (setting) + g_object_unref (setting); + return ret; +} + +static char * +gen_compat_devices (const char *text, int state) +{ + int i, j = 0; + const GPtrArray *devices; + const char **compatible_devices; + char *ret; + + devices = nm_client_get_devices (nmc_tab_completion.nmc->client); + if (devices->len == 0) + return NULL; + + compatible_devices = g_new (const char *, devices->len + 1); + for (i = 0; i < devices->len; i++) { + NMDevice *dev = g_ptr_array_index (devices, i); + const char *ifname = nm_device_get_iface (dev); + NMDevice *device = NULL; + const char *spec_object = NULL; + + if (find_device_for_connection (nmc_tab_completion.nmc, nmc_tab_completion.connection, + ifname, NULL, NULL, &device, &spec_object, NULL)) { + compatible_devices[j++] = ifname; + } + } + compatible_devices[j] = NULL; + + ret = nmc_rl_gen_func_basic (text, state, compatible_devices); + + g_free (compatible_devices); + return ret; +} + +static char * +gen_vpn_uuids (const char *text, int state) +{ + const GPtrArray *connections = nmc_tab_completion.nmc->connections; + int c, u = 0; + const char **uuids; + char *ret; + + if (connections->len < 1) + return NULL; + + uuids = g_new (const char *, connections->len + 1); + for (c = 0; c < connections->len; c++) { + NMConnection *connection = NM_CONNECTION (connections->pdata[c]); + const char *type = nm_connection_get_connection_type (connection); + + if (g_strcmp0 (type, NM_SETTING_VPN_SETTING_NAME) == 0) + uuids[u++] = nm_connection_get_uuid (connection); + } + uuids[u] = NULL; + + ret = nmc_rl_gen_func_basic (text, state, uuids); + + g_free (uuids); + return ret; +} + +static rl_compentry_func_t * +get_gen_func_cmd_nmcli (const char *str) +{ + if (!str) + return NULL; + if (matches (str, "status-line") == 0) + return gen_func_bool_values; + if (matches (str, "save-confirmation") == 0) + return gen_func_bool_values; + if (matches (str, "show-secrets") == 0) + return gen_func_bool_values; + if (matches (str, "prompt-color") == 0) + return gen_cmd_nmcli_prompt_color; + return NULL; +} + +/* + * Helper function parsing line for completion. + * IN: + * line : the whole line to be parsed + * end : the position of cursor in the line + * cmd : command to match + * OUT: + * cw_num : is set to the word number being completed (1, 2, 3, 4). + * prev_word : returns the previous word (so that we have some context). + * + * Returns TRUE when the first word of the 'line' matches 'cmd'. + * + * Examples: + * line="rem" cmd="remove" -> TRUE cw_num=1 + * line="set con" cmd="set" -> TRUE cw_num=2 + * line="go ipv4.method" cmd="goto" -> TRUE cw_num=2 + * line=" des eth.mtu " cmd="describe" -> TRUE cw_num=3 + * line=" bla ipv4.method" cmd="goto" -> FALSE + */ +static gboolean +should_complete_cmd (const char *line, int end, const char *cmd, + int *cw_num, char **prev_word) +{ + char *tmp; + const char *word1, *word2, *word3; + size_t n1, n2, n3, n4, n5, n6; + gboolean word1_done, word2_done, word3_done; + gboolean ret = FALSE; + + if (!line) + return FALSE; + + tmp = g_strdup (line); + + n1 = strspn (tmp, " \t"); + n2 = strcspn (tmp+n1, " \t\0") + n1; + n3 = strspn (tmp+n2, " \t") + n2; + n4 = strcspn (tmp+n3, " \t\0") + n3; + n5 = strspn (tmp+n4, " \t") + n4; + n6 = strcspn (tmp+n5, " \t\0") + n5; + + word1_done = end > n2; + word2_done = end > n4; + word3_done = end > n6; + tmp[n2] = tmp[n4] = tmp[n6] = '\0'; + + word1 = tmp[n1] ? tmp + n1 : NULL; + word2 = tmp[n3] ? tmp + n3 : NULL; + word3 = tmp[n5] ? tmp + n5 : NULL; + + if (!word1_done) { + if (cw_num) + *cw_num = 1; + if (prev_word) + *prev_word = NULL; + } else if (!word2_done) { + if (cw_num) + *cw_num = 2; + if (prev_word) + *prev_word = g_strdup (word1); + } else if (!word3_done) { + if (cw_num) + *cw_num = 3; + if (prev_word) + *prev_word = g_strdup (word2); + } else { + if (cw_num) + *cw_num = 4; + if (prev_word) + *prev_word = g_strdup (word3); + } + + if (word1 && matches (word1, cmd) == 0) + ret = TRUE; + + g_free (tmp); + return ret; +} + +static char * +extract_property_name (const char *prompt, const char *line) +{ + char *prop = NULL; + + /* If prompt is set take the property name from it, else extract it from line */ + if (!prompt) { + const char *p1; + size_t num; + p1 = strchr (line, '.'); + if (p1) { + p1++; + } else { + size_t n1, n2, n3; + n1 = strspn (line, " \t"); + n2 = strcspn (line+n1, " \t\0") + n1; + n3 = strspn (line+n2, " \t") + n2; + p1 = line + n3; + } + num = strcspn (p1, " \t\0"); + prop = g_strndup (p1, num); + } else { + const char *p1, *dot; + size_t num; + p1 = strchr (prompt, ' '); + /* prompt looks like this: "nmcli 802-1x>" or "nmcli 802-1x.pac-file>" */ + if (p1) { + dot = strchr (p1 + 1, '.'); + p1 = dot ? dot + 1 : p1; + num = strcspn (p1, ">"); + prop = g_strndup (p1, num); + } + } + + return prop; +} + +static gboolean +should_complete_files (const char *prompt, const char *line) +{ + char *prop; + gboolean found = FALSE; + const char *file_properties[] = { + /* '802-1x' properties */ + "ca-cert", + "ca-path", + "client-cert", + "pac-file", + "phase2-ca-cert", + "phase2-ca-path", + "phase2-client-cert", + "private-key", + "phase2-private-key", + /* 'team' and 'team-port' properties */ + "config", + NULL + }; + + prop = extract_property_name (prompt, line); + if (prop) { + found = !!nmc_string_is_valid (prop, file_properties, NULL); + g_free (prop); + } + return found; +} + +static gboolean +should_complete_vpn_uuids (const char *prompt, const char *line) +{ + char *prop; + gboolean found = FALSE; + const char *uuid_properties[] = { + /* 'connection' properties */ + "secondaries", + NULL + }; + + prop = extract_property_name (prompt, line); + if (prop) { + found = !!nmc_string_is_valid (prop, uuid_properties, NULL); + g_free (prop); + } + return found; +} + +/* from readline */ +extern int rl_complete_with_tilde_expansion; + +/* + * Attempt to complete on the contents of TEXT. START and END show the + * region of TEXT that contains the word to complete. We can use the + * entire line in case we want to do some simple parsing. Return the + * array of matches, or NULL if there aren't any. + */ +static char ** +nmcli_editor_tab_completion (const char *text, int start, int end) +{ + char **match_array = NULL; + const char *line = rl_line_buffer; + const char *prompt = rl_prompt; + rl_compentry_func_t *generator_func = NULL; + gboolean copy_char; + const char *p1; + char *p2, *prompt_tmp; + char *word = NULL; + size_t n1; + int num; + + /* Restore standard append character to space */ + rl_completion_append_character = ' '; + + /* Restore standard function for displaying matches */ + rl_completion_display_matches_hook = NULL; + + /* Disable default filename completion */ + rl_attempted_completion_over = 1; + + /* Enable tilde expansion when filenames are completed */ + rl_complete_with_tilde_expansion = 1; + + /* Filter out possible ANSI color escape sequences */ + p1 = prompt; + p2 = prompt_tmp = g_strdup (prompt); + copy_char = TRUE; + while (*p1) { + if (*p1 == '\33') + copy_char = FALSE; + if (copy_char) + *p2++ = *p1; + if (!copy_char && *p1 == 'm') + copy_char = TRUE; + p1++; + } + *p2 = '\0'; + + /* Find the first non-space character */ + n1 = strspn (line, " \t"); + + /* Choose the right generator function */ + if (strcmp (prompt_tmp, EDITOR_PROMPT_CON_TYPE) == 0) + generator_func = gen_connection_types; + else if (strcmp (prompt_tmp, EDITOR_PROMPT_SETTING) == 0) + generator_func = gen_setting_names; + else if (strcmp (prompt_tmp, EDITOR_PROMPT_PROPERTY) == 0) + generator_func = gen_property_names; + else if ( g_str_has_suffix (rl_prompt, prompt_yes_no (TRUE, NULL)) + || g_str_has_suffix (rl_prompt, prompt_yes_no (FALSE, NULL))) + generator_func = gen_func_bool_values_l10n; + else if (g_str_has_prefix (prompt_tmp, "nmcli")) { + if (!strchr (prompt_tmp, '.')) { + int level = g_str_has_prefix (prompt_tmp, "nmcli>") ? 0 : 1; + const char *dot = strchr (line, '.'); + + /* Main menu - level 0,1 */ + if (start == n1) + generator_func = gen_nmcli_cmds_menu; + else { + if (should_complete_cmd (line, end, "goto", &num, NULL) && num <= 2) { + if (level == 0 && (!dot || dot >= line + end)) + generator_func = gen_setting_names; + else + generator_func = gen_property_names; + } else if (should_complete_cmd (line, end, "set", &num, NULL)) { + if (num < 3) { + if (level == 0 && (!dot || dot >= line + end)) { + generator_func = gen_setting_names; + rl_completion_append_character = '.'; + } else + generator_func = gen_property_names; + } else if (num >= 3) { + if (num == 3 && should_complete_files (NULL, line)) + rl_attempted_completion_over = 0; + if (should_complete_vpn_uuids (NULL, line)) { + rl_completion_display_matches_hook = uuid_display_hook; + generator_func = gen_vpn_uuids; + } + } + } else if ( ( should_complete_cmd (line, end, "remove", &num, NULL) + || should_complete_cmd (line, end, "describe", &num, NULL)) + && num <= 2) { + if (level == 0 && (!dot || dot >= line + end)) { + generator_func = gen_setting_names; + rl_completion_append_character = '.'; + } else + generator_func = gen_property_names; + } else if (should_complete_cmd (line, end, "nmcli", &num, &word)) { + if (num < 3) + generator_func = gen_cmd_nmcli; + else if (num == 3) + generator_func = get_gen_func_cmd_nmcli (word); + } else if (should_complete_cmd (line, end, "print", &num, NULL) && num <= 2) { + if (level == 0 && (!dot || dot >= line + end)) + generator_func = gen_cmd_print0; + else + generator_func = gen_property_names; + } else if (should_complete_cmd (line, end, "verify", &num, NULL) && num <= 2) { + generator_func = gen_cmd_verify0; + } else if (should_complete_cmd (line, end, "activate", &num, NULL) && num <= 2) { + generator_func = gen_compat_devices; + } else if (should_complete_cmd (line, end, "save", &num, NULL) && num <= 2) { + generator_func = gen_cmd_save; + } else if (should_complete_cmd (line, end, "help", &num, NULL) && num <= 2) + generator_func = gen_nmcli_cmds_menu; + } + } else { + /* Submenu - level 2 */ + if (start == n1) + generator_func = gen_nmcli_cmds_submenu; + else { + if ( should_complete_cmd (line, end, "add", &num, NULL) + || should_complete_cmd (line, end, "set", &num, NULL)) { + if (num <= 2 && should_complete_files (prompt_tmp, line)) + rl_attempted_completion_over = 0; + else if (should_complete_vpn_uuids (prompt_tmp, line)) { + rl_completion_display_matches_hook = uuid_display_hook; + generator_func = gen_vpn_uuids; + } + } + if (should_complete_cmd (line, end, "print", &num, NULL) && num <= 2) + generator_func = gen_cmd_print2; + else if (should_complete_cmd (line, end, "help", &num, NULL) && num <= 2) + generator_func = gen_nmcli_cmds_submenu; + } + } + } + + if (generator_func) + match_array = rl_completion_matches (text, generator_func); + + g_free (prompt_tmp); + g_free (word); + return match_array; +} + +#define NMCLI_EDITOR_HISTORY ".nmcli-history" + +static void +load_history_cmds (const char *uuid) +{ + GKeyFile *kf; + char *filename; + char **keys; + char *line; + size_t i; + GError *err = NULL; + + filename = g_build_filename (g_get_home_dir (), NMCLI_EDITOR_HISTORY, NULL); + kf = g_key_file_new (); + if (!g_key_file_load_from_file (kf, filename, G_KEY_FILE_KEEP_COMMENTS, &err)) { + if (err->code == G_KEY_FILE_ERROR_PARSE) + g_print ("Warning: %s parse error: %s\n", filename, err->message); + g_key_file_free (kf); + g_free (filename); + return; + } + keys = g_key_file_get_keys (kf, uuid, NULL, NULL); + for (i = 0; keys && keys[i]; i++) { + line = g_key_file_get_string (kf, uuid, keys[i], NULL); + if (line && *line) + add_history (line); + g_free (line); + } + g_strfreev (keys); + g_key_file_free (kf); + g_free (filename); +} + +static void +save_history_cmds (const char *uuid) +{ + HIST_ENTRY **hist = NULL; + GKeyFile *kf; + char *filename; + size_t i; + char *key; + char *data; + gsize len = 0; + GError *err = NULL; + + hist = history_list (); + if (hist) { + filename = g_build_filename (g_get_home_dir (), NMCLI_EDITOR_HISTORY, NULL); + kf = g_key_file_new (); + if (!g_key_file_load_from_file (kf, filename, G_KEY_FILE_KEEP_COMMENTS, &err)) { + if ( err->code != G_FILE_ERROR_NOENT + && err->code != G_KEY_FILE_ERROR_NOT_FOUND) { + g_print ("Warning: %s parse error: %s\n", filename, err->message); + g_key_file_free (kf); + g_free (filename); + g_clear_error (&err); + return; + } + g_clear_error (&err); + } + + /* Remove previous history group and save new history entries */ + g_key_file_remove_group (kf, uuid, NULL); + for (i = 0; hist[i]; i++) + { + key = g_strdup_printf ("%zd", i); + g_key_file_set_string (kf, uuid, key, hist[i]->line); + g_free (key); + } + + /* Write history to file */ + data = g_key_file_to_data (kf, &len, NULL); + if (data) { + g_file_set_contents (filename, data, len, NULL); + g_free (data); + } + g_key_file_free (kf); + g_free (filename); + } +} + +/*----------------------------------------------------------------------------*/ + +static void +editor_show_connection (NMConnection *connection, NmCli *nmc) +{ + nmc->print_output = NMC_PRINT_PRETTY; + nmc->multiline_output = TRUE; + nmc->escape_values = 0; + + /* Remove any previous data */ + nmc_empty_output_fields (nmc); + + nmc_connection_profile_details (connection, nmc, nmc->editor_show_secrets); +} + +static void +editor_show_setting (NMSetting *setting, NmCli *nmc) +{ + g_print (_("['%s' setting values]\n"), + nm_setting_get_name (setting)); + + nmc->print_output = NMC_PRINT_NORMAL; + nmc->multiline_output = TRUE; + nmc->escape_values = 0; + + /* Remove any previous data */ + nmc_empty_output_fields (nmc); + + setting_details (setting, nmc, NULL, nmc->editor_show_secrets); +} + +typedef enum { + NMC_EDITOR_MAIN_CMD_UNKNOWN = 0, + NMC_EDITOR_MAIN_CMD_GOTO, + NMC_EDITOR_MAIN_CMD_REMOVE, + NMC_EDITOR_MAIN_CMD_SET, + NMC_EDITOR_MAIN_CMD_DESCRIBE, + NMC_EDITOR_MAIN_CMD_PRINT, + NMC_EDITOR_MAIN_CMD_VERIFY, + NMC_EDITOR_MAIN_CMD_SAVE, + NMC_EDITOR_MAIN_CMD_ACTIVATE, + NMC_EDITOR_MAIN_CMD_BACK, + NMC_EDITOR_MAIN_CMD_HELP, + NMC_EDITOR_MAIN_CMD_NMCLI, + NMC_EDITOR_MAIN_CMD_QUIT, +} NmcEditorMainCmd; + +static NmcEditorMainCmd +parse_editor_main_cmd (const char *cmd, char **cmd_arg) +{ + NmcEditorMainCmd editor_cmd = NMC_EDITOR_MAIN_CMD_UNKNOWN; + char **vec; + + vec = nmc_strsplit_set (cmd, " \t", 2); + if (g_strv_length (vec) < 1) { + if (cmd_arg) + *cmd_arg = NULL; + return NMC_EDITOR_MAIN_CMD_UNKNOWN; + } + + if (matches (vec[0], "goto") == 0) + editor_cmd = NMC_EDITOR_MAIN_CMD_GOTO; + else if (matches (vec[0], "remove") == 0) + editor_cmd = NMC_EDITOR_MAIN_CMD_REMOVE; + else if (matches (vec[0], "set") == 0) + editor_cmd = NMC_EDITOR_MAIN_CMD_SET; + else if (matches (vec[0], "describe") == 0) + editor_cmd = NMC_EDITOR_MAIN_CMD_DESCRIBE; + else if (matches (vec[0], "print") == 0) + editor_cmd = NMC_EDITOR_MAIN_CMD_PRINT; + else if (matches (vec[0], "verify") == 0) + editor_cmd = NMC_EDITOR_MAIN_CMD_VERIFY; + else if (matches (vec[0], "save") == 0) + editor_cmd = NMC_EDITOR_MAIN_CMD_SAVE; + else if (matches (vec[0], "activate") == 0) + editor_cmd = NMC_EDITOR_MAIN_CMD_ACTIVATE; + else if (matches (vec[0], "back") == 0) + editor_cmd = NMC_EDITOR_MAIN_CMD_BACK; + else if (matches (vec[0], "help") == 0 || strcmp (vec[0], "?") == 0) + editor_cmd = NMC_EDITOR_MAIN_CMD_HELP; + else if (matches (vec[0], "quit") == 0) + editor_cmd = NMC_EDITOR_MAIN_CMD_QUIT; + else if (matches (vec[0], "nmcli") == 0) + editor_cmd = NMC_EDITOR_MAIN_CMD_NMCLI; + + /* set pointer to command argument */ + if (cmd_arg) + *cmd_arg = vec[1] ? g_strstrip (g_strdup (vec[1])) : NULL; + + g_strfreev (vec); + return editor_cmd; +} + +static void +editor_main_usage (void) +{ + g_print ("------------------------------------------------------------------------------\n"); + /* TRANSLATORS: do not translate command names and keywords before :: + * However, you should translate terms enclosed in <>. + */ + g_print (_("---[ Main menu ]---\n" + "goto [ | ] :: go to a setting or property\n" + "remove [.] | :: remove setting or reset property value\n" + "set [. ] :: set property value\n" + "describe [.] :: describe property\n" + "print [all | [.]] :: print the connection\n" + "verify [all | fix] :: verify the connection\n" + "save [persistent|temporary] :: save the connection\n" + "activate [] [/|] :: activate the connection\n" + "back :: go one level up (back)\n" + "help/? [] :: print this help\n" + "nmcli :: nmcli configuration\n" + "quit :: exit nmcli\n")); + g_print ("------------------------------------------------------------------------------\n"); +} + +static void +editor_main_help (const char *command) +{ + if (!command) + editor_main_usage (); + else { + /* detailed command descriptions */ + NmcEditorMainCmd cmd = parse_editor_main_cmd (command, NULL); + + switch (cmd) { + case NMC_EDITOR_MAIN_CMD_GOTO: + g_print (_("goto [.] | :: enter setting/property for editing\n\n" + "This command enters into a setting or property for editing it.\n\n" + "Examples: nmcli> goto connection\n" + " nmcli connection> goto secondaries\n" + " nmcli> goto ipv4.addresses\n")); + break; + case NMC_EDITOR_MAIN_CMD_REMOVE: + g_print (_("remove [.] :: remove setting or reset property value\n\n" + "This command removes an entire setting from the connection, or if a property\n" + "is given, resets that property to the default value.\n\n" + "Examples: nmcli> remove wifi-sec\n" + " nmcli> remove eth.mtu\n")); + break; + case NMC_EDITOR_MAIN_CMD_SET: + g_print (_("set [. ] :: set property value\n\n" + "This command sets property value.\n\n" + "Example: nmcli> set con.id My connection\n")); + break; + case NMC_EDITOR_MAIN_CMD_DESCRIBE: + g_print (_("describe [.] :: describe property\n\n" + "Shows property description. You can consult nm-settings(5) " + "manual page to see all NM settings and properties.\n")); + break; + case NMC_EDITOR_MAIN_CMD_PRINT: + g_print (_("print [all] :: print setting or connection values\n\n" + "Shows current property or the whole connection.\n\n" + "Example: nmcli ipv4> print all\n")); + break; + case NMC_EDITOR_MAIN_CMD_VERIFY: + g_print (_("verify [all | fix] :: verify setting or connection validity\n\n" + "Verifies whether the setting or connection is valid and can be saved later.\n" + "It indicates invalid values on error. Some errors may be fixed automatically\n" + "by 'fix' option.\n\n" + "Examples: nmcli> verify\n" + " nmcli> verify fix\n" + " nmcli bond> verify\n")); + break; + case NMC_EDITOR_MAIN_CMD_SAVE: + g_print (_("save [persistent|temporary] :: save the connection\n\n" + "Sends the connection profile to NetworkManager that either will save it\n" + "persistently, or will only keep it in memory. 'save' without an argument\n" + "means 'save persistent'.\n" + "Note that once you save the profile persistently those settings are saved\n" + "across reboot or restart. Subsequent changes can also be temporary or\n" + "persistent, but any temporary changes will not persist across reboot or\n" + "restart. If you want to fully remove the persistent connection, the connection\n" + "profile must be deleted.\n")); + break; + case NMC_EDITOR_MAIN_CMD_ACTIVATE: + g_print (_("activate [] [/|] :: activate the connection\n\n" + "Activates the connection.\n\n" + "Available options:\n" + " - device the connection will be activated on\n" + "/| - AP (Wi-Fi) or NSP (WiMAX) (prepend with / when is not specified)\n")); + break; + case NMC_EDITOR_MAIN_CMD_BACK: + g_print (_("back :: go to upper menu level\n\n")); + break; + case NMC_EDITOR_MAIN_CMD_HELP: + g_print (_("help/? [] :: help for the nmcli commands\n\n")); + break; + case NMC_EDITOR_MAIN_CMD_NMCLI: + g_print (_("nmcli [ ] :: nmcli configuration\n\n" + "Configures nmcli. The following options are available:\n" + "status-line yes | no [default: no]\n" + "save-confirmation yes | no [default: yes]\n" + "show-secrets yes | no [default: no]\n" + "prompt-color <0-8> [default: 0]\n" + " 0 = normal\n" + " 1 = \33[30mblack\33[0m\n" + " 2 = \33[31mred\33[0m\n" + " 3 = \33[32mgreen\33[0m\n" + " 4 = \33[33myellow\33[0m\n" + " 5 = \33[34mblue\33[0m\n" + " 6 = \33[35mmagenta\33[0m\n" + " 7 = \33[36mcyan\33[0m\n" + " 8 = \33[37mwhite\33[0m\n" + "\n" + "Examples: nmcli> nmcli status-line yes\n" + " nmcli> nmcli save-confirmation no\n" + " nmcli> nmcli prompt-color 3\n")); + break; + case NMC_EDITOR_MAIN_CMD_QUIT: + g_print (_("quit :: exit nmcli\n\n" + "This command exits nmcli. When the connection being edited " + "is not saved, the user is asked to confirm the action.\n")); + break; + default: + g_print (_("Unknown command: '%s'\n"), command); + break; + } + } +} + +typedef enum { + NMC_EDITOR_SUB_CMD_UNKNOWN = 0, + NMC_EDITOR_SUB_CMD_SET, + NMC_EDITOR_SUB_CMD_ADD, + NMC_EDITOR_SUB_CMD_CHANGE, + NMC_EDITOR_SUB_CMD_REMOVE, + NMC_EDITOR_SUB_CMD_DESCRIBE, + NMC_EDITOR_SUB_CMD_PRINT, + NMC_EDITOR_SUB_CMD_BACK, + NMC_EDITOR_SUB_CMD_HELP, + NMC_EDITOR_SUB_CMD_QUIT +} NmcEditorSubCmd; + +static NmcEditorSubCmd +parse_editor_sub_cmd (const char *cmd, char **cmd_arg) +{ + NmcEditorSubCmd editor_cmd = NMC_EDITOR_SUB_CMD_UNKNOWN; + char **vec; + + vec = nmc_strsplit_set (cmd, " \t", 2); + if (g_strv_length (vec) < 1) { + if (cmd_arg) + *cmd_arg = NULL; + return NMC_EDITOR_SUB_CMD_UNKNOWN; + } + + if (matches (vec[0], "set") == 0) + editor_cmd = NMC_EDITOR_SUB_CMD_SET; + else if (matches (vec[0], "add") == 0) + editor_cmd = NMC_EDITOR_SUB_CMD_ADD; + else if (matches (vec[0], "change") == 0) + editor_cmd = NMC_EDITOR_SUB_CMD_CHANGE; + else if (matches (vec[0], "remove") == 0) + editor_cmd = NMC_EDITOR_SUB_CMD_REMOVE; + else if (matches (vec[0], "describe") == 0) + editor_cmd = NMC_EDITOR_SUB_CMD_DESCRIBE; + else if (matches (vec[0], "print") == 0) + editor_cmd = NMC_EDITOR_SUB_CMD_PRINT; + else if (matches (vec[0], "back") == 0) + editor_cmd = NMC_EDITOR_SUB_CMD_BACK; + else if (matches (vec[0], "help") == 0 || strcmp (vec[0], "?") == 0) + editor_cmd = NMC_EDITOR_SUB_CMD_HELP; + else if (matches (vec[0], "quit") == 0) + editor_cmd = NMC_EDITOR_SUB_CMD_QUIT; + + /* set pointer to command argument */ + if (cmd_arg) + *cmd_arg = g_strdup (vec[1]); + + g_strfreev (vec); + return editor_cmd; +} + +static void +editor_sub_help (void) +{ + g_print ("------------------------------------------------------------------------------\n"); + /* TRANSLATORS: do not translate command names and keywords before :: + * However, you should translate terms enclosed in <>. + */ + g_print (_("---[ Property menu ]---\n" + "set [] :: set new value\n" + "add [] :: add new option to the property\n" + "change :: change current value\n" + "remove [ |