diff options
| author | Michael Biebl <biebl@debian.org> | 2015-01-22 00:29:39 +0100 |
|---|---|---|
| committer | Michael Biebl <biebl@debian.org> | 2015-01-22 00:29:39 +0100 |
| commit | 2c032d8f1c6292c1338a615e6ec40252889ba85c (patch) | |
| tree | 1f77182220b2b0264288ba4a476ab47e5bc48716 /clients/cli | |
| parent | 33491bc4279481db8ae47213e34a6d695a0e8830 (diff) | |
Imported Upstream version 1.0.0 upstream/1.0.0
Diffstat (limited to 'clients/cli')
| -rw-r--r-- | clients/cli/Makefile.am | 72 | ||||
| -rw-r--r-- | clients/cli/Makefile.in | 862 | ||||
| -rw-r--r-- | clients/cli/agent.c | 252 | ||||
| -rw-r--r-- | clients/cli/agent.h | 29 | ||||
| -rw-r--r-- | clients/cli/common.c | 1146 | ||||
| -rw-r--r-- | clients/cli/common.h | 69 | ||||
| -rw-r--r-- | clients/cli/connections.c | 9020 | ||||
| -rw-r--r-- | clients/cli/connections.h | 27 | ||||
| -rw-r--r-- | clients/cli/devices.c | 2925 | ||||
| -rw-r--r-- | clients/cli/devices.h | 27 | ||||
| -rw-r--r-- | clients/cli/general.c | 899 | ||||
| -rw-r--r-- | clients/cli/general.h | 29 | ||||
| -rw-r--r-- | clients/cli/nmcli-completion | 1287 | ||||
| -rw-r--r-- | clients/cli/nmcli.c | 621 | ||||
| -rw-r--r-- | clients/cli/nmcli.h | 156 | ||||
| -rw-r--r-- | clients/cli/polkit-agent.c | 166 | ||||
| -rw-r--r-- | clients/cli/polkit-agent.h | 30 | ||||
| -rw-r--r-- | clients/cli/settings-docs.c | 388 | ||||
| -rw-r--r-- | clients/cli/settings-docs.xsl | 86 | ||||
| -rw-r--r-- | clients/cli/settings.c | 7445 | ||||
| -rw-r--r-- | clients/cli/settings.h | 66 | ||||
| -rw-r--r-- | clients/cli/utils.c | 1151 | ||||
| -rw-r--r-- | clients/cli/utils.h | 96 |
23 files changed, 26849 insertions, 0 deletions
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 <glib.h> +#include <glib/gi18n.h> +#include <stdio.h> +#include <stdlib.h> +#include <errno.h> + +#include <readline/readline.h> +#include <readline/history.h> + +#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 <glib.h> +#include <glib/gi18n.h> +#include <stdio.h> +#include <stdlib.h> +#include <errno.h> + +#include <readline/readline.h> +#include <readline/history.h> + +#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 <glib.h> +#include <glib/gi18n.h> +#include <stdio.h> +#include <string.h> +#include <stdlib.h> +#include <unistd.h> +#include <errno.h> +#include <signal.h> +#include <netinet/ether.h> +#include <readline/readline.h> +#include <readline/history.h> + +#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 <con>' - 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 <con>' - 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 <con>' */ +/* 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] <ID>] ...\n\n" +#if WITH_WIMAX + " up [[id | uuid | path] <ID>] [ifname <ifname>] [ap <BSSID>] [nsp <name>] [passwd-file <file with passwords>]\n\n" +#else + " up [[id | uuid | path] <ID>] [ifname <ifname>] [ap <BSSID>] [passwd-file <file with passwords>]\n\n" +#endif + " down [id | uuid | path | apath] <ID> ...\n\n" + " add COMMON_OPTIONS TYPE_SPECIFIC_OPTIONS IP_OPTIONS\n\n" + " modify [--temporary] [id | uuid | path] <ID> ([+|-]<setting>.<property> <value>)+\n\n" + " edit [id | uuid | path] <ID>\n" + " edit [type <new_con_type>] [con-name <new_con_name>]\n\n" + " delete [id | uuid | path] <ID>\n\n" + " reload\n\n" + " load <filename> [ <filename>... ]\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] <ID> ...\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] <ID> [ifname <ifname>] [ap <BSSID>] [nsp <name>] [passwd-file <file with passwords>]\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 <ifname> [ap <BSSID>] [nsp <name>] [passwd-file <file with passwords>]\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] <ID> ...\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 <type>\n" + " ifname <interface name> | \"*\"\n" + " [con-name <connection name>]\n" + " [autoconnect yes|no]\n\n" + " [save yes|no]\n\n" + " TYPE_SPECIFIC_OPTIONS:\n" + " ethernet: [mac <MAC address>]\n" + " [cloned-mac <cloned MAC address>]\n" + " [mtu <MTU>]\n\n" + " wifi: ssid <SSID>\n" + " [mac <MAC address>]\n" + " [cloned-mac <cloned MAC address>]\n" + " [mtu <MTU>]\n" + " [mode infrastructure|ap|adhoc]\n\n" + " wimax: [mac <MAC address>]\n" + " [nsp <NSP>]\n\n" + " pppoe: username <PPPoE username>\n" + " [password <PPPoE password>]\n" + " [service <PPPoE service name>]\n" + " [mtu <MTU>]\n" + " [mac <MAC address>]\n\n" + " gsm: apn <APN>\n" + " [user <username>]\n" + " [password <password>]\n\n" + " cdma: [user <username>]\n" + " [password <password>]\n\n" + " infiniband: [mac <MAC address>]\n" + " [mtu <MTU>]\n" + " [transport-mode datagram | connected]\n" + " [parent <ifname>]\n" + " [p-key <IPoIB P_Key>]\n\n" + " bluetooth: [addr <bluetooth address>]\n" + " [bt-type panu|dun-gsm|dun-cdma]\n\n" + " vlan: dev <parent device (connection UUID, ifname, or MAC)>\n" + " id <VLAN ID>\n" + " [flags <VLAN flags>]\n" + " [ingress <ingress priority mapping>]\n" + " [egress <egress priority mapping>]\n" + " [mtu <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 <ifname>]\n" + " [miimon <num>]\n" + " [downdelay <num>]\n" + " [updelay <num>]\n" + " [arp-interval <num>]\n" + " [arp-ip-target <num>]\n" + " [lacp-rate slow (0) | fast (1)]\n\n" + " bond-slave: master <master (ifname, or connection UUID or name)>\n\n" + " team: [config <file>|<raw JSON data>]\n\n" + " team-slave: master <master (ifname, or connection UUID or name)>\n" + " [config <file>|<raw JSON data>]\n\n" + " bridge: [stp yes|no]\n" + " [priority <num>]\n" + " [forward-delay <2-30>]\n" + " [hello-time <1-10>]\n" + " [max-age <6-40>]\n" + " [ageing-time <0-1000000>]\n" + " [mac <MAC address>]\n\n" + " bridge-slave: master <master (ifname, or connection UUID or name)>\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 <username>]\n\n" + " olpc-mesh: ssid <SSID>\n" + " [channel <1-13>]\n" + " [dhcp-anycast <MAC address>]\n\n" + " IP_OPTIONS:\n" + " [ip4 <IPv4 address>] [gw4 <IPv4 gateway>]\n" + " [ip6 <IPv6 address>] [gw6 <IPv6 gateway>]\n\n")); +} + +static void +usage_connection_modify (void) +{ + g_printerr (_("Usage: nmcli connection modify { ARGUMENTS | help }\n" + "\n" + "ARGUMENTS := [id | uuid | path] <ID> ([+|-]<setting>.<property> <value>)+\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] <ID>\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 <new connection type>] [con-name <new connection 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] <ID>\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 := <filename> [<filename>...]\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 <ID>' */ + 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 <Enter> 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 [<setting> | <prop>] :: go to a setting or property\n" + "remove <setting>[.<prop>] | <prop> :: remove setting or reset property value\n" + "set [<setting>.<prop> <value>] :: set property value\n" + "describe [<setting>.<prop>] :: describe property\n" + "print [all | <setting>[.<prop>]] :: print the connection\n" + "verify [all | fix] :: verify the connection\n" + "save [persistent|temporary] :: save the connection\n" + "activate [<ifname>] [/<ap>|<nsp>] :: activate the connection\n" + "back :: go one level up (back)\n" + "help/? [<command>] :: print this help\n" + "nmcli <conf-option> <value> :: 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 <setting>[.<prop>] | <prop> :: 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 <setting>[.<prop>] :: 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 [<setting>.<prop> <value>] :: 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 [<setting>.<prop>] :: 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 [<ifname>] [/<ap>|<nsp>] :: activate the connection\n\n" + "Activates the connection.\n\n" + "Available options:\n" + "<ifname> - device the connection will be activated on\n" + "/<ap>|<nsp> - AP (Wi-Fi) or NSP (WiMAX) (prepend with / when <ifname> 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/? [<command>] :: help for the nmcli commands\n\n")); + break; + case NMC_EDITOR_MAIN_CMD_NMCLI: + g_print (_("nmcli [<conf-option> <value>] :: 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 [<value>] :: set new value\n" + "add [<value>] :: add new option to the property\n" + "change :: change current value\n" + "remove [<index> | <option>] :: delete the value\n" + "describe :: describe property\n" + "print [setting | connection] :: print property (setting/connection) value(s)\n" + "back :: go to upper level\n" + "help/? [<command>] :: print this help or command description\n" + "quit :: exit nmcli\n")); + g_print ("------------------------------------------------------------------------------\n"); +} + +static void +editor_sub_usage (const char *command) +{ + + if (!command) + editor_sub_help (); + else { + /* detailed command descriptions */ + NmcEditorSubCmd cmdsub = parse_editor_sub_cmd (command, NULL); + + switch (cmdsub) { + case NMC_EDITOR_SUB_CMD_SET: + g_print (_("set [<value>] :: set new value\n\n" + "This command sets provided <value> to this property\n")); + break; + case NMC_EDITOR_SUB_CMD_ADD: + g_print (_("add [<value>] :: append new value to the property\n\n" + "This command adds provided <value> to this property, if " + "the property is of a container type. For single-valued " + "properties the property value is replaced (same as 'set').\n")); + break; + case NMC_EDITOR_SUB_CMD_CHANGE: + g_print (_("change :: change current value\n\n" + "Displays current value and allows editing it.\n")); + break; + case NMC_EDITOR_SUB_CMD_REMOVE: + g_print (_("remove [<value>|<index>|<option name>] :: delete the value\n\n" + "Removes the property value. For single-valued properties, this sets the\n" + "property back to its default value. For container-type properties, this removes\n" + "all the values of that property, or you can specify an argument to remove just\n" + "a single item or option. The argument is either a value or index of the item to\n" + "remove, or an option name (for properties with named options).\n\n" + "Examples: nmcli ipv4.dns> remove 8.8.8.8\n" + " nmcli ipv4.dns> remove 2\n" + " nmcli bond.options> remove downdelay\n\n")); + break; + case NMC_EDITOR_SUB_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_SUB_CMD_PRINT: + g_print (_("print [property|setting|connection] :: print property (setting, connection) value(s)\n\n" + "Shows property value. Providing an argument you can also display " + "values for the whole setting or connection.\n")); + break; + case NMC_EDITOR_SUB_CMD_BACK: + g_print (_("back :: go to upper menu level\n\n")); + break; + case NMC_EDITOR_SUB_CMD_HELP: + g_print (_("help/? [<command>] :: help for nmcli commands\n\n")); + break; + case NMC_EDITOR_SUB_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 struct { + NMDevice *device; + NMActiveConnection *ac; + guint monitor_id; +} MonitorACInfo; + +static gboolean nmc_editor_cb_called; +static GError *nmc_editor_error; +static MonitorACInfo *nmc_editor_monitor_ac; +static GMutex nmc_editor_mutex; +static GCond nmc_editor_cond; + +/* + * Store 'error' to shared 'nmc_editor_error' and monitoring info to + * 'nmc_editor_monitor_ac' and signal the condition so that + * the 'editor-thread' thread could process that. + */ +static void +set_info_and_signal_editor_thread (GError *error, MonitorACInfo *monitor_ac_info) +{ + g_mutex_lock (&nmc_editor_mutex); + nmc_editor_cb_called = TRUE; + nmc_editor_error = error ? g_error_copy (error) : NULL; + nmc_editor_monitor_ac = monitor_ac_info; + g_cond_signal (&nmc_editor_cond); + g_mutex_unlock (&nmc_editor_mutex); +} + +static void +add_connection_editor_cb (GObject *client, + GAsyncResult *result, + gpointer user_data) +{ + NMRemoteConnection *connection; + GError *error = NULL; + + connection = nm_client_add_connection_finish (NM_CLIENT (client), result, &error); + set_info_and_signal_editor_thread (error, NULL); + + g_clear_object (&connection); + g_clear_error (&error); +} + +static void +update_connection_editor_cb (GObject *connection, + GAsyncResult *result, + gpointer user_data) +{ + GError *error = NULL; + + nm_remote_connection_commit_changes_finish (NM_REMOTE_CONNECTION (connection), + result, &error); + set_info_and_signal_editor_thread (error, NULL); + g_clear_error (&error); +} + +static gboolean +progress_activation_editor_cb (gpointer user_data) +{ + MonitorACInfo *info = (MonitorACInfo *) user_data; + NMDevice *device = info->device; + NMActiveConnection *ac = info->ac; + NMActiveConnectionState ac_state; + NMDeviceState dev_state; + + if (!device || !ac) + goto finish; + + ac_state = nm_active_connection_get_state (ac); + dev_state = nm_device_get_state (device); + + nmc_terminal_show_progress (nmc_device_state_to_string (dev_state)); + + if ( ac_state == NM_ACTIVE_CONNECTION_STATE_ACTIVATED + || dev_state == NM_DEVICE_STATE_ACTIVATED) { + nmc_terminal_erase_line (); + g_print (_("Connection successfully activated (D-Bus active path: %s)\n"), + nm_object_get_path (NM_OBJECT (ac))); + goto finish; /* we are done */ + } else if ( ac_state == NM_ACTIVE_CONNECTION_STATE_DEACTIVATED + || dev_state == NM_DEVICE_STATE_FAILED) { + nmc_terminal_erase_line (); + g_print (_("Error: Connection activation failed.\n")); + goto finish; /* we are done */ + } + + return TRUE; + +finish: + if (device) + g_object_unref (device); + if (ac) + g_object_unref (ac); + return FALSE; +} + +static void +activate_connection_editor_cb (GObject *client, + GAsyncResult *result, + gpointer user_data) +{ + ActivateConnectionInfo *info = (ActivateConnectionInfo *) user_data; + NMDevice *device = info->device; + const GPtrArray *ac_devs; + MonitorACInfo *monitor_ac_info = NULL; + NMActiveConnection *active; + GError *error = NULL; + + active = nm_client_activate_connection_finish (NM_CLIENT (client), result, &error); + + if (!error) { + if (!device) { + ac_devs = nm_active_connection_get_devices (active); + device = ac_devs->len > 0 ? g_ptr_array_index (ac_devs, 0) : NULL; + } + if (device) { + monitor_ac_info = g_malloc0 (sizeof (AddConnectionInfo)); + monitor_ac_info->device = g_object_ref (device); + monitor_ac_info->ac = active; + monitor_ac_info->monitor_id = g_timeout_add (120, progress_activation_editor_cb, monitor_ac_info); + } else + g_object_unref (active); + } + set_info_and_signal_editor_thread (error, monitor_ac_info); + g_clear_error (&error); +} + +/*----------------------------------------------------------------------------*/ + +static void +print_property_description (NMSetting *setting, const char *prop_name) +{ + char *desc; + + desc = nmc_setting_get_property_desc (setting, prop_name); + g_print ("\n=== [%s] ===\n%s\n", prop_name, desc); + g_free (desc); +} + +static void +print_setting_description (NMSetting *setting) +{ + /* Show description of all properties */ + char **all_props; + int i; + + all_props = nmc_setting_get_valid_properties (setting); + g_print (("<<< %s >>>\n"), nm_setting_get_name (setting)); + for (i = 0; all_props && all_props[i]; i++) + print_property_description (setting, all_props[i]); + g_strfreev (all_props); +} + +static gboolean +connection_remove_setting (NMConnection *connection, NMSetting *setting) +{ + gboolean mandatory; + + g_return_val_if_fail (setting, FALSE); + + mandatory = is_setting_mandatory (connection, setting); + if (!mandatory) { + nm_connection_remove_setting (connection, G_OBJECT_TYPE (setting)); + return TRUE; + } + g_print (_("Error: setting '%s' is mandatory and cannot be removed.\n"), + nm_setting_get_name (setting)); + return FALSE; +} + +static void +editor_show_status_line (NMConnection *connection, gboolean dirty, gboolean temp) +{ + NMSettingConnection *s_con; + const char *con_type, *con_id, *con_uuid; + + s_con = nm_connection_get_setting_connection (connection); + g_assert (s_con); + con_type = nm_setting_connection_get_connection_type (s_con); + con_id = nm_connection_get_id (connection); + con_uuid = nm_connection_get_uuid (connection); + + /* TRANSLATORS: status line in nmcli connection editor */ + g_print (_("[ Type: %s | Name: %s | UUID: %s | Dirty: %s | Temp: %s ]\n"), + con_type, con_id, con_uuid, + dirty ? _("yes") : _("no"), + temp ? _("yes") : _("no")); +} + +static gboolean +refresh_remote_connection (GWeakRef *weak, NMRemoteConnection **remote) +{ + gboolean previous; + + g_return_val_if_fail (remote != NULL, FALSE); + + previous = (*remote != NULL); + if (*remote) + g_object_unref (*remote); + *remote = g_weak_ref_get (weak); + + return (previous && !*remote); +} + +static gboolean +is_connection_dirty (NMConnection *connection, NMRemoteConnection *remote) +{ + return !nm_connection_compare (connection, + remote ? NM_CONNECTION (remote) : NULL, + NM_SETTING_COMPARE_FLAG_IGNORE_SECRETS | + NM_SETTING_COMPARE_FLAG_IGNORE_TIMESTAMP); +} + +static gboolean +confirm_quit (void) +{ + char *answer; + gboolean want_quit = FALSE; + + answer = nmc_readline (_("The connection is not saved. " + "Do you really want to quit? %s"), + prompt_yes_no (FALSE, NULL)); + answer = answer ? g_strstrip (answer) : NULL; + if (answer && matches (answer, WORD_LOC_YES) == 0) + want_quit = TRUE; + + g_free (answer); + return want_quit; +} + +/* + * Submenu for detailed property editing + * Return: TRUE - continue; FALSE - should quit + */ +static gboolean +property_edit_submenu (NmCli *nmc, + NMConnection *connection, + NMRemoteConnection **rem_con, + GWeakRef *rem_con_weak, + NMSetting *curr_setting, + const char *prop_name) +{ + NmcEditorSubCmd cmdsub; + gboolean cmd_property_loop = TRUE; + gboolean should_quit = FALSE; + char *prop_val_user; + gboolean set_result; + GError *tmp_err = NULL; + char *prompt; + gboolean dirty; + GValue prop_g_value = G_VALUE_INIT; + gboolean temp_changes; + gboolean removed; + + prompt = nmc_colorize (nmc->editor_prompt_color, "nmcli %s.%s> ", + nm_setting_get_name (curr_setting), prop_name); + + while (cmd_property_loop) { + char *cmd_property_user; + char *cmd_property_arg; + + /* Get the remote connection again, it may have disapeared */ + removed = refresh_remote_connection (rem_con_weak, rem_con); + if (removed) + g_print (_("The connection profile has been removed from another client. " + "You may type 'save' in the main menu to restore it.\n")); + + /* Connection is dirty? (not saved or differs from the saved) */ + dirty = is_connection_dirty (connection, *rem_con); + temp_changes = *rem_con ? nm_remote_connection_get_unsaved (*rem_con) : TRUE; + if (nmc->editor_status_line) + editor_show_status_line (connection, dirty, temp_changes); + + cmd_property_user = nmc_readline ("%s", prompt); + if (!cmd_property_user || *cmd_property_user == '\0') + continue; + cmdsub = parse_editor_sub_cmd (g_strstrip (cmd_property_user), &cmd_property_arg); + + switch (cmdsub) { + case NMC_EDITOR_SUB_CMD_SET: + case NMC_EDITOR_SUB_CMD_ADD: + /* list, arrays,...: SET replaces the whole property value + * ADD adds the new value(s) + * single values: : both SET and ADD sets the new value + */ + if (!cmd_property_arg) + prop_val_user = nmc_readline (_("Enter '%s' value: "), prop_name); + else + prop_val_user = g_strdup (cmd_property_arg); + + /* nmc_setting_set_property() only adds new value, thus we have to + * remove the original value and save it for error cases. + */ + if (cmdsub == NMC_EDITOR_SUB_CMD_SET) { + nmc_property_get_gvalue (curr_setting, prop_name, &prop_g_value); + nmc_property_set_default_value (curr_setting, prop_name); + } + + set_result = nmc_setting_set_property (curr_setting, prop_name, prop_val_user, &tmp_err); + g_free (prop_val_user); + if (!set_result) { + g_print (_("Error: failed to set '%s' property: %s\n"), prop_name, tmp_err->message); + g_clear_error (&tmp_err); + if (cmdsub == NMC_EDITOR_SUB_CMD_SET) { + /* Block change signals and restore original value */ + g_signal_handlers_block_matched (curr_setting, G_SIGNAL_MATCH_DATA, 0, 0, NULL, NULL, NULL); + nmc_property_set_gvalue (curr_setting, prop_name, &prop_g_value); + g_signal_handlers_unblock_matched (curr_setting, G_SIGNAL_MATCH_DATA, 0, 0, NULL, NULL, NULL); + } + } + if (G_IS_VALUE (&prop_g_value)) + g_value_unset (&prop_g_value); + break; + + case NMC_EDITOR_SUB_CMD_CHANGE: + rl_startup_hook = nmc_rl_set_deftext; + nmc_rl_pre_input_deftext = nmc_setting_get_property_out2in (curr_setting, prop_name, NULL); + prop_val_user = nmc_readline (_("Edit '%s' value: "), prop_name); + + nmc_property_get_gvalue (curr_setting, prop_name, &prop_g_value); + nmc_property_set_default_value (curr_setting, prop_name); + + if (!nmc_setting_set_property (curr_setting, prop_name, prop_val_user, &tmp_err)) { + g_print (_("Error: failed to set '%s' property: %s\n"), prop_name, tmp_err->message); + g_clear_error (&tmp_err); + g_signal_handlers_block_matched (curr_setting, G_SIGNAL_MATCH_DATA, 0, 0, NULL, NULL, NULL); + nmc_property_set_gvalue (curr_setting, prop_name, &prop_g_value); + g_signal_handlers_unblock_matched (curr_setting, G_SIGNAL_MATCH_DATA, 0, 0, NULL, NULL, NULL); + } + g_free (prop_val_user); + if (G_IS_VALUE (&prop_g_value)) + g_value_unset (&prop_g_value); + break; + + case NMC_EDITOR_SUB_CMD_REMOVE: + if (cmd_property_arg) { + unsigned long val_int = G_MAXUINT32; + char *option = NULL; + + if (!nmc_string_to_uint (cmd_property_arg, TRUE, 0, G_MAXUINT32, &val_int)) + option = g_strdup (cmd_property_arg); + + if (!nmc_setting_remove_property_option (curr_setting, prop_name, + option ? g_strstrip (option) : NULL, + (guint32) val_int, + &tmp_err)) { + g_print (_("Error: %s\n"), tmp_err->message); + g_clear_error (&tmp_err); + } + g_free (option); + } else { + if (!nmc_setting_reset_property (curr_setting, prop_name, &tmp_err)) { + g_print (_("Error: failed to remove value of '%s': %s\n"), prop_name, + tmp_err->message); + g_clear_error (&tmp_err); + } + } + break; + + case NMC_EDITOR_SUB_CMD_DESCRIBE: + /* Show property description */ + print_property_description (curr_setting, prop_name); + break; + + case NMC_EDITOR_SUB_CMD_PRINT: + /* Print current connection settings/properties */ + if (cmd_property_arg) { + if (matches (cmd_property_arg, "setting") == 0) + editor_show_setting (curr_setting, nmc); + else if ( matches (cmd_property_arg, "connection") == 0 + || matches (cmd_property_arg, "all") == 0) + editor_show_connection (connection, nmc); + else + g_print (_("Unknown command argument: '%s'\n"), cmd_property_arg); + } else { + char *prop_val = nmc_setting_get_property (curr_setting, prop_name, NULL); + g_print ("%s: %s\n", prop_name, prop_val); + g_free (prop_val); + } + break; + + case NMC_EDITOR_SUB_CMD_BACK: + cmd_property_loop = FALSE; + break; + + case NMC_EDITOR_SUB_CMD_HELP: + editor_sub_usage (cmd_property_arg); + break; + + case NMC_EDITOR_SUB_CMD_QUIT: + if (is_connection_dirty (connection, *rem_con)) { + if (confirm_quit ()) { + cmd_property_loop = FALSE; + should_quit = TRUE; /* we will quit nmcli */ + } + } else { + cmd_property_loop = FALSE; + should_quit = TRUE; /* we will quit nmcli */ + } + break; + + case NMC_EDITOR_SUB_CMD_UNKNOWN: + default: + g_print (_("Unknown command: '%s'\n"), cmd_property_user); + break; + } + g_free (cmd_property_user); + g_free (cmd_property_arg); + } + g_free (prompt); + + return !should_quit; +} + +/* + * Split 'str' in the following format: [[[setting.]property] [value]] + * and return the components in 'setting', 'property' and 'value' + * Use g_free() to deallocate the returned strings. + */ +static void +split_editor_main_cmd_args (const char *str, char **setting, char **property, char **value) +{ + char **args, **items; + + if (!str) + return; + + args = nmc_strsplit_set (str, " \t", 2); + if (args[0]) { + items = nmc_strsplit_set (args[0], ".", 2); + if (g_strv_length (items) == 2) { + if (setting) + *setting = g_strdup (items[0]); + if (property) + *property = g_strdup (items[1]); + } else { + if (property) + *property = g_strdup (items[0]); + } + g_strfreev (items); + + if (value && args[1]) + *value = g_strstrip (g_strdup (args[1])); + } + g_strfreev (args); +} + +static NMSetting * +is_setting_valid (NMConnection *connection, const NameItem *valid_settings, char *setting) +{ + const char *setting_name; + + if (!(setting_name = check_valid_name (setting, valid_settings, NULL))) + return NULL; + return nm_connection_get_setting_by_name (connection, setting_name); +} + +static char * +is_property_valid (NMSetting *setting, const char *property, GError **error) +{ + char **valid_props = NULL; + const char *prop_name; + char *ret; + + valid_props = nmc_setting_get_valid_properties (setting); + prop_name = nmc_string_is_valid (property, (const char **) valid_props, error); + ret = g_strdup (prop_name); + g_strfreev (valid_props); + return ret; +} + +static NMSetting * +create_setting_by_name (const char *name, const NameItem *valid_settings) +{ + const char *setting_name; + NMSetting *setting = NULL; + + /* Get a valid setting name */ + setting_name = check_valid_name (name, valid_settings, NULL); + + if (setting_name) { + setting = nmc_setting_new_for_name (setting_name); + if (!setting) + return NULL; /* This should really not happen */ + nmc_setting_custom_init (setting); + } + return setting; +} + +static const char * +ask_check_setting (const char *arg, + const NameItem *valid_settings_arr, + const char *valid_settings_str) +{ + char *setting_name_user; + const char *setting_name; + GError *err = NULL; + + if (!arg) { + g_print (_("Available settings: %s\n"), valid_settings_str); + setting_name_user = nmc_readline (EDITOR_PROMPT_SETTING); + } else + setting_name_user = g_strdup (arg); + + if (setting_name_user) + g_strstrip (setting_name_user); + + if (!(setting_name = check_valid_name (setting_name_user, valid_settings_arr, &err))) { + g_print (_("Error: invalid setting name; %s\n"), err->message); + g_clear_error (&err); + } + g_free (setting_name_user); + return setting_name; +} + +static const char * +ask_check_property (const char *arg, + const char **valid_props, + const char *valid_props_str) +{ + char *prop_name_user; + const char *prop_name; + GError *tmp_err = NULL; + + if (!arg) { + g_print (_("Available properties: %s\n"), valid_props_str); + prop_name_user = nmc_readline (EDITOR_PROMPT_PROPERTY); + if (prop_name_user) + g_strstrip (prop_name_user); + } else + prop_name_user = g_strdup (arg); + + if (!(prop_name = nmc_string_is_valid (prop_name_user, valid_props, &tmp_err))) { + g_print (_("Error: property %s\n"), tmp_err->message); + g_clear_error (&tmp_err); + } + g_free (prop_name_user); + return prop_name; +} + +/* Copy timestamp from src do dst */ +static void +update_connection_timestamp (NMConnection *src, NMConnection *dst) +{ + NMSettingConnection *s_con_src, *s_con_dst; + + s_con_src = nm_connection_get_setting_connection (src); + s_con_dst = nm_connection_get_setting_connection (dst); + if (s_con_src && s_con_dst) { + guint64 timestamp = nm_setting_connection_get_timestamp (s_con_src); + g_object_set (s_con_dst, NM_SETTING_CONNECTION_TIMESTAMP, timestamp, NULL); + } +} + +static gboolean +confirm_connection_saving (NMConnection *local, NMConnection *remote) +{ + NMSettingConnection *s_con_loc, *s_con_rem; + gboolean ac_local, ac_remote; + gboolean confirmed = TRUE; + + s_con_loc = nm_connection_get_setting_connection (local); + g_assert (s_con_loc); + ac_local = nm_setting_connection_get_autoconnect (s_con_loc); + + if (remote) { + s_con_rem = nm_connection_get_setting_connection (remote); + g_assert (s_con_rem); + ac_remote = nm_setting_connection_get_autoconnect (s_con_rem); + } else + ac_remote = FALSE; + + if (ac_local && !ac_remote) { + char *answer; + answer = nmc_readline (_("Saving the connection with 'autoconnect=yes'. " + "That might result in an immediate activation of the connection.\n" + "Do you still want to save? %s"), prompt_yes_no (TRUE, NULL)); + answer = answer ? g_strstrip (answer) : NULL; + if (!answer || matches (answer, WORD_LOC_YES) == 0) + confirmed = TRUE; + else + confirmed = FALSE; + g_free (answer); + } + return confirmed; +} + +typedef struct { + guint level; + char *main_prompt; + NMSetting *curr_setting; + char **valid_props; + char *valid_props_str; +} NmcEditorMenuContext; + +static void +menu_switch_to_level0 (NmcEditorMenuContext *menu_ctx, + const char *prompt, + NmcTermColor prompt_color) +{ + menu_ctx->level = 0; + g_free (menu_ctx->main_prompt); + menu_ctx->main_prompt = nmc_colorize (prompt_color, "%s", prompt); + menu_ctx->curr_setting = NULL; + g_strfreev (menu_ctx->valid_props); + menu_ctx->valid_props = NULL; + g_free (menu_ctx->valid_props_str); + menu_ctx->valid_props_str = NULL; +} + +static void +menu_switch_to_level1 (NmcEditorMenuContext *menu_ctx, + NMSetting *setting, + const char *setting_name, + NmcTermColor prompt_color) +{ + menu_ctx->level = 1; + g_free (menu_ctx->main_prompt); + menu_ctx->main_prompt = nmc_colorize (prompt_color, "nmcli %s> ", setting_name); + menu_ctx->curr_setting = setting; + g_strfreev (menu_ctx->valid_props); + menu_ctx->valid_props = nmc_setting_get_valid_properties (menu_ctx->curr_setting); + g_free (menu_ctx->valid_props_str); + menu_ctx->valid_props_str = g_strjoinv (", ", menu_ctx->valid_props); +} + +static gboolean +editor_menu_main (NmCli *nmc, NMConnection *connection, const char *connection_type) +{ + NMRemoteConnection *rem_con; + NMRemoteConnection *con_tmp; + GWeakRef weak = { { NULL } }; + gboolean removed; + NmcEditorMainCmd cmd; + char *cmd_user; + gboolean cmd_loop = TRUE; + char *cmd_arg = NULL; + char *cmd_arg_s, *cmd_arg_p, *cmd_arg_v; + const char *BASE_PROMPT = "nmcli> "; + const NameItem *valid_settings_arr = NULL; + char *valid_settings_str = NULL; + AddConnectionInfo *info = NULL; + gboolean dirty; + gboolean temp_changes; + GError *err1 = NULL; + NmcEditorMenuContext menu_ctx; + + valid_settings_arr = get_valid_settings_array (connection_type); + valid_settings_str = get_valid_options_string (valid_settings_arr); + g_print (_("You may edit the following settings: %s\n"), valid_settings_str); + + menu_ctx.level = 0; + menu_ctx.main_prompt = nmc_colorize (nmc->editor_prompt_color, BASE_PROMPT); + menu_ctx.curr_setting = NULL; + menu_ctx.valid_props = NULL; + menu_ctx.valid_props_str = NULL; + + /* Get remote connection */ + con_tmp = nm_client_get_connection_by_uuid (nmc->client, + nm_connection_get_uuid (connection)); + g_weak_ref_init (&weak, con_tmp); + rem_con = g_weak_ref_get (&weak); + + while (cmd_loop) { + /* Connection is dirty? (not saved or differs from the saved) */ + dirty = is_connection_dirty (connection, rem_con); + temp_changes = rem_con ? nm_remote_connection_get_unsaved (rem_con) : TRUE; + if (nmc->editor_status_line) + editor_show_status_line (connection, dirty, temp_changes); + + /* Read user input */ + cmd_user = nmc_readline ("%s", menu_ctx.main_prompt); + + /* Get the remote connection again, it may have disapeared */ + removed = refresh_remote_connection (&weak, &rem_con); + if (removed) + g_print (_("The connection profile has been removed from another client. " + "You may type 'save' to restore it.\n")); + + if (!cmd_user || *cmd_user == '\0') + continue; + cmd = parse_editor_main_cmd (g_strstrip (cmd_user), &cmd_arg); + + cmd_arg_s = NULL; + cmd_arg_p = NULL; + cmd_arg_v = NULL; + split_editor_main_cmd_args (cmd_arg, &cmd_arg_s, &cmd_arg_p, &cmd_arg_v); + switch (cmd) { + case NMC_EDITOR_MAIN_CMD_SET: + /* Set property value */ + if (!cmd_arg) { + if (menu_ctx.level == 1) { + const char *prop_name; + char *prop_val_user = NULL; + const char *avals; + GError *tmp_err = NULL; + + prop_name = ask_check_property (cmd_arg, + (const char **) menu_ctx.valid_props, + menu_ctx.valid_props_str); + if (!prop_name) + break; + + avals = nmc_setting_get_property_allowed_values (menu_ctx.curr_setting, prop_name); + if (avals) + g_print (_("Allowed values for '%s' property: %s\n"), prop_name, avals); + + prop_val_user = nmc_readline (_("Enter '%s' value: "), prop_name); + + /* Set property value */ + if (!nmc_setting_set_property (menu_ctx.curr_setting, prop_name, prop_val_user, &tmp_err)) { + g_print (_("Error: failed to set '%s' property: %s\n"), prop_name, tmp_err->message); + g_clear_error (&tmp_err); + } + } else { + g_print (_("Error: no setting selected; valid are [%s]\n"), valid_settings_str); + g_print (_("use 'goto <setting>' first, or 'set <setting>.<property>'\n")); + } + } else { + NMSetting *ss = NULL; + gboolean created_ss = FALSE; + char *prop_name; + GError *tmp_err = NULL; + + if (cmd_arg_s) { + /* setting provided as "setting.property" */ + ss = is_setting_valid (connection, valid_settings_arr, cmd_arg_s); + if (!ss) { + ss = create_setting_by_name (cmd_arg_s, valid_settings_arr); + if (!ss) { + g_print (_("Error: invalid setting argument '%s'; valid are [%s]\n"), + cmd_arg_s, valid_settings_str); + break; + } + created_ss = TRUE; + } + } else { + if (menu_ctx.curr_setting) + ss = menu_ctx.curr_setting; + else { + g_print (_("Error: missing setting for '%s' property\n"), cmd_arg_p); + break; + } + } + + prop_name = is_property_valid (ss, cmd_arg_p, &tmp_err); + if (!prop_name) { + g_print (_("Error: invalid property: %s\n"), tmp_err->message); + g_clear_error (&tmp_err); + if (created_ss) + g_object_unref (ss); + break; + } + + + + /* Ask for value */ + if (!cmd_arg_v) { + const char *avals = nmc_setting_get_property_allowed_values (ss, prop_name); + if (avals) + g_print (_("Allowed values for '%s' property: %s\n"), prop_name, avals); + + cmd_arg_v = nmc_readline (_("Enter '%s' value: "), prop_name); + } + + /* Set property value */ + if (!nmc_setting_set_property (ss, prop_name, cmd_arg_v, &tmp_err)) { + g_print (_("Error: failed to set '%s' property: %s\n"), + prop_name, tmp_err->message); + g_clear_error (&tmp_err); + } + + if (created_ss) + nm_connection_add_setting (connection, ss); + g_free (prop_name); + } + break; + + case NMC_EDITOR_MAIN_CMD_GOTO: + /* cmd_arg_s != NULL means 'setting.property' argument */ + if (menu_ctx.level == 0 || cmd_arg_s) { + /* in top level - no setting selected yet */ + const char *setting_name; + NMSetting *setting; + const char *user_arg = cmd_arg_s ? cmd_arg_s : cmd_arg_p; + + setting_name = ask_check_setting (user_arg, valid_settings_arr, valid_settings_str); + if (!setting_name) + break; + + setting = nm_connection_get_setting_by_name (connection, setting_name); + if (!setting) { + setting = nmc_setting_new_for_name (setting_name); + if (!setting) { + g_print (_("Error: unknown setting '%s'\n"), setting_name); + break; + } + nmc_setting_custom_init (setting); + nm_connection_add_setting (connection, setting); + } + /* Set global variable for use in TAB completion */ + nmc_tab_completion.setting = setting; + + /* Switch to level 1 */ + menu_switch_to_level1 (&menu_ctx, setting, setting_name, nmc->editor_prompt_color); + + if (!cmd_arg_s) { + g_print (_("You may edit the following properties: %s\n"), menu_ctx.valid_props_str); + break; + } + } + if (menu_ctx.level == 1 || cmd_arg_s) { + /* level 1 - setting selected */ + const char *prop_name; + + prop_name = ask_check_property (cmd_arg_p, + (const char **) menu_ctx.valid_props, + menu_ctx.valid_props_str); + if (!prop_name) + break; + + /* submenu - level 2 - editing properties */ + cmd_loop = property_edit_submenu (nmc, + connection, + &rem_con, + &weak, + menu_ctx.curr_setting, + prop_name); + } + break; + + case NMC_EDITOR_MAIN_CMD_REMOVE: + /* Remove setting from connection, or delete value of a property */ + if (!cmd_arg) { + if (menu_ctx.level == 1) { + GError *tmp_err = NULL; + const char *prop_name; + + prop_name = ask_check_property (cmd_arg, + (const char **) menu_ctx.valid_props, + menu_ctx.valid_props_str); + if (!prop_name) + break; + + /* Delete property value */ + if (!nmc_setting_reset_property (menu_ctx.curr_setting, prop_name, &tmp_err)) { + g_print (_("Error: failed to remove value of '%s': %s\n"), prop_name, + tmp_err->message); + g_clear_error (&tmp_err); + } + } else + g_print (_("Error: no argument given; valid are [%s]\n"), valid_settings_str); + } else { + NMSetting *ss = NULL; + gboolean descr_all; + char *user_s; + + /* cmd_arg_s != NULL means argument is "setting.property" */ + descr_all = !cmd_arg_s && !menu_ctx.curr_setting; + user_s = descr_all ? cmd_arg_p : cmd_arg_s ? cmd_arg_s : NULL; + if (user_s) { + ss = is_setting_valid (connection, valid_settings_arr, user_s); + if (!ss) { + if (check_valid_name (user_s, valid_settings_arr, NULL)) + g_print (_("Setting '%s' is not present in the connection.\n"), user_s); + else + g_print (_("Error: invalid setting argument '%s'; valid are [%s]\n"), + user_s, valid_settings_str); + break; + } + } else + ss = menu_ctx.curr_setting; + + if (descr_all) { + /* Remove setting from the connection */ + connection_remove_setting (connection, ss); + if (ss == menu_ctx.curr_setting) { + /* If we removed the setting we are in, go up */ + menu_switch_to_level0 (&menu_ctx, BASE_PROMPT, nmc->editor_prompt_color); + nmc_tab_completion.setting = NULL; /* for TAB completion */ + } + } else { + GError *tmp_err = NULL; + char *prop_name = is_property_valid (ss, cmd_arg_p, &tmp_err); + if (prop_name) { + /* Delete property value */ + if (!nmc_setting_reset_property (ss, prop_name, &tmp_err)) { + g_print (_("Error: failed to remove value of '%s': %s\n"), prop_name, + tmp_err->message); + g_clear_error (&tmp_err); + } + } else { + /* If the string is not a property, try it as a setting */ + NMSetting *s_tmp; + s_tmp = is_setting_valid (connection, valid_settings_arr, cmd_arg_p); + if (s_tmp) { + /* Remove setting from the connection */ + connection_remove_setting (connection, s_tmp); + /* coverity[copy_paste_error] - suppress Coverity COPY_PASTE_ERROR defect */ + if (ss == menu_ctx.curr_setting) { + /* If we removed the setting we are in, go up */ + menu_switch_to_level0 (&menu_ctx, BASE_PROMPT, nmc->editor_prompt_color); + nmc_tab_completion.setting = NULL; /* for TAB completion */ + } + } else + g_print (_("Error: %s properties, nor it is a setting name.\n"), + tmp_err->message); + g_clear_error (&tmp_err); + } + g_free (prop_name); + } + } + break; + + case NMC_EDITOR_MAIN_CMD_DESCRIBE: + /* Print property description */ + if (!cmd_arg) { + if (menu_ctx.level == 1) { + const char *prop_name; + + prop_name = ask_check_property (cmd_arg, + (const char **) menu_ctx.valid_props, + menu_ctx.valid_props_str); + if (!prop_name) + break; + + /* Show property description */ + print_property_description (menu_ctx.curr_setting, prop_name); + } else { + g_print (_("Error: no setting selected; valid are [%s]\n"), valid_settings_str); + g_print (_("use 'goto <setting>' first, or 'describe <setting>.<property>'\n")); + } + } else { + NMSetting *ss = NULL; + gboolean unref_ss = FALSE; + gboolean descr_all; + char *user_s; + + /* cmd_arg_s != NULL means argument is "setting.property" */ + descr_all = !cmd_arg_s && !menu_ctx.curr_setting; + user_s = descr_all ? cmd_arg_p : cmd_arg_s ? cmd_arg_s : NULL; + if (user_s) { + ss = is_setting_valid (connection, valid_settings_arr, user_s); + if (!ss) { + ss = create_setting_by_name (user_s, valid_settings_arr); + if (!ss) { + g_print (_("Error: invalid setting argument '%s'; valid are [%s]\n"), + user_s, valid_settings_str); + break; + } + unref_ss = TRUE; + } + } else + ss = menu_ctx.curr_setting; + + if (descr_all) { + /* Show description for all properties */ + print_setting_description (ss); + } else { + GError *tmp_err = NULL; + char *prop_name = is_property_valid (ss, cmd_arg_p, &tmp_err); + if (prop_name) { + /* Show property description */ + print_property_description (ss, prop_name); + } else { + /* If the string is not a property, try it as a setting */ + NMSetting *s_tmp; + s_tmp = is_setting_valid (connection, valid_settings_arr, cmd_arg_p); + if (s_tmp) + print_setting_description (s_tmp); + else + g_print (_("Error: invalid property: %s, " + "neither a valid setting name.\n"), + tmp_err->message); + g_clear_error (&tmp_err); + } + g_free (prop_name); + } + if (unref_ss) + g_object_unref (ss); + } + break; + + case NMC_EDITOR_MAIN_CMD_PRINT: + /* Print current connection settings/properties */ + if (cmd_arg) { + if (strcmp (cmd_arg, "all") == 0) + editor_show_connection (connection, nmc); + else { + NMSetting *ss = NULL; + gboolean whole_setting; + char *user_s; + + /* cmd_arg_s != NULL means argument is "setting.property" */ + whole_setting = !cmd_arg_s && !menu_ctx.curr_setting; + user_s = whole_setting ? cmd_arg_p : cmd_arg_s ? cmd_arg_s : NULL; + if (user_s) { + const char *s_name; + s_name = check_valid_name (user_s, valid_settings_arr, NULL); + if (!s_name) { + g_print (_("Error: unknown setting: '%s'\n"), user_s); + break; + } + ss = nm_connection_get_setting_by_name (connection, s_name); + if (!ss) { + g_print (_("Error: '%s' setting not present in the connection\n"), s_name); + break; + } + } else + ss = menu_ctx.curr_setting; + + if (whole_setting) { + /* Print the whole setting */ + editor_show_setting (ss, nmc); + } else { + GError *err = NULL; + char *prop_name = is_property_valid (ss, cmd_arg_p, &err); + if (prop_name) { + /* Print one property */ + char *prop_val = nmc_setting_get_property (ss, prop_name, NULL); + g_print ("%s.%s: %s\n", nm_setting_get_name (ss),prop_name , prop_val); + g_free (prop_val); + } else { + /* If the string is not a property, try it as a setting */ + NMSetting *s_tmp; + s_tmp = is_setting_valid (connection, valid_settings_arr, cmd_arg_p); + if (s_tmp) { + /* Print the whole setting */ + editor_show_setting (s_tmp, nmc); + } else + g_print (_("Error: invalid property: %s%s\n"), + err->message, + cmd_arg_s ? "" : _(", neither a valid setting name")); + g_clear_error (&err); + } + g_free (prop_name); + } + } + } else { + if (menu_ctx.curr_setting) + editor_show_setting (menu_ctx.curr_setting, nmc); + else + editor_show_connection (connection, nmc); + } + break; + + case NMC_EDITOR_MAIN_CMD_VERIFY: + /* Verify current setting or the whole connection */ + if (cmd_arg && strcmp (cmd_arg, "all") && strcmp (cmd_arg, "fix")) { + g_print (_("Invalid verify option: %s\n"), cmd_arg); + break; + } + + if ( menu_ctx.curr_setting + && (!cmd_arg || strcmp (cmd_arg, "all") != 0)) { + GError *tmp_err = NULL; + (void) nm_setting_verify (menu_ctx.curr_setting, NULL, &tmp_err); + g_print (_("Verify setting '%s': %s\n"), + nm_setting_get_name (menu_ctx.curr_setting), + tmp_err ? tmp_err->message : "OK"); + g_clear_error (&tmp_err); + } else { + GError *tmp_err = NULL; + gboolean valid, modified; + gboolean fixed = TRUE; + + valid = nm_connection_verify (connection, &tmp_err); + if (!valid && (g_strcmp0 (cmd_arg, "fix") == 0)) { + /* Try to fix normalizable errors */ + g_clear_error (&tmp_err); + fixed = nm_connection_normalize (connection, NULL, &modified, &tmp_err); + } + g_print (_("Verify connection: %s\n"), + tmp_err ? tmp_err->message : "OK"); + if (!fixed) + g_print (_("The error cannot be fixed automatically.\n")); + g_clear_error (&tmp_err); + } + break; + + case NMC_EDITOR_MAIN_CMD_SAVE: + /* Save the connection */ + if (nm_connection_verify (connection, &err1)) { + gboolean persistent = TRUE; + + /* parse argument */ + if (cmd_arg) { + if (matches (cmd_arg, "temporary") == 0) + persistent = FALSE; + else if (matches (cmd_arg, "persistent") == 0) + persistent = TRUE; + else { + g_print (_("Error: invalid argument '%s'\n"), cmd_arg); + break; + } + } + + /* Ask for save confirmation if the connection changes to autoconnect=yes */ + if (nmc->editor_save_confirmation) + if (!confirm_connection_saving (connection, NM_CONNECTION (rem_con))) + break; + + if (!rem_con) { + /* Tell the settings service to add the new connection */ + info = g_malloc0 (sizeof (AddConnectionInfo)); + info->nmc = nmc; + info->con_name = g_strdup (nm_connection_get_id (connection)); + add_new_connection (persistent, + nmc->client, + connection, + add_connection_editor_cb, + info); + } else { + /* Save/update already saved (existing) connection */ + nm_connection_replace_settings_from_connection (NM_CONNECTION (rem_con), + connection); + update_connection (persistent, rem_con, update_connection_editor_cb, NULL); + } + + g_mutex_lock (&nmc_editor_mutex); + //FIXME: add also a timeout for cases the callback is not called + while (!nmc_editor_cb_called) + g_cond_wait (&nmc_editor_cond, &nmc_editor_mutex); + + if (nmc_editor_error) { + g_print (_("Error: Failed to save '%s' (%s) connection: %s\n"), + nm_connection_get_id (connection), + nm_connection_get_uuid (connection), + nmc_editor_error->message); + g_error_free (nmc_editor_error); + } else { + g_print (!rem_con ? + _("Connection '%s' (%s) successfully saved.\n") : + _("Connection '%s' (%s) successfully updated.\n"), + nm_connection_get_id (connection), + nm_connection_get_uuid (connection)); + + con_tmp = nm_client_get_connection_by_uuid (nmc->client, + nm_connection_get_uuid (connection)); + g_weak_ref_set (&weak, con_tmp); + refresh_remote_connection (&weak, &rem_con); + + /* Replace local connection with the remote one to be sure they are equal. + * This mitigates problems with plugins not preserving some properties or + * adding ipv{4,6} settings when not present. + */ + if (con_tmp) { + char *s_name = NULL; + if (menu_ctx.curr_setting) + s_name = g_strdup (nm_setting_get_name (menu_ctx.curr_setting)); + + /* Update settings in the local connection */ + nm_connection_replace_settings_from_connection (connection, + NM_CONNECTION (con_tmp)); + + /* Also update setting for menu context and TAB-completion */ + menu_ctx.curr_setting = s_name ? nm_connection_get_setting_by_name (connection, s_name) : NULL; + nmc_tab_completion.setting = menu_ctx.curr_setting; + g_free (s_name); + } + } + + nmc_editor_cb_called = FALSE; + nmc_editor_error = NULL; + g_mutex_unlock (&nmc_editor_mutex); + } else { + g_print (_("Error: connection verification failed: %s\n"), + err1 ? err1->message : _("(unknown error)")); + g_print (_("You may try running 'verify fix' to fix errors.\n")); + } + + g_clear_error (&err1); + break; + + case NMC_EDITOR_MAIN_CMD_ACTIVATE: + { + GError *tmp_err = NULL; + const char *ifname = cmd_arg_p; + const char *ap_nsp = cmd_arg_v; + + /* When only AP/NSP is specified it is prepended with '/' */ + if (!cmd_arg_v) { + if (ifname && ifname[0] == '/') { + ap_nsp = ifname + 1; + ifname = NULL; + } + } else + ap_nsp = ap_nsp && ap_nsp[0] == '/' ? ap_nsp + 1 : ap_nsp; + + if (is_connection_dirty (connection, rem_con)) { + g_print (_("Error: connection is not saved. Type 'save' first.\n")); + break; + } + if (!nm_connection_verify (NM_CONNECTION (rem_con), &tmp_err)) { + g_print (_("Error: connection is not valid: %s\n"), tmp_err->message); + g_clear_error (&tmp_err); + break; + } + + nmc->nowait_flag = FALSE; + nmc->should_wait = TRUE; + nmc->print_output = NMC_PRINT_PRETTY; + if (!nmc_activate_connection (nmc, NM_CONNECTION (rem_con), ifname, ap_nsp, ap_nsp, NULL, + activate_connection_editor_cb, &tmp_err)) { + g_print (_("Error: Cannot activate connection: %s.\n"), tmp_err->message); + g_clear_error (&tmp_err); + break; + } + + g_mutex_lock (&nmc_editor_mutex); + while (!nmc_editor_cb_called) + g_cond_wait (&nmc_editor_cond, &nmc_editor_mutex); + + if (nmc_editor_error) { + g_print (_("Error: Failed to activate '%s' (%s) connection: %s\n"), + nm_connection_get_id (connection), + nm_connection_get_uuid (connection), + nmc_editor_error->message); + g_error_free (nmc_editor_error); + } else { + g_print (_("Monitoring connection activation (press any key to continue)\n")); + nmc_get_user_input (""); + } + + if (nmc_editor_monitor_ac) { + if (nmc_editor_monitor_ac->monitor_id) + g_source_remove (nmc_editor_monitor_ac->monitor_id); + g_free (nmc_editor_monitor_ac); + } + nmc_editor_cb_called = FALSE; + nmc_editor_error = NULL; + nmc_editor_monitor_ac = NULL; + g_mutex_unlock (&nmc_editor_mutex); + + /* Update timestamp in local connection */ + update_connection_timestamp (NM_CONNECTION (rem_con), connection); + + } + break; + + case NMC_EDITOR_MAIN_CMD_BACK: + /* Go back (up) an the menu */ + if (menu_ctx.level == 1) { + menu_switch_to_level0 (&menu_ctx, BASE_PROMPT, nmc->editor_prompt_color); + nmc_tab_completion.setting = NULL; /* for TAB completion */ + } + break; + + case NMC_EDITOR_MAIN_CMD_HELP: + /* Print command help */ + editor_main_help (cmd_arg); + break; + + case NMC_EDITOR_MAIN_CMD_NMCLI: + if (cmd_arg_p && matches (cmd_arg_p, "status-line") == 0) { + GError *tmp_err = NULL; + gboolean bb; + if (!nmc_string_to_bool (cmd_arg_v ? g_strstrip (cmd_arg_v) : "", &bb, &tmp_err)) { + g_print (_("Error: status-line: %s\n"), tmp_err->message); + g_clear_error (&tmp_err); + } else + nmc->editor_status_line = bb; + } else if (cmd_arg_p && matches (cmd_arg_p, "save-confirmation") == 0) { + GError *tmp_err = NULL; + gboolean bb; + if (!nmc_string_to_bool (cmd_arg_v ? g_strstrip (cmd_arg_v) : "", &bb, &tmp_err)) { + g_print (_("Error: save-confirmation: %s\n"), tmp_err->message); + g_clear_error (&tmp_err); + } else + nmc->editor_save_confirmation = bb; + } else if (cmd_arg_p && matches (cmd_arg_p, "show-secrets") == 0) { + GError *tmp_err = NULL; + gboolean bb; + if (!nmc_string_to_bool (cmd_arg_v ? g_strstrip (cmd_arg_v) : "", &bb, &tmp_err)) { + g_print (_("Error: show-secrets: %s\n"), tmp_err->message); + g_clear_error (&tmp_err); + } else + nmc->editor_show_secrets = bb; + } else if (cmd_arg_p && matches (cmd_arg_p, "prompt-color") == 0) { + unsigned long color; + if (!nmc_string_to_uint (cmd_arg_v ? g_strstrip (cmd_arg_v) : "X", + TRUE, 0, 8, &color)) + g_print (_("Error: bad color number: '%s'; use <0-8>\n"), + cmd_arg_v ? cmd_arg_v : ""); + else { + nmc->editor_prompt_color = color; + g_free (menu_ctx.main_prompt); + if (menu_ctx.level == 0) + menu_ctx.main_prompt = nmc_colorize (nmc->editor_prompt_color, BASE_PROMPT); + else + menu_ctx.main_prompt = nmc_colorize (nmc->editor_prompt_color, "nmcli %s> ", + nm_setting_get_name (menu_ctx.curr_setting)); + } + } else if (!cmd_arg_p) { + g_print (_("Current nmcli configuration:\n")); + g_print ("status-line: %s\n" + "save-confirmation: %s\n" + "show-secrets: %s\n" + "prompt-color: %d\n", + nmc->editor_status_line ? "yes" : "no", + nmc->editor_save_confirmation ? "yes" : "no", + nmc->editor_show_secrets ? "yes" : "no", + nmc->editor_prompt_color); + } else + g_print (_("Invalid configuration option '%s'; allowed [%s]\n"), + cmd_arg_v ? cmd_arg_v : "", "status-line, save-confirmation, show-secrets, prompt-color"); + + break; + + case NMC_EDITOR_MAIN_CMD_QUIT: + if (is_connection_dirty (connection, rem_con)) { + if (confirm_quit ()) + cmd_loop = FALSE; /* quit command loop */ + } else + cmd_loop = FALSE; /* quit command loop */ + break; + + case NMC_EDITOR_MAIN_CMD_UNKNOWN: + default: + g_print (_("Unknown command: '%s'\n"), cmd_user); + break; + } + + g_free (cmd_user); + g_free (cmd_arg); + g_free (cmd_arg_s); + g_free (cmd_arg_p); + g_free (cmd_arg_v); + } + g_free (valid_settings_str); + g_free (menu_ctx.main_prompt); + g_strfreev (menu_ctx.valid_props); + g_free (menu_ctx.valid_props_str); + if (rem_con) + g_object_unref (rem_con); + g_weak_ref_clear (&weak); + + /* Save history file */ + save_history_cmds (nm_connection_get_uuid (connection)); + + return TRUE; +} + +static const char * +get_ethernet_device_name (NmCli *nmc) +{ + const GPtrArray *devices; + int i; + + devices = nm_client_get_devices (nmc->client); + for (i = 0; i < devices->len; i++) { + NMDevice *dev = g_ptr_array_index (devices, i); + if (NM_IS_DEVICE_ETHERNET (dev)) + return nm_device_get_iface (dev); + } + return NULL; +} + +static void +editor_init_new_connection (NmCli *nmc, NMConnection *connection) +{ + NMSetting *setting, *base_setting; + NMSettingConnection *s_con; + const char *con_type; + const char *slave_type = NULL; + + s_con = nm_connection_get_setting_connection (connection); + g_assert (s_con); + con_type = nm_setting_connection_get_connection_type (s_con); + + /* Initialize new connection according to its type using sensible defaults. */ + + nmc_setting_connection_connect_handlers (s_con, connection); + + if (g_strcmp0 (con_type, "bond-slave") == 0) + slave_type = NM_SETTING_BOND_SETTING_NAME; + if (g_strcmp0 (con_type, "team-slave") == 0) + slave_type = NM_SETTING_TEAM_SETTING_NAME; + if (g_strcmp0 (con_type, "bridge-slave") == 0) + slave_type = NM_SETTING_BRIDGE_SETTING_NAME; + + if (slave_type) { + const char *dev_ifname = get_ethernet_device_name (nmc); + + /* For bond/team/bridge slaves add 'wired' setting */ + setting = nm_setting_wired_new (); + nm_connection_add_setting (connection, setting); + + g_object_set (s_con, + NM_SETTING_CONNECTION_TYPE, NM_SETTING_WIRED_SETTING_NAME, + NM_SETTING_CONNECTION_MASTER, dev_ifname ? dev_ifname : "eth0", + NM_SETTING_CONNECTION_SLAVE_TYPE, slave_type, + NULL); + } else { + /* Add a "base" setting to the connection by default */ + base_setting = nmc_setting_new_for_name (con_type); + if (!base_setting) + return; + nm_connection_add_setting (connection, base_setting); + + /* Set a sensible bond/team/bridge interface name by default */ + if (g_strcmp0 (con_type, NM_SETTING_BOND_SETTING_NAME) == 0) + g_object_set (s_con, + NM_SETTING_CONNECTION_INTERFACE_NAME, "nm-bond", + NULL); + if (g_strcmp0 (con_type, NM_SETTING_TEAM_SETTING_NAME) == 0) + g_object_set (s_con, + NM_SETTING_CONNECTION_INTERFACE_NAME, "nm-team", + NULL); + if (g_strcmp0 (con_type, NM_SETTING_BRIDGE_SETTING_NAME) == 0) + g_object_set (s_con, + NM_SETTING_CONNECTION_INTERFACE_NAME, "nm-bridge", + NULL); + + /* Set sensible initial VLAN values */ + if (g_strcmp0 (con_type, NM_SETTING_VLAN_SETTING_NAME) == 0) { + const char *dev_ifname = get_ethernet_device_name (nmc); + + g_object_set (NM_SETTING_VLAN (base_setting), + NM_SETTING_VLAN_PARENT, dev_ifname ? dev_ifname : "eth0", + NM_SETTING_VLAN_ID, 1, + NULL); + g_object_set (s_con, + NM_SETTING_CONNECTION_MASTER, dev_ifname ? dev_ifname : "eth0", + NM_SETTING_CONNECTION_SLAVE_TYPE, NM_SETTING_VLAN_SETTING_NAME, + NULL); + } + + /* Initialize 'transport-mode' so that 'infiniband' is valid */ + if (g_strcmp0 (con_type, NM_SETTING_INFINIBAND_SETTING_NAME) == 0) + g_object_set (NM_SETTING_INFINIBAND (base_setting), + NM_SETTING_INFINIBAND_TRANSPORT_MODE, "datagram", + NULL); + + /* Initialize 'number' so that 'cdma' is valid */ + if (g_strcmp0 (con_type, NM_SETTING_CDMA_SETTING_NAME) == 0) + g_object_set (NM_SETTING_CDMA (base_setting), + NM_SETTING_CDMA_NUMBER, "#777", + NULL); + + /* Initialize 'number' so that 'gsm' is valid */ + if (g_strcmp0 (con_type, NM_SETTING_GSM_SETTING_NAME) == 0) + g_object_set (NM_SETTING_GSM (base_setting), + NM_SETTING_GSM_NUMBER, "*99#", + NULL); + + /* Wi-Fi */ + if (g_strcmp0 (con_type, NM_SETTING_WIRELESS_SETTING_NAME) == 0) { + /* For Wi-Fi set mode to "infrastructure". Even though mode == NULL + * is regarded as "infrastructure", explicit value makes no doubts. + */ + g_object_set (NM_SETTING_WIRELESS (base_setting), + NM_SETTING_WIRELESS_MODE, NM_SETTING_WIRELESS_MODE_INFRA, + NULL); + + /* Do custom initialization for wifi setting */ + nmc_setting_custom_init (base_setting); + } + + /* Always add IPv4 and IPv6 settings for non-slave connections */ + setting = nm_setting_ip4_config_new (); + nmc_setting_custom_init (setting); + nm_connection_add_setting (connection, setting); + + setting = nm_setting_ip6_config_new (); + nmc_setting_custom_init (setting); + nm_connection_add_setting (connection, setting); + } +} + +static void +editor_init_existing_connection (NMConnection *connection) +{ + NMSettingIPConfig *s_ip4, *s_ip6; + NMSettingWireless *s_wireless; + NMSettingConnection *s_con; + + s_ip4 = nm_connection_get_setting_ip4_config (connection); + s_ip6 = nm_connection_get_setting_ip6_config (connection); + s_wireless = nm_connection_get_setting_wireless (connection); + s_con = nm_connection_get_setting_connection (connection); + + if (s_ip4) + nmc_setting_ip4_connect_handlers (s_ip4); + if (s_ip6) + nmc_setting_ip6_connect_handlers (s_ip6); + if (s_wireless) + nmc_setting_wireless_connect_handlers (s_wireless); + if (s_con) + nmc_setting_connection_connect_handlers (s_con, connection); +} + +static NMCResultCode +do_connection_edit (NmCli *nmc, int argc, char **argv) +{ + NMConnection *connection = NULL; + NMSettingConnection *s_con; + const char *connection_type; + char *uuid; + char *default_name = NULL; + const char *type = NULL; + char *type_ask = NULL; + const char *con_name = NULL; + const char *con = NULL; + const char *con_id = NULL; + const char *con_uuid = NULL; + const char *con_path = NULL; + const char *selector = NULL; + char *tmp_str; + GError *error = NULL; + GError *err1 = NULL; + nmc_arg_t exp_args[] = { {"type", TRUE, &type, FALSE}, + {"con-name", TRUE, &con_name, FALSE}, + {"id", TRUE, &con_id, FALSE}, + {"uuid", TRUE, &con_uuid, FALSE}, + {"path", TRUE, &con_path, FALSE}, + {NULL} }; + + nmc->return_value = NMC_RESULT_SUCCESS; + + if (argc == 1) + con = *argv; + else { + if (!nmc_parse_args (exp_args, TRUE, &argc, &argv, &error)) { + g_string_assign (nmc->return_text, error->message); + nmc->return_value = error->code; + g_clear_error (&error); + goto error; + } + } + + /* Setup some readline completion stuff */ + /* Set a pointer to an alternative function to create matches */ + rl_attempted_completion_function = (rl_completion_func_t *) nmcli_editor_tab_completion; + /* Use ' ' and '.' as word break characters */ + rl_completer_word_break_characters = ". "; + + if (!con) { + if (con_id && !con_uuid && !con_path) { + con = con_id; + selector = "id"; + } else if (con_uuid && !con_id && !con_path) { + con = con_uuid; + selector = "uuid"; + } else if (con_path && !con_id && !con_uuid) { + con = con_path; + selector = "path"; + } else if (!con_path && !con_id && !con_uuid) { + /* no-op */ + } else { + g_string_printf (nmc->return_text, + _("Error: only one of 'id', uuid, or 'path' can be provided.")); + nmc->return_value = NMC_RESULT_ERROR_USER_INPUT; + goto error; + } + } + + if (con) { + /* Existing connection */ + NMConnection *found_con; + + found_con = nmc_find_connection (nmc->connections, selector, con, NULL); + if (!found_con) { + g_string_printf (nmc->return_text, _("Error: Unknown connection '%s'."), con); + nmc->return_value = NMC_RESULT_ERROR_NOT_FOUND; + goto error; + } + + /* Duplicate the connection and use that so that we need not + * differentiate existing vs. new later + */ + connection = nm_simple_connection_new_clone (found_con); + + /* Merge secrets into the connection */ + update_secrets_in_connection (NM_REMOTE_CONNECTION (found_con), connection); + + s_con = nm_connection_get_setting_connection (connection); + g_assert (s_con); + connection_type = nm_setting_connection_get_connection_type (s_con); + + if (type) + g_print (_("Warning: editing existing connection '%s'; 'type' argument is ignored\n"), + nm_connection_get_id (connection)); + if (con_name) + g_print (_("Warning: editing existing connection '%s'; 'con-name' argument is ignored\n"), + nm_connection_get_id (connection)); + + /* Load previously saved history commands for the connection */ + load_history_cmds (nm_connection_get_uuid (connection)); + + editor_init_existing_connection (connection); + } else { + /* New connection */ + connection_type = check_valid_name (type, nmc_valid_connection_types, &err1); + tmp_str = get_valid_options_string (nmc_valid_connection_types); + + while (!connection_type) { + if (!type) + g_print (_("Valid connection types: %s\n"), tmp_str); + else + g_print (_("Error: invalid connection type; %s\n"), err1->message); + g_clear_error (&err1); + + type_ask = nmc_readline (EDITOR_PROMPT_CON_TYPE); + type = type_ask = type_ask ? g_strstrip (type_ask) : NULL; + connection_type = check_valid_name (type_ask, nmc_valid_connection_types, &err1); + g_free (type_ask); + } + g_free (tmp_str); + + /* 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 + default_name = unique_connection_name (nmc->connections, + get_name_alias (connection_type, nmc_valid_connection_types)); + + g_object_set (s_con, + NM_SETTING_CONNECTION_ID, default_name, + NM_SETTING_CONNECTION_UUID, uuid, + NM_SETTING_CONNECTION_TYPE, connection_type, + NULL); + g_free (uuid); + g_free (default_name); + nm_connection_add_setting (connection, NM_SETTING (s_con)); + + /* Initialize the new connection so that it is valid from the start */ + editor_init_new_connection (nmc, connection); + } + + /* nmcli runs the editor */ + nmc->in_editor = TRUE; + + g_print ("\n"); + g_print (_("===| nmcli interactive connection editor |===")); + g_print ("\n\n"); + if (con) + g_print (_("Editing existing '%s' connection: '%s'"), connection_type, con); + else + g_print (_("Adding a new '%s' connection"), connection_type); + g_print ("\n\n"); + g_print (_("Type 'help' or '?' for available commands.")); + g_print ("\n"); + g_print (_("Type 'describe [<setting>.<prop>]' for detailed property description.")); + g_print ("\n\n"); + + /* Set global variables for use in TAB completion */ + nmc_tab_completion.nmc = nmc; + nmc_tab_completion.con_type = g_strdup (connection_type); + nmc_tab_completion.connection = connection; + + /* Run menu loop */ + editor_menu_main (nmc, connection, connection_type); + + if (connection) + g_object_unref (connection); + g_free (nmc_tab_completion.con_type); + + nmc->should_wait = TRUE; + return nmc->return_value; + +error: + g_assert (!connection); + g_free (type_ask); + + nmc->should_wait = FALSE; + return nmc->return_value; +} + + +static void +modify_connection_cb (GObject *connection, + GAsyncResult *result, + gpointer user_data) +{ + NmCli *nmc = (NmCli *) user_data; + GError *error = NULL; + + if (!nm_remote_connection_commit_changes_finish (NM_REMOTE_CONNECTION (connection), + result, &error)) { + g_string_printf (nmc->return_text, + _("Error: Failed to modify connection '%s': %s"), + nm_connection_get_id (NM_CONNECTION (connection)), + error->message); + g_error_free (error); + nmc->return_value = NMC_RESULT_ERROR_UNKNOWN; + } else { + if (nmc->print_output == NMC_PRINT_PRETTY) + g_print (_("Connection '%s' (%s) successfully modified.\n"), + nm_connection_get_id (NM_CONNECTION (connection)), + nm_connection_get_uuid (NM_CONNECTION (connection))); + } + quit (); +} + +static NMCResultCode +do_connection_modify (NmCli *nmc, + gboolean temporary, + int argc, + char **argv) +{ + NMConnection *connection = NULL; + NMRemoteConnection *rc = NULL; + NMSetting *setting; + NMSettingConnection *s_con; + const char *con_type; + const char *name; + const char *selector = NULL; + const char *s_dot_p; + const char *value; + char **strv = NULL; + const char *setting_name; + char *property_name = NULL; + gboolean append = FALSE; + gboolean remove = FALSE; + GError *error = NULL; + + nmc->should_wait = FALSE; + + if (argc == 0) { + g_string_printf (nmc->return_text, _("Error: No arguments provided.")); + nmc->return_value = NMC_RESULT_ERROR_USER_INPUT; + goto finish; + } + 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 finish; + } + name = *argv; + } + name = *argv; + if (!name) { + g_string_printf (nmc->return_text, _("Error: connection ID is missing.")); + nmc->return_value = NMC_RESULT_ERROR_USER_INPUT; + goto finish; + } + connection = nmc_find_connection (nmc->connections, selector, name, NULL); + if (!connection) { + g_string_printf (nmc->return_text, _("Error: Unknown connection '%s'."), name); + nmc->return_value = NMC_RESULT_ERROR_NOT_FOUND; + goto finish; + } + rc = nm_client_get_connection_by_uuid (nmc->client, + nm_connection_get_uuid (connection)); + if (!rc) { + g_string_printf (nmc->return_text, _("Error: Unknown connection '%s'."), name); + nmc->return_value = NMC_RESULT_ERROR_NOT_FOUND; + goto finish; + } + s_con = nm_connection_get_setting_connection (NM_CONNECTION (rc)); + g_assert (s_con); + con_type = nm_setting_connection_get_connection_type (s_con); + + if (next_arg (&argc, &argv) != 0) { + g_string_printf (nmc->return_text, _("Error: <setting>.<property> argument is missing.")); + nmc->return_value = NMC_RESULT_ERROR_USER_INPUT; + goto finish; + } + + /* Go through arguments and set properties */ + while (argc) { + s_dot_p = *argv; + next_arg (&argc, &argv); + value = *argv; + next_arg (&argc, &argv); + + if (!s_dot_p) { + g_string_printf (nmc->return_text, _("Error: <setting>.<property> argument is missing.")); + nmc->return_value = NMC_RESULT_ERROR_USER_INPUT; + goto finish; + } + if (!value) { + g_string_printf (nmc->return_text, _("Error: value for '%s' is missing."), s_dot_p); + nmc->return_value = NMC_RESULT_ERROR_USER_INPUT; + goto finish; + } + /* Empty string will reset the value to default */ + if (value[0] == '\0') + value = NULL; + + if (s_dot_p[0] == '+') { + s_dot_p++; + append = TRUE; + } else if (s_dot_p[0] == '-') { + s_dot_p++; + remove = TRUE; + } + + strv = g_strsplit (s_dot_p, ".", 2); + if (g_strv_length (strv) != 2) { + g_string_printf (nmc->return_text, _("Error: invalid <setting>.<property> '%s'."), + s_dot_p); + nmc->return_value = NMC_RESULT_ERROR_USER_INPUT; + goto finish; + } + + setting_name = check_valid_name (strv[0], get_valid_settings_array (con_type), &error); + if (!setting_name) { + g_string_printf (nmc->return_text, _("Error: invalid or not allowed setting '%s': %s."), + strv[0], error->message); + nmc->return_value = NMC_RESULT_ERROR_USER_INPUT; + goto finish; + } + setting = nm_connection_get_setting_by_name (NM_CONNECTION (rc), setting_name); + if (!setting) { + setting = nmc_setting_new_for_name (setting_name); + if (!setting) { + /* This should really not happen */ + g_string_printf (nmc->return_text, + "Error: don't know how to create '%s' setting.", + setting_name); + nmc->return_value = NMC_RESULT_ERROR_UNKNOWN; + goto finish; + } + nm_connection_add_setting (NM_CONNECTION (rc), setting); + } + + property_name = is_property_valid (setting, strv[1], &error); + if (!property_name) { + g_string_printf (nmc->return_text, _("Error: invalid property '%s': %s."), + strv[1], error->message); + nmc->return_value = NMC_RESULT_ERROR_USER_INPUT; + goto finish; + } + + if (!remove) { + /* Set/add value */ + if (!append) + nmc_setting_reset_property (setting, property_name, NULL); + if (!nmc_setting_set_property (setting, property_name, value, &error)) { + g_string_printf (nmc->return_text, _("Error: failed to modify %s.%s: %s."), + strv[0], strv[1], error->message); + nmc->return_value = NMC_RESULT_ERROR_USER_INPUT; + goto finish; + } + } else { + /* Remove value + * - either empty: remove whole value + * - or specified by index <0-n>: remove item at the index + * - or option name: remove item with the option name + */ + if (value) { + unsigned long idx; + if (nmc_string_to_uint (value, TRUE, 0, G_MAXUINT32, &idx)) + nmc_setting_remove_property_option (setting, property_name, NULL, idx, &error); + else + nmc_setting_remove_property_option (setting, property_name, value, 0, &error); + if (error) { + g_string_printf (nmc->return_text, _("Error: failed to remove a value from %s.%s: %s."), + strv[0], strv[1], error->message); + nmc->return_value = NMC_RESULT_ERROR_USER_INPUT; + goto finish; + } + } else + nmc_setting_reset_property (setting, property_name, NULL); + } + + g_strfreev (strv); + strv = NULL; + } + + update_connection (!temporary, rc, modify_connection_cb, nmc); + +finish: + nmc->should_wait = (nmc->return_value == NMC_RESULT_SUCCESS); + g_free (property_name); + if (strv) + g_strfreev (strv); + g_clear_error (&error); + return nmc->return_value; +} + + +typedef struct { + NmCli *nmc; + int counter; +} DeleteStateInfo; + +static void +delete_cb (GObject *con, GAsyncResult *result, gpointer user_data) +{ + DeleteStateInfo *info = (DeleteStateInfo *) user_data; + GError *error = NULL; + + if (!nm_remote_connection_delete_finish (NM_REMOTE_CONNECTION (con), result, &error)) { + g_string_printf (info->nmc->return_text, _("Error: Connection deletion failed: %s"), + error->message); + g_error_free (error); + info->nmc->return_value = NMC_RESULT_ERROR_CON_DEL; + } + + info->counter--; + if (info->counter == 0) { + g_free (info); + quit (); + } +} + +static NMCResultCode +do_connection_delete (NmCli *nmc, int argc, char **argv) +{ + NMConnection *connection = NULL; + DeleteStateInfo *del_info = NULL; + char *line = NULL; + char **arg_arr = NULL; + char **arg_ptr = argv; + int arg_num = argc; + GString *invalid_cons = NULL; + gboolean del_info_free = FALSE; + int pos = 0; + + nmc->return_value = NMC_RESULT_SUCCESS; + nmc->should_wait = FALSE; + + 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 finish; + } + } + + del_info = g_malloc0 (sizeof (DeleteStateInfo)); + del_info->nmc = nmc; + del_info->counter = 0; + del_info_free = TRUE; + + while (arg_num > 0) { + const char *selector = NULL; + + if ( strcmp (*arg_ptr, "id") == 0 + || strcmp (*arg_ptr, "uuid") == 0 + || strcmp (*arg_ptr, "path") == 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 finish; + } + } + + connection = nmc_find_connection (nmc->connections, selector, *arg_ptr, &pos); + if (!connection) { + if (nmc->print_output != NMC_PRINT_TERSE) + g_print (_("Error: unknown connection: %s\n"), *arg_ptr); + + if (!invalid_cons) + invalid_cons = g_string_new (NULL); + g_string_append_printf (invalid_cons, "'%s', ", *arg_ptr); + + /* take the next argument and continue */ + next_arg (&arg_num, &arg_ptr); + continue; + } + + /* We need to wait a bit so that nmcli's permissions can be checked. + * We will exit when D-Bus return (error) messages are received. + */ + nmc->should_wait = TRUE; + + /* del_info deallocation is handled in delete_cb() */ + del_info_free = FALSE; + + del_info->counter++; + + /* Delete the connection */ + nm_remote_connection_delete_async (NM_REMOTE_CONNECTION (connection), + NULL, delete_cb, del_info); + + /* Take next argument (if there's no other connection of the same name) */ + if (!pos) + next_arg (&arg_num, &arg_ptr); + } + +finish: + if (del_info_free) + g_free (del_info); + g_strfreev (arg_arr); + + if (invalid_cons) { + g_string_truncate (invalid_cons, invalid_cons->len-2); /* truncate trailing ", " */ + g_string_printf (nmc->return_text, _("Error: cannot delete unknown connection(s): %s."), + invalid_cons->str); + nmc->return_value = NMC_RESULT_ERROR_NOT_FOUND; + g_string_free (invalid_cons, TRUE); + } + return nmc->return_value; +} + +static NMCResultCode +do_connection_reload (NmCli *nmc, int argc, char **argv) +{ + GError *error = NULL; + + nmc->return_value = NMC_RESULT_SUCCESS; + nmc->should_wait = FALSE; + + 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; + } + + if (!nm_client_reload_connections (nmc->client, NULL, &error)) { + g_string_printf (nmc->return_text, _("Error: failed to reload connections: %s."), + error->message); + nmc->return_value = NMC_RESULT_ERROR_UNKNOWN; + g_clear_error (&error); + } + + return nmc->return_value; +} + +static NMCResultCode +do_connection_load (NmCli *nmc, int argc, char **argv) +{ + GError *error = NULL; + char **filenames, **failures = NULL; + int i; + + nmc->return_value = NMC_RESULT_SUCCESS; + nmc->should_wait = FALSE; + + 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; + } + + if (argc == 0) { + g_string_printf (nmc->return_text, _("Error: No connection specified.")); + nmc->return_value = NMC_RESULT_ERROR_USER_INPUT; + return nmc->return_value; + } + + filenames = g_new (char *, argc + 1); + for (i = 0; i < argc; i++) + filenames[i] = argv[i]; + filenames[i] = NULL; + + nm_client_load_connections (nmc->client, filenames, &failures, NULL, &error); + g_free (filenames); + if (error) { + g_string_printf (nmc->return_text, _("Error: failed to load connection: %s."), + error->message); + nmc->return_value = NMC_RESULT_ERROR_UNKNOWN; + g_error_free (error); + } + + if (failures) { + for (i = 0; failures[i]; i++) + g_printerr (_("Could not load file '%s'\n"), failures[i]); + g_strfreev (failures); + } + + return nmc->return_value; +} + + +typedef struct { + NmCli *nmc; + int argc; + char **argv; +} NmcEditorThreadData; + +static GThread *editor_thread; +static NmcEditorThreadData editor_thread_data; + +/* + * We need to run do_connection_edit() in a thread so that + * glib main loop is not blocked and could receive and process D-Bus + * return messages. + */ +static gpointer +connection_editor_thread_func (gpointer data) +{ + NmcEditorThreadData *td = (NmcEditorThreadData *) data; + + /* run editor for editing/adding connections */ + td->nmc->return_value = do_connection_edit (td->nmc, td->argc, td->argv); + + /* quit glib main loop now that we are done with this thread */ + quit (); + + return NULL; +} + +static char * +gen_func_connection_names (const char *text, int state) +{ + int i; + const char **connections; + char *ret; + + if (nm_cli.connections->len == 0) + return NULL; + + connections = g_new (const char *, nm_cli.connections->len + 1); + for (i = 0; i < nm_cli.connections->len; i++) { + NMConnection *con = NM_CONNECTION (nm_cli.connections->pdata[i]); + const char *id = nm_connection_get_id (con); + connections[i++] = id; + } + connections[i] = NULL; + + ret = nmc_rl_gen_func_basic (text, state, connections); + + g_free (connections); + return ret; +} + +static char ** +nmcli_con_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; + + /* Disable appending space after completion */ + rl_completion_append_character = '\0'; + + if (!is_single_word (rl_line_buffer)) + return NULL; + + if (g_strcmp0 (rl_prompt, PROMPT_CONNECTION) == 0) + generator_func = gen_func_connection_names; + + if (generator_func) + match_array = rl_completion_matches (text, generator_func); + + return match_array; +} + +/* Entry point function for connections-related commands: 'nmcli connection' */ +NMCResultCode +do_connections (NmCli *nmc, int argc, char **argv) +{ + GError *error = NULL; + + /* Register polkit agent */ + nmc_start_polkit_agent_start_try (nmc); + + /* Set completion function for 'nmcli con' */ + rl_attempted_completion_function = (rl_completion_func_t *) nmcli_con_tab_completion; + + /* Exit early on help */ + if (nmc_arg_is_help (*argv)) { + usage (); + return nmc->return_value; + } + if (argc != 0 && nmc_arg_is_help (*(argv+1))) { + if (usage_connection_second_level (*argv)) + return nmc->return_value; + } + + /* Get NMClient object early */ + 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; + + /* Get the connection list */ + nmc->connections = nm_client_get_connections (nmc->client); + + /* Now parse the command line and perform the required operation */ + if (argc == 0) { + if (!nmc_terse_option_check (nmc->print_output, nmc->required_fields, &error)) + goto opt_error; + nmc->return_value = do_connections_show (nmc, FALSE, FALSE, argc, argv); + } else { + if (matches (*argv, "show") == 0) { + gboolean active = FALSE; + gboolean show_secrets = FALSE; + int i; + + next_arg (&argc, &argv); + /* check connection show options [--active] [--show-secrets] */ + for (i = 0; i < 2; i++) { + if (!active && nmc_arg_is_option (*argv, "active")) { + active = TRUE; + next_arg (&argc, &argv); + } + if (!show_secrets && nmc_arg_is_option (*argv, "show-secrets")) { + show_secrets = TRUE; + next_arg (&argc, &argv); + } + } + nmc->return_value = do_connections_show (nmc, active, show_secrets, argc, argv); + } else if (matches(*argv, "up") == 0) { + nmc->return_value = do_connection_up (nmc, argc-1, argv+1); + } else if (matches(*argv, "down") == 0) { + nmc->return_value = do_connection_down (nmc, argc-1, argv+1); + } else if (matches(*argv, "add") == 0) { + nmc->return_value = do_connection_add (nmc, argc-1, argv+1); + } else if (matches(*argv, "edit") == 0) { + nmc->should_wait = TRUE; + editor_thread_data.nmc = nmc; + editor_thread_data.argc = argc - 1; + editor_thread_data.argv = argv + 1; + editor_thread = g_thread_new ("editor-thread", connection_editor_thread_func, &editor_thread_data); + g_thread_unref (editor_thread); + } else if (matches(*argv, "delete") == 0) { + nmc->return_value = do_connection_delete (nmc, argc-1, argv+1); + } else if (matches(*argv, "reload") == 0) { + nmc->return_value = do_connection_reload (nmc, argc-1, argv+1); + } else if (matches(*argv, "load") == 0) { + nmc->return_value = do_connection_load (nmc, argc-1, argv+1); + } else if (matches (*argv, "modify") == 0) { + gboolean temporary = FALSE; + + next_arg (&argc, &argv); + if (nmc_arg_is_option (*argv, "temporary")) { + temporary = TRUE; + next_arg (&argc, &argv); + } + nmc->return_value = do_connection_modify (nmc, temporary, argc, argv); + } else { + usage (); + g_string_printf (nmc->return_text, _("Error: '%s' is not valid 'connection' command."), *argv); + nmc->return_value = NMC_RESULT_ERROR_USER_INPUT; + } + } + + return nmc->return_value; + +opt_error: + g_string_printf (nmc->return_text, _("Error: %s."), error->message); + nmc->return_value = NMC_RESULT_ERROR_USER_INPUT; + g_error_free (error); + return nmc->return_value; +} diff --git a/clients/cli/connections.h b/clients/cli/connections.h new file mode 100644 index 00000000..c7368597 --- /dev/null +++ b/clients/cli/connections.h @@ -0,0 +1,27 @@ +/* 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. + * + * (C) Copyright 2010 - 2014 Red Hat, Inc. + */ + +#ifndef NMC_CONNECTIONS_H +#define NMC_CONNECTIONS_H + +#include "nmcli.h" + +NMCResultCode do_connections (NmCli *nmc, int argc, char **argv); + +#endif /* NMC_CONNECTIONS_H */ diff --git a/clients/cli/devices.c b/clients/cli/devices.c new file mode 100644 index 00000000..5530adc7 --- /dev/null +++ b/clients/cli/devices.c @@ -0,0 +1,2925 @@ +/* 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 <stdio.h> +#include <string.h> +#include <stdlib.h> +#include <errno.h> +#include <readline/readline.h> + +#include <glib.h> +#include <glib/gi18n.h> + +#include "nm-secret-agent-simple.h" +#include "polkit-agent.h" +#include "utils.h" +#include "common.h" +#include "devices.h" + +/* define some prompts */ +#define PROMPT_INTERFACE _("Interface: ") + +/* Available fields for 'device status' */ +static NmcOutputField nmc_fields_dev_status[] = { + {"DEVICE", N_("DEVICE"), 10}, /* 0 */ + {"TYPE", N_("TYPE"), 17}, /* 1 */ + {"STATE", N_("STATE"), 13}, /* 2 */ + {"DBUS-PATH", N_("DBUS-PATH"), 43}, /* 3 */ + {"CONNECTION", N_("CONNECTION"), 20}, /* 4 */ + {"CON-UUID", N_("CON-UUID"), 38}, /* 5 */ + {"CON-PATH", N_("CON-PATH"), 51}, /* 6 */ + {NULL, NULL, 0} +}; +#define NMC_FIELDS_DEV_STATUS_ALL "DEVICE,TYPE,STATE,DBUS-PATH,CONNECTION,CON-UUID,CON-PATH" +#define NMC_FIELDS_DEV_STATUS_COMMON "DEVICE,TYPE,STATE,CONNECTION" + + +/* Available fields for 'device show' - GENERAL part */ +static NmcOutputField nmc_fields_dev_show_general[] = { + {"NAME", N_("NAME"), 10}, /* 0 */ + {"DEVICE", N_("DEVICE"), 10}, /* 1 */ + {"TYPE", N_("TYPE"), 17}, /* 2 */ + {"VENDOR", N_("VENDOR"), 20}, /* 3 */ + {"PRODUCT", N_("PRODUCT"), 50}, /* 4 */ + {"DRIVER", N_("DRIVER"), 9}, /* 5 */ + {"DRIVER-VERSION", N_("DRIVER-VERSION"), 18}, /* 6 */ + {"FIRMWARE-VERSION", N_("FIRMWARE-VERSION"), 18}, /* 7 */ + {"HWADDR", N_("HWADDR"), 19}, /* 8 */ + {"MTU", N_("MTU"), 10}, /* 9 */ + {"STATE", N_("STATE"), 14}, /* 10 */ + {"REASON", N_("REASON"), 25}, /* 11 */ + {"UDI", N_("UDI"), 64}, /* 12 */ + {"IP-IFACE", N_("IP-IFACE"), 10}, /* 13 */ + {"IS-SOFTWARE", N_("IS-SOFTWARE"), 15}, /* 14 */ + {"NM-MANAGED", N_("NM-MANAGED"), 15}, /* 15 */ + {"AUTOCONNECT", N_("AUTOCONNECT"), 15}, /* 16 */ + {"FIRMWARE-MISSING", N_("FIRMWARE-MISSING"), 18}, /* 17 */ + {"CONNECTION", N_("CONNECTION"), 20}, /* 18 */ + {"CON-UUID", N_("CON-UUID"), 38}, /* 19 */ + {"CON-PATH", N_("CON-PATH"), 51}, /* 20 */ + {NULL, NULL, 0} +}; +#define NMC_FIELDS_DEV_SHOW_GENERAL_ALL "NAME,DEVICE,TYPE,VENDOR,PRODUCT,DRIVER,DRIVER-VERSION,FIRMWARE-VERSION,HWADDR,MTU,STATE,REASON,"\ + "UDI,IP-IFACE,IS-SOFTWARE,NM-MANAGED,AUTOCONNECT,FIRMWARE-MISSING,CONNECTION,CON-UUID,CON-PATH" +#define NMC_FIELDS_DEV_SHOW_GENERAL_COMMON "NAME,DEVICE,TYPE,VENDOR,PRODUCT,DRIVER,HWADDR,STATE" + +/* Available fields for 'device show' - CONNECTIONS part */ +static NmcOutputField nmc_fields_dev_show_connections[] = { + {"NAME", N_("NAME"), 10}, /* 0 */ + {"AVAILABLE-CONNECTION-PATHS", N_("AVAILABLE-CONNECTION-PATHS"), 80}, /* 1 */ + {"AVAILABLE-CONNECTIONS", N_("AVAILABLE-CONNECTIONS"), 80}, /* 2 */ + {NULL, NULL, 0} +}; +#define NMC_FIELDS_DEV_SHOW_CONNECTIONS_ALL "AVAILABLE-CONNECTION-PATHS,AVAILABLE-CONNECTIONS" +#define NMC_FIELDS_DEV_SHOW_CONNECTIONS_COMMON "AVAILABLE-CONNECTION-PATHS,AVAILABLE-CONNECTIONS" + +/* Available fields for 'device show' - CAPABILITIES part */ +static NmcOutputField nmc_fields_dev_show_cap[] = { + {"NAME", N_("NAME"), 13}, /* 0 */ + {"CARRIER-DETECT", N_("CARRIER-DETECT"), 16}, /* 1 */ + {"SPEED", N_("SPEED"), 10}, /* 2 */ + {"IS-SOFTWARE", N_("IS-SOFTWARE"), 12}, /* 3 */ + {NULL, NULL, 0} +}; +#define NMC_FIELDS_DEV_SHOW_CAP_ALL "NAME,CARRIER-DETECT,SPEED,IS-SOFTWARE" +#define NMC_FIELDS_DEV_SHOW_CAP_COMMON "NAME,CARRIER-DETECT,SPEED,IS-SOFTWARE" + +/* Available fields for 'device show' - wired properties part */ +static NmcOutputField nmc_fields_dev_show_wired_prop[] = { + {"NAME", N_("NAME"), 18}, /* 0 */ + {"CARRIER", N_("CARRIER"), 10}, /* 1 */ + {NULL, NULL, 0} +}; +#define NMC_FIELDS_DEV_SHOW_WIRED_PROP_ALL "NAME,CARRIER" +#define NMC_FIELDS_DEV_SHOW_WIRED_PROP_COMMON "NAME,CARRIER" + +/* Available fields for 'device show' - wireless properties part */ +static NmcOutputField nmc_fields_dev_show_wifi_prop[] = { + {"NAME", N_("NAME"), 18}, /* 0 */ + {"WEP", N_("WEP"), 5}, /* 1 */ + {"WPA", N_("WPA"), 5}, /* 2 */ + {"WPA2", N_("WPA2"), 6}, /* 3 */ + {"TKIP", N_("TKIP"), 6}, /* 4 */ + {"CCMP", N_("CCMP"), 6}, /* 5 */ + {"AP", N_("AP"), 6}, /* 6 */ + {"ADHOC", N_("ADHOC"), 6}, /* 7 */ + {NULL, NULL, 0} +}; +#define NMC_FIELDS_DEV_SHOW_WIFI_PROP_ALL "NAME,WEP,WPA,WPA2,TKIP,CCMP,AP,ADHOC" +#define NMC_FIELDS_DEV_SHOW_WIFI_PROP_COMMON "NAME,WEP,WPA,WPA2,TKIP,CCMP,AP,ADHOC" + +/* Available fields for 'device show' - wimax properties part */ +static NmcOutputField nmc_fields_dev_show_wimax_prop[] = { + {"NAME", N_("NAME"), 18}, /* 0 */ + {"CTR-FREQ", N_("CTR-FREQ"), 7}, /* 1 */ + {"RSSI", N_("RSSI"), 5}, /* 2 */ + {"CINR", N_("CINR"), 5}, /* 3 */ + {"TX-POW", N_("TX-POW"), 5}, /* 4 */ + {"BSID", N_("BSID"), 18}, /* 5 */ + {NULL, NULL, 0} +}; +#define NMC_FIELDS_DEV_SHOW_WIMAX_PROP_ALL "NAME,CTR-FREQ,RSSI,CINR,TX-POW,BSID" +#define NMC_FIELDS_DEV_SHOW_WIMAX_PROP_COMMON "NAME,CTR-FREQ,RSSI,CINR,TX-POW,BSID" + +/* Available fields for 'device wifi list' */ +static NmcOutputField nmc_fields_dev_wifi_list[] = { + {"NAME", N_("NAME"), 15}, /* 0 */ + {"SSID", N_("SSID"), 33}, /* 1 */ + {"SSID-HEX", N_("SSID-HEX"), 66}, /* 2 */ + {"BSSID", N_("BSSID"), 19}, /* 3 */ + {"MODE", N_("MODE"), 16}, /* 4 */ + {"CHAN", N_("CHAN"), 6}, /* 5 */ + {"FREQ", N_("FREQ"), 10}, /* 6 */ + {"RATE", N_("RATE"), 10}, /* 7 */ + {"SIGNAL", N_("SIGNAL"), 8}, /* 8 */ + {"BARS", N_("BARS"), 6}, /* 9 */ + {"SECURITY", N_("SECURITY"), 10}, /* 10 */ + {"WPA-FLAGS", N_("WPA-FLAGS"), 25}, /* 11 */ + {"RSN-FLAGS", N_("RSN-FLAGS"), 25}, /* 12 */ + {"DEVICE", N_("DEVICE"), 10}, /* 13 */ + {"ACTIVE", N_("ACTIVE"), 8}, /* 14 */ + {"IN-USE", N_("*"), 1}, /* 15 */ + {"DBUS-PATH", N_("DBUS-PATH"), 46}, /* 16 */ + {NULL, NULL, 0} +}; +#define NMC_FIELDS_DEV_WIFI_LIST_ALL "SSID,SSID-HEX,BSSID,MODE,CHAN,FREQ,RATE,SIGNAL,BARS,SECURITY,"\ + "WPA-FLAGS,RSN-FLAGS,DEVICE,ACTIVE,IN-USE,DBUS-PATH" +#define NMC_FIELDS_DEV_WIFI_LIST_COMMON "IN-USE,SSID,MODE,CHAN,RATE,SIGNAL,BARS,SECURITY" +#define NMC_FIELDS_DEV_WIFI_LIST_FOR_DEV_LIST "NAME,"NMC_FIELDS_DEV_WIFI_LIST_COMMON + +/* Available fields for 'device wimax list' */ +static NmcOutputField nmc_fields_dev_wimax_list[] = { + {"NAME", N_("NAME"), 15}, /* 0 */ + {"NSP", N_("NSP"), 33}, /* 1 */ + {"SIGNAL", N_("SIGNAL"), 8}, /* 2 */ + {"TYPE", N_("TYPE"), 16}, /* 3 */ + {"DEVICE", N_("DEVICE"), 10}, /* 4 */ + {"ACTIVE", N_("ACTIVE"), 8}, /* 5 */ + {"DBUS-PATH", N_("DBUS-PATH"), 46}, /* 6 */ + {NULL, NULL, 0} +}; +#define NMC_FIELDS_DEV_WIMAX_LIST_ALL "NSP,SIGNAL,TYPE,DEVICE,ACTIVE,DBUS-PATH" +#define NMC_FIELDS_DEV_WIMAX_LIST_COMMON "NSP,SIGNAL,TYPE,DEVICE,ACTIVE" +#define NMC_FIELDS_DEV_WIMAX_LIST_FOR_DEV_LIST "NAME,"NMC_FIELDS_DEV_WIMAX_LIST_COMMON + +/* Available fields for 'device show' - BOND, TEAM, BRIDGE part */ +static NmcOutputField nmc_fields_dev_show_master_prop[] = { + {"NAME", N_("NAME"), 18}, /* 0 */ + {"SLAVES", N_("SLAVES"), 20}, /* 1 */ + {NULL, NULL, 0} +}; +#define NMC_FIELDS_DEV_SHOW_MASTER_PROP_ALL "NAME,SLAVES" +#define NMC_FIELDS_DEV_SHOW_MASTER_PROP_COMMON "NAME,SLAVES" + +/* Available fields for 'device show' - VLAN part */ +static NmcOutputField nmc_fields_dev_show_vlan_prop[] = { + {"NAME", N_("NAME"), 18}, /* 0 */ + {"PARENT", N_("PARENT"), 10}, /* 1 */ + {"ID", N_("ID"), 5}, /* 2 */ + {NULL, NULL, 0} +}; +#define NMC_FIELDS_DEV_SHOW_VLAN_PROP_ALL "NAME,PARENT,ID" +#define NMC_FIELDS_DEV_SHOW_VLAN_PROP_COMMON "NAME,PARENT,ID" + +/* Available fields for 'device show' - BLUETOOTH part */ +static NmcOutputField nmc_fields_dev_show_bluetooth[] = { + {"NAME", N_("NAME"), 0}, /* 0 */ + {"CAPABILITIES", N_("CAPABILITIES"), 0}, /* 1 */ + {NULL, NULL, 0} +}; +#define NMC_FIELDS_DEV_SHOW_BLUETOOTH_ALL "NAME,CAPABILITIES" +#define NMC_FIELDS_DEV_SHOW_BLUETOOTH_COMMON "NAME,CAPABILITIES" + +/* 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 sections for 'device show' */ +static NmcOutputField nmc_fields_dev_show_sections[] = { + {"GENERAL", N_("GENERAL"), 0, nmc_fields_dev_show_general + 1 }, /* 0 */ + {"CAPABILITIES", N_("CAPABILITIES"), 0, nmc_fields_dev_show_cap + 1 }, /* 1 */ + {"WIFI-PROPERTIES", N_("WIFI-PROPERTIES"), 0, nmc_fields_dev_show_wifi_prop + 1 }, /* 2 */ + {"AP", N_("AP"), 0, nmc_fields_dev_wifi_list + 1 }, /* 3 */ + {"WIRED-PROPERTIES", N_("WIRED-PROPERTIES"), 0, nmc_fields_dev_show_wired_prop + 1 }, /* 4 */ + {"WIMAX-PROPERTIES", N_("WIMAX-PROPERTIES"), 0, nmc_fields_dev_show_wimax_prop + 1 }, /* 5 */ + {"NSP", N_("NSP"), 0, nmc_fields_dev_wimax_list + 1 }, /* 6 */ + {"IP4", N_("IP4"), 0, nmc_fields_ip4_config + 1 }, /* 7 */ + {"DHCP4", N_("DHCP4"), 0, nmc_fields_dhcp4_config + 1 }, /* 8 */ + {"IP6", N_("IP6"), 0, nmc_fields_ip6_config + 1 }, /* 9 */ + {"DHCP6", N_("DHCP6"), 0, nmc_fields_dhcp6_config + 1 }, /* 10 */ + {"BOND", N_("BOND"), 0, nmc_fields_dev_show_master_prop + 1 }, /* 11 */ + {"TEAM", N_("TEAM"), 0, nmc_fields_dev_show_master_prop + 1 }, /* 12 */ + {"BRIDGE", N_("BRIDGE"), 0, nmc_fields_dev_show_master_prop + 1 }, /* 13 */ + {"VLAN", N_("VLAN"), 0, nmc_fields_dev_show_vlan_prop + 1 }, /* 14 */ + {"BLUETOOTH", N_("BLUETOOTH"), 0, nmc_fields_dev_show_bluetooth + 1 }, /* 15 */ + {"CONNECTIONS", N_("CONNECTIONS"), 0, nmc_fields_dev_show_connections + 1 }, /* 16 */ + {NULL, NULL, 0, NULL } +}; +#if WITH_WIMAX +#define NMC_FIELDS_DEV_SHOW_SECTIONS_ALL "GENERAL,CAPABILITIES,BOND,TEAM,BRIDGE,VLAN,WIFI-PROPERTIES,AP,WIRED-PROPERTIES,"\ + "WIMAX-PROPERTIES,NSP,BLUETOOTH,CONNECTIONS,IP4,DHCP4,IP6,DHCP6" +#define NMC_FIELDS_DEV_SHOW_SECTIONS_COMMON "GENERAL.DEVICE,GENERAL.TYPE,GENERAL.HWADDR,GENERAL.MTU,GENERAL.STATE,"\ + "GENERAL.CONNECTION,GENERAL.CON-PATH,WIRED-PROPERTIES,IP4,IP6" +#else +#define NMC_FIELDS_DEV_SHOW_SECTIONS_ALL "GENERAL,CAPABILITIES,BOND,TEAM,BRIDGE,VLAN,WIFI-PROPERTIES,AP,WIRED-PROPERTIES,"\ + "BLUETOOTH,CONNECTIONS,IP4,DHCP4,IP6,DHCP6" +#define NMC_FIELDS_DEV_SHOW_SECTIONS_COMMON "GENERAL.DEVICE,GENERAL.TYPE,GENERAL.HWADDR,GENERAL.MTU,GENERAL.STATE,"\ + "GENERAL.CONNECTION,GENERAL.CON-PATH,WIRED-PROPERTIES,IP4,IP6" +#endif + + +/* glib main loop variable - defined in nmcli.c */ +extern GMainLoop *loop; + +static guint progress_id = 0; /* ID of event source for displaying progress */ + +static void +usage (void) +{ + g_printerr (_("Usage: nmcli device { COMMAND | help }\n\n" +#if WITH_WIMAX + "COMMAND := { status | show | connect | disconnect | delete | wifi | wimax }\n\n" +#else + "COMMAND := { status | show | connect | disconnect | delete | wifi }\n\n" +#endif + " status\n\n" + " show [<ifname>]\n\n" + " connect <ifname>\n\n" + " disconnect <ifname>\n\n" + " delete <ifname>\n\n" + " wifi [list [ifname <ifname>] [bssid <BSSID>]]\n\n" + " wifi connect <(B)SSID> [password <password>] [wep-key-type key|phrase] [ifname <ifname>]\n" + " [bssid <BSSID>] [name <name>] [private yes|no]\n\n" + " wifi rescan [[ifname] <ifname>]\n\n" +#if WITH_WIMAX + " wimax [list [ifname <ifname>] [nsp <name>]]\n\n" +#endif + )); +} + +static void +usage_device_status (void) +{ + g_printerr (_("Usage: nmcli device status { help }\n" + "\n" + "Show status for all devices.\n" + "By default, the following columns are shown:\n" + " DEVICE - interface name\n" + " TYPE - device type\n" + " STATE - device state\n" + " CONNECTION - connection activated on device (if any)\n" + "Displayed columns can be changed using '--fields' global option. 'status' is\n" + "the default command, which means 'nmcli device' calls 'nmcli device status'.\n\n")); +} + +static void +usage_device_show (void) +{ + g_printerr (_("Usage: nmcli device show { ARGUMENTS | help }\n" + "\n" + "ARGUMENTS := [<ifname>]\n" + "\n" + "Show details of device(s).\n" + "The command lists details for all devices, or for a given device.\n\n")); +} + +static void +usage_device_connect (void) +{ + g_printerr (_("Usage: nmcli device connect { ARGUMENTS | help }\n" + "\n" + "ARGUMENTS := <ifname>\n" + "\n" + "Connect the device.\n" + "NetworkManager will try to find a suitable connection that will be activated.\n" + "It will also consider connections that are not set to auto-connect.\n\n")); +} + +static void +usage_device_disconnect (void) +{ + g_printerr (_("Usage: nmcli device disconnect { ARGUMENTS | help }\n" + "\n" + "ARGUMENTS := <ifname>\n" + "\n" + "Disconnect the device.\n" + "The command disconnects the device and prevents it from auto-activating\n" + "further connections without user/manual intervention.\n\n")); +} + +static void +usage_device_delete (void) +{ + g_printerr (_("Usage: nmcli device delete { ARGUMENTS | help }\n" + "\n" + "ARGUMENTS := <ifname>\n" + "\n" + "Deletes the software device.\n" + "The command removes the interface. It only works for software devices\n" + "(like bonds, bridges, etc.). Hardware devices cannot be deleted by the\n" + "command.\n\n")); +} + +static void +usage_device_wifi (void) +{ + g_printerr (_("Usage: nmcli device wifi { ARGUMENTS | help }\n" + "\n" + "Perform operation on Wi-Fi devices.\n" + "\n" + "ARGUMENTS := [list [ifname <ifname>] [bssid <BSSID>]]\n" + "\n" + "List available Wi-Fi access points. The 'ifname' and 'bssid' options can be\n" + "used to list APs for a particular interface, or with a specific BSSID.\n" + "\n" + "ARGUMENTS := connect <(B)SSID> [password <password>] [wep-key-type key|phrase] [ifname <ifname>]\n" + " [bssid <BSSID>] [name <name>] [private yes|no]\n" + "\n" + "Connect to a Wi-Fi network specified by SSID or BSSID. The command creates\n" + "a new connection and then activates it on a device. This is a command-line\n" + "counterpart of clicking an SSID in a GUI client. The command always creates\n" + "a new connection and thus it is mainly useful for connecting to new Wi-Fi\n" + "networks. If a connection for the network already exists, it is better to\n" + "bring up the existing profile as follows: nmcli con up id <name>. Note that\n" + "only open, WEP and WPA-PSK networks are supported at the moment. It is also\n" + "assumed that IP configuration is obtained via DHCP.\n" + "\n" + "ARGUMENTS := rescan [[ifname] <ifname>]\n" + "\n" + "Request that NetworkManager immediately re-scan for available access points.\n" + "NetworkManager scans Wi-Fi networks periodically, but in some cases it might\n" + "be useful to start scanning manually. Note that this command does not show\n" + "the APs, use 'nmcli device wifi list' for that.\n\n")); +} + +#if WITH_WIMAX +static void +usage_device_wimax (void) +{ + g_printerr (_("Usage: nmcli device wimax { ARGUMENTS | help }\n" + "\n" + "Perform operation on WiMAX devices.\n" + "\n" + "ARGUMENTS := [list [ifname <ifname>] [nsp <name>]]\n" + "\n" + "List available WiMAX NSPs. The 'ifname' and 'nsp' options can be used to\n" + "list networks for a particular interface, or with a specific NSP.\n\n")); +} +#endif + +/* quit main loop */ +static void +quit (void) +{ + if (progress_id) { + g_source_remove (progress_id); + nmc_terminal_erase_line (); + } + + g_main_loop_quit (loop); /* quit main loop */ +} + +static int +compare_devices (const void *a, const void *b) +{ + NMDevice *da = *(NMDevice **)a; + NMDevice *db = *(NMDevice **)b; + int cmp; + + /* Sort by later device states first */ + cmp = nm_device_get_state (db) - nm_device_get_state (da); + if (cmp != 0) + return cmp; + + cmp = g_strcmp0 (nm_device_get_type_description (da), + nm_device_get_type_description (db)); + if (cmp != 0) + return cmp; + + return g_strcmp0 (nm_device_get_iface (da), + nm_device_get_iface (db)); +} + +static NMDevice ** +get_devices_sorted (NMClient *client) +{ + const GPtrArray *devs; + NMDevice **sorted; + + devs = nm_client_get_devices (client); + + sorted = g_new (NMDevice *, devs->len + 1); + memcpy (sorted, devs->pdata, devs->len * sizeof (NMDevice *)); + sorted[devs->len] = NULL; + + qsort (sorted, devs->len, sizeof (NMDevice *), compare_devices); + return sorted; +} + +static char * +ap_wpa_rsn_flags_to_string (NM80211ApSecurityFlags flags) +{ + char *flags_str[16]; /* Enough space for flags and terminating NULL */ + char *ret_str; + int i = 0; + + if (flags & NM_802_11_AP_SEC_PAIR_WEP40) + flags_str[i++] = g_strdup ("pair_wpe40"); + if (flags & NM_802_11_AP_SEC_PAIR_WEP104) + flags_str[i++] = g_strdup ("pair_wpe104"); + if (flags & NM_802_11_AP_SEC_PAIR_TKIP) + flags_str[i++] = g_strdup ("pair_tkip"); + if (flags & NM_802_11_AP_SEC_PAIR_CCMP) + flags_str[i++] = g_strdup ("pair_ccmp"); + if (flags & NM_802_11_AP_SEC_GROUP_WEP40) + flags_str[i++] = g_strdup ("group_wpe40"); + if (flags & NM_802_11_AP_SEC_GROUP_WEP104) + flags_str[i++] = g_strdup ("group_wpe104"); + if (flags & NM_802_11_AP_SEC_GROUP_TKIP) + flags_str[i++] = g_strdup ("group_tkip"); + if (flags & NM_802_11_AP_SEC_GROUP_CCMP) + flags_str[i++] = g_strdup ("group_ccmp"); + if (flags & NM_802_11_AP_SEC_KEY_MGMT_PSK) + flags_str[i++] = g_strdup ("psk"); + if (flags & NM_802_11_AP_SEC_KEY_MGMT_802_1X) + flags_str[i++] = g_strdup ("802.1X"); + + if (i == 0) + flags_str[i++] = g_strdup (_("(none)")); + + flags_str[i] = NULL; + + ret_str = g_strjoinv (" ", flags_str); + + i = 0; + while (flags_str[i]) + g_free (flags_str[i++]); + + return ret_str; +} + +typedef struct { + NmCli *nmc; + int index; + guint32 output_flags; + const char* active_bssid; + const char* device; +} APInfo; + +static void +fill_output_access_point (gpointer data, gpointer user_data) +{ + NMAccessPoint *ap = NM_ACCESS_POINT (data); + APInfo *info = (APInfo *) user_data; + NmcOutputField *arr; + gboolean active = FALSE; + NM80211ApFlags flags; + NM80211ApSecurityFlags wpa_flags, rsn_flags; + guint32 freq, bitrate; + guint8 strength; + GBytes *ssid; + const char *bssid; + NM80211Mode mode; + char *channel_str, *freq_str, *ssid_str = NULL, *ssid_hex_str = NULL, + *bitrate_str, *strength_str, *wpa_flags_str, *rsn_flags_str; + GString *security_str; + char *ap_name; + const char *sig_bars; + + if (info->active_bssid) { + const char *current_bssid = nm_access_point_get_bssid (ap); + if (current_bssid && !strcmp (current_bssid, info->active_bssid)) + active = TRUE; + } + + /* Get AP properties */ + flags = nm_access_point_get_flags (ap); + wpa_flags = nm_access_point_get_wpa_flags (ap); + rsn_flags = nm_access_point_get_rsn_flags (ap); + ssid = nm_access_point_get_ssid (ap); + bssid = nm_access_point_get_bssid (ap); + freq = nm_access_point_get_frequency (ap); + mode = nm_access_point_get_mode (ap); + bitrate = nm_access_point_get_max_bitrate (ap); + strength = MIN (nm_access_point_get_strength (ap), 100); + + /* Convert to strings */ + if (ssid) { + const guint8 *ssid_data; + gsize ssid_len; + + ssid_data = g_bytes_get_data (ssid, &ssid_len); + ssid_str = nm_utils_ssid_to_utf8 (ssid_data, ssid_len); + ssid_hex_str = ssid_to_hex ((const char *) ssid_data, ssid_len); + } + channel_str = g_strdup_printf ("%u", nm_utils_wifi_freq_to_channel (freq)); + freq_str = g_strdup_printf (_("%u MHz"), freq); + bitrate_str = g_strdup_printf (_("%u Mbit/s"), bitrate/1000); + strength_str = g_strdup_printf ("%u", strength); + wpa_flags_str = ap_wpa_rsn_flags_to_string (wpa_flags); + rsn_flags_str = ap_wpa_rsn_flags_to_string (rsn_flags); + sig_bars = nm_utils_wifi_strength_bars (strength); + + security_str = g_string_new (NULL); + + if ( (flags & NM_802_11_AP_FLAGS_PRIVACY) + && (wpa_flags == NM_802_11_AP_SEC_NONE) + && (rsn_flags == NM_802_11_AP_SEC_NONE)) { + g_string_append (security_str, _("WEP")); + g_string_append_c (security_str, ' '); + } + if (wpa_flags != NM_802_11_AP_SEC_NONE) { + g_string_append (security_str, _("WPA1")); + g_string_append_c (security_str, ' '); + } + if (rsn_flags != NM_802_11_AP_SEC_NONE) { + g_string_append (security_str, _("WPA2")); + g_string_append_c (security_str, ' '); + } + if ( (wpa_flags & NM_802_11_AP_SEC_KEY_MGMT_802_1X) + || (rsn_flags & NM_802_11_AP_SEC_KEY_MGMT_802_1X)) { + g_string_append (security_str, _("802.1X")); + g_string_append_c (security_str, ' '); + } + + if (security_str->len > 0) + g_string_truncate (security_str, security_str->len-1); /* Chop off last space */ + + arr = nmc_dup_fields_array (nmc_fields_dev_wifi_list, + sizeof (nmc_fields_dev_wifi_list), + info->output_flags); + + ap_name = g_strdup_printf ("AP[%d]", info->index++); /* AP */ + set_val_str (arr, 0, ap_name); + set_val_str (arr, 1, ssid_str); + set_val_str (arr, 2, ssid_hex_str); + set_val_strc (arr, 3, bssid); + set_val_strc (arr, 4, mode == NM_802_11_MODE_ADHOC ? _("Ad-Hoc") + : mode == NM_802_11_MODE_INFRA ? _("Infra") + : _("N/A")); + set_val_str (arr, 5, channel_str); + set_val_str (arr, 6, freq_str); + set_val_str (arr, 7, bitrate_str); + set_val_str (arr, 8, strength_str); + set_val_strc (arr, 9, sig_bars); + set_val_str (arr, 10, security_str->str); + set_val_str (arr, 11, wpa_flags_str); + set_val_str (arr, 12, rsn_flags_str); + set_val_strc (arr, 13, info->device); + set_val_strc (arr, 14, active ? _("yes") : _("no")); + set_val_strc (arr, 15, active ? "*" : " "); + set_val_strc (arr, 16, nm_object_get_path (NM_OBJECT (ap))); + + g_ptr_array_add (info->nmc->output_data, arr); + + g_string_free (security_str, FALSE); +} + +#if WITH_WIMAX +static void +fill_output_wimax_nsp (NMWimaxNsp *nsp, NmCli *nmc, NMDevice *dev, int idx, guint32 o_flags) +{ + NMDeviceWimax *wimax = NM_DEVICE_WIMAX (dev); + char *nsp_name, *quality_str; + const char *ntype; + gboolean active = FALSE; + NmcOutputField *arr; + + switch (nm_wimax_nsp_get_network_type (nsp)) { + case NM_WIMAX_NSP_NETWORK_TYPE_HOME: + ntype = _("Home"); + break; + case NM_WIMAX_NSP_NETWORK_TYPE_PARTNER: + ntype = _("Partner"); + break; + case NM_WIMAX_NSP_NETWORK_TYPE_ROAMING_PARTNER: + ntype = _("Roaming"); + break; + default: + ntype = _("Unknown"); + break; + } + + if (nm_device_get_state (dev) == NM_DEVICE_STATE_ACTIVATED) { + if (nsp == nm_device_wimax_get_active_nsp (wimax)) + active = TRUE; + } + + quality_str = g_strdup_printf ("%u", nm_wimax_nsp_get_signal_quality (nsp)); + nsp_name = g_strdup_printf ("NSP[%d]", idx); /* NSP */ + + arr = nmc_dup_fields_array (nmc_fields_dev_wimax_list, + sizeof (nmc_fields_dev_wimax_list), + o_flags); + set_val_str (arr, 0, nsp_name); + set_val_strc (arr, 1, nm_wimax_nsp_get_name (nsp)); + set_val_str (arr, 2, quality_str); + set_val_strc (arr, 3, ntype); + set_val_strc (arr, 4, nm_device_get_iface (dev)); + set_val_strc (arr, 5, active ? _("yes") : _("no")); + set_val_strc (arr, 6, nm_object_get_path (NM_OBJECT (nsp))); + + g_ptr_array_add (nmc->output_data, arr); +} +#endif + +static char * +bluetooth_caps_to_string (NMBluetoothCapabilities caps) +{ + char *caps_str[8]; /* Enough space for caps and terminating NULL */ + char *ret_str; + int i = 0; + + if (caps & NM_BT_CAPABILITY_DUN) + caps_str[i++] = g_strdup ("DUN"); + if (caps & NM_BT_CAPABILITY_NAP) + caps_str[i++] = g_strdup ("NAP"); + + if (i == 0) + caps_str[i++] = g_strdup (_("(none)")); + + caps_str[i] = NULL; + + ret_str = g_strjoinv (" ", caps_str); + + i = 0; + while (caps_str[i]) + g_free (caps_str[i++]); + + return ret_str; +} + +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 * +get_active_connection_id (NMDevice *device) +{ + NMActiveConnection *ac; + + ac = nm_device_get_active_connection (device); + if (!ac) + return NULL; + + return nm_active_connection_get_id (ac); +} + +static gboolean +print_bond_team_bridge_info (NMDevice *device, + NmCli *nmc, + const char *group_prefix, + const char *one_field) +{ + const GPtrArray *slaves = NULL; + GString *slaves_str; + int idx; + NmcOutputField *tmpl, *arr; + size_t tmpl_len; + + if (NM_IS_DEVICE_BOND (device)) + slaves = nm_device_bond_get_slaves (NM_DEVICE_BOND (device)); + else if (NM_IS_DEVICE_TEAM (device)) + slaves = nm_device_team_get_slaves (NM_DEVICE_TEAM (device)); + else if (NM_IS_DEVICE_BRIDGE (device)) + slaves = nm_device_bridge_get_slaves (NM_DEVICE_BRIDGE (device)); + + slaves_str = g_string_new (NULL); + for (idx = 0; slaves && idx < slaves->len; idx++) { + NMDevice *slave = g_ptr_array_index (slaves, idx); + const char *iface = nm_device_get_iface (slave); + + if (iface) { + g_string_append (slaves_str, iface); + g_string_append_c (slaves_str, ' '); + } + } + if (slaves_str->len > 0) + g_string_truncate (slaves_str, slaves_str->len-1); /* Chop off last space */ + + tmpl = nmc_fields_dev_show_master_prop; + tmpl_len = sizeof (nmc_fields_dev_show_master_prop); + nmc->print_fields.indices = parse_output_fields (one_field ? one_field : NMC_FIELDS_DEV_SHOW_MASTER_PROP_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); + + arr = nmc_dup_fields_array (tmpl, tmpl_len, NMC_OF_FLAG_SECTION_PREFIX); + set_val_strc (arr, 0, group_prefix); /* i.e. BOND, TEAM, BRIDGE */ + set_val_str (arr, 1, slaves_str->str); + g_ptr_array_add (nmc->output_data, arr); + + print_data (nmc); /* Print all data */ + + g_string_free (slaves_str, FALSE); + nmc_empty_output_fields (nmc); + + return TRUE; +} + +static gboolean +show_device_info (NMDevice *device, NmCli *nmc) +{ + GError *error = NULL; + APInfo *info; + const char *hwaddr = NULL; + NMDeviceState state = NM_DEVICE_STATE_UNKNOWN; + NMDeviceStateReason reason = NM_DEVICE_STATE_REASON_NONE; + NMDeviceCapabilities caps; + NMActiveConnection *acon; + guint32 speed; + char *speed_str, *state_str, *reason_str, *mtu_str; + GArray *sections_array; + int k; + char *fields_str; + char *fields_all = NMC_FIELDS_DEV_SHOW_SECTIONS_ALL; + char *fields_common = NMC_FIELDS_DEV_SHOW_SECTIONS_COMMON; + NmcOutputField *tmpl, *arr; + size_t tmpl_len; + gboolean was_output = FALSE; + NMIPConfig *cfg4, *cfg6; + NMDhcpConfig *dhcp4, *dhcp6; + const char *base_hdr = _("Device details"); + GPtrArray *fields_in_section = NULL; + + 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; + + sections_array = parse_output_fields (fields_str, nmc_fields_dev_show_sections, TRUE, &fields_in_section, &error); + if (error) { + g_string_printf (nmc->return_text, _("Error: 'device show': %s"), error->message); + g_error_free (error); + nmc->return_value = NMC_RESULT_ERROR_USER_INPUT; + return FALSE; + } + + /* Main header */ + nmc->print_fields.header_name = (char *) construct_header_name (base_hdr, nm_device_get_iface (device)); + nmc->print_fields.indices = parse_output_fields (NMC_FIELDS_DEV_SHOW_GENERAL_ALL, + nmc_fields_dev_show_general, FALSE, NULL, NULL); + + nmc_fields_dev_show_general[0].flags = NMC_OF_FLAG_MAIN_HEADER_ONLY; + print_required_fields (nmc, nmc_fields_dev_show_general); + + /* Loop through the required sections and print them. */ + for (k = 0; k < sections_array->len; k++) { + int section_idx = g_array_index (sections_array, int, k); + char *section_fld = (char *) g_ptr_array_index (fields_in_section, k); + + if (nmc->print_output != NMC_PRINT_TERSE && !nmc->multiline_output && was_output) + g_print ("\n"); /* Print empty line between groups in tabular mode */ + + was_output = FALSE; + + /* Remove any previous data */ + nmc_empty_output_fields (nmc); + + state = nm_device_get_state (device); + reason = nm_device_get_state_reason (device); + + /* section GENERAL */ + if (!strcasecmp (nmc_fields_dev_show_sections[section_idx].name, nmc_fields_dev_show_sections[0].name)) { + tmpl = nmc_fields_dev_show_general; + tmpl_len = sizeof (nmc_fields_dev_show_general); + nmc->print_fields.indices = parse_output_fields (section_fld ? section_fld : NMC_FIELDS_DEV_SHOW_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); + + state_str = g_strdup_printf ("%d (%s)", state, nmc_device_state_to_string (state)); + reason_str = g_strdup_printf ("%d (%s)", reason, nmc_device_reason_to_string (reason)); + hwaddr = nm_device_get_hw_address (device); + mtu_str = g_strdup_printf ("%u", nm_device_get_mtu (device)); + acon = nm_device_get_active_connection (device); + + arr = nmc_dup_fields_array (tmpl, tmpl_len, NMC_OF_FLAG_SECTION_PREFIX); + set_val_strc (arr, 0, nmc_fields_dev_show_sections[0].name); /* "GENERAL"*/ + set_val_strc (arr, 1, nm_device_get_iface (device)); + set_val_strc (arr, 2, nm_device_get_type_description (device)); + set_val_strc (arr, 3, nm_device_get_vendor (device)); + set_val_strc (arr, 4, nm_device_get_product (device)); + set_val_strc (arr, 5, nm_device_get_driver (device) ? nm_device_get_driver (device) : _("(unknown)")); + set_val_strc (arr, 6, nm_device_get_driver_version (device)); + set_val_strc (arr, 7, nm_device_get_firmware_version (device)); + set_val_strc (arr, 8, hwaddr ? hwaddr : _("(unknown)")); + set_val_str (arr, 9, mtu_str); + set_val_str (arr, 10, state_str); + set_val_str (arr, 11, reason_str); + set_val_strc (arr, 12, nm_device_get_udi (device)); + set_val_strc (arr, 13, nm_device_get_ip_iface (device)); + set_val_strc (arr, 14, nm_device_is_software (device) ? _("yes") : _("no")); + set_val_strc (arr, 15, nm_device_get_managed (device) ? _("yes") : _("no")); + set_val_strc (arr, 16, nm_device_get_autoconnect (device) ? _("yes") : _("no")); + set_val_strc (arr, 17, nm_device_get_firmware_missing (device) ? _("yes") : _("no")); + set_val_strc (arr, 18, get_active_connection_id (device)); + set_val_strc (arr, 19, acon ? nm_active_connection_get_uuid (acon) : NULL); + set_val_strc (arr, 20, acon ? nm_object_get_path (NM_OBJECT (acon)) : NULL); + g_ptr_array_add (nmc->output_data, arr); + + print_data (nmc); /* Print all data */ + was_output = TRUE; + } + + /* section CAPABILITIES */ + if (!strcasecmp (nmc_fields_dev_show_sections[section_idx].name, nmc_fields_dev_show_sections[1].name)) { + tmpl = nmc_fields_dev_show_cap; + tmpl_len = sizeof (nmc_fields_dev_show_cap); + nmc->print_fields.indices = parse_output_fields (section_fld ? section_fld : NMC_FIELDS_DEV_SHOW_CAP_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); + + caps = nm_device_get_capabilities (device); + speed = 0; + if (NM_IS_DEVICE_ETHERNET (device)) { + /* Speed in Mb/s */ + speed = nm_device_ethernet_get_speed (NM_DEVICE_ETHERNET (device)); + } else if (NM_IS_DEVICE_WIFI (device)) { + /* Speed in b/s */ + speed = nm_device_wifi_get_bitrate (NM_DEVICE_WIFI (device)); + speed /= 1000; + } + speed_str = speed ? g_strdup_printf (_("%u Mb/s"), speed) : g_strdup (_("unknown")); + + arr = nmc_dup_fields_array (tmpl, tmpl_len, NMC_OF_FLAG_SECTION_PREFIX); + set_val_strc (arr, 0, nmc_fields_dev_show_sections[1].name); /* "CAPABILITIES" */ + set_val_strc (arr, 1, (caps & NM_DEVICE_CAP_CARRIER_DETECT) ? _("yes") : _("no")); + set_val_str (arr, 2, speed_str); + set_val_strc (arr, 3, (caps & NM_DEVICE_CAP_IS_SOFTWARE) ? _("yes") : _("no")); + g_ptr_array_add (nmc->output_data, arr); + + print_data (nmc); /* Print all data */ + was_output = TRUE; + } + + /* Wireless specific information */ + if ((NM_IS_DEVICE_WIFI (device))) { + NMDeviceWifiCapabilities wcaps; + NMAccessPoint *active_ap = NULL; + const char *active_bssid = NULL; + const GPtrArray *aps; + + /* section WIFI-PROPERTIES */ + if (!strcasecmp (nmc_fields_dev_show_sections[section_idx].name, nmc_fields_dev_show_sections[2].name)) { + wcaps = nm_device_wifi_get_capabilities (NM_DEVICE_WIFI (device)); + + tmpl = nmc_fields_dev_show_wifi_prop; + tmpl_len = sizeof (nmc_fields_dev_show_wifi_prop); + nmc->print_fields.indices = parse_output_fields (section_fld ? section_fld : NMC_FIELDS_DEV_SHOW_WIFI_PROP_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); + + arr = nmc_dup_fields_array (tmpl, tmpl_len, NMC_OF_FLAG_SECTION_PREFIX); + set_val_strc (arr, 0, nmc_fields_dev_show_sections[2].name); /* "WIFI-PROPERTIES" */ + set_val_strc (arr, 1, (wcaps & (NM_WIFI_DEVICE_CAP_CIPHER_WEP40 | NM_WIFI_DEVICE_CAP_CIPHER_WEP104)) ? + _("yes") : _("no")); + set_val_strc (arr, 2, (wcaps & NM_WIFI_DEVICE_CAP_WPA) ? _("yes") : _("no")); + set_val_strc (arr, 3, (wcaps & NM_WIFI_DEVICE_CAP_RSN) ? _("yes") : _("no")); + set_val_strc (arr, 4, (wcaps & NM_WIFI_DEVICE_CAP_CIPHER_TKIP) ? _("yes") : _("no")); + set_val_strc (arr, 5, (wcaps & NM_WIFI_DEVICE_CAP_CIPHER_CCMP) ? _("yes") : _("no")); + set_val_strc (arr, 6, (wcaps & NM_WIFI_DEVICE_CAP_AP) ? _("yes") : _("no")); + set_val_strc (arr, 7, (wcaps & NM_WIFI_DEVICE_CAP_ADHOC) ? _("yes") : _("no")); + g_ptr_array_add (nmc->output_data, arr); + + print_data (nmc); /* Print all data */ + was_output = TRUE; + } + + /* section AP */ + if (!strcasecmp (nmc_fields_dev_show_sections[section_idx].name, nmc_fields_dev_show_sections[3].name)) { + if (state == NM_DEVICE_STATE_ACTIVATED) { + active_ap = nm_device_wifi_get_active_access_point (NM_DEVICE_WIFI (device)); + active_bssid = active_ap ? nm_access_point_get_bssid (active_ap) : NULL; + } + + tmpl = nmc_fields_dev_wifi_list; + tmpl_len = sizeof (nmc_fields_dev_wifi_list); + nmc->print_fields.indices = parse_output_fields (section_fld ? section_fld : NMC_FIELDS_DEV_WIFI_LIST_FOR_DEV_LIST, + 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); + + info = g_malloc0 (sizeof (APInfo)); + info->nmc = nmc; + info->index = 1; + info->output_flags = NMC_OF_FLAG_SECTION_PREFIX; + info->active_bssid = active_bssid; + info->device = nm_device_get_iface (device); + aps = nm_device_wifi_get_access_points (NM_DEVICE_WIFI (device)); + g_ptr_array_foreach ((GPtrArray *) aps, fill_output_access_point, (gpointer) info); + g_free (info); + print_data (nmc); /* Print all data */ + was_output = TRUE; + } + } else if (NM_IS_DEVICE_ETHERNET (device)) { + /* WIRED-PROPERTIES */ + if (!strcasecmp (nmc_fields_dev_show_sections[section_idx].name, nmc_fields_dev_show_sections[4].name)) { + tmpl = nmc_fields_dev_show_wired_prop; + tmpl_len = sizeof (nmc_fields_dev_show_wired_prop); + nmc->print_fields.indices = parse_output_fields (section_fld ? section_fld : NMC_FIELDS_DEV_SHOW_WIRED_PROP_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); + + arr = nmc_dup_fields_array (tmpl, tmpl_len, NMC_OF_FLAG_SECTION_PREFIX); + set_val_strc (arr, 0, nmc_fields_dev_show_sections[4].name); /* "WIRED-PROPERTIES" */ + set_val_strc (arr, 1, (nm_device_ethernet_get_carrier (NM_DEVICE_ETHERNET (device))) ? + _("on") : _("off")); + g_ptr_array_add (nmc->output_data, arr); + + print_data (nmc); /* Print all data */ + was_output = TRUE; + } + } +#if WITH_WIMAX + else if (NM_IS_DEVICE_WIMAX (device)) { + /* WIMAX-PROPERTIES */ + if (!strcasecmp (nmc_fields_dev_show_sections[section_idx].name, nmc_fields_dev_show_sections[5].name)) { + char *cfreq = NULL, *rssi = NULL, *cinr = NULL, *txpow = NULL; + guint tmp_uint; + gint tmp_int; + + /* Field names */ + tmpl = nmc_fields_dev_show_wimax_prop; + tmpl_len = sizeof (nmc_fields_dev_show_wimax_prop); + nmc->print_fields.indices = parse_output_fields (section_fld ? section_fld : NMC_FIELDS_DEV_SHOW_WIMAX_PROP_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); + + /* Center frequency */ + tmp_uint = nm_device_wimax_get_center_frequency (NM_DEVICE_WIMAX (device)); + if (tmp_uint) + cfreq = g_strdup_printf ("%'.1f MHz", (double) tmp_uint / 1000.0); + + /* RSSI */ + tmp_int = nm_device_wimax_get_rssi (NM_DEVICE_WIMAX (device)); + if (tmp_int) + rssi = g_strdup_printf ("%d dBm", tmp_int); + + /* CINR */ + tmp_int = nm_device_wimax_get_cinr (NM_DEVICE_WIMAX (device)); + if (tmp_int) + cinr = g_strdup_printf ("%d dB", tmp_int); + + /* TX Power */ + tmp_int = nm_device_wimax_get_tx_power (NM_DEVICE_WIMAX (device)); + if (tmp_int) + txpow = g_strdup_printf ("%'.2f dBm", (float) tmp_int / 2.0); + + arr = nmc_dup_fields_array (tmpl, tmpl_len, NMC_OF_FLAG_SECTION_PREFIX); + set_val_strc (arr, 0, nmc_fields_dev_show_sections[5].name); /* "WIMAX-PROPERTIES" */ + set_val_str (arr, 1, cfreq); + set_val_str (arr, 2, rssi); + set_val_str (arr, 3, cinr); + set_val_str (arr, 4, txpow); + set_val_strc (arr, 5, nm_device_wimax_get_bsid (NM_DEVICE_WIMAX (device))); + g_ptr_array_add (nmc->output_data, arr); + + print_data (nmc); /* Print all data */ + was_output = TRUE; + } + + /* section NSP */ + if (!strcasecmp (nmc_fields_dev_show_sections[section_idx].name, nmc_fields_dev_show_sections[6].name)) { + const GPtrArray *nsps; + int g, idx = 1; + + tmpl = nmc_fields_dev_wimax_list; + tmpl_len = sizeof (nmc_fields_dev_wimax_list); + nmc->print_fields.indices = parse_output_fields (section_fld ? section_fld : NMC_FIELDS_DEV_WIMAX_LIST_FOR_DEV_LIST, + 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); + + nsps = nm_device_wimax_get_nsps (NM_DEVICE_WIMAX (device)); + for (g = 0; g < nsps->len; g++) { + NMWimaxNsp *nsp = g_ptr_array_index (nsps, g); + + fill_output_wimax_nsp (nsp, nmc, device, idx++, NMC_OF_FLAG_SECTION_PREFIX); + } + print_data (nmc); /* Print all data */ + was_output = TRUE; + } + } +#endif + + /* IP configuration info */ + cfg4 = nm_device_get_ip4_config (device); + cfg6 = nm_device_get_ip6_config (device); + dhcp4 = nm_device_get_dhcp4_config (device); + dhcp6 = nm_device_get_dhcp6_config (device); + + /* IP4 */ + if (cfg4 && !strcasecmp (nmc_fields_dev_show_sections[section_idx].name, nmc_fields_dev_show_sections[7].name)) + was_output = print_ip4_config (cfg4, nmc, nmc_fields_dev_show_sections[7].name, section_fld); + + /* DHCP4 */ + if (dhcp4 && !strcasecmp (nmc_fields_dev_show_sections[section_idx].name, nmc_fields_dev_show_sections[8].name)) + was_output = print_dhcp4_config (dhcp4, nmc, nmc_fields_dev_show_sections[8].name, section_fld); + + /* IP6 */ + if (cfg6 && !strcasecmp (nmc_fields_dev_show_sections[section_idx].name, nmc_fields_dev_show_sections[9].name)) + was_output = print_ip6_config (cfg6, nmc, nmc_fields_dev_show_sections[9].name, section_fld); + + /* DHCP6 */ + if (dhcp6 && !strcasecmp (nmc_fields_dev_show_sections[section_idx].name, nmc_fields_dev_show_sections[10].name)) + was_output = print_dhcp6_config (dhcp6, nmc, nmc_fields_dev_show_sections[10].name, section_fld); + + /* Bond specific information */ + if (NM_IS_DEVICE_BOND (device)) { + if (!strcasecmp (nmc_fields_dev_show_sections[section_idx].name, nmc_fields_dev_show_sections[11].name)) + was_output = print_bond_team_bridge_info (device, nmc, nmc_fields_dev_show_sections[11].name, section_fld); + } + + /* Team specific information */ + if (NM_IS_DEVICE_TEAM (device)) { + if (!strcasecmp (nmc_fields_dev_show_sections[section_idx].name, nmc_fields_dev_show_sections[12].name)) + was_output = print_bond_team_bridge_info (device, nmc, nmc_fields_dev_show_sections[12].name, section_fld); + } + + /* Bridge specific information */ + if (NM_IS_DEVICE_BRIDGE (device)) { + if (!strcasecmp (nmc_fields_dev_show_sections[section_idx].name, nmc_fields_dev_show_sections[13].name)) + was_output = print_bond_team_bridge_info (device, nmc, nmc_fields_dev_show_sections[13].name, section_fld); + } + + /* VLAN-specific information */ + if ((NM_IS_DEVICE_VLAN (device))) { + if (!strcasecmp (nmc_fields_dev_show_sections[section_idx].name, nmc_fields_dev_show_sections[14].name)) { + char * vlan_id_str = g_strdup_printf ("%u", nm_device_vlan_get_vlan_id (NM_DEVICE_VLAN (device))); + NMDevice *parent = nm_device_vlan_get_parent (NM_DEVICE_VLAN (device)); + + tmpl = nmc_fields_dev_show_vlan_prop; + tmpl_len = sizeof (nmc_fields_dev_show_vlan_prop); + nmc->print_fields.indices = parse_output_fields (section_fld ? section_fld : NMC_FIELDS_DEV_SHOW_VLAN_PROP_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); + + arr = nmc_dup_fields_array (tmpl, tmpl_len, NMC_OF_FLAG_SECTION_PREFIX); + set_val_strc (arr, 0, nmc_fields_dev_show_sections[14].name); /* "VLAN" */ + set_val_strc (arr, 1, parent ? nm_device_get_iface (parent) : NULL); + set_val_str (arr, 2, vlan_id_str); + g_ptr_array_add (nmc->output_data, arr); + + print_data (nmc); /* Print all data */ + + was_output = TRUE; + } + } + + if (NM_IS_DEVICE_BT (device)) { + if (!strcasecmp (nmc_fields_dev_show_sections[section_idx].name, nmc_fields_dev_show_sections[15].name)) { + tmpl = nmc_fields_dev_show_bluetooth; + tmpl_len = sizeof (nmc_fields_dev_show_bluetooth); + nmc->print_fields.indices = parse_output_fields (section_fld ? section_fld : NMC_FIELDS_DEV_SHOW_BLUETOOTH_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); + + arr = nmc_dup_fields_array (tmpl, tmpl_len, NMC_OF_FLAG_SECTION_PREFIX); + set_val_strc (arr, 0, nmc_fields_dev_show_sections[15].name); /* "BLUETOOTH" */ + set_val_str (arr, 1, bluetooth_caps_to_string (nm_device_bt_get_capabilities (NM_DEVICE_BT (device)))); + g_ptr_array_add (nmc->output_data, arr); + + print_data (nmc); /* Print all data */ + was_output = TRUE; + } + } + + /* section CONNECTIONS */ + if (!strcasecmp (nmc_fields_dev_show_sections[section_idx].name, nmc_fields_dev_show_sections[16].name)) { + const GPtrArray *avail_cons; + GString *ac_paths_str; + char **ac_arr = NULL; + int i; + + tmpl = nmc_fields_dev_show_connections; + tmpl_len = sizeof (nmc_fields_dev_show_connections); + nmc->print_fields.indices = parse_output_fields (section_fld ? section_fld : NMC_FIELDS_DEV_SHOW_CONNECTIONS_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); + + /* available-connections */ + avail_cons = nm_device_get_available_connections (device); + ac_paths_str = g_string_new (NULL); + if (avail_cons->len) { + ac_arr = g_new (char *, avail_cons->len + 1); + ac_arr[avail_cons->len] = NULL; + } + for (i = 0; i < avail_cons->len; i++) { + NMRemoteConnection *avail_con = g_ptr_array_index (avail_cons, i); + const char *ac_path = nm_connection_get_path (NM_CONNECTION (avail_con)); + const char *ac_id = nm_connection_get_id (NM_CONNECTION (avail_con)); + const char *ac_uuid = nm_connection_get_uuid (NM_CONNECTION (avail_con)); + + ac_arr[i] = g_strdup_printf ("%s | %s", ac_uuid, ac_id); + + if (i == 0) + g_string_printf (ac_paths_str, "%s/{", NM_DBUS_PATH_SETTINGS); + else + g_string_append_c (ac_paths_str, ','); + g_string_append (ac_paths_str, strrchr (ac_path, '/') + 1); + } + if (ac_paths_str->len > 0) + g_string_append_c (ac_paths_str, '}'); + + arr = nmc_dup_fields_array (tmpl, tmpl_len, NMC_OF_FLAG_SECTION_PREFIX); + set_val_strc (arr, 0, nmc_fields_dev_show_sections[16].name); /* "CONNECTIONS" */ + set_val_str (arr, 1, ac_paths_str->str); + set_val_arr (arr, 2, (ac_arr)); + g_ptr_array_add (nmc->output_data, arr); + + print_data (nmc); /* Print all data */ + + g_string_free (ac_paths_str, FALSE); + was_output = TRUE; + } + } + + if (sections_array) + g_array_free (sections_array, TRUE); + if (fields_in_section) + g_ptr_array_free (fields_in_section, TRUE); + + return TRUE; +} + +static void +fill_output_device_status (NMDevice *device, NmCli *nmc) +{ + NMActiveConnection *ac; + NmcOutputField *arr = nmc_dup_fields_array (nmc_fields_dev_status, + sizeof (nmc_fields_dev_status), + 0); + + ac = nm_device_get_active_connection (device); + + set_val_strc (arr, 0, nm_device_get_iface (device)); + set_val_strc (arr, 1, nm_device_get_type_description (device)); + set_val_strc (arr, 2, nmc_device_state_to_string (nm_device_get_state (device))); + set_val_strc (arr, 3, nm_object_get_path (NM_OBJECT (device))); + set_val_strc (arr, 4, get_active_connection_id (device)); + set_val_strc (arr, 5, ac ? nm_active_connection_get_uuid (ac) : NULL); + set_val_strc (arr, 6, ac ? nm_object_get_path (NM_OBJECT (ac)) : NULL); + + g_ptr_array_add (nmc->output_data, arr); +} + +static NMCResultCode +do_devices_status (NmCli *nmc, int argc, char **argv) +{ + GError *error = NULL; + NMDevice **devices; + int i; + char *fields_str; + char *fields_all = NMC_FIELDS_DEV_STATUS_ALL; + char *fields_common = NMC_FIELDS_DEV_STATUS_COMMON; + NmcOutputField *tmpl, *arr; + size_t tmpl_len; + + while (argc > 0) { + g_printerr (_("Unknown parameter: %s\n"), *argv); + argc--; + argv++; + } + + 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_dev_status; + tmpl_len = sizeof (nmc_fields_dev_status); + nmc->print_fields.indices = parse_output_fields (fields_str, tmpl, FALSE, NULL, &error); + + if (error) { + g_string_printf (nmc->return_text, _("Error: 'device status': %s"), error->message); + g_error_free (error); + nmc->return_value = NMC_RESULT_ERROR_USER_INPUT; + goto error; + } + + /* Add headers */ + nmc->print_fields.header_name = _("Status of devices"); + 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); + + devices = get_devices_sorted (nmc->client); + for (i = 0; devices[i]; i++) + fill_output_device_status (devices[i], nmc); + + /* Now print all data */ + print_data (nmc); + + g_free (devices); + + return NMC_RESULT_SUCCESS; + +error: + return nmc->return_value; +} + +static NMCResultCode +do_devices_show (NmCli *nmc, int argc, char **argv) +{ + NMDevice **devices = NULL; + NMDevice *device = NULL; + const char *ifname = NULL; + int i; + gboolean ret; + + if (argc == 1) + ifname = *argv; + else if (argc > 1) { + g_string_printf (nmc->return_text, _("Error: invalid extra argument '%s'."), *(argv+1)); + nmc->return_value = NMC_RESULT_ERROR_USER_INPUT; + goto error; + } + + devices = get_devices_sorted (nmc->client); + + if (ifname) { + /* Interface specified; show details only for the device */ + for (i = 0; devices[i]; i++) { + NMDevice *candidate = devices[i]; + const char *dev_iface = nm_device_get_iface (candidate); + + if (!g_strcmp0 (dev_iface, ifname)) + device = candidate; + } + if (!device) { + g_string_printf (nmc->return_text, _("Error: Device '%s' not found."), ifname); + nmc->return_value = NMC_RESULT_ERROR_NOT_FOUND; + goto error; + } + show_device_info (device, nmc); + } else { + /* Show details for all devices */ + for (i = 0; devices[i]; i++) { + nmc_empty_output_fields (nmc); + ret = show_device_info (devices[i], nmc); + if (!ret) + break; + if (devices[i + 1]) + g_print ("\n"); /* Empty line */ + } + } + +error: + g_free (devices); + return nmc->return_value; +} + +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) +{ + NMDevice *device = (NMDevice *) user_data; + + nmc_terminal_show_progress (device ? nmc_device_state_to_string (nm_device_get_state (device)) : ""); + + return TRUE; +} + +static void connected_state_cb (NMDevice *device, NMActiveConnection *active); + +static void +device_state_cb (NMDevice *device, GParamSpec *pspec, gpointer user_data) +{ + NMActiveConnection *active = (NMActiveConnection *) user_data; + + connected_state_cb (device, active); +} + +static void +active_state_cb (NMActiveConnection *active, GParamSpec *pspec, gpointer user_data) +{ + NMDevice *device = (NMDevice *) user_data; + + connected_state_cb (device, active); +} + +static void +connected_state_cb (NMDevice *device, NMActiveConnection *active) +{ + NMDeviceState state; + NMDeviceStateReason reason; + NMActiveConnectionState ac_state; + + state = nm_device_get_state (device); + ac_state = nm_active_connection_get_state (active); + + if (ac_state == NM_ACTIVE_CONNECTION_STATE_ACTIVATING) + return; + + if (state == NM_DEVICE_STATE_ACTIVATED) { + nmc_terminal_erase_line (); + g_print (_("Device '%s' successfully activated with '%s'.\n"), + nm_device_get_iface (device), + nm_active_connection_get_uuid (active)); + } else if ( state <= NM_DEVICE_STATE_DISCONNECTED + || state >= NM_DEVICE_STATE_DEACTIVATING) { + reason = nm_device_get_state_reason (device); + g_print (_("Error: Connection activation failed: (%d) %s.\n"), + reason, nmc_device_reason_to_string (reason)); + } else + return; + + g_signal_handlers_disconnect_by_func (active, G_CALLBACK (active_state_cb), device); + g_signal_handlers_disconnect_by_func (device, G_CALLBACK (device_state_cb), active); + + g_object_unref (active); + g_object_unref (device); + + quit (); +} + +typedef struct { + NmCli *nmc; + NMDevice *device; +} AddAndActivateInfo; + +static void +add_and_activate_cb (GObject *client, + GAsyncResult *result, + gpointer user_data) +{ + AddAndActivateInfo *info = (AddAndActivateInfo *) user_data; + NmCli *nmc = info->nmc; + NMDevice *device = info->device; + NMActiveConnectionState state; + NMActiveConnection *active; + GError *error = NULL; + + active = nm_client_add_and_activate_connection_finish (NM_CLIENT (client), result, &error); + + if (error) { + g_string_printf (nmc->return_text, _("Error: Failed to add/activate new connection: %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 (state == NM_ACTIVE_CONNECTION_STATE_UNKNOWN) { + g_string_printf (nmc->return_text, _("Error: Failed to add/activate new connection: Unknown error")); + nmc->return_value = NMC_RESULT_ERROR_CON_ACTIVATION; + g_object_unref (active); + quit (); + } + + 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 with UUID '%s' created and activated on device '%s'\n"), + nm_active_connection_get_uuid (active), nm_device_get_iface (device)); + } + g_object_unref (active); + quit (); + } else { + g_object_ref (device); + g_signal_connect (device, "notify::state", G_CALLBACK (device_state_cb), active); + g_signal_connect (active, "notify::state", G_CALLBACK (active_state_cb), device); + + g_timeout_add_seconds (nmc->timeout, timeout_cb, nmc); /* Exit if timeout expires */ + + if (nmc->print_output == NMC_PRINT_PRETTY) + progress_id = g_timeout_add (120, progress_cb, device); + } + } + + g_free (info); +} + +static void +create_connect_connection_for_device (AddAndActivateInfo *info) +{ + NMConnection *connection; + NMSettingConnection *s_con; + + /* Create new connection and tie it to the device */ + connection = nm_simple_connection_new (); + s_con = (NMSettingConnection *) nm_setting_connection_new (); + nm_connection_add_setting (connection, NM_SETTING (s_con)); + g_object_set (s_con, + NM_SETTING_CONNECTION_ID, nm_device_get_iface (info->device), + NM_SETTING_CONNECTION_INTERFACE_NAME, nm_device_get_iface (info->device), + NULL); + + nm_client_add_and_activate_connection_async (info->nmc->client, + connection, + info->device, + NULL, + NULL, + add_and_activate_cb, + info); +} + +static void +connect_device_cb (GObject *client, GAsyncResult *result, gpointer user_data) +{ + AddAndActivateInfo *info = (AddAndActivateInfo *) user_data; + NmCli *nmc = info->nmc; + NMActiveConnection *active; + GError *error = NULL; + const GPtrArray *devices; + NMDevice *device; + NMDeviceState state; + + active = nm_client_activate_connection_finish (NM_CLIENT (client), result, &error); + + if (error) { + /* If no connection existed for the device, create one and activate it */ + if (g_error_matches (error, NM_MANAGER_ERROR, NM_MANAGER_ERROR_UNKNOWN_CONNECTION)) { + create_connect_connection_for_device (info); + return; + } + + g_string_printf (nmc->return_text, _("Error: Device activation failed: %s"), + error->message); + g_error_free (error); + nmc->return_value = NMC_RESULT_ERROR_CON_ACTIVATION; + quit (); + } else { + g_assert (active); + devices = nm_active_connection_get_devices (active); + if (devices->len == 0) { + g_string_printf (nmc->return_text, _("Error: Device activation failed: device was disconnected")); + nmc->return_value = NMC_RESULT_ERROR_CON_ACTIVATION; + g_object_unref (active); + quit (); + g_free (info); + return; + } + + device = g_ptr_array_index (devices, 0); + state = nm_device_get_state (device); + + if (nmc->nowait_flag || state == NM_DEVICE_STATE_ACTIVATED) { + /* Don't want to wait or device already activated */ + if (state == NM_DEVICE_STATE_ACTIVATED && nmc->print_output == NMC_PRINT_PRETTY) { + nmc_terminal_erase_line (); + g_print (_("Device '%s' has been connected.\n"), nm_device_get_iface (device)); + } + g_object_unref (active); + quit (); + } else { + 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))); + } + + g_object_ref (device); + g_signal_connect (device, "notify::state", G_CALLBACK (device_state_cb), active); + g_signal_connect (active, "notify::state", G_CALLBACK (active_state_cb), device); + /* Start timer not to loop forever if "notify::state" signal is not issued */ + g_timeout_add_seconds (nmc->timeout, timeout_cb, nmc); + } + } + g_free (info); +} + +static NMCResultCode +do_device_connect (NmCli *nmc, int argc, char **argv) +{ + NMDevice **devices; + NMDevice *device = NULL; + const char *ifname = NULL; + char *ifname_ask = NULL; + int i; + AddAndActivateInfo *info; + + /* Set default timeout for connect operation. */ + if (nmc->timeout == -1) + nmc->timeout = 90; + + if (argc == 0) { + if (nmc->ask) + ifname = ifname_ask = nmc_readline (PROMPT_INTERFACE); + + if (!ifname_ask) { + g_string_printf (nmc->return_text, _("Error: No interface specified.")); + nmc->return_value = NMC_RESULT_ERROR_USER_INPUT; + goto error; + } + } else { + ifname = *argv; + } + + if (!ifname) { + g_string_printf (nmc->return_text, _("Error: No interface specified.")); + nmc->return_value = NMC_RESULT_ERROR_USER_INPUT; + goto error; + } + + if (next_arg (&argc, &argv) == 0) { + g_string_printf (nmc->return_text, _("Error: extra argument not allowed: '%s'."), *argv); + nmc->return_value = NMC_RESULT_ERROR_USER_INPUT; + goto error; + } + + devices = get_devices_sorted (nmc->client); + for (i = 0; devices[i]; i++) { + NMDevice *candidate = devices[i]; + const char *dev_iface = nm_device_get_iface (candidate); + + if (!g_strcmp0 (dev_iface, ifname)) + device = candidate; + } + g_free (devices); + + if (!device) { + g_string_printf (nmc->return_text, _("Error: Device '%s' not found."), ifname); + nmc->return_value = NMC_RESULT_ERROR_NOT_FOUND; + goto error; + } + + /* + * Use nowait_flag instead of should_wait, because exiting has to be postponed + * till connect_device_cb() is called, giving NM time to check our permissions. + */ + nmc->nowait_flag = (nmc->timeout == 0); + nmc->should_wait = TRUE; + + /* 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); + + info = g_malloc0 (sizeof (AddAndActivateInfo)); + info->nmc = nmc; + info->device = device; + + nm_client_activate_connection_async (nmc->client, + NULL, /* let NM find a connection automatically */ + device, + NULL, + NULL, + connect_device_cb, + info); + + /* Start progress indication */ + if (nmc->print_output == NMC_PRINT_PRETTY) + progress_id = g_timeout_add (120, progress_cb, device); + +error: + g_free (ifname_ask); + + return nmc->return_value; +} + +static void +disconnect_state_cb (NMDevice *device, GParamSpec *pspec, gpointer user_data) +{ + NMDeviceState state; + + state = nm_device_get_state (device); + + if (state == NM_DEVICE_STATE_DISCONNECTED) { + g_signal_handlers_disconnect_by_data (device, user_data); + g_print (_("Device '%s' successfully disconnected.\n"), + nm_device_get_iface (device)); + quit (); + } +} + +static void +device_removed_cb (NMClient *client, NMDevice *device, gpointer user_data) +{ + /* Success: device has been removed. It happens when disconnecting a software device. */ + g_signal_handlers_disconnect_by_data (client, user_data); + g_print (_("Device '%s' successfully disconnected.\n"), + nm_device_get_iface (device)); + quit (); +} + +static void +disconnect_device_cb (GObject *object, GAsyncResult *result, gpointer user_data) +{ + NMDevice *device = NM_DEVICE (object); + NmCli *nmc = (NmCli *) user_data; + NMDeviceState state; + GError *error = NULL; + + if (!nm_device_disconnect_finish (device, result, &error)) { + g_string_printf (nmc->return_text, _("Error: Device '%s' (%s) disconnecting failed: %s"), + nm_device_get_iface (device), + nm_object_get_path (NM_OBJECT (device)), + error->message); + g_error_free (error); + nmc->return_value = NMC_RESULT_ERROR_DEV_DISCONNECT; + quit (); + } else { + state = nm_device_get_state (device); + + if (nmc->nowait_flag || state == NM_DEVICE_STATE_DISCONNECTED) { + /* Don't want to wait or device already disconnected */ + if (state == NM_DEVICE_STATE_DISCONNECTED) { + if (nmc->print_output == NMC_PRINT_PRETTY) + nmc_terminal_erase_line (); + g_print (_("Device '%s' has been disconnected.\n"), nm_device_get_iface (device)); + } + quit (); + } else { + g_signal_connect (device, "notify::state", G_CALLBACK (disconnect_state_cb), nmc); + g_signal_connect (nmc->client, NM_CLIENT_DEVICE_REMOVED, G_CALLBACK (device_removed_cb), nmc); + /* Start timer not to loop forever if "notify::state" signal is not issued */ + g_timeout_add_seconds (nmc->timeout, timeout_cb, nmc); + } + + } +} + +static NMCResultCode +do_device_disconnect (NmCli *nmc, int argc, char **argv) +{ + NMDevice **devices; + NMDevice *device = NULL; + const char *ifname = NULL; + char *ifname_ask = NULL; + int i; + + /* Set default timeout for disconnect operation. */ + if (nmc->timeout == -1) + nmc->timeout = 10; + + if (argc == 0) { + if (nmc->ask) + ifname = ifname_ask = nmc_readline (PROMPT_INTERFACE); + + if (!ifname_ask) { + g_string_printf (nmc->return_text, _("Error: No interface specified.")); + nmc->return_value = NMC_RESULT_ERROR_USER_INPUT; + goto error; + } + } else { + ifname = *argv; + } + + if (!ifname) { + g_string_printf (nmc->return_text, _("Error: No interface specified.")); + nmc->return_value = NMC_RESULT_ERROR_USER_INPUT; + goto error; + } + + if (next_arg (&argc, &argv) == 0) { + g_string_printf (nmc->return_text, _("Error: extra argument not allowed: '%s'."), *argv); + nmc->return_value = NMC_RESULT_ERROR_USER_INPUT; + goto error; + } + + devices = get_devices_sorted (nmc->client); + for (i = 0; devices[i]; i++) { + NMDevice *candidate = devices[i]; + const char *dev_iface = nm_device_get_iface (candidate); + + if (!g_strcmp0 (dev_iface, ifname)) + device = candidate; + } + g_free (devices); + + if (!device) { + g_string_printf (nmc->return_text, _("Error: Device '%s' not found."), ifname); + nmc->return_value = NMC_RESULT_ERROR_NOT_FOUND; + goto error; + } + + /* + * Use nowait_flag instead of should_wait, because exiting has to be postponed + * till disconnect_device_cb() is called, giving NM time to check our permissions. + */ + nmc->nowait_flag = (nmc->timeout == 0); + nmc->should_wait = TRUE; + nm_device_disconnect_async (device, NULL, disconnect_device_cb, nmc); + + /* Start progress indication */ + if (nmc->print_output == NMC_PRINT_PRETTY) + progress_id = g_timeout_add (120, progress_cb, device); + +error: + return nmc->return_value; +} + +static void +delete_device_cb (GObject *object, GAsyncResult *result, gpointer user_data) +{ + NMDevice *device = NM_DEVICE (object); + NmCli *nmc = (NmCli *) user_data; + GError *error = NULL; + + if (!nm_device_delete_finish (device, result, &error)) { + g_string_printf (nmc->return_text, _("Error: Device '%s' (%s) deletion failed: %s"), + nm_device_get_iface (device), + nm_object_get_path (NM_OBJECT (device)), + error->message); + g_error_free (error); + nmc->return_value = NMC_RESULT_ERROR_UNKNOWN; + } + quit (); +} + +static NMCResultCode +do_device_delete (NmCli *nmc, int argc, char **argv) +{ + NMDevice **devices; + NMDevice *device = NULL; + const char *ifname = NULL; + char *ifname_ask = NULL; + int i; + + /* Set default timeout for delete operation. */ + if (nmc->timeout == -1) + nmc->timeout = 10; + + if (argc == 0) { + if (nmc->ask) + ifname = ifname_ask = nmc_readline (PROMPT_INTERFACE); + + if (!ifname_ask) { + g_string_printf (nmc->return_text, _("Error: No interface specified.")); + nmc->return_value = NMC_RESULT_ERROR_USER_INPUT; + goto error; + } + } else + ifname = *argv; + + if (!ifname) { + g_string_printf (nmc->return_text, _("Error: No interface specified.")); + nmc->return_value = NMC_RESULT_ERROR_USER_INPUT; + goto error; + } + + if (next_arg (&argc, &argv) == 0) { + g_string_printf (nmc->return_text, _("Error: extra argument not allowed: '%s'."), *argv); + nmc->return_value = NMC_RESULT_ERROR_USER_INPUT; + goto error; + } + + devices = get_devices_sorted (nmc->client); + for (i = 0; devices[i]; i++) { + NMDevice *candidate = devices[i]; + const char *dev_iface = nm_device_get_iface (candidate); + + if (!g_strcmp0 (dev_iface, ifname)) + device = candidate; + } + g_free (devices); + + if (!device) { + g_string_printf (nmc->return_text, _("Error: Device '%s' not found."), ifname); + nmc->return_value = NMC_RESULT_ERROR_NOT_FOUND; + goto error; + } + + if (!nm_device_is_software (device)) { + g_string_printf (nmc->return_text, _("Error: Device '%s' is a hardware device. It can't be deleted."), ifname); + nmc->return_value = NMC_RESULT_ERROR_USER_INPUT; + goto error; + } + + /* + * Use nowait_flag instead of should_wait, because exiting has to be postponed + * till delete_device_cb() is called, giving NM time to check our permissions. + */ + nmc->nowait_flag = (nmc->timeout == 0); + nmc->should_wait = TRUE; + nm_device_delete_async (device, NULL, delete_device_cb, nmc); + + /* Start progress indication */ + if (nmc->print_output == NMC_PRINT_PRETTY) + progress_id = g_timeout_add (120, progress_cb, device); + +error: + return nmc->return_value; +} + +static void +show_access_point_info (NMDevice *device, NmCli *nmc) +{ + NMAccessPoint *active_ap = NULL; + const char *active_bssid = NULL; + const GPtrArray *aps; + APInfo *info; + NmcOutputField *arr; + + if (nm_device_get_state (device) == NM_DEVICE_STATE_ACTIVATED) { + active_ap = nm_device_wifi_get_active_access_point (NM_DEVICE_WIFI (device)); + active_bssid = active_ap ? nm_access_point_get_bssid (active_ap) : NULL; + } + + arr = nmc_dup_fields_array (nmc_fields_dev_wifi_list, sizeof (nmc_fields_dev_wifi_list), + NMC_OF_FLAG_MAIN_HEADER_ADD | NMC_OF_FLAG_FIELD_NAMES); + g_ptr_array_add (nmc->output_data, arr); + + info = g_malloc0 (sizeof (APInfo)); + info->nmc = nmc; + info->index = 1; + info->output_flags = 0; + info->active_bssid = active_bssid; + info->device = nm_device_get_iface (device); + aps = nm_device_wifi_get_access_points (NM_DEVICE_WIFI (device)); + g_ptr_array_foreach ((GPtrArray *) aps, fill_output_access_point, (gpointer) info); + + print_data (nmc); /* Print all data */ + nmc_empty_output_fields (nmc); + g_free (info); +} + +static NMCResultCode +do_device_wifi_list (NmCli *nmc, int argc, char **argv) +{ + GError *error = NULL; + NMDevice *device = NULL; + NMAccessPoint *ap = NULL; + const char *ifname = NULL; + const char *bssid_user = NULL; + NMDevice **devices = NULL; + const GPtrArray *aps; + APInfo *info; + int i, j; + char *fields_str; + char *fields_all = NMC_FIELDS_DEV_WIFI_LIST_ALL; + char *fields_common = NMC_FIELDS_DEV_WIFI_LIST_COMMON; + NmcOutputField *tmpl, *arr; + size_t tmpl_len; + const char *base_hdr = _("Wi-Fi scan list"); + + 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, "bssid") == 0 || strcmp (*argv, "hwaddr") == 0) { + /* hwaddr is deprecated and will be removed later */ + 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; + } + bssid_user = *argv; + } else { + g_printerr (_("Unknown parameter: %s\n"), *argv); + } + + argc--; + argv++; + } + + 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_dev_wifi_list; + tmpl_len = sizeof (nmc_fields_dev_wifi_list); + nmc->print_fields.indices = parse_output_fields (fields_str, tmpl, FALSE, NULL, &error); + + if (error) { + g_string_printf (nmc->return_text, _("Error: 'device wifi': %s"), error->message); + g_error_free (error); + nmc->return_value = NMC_RESULT_ERROR_USER_INPUT; + goto error; + } + + devices = get_devices_sorted (nmc->client); + if (ifname) { + /* Device specified - list only APs of this interface */ + for (i = 0; devices[i]; i++) { + NMDevice *candidate = devices[i]; + const char *dev_iface = nm_device_get_iface (candidate); + + if (!g_strcmp0 (dev_iface, ifname)) { + device = candidate; + break; + } + } + if (!device) { + g_string_printf (nmc->return_text, _("Error: Device '%s' not found."), ifname); + nmc->return_value = NMC_RESULT_ERROR_NOT_FOUND; + goto error; + } + + /* Main header name */ + nmc->print_fields.header_name = (char *) construct_header_name (base_hdr, ifname); + + if (NM_IS_DEVICE_WIFI (device)) { + if (bssid_user) { + /* Specific AP requested - list only that */ + aps = nm_device_wifi_get_access_points (NM_DEVICE_WIFI (device)); + for (j = 0; j < aps->len; j++) { + char *bssid_up; + NMAccessPoint *candidate_ap = g_ptr_array_index (aps, j); + const char *candidate_bssid = nm_access_point_get_bssid (candidate_ap); + + bssid_up = g_ascii_strup (bssid_user, -1); + if (!strcmp (bssid_up, candidate_bssid)) + ap = candidate_ap; + g_free (bssid_up); + } + if (!ap) { + g_string_printf (nmc->return_text, _("Error: Access point with bssid '%s' not found."), + bssid_user); + nmc->return_value = NMC_RESULT_ERROR_NOT_FOUND; + goto error; + } + /* Add headers (field names) */ + 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); + + info = g_malloc0 (sizeof (APInfo)); + info->nmc = nmc; + info->index = 1; + info->output_flags = 0; + info->active_bssid = NULL; + info->device = nm_device_get_iface (device); + + fill_output_access_point (ap, info); + + print_data (nmc); /* Print all data */ + g_free (info); + } else { + show_access_point_info (device, nmc); + } + } else { + g_string_printf (nmc->return_text, _("Error: Device '%s' is not a Wi-Fi device."), ifname); + nmc->return_value = NMC_RESULT_ERROR_UNKNOWN; + goto error; + } + } else { + gboolean empty_line = FALSE; + + /* List APs for all devices */ + if (bssid_user) { + /* Specific AP requested - list only that */ + for (i = 0; devices[i]; i++) { + NMDevice *dev = devices[i]; + + if (!NM_IS_DEVICE_WIFI (dev)) + continue; + + /* Main header name */ + nmc->print_fields.header_name = (char *) construct_header_name (base_hdr, nm_device_get_iface (dev)); + nmc->print_fields.indices = parse_output_fields (fields_str, tmpl, FALSE, NULL, NULL); + + 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); + + aps = nm_device_wifi_get_access_points (NM_DEVICE_WIFI (dev)); + for (j = 0; j < aps->len; j++) { + char *bssid_up; + NMAccessPoint *candidate_ap = g_ptr_array_index (aps, j); + const char *candidate_bssid = nm_access_point_get_bssid (candidate_ap); + + bssid_up = g_ascii_strup (bssid_user, -1); + if (!strcmp (bssid_up, candidate_bssid)) { + ap = candidate_ap; + + info = g_malloc0 (sizeof (APInfo)); + info->nmc = nmc; + info->index = 1; + info->output_flags = 0; + info->active_bssid = NULL; + info->device = nm_device_get_iface (dev); + fill_output_access_point (ap, info); + g_free (info); + } + g_free (bssid_up); + } + if (empty_line) + g_print ("\n"); /* Empty line between devices' APs */ + print_data (nmc); /* Print all data */ + nmc_empty_output_fields (nmc); + empty_line = TRUE; + } + if (!ap) { + g_string_printf (nmc->return_text, _("Error: Access point with bssid '%s' not found."), + bssid_user); + nmc->return_value = NMC_RESULT_ERROR_NOT_FOUND; + goto error; + } + } else { + for (i = 0; devices[i]; i++) { + NMDevice *dev = devices[i]; + + /* Main header name */ + nmc->print_fields.header_name = (char *) construct_header_name (base_hdr, + nm_device_get_iface (dev)); + nmc->print_fields.indices = parse_output_fields (fields_str, tmpl, FALSE, NULL, NULL); + + if (NM_IS_DEVICE_WIFI (dev)) { + if (empty_line) + g_print ("\n"); /* Empty line between devices' APs */ + show_access_point_info (dev, nmc); + empty_line = TRUE; + } + } + } + } + +error: + g_free (devices); + return nmc->return_value; +} + +/* + * Find a Wi-Fi device with 'iface' in 'devices' array. If 'iface' is NULL, + * the first Wi-Fi device is returned. 'idx' parameter is updated to the point + * where the function finished so that the function can be called repeatedly + * to get next matching device. + * Returns: found device or NULL + */ +static NMDevice * +find_wifi_device_by_iface (const GPtrArray *devices, const char *iface, int *idx) +{ + NMDevice *device = NULL; + int i; + + for (i = *idx; i < devices->len; i++) { + NMDevice *candidate = g_ptr_array_index (devices, i); + const char *dev_iface = nm_device_get_iface (candidate); + + if (!NM_IS_DEVICE_WIFI (candidate)) + continue; + + if (iface) { + /* If a iface was specified then use it. */ + if (g_strcmp0 (dev_iface, iface) == 0) { + device = candidate; + break; + } + } else { + /* Else return the first Wi-Fi device. */ + device = candidate; + break; + } + } + + *idx = i + 1; + return device; +} + +/* + * Find AP on 'device' according to 'bssid' or 'ssid' parameter. + * Returns: found AP or NULL + */ +static NMAccessPoint * +find_ap_on_device (NMDevice *device, GByteArray *bssid, const char *ssid) +{ + const GPtrArray *aps; + NMAccessPoint *ap = NULL; + int i; + + g_return_val_if_fail (NM_IS_DEVICE_WIFI (device), NULL); + g_return_val_if_fail ((bssid && !ssid) || (!bssid && ssid), NULL); + + aps = nm_device_wifi_get_access_points (NM_DEVICE_WIFI (device)); + for (i = 0; i < aps->len; i++) { + NMAccessPoint *candidate_ap = g_ptr_array_index (aps, i); + + if (ssid) { + /* Parameter is SSID */ + GBytes *candidate_ssid; + + candidate_ssid = nm_access_point_get_ssid (candidate_ap); + if (candidate_ssid) { + char *ssid_tmp = nm_utils_ssid_to_utf8 (g_bytes_get_data (candidate_ssid, NULL), + g_bytes_get_size (candidate_ssid)); + + /* Compare SSIDs */ + if (strcmp (ssid, ssid_tmp) == 0) { + ap = candidate_ap; + g_free (ssid_tmp); + break; + } + g_free (ssid_tmp); + } + } else if (bssid) { + /* Parameter is BSSID */ + const char *candidate_bssid = nm_access_point_get_bssid (candidate_ap); + char *bssid_up = nm_utils_hwaddr_ntoa (bssid->data, bssid->len); + + /* Compare BSSIDs */ + if (strcmp (bssid_up, candidate_bssid) == 0) { + ap = candidate_ap; + g_free (bssid_up); + break; + } + g_free (bssid_up); + } + } + + return ap; +} + +static NMCResultCode +do_device_wifi_connect_network (NmCli *nmc, int argc, char **argv) +{ + NMDevice *device = NULL; + NMAccessPoint *ap = NULL; + NM80211ApFlags ap_flags; + NM80211ApSecurityFlags ap_wpa_flags; + NM80211ApSecurityFlags ap_rsn_flags; + NMConnection *connection = NULL; + NMSettingConnection *s_con; + NMSettingWireless *s_wifi; + NMSettingWirelessSecurity *s_wsec; + AddAndActivateInfo *info; + const char *param_user = NULL; + const char *ifname = NULL; + const char *bssid = NULL; + const char *password = NULL; + const char *con_name = NULL; + gboolean private = FALSE; + gboolean wep_passphrase = FALSE; + GByteArray *bssid1_arr = NULL; + GByteArray *bssid2_arr = NULL; + const GPtrArray *devices; + int devices_idx; + char *ssid_ask = NULL; + char *passwd_ask = NULL; + + /* Set default timeout waiting for operation completion. */ + if (nmc->timeout == -1) + nmc->timeout = 90; + + /* Get the first compulsory argument (SSID or BSSID) */ + if (argc > 0) { + param_user = *argv; + bssid1_arr = nm_utils_hwaddr_atoba (param_user, ETH_ALEN); + + argc--; + argv++; + } else { + if (nmc->ask) { + ssid_ask = nmc_readline (_("SSID or BSSID: ")); + param_user = ssid_ask ? ssid_ask : ""; + bssid1_arr = nm_utils_hwaddr_atoba (param_user, ETH_ALEN); + } + if (!ssid_ask) { + g_string_printf (nmc->return_text, _("Error: SSID or BSSID are missing.")); + nmc->return_value = NMC_RESULT_ERROR_USER_INPUT; + goto error; + } + } + + /* Get the rest of the parameters */ + 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, "bssid") == 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; + } + bssid = *argv; + bssid2_arr = nm_utils_hwaddr_atoba (bssid, ETH_ALEN); + if (!bssid2_arr) { + g_string_printf (nmc->return_text, _("Error: bssid argument value '%s' is not a valid BSSID."), + bssid); + nmc->return_value = NMC_RESULT_ERROR_USER_INPUT; + goto error; + } + } else if (strcmp (*argv, "password") == 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; + } + password = *argv; + } else if (strcmp (*argv, "wep-key-type") == 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; + } + if (strcmp (*argv, "key") == 0) + wep_passphrase = FALSE; + else if (strcmp (*argv, "phrase") == 0) + wep_passphrase = TRUE; + else { + g_string_printf (nmc->return_text, + _("Error: wep-key-type argument value '%s' is invalid, use 'key' or 'phrase'."), + *argv); + nmc->return_value = NMC_RESULT_ERROR_USER_INPUT; + goto error; + } + } else if (strcmp (*argv, "name") == 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; + } + con_name = *argv; + } else if (strcmp (*argv, "private") == 0) { + GError *err_tmp = NULL; + 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; + } + if (!nmc_string_to_bool (*argv, &private, &err_tmp)) { + g_string_printf (nmc->return_text, _("Error: %s: %s."), *(argv-1), err_tmp->message); + nmc->return_value = NMC_RESULT_ERROR_USER_INPUT; + g_clear_error (&err_tmp); + goto error; + } + } else { + g_printerr (_("Unknown parameter: %s\n"), *argv); + } + + argc--; + argv++; + } + + /* Verify SSID/BSSID parameters */ + if (bssid1_arr && bssid2_arr && memcmp (bssid1_arr->data, bssid2_arr->data, ETH_ALEN)) { + g_string_printf (nmc->return_text, _("Error: BSSID to connect to (%s) differs from bssid argument (%s)."), + param_user, bssid); + nmc->return_value = NMC_RESULT_ERROR_USER_INPUT; + goto error; + } + if (!bssid1_arr && strlen (param_user) > 32) { + g_string_printf (nmc->return_text, _("Error: Parameter '%s' is neither SSID nor BSSID."), param_user); + nmc->return_value = NMC_RESULT_ERROR_USER_INPUT; + goto error; + } + + devices = nm_client_get_devices (nmc->client); + + /* Find a device to activate the connection on */ + devices_idx = 0; + device = find_wifi_device_by_iface (devices, ifname, &devices_idx); + + if (!device) { + if (ifname) + g_string_printf (nmc->return_text, _("Error: Device '%s' is not a Wi-Fi device."), ifname); + else + g_string_printf (nmc->return_text, _("Error: No Wi-Fi device found.")); + nmc->return_value = NMC_RESULT_ERROR_UNKNOWN; + goto error; + } + + /* Find an AP to connect to */ + ap = find_ap_on_device (device, bssid1_arr, bssid1_arr ? NULL : param_user); + if (!ap && !ifname) { + /* AP not found. ifname was not specified, so try finding the AP on another device. */ + while ((device = find_wifi_device_by_iface (devices, NULL, &devices_idx)) != NULL) { + ap = find_ap_on_device (device, bssid1_arr, bssid1_arr ? NULL : param_user); + if (ap) + break; + } + } + + if (!ap) { + if (!bssid1_arr) + g_string_printf (nmc->return_text, _("Error: No network with SSID '%s' found."), param_user); + else + g_string_printf (nmc->return_text, _("Error: No access point with BSSID '%s' found."), param_user); + nmc->return_value = NMC_RESULT_ERROR_NOT_FOUND; + goto error; + } + + /* If there are some connection data from user, create a connection and + * fill them into proper settings. */ + if (con_name || private || bssid2_arr || password) + connection = nm_simple_connection_new (); + + if (con_name || private) { + s_con = (NMSettingConnection *) nm_setting_connection_new (); + nm_connection_add_setting (connection, NM_SETTING (s_con)); + + /* Set user provided connection name */ + if (con_name) + g_object_set (s_con, NM_SETTING_CONNECTION_ID, con_name, NULL); + + /* Connection will only be visible to this user when '--private' is specified */ + if (private) + nm_setting_connection_add_permission (s_con, "user", g_get_user_name (), NULL); + } + if (bssid2_arr) { + s_wifi = (NMSettingWireless *) nm_setting_wireless_new (); + nm_connection_add_setting (connection, NM_SETTING (s_wifi)); + + /* 'bssid' parameter is used to restrict the conenction only to the BSSID */ + g_object_set (s_wifi, NM_SETTING_WIRELESS_BSSID, bssid2_arr, NULL); + } + + /* handle password */ + ap_flags = nm_access_point_get_flags (ap); + ap_wpa_flags = nm_access_point_get_wpa_flags (ap); + ap_rsn_flags = nm_access_point_get_rsn_flags (ap); + + /* Set password for WEP or WPA-PSK. */ + if (ap_flags & NM_802_11_AP_FLAGS_PRIVACY) { + /* Ask for missing password when one is expected and '--ask' is used */ + if (!password && nmc->ask) + password = passwd_ask = nmc_readline (_("Password: ")); + + if (password) { + if (!connection) + connection = nm_simple_connection_new (); + s_wsec = (NMSettingWirelessSecurity *) nm_setting_wireless_security_new (); + nm_connection_add_setting (connection, NM_SETTING (s_wsec)); + + if (ap_wpa_flags == NM_802_11_AP_SEC_NONE && ap_rsn_flags == NM_802_11_AP_SEC_NONE) { + /* WEP */ + nm_setting_wireless_security_set_wep_key (s_wsec, 0, password); + g_object_set (G_OBJECT (s_wsec), + NM_SETTING_WIRELESS_SECURITY_WEP_KEY_TYPE, + wep_passphrase ? NM_WEP_KEY_TYPE_PASSPHRASE: NM_WEP_KEY_TYPE_KEY, + NULL); + } else if ( !(ap_wpa_flags & NM_802_11_AP_SEC_KEY_MGMT_802_1X) + && !(ap_rsn_flags & NM_802_11_AP_SEC_KEY_MGMT_802_1X)) { + /* WPA PSK */ + g_object_set (s_wsec, NM_SETTING_WIRELESS_SECURITY_PSK, password, NULL); + } + } + } + // FIXME: WPA-Enterprise is not supported yet. + // We are not able to determine and fill all the parameters for + // 802.1X authentication automatically without user providing + // the data. Adding nmcli options for the 8021x setting would + // clutter the command. However, that could be solved later by + // implementing add/edit connections support for nmcli. + + /* nowait_flag indicates user input. should_wait says whether quit in start(). + * We have to delay exit after add_and_activate_cb() is called, even if + * the user doesn't want to wait, in order to give NM time to check our + * permissions. */ + nmc->nowait_flag = (nmc->timeout == 0); + nmc->should_wait = TRUE; + + info = g_malloc0 (sizeof (AddAndActivateInfo)); + info->nmc = nmc; + info->device = device; + + nm_client_add_and_activate_connection_async (nmc->client, + connection, + device, + nm_object_get_path (NM_OBJECT (ap)), + NULL, + add_and_activate_cb, + info); + +error: + if (bssid1_arr) + g_byte_array_free (bssid1_arr, TRUE); + if (bssid2_arr) + g_byte_array_free (bssid2_arr, TRUE); + g_free (ssid_ask); + g_free (passwd_ask); + + return nmc->return_value; +} + +static void +request_rescan_cb (GObject *object, GAsyncResult *result, gpointer user_data) +{ + NmCli *nmc = (NmCli *) user_data; + GError *error = NULL; + + nm_device_wifi_request_scan_finish (NM_DEVICE_WIFI (object), result, &error); + if (error) { + g_string_printf (nmc->return_text, _("Error: %s."), error->message); + nmc->return_value = NMC_RESULT_ERROR_UNKNOWN; + g_error_free (error); + } + quit (); +} + +static NMCResultCode +do_device_wifi_rescan (NmCli *nmc, int argc, char **argv) +{ + NMDevice *device; + const char *ifname = NULL; + const GPtrArray *devices; + int devices_idx; + + nmc->should_wait = TRUE; + + /* Get the parameters */ + if (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; + } + + /* Find Wi-Fi device to scan on. When no ifname is provided, the first Wi-Fi is used. */ + devices = nm_client_get_devices (nmc->client); + devices_idx = 0; + device = find_wifi_device_by_iface (devices, ifname, &devices_idx); + + if (!device) { + if (ifname) + g_string_printf (nmc->return_text, _("Error: Device '%s' is not a Wi-Fi device."), ifname); + else + g_string_printf (nmc->return_text, _("Error: No Wi-Fi device found.")); + nmc->return_value = NMC_RESULT_ERROR_UNKNOWN; + goto error; + } + + nm_device_wifi_request_scan_async (NM_DEVICE_WIFI (device), NULL, + request_rescan_cb, nmc); + + return nmc->return_value; +error: + nmc->should_wait = FALSE; + return nmc->return_value; +} + +static NMCResultCode +do_device_wifi (NmCli *nmc, int argc, char **argv) +{ + if (argc == 0) + nmc->return_value = do_device_wifi_list (nmc, argc-1, argv+1); + else if (argc > 0) { + if (matches (*argv, "list") == 0) { + nmc->return_value = do_device_wifi_list (nmc, argc-1, argv+1); + } else if (matches (*argv, "connect") == 0) { + nmc->return_value = do_device_wifi_connect_network (nmc, argc-1, argv+1); + } else if (matches (*argv, "rescan") == 0) { + nmc->return_value = do_device_wifi_rescan (nmc, argc-1, argv+1); + } else { + g_string_printf (nmc->return_text, _("Error: 'device wifi' command '%s' is not valid."), *argv); + nmc->return_value = NMC_RESULT_ERROR_USER_INPUT; + } + } + + return nmc->return_value; +} + +#if WITH_WIMAX +static void +show_nsp_info (NMDevice *device, NmCli *nmc) +{ + const GPtrArray *nsps; + int i, idx = 1; + NmcOutputField *arr; + + /* Add headers (field names) */ + arr = nmc_dup_fields_array (nmc_fields_dev_wimax_list, sizeof (nmc_fields_dev_wimax_list), + NMC_OF_FLAG_MAIN_HEADER_ADD | NMC_OF_FLAG_FIELD_NAMES); + g_ptr_array_add (nmc->output_data, arr); + + nsps = nm_device_wimax_get_nsps (NM_DEVICE_WIMAX (device)); + for (i = 0; i < nsps->len; i++) { + NMWimaxNsp *nsp = g_ptr_array_index (nsps, i); + + fill_output_wimax_nsp (nsp, nmc, device, idx++, 0); + } + print_data (nmc); /* Print all data */ + nmc_empty_output_fields (nmc); +} + +static NMCResultCode +do_device_wimax_list (NmCli *nmc, int argc, char **argv) +{ + GError *error = NULL; + NMDevice *device = NULL; + NMWimaxNsp *nsp = NULL; + const char *ifname = NULL; + const char *nsp_user = NULL; + const GPtrArray *devices; + const GPtrArray *nsps; + int i, j; + char *fields_str; + char *fields_all = NMC_FIELDS_DEV_WIMAX_LIST_ALL; + char *fields_common = NMC_FIELDS_DEV_WIMAX_LIST_COMMON; + NmcOutputField *tmpl, *arr; + size_t tmpl_len; + const char *base_hdr = _("WiMAX NSP list"); + + 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, "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_user = *argv; + } else { + g_printerr (_("Unknown parameter: %s\n"), *argv); + } + + argc--; + argv++; + } + + 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_dev_wimax_list; + tmpl_len = sizeof (nmc_fields_dev_wimax_list); + nmc->print_fields.indices = parse_output_fields (fields_str, tmpl, FALSE, NULL, &error); + + if (error) { + g_string_printf (nmc->return_text, _("Error: 'device wimax': %s"), error->message); + g_error_free (error); + nmc->return_value = NMC_RESULT_ERROR_USER_INPUT; + goto error; + } + + devices = nm_client_get_devices (nmc->client); + if (ifname) { + /* Device specified - list only NSPs of this interface */ + for (i = 0; i < devices->len; i++) { + NMDevice *candidate = g_ptr_array_index (devices, i); + const char *dev_iface = nm_device_get_iface (candidate); + + if (!g_strcmp0 (dev_iface, ifname)) { + device = candidate; + break; + } + } + + if (!device) { + g_string_printf (nmc->return_text, _("Error: Device '%s' not found."), ifname); + nmc->return_value = NMC_RESULT_ERROR_NOT_FOUND; + goto error; + } + + /* Main header name */ + nmc->print_fields.header_name = (char *) construct_header_name (base_hdr, ifname); + + if (NM_IS_DEVICE_WIMAX (device)) { + if (nsp_user) { + /* Specific NSP requested - list only that */ + nsps = nm_device_wimax_get_nsps (NM_DEVICE_WIMAX (device)); + for (j = 0, nsp = NULL; j < nsps->len; j++) { + NMWimaxNsp *candidate_nsp = g_ptr_array_index (nsps, j); + const char *candidate_name = nm_wimax_nsp_get_name (candidate_nsp); + char *nsp_up; + + nsp_up = g_ascii_strup (nsp_user, -1); + if (!strcmp (nsp_up, candidate_name)) + nsp = candidate_nsp; + g_free (nsp_up); + } + if (!nsp) { + g_string_printf (nmc->return_text, _("Error: NSP with name '%s' not found."), nsp_user); + nmc->return_value = NMC_RESULT_ERROR_NOT_FOUND; + goto error; + } + /* Add headers (field names) */ + 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); + fill_output_wimax_nsp (nsp, nmc, device, 1, 0); + print_data (nmc); /* Print all data */ + } else { + show_nsp_info (device, nmc); + } + } else { + g_string_printf (nmc->return_text, _("Error: Device '%s' is not a WiMAX device."), ifname); + nmc->return_value = NMC_RESULT_ERROR_UNKNOWN; + goto error; + } + } else { + gboolean empty_line = FALSE; + + /* List NSPs for all devices */ + if (nsp_user) { + /* Specific NSP requested - list only that */ + for (i = 0; i < devices->len; i++) { + NMDevice *dev = g_ptr_array_index (devices, i); + int idx = 1; + + if (!NM_IS_DEVICE_WIMAX (dev)) + continue; + + /* Main header name */ + nmc->print_fields.header_name = (char *) construct_header_name (base_hdr, nm_device_get_iface (dev)); + nmc->print_fields.indices = parse_output_fields (fields_str, tmpl, FALSE, NULL, NULL); + + 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); + + nsps = nm_device_wimax_get_nsps (NM_DEVICE_WIMAX (dev)); + for (j = 0, nsp = NULL; j < nsps->len; j++) { + NMWimaxNsp *candidate_nsp = g_ptr_array_index (nsps, j); + const char *candidate_name = nm_wimax_nsp_get_name (candidate_nsp); + char *nsp_up; + + nsp_up = g_ascii_strup (nsp_user, -1); + if (!strcmp (nsp_up, candidate_name)) { + nsp = candidate_nsp; + fill_output_wimax_nsp (nsp, nmc, dev, idx, 0); + } + g_free (nsp_up); + } + if (empty_line) + g_print ("\n"); /* Empty line between devices' NSPs */ + print_data (nmc); /* Print all data */ + nmc_empty_output_fields (nmc); + empty_line = TRUE; + } + if (!nsp) { + g_string_printf (nmc->return_text, _("Error: Access point with nsp '%s' not found."), nsp_user); + nmc->return_value = NMC_RESULT_ERROR_NOT_FOUND; + goto error; + } + } else { + for (i = 0; i < devices->len; i++) { + NMDevice *dev = g_ptr_array_index (devices, i); + + /* Main header name */ + nmc->print_fields.header_name = (char *) construct_header_name (base_hdr, + nm_device_get_iface (dev)); + nmc->print_fields.indices = parse_output_fields (fields_str, tmpl, FALSE, NULL, NULL); + + if (NM_IS_DEVICE_WIMAX (dev)) { + if (empty_line) + g_print ("\n"); /* Empty line between devices' NSPs */ + show_nsp_info (dev, nmc); + empty_line = TRUE; + } + } + } + } + +error: + return nmc->return_value; +} + +static NMCResultCode +do_device_wimax (NmCli *nmc, int argc, char **argv) +{ + if (argc == 0) + nmc->return_value = do_device_wimax_list (nmc, argc-1, argv+1); + else if (argc > 0) { + if (matches (*argv, "list") == 0) { + nmc->return_value = do_device_wimax_list (nmc, argc-1, argv+1); + } + else { + g_string_printf (nmc->return_text, _("Error: 'device wimax' command '%s' is not valid."), *argv); + nmc->return_value = NMC_RESULT_ERROR_USER_INPUT; + } + } + + return nmc->return_value; +} +#endif + +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; +} + +/* Global variable defined in nmcli.c */ +extern NmCli nm_cli; + +static char * +gen_func_ifnames (const char *text, int state) +{ + int i, j = 0; + const GPtrArray *devices; + const char **ifnames; + char *ret; + + nm_cli.get_client (&nm_cli); + devices = nm_client_get_devices (nm_cli.client); + if (devices->len == 0) + return NULL; + + ifnames = 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); + ifnames[j++] = ifname; + } + ifnames[j] = NULL; + + ret = nmc_rl_gen_func_basic (text, state, ifnames); + + g_free (ifnames); + return ret; +} + +static char ** +nmcli_device_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; + + /* Disable appending space after completion */ + rl_completion_append_character = '\0'; + + if (!is_single_word (rl_line_buffer)) + return NULL; + + if (g_strcmp0 (rl_prompt, PROMPT_INTERFACE) == 0) + generator_func = gen_func_ifnames; + + if (generator_func) + match_array = rl_completion_matches (text, generator_func); + + return match_array; +} + +NMCResultCode +do_devices (NmCli *nmc, int argc, char **argv) +{ + GError *error = NULL; + + /* Register polkit agent */ + nmc_start_polkit_agent_start_try (nmc); + + rl_attempted_completion_function = (rl_completion_func_t *) nmcli_device_tab_completion; + + /* Get NMClient object early */ + 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) { + if (!nmc_terse_option_check (nmc->print_output, nmc->required_fields, &error)) + goto opt_error; + nmc->return_value = do_devices_status (nmc, 0, NULL); + } + + if (argc > 0) { + if (nmc_arg_is_help (*argv)) { + usage (); + goto usage_exit; + } + else if (matches (*argv, "status") == 0) { + if (nmc_arg_is_help (*(argv+1))) { + usage_device_status (); + goto usage_exit; + } + if (!nmc_terse_option_check (nmc->print_output, nmc->required_fields, &error)) + goto opt_error; + nmc->return_value = do_devices_status (nmc, argc-1, argv+1); + } + else if (matches (*argv, "show") == 0) { + if (nmc_arg_is_help (*(argv+1))) { + usage_device_show (); + goto usage_exit; + } + if (!nmc->mode_specified) + nmc->multiline_output = TRUE; /* multiline mode is default for 'device show' */ + nmc->return_value = do_devices_show (nmc, argc-1, argv+1); + } + else if (matches (*argv, "connect") == 0) { + if (nmc_arg_is_help (*(argv+1))) { + usage_device_connect (); + goto usage_exit; + } + nmc->return_value = do_device_connect (nmc, argc-1, argv+1); + } + else if (matches (*argv, "disconnect") == 0) { + if (nmc_arg_is_help (*(argv+1))) { + usage_device_disconnect (); + goto usage_exit; + } + nmc->return_value = do_device_disconnect (nmc, argc-1, argv+1); + } + else if (matches (*argv, "delete") == 0) { + if (nmc_arg_is_help (*(argv+1))) { + usage_device_delete (); + goto usage_exit; + } + nmc->return_value = do_device_delete (nmc, argc-1, argv+1); + } + else if (matches (*argv, "wifi") == 0) { + if (nmc_arg_is_help (*(argv+1))) { + usage_device_wifi (); + goto usage_exit; + } + if (!nmc_terse_option_check (nmc->print_output, nmc->required_fields, &error)) + goto opt_error; + nmc->return_value = do_device_wifi (nmc, argc-1, argv+1); + } +#if WITH_WIMAX + else if (matches (*argv, "wimax") == 0) { + if (nmc_arg_is_help (*(argv+1))) { + usage_device_wimax (); + goto usage_exit; + } + if (!nmc_terse_option_check (nmc->print_output, nmc->required_fields, &error)) + goto opt_error; + nmc->return_value = do_device_wimax (nmc, argc-1, argv+1); + } +#endif + else { + usage (); + g_string_printf (nmc->return_text, _("Error: 'dev' command '%s' is not valid."), *argv); + nmc->return_value = NMC_RESULT_ERROR_USER_INPUT; + } + } + +usage_exit: + return nmc->return_value; + +opt_error: + g_string_printf (nmc->return_text, _("Error: %s."), error->message); + nmc->return_value = NMC_RESULT_ERROR_USER_INPUT; + g_error_free (error); + return nmc->return_value; +} diff --git a/clients/cli/devices.h b/clients/cli/devices.h new file mode 100644 index 00000000..152dd207 --- /dev/null +++ b/clients/cli/devices.h @@ -0,0 +1,27 @@ +/* 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. + * + * (C) Copyright 2010 Red Hat, Inc. + */ + +#ifndef NMC_DEVICES_H +#define NMC_DEVICES_H + +#include "nmcli.h" + +NMCResultCode do_devices (NmCli *nmc, int argc, char **argv); + +#endif /* NMC_DEVICES_H */ diff --git a/clients/cli/general.c b/clients/cli/general.c new file mode 100644 index 00000000..44c63c35 --- /dev/null +++ b/clients/cli/general.c @@ -0,0 +1,899 @@ +/* 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 <string.h> +#include <stdlib.h> + +#include <glib.h> +#include <glib/gi18n.h> + +#include "polkit-agent.h" +#include "utils.h" +#include "general.h" + + +/* Available fields for 'general status' */ +static NmcOutputField nmc_fields_nm_status[] = { + {"RUNNING", N_("RUNNING"), 15}, /* 0 */ + {"VERSION", N_("VERSION"), 10}, /* 1 */ + {"STATE", N_("STATE"), 15}, /* 2 */ + {"STARTUP", N_("STARTUP"), 10}, /* 3 */ + {"CONNECTIVITY", N_("CONNECTIVITY"), 15}, /* 4 */ + {"NETWORKING", N_("NETWORKING"), 13}, /* 5 */ + {"WIFI-HW", N_("WIFI-HW"), 15}, /* 6 */ + {"WIFI", N_("WIFI"), 10}, /* 7 */ + {"WWAN-HW", N_("WWAN-HW"), 15}, /* 8 */ + {"WWAN", N_("WWAN"), 10}, /* 9 */ + {"WIMAX-HW", N_("WIMAX-HW"), 15}, /* 10 */ + {"WIMAX", N_("WIMAX"), 10}, /* 11 */ + {NULL, NULL, 0} +}; +#if WITH_WIMAX +#define NMC_FIELDS_NM_STATUS_ALL "RUNNING,VERSION,STATE,STARTUP,CONNECTIVITY,NETWORKING,WIFI-HW,WIFI,WWAN-HW,WWAN,WIMAX-HW,WIMAX" +#define NMC_FIELDS_NM_STATUS_SWITCH "NETWORKING,WIFI-HW,WIFI,WWAN-HW,WWAN,WIMAX-HW,WIMAX" +#define NMC_FIELDS_NM_STATUS_RADIO "WIFI-HW,WIFI,WWAN-HW,WWAN,WIMAX-HW,WIMAX" +#else +#define NMC_FIELDS_NM_STATUS_ALL "RUNNING,VERSION,STATE,STARTUP,CONNECTIVITY,NETWORKING,WIFI-HW,WIFI,WWAN-HW,WWAN" +#define NMC_FIELDS_NM_STATUS_SWITCH "NETWORKING,WIFI-HW,WIFI,WWAN-HW,WWAN" +#define NMC_FIELDS_NM_STATUS_RADIO "WIFI-HW,WIFI,WWAN-HW,WWAN" +#endif +#define NMC_FIELDS_NM_STATUS_COMMON "STATE,CONNECTIVITY,WIFI-HW,WIFI,WWAN-HW,WWAN" +#define NMC_FIELDS_NM_NETWORKING "NETWORKING" +#define NMC_FIELDS_NM_WIFI "WIFI" +#define NMC_FIELDS_NM_WWAN "WWAN" +#define NMC_FIELDS_NM_WIMAX "WIMAX" +#define NMC_FIELDS_NM_CONNECTIVITY "CONNECTIVITY" + + +/* Available fields for 'general permissions' */ +static NmcOutputField nmc_fields_nm_permissions[] = { + {"PERMISSION", N_("PERMISSION"), 57}, /* 0 */ + {"VALUE", N_("VALUE"), 10}, /* 1 */ + {NULL, NULL, 0} +}; +#define NMC_FIELDS_NM_PERMISSIONS_ALL "PERMISSION,VALUE" +#define NMC_FIELDS_NM_PERMISSIONS_COMMON "PERMISSION,VALUE" + +/* Available fields for 'general logging' */ +static NmcOutputField nmc_fields_nm_logging[] = { + {"LEVEL", N_("LEVEL"), 10}, /* 0 */ + {"DOMAINS", N_("DOMAINS"), 70}, /* 1 */ + {NULL, NULL, 0} +}; +#define NMC_FIELDS_NM_LOGGING_ALL "LEVEL,DOMAINS" +#define NMC_FIELDS_NM_LOGGING_COMMON "LEVEL,DOMAINS" + + +/* glib main loop variable - defined in nmcli.c */ +extern GMainLoop *loop; + + +static void +usage_general (void) +{ + g_printerr (_("Usage: nmcli general { COMMAND | help }\n\n" + "COMMAND := { status | hostname | permissions | logging }\n\n" + " status\n\n" + " hostname [<hostname>]\n\n" + " permissions\n\n" + " logging [level <log level>] [domains <log domains>]\n\n")); +} + +static void +usage_general_status (void) +{ + g_printerr (_("Usage: nmcli general status { help }\n" + "\n" + "Show overall status of NetworkManager.\n" + "'status' is the default action, which means 'nmcli gen' calls 'nmcli gen status'\n\n")); +} + +static void +usage_general_hostname (void) +{ + g_printerr (_("Usage: nmcli general hostname { ARGUMENTS | help }\n" + "\n" + "ARGUMENTS := [<hostname>]\n" + "\n" + "Get or change persistent system hostname.\n" + "With no arguments, this prints currently configured hostname. When you pass\n" + "a hostname, NetworkManager will set it as the new persistent system hostname.\n\n")); +} + +static void +usage_general_permissions (void) +{ + g_printerr (_("Usage: nmcli general permissions { help }\n" + "\n" + "Show caller permissions for authenticated operations.\n\n")); +} + +static void +usage_general_logging (void) +{ + g_printerr (_("Usage: nmcli general logging { ARGUMENTS | help }\n" + "\n" + "ARGUMENTS := [level <log level>] [domains <log domains>]\n" + "\n" + "Get or change NetworkManager logging level and domains.\n" + "Without any argument current logging level and domains are shown. In order to\n" + "change logging state, provide level and/or domain. Please refer to the man page\n" + "for the list of possible logging domains.\n\n")); +} + +static void +usage_networking (void) +{ + g_printerr (_("Usage: nmcli networking { COMMAND | help }\n\n" + "COMMAND := { [ on | off | connectivity ] }\n\n" + " on\n\n" + " off\n\n" + " connectivity [check]\n\n")); +} + +static void +usage_networking_on (void) +{ + g_printerr (_("Usage: nmcli networking on { help }\n" + "\n" + "Switch networking on.\n\n")); +} + +static void +usage_networking_off (void) +{ + g_printerr (_("Usage: nmcli networking off { help }\n" + "\n" + "Switch networking off.\n\n")); +} + +static void +usage_networking_connectivity (void) +{ + g_printerr (_("Usage: nmcli networking connectivity { ARGUMENTS | help }\n" + "\n" + "ARGUMENTS := [check]\n" + "\n" + "Get network connectivity state.\n" + "The optional 'check' argument makes NetworkManager re-check the connectivity.\n\n")); + +} + +static void +usage_radio (void) +{ + g_printerr (_("Usage: nmcli radio { COMMAND | help }\n\n" +#if WITH_WIMAX + "COMMAND := { all | wifi | wwan | wimax }\n\n" + " all | wifi | wwan | wimax [ on | off ]\n\n" +#else + "COMMAND := { all | wifi | wwan }\n\n" + " all | wifi | wwan [ on | off ]\n\n" +#endif + )); +} + +static void +usage_radio_all (void) +{ + g_printerr (_("Usage: nmcli radio all { ARGUMENTS | help }\n" + "\n" + "ARGUMENTS := [on | off]\n" + "\n" + "Get status of all radio switches, or turn them on/off.\n\n")); +} + +static void +usage_radio_wifi (void) +{ + g_printerr (_("Usage: nmcli radio wifi { ARGUMENTS | help }\n" + "\n" + "ARGUMENTS := [on | off]\n" + "\n" + "Get status of Wi-Fi radio switch, or turn it on/off.\n\n")); +} + +static void +usage_radio_wwan (void) +{ + g_printerr (_("Usage: nmcli radio wwan { ARGUMENTS | help }\n" + "\n" + "ARGUMENTS := [on | off]\n" + "\n" + "Get status of mobile broadband radio switch, or turn it on/off.\n\n")); +} + +#if WITH_WIMAX +static void +usage_radio_wimax (void) +{ + g_printerr (_("Usage: nmcli radio wimax { ARGUMENTS | help }\n" + "\n" + "ARGUMENTS := [on | off]\n" + "\n" + "Get status of WiMAX radio switch, or turn it on/off.\n\n")); +} +#endif + +/* quit main loop */ +static void +quit (void) +{ + g_main_loop_quit (loop); /* quit main loop */ +} + +static const char * +nm_state_to_string (NMState state) +{ + switch (state) { + case NM_STATE_ASLEEP: + return _("asleep"); + case NM_STATE_CONNECTING: + return _("connecting"); + case NM_STATE_CONNECTED_LOCAL: + return _("connected (local only)"); + case NM_STATE_CONNECTED_SITE: + return _("connected (site only)"); + case NM_STATE_CONNECTED_GLOBAL: + return _("connected"); + case NM_STATE_DISCONNECTING: + return _("disconnecting"); + case NM_STATE_DISCONNECTED: + return _("disconnected"); + case NM_STATE_UNKNOWN: + default: + return _("unknown"); + } +} + +static const char * +nm_connectivity_to_string (NMConnectivityState connectivity) +{ + switch (connectivity) { + case NM_CONNECTIVITY_NONE: + return _("none"); + case NM_CONNECTIVITY_PORTAL: + return _("portal"); + case NM_CONNECTIVITY_LIMITED: + return _("limited"); + case NM_CONNECTIVITY_FULL: + return _("full"); + case NM_CONNECTIVITY_UNKNOWN: + default: + return _("unknown"); + } +} + +static gboolean +show_nm_status (NmCli *nmc, const char *pretty_header_name, const char *print_flds) +{ + gboolean startup = FALSE; + NMState state = NM_STATE_UNKNOWN; + NMConnectivityState connectivity = NM_CONNECTIVITY_UNKNOWN; + const char *net_enabled_str; + const char *wireless_hw_enabled_str, *wireless_enabled_str; + const char *wwan_hw_enabled_str, *wwan_enabled_str; +#if WITH_WIMAX + const char *wimax_hw_enabled_str, *wimax_enabled_str; +#endif + GError *error = NULL; + const char *fields_str; + const char *fields_all = print_flds ? print_flds : NMC_FIELDS_NM_STATUS_ALL; + const char *fields_common = print_flds ? print_flds : NMC_FIELDS_NM_STATUS_COMMON; + NmcOutputField *tmpl, *arr; + size_t tmpl_len; + + 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_nm_status; + tmpl_len = sizeof (nmc_fields_nm_status); + nmc->print_fields.indices = parse_output_fields (fields_str, tmpl, FALSE, NULL, &error); + + if (error) { + g_string_printf (nmc->return_text, _("Error: only these fields are allowed: %s"), fields_all); + g_error_free (error); + nmc->return_value = NMC_RESULT_ERROR_USER_INPUT; + return FALSE; + } + + nmc->get_client (nmc); /* create NMClient */ + + 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 FALSE; + } + + if (!nmc_versions_match (nmc)) + return FALSE; + + state = nm_client_get_state (nmc->client); + startup = nm_client_get_startup (nmc->client); + connectivity = nm_client_get_connectivity (nmc->client); + net_enabled_str = nm_client_networking_get_enabled (nmc->client) ? _("enabled") : _("disabled"); + wireless_hw_enabled_str = nm_client_wireless_hardware_get_enabled (nmc->client) ? _("enabled") : _("disabled"); + wireless_enabled_str = nm_client_wireless_get_enabled (nmc->client) ? _("enabled") : _("disabled"); + wwan_hw_enabled_str = nm_client_wwan_hardware_get_enabled (nmc->client) ? _("enabled") : _("disabled"); + wwan_enabled_str = nm_client_wwan_get_enabled (nmc->client) ? _("enabled") : _("disabled"); +#if WITH_WIMAX + wimax_hw_enabled_str = nm_client_wimax_hardware_get_enabled (nmc->client) ? _("enabled") : _("disabled"); + wimax_enabled_str = nm_client_wimax_get_enabled (nmc->client) ? _("enabled") : _("disabled"); +#endif + + nmc->print_fields.header_name = pretty_header_name ? (char *) pretty_header_name : _("NetworkManager status"); + 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); + + arr = nmc_dup_fields_array (tmpl, tmpl_len, 0); + set_val_strc (arr, 0, _("running")); + set_val_strc (arr, 1, nm_client_get_version (nmc->client)); + set_val_strc (arr, 2, nm_state_to_string (state)); + set_val_strc (arr, 3, startup ? _("starting") : _("started")); + set_val_strc (arr, 4, nm_connectivity_to_string (connectivity)); + set_val_strc (arr, 5, net_enabled_str); + set_val_strc (arr, 6, wireless_hw_enabled_str); + set_val_strc (arr, 7, wireless_enabled_str); + set_val_strc (arr, 8, wwan_hw_enabled_str); + set_val_strc (arr, 9, wwan_enabled_str); +#if WITH_WIMAX + set_val_strc (arr, 10, wimax_hw_enabled_str); + set_val_strc (arr, 11, wimax_enabled_str); +#endif + g_ptr_array_add (nmc->output_data, arr); + + print_data (nmc); /* Print all data */ + + return TRUE; +} + +#define NM_AUTH_PERMISSION_ENABLE_DISABLE_NETWORK "org.freedesktop.NetworkManager.enable-disable-network" +#define NM_AUTH_PERMISSION_ENABLE_DISABLE_WIFI "org.freedesktop.NetworkManager.enable-disable-wifi" +#define NM_AUTH_PERMISSION_ENABLE_DISABLE_WWAN "org.freedesktop.NetworkManager.enable-disable-wwan" +#define NM_AUTH_PERMISSION_ENABLE_DISABLE_WIMAX "org.freedesktop.NetworkManager.enable-disable-wimax" +#define NM_AUTH_PERMISSION_SLEEP_WAKE "org.freedesktop.NetworkManager.sleep-wake" +#define NM_AUTH_PERMISSION_NETWORK_CONTROL "org.freedesktop.NetworkManager.network-control" +#define NM_AUTH_PERMISSION_WIFI_SHARE_PROTECTED "org.freedesktop.NetworkManager.wifi.share.protected" +#define NM_AUTH_PERMISSION_WIFI_SHARE_OPEN "org.freedesktop.NetworkManager.wifi.share.open" +#define NM_AUTH_PERMISSION_SETTINGS_MODIFY_SYSTEM "org.freedesktop.NetworkManager.settings.modify.system" +#define NM_AUTH_PERMISSION_SETTINGS_MODIFY_OWN "org.freedesktop.NetworkManager.settings.modify.own" +#define NM_AUTH_PERMISSION_SETTINGS_MODIFY_HOSTNAME "org.freedesktop.NetworkManager.settings.modify.hostname" + +static const char * +permission_to_string (NMClientPermission perm) +{ + switch (perm) { + case NM_CLIENT_PERMISSION_ENABLE_DISABLE_NETWORK: + return NM_AUTH_PERMISSION_ENABLE_DISABLE_NETWORK; + case NM_CLIENT_PERMISSION_ENABLE_DISABLE_WIFI: + return NM_AUTH_PERMISSION_ENABLE_DISABLE_WIFI; + case NM_CLIENT_PERMISSION_ENABLE_DISABLE_WWAN: + return NM_AUTH_PERMISSION_ENABLE_DISABLE_WWAN; + case NM_CLIENT_PERMISSION_ENABLE_DISABLE_WIMAX: + return NM_AUTH_PERMISSION_ENABLE_DISABLE_WIMAX; + case NM_CLIENT_PERMISSION_SLEEP_WAKE: + return NM_AUTH_PERMISSION_SLEEP_WAKE; + case NM_CLIENT_PERMISSION_NETWORK_CONTROL: + return NM_AUTH_PERMISSION_NETWORK_CONTROL; + case NM_CLIENT_PERMISSION_WIFI_SHARE_PROTECTED: + return NM_AUTH_PERMISSION_WIFI_SHARE_PROTECTED; + case NM_CLIENT_PERMISSION_WIFI_SHARE_OPEN: + return NM_AUTH_PERMISSION_WIFI_SHARE_OPEN; + case NM_CLIENT_PERMISSION_SETTINGS_MODIFY_SYSTEM: + return NM_AUTH_PERMISSION_SETTINGS_MODIFY_SYSTEM; + case NM_CLIENT_PERMISSION_SETTINGS_MODIFY_OWN: + return NM_AUTH_PERMISSION_SETTINGS_MODIFY_OWN; + case NM_CLIENT_PERMISSION_SETTINGS_MODIFY_HOSTNAME: + return NM_AUTH_PERMISSION_SETTINGS_MODIFY_HOSTNAME; + default: + return _("unknown"); + } +} + +static const char * +permission_result_to_string (NMClientPermissionResult perm_result) +{ + + switch (perm_result) { + case NM_CLIENT_PERMISSION_RESULT_YES: + return _("yes"); + case NM_CLIENT_PERMISSION_RESULT_NO: + return _("no"); + case NM_CLIENT_PERMISSION_RESULT_AUTH: + return _("auth"); + default: + return _("unknown"); + } +} + +static gboolean +show_nm_permissions (NmCli *nmc) +{ + NMClientPermission perm; + GError *error = NULL; + const char *fields_str; + const char *fields_all = NMC_FIELDS_NM_PERMISSIONS_ALL; + const char *fields_common = NMC_FIELDS_NM_PERMISSIONS_COMMON; + NmcOutputField *tmpl, *arr; + size_t tmpl_len; + + 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_nm_permissions; + tmpl_len = sizeof (nmc_fields_nm_permissions); + nmc->print_fields.indices = parse_output_fields (fields_str, tmpl, FALSE, NULL, &error); + + if (error) { + g_string_printf (nmc->return_text, _("Error: 'general permissions': %s"), error->message); + g_error_free (error); + nmc->return_value = NMC_RESULT_ERROR_USER_INPUT; + return FALSE; + } + + nmc->get_client (nmc); /* create NMClient */ + + 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 FALSE; + } + + nmc->print_fields.header_name = _("NetworkManager permissions"); + 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); + + for (perm = NM_CLIENT_PERMISSION_NONE + 1; perm <= NM_CLIENT_PERMISSION_LAST; perm++) { + NMClientPermissionResult perm_result = nm_client_get_permission_result (nmc->client, perm); + + arr = nmc_dup_fields_array (tmpl, tmpl_len, 0); + set_val_strc (arr, 0, permission_to_string (perm)); + set_val_strc (arr, 1, permission_result_to_string (perm_result)); + g_ptr_array_add (nmc->output_data, arr); + } + print_data (nmc); /* Print all data */ + + return TRUE; +} + +static gboolean +show_general_logging (NmCli *nmc) +{ + char *level = NULL; + char *domains = NULL; + GError *error = NULL; + const char *fields_str; + const char *fields_all = NMC_FIELDS_NM_LOGGING_ALL; + const char *fields_common = NMC_FIELDS_NM_LOGGING_COMMON; + NmcOutputField *tmpl, *arr; + size_t tmpl_len; + + 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_nm_logging; + tmpl_len = sizeof (nmc_fields_nm_logging); + nmc->print_fields.indices = parse_output_fields (fields_str, tmpl, FALSE, NULL, &error); + + if (error) { + g_string_printf (nmc->return_text, _("Error: 'general logging': %s"), error->message); + g_error_free (error); + nmc->return_value = NMC_RESULT_ERROR_USER_INPUT; + return FALSE; + } + + nmc->get_client (nmc); /* create NMClient */ + nm_client_get_logging (nmc->client, &level, &domains, &error); + if (error) { + g_string_printf (nmc->return_text, _("Error: %s."), error->message); + nmc->return_value = NMC_RESULT_ERROR_USER_INPUT; + g_error_free (error); + return FALSE; + } + + nmc->print_fields.header_name = _("NetworkManager logging"); + 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); + + arr = nmc_dup_fields_array (tmpl, tmpl_len, 0); + set_val_str (arr, 0, level); + set_val_str (arr, 1, domains); + g_ptr_array_add (nmc->output_data, arr); + + print_data (nmc); /* Print all data */ + + return TRUE; +} + +static void +save_hostname_cb (GObject *object, GAsyncResult *result, gpointer user_data) +{ + NmCli *nmc = (NmCli *) user_data; + GError *error = NULL; + + nm_client_save_hostname_finish (NM_CLIENT (object), result, &error); + if (error) { + g_string_printf (nmc->return_text, _("Error: failed to set hostname: %s"), + error->message); + nmc->return_value = NMC_RESULT_ERROR_UNKNOWN; + g_error_free (error); + } + quit (); +} + +/* + * Entry point function for general operations 'nmcli general' + */ +NMCResultCode +do_general (NmCli *nmc, int argc, char **argv) +{ + GError *error = NULL; + + /* Register polkit agent */ + nmc_start_polkit_agent_start_try (nmc); + + if (argc == 0) { + if (!nmc_terse_option_check (nmc->print_output, nmc->required_fields, &error)) { + g_string_printf (nmc->return_text, _("Error: %s."), error->message); + nmc->return_value = NMC_RESULT_ERROR_USER_INPUT; + goto finish; + } + show_nm_status (nmc, NULL, NULL); + } + + if (argc > 0) { + if (nmc_arg_is_help (*argv)) { + usage_general (); + } + else if (matches (*argv, "status") == 0) { + if (nmc_arg_is_help (*(argv+1))) { + usage_general_status (); + goto finish; + } + if (!nmc_terse_option_check (nmc->print_output, nmc->required_fields, &error)) { + g_string_printf (nmc->return_text, _("Error: %s."), error->message); + nmc->return_value = NMC_RESULT_ERROR_USER_INPUT; + goto finish; + } + show_nm_status (nmc, NULL, NULL); + } + else if (matches (*argv, "hostname") == 0) { + if (nmc_arg_is_help (*(argv+1))) { + usage_general_hostname (); + goto finish; + } + + if (next_arg (&argc, &argv) != 0) { + /* no arguments -> get hostname */ + char *hostname = NULL; + + nmc->get_client (nmc); /* create NMClient */ + g_object_get (nmc->client, NM_CLIENT_HOSTNAME, &hostname, NULL); + if (hostname) + g_print ("%s\n", hostname); + g_free (hostname); + } else { + /* hostname provided -> set it */ + const char *hostname = *argv; + + if (next_arg (&argc, &argv) == 0) + g_print ("Warning: ignoring extra garbage after '%s' hostname\n", hostname); + + nmc->should_wait = TRUE; + nmc->get_client (nmc); /* create NMClient */ + nm_client_save_hostname_async (nmc->client, hostname, NULL, save_hostname_cb, nmc); + } + } + else if (matches (*argv, "permissions") == 0) { + if (nmc_arg_is_help (*(argv+1))) { + usage_general_permissions (); + goto finish; + } + if (!nmc_terse_option_check (nmc->print_output, nmc->required_fields, &error)) { + g_string_printf (nmc->return_text, _("Error: %s."), error->message); + nmc->return_value = NMC_RESULT_ERROR_USER_INPUT; + goto finish; + } + show_nm_permissions (nmc); + } + else if (matches (*argv, "logging") == 0) { + if (nmc_arg_is_help (*(argv+1))) { + usage_general_logging (); + goto finish; + } + if (next_arg (&argc, &argv) != 0) { + /* no arguments -> get logging level and domains */ + if (!nmc_terse_option_check (nmc->print_output, nmc->required_fields, &error)) { + g_string_printf (nmc->return_text, _("Error: %s."), error->message); + nmc->return_value = NMC_RESULT_ERROR_USER_INPUT; + goto finish; + } + show_general_logging (nmc); + } else { + /* arguments provided -> set logging level and domains */ + const char *level = NULL; + const char *domains = NULL; + nmc_arg_t exp_args[] = { {"level", TRUE, &level, TRUE}, + {"domains", TRUE, &domains, TRUE}, + {NULL} }; + + if (!nmc_parse_args (exp_args, FALSE, &argc, &argv, &error)) { + g_string_assign (nmc->return_text, error->message); + nmc->return_value = error->code; + goto finish; + } + + nmc->get_client (nmc); /* create NMClient */ + nm_client_set_logging (nmc->client, level, domains, &error); + if (error) { + g_string_printf (nmc->return_text, _("Error: failed to set logging: %s"), + error->message); + nmc->return_value = NMC_RESULT_ERROR_UNKNOWN; + goto finish; + } + } + } + else { + usage_general (); + g_string_printf (nmc->return_text, _("Error: 'general' command '%s' is not valid."), *argv); + nmc->return_value = NMC_RESULT_ERROR_USER_INPUT; + } + } + +finish: + if (error) + g_error_free (error); + return nmc->return_value; +} + +static gboolean +nmc_switch_show (NmCli *nmc, const char *switch_name, const char *header) +{ + g_return_val_if_fail (nmc != NULL, FALSE); + g_return_val_if_fail (switch_name != NULL, FALSE); + + if (nmc->required_fields && strcasecmp (nmc->required_fields, switch_name) != 0) { + g_string_printf (nmc->return_text, _("Error: '--fields' value '%s' is not valid here (allowed field: %s)"), + nmc->required_fields, switch_name); + nmc->return_value = NMC_RESULT_ERROR_USER_INPUT; + return FALSE; + } + if (nmc->print_output == NMC_PRINT_NORMAL) + nmc->print_output = NMC_PRINT_TERSE; + + if (!nmc->required_fields) + nmc->required_fields = g_strdup (switch_name); + return show_nm_status (nmc, header, NULL); +} + +static gboolean +nmc_switch_parse_on_off (NmCli *nmc, const char *arg1, const char *arg2, gboolean *res) +{ + g_return_val_if_fail (nmc != NULL, FALSE); + g_return_val_if_fail (arg1 && arg2, FALSE); + g_return_val_if_fail (res != NULL, FALSE); + + if (!strcmp (arg2, "on")) + *res = TRUE; + else if (!strcmp (arg2, "off")) + *res = FALSE; + else { + g_string_printf (nmc->return_text, _("Error: invalid '%s' argument: '%s' (use on/off)."), arg1, arg2); + nmc->return_value = NMC_RESULT_ERROR_USER_INPUT; + return FALSE; + } + + return TRUE; +} + +static gboolean +show_networking_connectivity (NmCli *nmc) +{ + return nmc_switch_show (nmc, NMC_FIELDS_NM_CONNECTIVITY, _("Connectivity")); +} + +/* + * Entry point function for 'nmcli networking' + */ +NMCResultCode +do_networking (NmCli *nmc, int argc, char **argv) +{ + gboolean enable_flag; + + /* Register polkit agent */ + nmc_start_polkit_agent_start_try (nmc); + + if (argc == 0) + nmc_switch_show (nmc, NMC_FIELDS_NM_NETWORKING, _("Networking")); + else if (argc > 0) { + if (nmc_arg_is_help (*argv)) { + usage_networking (); + } else if (matches (*argv, "connectivity") == 0) { + if (nmc_arg_is_help (*(argv+1))) { + usage_networking_connectivity (); + goto finish; + } + if (next_arg (&argc, &argv) != 0) { + /* no arguments -> get current state */ + show_networking_connectivity (nmc); + } else if (matches (*argv, "check") == 0) { + GError *error = NULL; + + nmc->get_client (nmc); /* create NMClient */ + nm_client_check_connectivity (nmc->client, NULL, &error); + if (error) { + g_string_printf (nmc->return_text, _("Error: %s."), error->message); + nmc->return_value = NMC_RESULT_ERROR_UNKNOWN; + g_clear_error (&error); + } else + show_networking_connectivity (nmc); + } else { + usage_networking (); + g_string_printf (nmc->return_text, _("Error: 'networking connectivity' command '%s' is not valid."), *argv); + nmc->return_value = NMC_RESULT_ERROR_USER_INPUT; + } + } else if (nmc_switch_parse_on_off (nmc, *(argv-1), *argv, &enable_flag)) { + if (nmc_arg_is_help (*(argv+1))) { + if (enable_flag) + usage_networking_on (); + else + usage_networking_off (); + goto finish; + } + + nmc->get_client (nmc); /* create NMClient */ + nm_client_networking_set_enabled (nmc->client, enable_flag, NULL); + } else { + usage_networking (); + g_string_printf (nmc->return_text, _("Error: 'networking' command '%s' is not valid."), *argv); + nmc->return_value = NMC_RESULT_ERROR_USER_INPUT; + } + } + +finish: + quit (); + return nmc->return_value; +} + +/* + * Entry point function for radio switch commands 'nmcli radio' + */ +NMCResultCode +do_radio (NmCli *nmc, int argc, char **argv) +{ + GError *error = NULL; + gboolean enable_flag; + + /* Register polkit agent */ + nmc_start_polkit_agent_start_try (nmc); + + if (argc == 0) { + if (!nmc_terse_option_check (nmc->print_output, nmc->required_fields, &error)) { + g_string_printf (nmc->return_text, _("Error: %s."), error->message); + nmc->return_value = NMC_RESULT_ERROR_USER_INPUT; + g_error_free (error); + goto finish; + } + show_nm_status (nmc, _("Radio switches"), NMC_FIELDS_NM_STATUS_RADIO); + } + + if (argc > 0) { + if (nmc_arg_is_help (*argv)) { + usage_radio (); + } + else if (matches (*argv, "all") == 0) { + if (nmc_arg_is_help (*(argv+1))) { + usage_radio_all (); + goto finish; + } + if (next_arg (&argc, &argv) != 0) { + /* no argument, show all radio switches */ + if (!nmc_terse_option_check (nmc->print_output, nmc->required_fields, &error)) { + g_string_printf (nmc->return_text, _("Error: %s."), error->message); + nmc->return_value = NMC_RESULT_ERROR_USER_INPUT; + g_error_free (error); + goto finish; + } + show_nm_status (nmc, _("Radio switches"), NMC_FIELDS_NM_STATUS_RADIO); + } else { + if (!nmc_switch_parse_on_off (nmc, *(argv-1), *argv, &enable_flag)) + goto finish; + + nmc->get_client (nmc); /* create NMClient */ + nm_client_wireless_set_enabled (nmc->client, enable_flag); + nm_client_wimax_set_enabled (nmc->client, enable_flag); + nm_client_wwan_set_enabled (nmc->client, enable_flag); + } + } + else if (matches (*argv, "wifi") == 0) { + if (nmc_arg_is_help (*(argv+1))) { + usage_radio_wifi (); + goto finish; + } + if (next_arg (&argc, &argv) != 0) { + /* no argument, show current WiFi state */ + nmc_switch_show (nmc, NMC_FIELDS_NM_WIFI, _("Wi-Fi radio switch")); + } else { + if (!nmc_switch_parse_on_off (nmc, *(argv-1), *argv, &enable_flag)) + goto finish; + + nmc->get_client (nmc); /* create NMClient */ + nm_client_wireless_set_enabled (nmc->client, enable_flag); + } + } + else if (matches (*argv, "wwan") == 0) { + if (nmc_arg_is_help (*(argv+1))) { + usage_radio_wwan (); + goto finish; + } + if (next_arg (&argc, &argv) != 0) { + /* no argument, show current WWAN (mobile broadband) state */ + nmc_switch_show (nmc, NMC_FIELDS_NM_WWAN, _("WWAN radio switch")); + } else { + if (!nmc_switch_parse_on_off (nmc, *(argv-1), *argv, &enable_flag)) + goto finish; + + nmc->get_client (nmc); /* create NMClient */ + nm_client_wwan_set_enabled (nmc->client, enable_flag); + } + } +#if WITH_WIMAX + else if (matches (*argv, "wimax") == 0) { + if (nmc_arg_is_help (*(argv+1))) { + usage_radio_wimax (); + goto finish; + } + if (next_arg (&argc, &argv) != 0) { + /* no argument, show current WiMAX state */ + nmc_switch_show (nmc, NMC_FIELDS_NM_WIMAX, _("WiMAX radio switch")); + } else { + if (!nmc_switch_parse_on_off (nmc, *(argv-1), *argv, &enable_flag)) + goto finish; + + nmc->get_client (nmc); /* create NMClient */ + nm_client_wimax_set_enabled (nmc->client, enable_flag); + } + } +#endif + else { + usage_radio (); + g_string_printf (nmc->return_text, _("Error: 'radio' command '%s' is not valid."), *argv); + nmc->return_value = NMC_RESULT_ERROR_USER_INPUT; + } + } + +finish: + quit (); + return nmc->return_value; +} + diff --git a/clients/cli/general.h b/clients/cli/general.h new file mode 100644 index 00000000..7e8c21f9 --- /dev/null +++ b/clients/cli/general.h @@ -0,0 +1,29 @@ +/* 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. + */ + +#ifndef NMC_GENERAL_H +#define NMC_GENERAL_H + +#include "nmcli.h" + +NMCResultCode do_general (NmCli *nmc, int argc, char **argv); +NMCResultCode do_networking (NmCli *nmc, int argc, char **argv); +NMCResultCode do_radio (NmCli *nmc, int argc, char **argv); + +#endif /* NMC_GENERAL_H */ diff --git a/clients/cli/nmcli-completion b/clients/cli/nmcli-completion new file mode 100644 index 00000000..73028bdf --- /dev/null +++ b/clients/cli/nmcli-completion @@ -0,0 +1,1287 @@ +# nmcli(1) completion -*- shell-script -*- +# Originally based on +# https://github.com/GArik/bash-completion/blob/master/completions/nmcli + +_nmcli_list() +{ + COMPREPLY=( $( compgen -W '$1' -- $cur ) ) +} + +_nmcli_list_nl() +{ + local IFS=$'\n' + COMPREPLY=( $( compgen -W '$1' -- $cur ) ) + + # Now escape special characters (spaces, single and double quotes), + # so that the argument is really regarded a single argument by bash. + # See http://stackoverflow.com/questions/1146098/properly-handling-spaces-and-quotes-in-bash-completion + local escaped_single_quote="'\''" + local i=0 + local entry + for entry in ${COMPREPLY[*]} + do + if [[ "${cur:0:1}" == "'" ]]; then + # started with single quote, escaping only other single quotes + # [']bla'bla"bla\bla bla --> [']bla'\''bla"bla\bla bla + COMPREPLY[$i]="${entry//\'/${escaped_single_quote}}" + elif [[ "${cur:0:1}" == '"' ]]; then + # started with double quote, escaping all double quotes and all backslashes + # ["]bla'bla"bla\bla bla --> ["]bla'bla\"bla\\bla bla + entry="${entry//\\/\\\\}" + entry="${entry//\"/\\\"}" + COMPREPLY[$i]="$entry" + else + # no quotes in front, escaping _everything_ + # [ ]bla'bla"bla\bla bla --> [ ]bla\'bla\"bla\\bla\ bla + entry="${entry//\\/\\\\}" + entry="${entry//\'/\'}" + entry="${entry//\"/\\\"}" + entry="${entry// /\\ }" + COMPREPLY[$i]="$entry" + fi + (( i++ )) + done +} + +_nmcli_con_show() +{ + nmcli -t -f "$1" connection show $2 2> /dev/null +} + +_nmcli_wifi_list() +{ + nmcli -t -f "$1" device wifi list 2>/dev/null +} + +_nmcli_dev_status() +{ + nmcli -t -f "$1" device status 2>/dev/null +} + +_nmcli_array_has_value() { + # expects the name of an array as first parameter and + # returns true if if one of the remaining arguments is + # contained in the array ${$1[@]} + eval "local ARRAY=(\"\${$1[@]}\")" + local arg a + shift + for arg; do + for a in "${ARRAY[@]}"; do + if [[ "$a" = "$arg" ]]; then + return 0 + fi + done + done + return 1 +} + +_nmcli_array_delete_at() +{ + eval "local ARRAY=(\"\${$1[@]}\")" + local i + local tmp=() + local lower=$2 + local upper=${3:-$lower} + + # for some reason the following fails. So this clumsy workaround... + # A=(a "") + # echo " >> ${#A[@]}" + # >> 2 + # A=("${A[@]:1}") + # echo " >> ${#A[@]}" + # >> 0 + # ... seriously??? + + for i in "${!ARRAY[@]}"; do + if [[ "$i" -lt "$2" || "$i" -gt "${3-$2}" ]]; then + tmp=("${tmp[@]}" "${ARRAY[$i]}") + fi + done + eval "$1=(\"\${tmp[@]}\")" +} + +_nmcli_compl_match_option() +{ + local S="$1" + local V + shift + if [[ "${S:0:2}" == "--" ]]; then + S="${S:2}" + elif [[ "${S:0:1}" == "-" ]]; then + S="${S:1}" + fi + for V; do + case "$V" in + "$S"*) + printf "%s" "$V" + return 0 + ;; + esac + done + return 1 +} + +# OPTIONS appear first at the command line (before the OBJECT). +# This iterates over the argument list and tries to complete +# the options. If there are options that are to be completed, +# zero is returned and completion will be performed. +# Otherwise it will remove all the option parameters from the ${words[@]} +# array and return with zero (so that completion of OBJECT can continue). +_nmcli_compl_OPTIONS() +{ + local i W + + for (( ; ; )); do + if [[ "${#words[@]}" -le 1 ]]; then + return 1 + fi + W="$(_nmcli_compl_match_option "${words[0]}" "${LONG_OPTIONS[@]}")" + if [[ $? != 0 ]]; then + return 2 + fi + + # remove the options already seen. + for i in ${!LONG_OPTIONS[@]}; do + if [[ "${LONG_OPTIONS[$i]}" == "$W" ]]; then + _nmcli_array_delete_at LONG_OPTIONS $i + break + fi + done + + if [[ "$HELP_ONLY_AS_FIRST" == '1' ]]; then + for i in ${!LONG_OPTIONS[@]}; do + if [[ "${LONG_OPTIONS[$i]}" == "help" ]]; then + _nmcli_array_delete_at LONG_OPTIONS $i + break + fi + done + fi + + case "$W" in + terse) + _nmcli_array_delete_at words 0 + ;; + pretty) + _nmcli_array_delete_at words 0 + ;; + nocheck) + _nmcli_array_delete_at words 0 + ;; + ask) + _nmcli_array_delete_at words 0 + ;; + show-secrets) + _nmcli_array_delete_at words 0 + ;; + active) + _nmcli_array_delete_at words 0 + ;; + version) + _nmcli_array_delete_at words 0 + ;; + help) + _nmcli_array_delete_at words 0 + if [[ "$HELP_ONLY_AS_FIRST" == 1 ]]; then + HELP_ONLY_AS_FIRST=0 + return 0 + fi + HELP_ONLY_AS_FIRST=0 + ;; + temporary) + _nmcli_array_delete_at words 0 + ;; + mode) + if [[ "${#words[@]}" -eq 2 ]]; then + _nmcli_list "tabular multiline" + return 0 + fi + _nmcli_array_delete_at words 0 1 + ;; + fields) + if [[ "${#words[@]}" -eq 2 ]]; then + _nmcli_list "all common + NAME UUID TYPE TIMESTAMP TIMESTAMP-REAL AUTOCONNECT READONLY DBUS-PATH ACTIVE DEVICE STATE ACTIVE-PATH + connection 802-3-ethernet 802-1x 802-11-wireless 802-11-wireless-security ipv4 ipv6 serial ppp pppoe gsm cdma bluetooth 802-11-olpc-mesh vpn wimax infiniband bond vlan adsl bridge bridge-port team team-port dcb + GENERAL IP4 DHCP4 IP6 DHCP6 VPN + profile active" + return 0 + fi + _nmcli_array_delete_at words 0 1 + ;; + escape) + if [[ "${#words[@]}" -eq 2 ]]; then + _nmcli_list "no yes" + return 0 + fi + _nmcli_array_delete_at words 0 1 + ;; + wait) + if [[ "${#words[@]}" -eq 2 ]]; then + _nmcli_list "" + return 0 + fi + _nmcli_array_delete_at words 0 1 + ;; + *) + # something unexpected. We are finished with parsing the OPTIONS. + return 2 + ;; + esac + done +} + +# after the OPTIONS, the OBJECT, the COMMAND and possible the COMMAND_CONNECTION, the syntax for nmcli +# expects several options with parameters. This function can parse them and remove them from the words array. +_nmcli_compl_ARGS() +{ + local OPTIONS_ALL N_REMOVE_WORDS REMOVE_OPTIONS OPTIONS_HAS_MANDATORY i + OPTIONS_ALL=("${OPTIONS[@]}") + OPTIONS_UNKNOWN_OPTION= + + OPTIONS_HAS_MANDATORY=0 + if [[ "${#OPTIONS_MANDATORY[@]}" -ge 1 ]]; then + OPTIONS_HAS_MANDATORY=1 + fi + + for (( ; ; )); do + if [[ "${#words[@]}" -le 1 ]]; then + # we have no more words left... + if [[ ${#OPTIONS[@]} -eq 0 ]]; then + return 1; + fi + if [[ "$COMMAND_ARGS_WAIT_OPTIONS" -ne 1 ]]; then + _nmcli_list "$(echo "${OPTIONS[@]}")" + return 0 + fi + COMMAND_ARGS_WAIT_OPTIONS=0 + return 1 + fi + if ! _nmcli_array_has_value OPTIONS_ALL "${words[0]}"; then + # This is an entirely unknown option. + OPTIONS_UNKNOWN_OPTION="?${words[0]}" + return 1 + fi + if [[ "$OPTIONS_HAS_MANDATORY" -eq 1 && "${#OPTIONS_MANDATORY[@]}" -eq 0 ]]; then + # we had some mandatory options, but they are all satisfied... stop here... + # This means, that we can continue with more additional options from the NEXT_GROUP. + return 1 + fi + + N_REMOVE_WORDS=2 + REMOVE_OPTIONS=("${words[0]}") + case "${words[0]}" in + level) + if [[ "${#words[@]}" -eq 2 ]]; then + _nmcli_list "ERR WARN INFO DEBUG TRACE" + return 0 + fi + ;; + domains) + if [[ "${#words[@]}" -eq 2 ]]; then + local OPTIONS_DOM=(ALL DEFAULT PLATFORM RFKILL ETHER WIFI BT MB DHCP4 DHCP6 PPP WIFI_SCAN IP4 IP6 AUTOIP4 DNS VPN SHARING SUPPLICANT AGENTS SETTINGS SUSPEND CORE DEVICE OLPC WIMAX INFINIBAND FIREWALL ADSL BOND VLAN BRIDGE DBUS_PROPS TEAM CONCHECK DCB DISPATCH) + if [[ "${words[1]}" != "" ]]; then + + # split the comma separaeted domain string into its parts LOGD + local oIFS="$IFS" + IFS="," + local LOGD=($(printf '%s' "${words[1]}" | sed 's/\(^\|,\)/,#/g')) + IFS="$oIFS" + unset oIFS + + local LOGDLAST LOGDLAST_IS_OPTION LOGDI i + # first we iterate over all present domains and remove them from OPTIONS_DOM + for LOGDI in ${LOGD[@]}; do + LOGDI="${LOGDI:1}" + LOGDLAST="$LOGDI" + LOGDLAST_IS_OPTION=0 + for i in ${!OPTIONS_DOM[*]}; do + if [[ "${OPTIONS_DOM[$i]}" = "$LOGDI" ]]; then + LOGDLAST_IS_OPTION=1 + unset OPTIONS_DOM[$i] + fi + done + done + + local OPTIONS_DOM2=() + if [[ "$LOGDLAST" = "" ]]; then + # we have a word that ends with ','. Just append all remaining options. + for i in ${!OPTIONS_DOM[*]}; do + OPTIONS_DOM2[${#OPTIONS_DOM2[@]}]="${words[1]}${OPTIONS_DOM[$i]}" + done + else + # if the last option is not "" we keep only those option with the same prefix + # as the last domain (LOGDLAST) + for i in ${!OPTIONS_DOM[*]}; do + if [[ "${OPTIONS_DOM[$i]:0:${#LOGDLAST}}" == "$LOGDLAST" ]]; then + # modify the option with the present prefix + OPTIONS_DOM2[${#OPTIONS_DOM2[@]}]="${words[1]}${OPTIONS_DOM[$i]:${#LOGDLAST}}" + fi + done + + if [[ $LOGDLAST_IS_OPTION -eq 1 ]]; then + # if the last logd itself was a valid iption, ${words[1]} is itself a valid match + OPTIONS_DOM2[${#OPTIONS_DOM2[@]}]="${words[1]}" + + # also, add all remaining options by comma separated to the word. + for i in ${!OPTIONS_DOM[*]}; do + OPTIONS_DOM2[${#OPTIONS_DOM2[@]}]="${words[1]},${OPTIONS_DOM[$i]}" + done + fi + if [[ ${#OPTIONS_DOM2[@]} -eq 1 ]]; then + for i in ${!OPTIONS_DOM[*]}; do + if [[ "$LOGDLAST" != "${OPTIONS_DOM[$i]:0:${#LOGDLAST}}" ]]; then + OPTIONS_DOM2[${#OPTIONS_DOM2[@]}]="${OPTIONS_DOM2[0]},${OPTIONS_DOM[$i]}" + fi + done + fi + + fi + OPTIONS_DOM=(${OPTIONS_DOM2[@]}) + fi + + _nmcli_list "$(echo "${OPTIONS_DOM[@]}")" + return 0 + fi + ;; + type) + if [[ "$OPTIONS_TYPE" != "" ]]; then + return 1 + fi + if [[ "${#words[@]}" -eq 2 ]]; then + if [[ "${words[1]:0:1}" = "8" ]]; then + # usually we don't want to show the 802-x types (because the shorter aliases are more + # user friendly. Only complete them, if the current word already starts with an "8". + _nmcli_list "802-3-ethernet 802-11-wireless 802-11-olpc-mesh" + else + _nmcli_list "ethernet wifi wimax gsm cdma infiniband bluetooth vpn olpc-mesh vlan bond bond-slave bridge bridge-slave team team-slave pppoe" + fi + return 0 + fi + OPTIONS_TYPE="${words[1]}" + + if [[ "x$OPTIONS_MANDATORY_IFNAME" != x ]]; then + # the ifname is not a mandatory option for a few connection types... + # Check, if we have such a 'type' and remove the 'ifname' from the list + # of mandatory options. + case "$OPTIONS_TYPE" in + vl|vla|vlan| \ + bond| \ + team| \ + bridge) + for i in ${!OPTIONS_MANDATORY[*]}; do + if [[ "${OPTIONS_MANDATORY[$i]}" = "ifname" ]]; then + unset OPTIONS_MANDATORY[$i] + fi + done + ;; + *) + ;; + + esac + OPTIONS_MANDATORY_IFNAME= + fi + ;; + master) + if [[ "${#words[@]}" -eq 2 ]]; then + if [[ "${words[1]}" = "" ]]; then + _nmcli_list_nl "$(_nmcli_dev_status DEVICE)" + else + _nmcli_list_nl "$(printf "%s\n%s\n%s" "$(_nmcli_dev_status DEVICE)" "$(_nmcli_con_show UUID)")" + fi + return 0 + fi + ;; + dev) + if [[ "${#words[@]}" -eq 2 ]]; then + if [[ "${words[1]}" = "" ]]; then + _nmcli_list_nl "$(_nmcli_dev_status DEVICE)" + else + _nmcli_list_nl "$(printf "%s\n%s\n%s" "$(_nmcli_dev_status DEVICE)" "$(_nmcli_wifi_list BSSID)" "$(_nmcli_con_show UUID)")" + fi + return 0 + fi + ;; + primary| \ + ifname) + if [[ "${#words[@]}" -eq 2 ]]; then + _nmcli_list_nl "$(_nmcli_dev_status DEVICE)" + return 0 + fi + ;; + mode) + if [[ "${#words[@]}" -eq 2 ]]; then + case "$OPTIONS_TYPE" in + "wifi") + _nmcli_list "infrastructure ap adhoc" + ;; + "bond"| \ + *) + _nmcli_list "balance-rr active-backup balance-xor broadcast 802.3ad balance-tlb balance-alb" + esac + return 0 + fi + ;; + transport-mode) + if [[ "${#words[@]}" -eq 2 ]]; then + _nmcli_list "datagram connected" + return 0 + fi + ;; + vpn-type) + if [[ "${#words[@]}" -eq 2 ]]; then + _nmcli_list "vpnc openvpn pptp openconnect openswan libreswan ssh l2tp iodine" + return 0 + fi + ;; + lacp-rate) + if [[ "${#words[@]}" -eq 2 ]]; then + _nmcli_list "slow fast" + return 0 + fi + ;; + bt-type) + if [[ "${#words[@]}" -eq 2 ]]; then + _nmcli_list "panu dun-gsm dun-cdma" + return 0 + fi + ;; + wep-key-type) + if [[ "${#words[@]}" -eq 2 ]]; then + _nmcli_list "key phrase" + return 0 + fi + ;; + autoconnect| \ + stp| \ + hairpin| \ + save| \ + private) + if [[ "${#words[@]}" -eq 2 ]]; then + _nmcli_list "yes no" + return 0 + fi + ;; + config) + if [[ "${#words[@]}" -eq 2 ]]; then + compopt -o default + COMPREPLY=() + return 0 + fi + ;; + ip4| \ + ip6| \ + gw4| \ + gw6| \ + priority| \ + forward-delay| \ + hello-time| \ + max-age| \ + ageing-time| \ + nsp| \ + path-cost| \ + name| \ + mtu| \ + cloned-mac| \ + addr| \ + parent| \ + miimon| \ + arp-interval| \ + arp-ip-target| \ + downdelay| \ + updelay| \ + p-key| \ + mac| \ + id| \ + flags| \ + ingress| \ + dhcp-anycast| \ + channel| \ + egress| \ + apn| \ + con-name| \ + user| \ + username| \ + service| \ + password| \ + passwd-file) + if [[ "${#words[@]}" -eq 2 ]]; then + return 0 + fi + ;; + ssid) + if [[ "${#words[@]}" -eq 2 ]]; then + _nmcli_list_nl "$(_nmcli_wifi_list SSID)" + return 0 + fi + ;; + ap| \ + bssid) + if [[ "${#words[@]}" -eq 2 ]]; then + _nmcli_list_nl "$(_nmcli_wifi_list BSSID)" + return 0 + fi + ;; + *) + return 1 + ;; + esac + + + if [[ "${#OPTIONS_NEXT_GROUP[@]}" -gt 0 ]]; then + if _nmcli_array_has_value OPTIONS_NEXT_GROUP "${words[0]}"; then + # the current value is from the next group... + # We back off, because the current group is complete. + return 1 + fi + fi + + _nmcli_array_delete_at words 0 $((N_REMOVE_WORDS-1)) + # remove the options already seen. + for i in ${!OPTIONS[*]}; do + if [[ "${OPTIONS[$i]}" = "${REMOVE_OPTIONS[0]}" || "${OPTIONS[$i]}" = "${REMOVE_OPTIONS[1]}" ]]; then + unset OPTIONS[$i] + fi + done + for i in ${!OPTIONS_MANDATORY[*]}; do + if [[ "${OPTIONS_MANDATORY[$i]}" = "${REMOVE_OPTIONS[0]}" || "${OPTIONS_MANDATORY[$i]}" = "${REMOVE_OPTIONS[1]}" ]]; then + unset OPTIONS_MANDATORY[$i] + fi + done + done +} + +# some commands expect a connection as parameter. This connection can either be given +# as id|uuid|path|apath. Parse that connection parameter. +_nmcli_compl_ARGS_CONNECTION() +{ + if ! _nmcli_array_has_value OPTIONS "${words[0]}"; then + COMMAND_CONNECTION_TYPE= + COMMAND_CONNECTION_ID="${words[0]}" + _nmcli_array_delete_at words 0 + return 1 + fi + COMMAND_CONNECTION_TYPE="${words[0]}" + COMMAND_CONNECTION_ID="${words[1]}" + local CON_TYPE= + if [[ "x$COMMAND_CONNECTION_ACTIVE" != x ]]; then + CON_TYPE=--active + fi + case "${words[0]}" in + id) + if [[ ${#words[@]} -le 2 ]]; then + _nmcli_list_nl "$(_nmcli_con_show NAME $CON_TYPE)" + return 0 + fi + _nmcli_array_delete_at words 0 1 + ;; + uuid) + if [[ ${#words[@]} -le 2 ]]; then + _nmcli_list_nl "$(_nmcli_con_show UUID $CON_TYPE)" + return 0 + fi + _nmcli_array_delete_at words 0 1 + ;; + path) + if [[ ${#words[@]} -le 2 ]]; then + _nmcli_list_nl "$(_nmcli_con_show DBUS-PATH $CON_TYPE)" + return 0 + fi + _nmcli_array_delete_at words 0 1 + ;; + apath) + if [[ ${#words[@]} -le 2 ]]; then + _nmcli_list_nl "$(_nmcli_con_show ACTIVE-PATH --active)" + return 0 + fi + _nmcli_array_delete_at words 0 1 + ;; + ifname) + if [[ ${#words[@]} -le 2 ]]; then + _nmcli_list_nl "$(_nmcli_dev_status DEVICE)" + return 0 + fi + _nmcli_array_delete_at words 0 1 + ;; + *) + COMMAND_CONNECTION_TYPE= + COMMAND_CONNECTION_ID="${words[0]}" + _nmcli_array_delete_at words 0 + ;; + esac + return 1 +} + +_nmcli_compl_COMMAND() { + local command="$1" + shift + local V=("$@") + local H= + if [[ "${command[0]:0:1}" != '-' ]]; then + H=help + elif [[ "${command[0]:1:1}" == '-' || "${command[0]}" == "-" ]]; then + H=--help + else + H=-help + fi + if [[ "x$COMPL_COMMAND_NO_HELP" == x ]]; then + V=("${V[@]}" "$H") + fi + _nmcli_list "${V[*]}" +} + +_nmcli_compl_COMMAND_nl() { + local command="$1" + local a="$2" + shift + shift + local V=("$@") + local H= + if [[ "${command[0]:0:1}" != '-' ]]; then + V=("${V[@]/#/--}") + H=help + elif [[ "${command[0]:1:1}" == '-' || "${command[0]}" == "-" ]]; then + V=("${V[@]/#/--}") + H=--help + else + V=("${V[@]/#/-}") + H=-help + fi + if [[ "x$COMPL_COMMAND_NO_HELP" == x ]]; then + V=("${V[@]}" "$H") + fi + local IFS=$'\n' + V="${V[*]}" + _nmcli_list_nl "$(printf "%s%s\n%s" "" "$V" "$a")" +} + +_nmcli() +{ + local cur prev words cword i + _init_completion || return + + # we don't care about any arguments after the current cursor position + # because we only parse from left to right. So, if there are some arguments + # right of the cursor, just ignore them. Also don't care about ${words[0]}. + _nmcli_array_delete_at words $((cword+1)) ${#words[@]} + _nmcli_array_delete_at words 0 + + # _init_completion returns the words with all the quotes and escaping + # characters. We don't care about them, drop them at first. + for i in ${!words[@]}; do + words[i]="$(printf '%s' "${words[i]}" | xargs printf '%s\n' 2>/dev/null || true)" + done + + # In case the cursor is not at the end of the line, + # $cur consists of spaces that we want do remove. + # For example: `nmcli connection modify id <TAB> lo` + if [[ "$cur" =~ ^[[:space:]]+ ]]; then + cur='' + fi + + local OPTIONS_UNKNOWN_OPTION OPTIONS_TYPE OPTIONS_TYPED OPTIONS OPTIONS_MANDATORY COMMAND_ARGS_WAIT_OPTIONS OPTIONS_IP OPTIONS_MANDATORY OPTIONS_NEXT_GROUP + local COMMAND_CONNECTION_TYPE COMMAND_CONNECTION_ID OPTIONS_MANDATORY_IFNAME HELP_ONLY_AS_FIRST + local COMMAND_CONNECTION_ACTIVE="" + + HELP_ONLY_AS_FIRST= + local LONG_OPTIONS=(terse pretty mode fields escape nocheck ask wait version help) + _nmcli_compl_OPTIONS + i=$? + + if [[ "$HELP_ONLY_AS_FIRST" == '0' ]]; then + # got a --help. No more completion. + return 0 + fi + + case $i in + 0) + return 0 + ;; + 1) + # we show for completion either the (remaining) OPTIONS + # (if the current word starts with a dash) or the OBJECT list + # otherwise. + if [[ "${words[0]:0:1}" != '-' ]]; then + OPTIONS=(help general networking radio connection device agent) + elif [[ "${words[0]:1:1}" == '-' || "${words[0]}" == "-" ]]; then + OPTIONS=("${LONG_OPTIONS[@]/#/--}") + else + OPTIONS=("${LONG_OPTIONS[@]/#/-}") + fi + _nmcli_list "${OPTIONS[*]}" + return 0 + ;; + esac + + local command="${words[1]}" + case "${words[0]}" in + h|he|hel|help) + ;; + g|ge|gen|gene|gener|genera|general) + if [[ ${#words[@]} -eq 2 ]]; then + _nmcli_compl_COMMAND "$command" status permissions logging hostname + elif [[ ${#words[@]} -gt 2 ]]; then + case "$command" in + ho|hos|host|hostn|hostna|hostnam|hostname) + if [[ ${#words[@]} -eq 3 ]]; then + _nmcli_compl_COMMAND_nl "${words[2]}" \ + "$(printf '%s\n%s\n%s\n' \ + "$(nmcli general hostname 2>/dev/null)" \ + "$(cat /etc/hostname 2>/dev/null)" \ + "$(hostnamectl status 2>/dev/null | sed -n '1s/^.\+hostname: \(.\+\)$/\1/p')" \ + "$HOSTNAME")" + fi + ;; + l|lo|log|logg|loggi|loggin|logging) + if [[ ${#words[@]} -eq 3 ]]; then + _nmcli_compl_COMMAND "${words[2]}" level domains + else + _nmcli_array_delete_at words 0 1 + OPTIONS=(level domains) + _nmcli_compl_ARGS + fi + ;; + s|st|sta|stat|statu|status| \ + p|pe|per|perm|permi|permis|permiss|permissi|permissio|permission|permissions) + if [[ ${#words[@]} -eq 3 ]]; then + _nmcli_compl_COMMAND "${words[2]}" + fi + ;; + esac + fi + ;; + n|ne|net|netw|netwo|networ|network|networki|networkin|networking) + if [[ ${#words[@]} -eq 2 ]]; then + _nmcli_compl_COMMAND "$command" on off connectivity + elif [[ ${#words[@]} -eq 3 ]]; then + case "$command" in + c|co|con|conn|conne|connec|connect|connecti|connectiv|connectivi|connectivit|connectivity) + _nmcli_compl_COMMAND "${words[2]}" "check" + ;; + esac + fi + ;; + r|ra|rad|radi|radio) + if [[ ${#words[@]} -eq 2 ]]; then + _nmcli_compl_COMMAND "$command" all wifi wwan wimax + elif [[ ${#words[@]} -eq 3 ]]; then + case "$command" in + a|al|all | w|wi|wif|wifi | ww|wwa|wwan | wim|wima|wimax) + _nmcli_compl_COMMAND "${words[2]}" "on off" + ;; + esac + fi + ;; + c|co|con|conn|conne|connec|connect|connecti|connectio|connection) + if [[ ${#words[@]} -eq 2 ]]; then + _nmcli_compl_COMMAND "$command" show up down add modify edit delete reload load + elif [[ ${#words[@]} -gt 2 ]]; then + case "$command" in + s|sh|sho|show) + if [[ ${#words[@]} -eq 3 ]]; then + _nmcli_compl_COMMAND_nl "${words[2]}" "$(printf "id\nuuid\npath\napath\n%s" "$(_nmcli_con_show NAME)")" active show-secrets + elif [[ ${#words[@]} -gt 3 ]]; then + _nmcli_array_delete_at words 0 1 + + LONG_OPTIONS=(help active show-secrets) + HELP_ONLY_AS_FIRST=1 + _nmcli_compl_OPTIONS + i=$? + + if ! _nmcli_array_has_value LONG_OPTIONS active; then + COMMAND_CONNECTION_ACTIVE=1 + fi + + case $i in + 0) + return 0 + ;; + 1) + if [[ "$HELP_ONLY_AS_FIRST" == 1 ]]; then + if [[ "x$COMMAND_CONNECTION_ACTIVE" = x ]]; then + _nmcli_compl_COMMAND_nl "${words[2]}" "$(printf "id\nuuid\npath\napath\n%s" "$(_nmcli_con_show NAME)")" "${LONG_OPTIONS[@]}" + + else + _nmcli_compl_COMMAND_nl "${words[2]}" "$(printf "id\nuuid\npath\napath\n%s" "$(_nmcli_con_show NAME --active)")" "${LONG_OPTIONS[@]}" + fi + fi + return 0 + ;; + esac + + OPTIONS=(id uuid path apath) + while [[ ${#words[@]} -gt 0 ]]; do + _nmcli_compl_ARGS_CONNECTION && return 0 + done + if [[ "x$COMMAND_CONNECTION_ACTIVE" = x ]]; then + _nmcli_list_nl "$(printf "id\nuuid\npath\napath\n%s" "$(_nmcli_con_show NAME)")" + else + _nmcli_list_nl "$(printf "id\nuuid\npath\napath\n%s" "$(_nmcli_con_show NAME --active)")" + fi + fi + ;; + u|up) + if [[ ${#words[@]} -eq 3 ]]; then + _nmcli_compl_COMMAND_nl "${words[2]}" "$(printf "ifname\nid\nuuid\npath\n%s" "$(_nmcli_con_show NAME)")" + elif [[ ${#words[@]} -gt 3 ]]; then + _nmcli_array_delete_at words 0 1 + + LONG_OPTIONS=(help) + HELP_ONLY_AS_FIRST=1 + _nmcli_compl_OPTIONS + + case $? in + 0) + return 0 + ;; + 1) + if [[ "$HELP_ONLY_AS_FIRST" == 1 ]]; then + _nmcli_compl_COMMAND_nl "${words[2]}" "$(printf "ifname\nid\nuuid\npath\n%s" "$(_nmcli_con_show NAME)")" "${LONG_OPTIONS[@]}" + fi + return 0 + ;; + esac + + local COMMAND_CONNECTION_TYPE='' + OPTIONS=(ifname id uuid path) + _nmcli_compl_ARGS_CONNECTION && return 0 + + if [[ "$COMMAND_CONNECTION_TYPE" = "ifname" ]]; then + OPTIONS=(ap nsp passwd-file) + else + OPTIONS=(ifname ap nsp passwd-file) + fi + _nmcli_compl_ARGS + fi + ;; + d|do|dow|down) + if [[ ${#words[@]} -eq 3 ]]; then + _nmcli_compl_COMMAND_nl "${words[2]}" "$(printf "id\nuuid\npath\napath\n%s" "$(_nmcli_con_show NAME --active)")" + elif [[ ${#words[@]} -gt 3 ]]; then + _nmcli_array_delete_at words 0 1 + + LONG_OPTIONS=(help) + HELP_ONLY_AS_FIRST=1 + _nmcli_compl_OPTIONS + case $? in + 0) + return 0 + ;; + 1) + if [[ "$HELP_ONLY_AS_FIRST" == 1 ]]; then + _nmcli_compl_COMMAND_nl "${words[2]}" "$(printf "id\nuuid\npath\napath\n%s" "$(_nmcli_con_show NAME --active)")" "${LONG_OPTIONS[@]}" + fi + return 0 + ;; + esac + + OPTIONS=(id uuid path apath) + COMMAND_CONNECTION_ACTIVE=1 + _nmcli_compl_ARGS_CONNECTION && return 0 + fi + ;; + a|ad|add) + if [[ ${#words[@]} -eq 3 ]]; then + _nmcli_compl_COMMAND "${words[2]}" type ifname con-name autoconnect + elif [[ ${#words[@]} -gt 3 ]]; then + _nmcli_array_delete_at words 0 1 + + LONG_OPTIONS=(help) + HELP_ONLY_AS_FIRST=1 + _nmcli_compl_OPTIONS + case $? in + 0) + return 0 + ;; + 1) + if [[ "$HELP_ONLY_AS_FIRST" == 1 ]]; then + _nmcli_compl_COMMAND "${words[2]}" type ifname con-name autoconnect + fi + return 0 + ;; + esac + + OPTIONS_TYPE= + OPTIONS=(type ifname con-name autoconnect save) + OPTIONS_MANDATORY=(type ifname) + COMMAND_ARGS_WAIT_OPTIONS=1 + OPTIONS_MANDATORY_IFNAME=1 + _nmcli_compl_ARGS && return 0 + + OPTIONS_MANDATORY_IFNAME= + if _nmcli_array_has_value OPTIONS "${OPTIONS_MANDATORY[@]}"; then + # we still have some missing mandatory options... + if [[ "$OPTIONS_UNKNOWN_OPTION" != '' ]]; then + if ! _nmcli_array_has_value OPTIONS "${OPTIONS_UNKNOWN_OPTION:1}"; then + # if we encountered an unknown option while having mandatory + # options, just return. + return 0 + fi + fi + _nmcli_list "$(echo "${OPTIONS[@]}")" + return 0 + fi + + OPTIONS_IP=(ip4 ip6 gw4 gw6) + OPTIONS_MANDATORY=() + case "$OPTIONS_TYPE" in + 802-3|802-3-|802-3-e|802-3-et|802-3-eth|802-3-ethe|802-3-ether|802-3-ethern|802-3-etherne|802-3-ethernet| \ + e|et|eth|ethe|ether|ethern|etherne|ethernet) + OPTIONS_TYPE=ethernet + OPTIONS_TYPED=(mac cloned-mac mtu) + ;; + 802-11-w|802-11-wi|802-11-wir|802-11-wire|802-11-wirel|802-11-wirele|802-11-wireles|802-11-wireless| \ + wif|wifi) + OPTIONS_TYPE=wifi + OPTIONS_TYPED=(ssid mac cloned-mac mtu mode) + OPTIONS_MANDATORY=(ssid) + ;; + wim|wima|wimax) + OPTIONS_TYPE=wimax + OPTIONS_TYPED=(mac nsp) + ;; + g|gs|gsm) + OPTIONS_TYPE=gsm + OPTIONS_TYPED=(apn user password) + OPTIONS_MANDATORY=(apn) + ;; + c|cd|cdm|cdma) + OPTIONS_TYPE=cdma + OPTIONS_TYPED=(user password) + ;; + i|in|inf|infi|infin|infini|infinib|infiniba|infiniban|infiniband) + OPTIONS_TYPE=infiniband + OPTIONS_TYPED=(mac mtu transport-mode parent p-key) + ;; + bl|blu|blue|bluet|blueto|bluetoo|bluetoot|bluetooth) + OPTIONS_TYPE=bluetooth + OPTIONS_TYPED=(addr bt-type) + ;; + vl|vla|vlan) + OPTIONS_TYPE=vlan + OPTIONS_TYPED=(dev id flags ingress egress mtu) + OPTIONS_MANDATORY=(dev) + ;; + bond) + OPTIONS_TYPE=bond + OPTIONS_TYPED=(mode miimon downdelay updelay arp-interval arp-ip-target primary lacp-rate) + ;; + bond-|bond-s|bond-sl|bond-sla|bond-slav|bond-slave) + OPTIONS_TYPE=bond-slave + OPTIONS_TYPED=(master) + OPTIONS_MANDATORY=(master) + OPTIONS_IP=() + ;; + team) + OPTIONS_TYPE=team + OPTIONS_TYPED=(config) + ;; + team-|team-s|team-sl|team-sla|team-slav|team-slave) + OPTIONS_TYPE=team-slave + OPTIONS_TYPED=(master config) + OPTIONS_MANDATORY=(master) + OPTIONS_IP=() + ;; + bridge) + OPTIONS_TYPE=bridge + OPTIONS_TYPED=(stp priority forward-delay hello-time max-age ageing-time mac) + ;; + bridge-|bridge-s|bridge-sl|bridge-sla|bridge-slav|bridge-slave) + OPTIONS_TYPE=bridge-slave + OPTIONS_TYPED=(master priority path-cost hairpin) + OPTIONS_MANDATORY=(master) + OPTIONS_IP=() + ;; + vp|vpn) + OPTIONS_TYPE=vpn + OPTIONS_TYPED=(vpn-type user) + OPTIONS_MANDATORY=(vpn-type) + ;; + 802-11-o|802-11-ol|802-11-olp|802-11-olpc|802-11-olpc-|802-11-olpc-m|802-11-olpc-me|802-11-olpc-mes|802-11-olpc-mesh| \ + o|ol|olp|olpc|olpc-|olpc-m|olpc-me|olpc-mes|olpc-mesh) + OPTIONS_TYPE=olpc-mesh + OPTIONS_TYPED=(ssid channel dhcp-anycast) + OPTIONS_MANDATORY=(ssid) + ;; + p|pp|ppp|pppo|pppoe) + OPTIONS_TYPE=pppoe + OPTIONS_TYPED=(username password service mtu mac) + OPTIONS_MANDATORY=(username) + ;; + *) + # for an unknown connection type, we stop completion here + return 0 + ;; + esac + if [[ "$COMMAND_ARGS_WAIT_OPTIONS" -ne 1 ]]; then + # means, we are at the end of options. Nothing more to parse, just show + # what are the options now. + if [[ "${#OPTIONS_MANDATORY[@]}" -gt 0 ]]; then + _nmcli_list "$(echo "${OPTIONS[@]}") $(echo "${OPTIONS_TYPED[@]}")" + else + _nmcli_list "$(echo "${OPTIONS[@]}") $(echo "${OPTIONS_TYPED[@]}") $(echo "${OPTIONS_IP[@]}")" + fi + return 0 + fi + if [[ "${#OPTIONS[@]}" -gt 0 ]]; then + # we still have some options from before, but no mandatory ones. Mix them with OPTIONS_TYPED + # and continue parsing the options... + OPTIONS=("${OPTIONS[@]}" "${OPTIONS_TYPED[@]}") + OPTIONS_NEXT_GROUP=("${OPTIONS_TYPED[@]}") + _nmcli_compl_ARGS && return 0 + + if [[ "$COMMAND_ARGS_WAIT_OPTIONS" -ne 1 ]]; then + # means, we are at the end of options. Nothing more to parse, just show + # what are the options now. + if [[ "${#OPTIONS_MANDATORY[@]}" -gt 0 ]]; then + _nmcli_list "$(echo "${OPTIONS[@]}")" + else + _nmcli_list "$(echo "${OPTIONS[@]}") $(echo "${OPTIONS_IP[@]}")" + fi + return 0 + fi + + if [[ "$OPTIONS_UNKNOWN_OPTION" != "" ]]; then + # there was an unknown option specified. Maybe we have to stop with the completion. + if [[ "${#OPTIONS_MANDATORY[@]}" -gt 0 ]]; then + # we have an unknown option, but still mandatory ones that must be fullfiled first. + return 0 + fi + if ! _nmcli_array_has_value OPTIONS_IP "${OPTIONS_UNKNOWN_OPTION:1}"; then + # the unknown option is NOT an IP option. + return 0 + fi + # The unknown option is an IP option, which is fine... continue... + fi + + fi + OPTIONS=("${OPTIONS_TYPED[@]}") + OPTIONS_NEXT_GROUP=() + + if [[ "${#OPTIONS_MANDATORY[@]}" -ge 1 ]]; then + # we have some mandatory options... don't check for IP options yet... + _nmcli_compl_ARGS && return 0 + + if _nmcli_array_has_value OPTIONS "${OPTIONS_MANDATORY[@]}"; then + _nmcli_list "$(echo "${OPTIONS[@]}")" + return 0 + fi + + if [[ "$OPTIONS_UNKNOWN_OPTION" != "" ]]; then + if ! _nmcli_array_has_value OPTIONS_IP "${OPTIONS_UNKNOWN_OPTION:1}"; then + # the unknown option is NOT an IP option. + return 0 + fi + fi + fi + + + # no mandatory options... do final completion including IP options + OPTIONS=("${OPTIONS[@]}" "${OPTIONS_IP[@]}") + OPTIONS_NEXT_GROUP=("${OPTIONS_IP[@]}") + _nmcli_compl_ARGS && return 0 + + if [[ "$OPTIONS_UNKNOWN_OPTION" != "" ]]; then + return 0 + fi + + if [[ "$COMMAND_ARGS_WAIT_OPTIONS" -ne 1 ]]; then + # means, we are at the end of options. Nothing more to parse, just show + # what are the options now. + _nmcli_list "$(echo "${OPTIONS[@]}")" + return 0 + fi + + # process the last group of options, as the OPTIONS_TYPED are already handled... + OPTIONS=("${OPTIONS_IP[@]}") + OPTIONS_NEXT_GROUP=() + COMMAND_ARGS_WAIT_OPTIONS=0 + _nmcli_compl_ARGS && return 0 + fi + ;; + e|ed|edi|edit) + if [[ ${#words[@]} -eq 3 ]]; then + _nmcli_compl_COMMAND_nl "${words[2]}" "$(printf "id\nuuid\npath\ntype\ncon-name\n%s" "$(_nmcli_con_show NAME)")" + elif [[ ${#words[@]} -gt 3 ]]; then + _nmcli_array_delete_at words 0 1 + + LONG_OPTIONS=(help) + HELP_ONLY_AS_FIRST=1 + _nmcli_compl_OPTIONS + + case $? in + 0) + return 0 + ;; + 1) + if [[ "$HELP_ONLY_AS_FIRST" == 1 ]]; then + _nmcli_compl_COMMAND_nl "${words[2]}" "$(printf "id\nuuid\npath\ntype\ncon-name\n%s" "$(_nmcli_con_show NAME)")" "${LONG_OPTIONS[@]}" + fi + return 0 + ;; + esac + + if [[ "${words[0]}" = 'type' || "${words[0]}" = 'con-name' ]]; then + OPTIONS=(type con-name) + _nmcli_compl_ARGS + else + OPTIONS=(id uuid path apath) + _nmcli_compl_ARGS_CONNECTION + fi + fi + ;; + m|mo|mod|modi|modif|modify) + if [[ ${#words[@]} -eq 3 ]]; then + _nmcli_compl_COMMAND_nl "${words[2]}" "$(printf "id\nuuid\npath\n%s" "$(_nmcli_con_show NAME)")" temporary + elif [[ ${#words[@]} -gt 3 ]]; then + _nmcli_array_delete_at words 0 1 + + LONG_OPTIONS=(help temporary) + HELP_ONLY_AS_FIRST=1 + _nmcli_compl_OPTIONS + case $? in + 0) + return 0 + ;; + 1) + if [[ "$HELP_ONLY_AS_FIRST" == 1 ]]; then + _nmcli_compl_COMMAND_nl "${words[2]}" "$(printf "id\nuuid\npath\n%s" "$(_nmcli_con_show NAME)")" "${LONG_OPTIONS[@]}" + fi + return 0 + ;; + esac + + OPTIONS=(id uuid path apath) + _nmcli_compl_ARGS_CONNECTION && return 0 + while [[ "${#words[@]}" -gt 0 ]]; do + if [[ ${#words[@]} -le 1 ]]; then + local PREFIX="" + + if [[ "${words[0]:0:1}" == [+-] ]]; then + PREFIX="${words[0]:0:1}" + fi + _nmcli_list_nl "$(nmcli --fields profile connection show "${COMMAND_CONNECTION_TYPE:-id}" "$COMMAND_CONNECTION_ID" 2>/dev/null | sed -n 's/^\([^:]\+\):.*/'$PREFIX'\1/p')" + return 0 + elif [[ ${#words[@]} -le 2 ]]; then + return 0 + fi + _nmcli_array_delete_at words 0 1 + done + _nmcli_list_nl "$(nmcli --fields profile connection show "${COMMAND_CONNECTION_TYPE:-id}" "$COMMAND_CONNECTION_ID" 2>/dev/null | sed -n 's/^\([^:]\+\):.*/\1/p')" + return 0 + fi + ;; + de|del|dele|delet|delete) + if [[ ${#words[@]} -eq 3 ]]; then + _nmcli_compl_COMMAND_nl "${words[2]}" "$(printf "id\nuuid\npath\n%s" "$(_nmcli_con_show NAME)")" + elif [[ ${#words[@]} -gt 3 ]]; then + _nmcli_array_delete_at words 0 1 + + LONG_OPTIONS=(help) + _nmcli_compl_OPTIONS + case $? in + 0) + return 0 + ;; + 1) + if ! _nmcli_array_has_value LONG_OPTIONS "help"; then + return 0 + fi + ;; + esac + + OPTIONS=(id uuid path apath) + while [[ ${#words[@]} -gt 0 ]]; do + _nmcli_compl_ARGS_CONNECTION && return 0 + done + _nmcli_list_nl "$(printf "id\nuuid\npath\n%s" "$(_nmcli_con_show NAME)")" + fi + ;; + l|lo|loa|load) + if [[ ${#words[@]} -gt 2 ]]; then + # we should also complete for help/--help, but who to mix that + # with file name completion? + compopt -o default + COMPREPLY=() + fi + ;; + esac + fi + ;; + d|de|dev|devi|devic|device) + if [[ ${#words[@]} -eq 2 ]]; then + _nmcli_compl_COMMAND "$command" status show connect disconnect delete wifi wimax + elif [[ ${#words[@]} -gt 2 ]]; then + case "$command" in + s|st|sta|stat|statu|status) + if [[ ${#words[@]} -eq 3 ]]; then + _nmcli_compl_COMMAND "${words[2]}" + fi + ;; + sh|sho|show| \ + c|co|con|conn|conne|connec|connect| \ + d|di|dis|disc|disco|discon|disconn|disconne|disconnec|disconnect| \ + de|del|dele|delet|delete) + if [[ ${#words[@]} -eq 3 ]]; then + _nmcli_compl_COMMAND_nl "${words[2]}" "$(_nmcli_dev_status DEVICE)" + fi + ;; + w|wi|wif|wifi) + if [[ ${#words[@]} -eq 3 ]]; then + _nmcli_compl_COMMAND "${words[2]}" list connect rescan + else + case "${words[2]}" in + l|li|lis|list) + _nmcli_array_delete_at words 0 2 + OPTIONS=(ifname bssid) + _nmcli_compl_ARGS + ;; + c|co|con|conn|conne|connec|connect) + if [[ ${#words[@]} -eq 4 ]]; then + if [[ "${words[3]}" = "" ]]; then + _nmcli_list_nl "$(_nmcli_wifi_list SSID)" + else + _nmcli_list_nl "$(printf "%s\n%s" "$(_nmcli_wifi_list SSID)" "$(_nmcli_wifi_list BSSID)")" + fi + else + _nmcli_array_delete_at words 0 3 + local OPTIONS=(password wep-key-type ifname bssid name private) + _nmcli_compl_ARGS + fi + ;; + r|re|res|resc|resca|rescan) + _nmcli_array_delete_at words 0 2 + OPTIONS=(ifname) + _nmcli_compl_ARGS + ;; + esac + fi + ;; + wim|wima|wimax) + if [[ ${#words[@]} -eq 3 ]]; then + _nmcli_compl_COMMAND "${words[2]}" list + else + case "${words[2]}" in + l|li|lis|list) + _nmcli_array_delete_at words 0 2 + OPTIONS=(ifname nsp) + _nmcli_compl_ARGS + ;; + esac + fi + ;; + + esac + fi + ;; + a|ag|age|agen|agent) + if [[ ${#words[@]} -eq 2 ]]; then + _nmcli_compl_COMMAND "$command" secret polkit all + fi + ;; + esac + + return 0 +} && +complete -F _nmcli nmcli + +# ex: ts=4 sw=4 et filetype=sh diff --git a/clients/cli/nmcli.c b/clients/cli/nmcli.c new file mode 100644 index 00000000..8d9f7fef --- /dev/null +++ b/clients/cli/nmcli.c @@ -0,0 +1,621 @@ +/* nmcli - command-line tool to control NetworkManager + * + * Jiri Klimes <jklimes@redhat.com> + * + * 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. + */ + +/* Generated configuration file */ +#include "config.h" + +#include <stdio.h> +#include <string.h> +#include <stdlib.h> +#include <signal.h> +#include <pthread.h> +#include <termios.h> +#include <unistd.h> +#include <locale.h> +#include <readline/readline.h> +#include <readline/history.h> + +#include <glib.h> +#include <glib/gi18n.h> + +#include "polkit-agent.h" +#include "nmcli.h" +#include "utils.h" +#include "common.h" +#include "connections.h" +#include "devices.h" +#include "general.h" +#include "agent.h" + +#if defined(NM_DIST_VERSION) +# define NMCLI_VERSION NM_DIST_VERSION +#else +# define NMCLI_VERSION VERSION +#endif + +/* Global NmCli object */ +// FIXME: Currently, we pass NmCli over in most APIs, but we might refactor +// that and use the global variable directly instead. +NmCli nm_cli; + +typedef struct { + NmCli *nmc; + int argc; + char **argv; +} ArgsInfo; + +/* --- Global variables --- */ +GMainLoop *loop = NULL; +static sigset_t signal_set; +struct termios termios_orig; + + +/* Get an error quark for use with GError */ +GQuark +nmcli_error_quark (void) +{ + static GQuark error_quark = 0; + + if (G_UNLIKELY (error_quark == 0)) + error_quark = g_quark_from_static_string ("nmcli-error-quark"); + + return error_quark; +} + +static void +usage (const char *prog_name) +{ + g_printerr (_("Usage: %s [OPTIONS] OBJECT { COMMAND | help }\n" + "\n" + "OPTIONS\n" + " -t[erse] terse output\n" + " -p[retty] pretty output\n" + " -m[ode] tabular|multiline output mode\n" + " -f[ields] <field1,field2,...>|all|common specify fields to output\n" + " -e[scape] yes|no escape columns separators in values\n" + " -n[ocheck] don't check nmcli and NetworkManager versions\n" + " -a[sk] ask for missing parameters\n" + " -w[ait] <seconds> set timeout waiting for finishing operations\n" + " -v[ersion] show program version\n" + " -h[elp] print this help\n" + "\n" + "OBJECT\n" + " g[eneral] NetworkManager's general status and operations\n" + " n[etworking] overall networking control\n" + " r[adio] NetworkManager radio switches\n" + " c[onnection] NetworkManager's connections\n" + " d[evice] devices managed by NetworkManager\n" + " a[gent] NetworkManager secret agent or polkit agent\n" + "\n"), + prog_name); +} + +static NMCResultCode +do_help (NmCli *nmc, int argc, char **argv) +{ + usage ("nmcli"); + return NMC_RESULT_SUCCESS; +} + +static const struct cmd { + const char *cmd; + NMCResultCode (*func) (NmCli *nmc, int argc, char **argv); +} nmcli_cmds[] = { + { "general", do_general }, + { "networking", do_networking }, + { "radio", do_radio }, + { "connection", do_connections }, + { "device", do_devices }, + { "agent", do_agent }, + { "help", do_help }, + { 0 } +}; + +static NMCResultCode +do_cmd (NmCli *nmc, const char *argv0, int argc, char **argv) +{ + const struct cmd *c; + + for (c = nmcli_cmds; c->cmd; ++c) { + if (matches (argv0, c->cmd) == 0) + return c->func (nmc, argc-1, argv+1); + } + + g_string_printf (nmc->return_text, _("Error: Object '%s' is unknown, try 'nmcli help'."), argv0); + nmc->return_value = NMC_RESULT_ERROR_USER_INPUT; + return nmc->return_value; +} + +static NMCResultCode +parse_command_line (NmCli *nmc, int argc, char **argv) +{ + char *base; + + base = strrchr (argv[0], '/'); + if (base == NULL) + base = argv[0]; + else + base++; + + /* parse options */ + while (argc > 1) { + char *opt = argv[1]; + /* '--' ends options */ + if (strcmp (opt, "--") == 0) { + argc--; argv++; + break; + } + if (opt[0] != '-') + break; + if (opt[1] == '-') + opt++; + if (matches (opt, "-terse") == 0) { + if (nmc->print_output == NMC_PRINT_TERSE) { + g_string_printf (nmc->return_text, _("Error: Option '--terse' is specified the second time.")); + nmc->return_value = NMC_RESULT_ERROR_USER_INPUT; + return nmc->return_value; + } + else if (nmc->print_output == NMC_PRINT_PRETTY) { + g_string_printf (nmc->return_text, _("Error: Option '--terse' is mutually exclusive with '--pretty'.")); + nmc->return_value = NMC_RESULT_ERROR_USER_INPUT; + return nmc->return_value; + } + else + nmc->print_output = NMC_PRINT_TERSE; + } else if (matches (opt, "-pretty") == 0) { + if (nmc->print_output == NMC_PRINT_PRETTY) { + g_string_printf (nmc->return_text, _("Error: Option '--pretty' is specified the second time.")); + nmc->return_value = NMC_RESULT_ERROR_USER_INPUT; + return nmc->return_value; + } + else if (nmc->print_output == NMC_PRINT_TERSE) { + g_string_printf (nmc->return_text, _("Error: Option '--pretty' is mutually exclusive with '--terse'.")); + nmc->return_value = NMC_RESULT_ERROR_USER_INPUT; + return nmc->return_value; + } + else + nmc->print_output = NMC_PRINT_PRETTY; + } else if (matches (opt, "-mode") == 0) { + nmc->mode_specified = TRUE; + next_arg (&argc, &argv); + if (argc <= 1) { + g_string_printf (nmc->return_text, _("Error: missing argument for '%s' option."), opt); + nmc->return_value = NMC_RESULT_ERROR_USER_INPUT; + return nmc->return_value; + } + if (matches (argv[1], "tabular") == 0) + nmc->multiline_output = FALSE; + else if (matches (argv[1], "multiline") == 0) + nmc->multiline_output = TRUE; + else { + g_string_printf (nmc->return_text, _("Error: '%s' is not valid argument for '%s' option."), argv[1], opt); + nmc->return_value = NMC_RESULT_ERROR_USER_INPUT; + return nmc->return_value; + } + } else if (matches (opt, "-escape") == 0) { + next_arg (&argc, &argv); + if (argc <= 1) { + g_string_printf (nmc->return_text, _("Error: missing argument for '%s' option."), opt); + nmc->return_value = NMC_RESULT_ERROR_USER_INPUT; + return nmc->return_value; + } + if (matches (argv[1], "yes") == 0) + nmc->escape_values = TRUE; + else if (matches (argv[1], "no") == 0) + nmc->escape_values = FALSE; + else { + g_string_printf (nmc->return_text, _("Error: '%s' is not valid argument for '%s' option."), argv[1], opt); + nmc->return_value = NMC_RESULT_ERROR_USER_INPUT; + return nmc->return_value; + } + } else if (matches (opt, "-fields") == 0) { + next_arg (&argc, &argv); + if (argc <= 1) { + g_string_printf (nmc->return_text, _("Error: fields for '%s' options are missing."), opt); + nmc->return_value = NMC_RESULT_ERROR_USER_INPUT; + return nmc->return_value; + } + nmc->required_fields = g_strdup (argv[1]); + } else if (matches (opt, "-nocheck") == 0) { + nmc->nocheck_ver = TRUE; + } else if (matches (opt, "-ask") == 0) { + nmc->ask = TRUE; + } else if (matches (opt, "-wait") == 0) { + unsigned long timeout; + next_arg (&argc, &argv); + if (argc <= 1) { + g_string_printf (nmc->return_text, _("Error: missing argument for '%s' option."), opt); + nmc->return_value = NMC_RESULT_ERROR_USER_INPUT; + return nmc->return_value; + } + if (!nmc_string_to_uint (argv[1], TRUE, 0, G_MAXINT, &timeout)) { + g_string_printf (nmc->return_text, _("Error: '%s' is not a valid timeout for '%s' option."), + argv[1], opt); + nmc->return_value = NMC_RESULT_ERROR_USER_INPUT; + return nmc->return_value; + } + nmc->timeout = (int) timeout; + } else if (matches (opt, "-version") == 0) { + g_print (_("nmcli tool, version %s\n"), NMCLI_VERSION); + return NMC_RESULT_SUCCESS; + } else if (matches (opt, "-help") == 0) { + usage (base); + return NMC_RESULT_SUCCESS; + } else { + g_string_printf (nmc->return_text, _("Error: Option '%s' is unknown, try 'nmcli -help'."), opt); + nmc->return_value = NMC_RESULT_ERROR_USER_INPUT; + return nmc->return_value; + } + argc--; + argv++; + } + + if (argc > 1) { + /* Now run the requested command */ + return do_cmd (nmc, argv[1], argc-1, argv+1); + } + + usage (base); + return nmc->return_value; +} + +static gboolean nmcli_sigint = FALSE; +static pthread_mutex_t sigint_mutex = PTHREAD_MUTEX_INITIALIZER; +static gboolean nmcli_sigquit_internal = FALSE; + +gboolean +nmc_seen_sigint (void) +{ + gboolean sigint; + + pthread_mutex_lock (&sigint_mutex); + sigint = nmcli_sigint; + pthread_mutex_unlock (&sigint_mutex); + return sigint; +} + +void +nmc_clear_sigint (void) +{ + pthread_mutex_lock (&sigint_mutex); + nmcli_sigint = FALSE; + pthread_mutex_unlock (&sigint_mutex); +} + +void +nmc_set_sigquit_internal (void) +{ + nmcli_sigquit_internal = TRUE; +} + +static int +event_hook_for_readline (void) +{ + /* Make readline() exit on SIGINT */ + if (nmc_seen_sigint ()) { + rl_echo_signal_char (SIGINT); + rl_stuff_char ('\n'); + } + return 0; +} + +void *signal_handling_thread (void *arg); +/* + * Thread function waiting for signals and processing them. + * Wait for signals in signal set. The semantics of sigwait() require that all + * threads (including the thread calling sigwait()) have the signal masked, for + * reliable operation. Otherwise, a signal that arrives while this thread is + * not blocked in sigwait() might be delivered to another thread. + */ +void * +signal_handling_thread (void *arg) { + int signo; + + while (1) { + sigwait (&signal_set, &signo); + + switch (signo) { + case SIGINT: + if (nmc_get_in_readline ()) { + /* Don't quit when in readline, only signal we received SIGINT */ + pthread_mutex_lock (&sigint_mutex); + nmcli_sigint = TRUE; + pthread_mutex_unlock (&sigint_mutex); + } else { + /* We can quit nmcli */ + tcsetattr (STDIN_FILENO, TCSADRAIN, &termios_orig); + nmc_cleanup_readline (); + g_print (_("\nError: nmcli terminated by signal %s (%d)\n"), + strsignal (signo), signo); + exit (1); + } + break; + case SIGQUIT: + case SIGTERM: + tcsetattr (STDIN_FILENO, TCSADRAIN, &termios_orig); + nmc_cleanup_readline (); + if (!nmcli_sigquit_internal) + g_print (_("\nError: nmcli terminated by signal %s (%d)\n"), + strsignal (signo), signo); + exit (1); + break; + default: + break; + } + } + return NULL; +} + +/* + * Mask the signals we are interested in and create a signal handling thread. + * Because all threads inherit the signal mask from their creator, all threads + * in the process will have the signals masked. That's why setup_signals() has + * to be called before creating other threads. + */ +static gboolean +setup_signals (void) +{ + pthread_t signal_thread_id; + int status; + + sigemptyset (&signal_set); + sigaddset (&signal_set, SIGINT); + sigaddset (&signal_set, SIGQUIT); + sigaddset (&signal_set, SIGTERM); + + /* Block all signals of interest. */ + status = pthread_sigmask (SIG_BLOCK, &signal_set, NULL); + if (status != 0) { + g_printerr (_("Failed to set signal mask: %d\n"), status); + return FALSE; + } + + /* Create the signal handling thread. */ + status = pthread_create (&signal_thread_id, NULL, signal_handling_thread, NULL); + if (status != 0) { + g_printerr (_("Failed to create signal handling thread: %d\n"), status); + return FALSE; + } + + return TRUE; +} + +static void +nmc_convert_strv_to_string (const GValue *src_value, GValue *dest_value) +{ + char **strings; + + strings = g_value_get_boxed (src_value); + if (strings) + g_value_take_string (dest_value, g_strjoinv (",", strings)); + else + g_value_set_string (dest_value, ""); +} + +static void +nmc_convert_string_hash_to_string (const GValue *src_value, GValue *dest_value) +{ + GHashTable *hash; + GHashTableIter iter; + const char *key, *value; + GString *string; + + hash = (GHashTable *) g_value_get_boxed (src_value); + + string = g_string_new (NULL); + if (hash) { + g_hash_table_iter_init (&iter, hash); + while (g_hash_table_iter_next (&iter, (gpointer *) &key, (gpointer *) &value)) { + if (string->len) + g_string_append_c (string, ','); + g_string_append_printf (string, "%s=%s", key, value); + } + } + + g_value_take_string (dest_value, g_string_free (string, FALSE)); +} + +static void +nmc_convert_bytes_to_string (const GValue *src_value, GValue *dest_value) +{ + GBytes *bytes; + const guint8 *array; + gsize length; + GString *printable; + guint i = 0; + + bytes = g_value_get_boxed (src_value); + + printable = g_string_new ("["); + + if (bytes) { + array = g_bytes_get_data (bytes, &length); + while (i < MIN (length, 35)) { + if (i > 0) + g_string_append_c (printable, ' '); + g_string_append_printf (printable, "0x%02X", array[i++]); + } + if (i < length) + g_string_append (printable, " ... "); + } + g_string_append_c (printable, ']'); + + g_value_take_string (dest_value, g_string_free (printable, FALSE)); +} + +static void +nmc_value_transforms_register (void) +{ + g_value_register_transform_func (G_TYPE_STRV, + G_TYPE_STRING, + nmc_convert_strv_to_string); + + /* This depends on the fact that all of the hash-table-valued properties + * in libnm-core are string->string. + */ + g_value_register_transform_func (G_TYPE_HASH_TABLE, + G_TYPE_STRING, + nmc_convert_string_hash_to_string); + + g_value_register_transform_func (G_TYPE_BYTES, + G_TYPE_STRING, + nmc_convert_bytes_to_string); +} + +static NMClient * +nmc_get_client (NmCli *nmc) +{ + GError *error = NULL; + + if (!nmc->client) { + nmc->client = nm_client_new (NULL, &error); + if (!nmc->client) { + g_critical (_("Error: Could not create NMClient object: %s."), error->message); + g_clear_error (&error); + exit (NMC_RESULT_ERROR_UNKNOWN); + } + } + + return nmc->client; +} + +/* Initialize NmCli structure - set default values */ +static void +nmc_init (NmCli *nmc) +{ + nmc->client = NULL; + nmc->get_client = &nmc_get_client; + + nmc->return_value = NMC_RESULT_SUCCESS; + nmc->return_text = g_string_new (_("Success")); + + nmc->timeout = -1; + + nmc->connections = NULL; + + nmc->secret_agent = NULL; + nmc->pwds_hash = NULL; + nmc->pk_listener = NULL; + + nmc->should_wait = FALSE; + nmc->nowait_flag = TRUE; + nmc->print_output = NMC_PRINT_NORMAL; + nmc->multiline_output = FALSE; + nmc->mode_specified = FALSE; + nmc->escape_values = TRUE; + nmc->required_fields = NULL; + nmc->output_data = g_ptr_array_new_full (20, g_free); + memset (&nmc->print_fields, '\0', sizeof (NmcPrintFields)); + nmc->nocheck_ver = FALSE; + nmc->ask = FALSE; + nmc->in_editor = FALSE; + nmc->editor_status_line = FALSE; + nmc->editor_save_confirmation = TRUE; + nmc->editor_show_secrets = FALSE; + nmc->editor_prompt_color = NMC_TERM_COLOR_NORMAL; +} + +static void +nmc_cleanup (NmCli *nmc) +{ + if (nmc->client) g_object_unref (nmc->client); + + g_string_free (nmc->return_text, TRUE); + + if (nmc->secret_agent) { + /* Destroy secret agent if we have one. */ + nm_secret_agent_old_unregister (nmc->secret_agent, NULL, NULL); + g_object_unref (nmc->secret_agent); + } + if (nmc->pwds_hash) + g_hash_table_destroy (nmc->pwds_hash); + + g_free (nmc->required_fields); + nmc_empty_output_fields (nmc); + g_ptr_array_unref (nmc->output_data); + + nmc_polkit_agent_fini (nmc); +} + +static gboolean +start (gpointer data) +{ + ArgsInfo *info = (ArgsInfo *) data; + info->nmc->return_value = parse_command_line (info->nmc, info->argc, info->argv); + + if (!info->nmc->should_wait) + g_main_loop_quit (loop); + + return FALSE; +} + + +int +main (int argc, char *argv[]) +{ + ArgsInfo args_info = { &nm_cli, argc, argv }; + + /* Set up unix signal handling */ + if (!setup_signals ()) + exit (NMC_RESULT_ERROR_UNKNOWN); + + /* Set locale to use environment variables */ + setlocale (LC_ALL, ""); + +#ifdef GETTEXT_PACKAGE + /* Set i18n stuff */ + bindtextdomain (GETTEXT_PACKAGE, NMCLI_LOCALEDIR); + bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8"); + textdomain (GETTEXT_PACKAGE); +#endif + +#if !GLIB_CHECK_VERSION (2, 35, 0) + g_type_init (); +#endif + + /* Save terminal settings */ + tcgetattr (STDIN_FILENO, &termios_orig); + + /* readline init */ + rl_event_hook = event_hook_for_readline; + /* Set 0.01s timeout to mitigate slowness in readline when a broken version is used. + * See https://bugzilla.redhat.com/show_bug.cgi?id=1109946 + */ + rl_set_keyboard_input_timeout (10000); + + nmc_value_transforms_register (); + + nmc_init (&nm_cli); + g_idle_add (start, &args_info); + + loop = g_main_loop_new (NULL, FALSE); /* create main loop */ + g_main_loop_run (loop); /* run main loop */ + + /* Print result descripting text */ + if (nm_cli.return_value != NMC_RESULT_SUCCESS) { + g_printerr ("%s\n", nm_cli.return_text->str); + } + + g_main_loop_unref (loop); + nmc_cleanup (&nm_cli); + + return nm_cli.return_value; +} diff --git a/clients/cli/nmcli.h b/clients/cli/nmcli.h new file mode 100644 index 00000000..32e7e5dd --- /dev/null +++ b/clients/cli/nmcli.h @@ -0,0 +1,156 @@ +/* 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. + */ + +#ifndef NMC_NMCLI_H +#define NMC_NMCLI_H + +#include "config.h" + +#include <NetworkManager.h> +#include <nm-secret-agent-old.h> + +#if WITH_POLKIT_AGENT +#include "nm-polkit-listener.h" +#else +/* polkit agent is not available; define fake NMPolkitListener */ +typedef gpointer NMPolkitListener; +#endif + +/* nmcli exit codes */ +typedef enum { + /* Indicates successful execution */ + NMC_RESULT_SUCCESS = 0, + + /* Unknown / unspecified error */ + NMC_RESULT_ERROR_UNKNOWN = 1, + + /* Wrong invocation of nmcli */ + NMC_RESULT_ERROR_USER_INPUT = 2, + + /* A timeout expired */ + NMC_RESULT_ERROR_TIMEOUT_EXPIRED = 3, + + /* Error in connection activation */ + NMC_RESULT_ERROR_CON_ACTIVATION = 4, + + /* Error in connection deactivation */ + NMC_RESULT_ERROR_CON_DEACTIVATION = 5, + + /* Error in device disconnect */ + NMC_RESULT_ERROR_DEV_DISCONNECT = 6, + + /* Error in connection deletion */ + NMC_RESULT_ERROR_CON_DEL = 7, + + /* NetworkManager is not running */ + NMC_RESULT_ERROR_NM_NOT_RUNNING = 8, + + /* nmcli and NetworkManager versions mismatch */ + NMC_RESULT_ERROR_VERSIONS_MISMATCH = 9, + + /* Connection/Device/AP not found */ + NMC_RESULT_ERROR_NOT_FOUND = 10 +} NMCResultCode; + +typedef enum { + NMC_PRINT_TERSE = 0, + NMC_PRINT_NORMAL = 1, + NMC_PRINT_PRETTY = 2 +} NMCPrintOutput; + +/* === Output fields === */ +/* Flags for NmcOutputField */ +#define NMC_OF_FLAG_FIELD_NAMES 0x00000001 /* Print field names instead of values */ +#define NMC_OF_FLAG_SECTION_PREFIX 0x00000002 /* Use the first value as section prefix for the other field names - just in multiline */ +#define NMC_OF_FLAG_MAIN_HEADER_ADD 0x00000004 /* Print main header in addition to values/field names */ +#define NMC_OF_FLAG_MAIN_HEADER_ONLY 0x00000008 /* Print main header only */ + +typedef struct _NmcOutputField { + const char *name; /* Field's name */ + const char *name_l10n; /* Field's name for translation */ + int width; /* Width in screen columns */ + struct _NmcOutputField *group; /* Points to an array with available section field names if this is a section (group) field */ + void *value; /* Value of current field - char* or char** (NULL-terminated array) */ + gboolean value_is_array; /* Whether value is char** instead of char* */ + gboolean free_value; /* Whether to free the value */ + guint32 flags; /* Flags - whether and how to print values/field names/headers */ +} NmcOutputField; + +typedef struct { + GArray *indices; /* Array of field indices to the array of allowed fields */ + char *header_name; /* Name of the output */ + int indent; /* Indent by this number of spaces */ +} NmcPrintFields; + +typedef enum { + NMC_TERM_COLOR_NORMAL = 0, + NMC_TERM_COLOR_BLACK = 1, + NMC_TERM_COLOR_RED = 2, + NMC_TERM_COLOR_GREEN = 3, + NMC_TERM_COLOR_YELLOW = 4, + NMC_TERM_COLOR_BLUE = 5, + NMC_TERM_COLOR_MAGENTA = 6, + NMC_TERM_COLOR_CYAN = 7, + NMC_TERM_COLOR_WHITE = 8 +} NmcTermColor; + +/* NmCli - main structure */ +typedef struct _NmCli { + NMClient *client; /* Pointer to NMClient of libnm */ + NMClient *(*get_client) (struct _NmCli *nmc); /* Pointer to function for creating NMClient */ + + NMCResultCode return_value; /* Return code of nmcli */ + GString *return_text; /* Reason text */ + + int timeout; /* Operation timeout */ + + const GPtrArray *connections; /* List of connections */ + + NMSecretAgentOld *secret_agent; /* Secret agent */ + GHashTable *pwds_hash; /* Hash table with passwords in passwd-file */ + NMPolkitListener *pk_listener ; /* polkit agent listener */ + + gboolean should_wait; /* Indication that nmcli should not end yet */ + gboolean nowait_flag; /* '--nowait' option; used for passing to callbacks */ + NMCPrintOutput print_output; /* Output mode */ + gboolean multiline_output; /* Multiline output instead of default tabular */ + gboolean mode_specified; /* Whether tabular/multiline mode was specified via '--mode' option */ + gboolean escape_values; /* Whether to escape ':' and '\' in terse tabular mode */ + char *required_fields; /* Required fields in output: '--fields' option */ + GPtrArray *output_data; /* GPtrArray of arrays of NmcOutputField structs - accumulates data for output */ + NmcPrintFields print_fields; /* Structure with field indices to print */ + gboolean nocheck_ver; /* Don't check nmcli and NM versions: option '--nocheck' */ + gboolean ask; /* Ask for missing parameters: option '--ask' */ + gboolean in_editor; /* Whether running the editor - nmcli con edit' */ + gboolean editor_status_line; /* Whether to display status line in connection editor */ + gboolean editor_save_confirmation; /* Whether to ask for confirmation on saving connections with 'autoconnect=yes' */ + gboolean editor_show_secrets; /* Whether to display secrets in the editor' */ + NmcTermColor editor_prompt_color; /* Color of prompt in connection editor */ +} NmCli; + +/* Error quark for GError domain */ +#define NMCLI_ERROR (nmcli_error_quark ()) +GQuark nmcli_error_quark (void); + +gboolean nmc_seen_sigint (void); +void nmc_clear_sigint (void); +void nmc_set_sigquit_internal (void); + + +#endif /* NMC_NMCLI_H */ diff --git a/clients/cli/polkit-agent.c b/clients/cli/polkit-agent.c new file mode 100644 index 00000000..a163b824 --- /dev/null +++ b/clients/cli/polkit-agent.c @@ -0,0 +1,166 @@ +/* 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 2014 Red Hat, Inc. + */ + +#include "config.h" + +#if WITH_POLKIT_AGENT + +#include <stdio.h> +#include <string.h> +#include <sys/types.h> +#include <unistd.h> +#include <termios.h> + +#include <glib.h> +#include <glib/gi18n-lib.h> + +#include "polkit-agent.h" +#include "nm-polkit-listener.h" +#include "common.h" + +static char * +polkit_request (const char *request, + const char *action_id, + const char *message, + const char *icon_name, + const char *user, + gboolean echo_on, + gpointer user_data) +{ + char *response, *tmp, *p; + struct termios termios_orig, termios_new; + + g_print ("%s\n", message); + g_print ("(action_id: %s)\n", action_id); + + if (!echo_on) { + tcgetattr (STDIN_FILENO, &termios_orig); + termios_new = termios_orig; + termios_new.c_lflag &= ~(ECHO); + tcsetattr (STDIN_FILENO, TCSADRAIN, &termios_new); + } + + /* Ask user for polkit authorization password */ + if (user) { + /* chop of ": " if present */ + tmp = g_strdup (request); + p = strrchr (tmp, ':'); + if (p && !strcmp (p, ": ")) + *p = '\0'; + response = nmc_readline ("%s (%s): ", tmp, user); + g_free (tmp); + } else + response = nmc_readline ("%s", request); + g_print ("\n"); + + /* Restore original terminal settings */ + if (!echo_on) + tcsetattr (STDIN_FILENO, TCSADRAIN, &termios_orig); + + return response; +} + +static void +polkit_show_info (const char *text) +{ + g_print (_("Authentication message: %s\n"), text); +} + +static void +polkit_show_error (const char *text) +{ + g_print (_("Authentication error: %s\n"), text); +} + +static void +polkit_completed (gboolean gained_authorization) +{ + /* We don't print anything here. The outcome will be evident from + * the operation result anyway. */ +} + +gboolean +nmc_polkit_agent_init (NmCli* nmc, gboolean for_session, GError **error) +{ + PolkitAgentListener *listener; + + g_return_val_if_fail (error == NULL || *error == NULL, FALSE); + + listener = nm_polkit_listener_new (for_session, error); + if (!listener) + return FALSE; + + nm_polkit_listener_set_request_callback (NM_POLKIT_LISTENER (listener), polkit_request, nmc); + nm_polkit_listener_set_show_info_callback (NM_POLKIT_LISTENER (listener), polkit_show_info); + nm_polkit_listener_set_show_error_callback (NM_POLKIT_LISTENER (listener), polkit_show_error); + nm_polkit_listener_set_completed_callback (NM_POLKIT_LISTENER (listener), polkit_completed); + + nmc->pk_listener = NM_POLKIT_LISTENER (listener); + return TRUE; +} + +void +nmc_polkit_agent_fini (NmCli* nmc) +{ + g_clear_object (&nmc->pk_listener); +} + +gboolean +nmc_start_polkit_agent_start_try (NmCli *nmc) +{ + GError *error = NULL; + + /* We don't register polkit agent at all when running non-interactively */ + if (!nmc->ask) + return TRUE; + + if (!nmc_polkit_agent_init (nmc, FALSE, &error)) { + g_printerr (_("Warning: polkit agent initialization failed: %s\n"), + error->message); + g_error_free (error); + return FALSE; + } + return TRUE; +} + +#else +/* polkit agent is not avalable; implement stub functions. */ + +#include <glib.h> +#include "nmcli.h" +#include "polkit-agent.h" + +gboolean +nmc_polkit_agent_init (NmCli* nmc, gboolean for_session, GError **error) +{ + return TRUE; +} + +void +nmc_polkit_agent_fini (NmCli* nmc) +{ +} + +gboolean +nmc_start_polkit_agent_start_try (NmCli *nmc) +{ + return TRUE; +} + +#endif /* #if WITH_POLKIT_AGENT */ diff --git a/clients/cli/polkit-agent.h b/clients/cli/polkit-agent.h new file mode 100644 index 00000000..e2902dc0 --- /dev/null +++ b/clients/cli/polkit-agent.h @@ -0,0 +1,30 @@ +/* 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 2014 Red Hat, Inc. + */ + +#ifndef __NMC_POLKIT_AGENT_H__ +#define __NMC_POLKIT_AGENT_H__ + +#include "nmcli.h" + +gboolean nmc_polkit_agent_init (NmCli *nmc, gboolean for_session, GError **error); +void nmc_polkit_agent_fini (NmCli* nmc); + +gboolean nmc_start_polkit_agent_start_try (NmCli *nmc); + +#endif /* __NMC_POLKIT_AGENT_H__ */ diff --git a/clients/cli/settings-docs.c b/clients/cli/settings-docs.c new file mode 100644 index 00000000..1edfddb5 --- /dev/null +++ b/clients/cli/settings-docs.c @@ -0,0 +1,388 @@ +/* Generated file. Do not edit. */ + +typedef struct { + const char *name; + const char *docs; +} NmcPropertyDesc; + +NmcPropertyDesc setting_802_11_olpc_mesh[] = { + { "channel", "Channel on which the mesh network to join is located." }, + { "dhcp-anycast-address", "Anycast DHCP MAC address used when requesting an IP address via DHCP. The specific anycast address used determines which DHCP server class answers the request." }, + { "name", "The setting's name, which uniquely identifies the setting within the connection. Each setting type has a name unique to that type, for example \"ppp\" or \"wireless\" or \"wired\"." }, + { "ssid", "SSID of the mesh network to join." }, +}; + +NmcPropertyDesc setting_802_11_wireless[] = { + { "band", "802.11 frequency band of the network. One of \"a\" for 5GHz 802.11a or \"bg\" for 2.4GHz 802.11. This will lock associations to the Wi-Fi network to the specific band, i.e. if \"a\" is specified, the device will not associate with the same network in the 2.4GHz band even if the network's settings are compatible. This setting depends on specific driver capability and may not work with all drivers." }, + { "bssid", "If specified, directs the device to only associate with the given access point. This capability is highly driver dependent and not supported by all devices. Note: this property does not control the BSSID used when creating an Ad-Hoc network and is unlikely to in the future." }, + { "channel", "Wireless channel to use for the Wi-Fi connection. The device will only join (or create for Ad-Hoc networks) a Wi-Fi network on the specified channel. Because channel numbers overlap between bands, this property also requires the \"band\" property to be set." }, + { "cloned-mac-address", "If specified, request that the Wi-Fi device use this MAC address instead of its permanent MAC address. This is known as MAC cloning or spoofing." }, + { "hidden", "If TRUE, indicates this network is a non-broadcasting network that hides its SSID. In this case various workarounds may take place, such as probe-scanning the SSID for more reliable network discovery. However, these workarounds expose inherent insecurities with hidden SSID networks, and thus hidden SSID networks should be used with caution." }, + { "mac-address", "If specified, this connection will only apply to the Wi-Fi device whose permanent MAC address matches. This property does not change the MAC address of the device (i.e. MAC spoofing)." }, + { "mac-address-blacklist", "A list of permanent MAC addresses of Wi-Fi devices to which this connection should never apply. Each MAC address should be given in the standard hex-digits-and-colons notation (eg \"00:11:22:33:44:55\")." }, + { "mode", "Wi-Fi network mode; one of \"infrastructure\", \"adhoc\" or \"ap\". If blank, infrastructure is assumed." }, + { "mtu", "If non-zero, only transmit packets of the specified size or smaller, breaking larger packets up into multiple Ethernet frames." }, + { "name", "The setting's name, which uniquely identifies the setting within the connection. Each setting type has a name unique to that type, for example \"ppp\" or \"wireless\" or \"wired\"." }, + { "rate", "If non-zero, directs the device to only use the specified bitrate for communication with the access point. Units are in Kb/s, ie 5500 = 5.5 Mbit/s. This property is highly driver dependent and not all devices support setting a static bitrate." }, + { "seen-bssids", "A list of BSSIDs (each BSSID formatted as a MAC address like \"00:11:22:33:44:55\") that have been detected as part of the Wi-Fi network. NetworkManager internally tracks previously seen BSSIDs. The property is only meant for reading and reflects the BSSID list of NetworkManager. The changes you make to this property will not be preserved." }, + { "ssid", "SSID of the Wi-Fi network. Must be specified." }, + { "tx-power", "If non-zero, directs the device to use the specified transmit power. Units are dBm. This property is highly driver dependent and not all devices support setting a static transmit power." }, +}; + +NmcPropertyDesc setting_802_11_wireless_security[] = { + { "auth-alg", "When WEP is used (ie, key-mgmt = \"none\" or \"ieee8021x\") indicate the 802.11 authentication algorithm required by the AP here. One of \"open\" for Open System, \"shared\" for Shared Key, or \"leap\" for Cisco LEAP. When using Cisco LEAP (ie, key-mgmt = \"ieee8021x\" and auth-alg = \"leap\") the \"leap-username\" and \"leap-password\" properties must be specified." }, + { "group", "A list of group/broadcast encryption algorithms which prevents connections to Wi-Fi networks that do not utilize one of the algorithms in the list. For maximum compatibility leave this property empty. Each list element may be one of \"wep40\", \"wep104\", \"tkip\", or \"ccmp\"." }, + { "key-mgmt", "Key management used for the connection. One of \"none\" (WEP), \"ieee8021x\" (Dynamic WEP), \"wpa-none\" (Ad-Hoc WPA-PSK), \"wpa-psk\" (infrastructure WPA-PSK), or \"wpa-eap\" (WPA-Enterprise). This property must be set for any Wi-Fi connection that uses security." }, + { "leap-password", "The login password for legacy LEAP connections (ie, key-mgmt = \"ieee8021x\" and auth-alg = \"leap\")." }, + { "leap-password-flags", "Flags indicating how to handle the \"leap-password\" property." }, + { "leap-username", "The login username for legacy LEAP connections (ie, key-mgmt = \"ieee8021x\" and auth-alg = \"leap\")." }, + { "name", "The setting's name, which uniquely identifies the setting within the connection. Each setting type has a name unique to that type, for example \"ppp\" or \"wireless\" or \"wired\"." }, + { "pairwise", "A list of pairwise encryption algorithms which prevents connections to Wi-Fi networks that do not utilize one of the algorithms in the list. For maximum compatibility leave this property empty. Each list element may be one of \"tkip\" or \"ccmp\"." }, + { "proto", "List of strings specifying the allowed WPA protocol versions to use. Each element may be one \"wpa\" (allow WPA) or \"rsn\" (allow WPA2/RSN). If not specified, both WPA and RSN connections are allowed." }, + { "psk", "Pre-Shared-Key for WPA networks. If the key is 64-characters long, it must contain only hexadecimal characters and is interpreted as a hexadecimal WPA key. Otherwise, the key must be between 8 and 63 ASCII characters (as specified in the 802.11i standard) and is interpreted as a WPA passphrase, and is hashed to derive the actual WPA-PSK used when connecting to the Wi-Fi network." }, + { "psk-flags", "Flags indicating how to handle the \"psk\" property." }, + { "wep-key-flags", "Flags indicating how to handle the \"wep-key0\", \"wep-key1\", \"wep-key2\", and \"wep-key3\" properties." }, + { "wep-key-type", "Controls the interpretation of WEP keys. Allowed values are NM_WEP_KEY_TYPE_KEY (1), in which case the key is either a 10- or 26-character hexadecimal string, or a 5- or 13-character ASCII password; or NM_WEP_KEY_TYPE_PASSPHRASE (2), in which case the passphrase is provided as a string and will be hashed using the de-facto MD5 method to derive the actual WEP key." }, + { "wep-key0", "Index 0 WEP key. This is the WEP key used in most networks. See the \"wep-key-type\" property for a description of how this key is interpreted." }, + { "wep-key1", "Index 1 WEP key. This WEP index is not used by most networks. See the \"wep-key-type\" property for a description of how this key is interpreted." }, + { "wep-key2", "Index 2 WEP key. This WEP index is not used by most networks. See the \"wep-key-type\" property for a description of how this key is interpreted." }, + { "wep-key3", "Index 3 WEP key. This WEP index is not used by most networks. See the \"wep-key-type\" property for a description of how this key is interpreted." }, + { "wep-tx-keyidx", "When static WEP is used (ie, key-mgmt = \"none\") and a non-default WEP key index is used by the AP, put that WEP key index here. Valid values are 0 (default key) through 3. Note that some consumer access points (like the Linksys WRT54G) number the keys 1 - 4." }, +}; + +NmcPropertyDesc setting_802_1x[] = { + { "altsubject-matches", "List of strings to be matched against the altSubjectName of the certificate presented by the authentication server. If the list is empty, no verification of the server certificate's altSubjectName is performed." }, + { "anonymous-identity", "Anonymous identity string for EAP authentication methods. Used as the unencrypted identity with EAP types that support different tunneled identity like EAP-TTLS." }, + { "ca-cert", "Contains the CA certificate if used by the EAP method specified in the \"eap\" property. Certificate data is specified using a \"scheme\"; two are currently supported: blob and path. When using the blob scheme (which is backwards compatible with NM 0.7.x) this property should be set to the certificate's DER encoded data. When using the path scheme, this property should be set to the full UTF-8 encoded path of the certificate, prefixed with the string \"file://\" and ending with a terminating NUL byte. This property can be unset even if the EAP method supports CA certificates, but this allows man-in-the-middle attacks and is NOT recommended." }, + { "ca-path", "UTF-8 encoded path to a directory containing PEM or DER formatted certificates to be added to the verification chain in addition to the certificate specified in the \"ca-cert\" property." }, + { "client-cert", "Contains the client certificate if used by the EAP method specified in the \"eap\" property. Certificate data is specified using a \"scheme\"; two are currently supported: blob and path. When using the blob scheme (which is backwards compatible with NM 0.7.x) this property should be set to the certificate's DER encoded data. When using the path scheme, this property should be set to the full UTF-8 encoded path of the certificate, prefixed with the string \"file://\" and ending with a terminating NUL byte." }, + { "eap", "The allowed EAP method to be used when authenticating to the network with 802.1x. Valid methods are: \"leap\", \"md5\", \"tls\", \"peap\", \"ttls\", \"pwd\", and \"fast\". Each method requires different configuration using the properties of this setting; refer to wpa_supplicant documentation for the allowed combinations." }, + { "identity", "Identity string for EAP authentication methods. Often the user's user or login name." }, + { "name", "The setting's name, which uniquely identifies the setting within the connection. Each setting type has a name unique to that type, for example \"ppp\" or \"wireless\" or \"wired\"." }, + { "pac-file", "UTF-8 encoded file path containing PAC for EAP-FAST." }, + { "password", "UTF-8 encoded password used for EAP authentication methods. If both the \"password\" property and the \"password-raw\" property are specified, \"password\" is preferred." }, + { "password-flags", "Flags indicating how to handle the \"password\" property." }, + { "password-raw", "Password used for EAP authentication methods, given as a byte array to allow passwords in other encodings than UTF-8 to be used. If both the \"password\" property and the \"password-raw\" property are specified, \"password\" is preferred." }, + { "password-raw-flags", "Flags indicating how to handle the \"password-raw\" property." }, + { "phase1-fast-provisioning", "Enables or disables in-line provisioning of EAP-FAST credentials when FAST is specified as the EAP method in the \"eap\" property. Recognized values are \"0\" (disabled), \"1\" (allow unauthenticated provisioning), \"2\" (allow authenticated provisioning), and \"3\" (allow both authenticated and unauthenticated provisioning). See the wpa_supplicant documentation for more details." }, + { "phase1-peaplabel", "Forces use of the new PEAP label during key derivation. Some RADIUS servers may require forcing the new PEAP label to interoperate with PEAPv1. Set to \"1\" to force use of the new PEAP label. See the wpa_supplicant documentation for more details." }, + { "phase1-peapver", "Forces which PEAP version is used when PEAP is set as the EAP method in the \"eap\" property. When unset, the version reported by the server will be used. Sometimes when using older RADIUS servers, it is necessary to force the client to use a particular PEAP version. To do so, this property may be set to \"0\" or \"1\" to force that specific PEAP version." }, + { "phase2-altsubject-matches", "List of strings to be matched against the altSubjectName of the certificate presented by the authentication server during the inner \"phase 2\" authentication. If the list is empty, no verification of the server certificate's altSubjectName is performed." }, + { "phase2-auth", "Specifies the allowed \"phase 2\" inner non-EAP authentication methods when an EAP method that uses an inner TLS tunnel is specified in the \"eap\" property. Recognized non-EAP \"phase 2\" methods are \"pap\", \"chap\", \"mschap\", \"mschapv2\", \"gtc\", \"otp\", \"md5\", and \"tls\". Each \"phase 2\" inner method requires specific parameters for successful authentication; see the wpa_supplicant documentation for more details." }, + { "phase2-autheap", "Specifies the allowed \"phase 2\" inner EAP-based authentication methods when an EAP method that uses an inner TLS tunnel is specified in the \"eap\" property. Recognized EAP-based \"phase 2\" methods are \"md5\", \"mschapv2\", \"otp\", \"gtc\", and \"tls\". Each \"phase 2\" inner method requires specific parameters for successful authentication; see the wpa_supplicant documentation for more details." }, + { "phase2-ca-cert", "Contains the \"phase 2\" CA certificate if used by the EAP method specified in the \"phase2-auth\" or \"phase2-autheap\" properties. Certificate data is specified using a \"scheme\"; two are currently supported: blob and path. When using the blob scheme (which is backwards compatible with NM 0.7.x) this property should be set to the certificate's DER encoded data. When using the path scheme, this property should be set to the full UTF-8 encoded path of the certificate, prefixed with the string \"file://\" and ending with a terminating NUL byte. This property can be unset even if the EAP method supports CA certificates, but this allows man-in-the-middle attacks and is NOT recommended." }, + { "phase2-ca-path", "UTF-8 encoded path to a directory containing PEM or DER formatted certificates to be added to the verification chain in addition to the certificate specified in the \"phase2-ca-cert\" property." }, + { "phase2-client-cert", "Contains the \"phase 2\" client certificate if used by the EAP method specified in the \"phase2-auth\" or \"phase2-autheap\" properties. Certificate data is specified using a \"scheme\"; two are currently supported: blob and path. When using the blob scheme (which is backwards compatible with NM 0.7.x) this property should be set to the certificate's DER encoded data. When using the path scheme, this property should be set to the full UTF-8 encoded path of the certificate, prefixed with the string \"file://\" and ending with a terminating NUL byte. This property can be unset even if the EAP method supports CA certificates, but this allows man-in-the-middle attacks and is NOT recommended." }, + { "phase2-private-key", "Contains the \"phase 2\" inner private key when the \"phase2-auth\" or \"phase2-autheap\" property is set to \"tls\". Key data is specified using a \"scheme\"; two are currently supported: blob and path. When using the blob scheme and private keys, this property should be set to the key's encrypted PEM encoded data. When using private keys with the path scheme, this property should be set to the full UTF-8 encoded path of the key, prefixed with the string \"file://\" and ending with a terminating NUL byte. When using PKCS#12 format private keys and the blob scheme, this property should be set to the PKCS#12 data and the \"phase2-private-key-password\" property must be set to password used to decrypt the PKCS#12 certificate and key. When using PKCS#12 files and the path scheme, this property should be set to the full UTF-8 encoded path of the key, prefixed with the string \"file://\" and and ending with a terminating NUL byte, and as with the blob scheme the \"phase2-private-key-password\" property must be set to the password used to decode the PKCS#12 private key and certificate." }, + { "phase2-private-key-password", "The password used to decrypt the \"phase 2\" private key specified in the \"phase2-private-key\" property when the private key either uses the path scheme, or is a PKCS#12 format key." }, + { "phase2-private-key-password-flags", "Flags indicating how to handle the \"phase2-private-key-password\" property." }, + { "phase2-subject-match", "Substring to be matched against the subject of the certificate presented by the authentication server during the inner \"phase 2\" authentication. When unset, no verification of the authentication server certificate's subject is performed." }, + { "pin", "PIN used for EAP authentication methods." }, + { "pin-flags", "Flags indicating how to handle the \"pin\" property." }, + { "private-key", "Contains the private key when the \"eap\" property is set to \"tls\". Key data is specified using a \"scheme\"; two are currently supported: blob and path. When using the blob scheme and private keys, this property should be set to the key's encrypted PEM encoded data. When using private keys with the path scheme, this property should be set to the full UTF-8 encoded path of the key, prefixed with the string \"file://\" and ending with a terminating NUL byte. When using PKCS#12 format private keys and the blob scheme, this property should be set to the PKCS#12 data and the \"private-key-password\" property must be set to password used to decrypt the PKCS#12 certificate and key. When using PKCS#12 files and the path scheme, this property should be set to the full UTF-8 encoded path of the key, prefixed with the string \"file://\" and and ending with a terminating NUL byte, and as with the blob scheme the \"private-key-password\" property must be set to the password used to decode the PKCS#12 private key and certificate. WARNING: \"private-key\" is not a \"secret\" property, and thus unencrypted private key data using the BLOB scheme may be readable by unprivileged users. Private keys should always be encrypted with a private key password to prevent unauthorized access to unencrypted private key data." }, + { "private-key-password", "The password used to decrypt the private key specified in the \"private-key\" property when the private key either uses the path scheme, or if the private key is a PKCS#12 format key." }, + { "private-key-password-flags", "Flags indicating how to handle the \"private-key-password\" property." }, + { "subject-match", "Substring to be matched against the subject of the certificate presented by the authentication server. When unset, no verification of the authentication server certificate's subject is performed." }, + { "system-ca-certs", "When TRUE, overrides the \"ca-path\" and \"phase2-ca-path\" properties using the system CA directory specified at configure time with the --system-ca-path switch. The certificates in this directory are added to the verification chain in addition to any certificates specified by the \"ca-cert\" and \"phase2-ca-cert\" properties." }, +}; + +NmcPropertyDesc setting_802_3_ethernet[] = { + { "auto-negotiate", "If TRUE, allow auto-negotiation of port speed and duplex mode. If FALSE, do not allow auto-negotiation, in which case the \"speed\" and \"duplex\" properties should be set." }, + { "cloned-mac-address", "If specified, request that the device use this MAC address instead of its permanent MAC address. This is known as MAC cloning or spoofing." }, + { "duplex", "If specified, request that the device only use the specified duplex mode. Either \"half\" or \"full\"." }, + { "mac-address", "If specified, this connection will only apply to the Ethernet device whose permanent MAC address matches. This property does not change the MAC address of the device (i.e. MAC spoofing)." }, + { "mac-address-blacklist", "If specified, this connection will never apply to the Ethernet device whose permanent MAC address matches an address in the list. Each MAC address is in the standard hex-digits-and-colons notation (00:11:22:33:44:55)." }, + { "mtu", "If non-zero, only transmit packets of the specified size or smaller, breaking larger packets up into multiple Ethernet frames." }, + { "name", "The setting's name, which uniquely identifies the setting within the connection. Each setting type has a name unique to that type, for example \"ppp\" or \"wireless\" or \"wired\"." }, + { "port", "Specific port type to use if multiple the device supports multiple attachment methods. One of \"tp\" (Twisted Pair), \"aui\" (Attachment Unit Interface), \"bnc\" (Thin Ethernet) or \"mii\" (Media Independent Interface. If the device supports only one port type, this setting is ignored." }, + { "s390-nettype", "s390 network device type; one of \"qeth\", \"lcs\", or \"ctc\", representing the different types of virtual network devices available on s390 systems." }, + { "s390-options", "Dictionary of key/value pairs of s390-specific device options. Both keys and values must be strings. Allowed keys include \"portno\", \"layer2\", \"portname\", \"protocol\", among others. Key names must contain only alphanumeric characters (ie, [a-zA-Z0-9])." }, + { "s390-subchannels", "Identifies specific subchannels that this network device uses for communication with z/VM or s390 host. Like the \"mac-address\" property for non-z/VM devices, this property can be used to ensure this connection only applies to the network device that uses these subchannels. The list should contain exactly 3 strings, and each string may only be composed of hexadecimal characters and the period (.) character." }, + { "speed", "If non-zero, request that the device use only the specified speed. In Mbit/s, ie 100 == 100Mbit/s." }, +}; + +NmcPropertyDesc setting_adsl[] = { + { "encapsulation", "Encapsulation of ADSL connection. Can be \"vcmux\" or \"llc\"." }, + { "name", "The setting's name, which uniquely identifies the setting within the connection. Each setting type has a name unique to that type, for example \"ppp\" or \"wireless\" or \"wired\"." }, + { "password", "Password used to authenticate with the ADSL service." }, + { "password-flags", "Flags indicating how to handle the \"password\" property." }, + { "protocol", "ADSL connection protocol. Can be \"pppoa\", \"pppoe\" or \"ipoatm\"." }, + { "username", "Username used to authenticate with the ADSL service." }, + { "vci", "VCI of ADSL connection" }, + { "vpi", "VPI of ADSL connection" }, +}; + +NmcPropertyDesc setting_bluetooth[] = { + { "bdaddr", "The Bluetooth address of the device." }, + { "name", "The setting's name, which uniquely identifies the setting within the connection. Each setting type has a name unique to that type, for example \"ppp\" or \"wireless\" or \"wired\"." }, + { "type", "Either \"dun\" for Dial-Up Networking connections or \"panu\" for Personal Area Networking connections to devices supporting the NAP profile." }, +}; + +NmcPropertyDesc setting_bond[] = { + { "name", "The setting's name, which uniquely identifies the setting within the connection. Each setting type has a name unique to that type, for example \"ppp\" or \"wireless\" or \"wired\"." }, + { "options", "Dictionary of key/value pairs of bonding options. Both keys and values must be strings. Option names must contain only alphanumeric characters (ie, [a-zA-Z0-9])." }, +}; + +NmcPropertyDesc setting_bridge[] = { + { "ageing-time", "The Ethernet MAC address aging time, in seconds." }, + { "forward-delay", "The Spanning Tree Protocol (STP) forwarding delay, in seconds." }, + { "hello-time", "The Spanning Tree Protocol (STP) hello time, in seconds." }, + { "mac-address", "If specified, the MAC address of bridge. When creating a new bridge, this MAC address will be set. When matching an existing (outside NetworkManager created) bridge, this MAC address must match." }, + { "max-age", "The Spanning Tree Protocol (STP) maximum message age, in seconds." }, + { "name", "The setting's name, which uniquely identifies the setting within the connection. Each setting type has a name unique to that type, for example \"ppp\" or \"wireless\" or \"wired\"." }, + { "priority", "Sets the Spanning Tree Protocol (STP) priority for this bridge. Lower values are \"better\"; the lowest priority bridge will be elected the root bridge." }, + { "stp", "Controls whether Spanning Tree Protocol (STP) is enabled for this bridge." }, +}; + +NmcPropertyDesc setting_bridge_port[] = { + { "hairpin-mode", "Enables or disabled \"hairpin mode\" for the port, which allows frames to be sent back out through the port the frame was received on." }, + { "name", "The setting's name, which uniquely identifies the setting within the connection. Each setting type has a name unique to that type, for example \"ppp\" or \"wireless\" or \"wired\"." }, + { "path-cost", "The Spanning Tree Protocol (STP) port cost for destinations via this port." }, + { "priority", "The Spanning Tree Protocol (STP) priority of this bridge port." }, +}; + +NmcPropertyDesc setting_cdma[] = { + { "name", "The setting's name, which uniquely identifies the setting within the connection. Each setting type has a name unique to that type, for example \"ppp\" or \"wireless\" or \"wired\"." }, + { "number", "The number to dial to establish the connection to the CDMA-based mobile broadband network, if any. If not specified, the default number (#777) is used when required." }, + { "password", "The password used to authenticate with the network, if required. Many providers do not require a password, or accept any password. But if a password is required, it is specified here." }, + { "password-flags", "Flags indicating how to handle the \"password\" property." }, + { "username", "The username used to authenticate with the network, if required. Many providers do not require a username, or accept any username. But if a username is required, it is specified here." }, +}; + +NmcPropertyDesc setting_connection[] = { + { "autoconnect", "Whether or not the connection should be automatically connected by NetworkManager when the resources for the connection are available. TRUE to automatically activate the connection, FALSE to require manual intervention to activate the connection." }, + { "autoconnect-priority", "The autoconnect priority. If the connection is set to autoconnect, connections with higher priority will be preferred. Defaults to 0. The higher number means higher priority." }, + { "gateway-ping-timeout", "If greater than zero, delay success of IP addressing until either the timeout is reached, or an IP gateway replies to a ping." }, + { "id", "A human readable unique identifier for the connection, like \"Work Wi-Fi\" or \"T-Mobile 3G\"." }, + { "interface-name", "The name of the network interface this connection is bound to. If not set, then the connection can be attached to any interface of the appropriate type (subject to restrictions imposed by other settings). For software devices this specifies the name of the created device. For connection types where interface names cannot easily be made persistent (e.g. mobile broadband or USB Ethernet), this property should not be used. Setting this property restricts the interfaces a connection can be used with, and if interface names change or are reordered the connection may be applied to the wrong interface." }, + { "master", "Interface name of the master device or UUID of the master connection." }, + { "name", "The setting's name, which uniquely identifies the setting within the connection. Each setting type has a name unique to that type, for example \"ppp\" or \"wireless\" or \"wired\"." }, + { "permissions", "An array of strings defining what access a given user has to this connection. If this is NULL or empty, all users are allowed to access this connection. Otherwise a user is allowed to access this connection if and only if they are in this list. Each entry is of the form \"[type]:[id]:[reserved]\"; for example, \"user:dcbw:blah\". At this time only the \"user\" [type] is allowed. Any other values are ignored and reserved for future use. [id] is the username that this permission refers to, which may not contain the \":\" character. Any [reserved] information present must be ignored and is reserved for future use. All of [type], [id], and [reserved] must be valid UTF-8." }, + { "read-only", "FALSE if the connection can be modified using the provided settings service's D-Bus interface with the right privileges, or TRUE if the connection is read-only and cannot be modified." }, + { "secondaries", "List of connection UUIDs that should be activated when the base connection itself is activated. Currently only VPN connections are supported." }, + { "slave-type", "Setting name of the device type of this slave's master connection (eg, \"bond\"), or NULL if this connection is not a slave." }, + { "timestamp", "The time, in seconds since the Unix Epoch, that the connection was last _successfully_ fully activated. NetworkManager updates the connection timestamp periodically when the connection is active to ensure that an active connection has the latest timestamp. The property is only meant for reading (changes to this property will not be preserved)." }, + { "type", "Base type of the connection. For hardware-dependent connections, should contain the setting name of the hardware-type specific setting (ie, \"802-3-ethernet\" or \"802-11-wireless\" or \"bluetooth\", etc), and for non-hardware dependent connections like VPN or otherwise, should contain the setting name of that setting type (ie, \"vpn\" or \"bridge\", etc)." }, + { "uuid", "A universally unique identifier for the connection, for example generated with libuuid. It should be assigned when the connection is created, and never changed as long as the connection still applies to the same network. For example, it should not be changed when the \"id\" property or NMSettingIP4Config changes, but might need to be re-created when the Wi-Fi SSID, mobile broadband network provider, or \"type\" property changes. The UUID must be in the format \"2815492f-7e56-435e-b2e9-246bd7cdc664\" (ie, contains only hexadecimal characters and \"-\")." }, + { "zone", "The trust level of a the connection. Free form case-insensitive string (for example \"Home\", \"Work\", \"Public\"). NULL or unspecified zone means the connection will be placed in the default zone as defined by the firewall." }, +}; + +NmcPropertyDesc setting_dcb[] = { + { "app-fcoe-flags", "Specifies the NMSettingDcbFlags for the DCB FCoE application. Flags may be any combination of NM_SETTING_DCB_FLAG_ENABLE (0x1), NM_SETTING_DCB_FLAG_ADVERTISE (0x2), and NM_SETTING_DCB_FLAG_WILLING (0x4)." }, + { "app-fcoe-mode", "The FCoE controller mode; either \"fabric\" (default) or \"vn2vn\"." }, + { "app-fcoe-priority", "The highest User Priority (0 - 7) which FCoE frames should use, or -1 for default priority. Only used when the \"app-fcoe-flags\" property includes the NM_SETTING_DCB_FLAG_ENABLE (0x1) flag." }, + { "app-fip-flags", "Specifies the NMSettingDcbFlags for the DCB FIP application. Flags may be any combination of NM_SETTING_DCB_FLAG_ENABLE (0x1), NM_SETTING_DCB_FLAG_ADVERTISE (0x2), and NM_SETTING_DCB_FLAG_WILLING (0x4)." }, + { "app-fip-priority", "The highest User Priority (0 - 7) which FIP frames should use, or -1 for default priority. Only used when the \"app-fip-flags\" property includes the NM_SETTING_DCB_FLAG_ENABLE (0x1) flag." }, + { "app-iscsi-flags", "Specifies the NMSettingDcbFlags for the DCB iSCSI application. Flags may be any combination of NM_SETTING_DCB_FLAG_ENABLE (0x1), NM_SETTING_DCB_FLAG_ADVERTISE (0x2), and NM_SETTING_DCB_FLAG_WILLING (0x4)." }, + { "app-iscsi-priority", "The highest User Priority (0 - 7) which iSCSI frames should use, or -1 for default priority. Only used when the \"app-iscsi-flags\" property includes the NM_SETTING_DCB_FLAG_ENABLE (0x1) flag." }, + { "name", "The setting's name, which uniquely identifies the setting within the connection. Each setting type has a name unique to that type, for example \"ppp\" or \"wireless\" or \"wired\"." }, + { "priority-bandwidth", "An array of 8 uint values, where the array index corresponds to the User Priority (0 - 7) and the value indicates the percentage of bandwidth of the priority's assigned group that the priority may use. The sum of all percentages for priorities which belong to the same group must total 100 percent." }, + { "priority-flow-control", "An array of 8 boolean values, where the array index corresponds to the User Priority (0 - 7) and the value indicates whether or not the corresponding priority should transmit priority pause." }, + { "priority-flow-control-flags", "Specifies the NMSettingDcbFlags for DCB Priority Flow Control (PFC). Flags may be any combination of NM_SETTING_DCB_FLAG_ENABLE (0x1), NM_SETTING_DCB_FLAG_ADVERTISE (0x2), and NM_SETTING_DCB_FLAG_WILLING (0x4)." }, + { "priority-group-bandwidth", "An array of 8 uint values, where the array index corresponds to the Priority Group ID (0 - 7) and the value indicates the percentage of link bandwidth allocated to that group. Allowed values are 0 - 100, and the sum of all values must total 100 percent." }, + { "priority-group-flags", "Specifies the NMSettingDcbFlags for DCB Priority Groups. Flags may be any combination of NM_SETTING_DCB_FLAG_ENABLE (0x1), NM_SETTING_DCB_FLAG_ADVERTISE (0x2), and NM_SETTING_DCB_FLAG_WILLING (0x4)." }, + { "priority-group-id", "An array of 8 uint values, where the array index corresponds to the User Priority (0 - 7) and the value indicates the Priority Group ID. Allowed Priority Group ID values are 0 - 7 or 15 for the unrestricted group." }, + { "priority-strict-bandwidth", "An array of 8 boolean values, where the array index corresponds to the User Priority (0 - 7) and the value indicates whether or not the priority may use all of the bandwidth allocated to its assigned group." }, + { "priority-traffic-class", "An array of 8 uint values, where the array index corresponds to the User Priority (0 - 7) and the value indicates the traffic class (0 - 7) to which the priority is mapped." }, +}; + +NmcPropertyDesc setting_generic[] = { + { "name", "The setting's name, which uniquely identifies the setting within the connection. Each setting type has a name unique to that type, for example \"ppp\" or \"wireless\" or \"wired\"." }, +}; + +NmcPropertyDesc setting_gsm[] = { + { "apn", "The GPRS Access Point Name specifying the APN used when establishing a data session with the GSM-based network. The APN often determines how the user will be billed for their network usage and whether the user has access to the Internet or just a provider-specific walled-garden, so it is important to use the correct APN for the user's mobile broadband plan. The APN may only be composed of the characters a-z, 0-9, ., and - per GSM 03.60 Section 14.9." }, + { "home-only", "When TRUE, only connections to the home network will be allowed. Connections to roaming networks will not be made." }, + { "name", "The setting's name, which uniquely identifies the setting within the connection. Each setting type has a name unique to that type, for example \"ppp\" or \"wireless\" or \"wired\"." }, + { "network-id", "The Network ID (GSM LAI format, ie MCC-MNC) to force specific network registration. If the Network ID is specified, NetworkManager will attempt to force the device to register only on the specified network. This can be used to ensure that the device does not roam when direct roaming control of the device is not otherwise possible." }, + { "number", "Number to dial when establishing a PPP data session with the GSM-based mobile broadband network. Many modems do not require PPP for connections to the mobile network and thus this property should be left blank, which allows NetworkManager to select the appropriate settings automatically." }, + { "password", "The password used to authenticate with the network, if required. Many providers do not require a password, or accept any password. But if a password is required, it is specified here." }, + { "password-flags", "Flags indicating how to handle the \"password\" property." }, + { "pin", "If the SIM is locked with a PIN it must be unlocked before any other operations are requested. Specify the PIN here to allow operation of the device." }, + { "pin-flags", "Flags indicating how to handle the \"pin\" property." }, + { "username", "The username used to authenticate with the network, if required. Many providers do not require a username, or accept any username. But if a username is required, it is specified here." }, +}; + +NmcPropertyDesc setting_infiniband[] = { + { "mac-address", "If specified, this connection will only apply to the IPoIB device whose permanent MAC address matches. This property does not change the MAC address of the device (i.e. MAC spoofing)." }, + { "mtu", "If non-zero, only transmit packets of the specified size or smaller, breaking larger packets up into multiple frames." }, + { "name", "The setting's name, which uniquely identifies the setting within the connection. Each setting type has a name unique to that type, for example \"ppp\" or \"wireless\" or \"wired\"." }, + { "p-key", "The InfiniBand P_Key to use for this device. A value of -1 means to use the default P_Key (aka \"the P_Key at index 0\"). Otherwise it is a 16-bit unsigned integer, whose high bit is set if it is a \"full membership\" P_Key." }, + { "parent", "The interface name of the parent device of this device. Normally NULL, but if the \"p_key\" property is set, then you must specify the base device by setting either this property or \"mac-address\"." }, + { "transport-mode", "The IP-over-InfiniBand transport mode. Either \"datagram\" or \"connected\"." }, +}; + +NmcPropertyDesc setting_ipv4[] = { + { "addresses", "Array of IP addresses." }, + { "dhcp-client-id", "A string sent to the DHCP server to identify the local machine which the DHCP server may use to customize the DHCP lease and options." }, + { "dhcp-hostname", "If the \"dhcp-send-hostname\" property is TRUE, then the specified name will be sent to the DHCP server when acquiring a lease." }, + { "dhcp-send-hostname", "If TRUE, a hostname is sent to the DHCP server when acquiring a lease. Some DHCP servers use this hostname to update DNS databases, essentially providing a static hostname for the computer. If the \"dhcp-hostname\" property is NULL and this property is TRUE, the current persistent hostname of the computer is sent." }, + { "dns", "Array of IP addresses of DNS servers." }, + { "dns-search", "Array of DNS search domains." }, + { "gateway", "The gateway associated with this configuration. This is only meaningful if \"addresses\" is also set." }, + { "ignore-auto-dns", "When \"method\" is set to \"auto\" and this property to TRUE, automatically configured nameservers and search domains are ignored and only nameservers and search domains specified in the \"dns\" and \"dns-search\" properties, if any, are used." }, + { "ignore-auto-routes", "When \"method\" is set to \"auto\" and this property to TRUE, automatically configured routes are ignored and only routes specified in the \"routes\" property, if any, are used." }, + { "may-fail", "If TRUE, allow overall network configuration to proceed even if the configuration specified by this property times out. Note that at least one IP configuration must succeed or overall network configuration will still fail. For example, in IPv6-only networks, setting this property to TRUE on the NMSettingIP4Config allows the overall network configuration to succeed if IPv4 configuration fails but IPv6 configuration completes successfully." }, + { "method", "IP configuration method. NMSettingIP4Config and NMSettingIP6Config both support \"auto\", \"manual\", and \"link-local\". See the subclass-specific documentation for other values. In general, for the \"auto\" method, properties such as \"dns\" and \"routes\" specify information that is added on to the information returned from automatic configuration. The \"ignore-auto-routes\" and \"ignore-auto-dns\" properties modify this behavior. For methods that imply no upstream network, such as \"shared\" or \"link-local\", these properties must be empty." }, + { "name", "The setting's name, which uniquely identifies the setting within the connection. Each setting type has a name unique to that type, for example \"ppp\" or \"wireless\" or \"wired\"." }, + { "never-default", "If TRUE, this connection will never be the default connection for this IP type, meaning it will never be assigned the default route by NetworkManager." }, + { "route-metric", "The default metric for routes that don't explicitly specify a metric. The default value -1 means that the metric is choosen automatically based on the device type. The metric applies to dynamic routes, manual (static) routes that don't have an explicit metric setting, address prefix routes, and the default route. Note that for IPv6, the kernel accepts zero (0) but coerces it to 1024 (user default). Hence, setting this property to zero effectively mean setting it to 1024. For IPv4, zero is a regular value for the metric." }, + { "routes", "Array of IP routes." }, +}; + +NmcPropertyDesc setting_ipv6[] = { + { "addresses", "Array of IP addresses." }, + { "dhcp-hostname", "If the \"dhcp-send-hostname\" property is TRUE, then the specified name will be sent to the DHCP server when acquiring a lease." }, + { "dhcp-send-hostname", "If TRUE, a hostname is sent to the DHCP server when acquiring a lease. Some DHCP servers use this hostname to update DNS databases, essentially providing a static hostname for the computer. If the \"dhcp-hostname\" property is NULL and this property is TRUE, the current persistent hostname of the computer is sent." }, + { "dns", "Array of IP addresses of DNS servers." }, + { "dns-search", "Array of DNS search domains." }, + { "gateway", "The gateway associated with this configuration. This is only meaningful if \"addresses\" is also set." }, + { "ignore-auto-dns", "When \"method\" is set to \"auto\" and this property to TRUE, automatically configured nameservers and search domains are ignored and only nameservers and search domains specified in the \"dns\" and \"dns-search\" properties, if any, are used." }, + { "ignore-auto-routes", "When \"method\" is set to \"auto\" and this property to TRUE, automatically configured routes are ignored and only routes specified in the \"routes\" property, if any, are used." }, + { "ip6-privacy", "Configure IPv6 Privacy Extensions for SLAAC, described in RFC4941. If enabled, it makes the kernel generate a temporary IPv6 address in addition to the public one generated from MAC address via modified EUI-64. This enhances privacy, but could cause problems in some applications, on the other hand. The permitted values are: 0: disabled, 1: enabled (prefer public address), 2: enabled (prefer temporary addresses)." }, + { "may-fail", "If TRUE, allow overall network configuration to proceed even if the configuration specified by this property times out. Note that at least one IP configuration must succeed or overall network configuration will still fail. For example, in IPv6-only networks, setting this property to TRUE on the NMSettingIP4Config allows the overall network configuration to succeed if IPv4 configuration fails but IPv6 configuration completes successfully." }, + { "method", "IP configuration method. NMSettingIP4Config and NMSettingIP6Config both support \"auto\", \"manual\", and \"link-local\". See the subclass-specific documentation for other values. In general, for the \"auto\" method, properties such as \"dns\" and \"routes\" specify information that is added on to the information returned from automatic configuration. The \"ignore-auto-routes\" and \"ignore-auto-dns\" properties modify this behavior. For methods that imply no upstream network, such as \"shared\" or \"link-local\", these properties must be empty." }, + { "name", "The setting's name, which uniquely identifies the setting within the connection. Each setting type has a name unique to that type, for example \"ppp\" or \"wireless\" or \"wired\"." }, + { "never-default", "If TRUE, this connection will never be the default connection for this IP type, meaning it will never be assigned the default route by NetworkManager." }, + { "route-metric", "The default metric for routes that don't explicitly specify a metric. The default value -1 means that the metric is choosen automatically based on the device type. The metric applies to dynamic routes, manual (static) routes that don't have an explicit metric setting, address prefix routes, and the default route. Note that for IPv6, the kernel accepts zero (0) but coerces it to 1024 (user default). Hence, setting this property to zero effectively mean setting it to 1024. For IPv4, zero is a regular value for the metric." }, + { "routes", "Array of IP routes." }, +}; + +NmcPropertyDesc setting_ppp[] = { + { "baud", "If non-zero, instruct pppd to set the serial port to the specified baudrate. This value should normally be left as 0 to automatically choose the speed." }, + { "crtscts", "If TRUE, specify that pppd should set the serial port to use hardware flow control with RTS and CTS signals. This value should normally be set to FALSE." }, + { "lcp-echo-failure", "If non-zero, instruct pppd to presume the connection to the peer has failed if the specified number of LCP echo-requests go unanswered by the peer. The \"lcp-echo-interval\" property must also be set to a non-zero value if this property is used." }, + { "lcp-echo-interval", "If non-zero, instruct pppd to send an LCP echo-request frame to the peer every n seconds (where n is the specified value). Note that some PPP peers will respond to echo requests and some will not, and it is not possible to autodetect this." }, + { "mppe-stateful", "If TRUE, stateful MPPE is used. See pppd documentation for more information on stateful MPPE." }, + { "mru", "If non-zero, instruct pppd to request that the peer send packets no larger than the specified size. If non-zero, the MRU should be between 128 and 16384." }, + { "mtu", "If non-zero, instruct pppd to send packets no larger than the specified size." }, + { "name", "The setting's name, which uniquely identifies the setting within the connection. Each setting type has a name unique to that type, for example \"ppp\" or \"wireless\" or \"wired\"." }, + { "no-vj-comp", "If TRUE, Van Jacobsen TCP header compression will not be requested." }, + { "noauth", "If TRUE, do not require the other side (usually the PPP server) to authenticate itself to the client. If FALSE, require authentication from the remote side. In almost all cases, this should be TRUE." }, + { "nobsdcomp", "If TRUE, BSD compression will not be requested." }, + { "nodeflate", "If TRUE, \"deflate\" compression will not be requested." }, + { "refuse-chap", "If TRUE, the CHAP authentication method will not be used." }, + { "refuse-eap", "If TRUE, the EAP authentication method will not be used." }, + { "refuse-mschap", "If TRUE, the MSCHAP authentication method will not be used." }, + { "refuse-mschapv2", "If TRUE, the MSCHAPv2 authentication method will not be used." }, + { "refuse-pap", "If TRUE, the PAP authentication method will not be used." }, + { "require-mppe", "If TRUE, MPPE (Microsoft Point-to-Point Encrpytion) will be required for the PPP session. If either 64-bit or 128-bit MPPE is not available the session will fail. Note that MPPE is not used on mobile broadband connections." }, + { "require-mppe-128", "If TRUE, 128-bit MPPE (Microsoft Point-to-Point Encrpytion) will be required for the PPP session, and the \"require-mppe\" property must also be set to TRUE. If 128-bit MPPE is not available the session will fail." }, +}; + +NmcPropertyDesc setting_pppoe[] = { + { "name", "The setting's name, which uniquely identifies the setting within the connection. Each setting type has a name unique to that type, for example \"ppp\" or \"wireless\" or \"wired\"." }, + { "password", "Password used to authenticate with the PPPoE service." }, + { "password-flags", "Flags indicating how to handle the \"password\" property." }, + { "service", "If specified, instruct PPPoE to only initiate sessions with access concentrators that provide the specified service. For most providers, this should be left blank. It is only required if there are multiple access concentrators or a specific service is known to be required." }, + { "username", "Username used to authenticate with the PPPoE service." }, +}; + +NmcPropertyDesc setting_serial[] = { + { "baud", "Speed to use for communication over the serial port. Note that this value usually has no effect for mobile broadband modems as they generally ignore speed settings and use the highest available speed." }, + { "bits", "Byte-width of the serial communication. The 8 in \"8n1\" for example." }, + { "name", "The setting's name, which uniquely identifies the setting within the connection. Each setting type has a name unique to that type, for example \"ppp\" or \"wireless\" or \"wired\"." }, + { "parity", "Parity setting of the serial port." }, + { "send-delay", "Time to delay between each byte sent to the modem, in microseconds." }, + { "stopbits", "Number of stop bits for communication on the serial port. Either 1 or 2. The 1 in \"8n1\" for example." }, +}; + +NmcPropertyDesc setting_team[] = { + { "config", "The JSON configuration for the team network interface. The property should contain raw JSON configuration data suitable for teamd, because the value is passed directly to teamd. If not specified, the default configuration is used. See man teamd.conf for the format details." }, + { "name", "The setting's name, which uniquely identifies the setting within the connection. Each setting type has a name unique to that type, for example \"ppp\" or \"wireless\" or \"wired\"." }, +}; + +NmcPropertyDesc setting_team_port[] = { + { "config", "The JSON configuration for the team port. The property should contain raw JSON configuration data suitable for teamd, because the value is passed directly to teamd. If not specified, the default configuration is used. See man teamd.conf for the format details." }, + { "name", "The setting's name, which uniquely identifies the setting within the connection. Each setting type has a name unique to that type, for example \"ppp\" or \"wireless\" or \"wired\"." }, +}; + +NmcPropertyDesc setting_vlan[] = { + { "egress-priority-map", "For outgoing packets, a list of mappings from Linux SKB priorities to 802.1p priorities. The mapping is given in the format \"from:to\" where both \"from\" and \"to\" are unsigned integers, ie \"7:3\"." }, + { "flags", "One or more flags which control the behavior and features of the VLAN interface. Flags include NM_VLAN_FLAG_REORDER_HEADERS (0x1) (reordering of output packet headers), NM_VLAN_FLAG_GVRP (0x2) (use of the GVRP protocol), and NM_VLAN_FLAG_LOOSE_BINDING (0x4) (loose binding of the interface to its master device's operating state)." }, + { "id", "The VLAN identifier that the interface created by this connection should be assigned." }, + { "ingress-priority-map", "For incoming packets, a list of mappings from 802.1p priorities to Linux SKB priorities. The mapping is given in the format \"from:to\" where both \"from\" and \"to\" are unsigned integers, ie \"7:3\"." }, + { "name", "The setting's name, which uniquely identifies the setting within the connection. Each setting type has a name unique to that type, for example \"ppp\" or \"wireless\" or \"wired\"." }, + { "parent", "If given, specifies the parent interface name or parent connection UUID from which this VLAN interface should be created. If this property is not specified, the connection must contain an \"802-3-ethernet\" setting with a \"mac-address\" property." }, +}; + +NmcPropertyDesc setting_vpn[] = { + { "data", "Dictionary of key/value pairs of VPN plugin specific data. Both keys and values must be strings." }, + { "name", "The setting's name, which uniquely identifies the setting within the connection. Each setting type has a name unique to that type, for example \"ppp\" or \"wireless\" or \"wired\"." }, + { "persistent", "If the VPN service supports persistence, and this property is TRUE, the VPN will attempt to stay connected across link changes and outages, until explicitly disconnected." }, + { "secrets", "Dictionary of key/value pairs of VPN plugin specific secrets like passwords or private keys. Both keys and values must be strings." }, + { "service-type", "D-Bus service name of the VPN plugin that this setting uses to connect to its network. i.e. org.freedesktop.NetworkManager.vpnc for the vpnc plugin." }, + { "user-name", "If the VPN connection requires a user name for authentication, that name should be provided here. If the connection is available to more than one user, and the VPN requires each user to supply a different name, then leave this property empty. If this property is empty, NetworkManager will automatically supply the username of the user which requested the VPN connection." }, +}; + +NmcPropertyDesc setting_wimax[] = { + { "mac-address", "If specified, this connection will only apply to the WiMAX device whose MAC address matches. This property does not change the MAC address of the device (known as MAC spoofing)." }, + { "name", "The setting's name, which uniquely identifies the setting within the connection. Each setting type has a name unique to that type, for example \"ppp\" or \"wireless\" or \"wired\"." }, + { "network-name", "Network Service Provider (NSP) name of the WiMAX network this connection should use." }, +}; + + +typedef struct { + const char *name; + NmcPropertyDesc *properties; + int n_properties; +} NmcSettingDesc; + +NmcSettingDesc all_settings[] = { + + { "802-11-olpc-mesh", setting_802_11_olpc_mesh, 4 }, + { "802-11-wireless", setting_802_11_wireless, 14 }, + { "802-11-wireless-security", setting_802_11_wireless_security, 18 }, + { "802-1x", setting_802_1x, 33 }, + { "802-3-ethernet", setting_802_3_ethernet, 12 }, + { "adsl", setting_adsl, 8 }, + { "bluetooth", setting_bluetooth, 3 }, + { "bond", setting_bond, 2 }, + { "bridge", setting_bridge, 8 }, + { "bridge-port", setting_bridge_port, 4 }, + { "cdma", setting_cdma, 5 }, + { "connection", setting_connection, 15 }, + { "dcb", setting_dcb, 16 }, + { "generic", setting_generic, 1 }, + { "gsm", setting_gsm, 10 }, + { "infiniband", setting_infiniband, 6 }, + { "ipv4", setting_ipv4, 15 }, + { "ipv6", setting_ipv6, 15 }, + { "ppp", setting_ppp, 19 }, + { "pppoe", setting_pppoe, 5 }, + { "serial", setting_serial, 6 }, + { "team", setting_team, 2 }, + { "team-port", setting_team_port, 2 }, + { "vlan", setting_vlan, 6 }, + { "vpn", setting_vpn, 6 }, + { "wimax", setting_wimax, 3 }, +}; + +static int +find_by_name (gconstpointer keyv, gconstpointer cmpv) +{ + const char *key = keyv; + struct { const char *name; gpointer data; } *cmp = (gpointer)cmpv; + + return strcmp (key, cmp->name); +} + +static const char * +nmc_setting_get_property_doc (NMSetting *setting, const char *prop) +{ + NmcSettingDesc *setting_desc; + NmcPropertyDesc *property_desc; + + setting_desc = bsearch (nm_setting_get_name (setting), + all_settings, G_N_ELEMENTS (all_settings), + sizeof (NmcSettingDesc), find_by_name); + if (!setting_desc) + return NULL; + property_desc = bsearch (prop, + setting_desc->properties, setting_desc->n_properties, + sizeof (NmcPropertyDesc), find_by_name); + if (!property_desc) + return NULL; + return property_desc->docs; +} + \ No newline at end of file diff --git a/clients/cli/settings-docs.xsl b/clients/cli/settings-docs.xsl new file mode 100644 index 00000000..996644bd --- /dev/null +++ b/clients/cli/settings-docs.xsl @@ -0,0 +1,86 @@ +<?xml version="1.0" encoding="UTF-8"?> +<xsl:stylesheet version="1.0" + xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> + + <xsl:output + method="text" + doctype-public="-//OASIS//DTD DocBook XML V4.3//EN" + doctype-system="http://www.oasis-open.org/docbook/xml/4.3/docbookx.dtd" + /> + + <xsl:template match="nm-setting-docs">/* Generated file. Do not edit. */ + +typedef struct { + const char *name; + const char *docs; +} NmcPropertyDesc; +<xsl:apply-templates select="setting" mode="properties"><xsl:sort select="@name"/></xsl:apply-templates> + +typedef struct { + const char *name; + NmcPropertyDesc *properties; + int n_properties; +} NmcSettingDesc; + +NmcSettingDesc all_settings[] = { +<xsl:apply-templates select="setting" mode="settings"><xsl:sort select="@name"/></xsl:apply-templates> +}; + +static int +find_by_name (gconstpointer keyv, gconstpointer cmpv) +{ + const char *key = keyv; + struct { const char *name; gpointer data; } *cmp = (gpointer)cmpv; + + return strcmp (key, cmp->name); +} + +static const char * +nmc_setting_get_property_doc (NMSetting *setting, const char *prop) +{ + NmcSettingDesc *setting_desc; + NmcPropertyDesc *property_desc; + + setting_desc = bsearch (nm_setting_get_name (setting), + all_settings, G_N_ELEMENTS (all_settings), + sizeof (NmcSettingDesc), find_by_name); + if (!setting_desc) + return NULL; + property_desc = bsearch (prop, + setting_desc->properties, setting_desc->n_properties, + sizeof (NmcPropertyDesc), find_by_name); + if (!property_desc) + return NULL; + return property_desc->docs; +} + </xsl:template> + + <xsl:template match="setting" mode="properties"> +NmcPropertyDesc setting_<xsl:value-of select="translate(@name,'-','_')"/>[] = {<xsl:apply-templates select="property"><xsl:sort select="@name"/></xsl:apply-templates> +}; + </xsl:template> + + <xsl:template match="property"> + <xsl:variable name="docs"> + <xsl:call-template name="escape_quotes"> + <xsl:with-param name="string" select="@description"/> + </xsl:call-template> + </xsl:variable> + { "<xsl:value-of select="@name"/>", "<xsl:value-of select="$docs"/>" },</xsl:template> + + <xsl:template match="setting" mode="settings"> + { "<xsl:value-of select="@name"/>", setting_<xsl:value-of select="translate(@name,'-','_')"/>, <xsl:value-of select="count(./property)"/> },</xsl:template> + + <xsl:template name="escape_quotes"> + <xsl:param name="string" /> + <xsl:choose> + <xsl:when test="contains($string, '"')"> + <xsl:value-of select="substring-before($string, '"')" />\"<xsl:call-template name="escape_quotes"><xsl:with-param name="string" select="substring-after($string, '"')" /></xsl:call-template> + </xsl:when> + <xsl:otherwise> + <xsl:value-of select="$string" /> + </xsl:otherwise> + </xsl:choose> + </xsl:template> + +</xsl:stylesheet> diff --git a/clients/cli/settings.c b/clients/cli/settings.c new file mode 100644 index 00000000..7d2a46c8 --- /dev/null +++ b/clients/cli/settings.c @@ -0,0 +1,7445 @@ +/* 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 <stdlib.h> +#include <arpa/inet.h> + +#include <glib.h> +#include <glib/gi18n.h> + +#include "utils.h" +#include "common.h" +#include "settings.h" +#include "nm-glib-compat.h" + +/* Forward declarations */ +static char *wep_key_type_to_string (NMWepKeyType type); + +/* Helper macro to define fields */ +#define SETTING_FIELD(setting, width) { setting, N_(setting), width, NULL, FALSE, FALSE, 0 } + +/* Available fields for NM_SETTING_CONNECTION_SETTING_NAME */ +NmcOutputField nmc_fields_setting_connection[] = { + SETTING_FIELD ("name", 15), /* 0 */ + SETTING_FIELD (NM_SETTING_CONNECTION_ID, 25), /* 1 */ + SETTING_FIELD (NM_SETTING_CONNECTION_UUID, 38), /* 2 */ + SETTING_FIELD (NM_SETTING_CONNECTION_INTERFACE_NAME, 20), /* 3 */ + SETTING_FIELD (NM_SETTING_CONNECTION_TYPE, 17), /* 4 */ + SETTING_FIELD (NM_SETTING_CONNECTION_AUTOCONNECT, 13), /* 5 */ + SETTING_FIELD (NM_SETTING_CONNECTION_AUTOCONNECT_PRIORITY, 10), /* 6 */ + SETTING_FIELD (NM_SETTING_CONNECTION_TIMESTAMP, 10), /* 7 */ + SETTING_FIELD (NM_SETTING_CONNECTION_READ_ONLY, 10), /* 8 */ + SETTING_FIELD (NM_SETTING_CONNECTION_PERMISSIONS, 30), /* 9 */ + SETTING_FIELD (NM_SETTING_CONNECTION_ZONE, 10), /* 10 */ + SETTING_FIELD (NM_SETTING_CONNECTION_MASTER, 20), /* 11 */ + SETTING_FIELD (NM_SETTING_CONNECTION_SLAVE_TYPE, 20), /* 12 */ + SETTING_FIELD (NM_SETTING_CONNECTION_SECONDARIES, 40), /* 13 */ + SETTING_FIELD (NM_SETTING_CONNECTION_GATEWAY_PING_TIMEOUT, 30), /* 14 */ + {NULL, NULL, 0, NULL, FALSE, FALSE, 0} +}; +#define NMC_FIELDS_SETTING_CONNECTION_ALL "name"","\ + NM_SETTING_CONNECTION_ID","\ + NM_SETTING_CONNECTION_UUID","\ + NM_SETTING_CONNECTION_INTERFACE_NAME","\ + NM_SETTING_CONNECTION_TYPE","\ + NM_SETTING_CONNECTION_AUTOCONNECT","\ + NM_SETTING_CONNECTION_AUTOCONNECT_PRIORITY","\ + NM_SETTING_CONNECTION_TIMESTAMP","\ + NM_SETTING_CONNECTION_READ_ONLY","\ + NM_SETTING_CONNECTION_PERMISSIONS","\ + NM_SETTING_CONNECTION_ZONE","\ + NM_SETTING_CONNECTION_MASTER","\ + NM_SETTING_CONNECTION_SLAVE_TYPE","\ + NM_SETTING_CONNECTION_SECONDARIES","\ + NM_SETTING_CONNECTION_GATEWAY_PING_TIMEOUT +#define NMC_FIELDS_SETTING_CONNECTION_COMMON NMC_FIELDS_SETTING_CONNECTION_ALL + +/* Available fields for NM_SETTING_WIRED_SETTING_NAME */ +NmcOutputField nmc_fields_setting_wired[] = { + SETTING_FIELD ("name", 17), /* 0 */ + SETTING_FIELD (NM_SETTING_WIRED_PORT, 8), /* 1 */ + SETTING_FIELD (NM_SETTING_WIRED_SPEED, 10), /* 2 */ + SETTING_FIELD (NM_SETTING_WIRED_DUPLEX, 10), /* 3 */ + SETTING_FIELD (NM_SETTING_WIRED_AUTO_NEGOTIATE, 15), /* 4 */ + SETTING_FIELD (NM_SETTING_WIRED_MAC_ADDRESS, 19), /* 5 */ + SETTING_FIELD (NM_SETTING_WIRED_CLONED_MAC_ADDRESS, 19), /* 6 */ + SETTING_FIELD (NM_SETTING_WIRED_MAC_ADDRESS_BLACKLIST, 39), /* 7 */ + SETTING_FIELD (NM_SETTING_WIRED_MTU, 6), /* 8 */ + SETTING_FIELD (NM_SETTING_WIRED_S390_SUBCHANNELS, 20), /* 9 */ + SETTING_FIELD (NM_SETTING_WIRED_S390_NETTYPE, 15), /* 10 */ + SETTING_FIELD (NM_SETTING_WIRED_S390_OPTIONS, 20), /* 11 */ + {NULL, NULL, 0, NULL, FALSE, FALSE, 0} +}; +#define NMC_FIELDS_SETTING_WIRED_ALL "name"","\ + NM_SETTING_WIRED_PORT","\ + NM_SETTING_WIRED_SPEED","\ + NM_SETTING_WIRED_DUPLEX","\ + NM_SETTING_WIRED_AUTO_NEGOTIATE","\ + NM_SETTING_WIRED_MAC_ADDRESS","\ + NM_SETTING_WIRED_CLONED_MAC_ADDRESS","\ + NM_SETTING_WIRED_MAC_ADDRESS_BLACKLIST","\ + NM_SETTING_WIRED_MTU","\ + NM_SETTING_WIRED_S390_SUBCHANNELS","\ + NM_SETTING_WIRED_S390_NETTYPE","\ + NM_SETTING_WIRED_S390_OPTIONS +#define NMC_FIELDS_SETTING_WIRED_COMMON NMC_FIELDS_SETTING_WIRED_ALL + +/* Available fields for NM_SETTING_802_1X_SETTING_NAME */ +NmcOutputField nmc_fields_setting_8021X[] = { + SETTING_FIELD ("name", 10), /* 0 */ + SETTING_FIELD (NM_SETTING_802_1X_EAP, 10), /* 1 */ + SETTING_FIELD (NM_SETTING_802_1X_IDENTITY, 15), /* 2 */ + SETTING_FIELD (NM_SETTING_802_1X_ANONYMOUS_IDENTITY, 15), /* 3 */ + SETTING_FIELD (NM_SETTING_802_1X_PAC_FILE, 15), /* 4 */ + SETTING_FIELD (NM_SETTING_802_1X_CA_CERT, 10), /* 5 */ + SETTING_FIELD (NM_SETTING_802_1X_CA_PATH, 10), /* 6 */ + SETTING_FIELD (NM_SETTING_802_1X_SUBJECT_MATCH, 10), /* 7 */ + SETTING_FIELD (NM_SETTING_802_1X_ALTSUBJECT_MATCHES, 10), /* 8 */ + SETTING_FIELD (NM_SETTING_802_1X_CLIENT_CERT, 10), /* 9 */ + SETTING_FIELD (NM_SETTING_802_1X_PHASE1_PEAPVER, 10), /* 10 */ + SETTING_FIELD (NM_SETTING_802_1X_PHASE1_PEAPLABEL, 10), /* 11 */ + SETTING_FIELD (NM_SETTING_802_1X_PHASE1_FAST_PROVISIONING, 10), /* 12 */ + SETTING_FIELD (NM_SETTING_802_1X_PHASE2_AUTH, 10), /* 13 */ + SETTING_FIELD (NM_SETTING_802_1X_PHASE2_AUTHEAP, 10), /* 14 */ + SETTING_FIELD (NM_SETTING_802_1X_PHASE2_CA_CERT, 20), /* 15 */ + SETTING_FIELD (NM_SETTING_802_1X_PHASE2_CA_PATH, 20), /* 16 */ + SETTING_FIELD (NM_SETTING_802_1X_PHASE2_SUBJECT_MATCH, 10), /* 17 */ + SETTING_FIELD (NM_SETTING_802_1X_PHASE2_ALTSUBJECT_MATCHES, 10), /* 18 */ + SETTING_FIELD (NM_SETTING_802_1X_PHASE2_CLIENT_CERT, 20), /* 19 */ + SETTING_FIELD (NM_SETTING_802_1X_PASSWORD, 10), /* 20 */ + SETTING_FIELD (NM_SETTING_802_1X_PASSWORD_FLAGS, 20), /* 21 */ + SETTING_FIELD (NM_SETTING_802_1X_PASSWORD_RAW, 20), /* 22 */ + SETTING_FIELD (NM_SETTING_802_1X_PASSWORD_RAW_FLAGS, 20), /* 23 */ + SETTING_FIELD (NM_SETTING_802_1X_PRIVATE_KEY, 15), /* 24 */ + SETTING_FIELD (NM_SETTING_802_1X_PRIVATE_KEY_PASSWORD, 20), /* 25 */ + SETTING_FIELD (NM_SETTING_802_1X_PRIVATE_KEY_PASSWORD_FLAGS, 20), /* 26 */ + SETTING_FIELD (NM_SETTING_802_1X_PHASE2_PRIVATE_KEY, 20), /* 27 */ + SETTING_FIELD (NM_SETTING_802_1X_PHASE2_PRIVATE_KEY_PASSWORD, 20), /* 28 */ + SETTING_FIELD (NM_SETTING_802_1X_PHASE2_PRIVATE_KEY_PASSWORD_FLAGS, 20), /* 29 */ + SETTING_FIELD (NM_SETTING_802_1X_PIN, 8), /* 30 */ + SETTING_FIELD (NM_SETTING_802_1X_PIN_FLAGS, 20), /* 31 */ + SETTING_FIELD (NM_SETTING_802_1X_SYSTEM_CA_CERTS, 17), /* 32 */ + {NULL, NULL, 0, NULL, FALSE, FALSE, 0} +}; +#define NMC_FIELDS_SETTING_802_1X_ALL "name"","\ + NM_SETTING_802_1X_EAP","\ + NM_SETTING_802_1X_IDENTITY","\ + NM_SETTING_802_1X_ANONYMOUS_IDENTITY","\ + NM_SETTING_802_1X_PAC_FILE","\ + NM_SETTING_802_1X_CA_CERT","\ + NM_SETTING_802_1X_CA_PATH","\ + NM_SETTING_802_1X_SUBJECT_MATCH","\ + NM_SETTING_802_1X_ALTSUBJECT_MATCHES","\ + NM_SETTING_802_1X_CLIENT_CERT","\ + NM_SETTING_802_1X_PHASE1_PEAPVER","\ + NM_SETTING_802_1X_PHASE1_PEAPLABEL","\ + NM_SETTING_802_1X_PHASE1_FAST_PROVISIONING","\ + NM_SETTING_802_1X_PHASE2_AUTH","\ + NM_SETTING_802_1X_PHASE2_AUTHEAP","\ + NM_SETTING_802_1X_PHASE2_CA_CERT","\ + NM_SETTING_802_1X_PHASE2_CA_PATH","\ + NM_SETTING_802_1X_PHASE2_SUBJECT_MATCH","\ + NM_SETTING_802_1X_PHASE2_ALTSUBJECT_MATCHES","\ + NM_SETTING_802_1X_PHASE2_CLIENT_CERT","\ + NM_SETTING_802_1X_PASSWORD","\ + NM_SETTING_802_1X_PASSWORD_FLAGS","\ + NM_SETTING_802_1X_PASSWORD_RAW","\ + NM_SETTING_802_1X_PASSWORD_RAW_FLAGS","\ + NM_SETTING_802_1X_PRIVATE_KEY","\ + NM_SETTING_802_1X_PRIVATE_KEY_PASSWORD","\ + NM_SETTING_802_1X_PRIVATE_KEY_PASSWORD_FLAGS","\ + NM_SETTING_802_1X_PHASE2_PRIVATE_KEY","\ + NM_SETTING_802_1X_PHASE2_PRIVATE_KEY_PASSWORD","\ + NM_SETTING_802_1X_PHASE2_PRIVATE_KEY_PASSWORD_FLAGS","\ + NM_SETTING_802_1X_PIN","\ + NM_SETTING_802_1X_PIN_FLAGS","\ + NM_SETTING_802_1X_SYSTEM_CA_CERTS +#define NMC_FIELDS_SETTING_802_1X_COMMON NMC_FIELDS_SETTING_802_1X_ALL + +/* Available fields for NM_SETTING_WIRELESS_SETTING_NAME */ +NmcOutputField nmc_fields_setting_wireless[] = { + SETTING_FIELD ("name", 17), /* 0 */ + SETTING_FIELD (NM_SETTING_WIRELESS_SSID, 34), /* 1 */ + SETTING_FIELD (NM_SETTING_WIRELESS_MODE, 15), /* 2 */ + SETTING_FIELD (NM_SETTING_WIRELESS_BAND, 10), /* 3 */ + SETTING_FIELD (NM_SETTING_WIRELESS_CHANNEL, 10), /* 4 */ + SETTING_FIELD (NM_SETTING_WIRELESS_BSSID, 19), /* 5 */ + SETTING_FIELD (NM_SETTING_WIRELESS_RATE, 10), /* 6 */ + SETTING_FIELD (NM_SETTING_WIRELESS_TX_POWER, 10), /* 7 */ + SETTING_FIELD (NM_SETTING_WIRELESS_MAC_ADDRESS, 19), /* 8 */ + SETTING_FIELD (NM_SETTING_WIRELESS_CLONED_MAC_ADDRESS, 19), /* 9 */ + SETTING_FIELD (NM_SETTING_WIRELESS_MAC_ADDRESS_BLACKLIST, 39), /* 10 */ + SETTING_FIELD (NM_SETTING_WIRELESS_MTU, 6), /* 11 */ + SETTING_FIELD (NM_SETTING_WIRELESS_SEEN_BSSIDS, 35), /* 12 */ + SETTING_FIELD (NM_SETTING_WIRELESS_HIDDEN, 10), /* 13 */ + {NULL, NULL, 0, NULL, FALSE, FALSE, 0} +}; +#define NMC_FIELDS_SETTING_WIRELESS_ALL "name"","\ + NM_SETTING_WIRELESS_SSID","\ + NM_SETTING_WIRELESS_MODE","\ + NM_SETTING_WIRELESS_BAND","\ + NM_SETTING_WIRELESS_CHANNEL","\ + NM_SETTING_WIRELESS_BSSID","\ + NM_SETTING_WIRELESS_RATE","\ + NM_SETTING_WIRELESS_TX_POWER","\ + NM_SETTING_WIRELESS_MAC_ADDRESS","\ + NM_SETTING_WIRELESS_CLONED_MAC_ADDRESS","\ + NM_SETTING_WIRELESS_MAC_ADDRESS_BLACKLIST","\ + NM_SETTING_WIRELESS_MTU","\ + NM_SETTING_WIRELESS_SEEN_BSSIDS","\ + NM_SETTING_WIRELESS_HIDDEN +#define NMC_FIELDS_SETTING_WIRELESS_COMMON NMC_FIELDS_SETTING_WIRELESS_ALL + +/* Available fields for NM_SETTING_WIRELESS_SECURITY_SETTING_NAME */ +NmcOutputField nmc_fields_setting_wireless_security[] = { + SETTING_FIELD ("name", 25), /* 0 */ + SETTING_FIELD (NM_SETTING_WIRELESS_SECURITY_KEY_MGMT, 10), /* 1 */ + SETTING_FIELD (NM_SETTING_WIRELESS_SECURITY_WEP_TX_KEYIDX, 15), /* 2 */ + SETTING_FIELD (NM_SETTING_WIRELESS_SECURITY_AUTH_ALG, 10), /* 3 */ + SETTING_FIELD (NM_SETTING_WIRELESS_SECURITY_PROTO, 10), /* 4 */ + SETTING_FIELD (NM_SETTING_WIRELESS_SECURITY_PAIRWISE, 10), /* 5 */ + SETTING_FIELD (NM_SETTING_WIRELESS_SECURITY_GROUP, 10), /* 6 */ + SETTING_FIELD (NM_SETTING_WIRELESS_SECURITY_LEAP_USERNAME, 15), /* 7 */ + SETTING_FIELD (NM_SETTING_WIRELESS_SECURITY_WEP_KEY0, 10), /* 8 */ + SETTING_FIELD (NM_SETTING_WIRELESS_SECURITY_WEP_KEY1, 10), /* 9 */ + SETTING_FIELD (NM_SETTING_WIRELESS_SECURITY_WEP_KEY2, 10), /* 10 */ + SETTING_FIELD (NM_SETTING_WIRELESS_SECURITY_WEP_KEY3, 10), /* 11 */ + SETTING_FIELD (NM_SETTING_WIRELESS_SECURITY_WEP_KEY_FLAGS, 20), /* 12 */ + SETTING_FIELD (NM_SETTING_WIRELESS_SECURITY_WEP_KEY_TYPE, 15), /* 13 */ + SETTING_FIELD (NM_SETTING_WIRELESS_SECURITY_PSK, 6), /* 14 */ + SETTING_FIELD (NM_SETTING_WIRELESS_SECURITY_PSK_FLAGS, 20), /* 15 */ + SETTING_FIELD (NM_SETTING_WIRELESS_SECURITY_LEAP_PASSWORD, 15), /* 16 */ + SETTING_FIELD (NM_SETTING_WIRELESS_SECURITY_LEAP_PASSWORD_FLAGS, 20), /* 17 */ + {NULL, NULL, 0, NULL, FALSE, FALSE, 0} +}; +#define NMC_FIELDS_SETTING_WIRELESS_SECURITY_ALL "name"","\ + NM_SETTING_WIRELESS_SECURITY_KEY_MGMT","\ + NM_SETTING_WIRELESS_SECURITY_WEP_TX_KEYIDX","\ + NM_SETTING_WIRELESS_SECURITY_AUTH_ALG","\ + NM_SETTING_WIRELESS_SECURITY_PROTO","\ + NM_SETTING_WIRELESS_SECURITY_PAIRWISE","\ + NM_SETTING_WIRELESS_SECURITY_GROUP","\ + NM_SETTING_WIRELESS_SECURITY_LEAP_USERNAME","\ + NM_SETTING_WIRELESS_SECURITY_WEP_KEY0","\ + NM_SETTING_WIRELESS_SECURITY_WEP_KEY1","\ + NM_SETTING_WIRELESS_SECURITY_WEP_KEY2","\ + NM_SETTING_WIRELESS_SECURITY_WEP_KEY3","\ + NM_SETTING_WIRELESS_SECURITY_WEP_KEY_FLAGS","\ + NM_SETTING_WIRELESS_SECURITY_WEP_KEY_TYPE","\ + NM_SETTING_WIRELESS_SECURITY_PSK","\ + NM_SETTING_WIRELESS_SECURITY_PSK_FLAGS","\ + NM_SETTING_WIRELESS_SECURITY_LEAP_PASSWORD","\ + NM_SETTING_WIRELESS_SECURITY_LEAP_PASSWORD_FLAGS +#define NMC_FIELDS_SETTING_WIRELESS_SECURITY_COMMON NMC_FIELDS_SETTING_WIRELESS_SECURITY_ALL + +/* Available fields for NM_SETTING_IP4_CONFIG_SETTING_NAME */ +NmcOutputField nmc_fields_setting_ip4_config[] = { + SETTING_FIELD ("name", 8), /* 0 */ + SETTING_FIELD (NM_SETTING_IP_CONFIG_METHOD, 10), /* 1 */ + SETTING_FIELD (NM_SETTING_IP_CONFIG_DNS, 20), /* 2 */ + SETTING_FIELD (NM_SETTING_IP_CONFIG_DNS_SEARCH, 15), /* 3 */ + SETTING_FIELD (NM_SETTING_IP_CONFIG_ADDRESSES, 20), /* 4 */ + SETTING_FIELD (NM_SETTING_IP_CONFIG_GATEWAY, 20), /* 5 */ + SETTING_FIELD (NM_SETTING_IP_CONFIG_ROUTES, 20), /* 6 */ + SETTING_FIELD (NM_SETTING_IP_CONFIG_ROUTE_METRIC, 15), /* 7 */ + SETTING_FIELD (NM_SETTING_IP_CONFIG_IGNORE_AUTO_ROUTES, 19), /* 8 */ + SETTING_FIELD (NM_SETTING_IP_CONFIG_IGNORE_AUTO_DNS, 16), /* 9 */ + SETTING_FIELD (NM_SETTING_IP4_CONFIG_DHCP_CLIENT_ID, 15), /* 10 */ + SETTING_FIELD (NM_SETTING_IP_CONFIG_DHCP_SEND_HOSTNAME, 19), /* 11 */ + SETTING_FIELD (NM_SETTING_IP_CONFIG_DHCP_HOSTNAME, 14), /* 12 */ + SETTING_FIELD (NM_SETTING_IP_CONFIG_NEVER_DEFAULT, 15), /* 13 */ + SETTING_FIELD (NM_SETTING_IP_CONFIG_MAY_FAIL, 12), /* 14 */ + {NULL, NULL, 0, NULL, FALSE, FALSE, 0} +}; +#define NMC_FIELDS_SETTING_IP4_CONFIG_ALL "name"","\ + NM_SETTING_IP_CONFIG_METHOD","\ + NM_SETTING_IP_CONFIG_DNS","\ + NM_SETTING_IP_CONFIG_DNS_SEARCH","\ + NM_SETTING_IP_CONFIG_ADDRESSES","\ + NM_SETTING_IP_CONFIG_GATEWAY","\ + NM_SETTING_IP_CONFIG_ROUTES","\ + NM_SETTING_IP_CONFIG_ROUTE_METRIC","\ + NM_SETTING_IP_CONFIG_IGNORE_AUTO_ROUTES","\ + NM_SETTING_IP_CONFIG_IGNORE_AUTO_DNS","\ + NM_SETTING_IP4_CONFIG_DHCP_CLIENT_ID","\ + NM_SETTING_IP_CONFIG_DHCP_SEND_HOSTNAME","\ + NM_SETTING_IP_CONFIG_DHCP_HOSTNAME","\ + NM_SETTING_IP_CONFIG_NEVER_DEFAULT","\ + NM_SETTING_IP_CONFIG_MAY_FAIL +#define NMC_FIELDS_SETTING_IP4_CONFIG_COMMON NMC_FIELDS_SETTING_IP4_CONFIG_ALL + +/* Available fields for NM_SETTING_IP6_CONFIG_SETTING_NAME */ +NmcOutputField nmc_fields_setting_ip6_config[] = { + SETTING_FIELD ("name", 8), /* 0 */ + SETTING_FIELD (NM_SETTING_IP_CONFIG_METHOD, 10), /* 1 */ + SETTING_FIELD (NM_SETTING_IP_CONFIG_DNS, 20), /* 2 */ + SETTING_FIELD (NM_SETTING_IP_CONFIG_DNS_SEARCH, 15), /* 3 */ + SETTING_FIELD (NM_SETTING_IP_CONFIG_ADDRESSES, 20), /* 4 */ + SETTING_FIELD (NM_SETTING_IP_CONFIG_GATEWAY, 20), /* 5 */ + SETTING_FIELD (NM_SETTING_IP_CONFIG_ROUTES, 20), /* 6 */ + SETTING_FIELD (NM_SETTING_IP_CONFIG_ROUTE_METRIC, 15), /* 7 */ + SETTING_FIELD (NM_SETTING_IP_CONFIG_IGNORE_AUTO_ROUTES, 19), /* 8 */ + SETTING_FIELD (NM_SETTING_IP_CONFIG_IGNORE_AUTO_DNS, 16), /* 9 */ + SETTING_FIELD (NM_SETTING_IP_CONFIG_NEVER_DEFAULT, 15), /* 10 */ + SETTING_FIELD (NM_SETTING_IP_CONFIG_MAY_FAIL, 12), /* 11 */ + SETTING_FIELD (NM_SETTING_IP6_CONFIG_IP6_PRIVACY, 15), /* 12 */ + SETTING_FIELD (NM_SETTING_IP_CONFIG_DHCP_SEND_HOSTNAME, 19), /* 13 */ + SETTING_FIELD (NM_SETTING_IP_CONFIG_DHCP_HOSTNAME, 14), /* 14 */ + {NULL, NULL, 0, NULL, FALSE, FALSE, 0} +}; +#define NMC_FIELDS_SETTING_IP6_CONFIG_ALL "name"","\ + NM_SETTING_IP_CONFIG_METHOD","\ + NM_SETTING_IP_CONFIG_DNS","\ + NM_SETTING_IP_CONFIG_DNS_SEARCH","\ + NM_SETTING_IP_CONFIG_ADDRESSES","\ + NM_SETTING_IP_CONFIG_GATEWAY","\ + NM_SETTING_IP_CONFIG_ROUTES","\ + NM_SETTING_IP_CONFIG_ROUTE_METRIC","\ + NM_SETTING_IP_CONFIG_IGNORE_AUTO_ROUTES","\ + NM_SETTING_IP_CONFIG_IGNORE_AUTO_DNS","\ + NM_SETTING_IP_CONFIG_NEVER_DEFAULT","\ + NM_SETTING_IP_CONFIG_MAY_FAIL","\ + NM_SETTING_IP6_CONFIG_IP6_PRIVACY","\ + NM_SETTING_IP_CONFIG_DHCP_SEND_HOSTNAME","\ + NM_SETTING_IP_CONFIG_DHCP_HOSTNAME +#define NMC_FIELDS_SETTING_IP6_CONFIG_COMMON NMC_FIELDS_SETTING_IP4_CONFIG_ALL + +/* Available fields for NM_SETTING_SERIAL_SETTING_NAME */ +NmcOutputField nmc_fields_setting_serial[] = { + SETTING_FIELD ("name", 10), /* 0 */ + SETTING_FIELD (NM_SETTING_SERIAL_BAUD, 10), /* 1 */ + SETTING_FIELD (NM_SETTING_SERIAL_BITS, 10), /* 2 */ + SETTING_FIELD (NM_SETTING_SERIAL_PARITY, 10), /* 3 */ + SETTING_FIELD (NM_SETTING_SERIAL_STOPBITS, 10), /* 4 */ + SETTING_FIELD (NM_SETTING_SERIAL_SEND_DELAY, 12), /* 5 */ + {NULL, NULL, 0, NULL, FALSE, FALSE, 0} +}; +#define NMC_FIELDS_SETTING_SERIAL_ALL "name"","\ + NM_SETTING_SERIAL_BAUD","\ + NM_SETTING_SERIAL_BITS","\ + NM_SETTING_SERIAL_PARITY","\ + NM_SETTING_SERIAL_STOPBITS","\ + NM_SETTING_SERIAL_SEND_DELAY +#define NMC_FIELDS_SETTING_SERIAL_COMMON NMC_FIELDS_SETTING_SERIAL_ALL + +/* Available fields for NM_SETTING_PPP_SETTING_NAME */ +NmcOutputField nmc_fields_setting_ppp[] = { + SETTING_FIELD ("name", 10), /* 0 */ + SETTING_FIELD (NM_SETTING_PPP_NOAUTH, 10), /* 1 */ + SETTING_FIELD (NM_SETTING_PPP_REFUSE_EAP, 10), /* 2 */ + SETTING_FIELD (NM_SETTING_PPP_REFUSE_PAP, 10), /* 3 */ + SETTING_FIELD (NM_SETTING_PPP_REFUSE_CHAP, 10), /* 4 */ + SETTING_FIELD (NM_SETTING_PPP_REFUSE_MSCHAP, 10), /* 5 */ + SETTING_FIELD (NM_SETTING_PPP_REFUSE_MSCHAPV2, 10), /* 6 */ + SETTING_FIELD (NM_SETTING_PPP_NOBSDCOMP, 10), /* 7 */ + SETTING_FIELD (NM_SETTING_PPP_NODEFLATE, 10), /* 8 */ + SETTING_FIELD (NM_SETTING_PPP_NO_VJ_COMP, 10), /* 9 */ + SETTING_FIELD (NM_SETTING_PPP_REQUIRE_MPPE, 10), /* 10 */ + SETTING_FIELD (NM_SETTING_PPP_REQUIRE_MPPE_128, 10), /* 11 */ + SETTING_FIELD (NM_SETTING_PPP_MPPE_STATEFUL, 10), /* 12 */ + SETTING_FIELD (NM_SETTING_PPP_CRTSCTS, 10), /* 13 */ + SETTING_FIELD (NM_SETTING_PPP_BAUD, 10), /* 14 */ + SETTING_FIELD (NM_SETTING_PPP_MRU, 10), /* 15 */ + SETTING_FIELD (NM_SETTING_PPP_MTU, 10), /* 16 */ + SETTING_FIELD (NM_SETTING_PPP_LCP_ECHO_FAILURE, 17), /* 17 */ + SETTING_FIELD (NM_SETTING_PPP_LCP_ECHO_INTERVAL, 18), /* 18 */ + {NULL, NULL, 0, NULL, FALSE, FALSE, 0} +}; +#define NMC_FIELDS_SETTING_PPP_ALL "name"","\ + NM_SETTING_PPP_NOAUTH","\ + NM_SETTING_PPP_REFUSE_EAP","\ + NM_SETTING_PPP_REFUSE_PAP","\ + NM_SETTING_PPP_REFUSE_CHAP","\ + NM_SETTING_PPP_REFUSE_MSCHAP","\ + NM_SETTING_PPP_REFUSE_MSCHAPV2","\ + NM_SETTING_PPP_NOBSDCOMP","\ + NM_SETTING_PPP_NODEFLATE","\ + NM_SETTING_PPP_NO_VJ_COMP","\ + NM_SETTING_PPP_REQUIRE_MPPE","\ + NM_SETTING_PPP_REQUIRE_MPPE_128","\ + NM_SETTING_PPP_MPPE_STATEFUL","\ + NM_SETTING_PPP_CRTSCTS","\ + NM_SETTING_PPP_BAUD","\ + NM_SETTING_PPP_MRU","\ + NM_SETTING_PPP_MTU","\ + NM_SETTING_PPP_LCP_ECHO_FAILURE","\ + NM_SETTING_PPP_LCP_ECHO_INTERVAL +#define NMC_FIELDS_SETTING_PPP_COMMON NMC_FIELDS_SETTING_PPP_ALL + +/* Available fields for NM_SETTING_PPPOE_SETTING_NAME */ +NmcOutputField nmc_fields_setting_pppoe[] = { + SETTING_FIELD ("name", 10), /* 0 */ + SETTING_FIELD (NM_SETTING_PPPOE_SERVICE, 12), /* 1 */ + SETTING_FIELD (NM_SETTING_PPPOE_USERNAME, 15), /* 2 */ + SETTING_FIELD (NM_SETTING_PPPOE_PASSWORD, 15), /* 3 */ + SETTING_FIELD (NM_SETTING_PPPOE_PASSWORD_FLAGS, 20), /* 4 */ + {NULL, NULL, 0, NULL, FALSE, FALSE, 0} +}; +#define NMC_FIELDS_SETTING_PPPOE_ALL "name"","\ + NM_SETTING_PPPOE_SERVICE","\ + NM_SETTING_PPPOE_USERNAME","\ + NM_SETTING_PPPOE_PASSWORD","\ + NM_SETTING_PPPOE_PASSWORD_FLAGS +#define NMC_FIELDS_SETTING_PPPOE_COMMON NMC_FIELDS_SETTING_PPPOE_ALL + +/* Available fields for NM_SETTING_ADSL_SETTING_NAME */ +NmcOutputField nmc_fields_setting_adsl[] = { + SETTING_FIELD ("name", 10), /* 0 */ + SETTING_FIELD (NM_SETTING_ADSL_USERNAME, 15), /* 1 */ + SETTING_FIELD (NM_SETTING_ADSL_PASSWORD, 15), /* 2 */ + SETTING_FIELD (NM_SETTING_ADSL_PASSWORD_FLAGS, 20), /* 3 */ + SETTING_FIELD (NM_SETTING_ADSL_PROTOCOL, 10), /* 4 */ + SETTING_FIELD (NM_SETTING_ADSL_ENCAPSULATION, 10), /* 5 */ + SETTING_FIELD (NM_SETTING_ADSL_VPI, 10), /* 6 */ + SETTING_FIELD (NM_SETTING_ADSL_VCI, 10), /* 7 */ + {NULL, NULL, 0, NULL, FALSE, FALSE, 0} +}; +#define NMC_FIELDS_SETTING_ADSL_ALL "name"","\ + NM_SETTING_ADSL_USERNAME","\ + NM_SETTING_ADSL_PASSWORD","\ + NM_SETTING_ADSL_PASSWORD_FLAGS","\ + NM_SETTING_ADSL_PROTOCOL","\ + NM_SETTING_ADSL_ENCAPSULATION","\ + NM_SETTING_ADSL_VPI","\ + NM_SETTING_ADSL_VCI +#define NMC_FIELDS_SETTING_ADSL_COMMON NMC_FIELDS_SETTING_ADSL_ALL + +/* Available fields for NM_SETTING_GSM_SETTING_NAME */ +NmcOutputField nmc_fields_setting_gsm[] = { + SETTING_FIELD ("name", 10), /* 0 */ + SETTING_FIELD (NM_SETTING_GSM_NUMBER, 10), /* 1 */ + SETTING_FIELD (NM_SETTING_GSM_USERNAME, 15), /* 2 */ + SETTING_FIELD (NM_SETTING_GSM_PASSWORD, 15), /* 3 */ + SETTING_FIELD (NM_SETTING_GSM_PASSWORD_FLAGS, 20), /* 4 */ + SETTING_FIELD (NM_SETTING_GSM_APN, 25), /* 5 */ + SETTING_FIELD (NM_SETTING_GSM_NETWORK_ID, 12), /* 6 */ + SETTING_FIELD (NM_SETTING_GSM_PIN, 10), /* 7 */ + SETTING_FIELD (NM_SETTING_GSM_PIN_FLAGS, 20), /* 8 */ + SETTING_FIELD (NM_SETTING_GSM_HOME_ONLY, 10), /* 9 */ + {NULL, NULL, 0, NULL, FALSE, FALSE, 0} +}; +#define NMC_FIELDS_SETTING_GSM_ALL "name"","\ + NM_SETTING_GSM_NUMBER","\ + NM_SETTING_GSM_USERNAME","\ + NM_SETTING_GSM_PASSWORD","\ + NM_SETTING_GSM_PASSWORD_FLAGS","\ + NM_SETTING_GSM_APN","\ + NM_SETTING_GSM_NETWORK_ID","\ + NM_SETTING_GSM_PIN","\ + NM_SETTING_GSM_PIN_FLAGS","\ + NM_SETTING_GSM_HOME_ONLY +#define NMC_FIELDS_SETTING_GSM_COMMON NMC_FIELDS_SETTING_GSM_ALL + +/* Available fields for NM_SETTING_CDMA_SETTING_NAME */ +NmcOutputField nmc_fields_setting_cdma[] = { + SETTING_FIELD ("name", 10), /* 0 */ + SETTING_FIELD (NM_SETTING_CDMA_NUMBER, 15), /* 1 */ + SETTING_FIELD (NM_SETTING_CDMA_USERNAME, 15), /* 2 */ + SETTING_FIELD (NM_SETTING_CDMA_PASSWORD, 15), /* 3 */ + SETTING_FIELD (NM_SETTING_CDMA_PASSWORD_FLAGS, 20), /* 4 */ + {NULL, NULL, 0, NULL, FALSE, FALSE, 0} +}; +#define NMC_FIELDS_SETTING_CDMA_ALL "name"","\ + NM_SETTING_CDMA_NUMBER","\ + NM_SETTING_CDMA_USERNAME","\ + NM_SETTING_CDMA_PASSWORD","\ + NM_SETTING_CDMA_PASSWORD_FLAGS +#define NMC_FIELDS_SETTING_CDMA_COMMON NMC_FIELDS_SETTING_CDMA_ALL + +/* Available fields for NM_SETTING_BLUETOOTH_SETTING_NAME */ +NmcOutputField nmc_fields_setting_bluetooth[] = { + SETTING_FIELD ("name", 11), /* 0 */ + SETTING_FIELD (NM_SETTING_BLUETOOTH_BDADDR, 19), /* 1 */ + SETTING_FIELD (NM_SETTING_BLUETOOTH_TYPE, 10), /* 2 */ + {NULL, NULL, 0, NULL, FALSE, FALSE, 0} +}; +#define NMC_FIELDS_SETTING_BLUETOOTH_ALL "name"","\ + NM_SETTING_BLUETOOTH_BDADDR","\ + NM_SETTING_BLUETOOTH_TYPE +#define NMC_FIELDS_SETTING_BLUETOOTH_COMMON NMC_FIELDS_SETTING_BLUETOOTH_ALL + +/* Available fields for NM_SETTING_OLPC_MESH_SETTING_NAME */ +NmcOutputField nmc_fields_setting_olpc_mesh[] = { + SETTING_FIELD ("name", 18), /* 0 */ + SETTING_FIELD (NM_SETTING_OLPC_MESH_SSID, 34), /* 1 */ + SETTING_FIELD (NM_SETTING_OLPC_MESH_CHANNEL, 12), /* 2 */ + SETTING_FIELD (NM_SETTING_OLPC_MESH_DHCP_ANYCAST_ADDRESS, 17), /* 3 */ + {NULL, NULL, 0, NULL, FALSE, FALSE, 0} +}; +#define NMC_FIELDS_SETTING_OLPC_MESH_ALL "name"","\ + NM_SETTING_OLPC_MESH_SSID","\ + NM_SETTING_OLPC_MESH_CHANNEL","\ + NM_SETTING_OLPC_MESH_DHCP_ANYCAST_ADDRESS +#define NMC_FIELDS_SETTING_OLPC_MESH_COMMON NMC_FIELDS_SETTING_OLPC_MESH_ALL + +/* Available fields for NM_SETTING_VPN_SETTING_NAME */ +NmcOutputField nmc_fields_setting_vpn[] = { + SETTING_FIELD ("name", 6), /* 0 */ + SETTING_FIELD (NM_SETTING_VPN_SERVICE_TYPE, 40), /* 1 */ + SETTING_FIELD (NM_SETTING_VPN_USER_NAME, 12), /* 2 */ + SETTING_FIELD (NM_SETTING_VPN_DATA, 30), /* 3 */ + SETTING_FIELD (NM_SETTING_VPN_SECRETS, 15), /* 4 */ + SETTING_FIELD (NM_SETTING_VPN_PERSISTENT, 15), /* 5 */ + {NULL, NULL, 0, NULL, FALSE, FALSE, 0} +}; +#define NMC_FIELDS_SETTING_VPN_ALL "name"","\ + NM_SETTING_VPN_SERVICE_TYPE","\ + NM_SETTING_VPN_USER_NAME","\ + NM_SETTING_VPN_DATA","\ + NM_SETTING_VPN_SECRETS","\ + NM_SETTING_VPN_PERSISTENT +#define NMC_FIELDS_SETTING_VPN_COMMON NMC_FIELDS_SETTING_VPN_ALL + +/* Available fields for NM_SETTING_WIMAX_SETTING_NAME */ +NmcOutputField nmc_fields_setting_wimax[] = { + SETTING_FIELD ("name", 6), /* 0 */ + SETTING_FIELD (NM_SETTING_WIMAX_MAC_ADDRESS, 19), /* 1 */ + SETTING_FIELD (NM_SETTING_WIMAX_NETWORK_NAME, 40), /* 2 */ + {NULL, NULL, 0, NULL, FALSE, FALSE, 0} +}; +#define NMC_FIELDS_SETTING_WIMAX_ALL "name"","\ + NM_SETTING_WIMAX_MAC_ADDRESS","\ + NM_SETTING_WIMAX_NETWORK_NAME +#define NMC_FIELDS_SETTING_WIMAX_COMMON NMC_FIELDS_SETTING_WIMAX_ALL + +/* Available fields for NM_SETTING_INFINIBAND_SETTING_NAME */ +NmcOutputField nmc_fields_setting_infiniband[] = { + SETTING_FIELD ("name", 12), /* 0 */ + SETTING_FIELD (NM_SETTING_INFINIBAND_MAC_ADDRESS, 61), /* 1 */ + SETTING_FIELD (NM_SETTING_INFINIBAND_MTU, 6), /* 2 */ + SETTING_FIELD (NM_SETTING_INFINIBAND_TRANSPORT_MODE, 12), /* 3 */ + SETTING_FIELD (NM_SETTING_INFINIBAND_P_KEY, 6), /* 4 */ + SETTING_FIELD (NM_SETTING_INFINIBAND_PARENT, 16), /* 5 */ + {NULL, NULL, 0, NULL, FALSE, FALSE, 0} +}; +#define NMC_FIELDS_SETTING_INFINIBAND_ALL "name"","\ + NM_SETTING_INFINIBAND_MAC_ADDRESS","\ + NM_SETTING_INFINIBAND_MTU"," \ + NM_SETTING_INFINIBAND_TRANSPORT_MODE"," \ + NM_SETTING_INFINIBAND_P_KEY"," \ + NM_SETTING_INFINIBAND_PARENT +#define NMC_FIELDS_SETTING_INFINIBAND_COMMON NMC_FIELDS_SETTING_INFINIBAND_ALL \ + +/* Available fields for NM_SETTING_BOND_SETTING_NAME */ +NmcOutputField nmc_fields_setting_bond[] = { + SETTING_FIELD ("name", 8), /* 0 */ + SETTING_FIELD (NM_SETTING_BOND_OPTIONS, 30), /* 1 */ + {NULL, NULL, 0, NULL, FALSE, FALSE, 0} +}; +#define NMC_FIELDS_SETTING_BOND_ALL "name"","\ + NM_SETTING_BOND_OPTIONS +#define NMC_FIELDS_SETTING_BOND_COMMON NMC_FIELDS_SETTING_BOND_ALL + +/* Available fields for NM_SETTING_VLAN_SETTING_NAME */ +NmcOutputField nmc_fields_setting_vlan[] = { + SETTING_FIELD ("name", 6), /* 0 */ + SETTING_FIELD (NM_SETTING_VLAN_PARENT, 8), /* 1 */ + SETTING_FIELD (NM_SETTING_VLAN_ID, 6), /* 2 */ + SETTING_FIELD (NM_SETTING_VLAN_FLAGS, 45), /* 3 */ + SETTING_FIELD (NM_SETTING_VLAN_INGRESS_PRIORITY_MAP, 22), /* 4 */ + SETTING_FIELD (NM_SETTING_VLAN_EGRESS_PRIORITY_MAP, 22), /* 5 */ + {NULL, NULL, 0, NULL, FALSE, FALSE, 0} +}; +#define NMC_FIELDS_SETTING_VLAN_ALL "name"","\ + NM_SETTING_VLAN_PARENT","\ + NM_SETTING_VLAN_ID","\ + NM_SETTING_VLAN_FLAGS","\ + NM_SETTING_VLAN_INGRESS_PRIORITY_MAP","\ + NM_SETTING_VLAN_EGRESS_PRIORITY_MAP +#define NMC_FIELDS_SETTING_VLAN_COMMON NMC_FIELDS_SETTING_VLAN_ALL + +/* Available fields for NM_SETTING_BRIDGE_SETTING_NAME */ +NmcOutputField nmc_fields_setting_bridge[] = { + SETTING_FIELD ("name", 8), /* 0 */ + SETTING_FIELD (NM_SETTING_BRIDGE_MAC_ADDRESS, 19), /* 1 */ + SETTING_FIELD (NM_SETTING_BRIDGE_STP, 5), /* 2 */ + SETTING_FIELD (NM_SETTING_BRIDGE_PRIORITY, 6), /* 3 */ + SETTING_FIELD (NM_SETTING_BRIDGE_FORWARD_DELAY, 6), /* 4 */ + SETTING_FIELD (NM_SETTING_BRIDGE_HELLO_TIME, 6), /* 5 */ + SETTING_FIELD (NM_SETTING_BRIDGE_MAX_AGE, 6), /* 6 */ + SETTING_FIELD (NM_SETTING_BRIDGE_AGEING_TIME, 6), /* 7 */ + {NULL, NULL, 0, NULL, FALSE, FALSE, 0} +}; +#define NMC_FIELDS_SETTING_BRIDGE_ALL "name"","\ + NM_SETTING_BRIDGE_MAC_ADDRESS","\ + NM_SETTING_BRIDGE_STP","\ + NM_SETTING_BRIDGE_PRIORITY","\ + NM_SETTING_BRIDGE_FORWARD_DELAY","\ + NM_SETTING_BRIDGE_HELLO_TIME","\ + NM_SETTING_BRIDGE_MAX_AGE","\ + NM_SETTING_BRIDGE_AGEING_TIME +#define NMC_FIELDS_SETTING_BRIDGE_COMMON NMC_FIELDS_SETTING_BRIDGE_ALL + +/* Available fields for NM_SETTING_BRIDGE_PORT_SETTING_NAME */ +NmcOutputField nmc_fields_setting_bridge_port[] = { + SETTING_FIELD ("name", 8), /* 0 */ + SETTING_FIELD (NM_SETTING_BRIDGE_PORT_PRIORITY, 10), /* 1 */ + SETTING_FIELD (NM_SETTING_BRIDGE_PORT_PATH_COST, 12), /* 2 */ + SETTING_FIELD (NM_SETTING_BRIDGE_PORT_HAIRPIN_MODE, 15), /* 3 */ + {NULL, NULL, 0, NULL, FALSE, FALSE, 0} +}; +#define NMC_FIELDS_SETTING_BRIDGE_PORT_ALL "name"","\ + NM_SETTING_BRIDGE_PORT_PRIORITY","\ + NM_SETTING_BRIDGE_PORT_PATH_COST","\ + NM_SETTING_BRIDGE_PORT_HAIRPIN_MODE +#define NMC_FIELDS_SETTING_BRIDGE_PORT_COMMON NMC_FIELDS_SETTING_BRIDGE_PORT_ALL + +/* Available fields for NM_SETTING_TEAM_SETTING_NAME */ +NmcOutputField nmc_fields_setting_team[] = { + SETTING_FIELD ("name", 8), /* 0 */ + SETTING_FIELD (NM_SETTING_TEAM_CONFIG, 30), /* 1 */ + {NULL, NULL, 0, NULL, FALSE, FALSE, 0} +}; +#define NMC_FIELDS_SETTING_TEAM_ALL "name"","\ + NM_SETTING_TEAM_CONFIG +#define NMC_FIELDS_SETTING_TEAM_COMMON NMC_FIELDS_SETTING_TEAM_ALL + +/* Available fields for NM_SETTING_TEAM_PORT_SETTING_NAME */ +NmcOutputField nmc_fields_setting_team_port[] = { + SETTING_FIELD ("name", 8), /* 0 */ + SETTING_FIELD (NM_SETTING_TEAM_PORT_CONFIG, 30), /* 1 */ + {NULL, NULL, 0, NULL, FALSE, FALSE, 0} +}; +#define NMC_FIELDS_SETTING_TEAM_PORT_ALL "name"","\ + NM_SETTING_TEAM_PORT_CONFIG +#define NMC_FIELDS_SETTING_TEAM_PORT_COMMON NMC_FIELDS_SETTING_TEAM_PORT_ALL + +/* Available fields for NM_SETTING_DCB_SETTING_NAME */ +NmcOutputField nmc_fields_setting_dcb[] = { + SETTING_FIELD ("name", 8), /* 0 */ + SETTING_FIELD (NM_SETTING_DCB_APP_FCOE_FLAGS, 5), /* 1 */ + SETTING_FIELD (NM_SETTING_DCB_APP_FCOE_PRIORITY, 5), /* 2 */ + SETTING_FIELD (NM_SETTING_DCB_APP_FCOE_MODE, 8), /* 3 */ + SETTING_FIELD (NM_SETTING_DCB_APP_ISCSI_FLAGS, 5), /* 4 */ + SETTING_FIELD (NM_SETTING_DCB_APP_ISCSI_PRIORITY, 5), /* 5 */ + SETTING_FIELD (NM_SETTING_DCB_APP_FIP_FLAGS, 5), /* 6 */ + SETTING_FIELD (NM_SETTING_DCB_APP_FIP_PRIORITY, 5), /* 7 */ + SETTING_FIELD (NM_SETTING_DCB_PRIORITY_FLOW_CONTROL_FLAGS, 5), /* 8 */ + SETTING_FIELD (NM_SETTING_DCB_PRIORITY_FLOW_CONTROL, 10), /* 9 */ + SETTING_FIELD (NM_SETTING_DCB_PRIORITY_GROUP_FLAGS, 5), /* 10 */ + SETTING_FIELD (NM_SETTING_DCB_PRIORITY_GROUP_ID, 10), /* 11 */ + SETTING_FIELD (NM_SETTING_DCB_PRIORITY_GROUP_BANDWIDTH, 30), /* 12 */ + SETTING_FIELD (NM_SETTING_DCB_PRIORITY_BANDWIDTH, 30), /* 13 */ + SETTING_FIELD (NM_SETTING_DCB_PRIORITY_STRICT_BANDWIDTH, 10), /* 14 */ + SETTING_FIELD (NM_SETTING_DCB_PRIORITY_TRAFFIC_CLASS, 30), /* 15 */ + {NULL, NULL, 0, NULL, FALSE, FALSE, 0} +}; +#define NMC_FIELDS_SETTING_DCB_ALL "name"","\ + NM_SETTING_DCB_APP_FCOE_FLAGS","\ + NM_SETTING_DCB_APP_FCOE_PRIORITY","\ + NM_SETTING_DCB_APP_FCOE_MODE","\ + NM_SETTING_DCB_APP_ISCSI_FLAGS","\ + NM_SETTING_DCB_APP_ISCSI_PRIORITY","\ + NM_SETTING_DCB_APP_FIP_FLAGS","\ + NM_SETTING_DCB_APP_FIP_PRIORITY","\ + NM_SETTING_DCB_PRIORITY_FLOW_CONTROL_FLAGS","\ + NM_SETTING_DCB_PRIORITY_FLOW_CONTROL","\ + NM_SETTING_DCB_PRIORITY_GROUP_FLAGS","\ + NM_SETTING_DCB_PRIORITY_GROUP_ID","\ + NM_SETTING_DCB_PRIORITY_GROUP_BANDWIDTH","\ + NM_SETTING_DCB_PRIORITY_BANDWIDTH","\ + NM_SETTING_DCB_PRIORITY_STRICT_BANDWIDTH","\ + NM_SETTING_DCB_PRIORITY_TRAFFIC_CLASS +#define NMC_FIELDS_SETTING_DCB_COMMON NMC_FIELDS_SETTING_DCB_ALL + +/*----------------------------------------------------------------------------*/ + +static char * +wep_key_type_to_string (NMWepKeyType type) +{ + switch (type) { + case NM_WEP_KEY_TYPE_KEY: + return g_strdup_printf (_("%d (key)"), type); + case NM_WEP_KEY_TYPE_PASSPHRASE: + return g_strdup_printf (_("%d (passphrase)"), type); + case NM_WEP_KEY_TYPE_UNKNOWN: + default: + return g_strdup_printf (_("%d (unknown)"), type); + } +} + +static char * +bytes_to_string (GBytes *bytes) +{ + const guint8 *data; + gsize len; + GString *cert = NULL; + int i; + + if (!bytes) + return NULL; + data = g_bytes_get_data (bytes, &len); + + cert = g_string_new (NULL); + for (i = 0; i < len; i++) + g_string_append_printf (cert, "%02X", data[i]); + + return g_string_free (cert, FALSE); +} + +static char * +vlan_flags_to_string (guint32 flags) +{ + GString *flag_str; + + if (flags == 0) + return g_strdup (_("0 (NONE)")); + + flag_str = g_string_new (NULL); + g_string_printf (flag_str, "%d (", flags); + + if (flags & NM_VLAN_FLAG_REORDER_HEADERS) + g_string_append (flag_str, _("REORDER_HEADERS, ")); + if (flags & NM_VLAN_FLAG_GVRP) + g_string_append (flag_str, _("GVRP, ")); + if (flags & NM_VLAN_FLAG_LOOSE_BINDING) + g_string_append (flag_str, _("LOOSE_BINDING, ")); + + if (flag_str->str[flag_str->len-1] == '(') + g_string_append (flag_str, _("unknown")); + else + g_string_truncate (flag_str, flag_str->len-2); /* chop off trailing ', ' */ + + g_string_append_c (flag_str, ')'); + + return g_string_free (flag_str, FALSE); +} + +static char * +vlan_priorities_to_string (NMSettingVlan *s_vlan, NMVlanPriorityMap map) +{ + GString *priorities; + int i; + + priorities = g_string_new (NULL); + for (i = 0; i < nm_setting_vlan_get_num_priorities (s_vlan, map); i++) { + guint32 from, to; + + if (nm_setting_vlan_get_priority (s_vlan, map, i, &from, &to)) + g_string_append_printf (priorities, "%d:%d,", from, to); + } + if (priorities->len) + g_string_truncate (priorities, priorities->len-1); /* chop off trailing ',' */ + + return g_string_free (priorities, FALSE); +} + +static char * +ip6_privacy_to_string (NMSettingIP6ConfigPrivacy ip6_privacy) +{ + switch (ip6_privacy) { + case NM_SETTING_IP6_CONFIG_PRIVACY_DISABLED: + return g_strdup_printf (_("%d (disabled)"), ip6_privacy); + case NM_SETTING_IP6_CONFIG_PRIVACY_PREFER_PUBLIC_ADDR: + return g_strdup_printf (_("%d (enabled, prefer public IP)"), ip6_privacy); + case NM_SETTING_IP6_CONFIG_PRIVACY_PREFER_TEMP_ADDR: + return g_strdup_printf (_("%d (enabled, prefer temporary IP)"), ip6_privacy); + default: + return g_strdup_printf (_("%d (unknown)"), ip6_privacy); + } +} + +static char * +secret_flags_to_string (guint32 flags) +{ + GString *flag_str; + + if (flags == 0) + return g_strdup (_("0 (none)")); + + flag_str = g_string_new (NULL); + g_string_printf (flag_str, "%d (", flags); + + if (flags & NM_SETTING_SECRET_FLAG_AGENT_OWNED) + g_string_append (flag_str, _("agent-owned, ")); + if (flags & NM_SETTING_SECRET_FLAG_NOT_SAVED) + g_string_append (flag_str, _("not saved, ")); + if (flags & NM_SETTING_SECRET_FLAG_NOT_REQUIRED) + g_string_append (flag_str, _("not required, ")); + + if (flag_str->str[flag_str->len-1] == '(') + g_string_append (flag_str, _("unknown")); + else + g_string_truncate (flag_str, flag_str->len-2); /* chop off trailing ', ' */ + + g_string_append_c (flag_str, ')'); + + return g_string_free (flag_str, FALSE); +} + +static void +vpn_data_item (const char *key, const char *value, gpointer user_data) +{ + GString *ret_str = (GString *) user_data; + + if (ret_str->len != 0) + g_string_append (ret_str, ", "); + + g_string_append_printf (ret_str, "%s = %s", key, value); +} + + +/* === property get functions === */ +#define DEFINE_GETTER(func_name, property_name) \ + static char * \ + func_name (NMSetting *setting) \ + { \ + char *s; \ + GValue val = G_VALUE_INIT; \ + g_value_init (&val, G_TYPE_STRING); \ + g_object_get_property (G_OBJECT (setting), property_name, &val); \ + s = g_value_dup_string (&val); \ + g_value_unset (&val); \ + return s; \ + } + +#define DEFINE_SECRET_FLAGS_GETTER(func_name, property_name) \ + static char * \ + func_name (NMSetting *setting) \ + { \ + guint v; \ + GValue val = G_VALUE_INIT; \ + g_value_init (&val, G_TYPE_UINT); \ + g_object_get_property (G_OBJECT (setting), property_name, &val); \ + v = g_value_get_uint (&val); \ + g_value_unset (&val); \ + return secret_flags_to_string (v); \ + } + +/* --- NM_SETTING_802_1X_SETTING_NAME property get functions --- */ +DEFINE_GETTER (nmc_property_802_1X_get_eap, NM_SETTING_802_1X_EAP) +DEFINE_GETTER (nmc_property_802_1X_get_identity, NM_SETTING_802_1X_IDENTITY) +DEFINE_GETTER (nmc_property_802_1X_get_anonymous_identity, NM_SETTING_802_1X_ANONYMOUS_IDENTITY) +DEFINE_GETTER (nmc_property_802_1X_get_pac_file, NM_SETTING_802_1X_PAC_FILE) +DEFINE_GETTER (nmc_property_802_1X_get_ca_path, NM_SETTING_802_1X_CA_PATH) +DEFINE_GETTER (nmc_property_802_1X_get_subject_match, NM_SETTING_802_1X_SUBJECT_MATCH) +DEFINE_GETTER (nmc_property_802_1X_get_altsubject_matches, NM_SETTING_802_1X_ALTSUBJECT_MATCHES) +DEFINE_GETTER (nmc_property_802_1X_get_phase1_peapver, NM_SETTING_802_1X_PHASE1_PEAPVER) +DEFINE_GETTER (nmc_property_802_1X_get_phase1_peaplabel, NM_SETTING_802_1X_PHASE1_PEAPLABEL) +DEFINE_GETTER (nmc_property_802_1X_get_phase1_fast_provisioning, NM_SETTING_802_1X_PHASE1_FAST_PROVISIONING) +DEFINE_GETTER (nmc_property_802_1X_get_phase2_auth, NM_SETTING_802_1X_PHASE2_AUTH) +DEFINE_GETTER (nmc_property_802_1X_get_phase2_autheap, NM_SETTING_802_1X_PHASE2_AUTHEAP) +DEFINE_GETTER (nmc_property_802_1X_get_phase2_ca_path, NM_SETTING_802_1X_PHASE2_CA_PATH) +DEFINE_GETTER (nmc_property_802_1X_get_phase2_subject_match, NM_SETTING_802_1X_PHASE2_SUBJECT_MATCH) +DEFINE_GETTER (nmc_property_802_1X_get_phase2_altsubject_matches, NM_SETTING_802_1X_PHASE2_ALTSUBJECT_MATCHES) +DEFINE_GETTER (nmc_property_802_1X_get_password, NM_SETTING_802_1X_PASSWORD) +DEFINE_SECRET_FLAGS_GETTER (nmc_property_802_1X_get_password_flags, NM_SETTING_802_1X_PASSWORD_FLAGS) +DEFINE_SECRET_FLAGS_GETTER (nmc_property_802_1X_get_password_raw_flags, NM_SETTING_802_1X_PASSWORD_RAW_FLAGS) +DEFINE_GETTER (nmc_property_802_1X_get_private_key_password, NM_SETTING_802_1X_PRIVATE_KEY_PASSWORD) +DEFINE_SECRET_FLAGS_GETTER (nmc_property_802_1X_get_private_key_password_flags, NM_SETTING_802_1X_PRIVATE_KEY_PASSWORD_FLAGS) +DEFINE_GETTER (nmc_property_802_1X_get_phase2_private_key_password, NM_SETTING_802_1X_PHASE2_PRIVATE_KEY_PASSWORD) +DEFINE_SECRET_FLAGS_GETTER (nmc_property_802_1X_get_phase2_private_key_password_flags, NM_SETTING_802_1X_PHASE2_PRIVATE_KEY_PASSWORD_FLAGS) +DEFINE_GETTER (nmc_property_802_1X_get_pin, NM_SETTING_802_1X_PIN) +DEFINE_SECRET_FLAGS_GETTER (nmc_property_802_1X_get_pin_flags, NM_SETTING_802_1X_PIN_FLAGS) +DEFINE_GETTER (nmc_property_802_1X_get_system_ca_certs, NM_SETTING_802_1X_SYSTEM_CA_CERTS) + +static char * +nmc_property_802_1X_get_ca_cert (NMSetting *setting) +{ + NMSetting8021x *s_8021X = NM_SETTING_802_1X (setting); + NMSetting8021xCKScheme scheme; + char *ca_cert_str = NULL; + + scheme = nm_setting_802_1x_get_ca_cert_scheme (s_8021X); + if (scheme == NM_SETTING_802_1X_CK_SCHEME_BLOB) + ca_cert_str = bytes_to_string (nm_setting_802_1x_get_ca_cert_blob (s_8021X)); + if (scheme == NM_SETTING_802_1X_CK_SCHEME_PATH) + ca_cert_str = g_strdup (nm_setting_802_1x_get_ca_cert_path (s_8021X)); + + return ca_cert_str; +} + +static char * +nmc_property_802_1X_get_client_cert (NMSetting *setting) +{ + NMSetting8021x *s_8021X = NM_SETTING_802_1X (setting); + NMSetting8021xCKScheme scheme; + char *client_cert_str = NULL; + + scheme = nm_setting_802_1x_get_client_cert_scheme (s_8021X); + if (scheme == NM_SETTING_802_1X_CK_SCHEME_BLOB) + client_cert_str = bytes_to_string (nm_setting_802_1x_get_client_cert_blob (s_8021X)); + if (scheme == NM_SETTING_802_1X_CK_SCHEME_PATH) + client_cert_str = g_strdup (nm_setting_802_1x_get_client_cert_path (s_8021X)); + + return client_cert_str; +} + +static char * +nmc_property_802_1X_get_phase2_ca_cert (NMSetting *setting) +{ + NMSetting8021x *s_8021X = NM_SETTING_802_1X (setting); + NMSetting8021xCKScheme scheme; + char *phase2_ca_cert_str = NULL; + + scheme = nm_setting_802_1x_get_phase2_ca_cert_scheme (s_8021X); + if (scheme == NM_SETTING_802_1X_CK_SCHEME_BLOB) + phase2_ca_cert_str = bytes_to_string (nm_setting_802_1x_get_phase2_ca_cert_blob (s_8021X)); + if (scheme == NM_SETTING_802_1X_CK_SCHEME_PATH) + phase2_ca_cert_str = g_strdup (nm_setting_802_1x_get_phase2_ca_cert_path (s_8021X)); + + return phase2_ca_cert_str; +} + +static char * +nmc_property_802_1X_get_phase2_client_cert (NMSetting *setting) +{ + NMSetting8021x *s_8021X = NM_SETTING_802_1X (setting); + NMSetting8021xCKScheme scheme; + char *phase2_client_cert_str = NULL; + + scheme = nm_setting_802_1x_get_phase2_client_cert_scheme (s_8021X); + if (scheme == NM_SETTING_802_1X_CK_SCHEME_BLOB) + phase2_client_cert_str = bytes_to_string (nm_setting_802_1x_get_phase2_client_cert_blob (s_8021X)); + if (scheme == NM_SETTING_802_1X_CK_SCHEME_PATH) + phase2_client_cert_str = g_strdup (nm_setting_802_1x_get_phase2_client_cert_path (s_8021X)); + + return phase2_client_cert_str; +} + +static char * +nmc_property_802_1X_get_password_raw (NMSetting *setting) +{ + NMSetting8021x *s_8021X = NM_SETTING_802_1X (setting); + return bytes_to_string (nm_setting_802_1x_get_password_raw (s_8021X)); +} + +static char * +nmc_property_802_1X_get_private_key (NMSetting *setting) +{ + NMSetting8021x *s_8021X = NM_SETTING_802_1X (setting); + NMSetting8021xCKScheme scheme; + char *private_key_str = NULL; + + scheme = nm_setting_802_1x_get_private_key_scheme (s_8021X); + if (scheme == NM_SETTING_802_1X_CK_SCHEME_BLOB) + private_key_str = bytes_to_string (nm_setting_802_1x_get_private_key_blob (s_8021X)); + if (scheme == NM_SETTING_802_1X_CK_SCHEME_PATH) + private_key_str = g_strdup (nm_setting_802_1x_get_private_key_path (s_8021X)); + + return private_key_str; +} + +static char * +nmc_property_802_1X_get_phase2_private_key (NMSetting *setting) +{ + NMSetting8021x *s_8021X = NM_SETTING_802_1X (setting); + NMSetting8021xCKScheme scheme; + char *phase2_private_key_str = NULL; + + scheme = nm_setting_802_1x_get_phase2_private_key_scheme (s_8021X); + if (scheme == NM_SETTING_802_1X_CK_SCHEME_BLOB) + phase2_private_key_str = bytes_to_string (nm_setting_802_1x_get_phase2_private_key_blob (s_8021X)); + if (scheme == NM_SETTING_802_1X_CK_SCHEME_PATH) + phase2_private_key_str = g_strdup (nm_setting_802_1x_get_phase2_private_key_path (s_8021X)); + + return phase2_private_key_str; +} + +/* --- NM_SETTING_ADSL_SETTING_NAME property get functions --- */ +DEFINE_GETTER (nmc_property_adsl_get_username, NM_SETTING_ADSL_USERNAME) +DEFINE_GETTER (nmc_property_adsl_get_password, NM_SETTING_ADSL_PASSWORD) +DEFINE_SECRET_FLAGS_GETTER (nmc_property_adsl_get_password_flags, NM_SETTING_ADSL_PASSWORD_FLAGS) +DEFINE_GETTER (nmc_property_adsl_get_protocol, NM_SETTING_ADSL_PROTOCOL) +DEFINE_GETTER (nmc_property_adsl_get_encapsulation, NM_SETTING_ADSL_ENCAPSULATION) +DEFINE_GETTER (nmc_property_adsl_get_vpi, NM_SETTING_ADSL_VPI) +DEFINE_GETTER (nmc_property_adsl_get_vci, NM_SETTING_ADSL_VCI) + +/* --- NM_SETTING_BLUETOOTH_SETTING_NAME property get functions --- */ +DEFINE_GETTER (nmc_property_bluetooth_get_bdaddr, NM_SETTING_BLUETOOTH_BDADDR) +DEFINE_GETTER (nmc_property_bluetooth_get_type, NM_SETTING_BLUETOOTH_TYPE) + +static char * +nmc_property_bond_get_options (NMSetting *setting) +{ + NMSettingBond *s_bond = NM_SETTING_BOND (setting); + GString *bond_options_s; + int i; + + bond_options_s = g_string_new (NULL); + for (i = 0; i < nm_setting_bond_get_num_options (s_bond); i++) { + const char *key, *value; + + nm_setting_bond_get_option (s_bond, i, &key, &value); + g_string_append_printf (bond_options_s, "%s=%s,", key, value); + } + g_string_truncate (bond_options_s, bond_options_s->len-1); /* chop off trailing ',' */ + + return g_string_free (bond_options_s, FALSE); +} + +/* --- NM_SETTING_BRIDGE_SETTING_NAME property get functions --- */ +DEFINE_GETTER (nmc_property_bridge_get_mac_address, NM_SETTING_BRIDGE_MAC_ADDRESS) +DEFINE_GETTER (nmc_property_bridge_get_stp, NM_SETTING_BRIDGE_STP) +DEFINE_GETTER (nmc_property_bridge_get_priority, NM_SETTING_BRIDGE_PRIORITY) +DEFINE_GETTER (nmc_property_bridge_get_forward_delay, NM_SETTING_BRIDGE_FORWARD_DELAY) +DEFINE_GETTER (nmc_property_bridge_get_hello_time, NM_SETTING_BRIDGE_HELLO_TIME) +DEFINE_GETTER (nmc_property_bridge_get_max_age, NM_SETTING_BRIDGE_MAX_AGE) +DEFINE_GETTER (nmc_property_bridge_get_ageing_time, NM_SETTING_BRIDGE_AGEING_TIME) + +/* --- NM_SETTING_BRIDGE_PORT_SETTING_NAME property get functions --- */ +DEFINE_GETTER (nmc_property_bridge_port_get_priority, NM_SETTING_BRIDGE_PORT_PRIORITY) +DEFINE_GETTER (nmc_property_bridge_port_get_path_cost, NM_SETTING_BRIDGE_PORT_PATH_COST) +DEFINE_GETTER (nmc_property_bridge_port_get_hairpin_mode, NM_SETTING_BRIDGE_PORT_HAIRPIN_MODE) + +/* --- NM_SETTING_TEAM_SETTING_NAME property get functions --- */ +DEFINE_GETTER (nmc_property_team_get_config, NM_SETTING_TEAM_CONFIG) + +/* --- NM_SETTING_TEAM_PORT_SETTING_NAME property get functions --- */ +DEFINE_GETTER (nmc_property_team_port_get_config, NM_SETTING_TEAM_PORT_CONFIG) + +/* --- NM_SETTING_CDMA_SETTING_NAME property get functions --- */ +DEFINE_GETTER (nmc_property_cdma_get_number, NM_SETTING_CDMA_NUMBER) +DEFINE_GETTER (nmc_property_cdma_get_username, NM_SETTING_CDMA_USERNAME) +DEFINE_GETTER (nmc_property_cdma_get_password, NM_SETTING_CDMA_PASSWORD) + +DEFINE_SECRET_FLAGS_GETTER (nmc_property_cdma_get_password_flags, NM_SETTING_CDMA_PASSWORD_FLAGS) + +/* --- NM_SETTING_CONNECTION_SETTING_NAME property get functions --- */ +DEFINE_GETTER (nmc_property_connection_get_id, NM_SETTING_CONNECTION_ID) +DEFINE_GETTER (nmc_property_connection_get_uuid, NM_SETTING_CONNECTION_UUID) +DEFINE_GETTER (nmc_property_connection_get_interface_name, NM_SETTING_CONNECTION_INTERFACE_NAME) +DEFINE_GETTER (nmc_property_connection_get_type, NM_SETTING_CONNECTION_TYPE) +DEFINE_GETTER (nmc_property_connection_get_autoconnect, NM_SETTING_CONNECTION_AUTOCONNECT) +DEFINE_GETTER (nmc_property_connection_get_autoconnect_priority, NM_SETTING_CONNECTION_AUTOCONNECT_PRIORITY) +DEFINE_GETTER (nmc_property_connection_get_timestamp, NM_SETTING_CONNECTION_TIMESTAMP) +DEFINE_GETTER (nmc_property_connection_get_read_only, NM_SETTING_CONNECTION_READ_ONLY) + +static char * +nmc_property_connection_get_permissions (NMSetting *setting) +{ + NMSettingConnection *s_con = NM_SETTING_CONNECTION (setting); + GString *perm = NULL; + const char *perm_item; + const char *perm_type; + int i; + + perm = g_string_new (NULL); + for (i = 0; i < nm_setting_connection_get_num_permissions (s_con); i++) { + if (nm_setting_connection_get_permission (s_con, i, &perm_type, &perm_item, NULL)) + g_string_append_printf (perm, "%s:%s,", perm_type, perm_item); + } + if (perm->len > 0) + g_string_truncate (perm, perm->len-1); /* remove trailing , */ + + return g_string_free (perm, FALSE); +} + +DEFINE_GETTER (nmc_property_connection_get_zone, NM_SETTING_CONNECTION_ZONE) +DEFINE_GETTER (nmc_property_connection_get_master, NM_SETTING_CONNECTION_MASTER) +DEFINE_GETTER (nmc_property_connection_get_slave_type, NM_SETTING_CONNECTION_SLAVE_TYPE) +DEFINE_GETTER (nmc_property_connection_get_secondaries, NM_SETTING_CONNECTION_SECONDARIES) +DEFINE_GETTER (nmc_property_connection_get_gateway_ping_timeout, NM_SETTING_CONNECTION_GATEWAY_PING_TIMEOUT) + +/* --- NM_SETTING_DCB_SETTING_NAME property get functions --- */ +static char * +dcb_flags_to_string (NMSettingDcbFlags flags) +{ + GString *flag_str; + + if (flags == 0) + return g_strdup (_("0 (disabled)")); + + flag_str = g_string_new (NULL); + g_string_printf (flag_str, "%d (", flags); + + if (flags & NM_SETTING_DCB_FLAG_ENABLE) + g_string_append (flag_str, _("enabled, ")); + if (flags & NM_SETTING_DCB_FLAG_ADVERTISE) + g_string_append (flag_str, _("advertise, ")); + if (flags & NM_SETTING_DCB_FLAG_WILLING) + g_string_append (flag_str, _("willing, ")); + + if (flag_str->str[flag_str->len-1] == '(') + g_string_append (flag_str, _("unknown")); + else + g_string_truncate (flag_str, flag_str->len-2); /* chop off trailing ', ' */ + + g_string_append_c (flag_str, ')'); + + return g_string_free (flag_str, FALSE); +} + +#define DEFINE_DCB_FLAGS_GETTER(func_name, property_name) \ + static char * \ + func_name (NMSetting *setting) \ + { \ + guint v; \ + GValue val = G_VALUE_INIT; \ + g_value_init (&val, G_TYPE_UINT); \ + g_object_get_property (G_OBJECT (setting), property_name, &val); \ + v = g_value_get_uint (&val); \ + g_value_unset (&val); \ + return dcb_flags_to_string (v); \ + } + +static char * +dcb_app_priority_to_string (gint priority) +{ + return (priority == -1) ? g_strdup (_("-1 (unset)")) : g_strdup_printf ("%d", priority); +} + +#define DEFINE_DCB_APP_PRIORITY_GETTER(func_name, property_name) \ + static char * \ + func_name (NMSetting *setting) \ + { \ + int v; \ + GValue val = G_VALUE_INIT; \ + g_value_init (&val, G_TYPE_INT); \ + g_object_get_property (G_OBJECT (setting), property_name, &val); \ + v = g_value_get_int (&val); \ + g_value_unset (&val); \ + return dcb_app_priority_to_string (v); \ + } + +#define DEFINE_DCB_BOOL_GETTER(func_name, getter_func_name) \ + static char * \ + func_name (NMSetting *setting) \ + { \ + NMSettingDcb *s_dcb = NM_SETTING_DCB (setting); \ + GString *str; \ + guint i; \ + \ + str = g_string_new (NULL); \ + for (i = 0; i < 8; i++) { \ + if (getter_func_name (s_dcb, i)) \ + g_string_append_c (str, '1'); \ + else \ + g_string_append_c (str, '0'); \ +\ + if (i < 7) \ + g_string_append_c (str, ','); \ + } \ +\ + return g_string_free (str, FALSE); \ + } + +#define DEFINE_DCB_UINT_GETTER(func_name, getter_func_name) \ + static char * \ + func_name (NMSetting *setting) \ + { \ + NMSettingDcb *s_dcb = NM_SETTING_DCB (setting); \ + GString *str; \ + guint i; \ + \ + str = g_string_new (NULL); \ + for (i = 0; i < 8; i++) { \ + g_string_append_printf (str, "%u", getter_func_name (s_dcb, i)); \ + if (i < 7) \ + g_string_append_c (str, ','); \ + } \ +\ + return g_string_free (str, FALSE); \ + } + +DEFINE_DCB_FLAGS_GETTER (nmc_property_dcb_get_app_fcoe_flags, NM_SETTING_DCB_APP_FCOE_FLAGS) +DEFINE_DCB_APP_PRIORITY_GETTER (nmc_property_dcb_get_app_fcoe_priority, NM_SETTING_DCB_APP_FCOE_PRIORITY) +DEFINE_GETTER (nmc_property_dcb_get_app_fcoe_mode, NM_SETTING_DCB_APP_FCOE_MODE) +DEFINE_DCB_FLAGS_GETTER (nmc_property_dcb_get_app_iscsi_flags, NM_SETTING_DCB_APP_ISCSI_FLAGS) +DEFINE_DCB_APP_PRIORITY_GETTER (nmc_property_dcb_get_app_iscsi_priority, NM_SETTING_DCB_APP_ISCSI_PRIORITY) +DEFINE_DCB_FLAGS_GETTER (nmc_property_dcb_get_app_fip_flags, NM_SETTING_DCB_APP_FIP_FLAGS) +DEFINE_DCB_APP_PRIORITY_GETTER (nmc_property_dcb_get_app_fip_priority, NM_SETTING_DCB_APP_FIP_PRIORITY) + +DEFINE_DCB_FLAGS_GETTER (nmc_property_dcb_get_pfc_flags, NM_SETTING_DCB_PRIORITY_FLOW_CONTROL_FLAGS) +DEFINE_DCB_BOOL_GETTER (nmc_property_dcb_get_pfc, nm_setting_dcb_get_priority_flow_control) + +DEFINE_DCB_FLAGS_GETTER (nmc_property_dcb_get_pg_flags, NM_SETTING_DCB_PRIORITY_GROUP_FLAGS) +DEFINE_DCB_UINT_GETTER (nmc_property_dcb_get_pg_group_id, nm_setting_dcb_get_priority_group_id) +DEFINE_DCB_UINT_GETTER (nmc_property_dcb_get_pg_group_bandwidth, nm_setting_dcb_get_priority_group_bandwidth) +DEFINE_DCB_UINT_GETTER (nmc_property_dcb_get_pg_bandwidth, nm_setting_dcb_get_priority_bandwidth) +DEFINE_DCB_BOOL_GETTER (nmc_property_dcb_get_pg_strict, nm_setting_dcb_get_priority_strict_bandwidth) +DEFINE_DCB_UINT_GETTER (nmc_property_dcb_get_pg_traffic_class, nm_setting_dcb_get_priority_traffic_class) + +/* --- NM_SETTING_GSM_SETTING_NAME property get functions --- */ +DEFINE_GETTER (nmc_property_gsm_get_number, NM_SETTING_GSM_NUMBER) +DEFINE_GETTER (nmc_property_gsm_get_username, NM_SETTING_GSM_USERNAME) +DEFINE_GETTER (nmc_property_gsm_get_password, NM_SETTING_GSM_PASSWORD) +DEFINE_SECRET_FLAGS_GETTER (nmc_property_gsm_get_password_flags, NM_SETTING_GSM_PASSWORD_FLAGS) +DEFINE_GETTER (nmc_property_gsm_get_apn, NM_SETTING_GSM_APN) +DEFINE_GETTER (nmc_property_gsm_get_network_id, NM_SETTING_GSM_NETWORK_ID) +DEFINE_GETTER (nmc_property_gsm_get_pin, NM_SETTING_GSM_PIN) +DEFINE_SECRET_FLAGS_GETTER (nmc_property_gsm_get_pin_flags, NM_SETTING_GSM_PIN_FLAGS) +DEFINE_GETTER (nmc_property_gsm_get_home_only, NM_SETTING_GSM_HOME_ONLY) + +/* --- NM_SETTING_INFINIBAND_SETTING_NAME property get functions --- */ +DEFINE_GETTER (nmc_property_ib_get_mac_address, NM_SETTING_INFINIBAND_MAC_ADDRESS) +DEFINE_GETTER (nmc_property_ib_get_transport_mode, NM_SETTING_INFINIBAND_TRANSPORT_MODE) + +static char * +nmc_property_ib_get_mtu (NMSetting *setting) +{ + NMSettingInfiniband *s_infiniband = NM_SETTING_INFINIBAND (setting); + int mtu; + + mtu = nm_setting_infiniband_get_mtu (s_infiniband); + if (mtu == 0) + return g_strdup (_("auto")); + else + return g_strdup_printf ("%d", mtu); +} + +static char * +nmc_property_ib_get_p_key (NMSetting *setting) +{ + NMSettingInfiniband *s_infiniband = NM_SETTING_INFINIBAND (setting); + int p_key; + + p_key = nm_setting_infiniband_get_p_key (s_infiniband); + if (p_key == -1) + return g_strdup (_("default")); + else + return g_strdup_printf ("0x%04x", p_key); +} + +DEFINE_GETTER (nmc_property_ib_get_parent, NM_SETTING_INFINIBAND_PARENT) + +/* --- NM_SETTING_IP4_CONFIG_SETTING_NAME property get functions --- */ +DEFINE_GETTER (nmc_property_ipv4_get_method, NM_SETTING_IP_CONFIG_METHOD) +DEFINE_GETTER (nmc_property_ipv4_get_dns, NM_SETTING_IP_CONFIG_DNS) +DEFINE_GETTER (nmc_property_ipv4_get_dns_search, NM_SETTING_IP_CONFIG_DNS_SEARCH) + +static char * +nmc_property_ip_get_addresses (NMSetting *setting) +{ + NMSettingIPConfig *s_ip = NM_SETTING_IP_CONFIG (setting); + GString *printable; + guint32 num_addresses, i; + NMIPAddress *addr; + + printable = g_string_new (NULL); + + num_addresses = nm_setting_ip_config_get_num_addresses (s_ip); + for (i = 0; i < num_addresses; i++) { + addr = nm_setting_ip_config_get_address (s_ip, i); + + if (printable->len > 0) + g_string_append (printable, ", "); + + g_string_append_printf (printable, "%s/%u", + nm_ip_address_get_address (addr), + nm_ip_address_get_prefix (addr)); + } + + return g_string_free (printable, FALSE); +} + +static char * +nmc_property_ipv4_get_routes (NMSetting *setting) +{ + NMSettingIPConfig *s_ip4 = NM_SETTING_IP_CONFIG (setting); + GString *printable; + guint32 num_routes, i; + NMIPRoute *route; + + printable = g_string_new (NULL); + + num_routes = nm_setting_ip_config_get_num_routes (s_ip4); + for (i = 0; i < num_routes; i++) { + route = nm_setting_ip_config_get_route (s_ip4, i); + + if (printable->len > 0) + g_string_append (printable, "; "); + + g_string_append (printable, "{ "); + + g_string_append_printf (printable, "ip = %s/%u", + nm_ip_route_get_dest (route), + nm_ip_route_get_prefix (route)); + + if (nm_ip_route_get_next_hop (route)) { + g_string_append_printf (printable, ", nh = %s", + nm_ip_route_get_next_hop (route)); + } + + if (nm_ip_route_get_metric (route) != -1) + g_string_append_printf (printable, ", mt = %u", (guint32) nm_ip_route_get_metric (route)); + + g_string_append (printable, " }"); + } + + return g_string_free (printable, FALSE); +} + +DEFINE_GETTER (nmc_property_ipv4_get_gateway, NM_SETTING_IP_CONFIG_GATEWAY) +DEFINE_GETTER (nmc_property_ipv4_get_route_metric, NM_SETTING_IP_CONFIG_ROUTE_METRIC) +DEFINE_GETTER (nmc_property_ipv4_get_ignore_auto_routes, NM_SETTING_IP_CONFIG_IGNORE_AUTO_ROUTES) +DEFINE_GETTER (nmc_property_ipv4_get_ignore_auto_dns, NM_SETTING_IP_CONFIG_IGNORE_AUTO_DNS) +DEFINE_GETTER (nmc_property_ipv4_get_dhcp_client_id, NM_SETTING_IP4_CONFIG_DHCP_CLIENT_ID) +DEFINE_GETTER (nmc_property_ipv4_get_dhcp_send_hostname, NM_SETTING_IP_CONFIG_DHCP_SEND_HOSTNAME) +DEFINE_GETTER (nmc_property_ipv4_get_dhcp_hostname, NM_SETTING_IP_CONFIG_DHCP_HOSTNAME) +DEFINE_GETTER (nmc_property_ipv4_get_never_default, NM_SETTING_IP_CONFIG_NEVER_DEFAULT) +DEFINE_GETTER (nmc_property_ipv4_get_may_fail, NM_SETTING_IP_CONFIG_MAY_FAIL) + +/* --- NM_SETTING_IP6_CONFIG_SETTING_NAME property get functions --- */ +DEFINE_GETTER (nmc_property_ipv6_get_method, NM_SETTING_IP_CONFIG_METHOD) +DEFINE_GETTER (nmc_property_ipv6_get_dns, NM_SETTING_IP_CONFIG_DNS) +DEFINE_GETTER (nmc_property_ipv6_get_dns_search, NM_SETTING_IP_CONFIG_DNS_SEARCH) + +static char * +nmc_property_ipv6_get_routes (NMSetting *setting) +{ + NMSettingIPConfig *s_ip6 = NM_SETTING_IP_CONFIG (setting); + GString *printable; + guint32 num_routes, i; + NMIPRoute *route; + + printable = g_string_new (NULL); + + num_routes = nm_setting_ip_config_get_num_routes (s_ip6); + for (i = 0; i < num_routes; i++) { + route = nm_setting_ip_config_get_route (s_ip6, i); + + if (printable->len > 0) + g_string_append (printable, "; "); + + g_string_append (printable, "{ "); + + g_string_append_printf (printable, "ip = %s/%u", + nm_ip_route_get_dest (route), + nm_ip_route_get_prefix (route)); + + if (nm_ip_route_get_next_hop (route)) { + g_string_append_printf (printable, ", nh = %s", + nm_ip_route_get_next_hop (route)); + } + + if (nm_ip_route_get_metric (route) != -1) + g_string_append_printf (printable, ", mt = %u", (guint32) nm_ip_route_get_metric (route)); + + g_string_append (printable, " }"); + } + + return g_string_free (printable, FALSE); +} + +DEFINE_GETTER (nmc_property_ipv6_get_gateway, NM_SETTING_IP_CONFIG_GATEWAY) +DEFINE_GETTER (nmc_property_ipv6_get_route_metric, NM_SETTING_IP_CONFIG_ROUTE_METRIC) +DEFINE_GETTER (nmc_property_ipv6_get_ignore_auto_routes, NM_SETTING_IP_CONFIG_IGNORE_AUTO_ROUTES) +DEFINE_GETTER (nmc_property_ipv6_get_ignore_auto_dns, NM_SETTING_IP_CONFIG_IGNORE_AUTO_DNS) +DEFINE_GETTER (nmc_property_ipv6_get_never_default, NM_SETTING_IP_CONFIG_NEVER_DEFAULT) +DEFINE_GETTER (nmc_property_ipv6_get_may_fail, NM_SETTING_IP_CONFIG_MAY_FAIL) +DEFINE_GETTER (nmc_property_ipv6_get_dhcp_send_hostname, NM_SETTING_IP_CONFIG_DHCP_SEND_HOSTNAME) +DEFINE_GETTER (nmc_property_ipv6_get_dhcp_hostname, NM_SETTING_IP_CONFIG_DHCP_HOSTNAME) + +static char * +nmc_property_ipv6_get_ip6_privacy (NMSetting *setting) +{ + NMSettingIP6Config *s_ip6 = NM_SETTING_IP6_CONFIG (setting); + return ip6_privacy_to_string (nm_setting_ip6_config_get_ip6_privacy (s_ip6)); +} + +/* --- NM_SETTING_OLPC_MESH_SETTING_NAME property get functions --- */ +DEFINE_GETTER (nmc_property_olpc_get_channel, NM_SETTING_OLPC_MESH_CHANNEL) +DEFINE_GETTER (nmc_property_olpc_get_anycast_address, NM_SETTING_OLPC_MESH_DHCP_ANYCAST_ADDRESS) + +static char * +nmc_property_olpc_get_ssid (NMSetting *setting) +{ + NMSettingOlpcMesh *s_olpc_mesh = NM_SETTING_OLPC_MESH (setting); + GBytes *ssid; + char *ssid_str = NULL; + + ssid = nm_setting_olpc_mesh_get_ssid (s_olpc_mesh); + if (ssid) { + ssid_str = nm_utils_ssid_to_utf8 (g_bytes_get_data (ssid, NULL), + g_bytes_get_size (ssid)); + } + + return ssid_str; +} + +/* --- NM_SETTING_PPP_SETTING_NAME property get functions --- */ +DEFINE_GETTER (nmc_property_ppp_get_noauth, NM_SETTING_PPP_NOAUTH) +DEFINE_GETTER (nmc_property_ppp_get_refuse_eap, NM_SETTING_PPP_REFUSE_EAP) +DEFINE_GETTER (nmc_property_ppp_get_refuse_pap, NM_SETTING_PPP_REFUSE_PAP) +DEFINE_GETTER (nmc_property_ppp_get_refuse_chap, NM_SETTING_PPP_REFUSE_CHAP) +DEFINE_GETTER (nmc_property_ppp_get_refuse_mschap, NM_SETTING_PPP_REFUSE_MSCHAP) +DEFINE_GETTER (nmc_property_ppp_get_refuse_mschapv2, NM_SETTING_PPP_REFUSE_MSCHAPV2) +DEFINE_GETTER (nmc_property_ppp_get_nobsdcomp, NM_SETTING_PPP_NOBSDCOMP) +DEFINE_GETTER (nmc_property_ppp_get_nodeflate, NM_SETTING_PPP_NODEFLATE) +DEFINE_GETTER (nmc_property_ppp_get_no_vj_comp, NM_SETTING_PPP_NO_VJ_COMP) +DEFINE_GETTER (nmc_property_ppp_get_require_mppe, NM_SETTING_PPP_REQUIRE_MPPE) +DEFINE_GETTER (nmc_property_ppp_get_require_mppe_128, NM_SETTING_PPP_REQUIRE_MPPE_128) +DEFINE_GETTER (nmc_property_ppp_get_mppe_stateful, NM_SETTING_PPP_MPPE_STATEFUL) +DEFINE_GETTER (nmc_property_ppp_get_crtscts, NM_SETTING_PPP_CRTSCTS) +DEFINE_GETTER (nmc_property_ppp_get_baud, NM_SETTING_PPP_BAUD) +DEFINE_GETTER (nmc_property_ppp_get_mru, NM_SETTING_PPP_MRU) +DEFINE_GETTER (nmc_property_ppp_get_mtu, NM_SETTING_PPP_MTU) +DEFINE_GETTER (nmc_property_ppp_get_lcp_echo_failure, NM_SETTING_PPP_LCP_ECHO_FAILURE) +DEFINE_GETTER (nmc_property_ppp_get_lcp_echo_interval, NM_SETTING_PPP_LCP_ECHO_INTERVAL) + +/* --- NM_SETTING_PPPOE_SETTING_NAME property get functions --- */ +DEFINE_GETTER (nmc_property_pppoe_get_service, NM_SETTING_PPPOE_SERVICE) +DEFINE_GETTER (nmc_property_pppoe_get_username, NM_SETTING_PPPOE_USERNAME) +DEFINE_GETTER (nmc_property_pppoe_get_password, NM_SETTING_PPPOE_PASSWORD) +DEFINE_SECRET_FLAGS_GETTER (nmc_property_pppoe_get_password_flags, NM_SETTING_PPPOE_PASSWORD_FLAGS) + +/* --- NM_SETTING_SERIAL_SETTING_NAME property get functions --- */ +DEFINE_GETTER (nmc_property_serial_get_baud, NM_SETTING_SERIAL_BAUD) +DEFINE_GETTER (nmc_property_serial_get_bits, NM_SETTING_SERIAL_BITS) +DEFINE_GETTER (nmc_property_serial_get_stopbits, NM_SETTING_SERIAL_STOPBITS) +DEFINE_GETTER (nmc_property_serial_get_send_delay, NM_SETTING_SERIAL_SEND_DELAY) + +/* --- NM_SETTING_VLAN_SETTING_NAME property get functions --- */ +DEFINE_GETTER (nmc_property_vlan_get_parent, NM_SETTING_VLAN_PARENT) +DEFINE_GETTER (nmc_property_vlan_get_id, NM_SETTING_VLAN_ID) + + +static char * +nmc_property_vlan_get_flags (NMSetting *setting) +{ + NMSettingVlan *s_vlan = NM_SETTING_VLAN (setting); + return vlan_flags_to_string (nm_setting_vlan_get_flags (s_vlan)); +} + +static char * +nmc_property_vlan_get_ingress_priority_map (NMSetting *setting) +{ + NMSettingVlan *s_vlan = NM_SETTING_VLAN (setting); + return vlan_priorities_to_string (s_vlan, NM_VLAN_INGRESS_MAP); +} + +static char * +nmc_property_vlan_get_egress_priority_map (NMSetting *setting) +{ + NMSettingVlan *s_vlan = NM_SETTING_VLAN (setting); + return vlan_priorities_to_string (s_vlan, NM_VLAN_EGRESS_MAP); +} + +/* --- NM_SETTING_VPN_SETTING_NAME property get functions --- */ +DEFINE_GETTER (nmc_property_vpn_get_service_type, NM_SETTING_VPN_SERVICE_TYPE) +DEFINE_GETTER (nmc_property_vpn_get_user_name, NM_SETTING_VPN_USER_NAME) + +static char * +nmc_property_vpn_get_data (NMSetting *setting) +{ + NMSettingVpn *s_vpn = NM_SETTING_VPN (setting); + GString *data_item_str; + + data_item_str = g_string_new (NULL); + nm_setting_vpn_foreach_data_item (s_vpn, &vpn_data_item, data_item_str); + + return g_string_free (data_item_str, FALSE); +} + +static char * +nmc_property_vpn_get_secrets (NMSetting *setting) +{ + NMSettingVpn *s_vpn = NM_SETTING_VPN (setting); + GString *secret_str; + + secret_str = g_string_new (NULL); + nm_setting_vpn_foreach_secret (s_vpn, &vpn_data_item, secret_str); + + return g_string_free (secret_str, FALSE); +} + +DEFINE_GETTER (nmc_property_vpn_get_persistent, NM_SETTING_VPN_PERSISTENT) + +/* --- NM_SETTING_WIMAX_SETTING_NAME property get functions --- */ +DEFINE_GETTER (nmc_property_wimax_get_network_name, NM_SETTING_WIMAX_NETWORK_NAME) +DEFINE_GETTER (nmc_property_wimax_get_mac_address, NM_SETTING_WIMAX_MAC_ADDRESS) + +/* --- NM_SETTING_WIRED_SETTING_NAME property get functions --- */ +DEFINE_GETTER (nmc_property_wired_get_port, NM_SETTING_WIRED_PORT) +DEFINE_GETTER (nmc_property_wired_get_speed, NM_SETTING_WIRED_SPEED) +DEFINE_GETTER (nmc_property_wired_get_duplex, NM_SETTING_WIRED_DUPLEX) +DEFINE_GETTER (nmc_property_wired_get_auto_negotiate, NM_SETTING_WIRED_AUTO_NEGOTIATE) +DEFINE_GETTER (nmc_property_wired_get_mac_address, NM_SETTING_WIRED_MAC_ADDRESS) +DEFINE_GETTER (nmc_property_wired_get_cloned_mac_address, NM_SETTING_WIRED_CLONED_MAC_ADDRESS) +DEFINE_GETTER (nmc_property_wired_get_mac_address_blacklist, NM_SETTING_WIRED_MAC_ADDRESS_BLACKLIST) +DEFINE_GETTER (nmc_property_wired_get_s390_subchannels, NM_SETTING_WIRED_S390_SUBCHANNELS) +DEFINE_GETTER (nmc_property_wired_get_s390_nettype, NM_SETTING_WIRED_S390_NETTYPE) +DEFINE_GETTER (nmc_property_wired_get_s390_options, NM_SETTING_WIRED_S390_OPTIONS) + +static char * +nmc_property_wired_get_mtu (NMSetting *setting) +{ + NMSettingWired *s_wired = NM_SETTING_WIRED (setting); + int mtu; + + mtu = nm_setting_wired_get_mtu (s_wired); + if (mtu == 0) + return g_strdup (_("auto")); + else + return g_strdup_printf ("%d", mtu); +} + +/* --- NM_SETTING_WIRELESS_SETTING_NAME property get functions --- */ +DEFINE_GETTER (nmc_property_wireless_get_mode, NM_SETTING_WIRELESS_MODE) +DEFINE_GETTER (nmc_property_wireless_get_band, NM_SETTING_WIRELESS_BAND) +DEFINE_GETTER (nmc_property_wireless_get_channel, NM_SETTING_WIRELESS_CHANNEL) +DEFINE_GETTER (nmc_property_wireless_get_bssid, NM_SETTING_WIRELESS_BSSID) +DEFINE_GETTER (nmc_property_wireless_get_rate, NM_SETTING_WIRELESS_RATE) +DEFINE_GETTER (nmc_property_wireless_get_tx_power, NM_SETTING_WIRELESS_TX_POWER) +DEFINE_GETTER (nmc_property_wireless_get_mac_address, NM_SETTING_WIRELESS_MAC_ADDRESS) +DEFINE_GETTER (nmc_property_wireless_get_cloned_mac_address, NM_SETTING_WIRELESS_CLONED_MAC_ADDRESS) +DEFINE_GETTER (nmc_property_wireless_get_mac_address_blacklist, NM_SETTING_WIRELESS_MAC_ADDRESS_BLACKLIST) +DEFINE_GETTER (nmc_property_wireless_get_seen_bssids, NM_SETTING_WIRELESS_SEEN_BSSIDS) +DEFINE_GETTER (nmc_property_wireless_get_hidden, NM_SETTING_WIRELESS_HIDDEN) + +static char * +nmc_property_wireless_get_ssid (NMSetting *setting) +{ + NMSettingWireless *s_wireless = NM_SETTING_WIRELESS (setting); + GBytes *ssid; + char *ssid_str = NULL; + + ssid = nm_setting_wireless_get_ssid (s_wireless); + if (ssid) { + ssid_str = nm_utils_ssid_to_utf8 (g_bytes_get_data (ssid, NULL), + g_bytes_get_size (ssid)); + } + + return ssid_str; +} + +static char * +nmc_property_wireless_get_mtu (NMSetting *setting) +{ + NMSettingWireless *s_wireless = NM_SETTING_WIRELESS (setting); + int mtu; + + mtu = nm_setting_wireless_get_mtu (s_wireless); + if (mtu == 0) + return g_strdup (_("auto")); + else + return g_strdup_printf ("%d", mtu); +} + +/* --- NM_SETTING_WIRELESS_SECURITY_SETTING_NAME property get functions --- */ +DEFINE_GETTER (nmc_property_wifi_sec_get_key_mgmt, NM_SETTING_WIRELESS_SECURITY_KEY_MGMT) +DEFINE_GETTER (nmc_property_wifi_sec_get_wep_tx_keyidx, NM_SETTING_WIRELESS_SECURITY_WEP_TX_KEYIDX) +DEFINE_GETTER (nmc_property_wifi_sec_get_auth_alg, NM_SETTING_WIRELESS_SECURITY_AUTH_ALG) +DEFINE_GETTER (nmc_property_wifi_sec_get_proto, NM_SETTING_WIRELESS_SECURITY_PROTO) +DEFINE_GETTER (nmc_property_wifi_sec_get_pairwise, NM_SETTING_WIRELESS_SECURITY_PAIRWISE) +DEFINE_GETTER (nmc_property_wifi_sec_get_group, NM_SETTING_WIRELESS_SECURITY_GROUP) +DEFINE_GETTER (nmc_property_wifi_sec_get_leap_username, NM_SETTING_WIRELESS_SECURITY_LEAP_USERNAME) +DEFINE_SECRET_FLAGS_GETTER (nmc_property_wifi_sec_get_wep_key_flags, NM_SETTING_WIRELESS_SECURITY_WEP_KEY_FLAGS) +DEFINE_GETTER (nmc_property_wifi_sec_get_psk, NM_SETTING_WIRELESS_SECURITY_PSK) +DEFINE_SECRET_FLAGS_GETTER (nmc_property_wifi_sec_get_psk_flags, NM_SETTING_WIRELESS_SECURITY_PSK_FLAGS) +DEFINE_GETTER (nmc_property_wifi_sec_get_leap_password, NM_SETTING_WIRELESS_SECURITY_LEAP_PASSWORD) +DEFINE_SECRET_FLAGS_GETTER (nmc_property_wifi_sec_get_leap_password_flags, NM_SETTING_WIRELESS_SECURITY_LEAP_PASSWORD_FLAGS) + +static char * +nmc_property_wifi_sec_get_wep_key0 (NMSetting *setting) +{ + NMSettingWirelessSecurity *s_wireless_sec = NM_SETTING_WIRELESS_SECURITY (setting); + return g_strdup (nm_setting_wireless_security_get_wep_key (s_wireless_sec, 0)); +} + +static char * +nmc_property_wifi_sec_get_wep_key1 (NMSetting *setting) +{ + NMSettingWirelessSecurity *s_wireless_sec = NM_SETTING_WIRELESS_SECURITY (setting); + return g_strdup (nm_setting_wireless_security_get_wep_key (s_wireless_sec, 1)); +} + +static char * +nmc_property_wifi_sec_get_wep_key2 (NMSetting *setting) +{ + NMSettingWirelessSecurity *s_wireless_sec = NM_SETTING_WIRELESS_SECURITY (setting); + return g_strdup (nm_setting_wireless_security_get_wep_key (s_wireless_sec, 2)); +} + +static char * +nmc_property_wifi_sec_get_wep_key3 (NMSetting *setting) +{ + NMSettingWirelessSecurity *s_wireless_sec = NM_SETTING_WIRELESS_SECURITY (setting); + return g_strdup (nm_setting_wireless_security_get_wep_key (s_wireless_sec, 3)); +} + +static char * +nmc_property_wifi_sec_get_wep_key_type (NMSetting *setting) +{ + NMSettingWirelessSecurity *s_wireless_sec = NM_SETTING_WIRELESS_SECURITY (setting); + return wep_key_type_to_string (nm_setting_wireless_security_get_wep_key_type (s_wireless_sec)); +} + +/*----------------------------------------------------------------------------*/ + +static void +nmc_value_transform_bool_string (const GValue *src_value, + GValue *dest_value) +{ + dest_value->data[0].v_pointer = g_strdup (src_value->data[0].v_int ? "yes" : "no"); +} + +static void +nmc_value_transform_char_string (const GValue *src_value, + GValue *dest_value) +{ + dest_value->data[0].v_pointer = g_strdup_printf ("%c", src_value->data[0].v_uint); +} + +static void __attribute__((constructor)) +register_nmcli_value_transforms (void) +{ + g_value_register_transform_func (G_TYPE_BOOLEAN, G_TYPE_STRING, nmc_value_transform_bool_string); + g_value_register_transform_func (G_TYPE_CHAR, G_TYPE_STRING, nmc_value_transform_char_string); +} + +/*----------------------------------------------------------------------------*/ + +/* Main hash table storing function pointer for manipulating properties */ +static GHashTable *nmc_properties = NULL; +typedef char * (*NmcPropertyGetFunc) (NMSetting *); +typedef gboolean (*NmcPropertySetFunc) (NMSetting *, const char *, const char *, GError **); +typedef gboolean (*NmcPropertyRemoveFunc) (NMSetting *, const char *, const char *, guint32, GError **); +typedef const char * (*NmcPropertyDescribeFunc) (NMSetting *, const char *); +typedef const char * (*NmcPropertyValuesFunc) (NMSetting *, const char *); +typedef char * (*NmcPropertyOut2InFunc) (const char *); + +typedef struct { + NmcPropertyGetFunc get_func; /* func getting property values */ + NmcPropertySetFunc set_func; /* func adding/setting property values */ + NmcPropertyRemoveFunc remove_func; /* func removing items from container options */ + NmcPropertyDescribeFunc describe_func; /* func returning property description */ + NmcPropertyValuesFunc values_func; /* func returning allowed property values */ + NmcPropertyOut2InFunc out2in_func; /* func converting property values from output to input format */ +} NmcPropertyFuncs; + +/* + * We need NmCli in some _set_property functions, and they aren't passed NmCli. + * So use the global variable. + */ +/* Global variable defined in nmcli.c */ +extern NmCli nm_cli; + +NMSetting * +nmc_setting_new_for_name (const char *name) +{ + GType stype; + NMSetting *setting = NULL; + + if (name) { + stype = nm_setting_lookup_type (name); + if (stype != G_TYPE_INVALID) { + setting = g_object_new (stype, NULL); + g_warn_if_fail (NM_IS_SETTING (setting)); + } + } + return setting; +} + +static gboolean +get_answer (const char *prop, const char *value) +{ + char *tmp_str; + char *question; + gboolean answer = FALSE; + + if (value) + question = g_strdup_printf (_("Do you also want to set '%s' to '%s'? [yes]: "), prop, value); + else + question = g_strdup_printf (_("Do you also want to clear '%s'? [yes]: "), prop); + tmp_str = nmc_get_user_input (question); + if (!tmp_str || matches (tmp_str, "yes") == 0) + answer = TRUE; + g_free (tmp_str); + g_free (question); + return answer; +} + +static void ipv4_method_changed_cb (GObject *object, GParamSpec *pspec, gpointer user_data); +static void ipv6_method_changed_cb (GObject *object, GParamSpec *pspec, gpointer user_data); + +static void +ipv4_addresses_changed_cb (GObject *object, GParamSpec *pspec, gpointer user_data) +{ + static gboolean answered = FALSE; + static gboolean answer = FALSE; + + g_signal_handlers_block_by_func (object, G_CALLBACK (ipv4_method_changed_cb), NULL); + + /* If we have some IP addresses set method to 'manual'. + * Else if the method was 'manual', change it back to 'auto'. + */ + if (nm_setting_ip_config_get_num_addresses (NM_SETTING_IP_CONFIG (object))) { + if (g_strcmp0 (nm_setting_ip_config_get_method (NM_SETTING_IP_CONFIG (object)), NM_SETTING_IP4_CONFIG_METHOD_MANUAL)) { + if (!answered) { + answered = TRUE; + answer = get_answer ("ipv4.method", "manual"); + } + if (answer) + g_object_set (object, NM_SETTING_IP_CONFIG_METHOD, NM_SETTING_IP4_CONFIG_METHOD_MANUAL, NULL); + } + } else { + answered = FALSE; + if (!g_strcmp0 (nm_setting_ip_config_get_method (NM_SETTING_IP_CONFIG (object)), NM_SETTING_IP4_CONFIG_METHOD_MANUAL)) + g_object_set (object, NM_SETTING_IP_CONFIG_METHOD, NM_SETTING_IP4_CONFIG_METHOD_AUTO, NULL); + } + + g_signal_handlers_unblock_by_func (object, G_CALLBACK (ipv4_method_changed_cb), NULL); +} + +static void +ipv4_method_changed_cb (GObject *object, GParamSpec *pspec, gpointer user_data) +{ + static GValue value = G_VALUE_INIT; + static gboolean answered = FALSE; + static gboolean answer = FALSE; + + g_signal_handlers_block_by_func (object, G_CALLBACK (ipv4_addresses_changed_cb), NULL); + + /* If method != manual, remove addresses (save them for restoring them later when method becomes 'manual' */ + if (g_strcmp0 (nm_setting_ip_config_get_method (NM_SETTING_IP_CONFIG (object)), NM_SETTING_IP4_CONFIG_METHOD_MANUAL)) { + if (nm_setting_ip_config_get_num_addresses (NM_SETTING_IP_CONFIG (object))) { + if (!answered) { + answered = TRUE; + answer = get_answer ("ipv4.addresses", NULL); + } + if (answer) { + if (G_IS_VALUE (&value)) + g_value_unset (&value); + nmc_property_get_gvalue (NM_SETTING (object), NM_SETTING_IP_CONFIG_ADDRESSES, &value); + g_object_set (object, NM_SETTING_IP_CONFIG_ADDRESSES, NULL, NULL); + } + } + } else { + answered = FALSE; + if (G_IS_VALUE (&value)) { + nmc_property_set_gvalue (NM_SETTING (object), NM_SETTING_IP_CONFIG_ADDRESSES, &value); + g_value_unset (&value); + } + } + + g_signal_handlers_unblock_by_func (object, G_CALLBACK (ipv4_addresses_changed_cb), NULL); +} + +static void +ipv6_addresses_changed_cb (GObject *object, GParamSpec *pspec, gpointer user_data) +{ + static gboolean answered = FALSE; + static gboolean answer = FALSE; + + g_signal_handlers_block_by_func (object, G_CALLBACK (ipv6_method_changed_cb), NULL); + + /* If we have some IP addresses set method to 'manual'. + * Else if the method was 'manual', change it back to 'auto'. + */ + if (nm_setting_ip_config_get_num_addresses (NM_SETTING_IP_CONFIG (object))) { + if (g_strcmp0 (nm_setting_ip_config_get_method (NM_SETTING_IP_CONFIG (object)), NM_SETTING_IP6_CONFIG_METHOD_MANUAL)) { + if (!answered) { + answered = TRUE; + answer = get_answer ("ipv6.method", "manual"); + } + if (answer) + g_object_set (object, NM_SETTING_IP_CONFIG_METHOD, NM_SETTING_IP6_CONFIG_METHOD_MANUAL, NULL); + } + } else { + answered = FALSE; + if (!g_strcmp0 (nm_setting_ip_config_get_method (NM_SETTING_IP_CONFIG (object)), NM_SETTING_IP6_CONFIG_METHOD_MANUAL)) + g_object_set (object, NM_SETTING_IP_CONFIG_METHOD, NM_SETTING_IP6_CONFIG_METHOD_AUTO, NULL); + } + + g_signal_handlers_unblock_by_func (object, G_CALLBACK (ipv6_method_changed_cb), NULL); +} + +static void +ipv6_method_changed_cb (GObject *object, GParamSpec *pspec, gpointer user_data) +{ + static GValue value = G_VALUE_INIT; + static gboolean answered = FALSE; + static gboolean answer = FALSE; + + g_signal_handlers_block_by_func (object, G_CALLBACK (ipv6_addresses_changed_cb), NULL); + + /* If method != manual, remove addresses (save them for restoring them later when method becomes 'manual' */ + if (g_strcmp0 (nm_setting_ip_config_get_method (NM_SETTING_IP_CONFIG (object)), NM_SETTING_IP6_CONFIG_METHOD_MANUAL)) { + if (nm_setting_ip_config_get_num_addresses (NM_SETTING_IP_CONFIG (object))) { + if (!answered) { + answered = TRUE; + answer = get_answer ("ipv6.addresses", NULL); + } + if (answer) { + if (G_IS_VALUE (&value)) + g_value_unset (&value); + nmc_property_get_gvalue (NM_SETTING (object), NM_SETTING_IP_CONFIG_ADDRESSES, &value); + g_object_set (object, NM_SETTING_IP_CONFIG_ADDRESSES, NULL, NULL); + } + } + } else { + answered = FALSE; + if (G_IS_VALUE (&value)) { + nmc_property_set_gvalue (NM_SETTING (object), NM_SETTING_IP_CONFIG_ADDRESSES, &value); + g_value_unset (&value); + } + } + + g_signal_handlers_unblock_by_func (object, G_CALLBACK (ipv6_addresses_changed_cb), NULL); +} + +static void +wireless_band_channel_changed_cb (GObject *object, GParamSpec *pspec, gpointer user_data) +{ + const char *value = NULL, *mode; + char str[16]; + NMSettingWireless *s_wireless = NM_SETTING_WIRELESS (object); + + if (strcmp (g_param_spec_get_name (pspec), NM_SETTING_WIRELESS_BAND) == 0) { + value = nm_setting_wireless_get_band (s_wireless); + if (!value) + return; + } else { + guint32 channel = nm_setting_wireless_get_channel (s_wireless); + + if (channel == 0) + return; + + g_snprintf (str, sizeof (str), "%d", nm_setting_wireless_get_channel (s_wireless)); + value = str; + } + + mode = nm_setting_wireless_get_mode (NM_SETTING_WIRELESS (object)); + if (!mode || !*mode || strcmp (mode, NM_SETTING_WIRELESS_MODE_INFRA) == 0) { + g_print (_("Warning: %s.%s set to '%s', but it might be ignored in infrastructure mode\n"), + nm_setting_get_name (NM_SETTING (s_wireless)), g_param_spec_get_name (pspec), + value); + } +} + +static void +connection_master_changed_cb (GObject *object, GParamSpec *pspec, gpointer user_data) +{ + NMSettingConnection *s_con = NM_SETTING_CONNECTION (object); + NMConnection *connection = NM_CONNECTION (user_data); + NMSetting *s_ipv4, *s_ipv6; + const char *value, *tmp_str; + + value = nm_setting_connection_get_master (s_con); + if (value) { + s_ipv4 = nm_connection_get_setting_by_name (connection, NM_SETTING_IP4_CONFIG_SETTING_NAME); + s_ipv6 = nm_connection_get_setting_by_name (connection, NM_SETTING_IP6_CONFIG_SETTING_NAME); + if (s_ipv4 || s_ipv6) { + g_print (_("Warning: setting %s.%s requires removing ipv4 and ipv6 settings\n"), + nm_setting_get_name (NM_SETTING (s_con)), g_param_spec_get_name (pspec)); + tmp_str = nmc_get_user_input (_("Do you want to remove them? [yes] ")); + if (!tmp_str || matches (tmp_str, "yes") == 0) { + if (s_ipv4) + nm_connection_remove_setting (connection, G_OBJECT_TYPE (s_ipv4)); + if (s_ipv6) + nm_connection_remove_setting (connection, G_OBJECT_TYPE (s_ipv6)); + } + } + } +} + +void +nmc_setting_ip4_connect_handlers (NMSettingIPConfig *setting) +{ + g_return_if_fail (NM_IS_SETTING_IP4_CONFIG (setting)); + + g_signal_connect (setting, "notify::" NM_SETTING_IP_CONFIG_ADDRESSES, + G_CALLBACK (ipv4_addresses_changed_cb), NULL); + g_signal_connect (setting, "notify::" NM_SETTING_IP_CONFIG_METHOD, + G_CALLBACK (ipv4_method_changed_cb), NULL); +} + +void +nmc_setting_ip6_connect_handlers (NMSettingIPConfig *setting) +{ + g_return_if_fail (NM_IS_SETTING_IP6_CONFIG (setting)); + + g_signal_connect (setting, "notify::" NM_SETTING_IP_CONFIG_ADDRESSES, + G_CALLBACK (ipv6_addresses_changed_cb), NULL); + g_signal_connect (setting, "notify::" NM_SETTING_IP_CONFIG_METHOD, + G_CALLBACK (ipv6_method_changed_cb), NULL); +} + +void +nmc_setting_wireless_connect_handlers (NMSettingWireless *setting) +{ + g_return_if_fail (NM_IS_SETTING_WIRELESS (setting)); + + g_signal_connect (setting, "notify::" NM_SETTING_WIRELESS_BAND, + G_CALLBACK (wireless_band_channel_changed_cb), NULL); + g_signal_connect (setting, "notify::" NM_SETTING_WIRELESS_CHANNEL, + G_CALLBACK (wireless_band_channel_changed_cb), NULL); +} + +void +nmc_setting_connection_connect_handlers (NMSettingConnection *setting, NMConnection *connection) +{ + g_return_if_fail (NM_IS_SETTING_CONNECTION (setting)); + + g_signal_connect (setting, "notify::" NM_SETTING_CONNECTION_MASTER, + G_CALLBACK (connection_master_changed_cb), connection); +} + +/* + * Customize some properties of the setting so that the setting has sensible + * values. + */ +void +nmc_setting_custom_init (NMSetting *setting) +{ + g_return_if_fail (NM_IS_SETTING (setting)); + + if (NM_IS_SETTING_IP4_CONFIG (setting)) { + g_object_set (NM_SETTING_IP_CONFIG (setting), + NM_SETTING_IP_CONFIG_METHOD, NM_SETTING_IP4_CONFIG_METHOD_AUTO, + NULL); + nmc_setting_ip4_connect_handlers (NM_SETTING_IP_CONFIG (setting)); + } else if (NM_IS_SETTING_IP6_CONFIG (setting)) { + g_object_set (NM_SETTING_IP_CONFIG (setting), + NM_SETTING_IP_CONFIG_METHOD, NM_SETTING_IP6_CONFIG_METHOD_AUTO, + NULL); + nmc_setting_ip6_connect_handlers (NM_SETTING_IP_CONFIG (setting)); + } else if (NM_IS_SETTING_WIRELESS (setting)) { + g_object_set (NM_SETTING_WIRELESS (setting), + NM_SETTING_WIRELESS_MODE, NM_SETTING_WIRELESS_MODE_INFRA, + NULL); + nmc_setting_wireless_connect_handlers (NM_SETTING_WIRELESS (setting)); + } +} + +/* === SetFunc, RemoveFunc, DescribeFunc, ValuesFunc functions === */ +static gboolean +verify_string_list (char **strv, + const char *prop, + gboolean (*validate_func) (const char *), + GError **error) +{ + char **iter; + + g_return_val_if_fail (error == NULL || *error == NULL, FALSE); + + for (iter = strv; iter && *iter; iter++) { + if (**iter == '\0') + continue; + if (validate_func) { + if (!validate_func (*iter)) { + g_set_error (error, 1, 0, _("'%s' is not valid"), + *iter); + return FALSE; + } + } + } + return TRUE; +} + +/* Validate 'val' number against to int property spec */ +static gboolean +validate_int (NMSetting *setting, const char* prop, gint val, GError **error) +{ + GParamSpec *pspec; + GValue value = G_VALUE_INIT; + gboolean success = TRUE; + + g_value_init (&value, G_TYPE_INT); + g_value_set_int (&value, val); + pspec = g_object_class_find_property (G_OBJECT_GET_CLASS (G_OBJECT (setting)), prop); + g_assert (G_IS_PARAM_SPEC (pspec)); + if (g_param_value_validate (pspec, &value)) { + GParamSpecInt *pspec_int = (GParamSpecInt *) pspec; + g_set_error (error, 1, 0, _("'%d' is not valid; use <%d-%d>"), + val, pspec_int->minimum, pspec_int->maximum); + success = FALSE; + } + g_value_unset (&value); + return success; +} + +static gboolean +validate_int64 (NMSetting *setting, const char* prop, gint64 val, GError **error) +{ + GParamSpec *pspec; + GValue value = G_VALUE_INIT; + gboolean success = TRUE; + + g_value_init (&value, G_TYPE_INT64); + g_value_set_int64 (&value, val); + pspec = g_object_class_find_property (G_OBJECT_GET_CLASS (G_OBJECT (setting)), prop); + g_assert (G_IS_PARAM_SPEC (pspec)); + if (g_param_value_validate (pspec, &value)) { + GParamSpecInt64 *pspec_int = (GParamSpecInt64 *) pspec; + G_STATIC_ASSERT (sizeof (long long) >= sizeof (gint64)); + g_set_error (error, 1, 0, _("'%lld' is not valid; use <%lld-%lld>"), + (long long) val, (long long) pspec_int->minimum, (long long) pspec_int->maximum); + success = FALSE; + } + g_value_unset (&value); + return success; +} + +/* Validate 'val' number against to uint property spec */ +static gboolean +validate_uint (NMSetting *setting, const char* prop, guint val, GError **error) +{ + GParamSpec *pspec; + GValue value = G_VALUE_INIT; + gboolean success = TRUE; + + g_value_init (&value, G_TYPE_UINT); + g_value_set_uint (&value, val); + pspec = g_object_class_find_property (G_OBJECT_GET_CLASS (G_OBJECT (setting)), prop); + g_assert (G_IS_PARAM_SPEC (pspec)); + if (g_param_value_validate (pspec, &value)) { + GParamSpecUInt *pspec_uint = (GParamSpecUInt *) pspec; + g_set_error (error, 1, 0, _("'%u' is not valid; use <%u-%u>"), + val, pspec_uint->minimum, pspec_uint->maximum); + success = FALSE; + } + g_value_unset (&value); + return success; +} + +static gboolean +check_and_set_string (NMSetting *setting, + const char *prop, + const char *val, + const char **valid_strv, + GError **error) +{ + const char *checked_val; + + g_return_val_if_fail (error == NULL || *error == NULL, FALSE); + + checked_val = nmc_string_is_valid (val, valid_strv, error); + if (!checked_val) + return FALSE; + + g_object_set (setting, prop, checked_val, NULL); + return TRUE; +} + +#define DEFINE_SETTER_STR_LIST_MULTI(def_func, s_macro, set_func) \ + static gboolean \ + def_func (NMSetting *setting, \ + const char *prop, \ + const char *val, \ + const char **valid_strv, \ + GError **error) \ + { \ + char **strv = NULL, **iter; \ + const char *item; \ + g_return_val_if_fail (error == NULL || *error == NULL, FALSE); \ + strv = nmc_strsplit_set (val, " \t,", 0); \ + for (iter = strv; iter && *iter; iter++) { \ + if (!(item = nmc_string_is_valid (g_strstrip (*iter), valid_strv, error))) { \ + g_strfreev (strv); \ + return FALSE; \ + } \ + set_func (s_macro (setting), item); \ + } \ + g_strfreev (strv); \ + return TRUE; \ + } + +#define DEFINE_SETTER_OPTIONS(def_func, s_macro, s_type, add_func, valid_func1, valid_func2) \ + static gboolean \ + def_func (NMSetting *setting, const char *prop, const char *val, GError **error) \ + { \ + char **strv = NULL, **iter; \ + const char **(*valid_func1_p) (s_type *) = valid_func1; \ + const char * (*valid_func2_p) (const char *, const char *, GError **) = valid_func2; \ + const char *opt_name, *opt_val; \ + \ + g_return_val_if_fail (error == NULL || *error == NULL, FALSE); \ + \ + strv = nmc_strsplit_set (val, ",", 0); \ + for (iter = strv; iter && *iter; iter++) { \ + char *left = g_strstrip (*iter); \ + char *right = strchr (left, '='); \ + if (!right) { \ + g_set_error (error, 1, 0, _("'%s' is not valid; use <option>=<value>"), *iter); \ + g_strfreev (strv); \ + return FALSE; \ + } \ + *right++ = '\0'; \ + \ + if (valid_func1_p) { \ + const char **valid_options = valid_func1_p (s_macro (setting)); \ + if (!(opt_name = nmc_string_is_valid (g_strstrip (left), valid_options, error))) { \ + g_strfreev (strv); \ + return FALSE; \ + } \ + } else \ + opt_name = g_strstrip (left);\ + \ + opt_val = g_strstrip (right); \ + if (valid_func2_p) { \ + if (!(opt_val = valid_func2_p ((const char *) left, (const char *) opt_val, error))) { \ + g_strfreev (strv); \ + return FALSE; \ + }\ + }\ + add_func (s_macro (setting), opt_name, opt_val); \ + } \ + g_strfreev (strv); \ + return TRUE; \ + } + +#define DEFINE_REMOVER_INDEX(def_func, s_macro, num_func, rem_func) \ + static gboolean \ + def_func (NMSetting *setting, const char *prop, const char *option, guint32 idx, GError **error) \ + { \ + guint32 num; \ + if (option) { \ + g_set_error (error, 1, 0, _("index '%s' is not valid"), option); \ + return FALSE; \ + } \ + num = num_func (s_macro (setting)); \ + if (num == 0) { \ + g_set_error_literal (error, 1, 0, _("no item to remove")); \ + return FALSE; \ + } \ + if (idx >= num) { \ + g_set_error (error, 1, 0, _("index '%d' is not in range <0-%d>"), idx, num - 1); \ + return FALSE; \ + } \ + rem_func (s_macro (setting), idx); \ + return TRUE; \ + } + +#define DEFINE_REMOVER_INDEX_OR_VALUE(def_func, s_macro, num_func, rem_func_idx, rem_func_val) \ + static gboolean \ + def_func (NMSetting *setting, const char *prop, const char *value, guint32 idx, GError **error) \ + { \ + guint32 num; \ + if (value) { \ + gboolean ret; \ + char *value_stripped = g_strstrip (g_strdup (value)); \ + ret = rem_func_val (s_macro (setting), value_stripped, error); \ + g_free (value_stripped); \ + return ret; \ + } \ + num = num_func (s_macro (setting)); \ + if (num == 0) { \ + g_set_error_literal (error, 1, 0, _("no item to remove")); \ + return FALSE; \ + } \ + if (idx >= num) { \ + g_set_error (error, 1, 0, _("index '%d' is not in range <0-%d>"), idx, num - 1); \ + return FALSE; \ + } \ + rem_func_idx (s_macro (setting), idx); \ + return TRUE; \ + } + +#define DEFINE_REMOVER_OPTION(def_func, s_macro, rem_func) \ + static gboolean \ + def_func (NMSetting *setting, const char *prop, const char *option, guint32 idx, GError **error) \ + { \ + gboolean success = FALSE; \ + if (option && *option) { \ + success = rem_func (s_macro (setting), option); \ + if (!success) \ + g_set_error (error, 1, 0, _("invalid option '%s'"), option); \ + } else \ + g_set_error_literal (error, 1, 0, _("missing option")); \ + return success; \ + } + +#define DEFINE_ALLOWED_VAL_FUNC(def_func, valid_values) \ + static const char * \ + def_func (NMSetting *setting, const char *prop) \ + { \ + static char *values = NULL; \ + if (G_UNLIKELY (values == NULL)) \ + values = g_strjoinv (", ", (char **) valid_values); \ + return values; \ + } + +/* --- generic property setter functions --- */ +static gboolean +nmc_property_set_string (NMSetting *setting, const char *prop, const char *val, GError **error) +{ + g_object_set (setting, prop, val, NULL); + return TRUE; +} + +static gboolean +nmc_property_set_uint (NMSetting *setting, const char *prop, const char *val, GError **error) +{ + unsigned long val_int; + + g_return_val_if_fail (error == NULL || *error == NULL, FALSE); + + if (!nmc_string_to_uint (val, TRUE, 0, G_MAXUINT, &val_int)) { + g_set_error (error, 1, 0, _("'%s' is not a valid number (or out of range)"), val); + return FALSE; + } + + /* Validate the number according to the property spec */ + if (!validate_uint (setting, prop, (guint) val_int, error)) + return FALSE; + + g_object_set (setting, prop, (guint) val_int, NULL); + return TRUE; +} + +static gboolean +nmc_property_set_int (NMSetting *setting, const char *prop, const char *val, GError **error) +{ + long int val_int; + + g_return_val_if_fail (error == NULL || *error == NULL, FALSE); + + if (!nmc_string_to_int (val, TRUE, G_MININT, G_MAXINT, &val_int)) { + g_set_error (error, 1, 0, _("'%s' is not a valid number (or out of range)"), val); + return FALSE; + } + + /* Validate the number according to the property spec */ + if (!validate_int (setting, prop, (gint) val_int, error)) + return FALSE; + + g_object_set (setting, prop, (gint) val_int, NULL); + return TRUE; +} + +static gboolean +nmc_property_set_int64 (NMSetting *setting, const char *prop, const char *val, GError **error) +{ + long val_int; + + g_return_val_if_fail (error == NULL || *error == NULL, FALSE); + + if (!nmc_string_to_int (val, FALSE, 0, 0, &val_int)) { + g_set_error (error, 1, 0, _("'%s' is not a valid number (or out of range)"), val); + return FALSE; + } + + /* Validate the number according to the property spec */ + if (!validate_int64 (setting, prop, (gint64) val_int, error)) + return FALSE; + + g_object_set (setting, prop, (gint64) val_int, NULL); + return TRUE; +} + +static gboolean +nmc_property_set_bool (NMSetting *setting, const char *prop, const char *val, GError **error) +{ + gboolean val_bool; + + g_return_val_if_fail (error == NULL || *error == NULL, FALSE); + + if (!nmc_string_to_bool (val, &val_bool, error)) + return FALSE; + + g_object_set (setting, prop, val_bool, NULL); + return TRUE; +} + +static gboolean +nmc_property_set_ssid (NMSetting *setting, const char *prop, const char *val, GError **error) +{ + GBytes *ssid; + + g_return_val_if_fail (error == NULL || *error == NULL, FALSE); + + if (strlen (val) > 32) { + g_set_error (error, 1, 0, _("'%s' is not valid"), val); + return FALSE; + } + + ssid = g_bytes_new (val, strlen (val)); + g_object_set (setting, prop, ssid, NULL); + g_bytes_unref (ssid); + return TRUE; +} + +static gboolean +nmc_property_set_mac (NMSetting *setting, const char *prop, const char *val, GError **error) +{ + g_return_val_if_fail (error == NULL || *error == NULL, FALSE); + + if (!nm_utils_hwaddr_valid (val, ETH_ALEN)) { + g_set_error (error, 1, 0, _("'%s' is not a valid Ethernet MAC"), val); + return FALSE; + } + + g_object_set (setting, prop, val, NULL); + return TRUE; +} + +static gboolean +nmc_property_set_mtu (NMSetting *setting, const char *prop, const char *val, GError **error) +{ + const char *mtu = val; + + if (strcmp (mtu, "auto") == 0) + mtu = "0"; + + return nmc_property_set_uint (setting, prop, mtu, error); +} + +static gboolean +nmc_property_set_ifname (NMSetting *setting, const char *prop, const char *val, GError **error) +{ + g_return_val_if_fail (error == NULL || *error == NULL, FALSE); + + if (!nm_utils_iface_valid_name (val)) { + g_set_error (error, 1, 0, _("'%s' is not a valid interface name"), val); + return FALSE; + } + g_object_set (setting, prop, val, NULL); + return TRUE; +} + +#define ALL_SECRET_FLAGS \ + (NM_SETTING_SECRET_FLAG_NONE | \ + NM_SETTING_SECRET_FLAG_AGENT_OWNED | \ + NM_SETTING_SECRET_FLAG_NOT_SAVED | \ + NM_SETTING_SECRET_FLAG_NOT_REQUIRED) + +static gboolean +nmc_property_set_secret_flags (NMSetting *setting, const char *prop, const char *val, GError **error) +{ + char **strv = NULL, **iter; + unsigned long flags = 0, val_int; + + g_return_val_if_fail (error == NULL || *error == NULL, FALSE); + + strv = nmc_strsplit_set (val, " \t,", 0); + for (iter = strv; iter && *iter; iter++) { + if (!nmc_string_to_uint (*iter, TRUE, 0, ALL_SECRET_FLAGS, &val_int)) { + g_set_error (error, 1, 0, _("'%s' is not a valid flag number; use <0-%d>"), + *iter, ALL_SECRET_FLAGS); + g_strfreev (strv); + return FALSE; + } + flags += val_int; + } + g_strfreev (strv); + + /* Validate the flags number */ + if (flags > ALL_SECRET_FLAGS) { + flags = ALL_SECRET_FLAGS; + g_print (_("Warning: '%s' sum is higher than all flags => all flags set\n"), val); + } + + g_object_set (setting, prop, (guint) flags, NULL); + return TRUE; +} + +static gboolean +nmc_util_is_domain (const char *domain) +{ + //FIXME: implement + return TRUE; +} + +static gboolean +nmc_property_set_byte_array (NMSetting *setting, const char *prop, const char *val, GError **error) +{ + char **strv = NULL, **iter; + char *val_strip; + const char *delimiters = " \t,"; + long int val_int; + GBytes *bytes; + GByteArray *array = NULL; + gboolean success = TRUE; + + g_return_val_if_fail (error == NULL || *error == NULL, FALSE); + + val_strip = g_strstrip (g_strdup (val)); + + /* First try hex string in the format of AAbbCCDd */ + bytes = nm_utils_hexstr2bin (val_strip); + if (bytes) { + array = g_bytes_unref_to_array (bytes); + goto done; + } + + /* Otherwise, consider the following format: AA b 0xCc D */ + strv = nmc_strsplit_set (val_strip, delimiters, 0); + array = g_byte_array_sized_new (g_strv_length (strv)); + for (iter = strv; iter && *iter; iter++) { + if (!nmc_string_to_int_base (g_strstrip (*iter), 16, TRUE, 0, 255, &val_int)) { + g_set_error (error, 1, 0, _("'%s' is not a valid hex character"), *iter); + success = FALSE; + goto done; + } + g_byte_array_append (array, (const guint8 *) &val_int, 1); + } + +done: + if (success) + g_object_set (setting, prop, array, NULL); + + g_strfreev (strv); + if (array) + g_byte_array_free (array, TRUE); + return success; +} + +#define DEFINE_SETTER_MAC_BLACKLIST(def_func, s_macro, add_func) \ + static gboolean \ + def_func (NMSetting *setting, const char *prop, const char *val, GError **error) \ + { \ + guint8 buf[32]; \ + char **list = NULL, **iter; \ + GSList *macaddr_blacklist = NULL; \ + \ + g_return_val_if_fail (error == NULL || *error == NULL, FALSE); \ + \ + list = nmc_strsplit_set (val, " \t,", 0); \ + for (iter = list; iter && *iter; iter++) { \ + if (!nm_utils_hwaddr_aton (*iter, buf, ETH_ALEN)) { \ + g_set_error (error, 1, 0, _("'%s' is not a valid MAC"), *iter); \ + g_strfreev (list); \ + g_slist_free (macaddr_blacklist); \ + return FALSE; \ + } \ + } \ + \ + for (iter = list; iter && *iter; iter++) \ + add_func (s_macro (setting), *iter); \ + \ + g_strfreev (list); \ + return TRUE; \ + } + +static char * +nmc_property_out2in_cut_paren (const char *out_format) +{ + const char *p; + size_t n; + + p = strstr (out_format, " ("); + n = p ? p - out_format : strlen (out_format); + return g_strndup (out_format, n); +} + +/* --- NM_SETTING_CONNECTION_SETTING_NAME property setter functions --- */ +#if 0 +/* + * Setting/removing UUID has been forbidden. + * Should it be enabled later, this function can be used. + */ +static gboolean +nmc_property_con_set_uuid (NMSetting *setting, const char *prop, const char *val, GError **error) +{ + g_return_val_if_fail (error == NULL || *error == NULL, FALSE); + + if (!nm_utils_is_uuid (val)) { + g_set_error (error, 1, 0, _("'%s' is not a valid UUID"), val); + return FALSE; + } + g_object_set (setting, prop, val, NULL); + return TRUE; +} +#endif + +/* 'permissions' */ +/* define from libnm-core/nm-setting-connection.c */ +#define PERM_USER_PREFIX "user:" + +static gboolean +permissions_valid (const char *perm) +{ + if (!perm || perm[0] == '\0') + return FALSE; + + if (strncmp (perm, PERM_USER_PREFIX, strlen (PERM_USER_PREFIX)) == 0) { + if ( strlen (perm) <= strlen (PERM_USER_PREFIX) + || strchr (perm + strlen (PERM_USER_PREFIX), ':')) + return FALSE; + } else { + if (strchr (perm, ':')) + return FALSE; + } + + return TRUE; +} + +static gboolean +nmc_property_connection_set_permissions (NMSetting *setting, const char *prop, const char *val, GError **error) +{ + char **strv = NULL; + guint i = 0; + + g_return_val_if_fail (error == NULL || *error == NULL, FALSE); + + strv = nmc_strsplit_set (val, " \t,", 0); + if (!verify_string_list (strv, prop, permissions_valid, error)) { + g_strfreev (strv); + return FALSE; + } + + for (i = 0; strv && strv[i]; i++) { + const char *user; + + if (strncmp (strv[i], PERM_USER_PREFIX, strlen (PERM_USER_PREFIX)) == 0) + user = strv[i]+strlen (PERM_USER_PREFIX); + else + user = strv[i]; + + nm_setting_connection_add_permission (NM_SETTING_CONNECTION (setting), "user", user, NULL); + } + + return TRUE; +} + +static gboolean +_validate_and_remove_connection_permission (NMSettingConnection *setting, + const char *perm, + GError **error) +{ + gboolean ret; + + ret = nm_setting_connection_remove_permission_by_value (setting, "user", perm, NULL); + if (!ret) + g_set_error (error, 1, 0, _("the property doesn't contain permission '%s'"), perm); + return ret; +} +DEFINE_REMOVER_INDEX_OR_VALUE (nmc_property_connection_remove_permissions, + NM_SETTING_CONNECTION, + nm_setting_connection_get_num_permissions, + nm_setting_connection_remove_permission, + _validate_and_remove_connection_permission) + +static const char * +nmc_property_connection_describe_permissions (NMSetting *setting, const char *prop) +{ + return _("Enter a list of user permissions. This is a list of user names formatted as:\n" + " [user:]<user name 1>, [user:]<user name 2>,...\n" + "The items can be separated by commas or spaces.\n\n" + "Example: alice bob charlie\n"); +} + +/* 'master' */ +static gboolean +nmc_property_con_set_master (NMSetting *setting, const char *prop, const char *val, GError **error) +{ + g_return_val_if_fail (error == NULL || *error == NULL, FALSE); + + if (!val) + ; + else if (!*val) + val = NULL; + else if ( !nm_utils_iface_valid_name (val) + && !nm_utils_is_uuid (val)) { + g_set_error (error, 1, 0, + _("'%s' is not valid master; use ifname or connection UUID"), + val); + return FALSE; + } + g_object_set (setting, prop, val, NULL); + return TRUE; +} + +/* 'slave-type' */ +static const char *con_valid_slave_types[] = { + NM_SETTING_BOND_SETTING_NAME, + NM_SETTING_BRIDGE_SETTING_NAME, + NM_SETTING_VLAN_SETTING_NAME, + NULL +}; + +static gboolean +nmc_property_con_set_slave_type (NMSetting *setting, const char *prop, const char *val, GError **error) +{ + return check_and_set_string (setting, prop, val, con_valid_slave_types, error); +} + + +DEFINE_ALLOWED_VAL_FUNC (nmc_property_con_allowed_slave_type, con_valid_slave_types) + +/* 'secondaries' */ +static gboolean +nmc_property_connection_set_secondaries (NMSetting *setting, const char *prop, const char *val, GError **error) +{ + NMConnection *con; + char **strv = NULL, **iter; + guint i = 0; + + g_return_val_if_fail (error == NULL || *error == NULL, FALSE); + + strv = nmc_strsplit_set (val, " \t,", 0); + for (iter = strv; iter && *iter; iter++) { + if (**iter == '\0') + continue; + + if (nm_utils_is_uuid (*iter)) { + con = nmc_find_connection (nm_cli.connections, + "uuid", *iter, NULL); + if (!con) + g_print (_("Warning: %s is not an UUID of any existing connection profile\n"), *iter); + else { + /* Currenly NM only supports VPN connections as secondaries */ + if (!nm_connection_is_type (con, NM_SETTING_VPN_SETTING_NAME)) { + g_set_error (error, 1, 0, _("'%s' is not a VPN connection profile"), *iter); + g_strfreev (strv); + return FALSE; + } + } + } else { + con = nmc_find_connection (nm_cli.connections, + "id", *iter, NULL); + if (!con) { + g_set_error (error, 1, 0, _("'%s' is not a name of any exiting profile"), *iter); + g_strfreev (strv); + return FALSE; + } + + /* Currenly NM only supports VPN connections as secondaries */ + if (!nm_connection_is_type (con, NM_SETTING_VPN_SETTING_NAME)) { + g_set_error (error, 1, 0, _("'%s' is not a VPN connection profile"), *iter); + g_strfreev (strv); + return FALSE; + } + + /* translate id to uuid */ + g_free (*iter); + *iter = g_strdup (nm_connection_get_uuid (con)); + } + } + + while (strv && strv[i]) + nm_setting_connection_add_secondary (NM_SETTING_CONNECTION (setting), strv[i++]); + g_strfreev (strv); + + return TRUE; +} + +static gboolean +_validate_and_remove_connection_secondary (NMSettingConnection *setting, + const char *secondary_uuid, + GError **error) +{ + gboolean ret; + + if (!nm_utils_is_uuid (secondary_uuid)) { + g_set_error (error, 1, 0, + _("the value '%s' is not a valid UUID"), secondary_uuid); + return FALSE; + } + + ret = nm_setting_connection_remove_secondary_by_value (setting, secondary_uuid); + if (!ret) + g_set_error (error, 1, 0, + _("the property doesn't contain UUID '%s'"), secondary_uuid); + return ret; +} +DEFINE_REMOVER_INDEX_OR_VALUE (nmc_property_connection_remove_secondaries, + NM_SETTING_CONNECTION, + nm_setting_connection_get_num_secondaries, + nm_setting_connection_remove_secondary, + _validate_and_remove_connection_secondary) + +static const char * +nmc_property_connection_describe_secondaries (NMSetting *setting, const char *prop) +{ + return _("Enter secondary connections that should be activated when this connection is\n" + "activated. Connections can be specified either by UUID or ID (name). nmcli\n" + "transparently translates names to UUIDs. Note that NetworkManager only supports\n" + "VPNs as secondary connections at the moment.\n" + "The items can be separated by commas or spaces.\n\n" + "Example: private-openvpn, fe6ba5d8-c2fc-4aae-b2e3-97efddd8d9a7\n"); +} + +/* --- NM_SETTING_802_1X_SETTING_NAME property setter functions --- */ +#define DEFINE_SETTER_STR_LIST(def_func, set_func) \ + static gboolean \ + def_func (NMSetting *setting, const char *prop, const char *val, GError **error) \ + { \ + char **strv = NULL; \ + guint i = 0; \ + \ + g_return_val_if_fail (error == NULL || *error == NULL, FALSE); \ + \ + strv = nmc_strsplit_set (val, " \t,", 0); \ + while (strv && strv[i]) \ + set_func (NM_SETTING_802_1X (setting), strv[i++]); \ + g_strfreev (strv); \ + return TRUE; \ + } + +#define DEFINE_SETTER_CERT(def_func, set_func) \ + static gboolean \ + def_func (NMSetting *setting, const char *prop, const char *val, GError **error) \ + { \ + const char *SCHEME_PATH = "file://"; \ + char *val_strip = g_strstrip (g_strdup (val)); \ + char *p = val_strip; \ + gboolean success; \ + \ + if (strncmp (val_strip, SCHEME_PATH, strlen (SCHEME_PATH)) == 0) \ + p += strlen (SCHEME_PATH); \ + \ + success = set_func (NM_SETTING_802_1X (setting), \ + p, \ + NM_SETTING_802_1X_CK_SCHEME_PATH, \ + NULL, \ + error); \ + g_free (val_strip); \ + return success; \ + } + +#define DEFINE_SETTER_PRIV_KEY(def_func, pwd_func, set_func) \ + static gboolean \ + def_func (NMSetting *setting, const char *prop, const char *val, GError **error) \ + { \ + char **strv = NULL; \ + const char *SCHEME_PATH = "file://"; \ + char *val_strip = g_strstrip (g_strdup (val)); \ + char *p = val_strip; \ + const char *path, *password; \ + gboolean success; \ + \ + if (strncmp (val_strip, SCHEME_PATH, strlen (SCHEME_PATH)) == 0) \ + p += strlen (SCHEME_PATH); \ + \ + strv = nmc_strsplit_set (p, " \t,", 2); \ + path = strv[0]; \ + if (g_strv_length (strv) == 2) \ + password = strv[1]; \ + else \ + password = pwd_func (NM_SETTING_802_1X (setting)); \ + if (password) { \ + char *tmp_pwd = g_strdup (password); \ + success = set_func (NM_SETTING_802_1X (setting), \ + path, \ + tmp_pwd, \ + NM_SETTING_802_1X_CK_SCHEME_PATH, \ + NULL, \ + error); \ + g_free (tmp_pwd); \ + } else { \ + success = FALSE; \ + g_set_error_literal (error, 1, 0, _("private key password not provided")); \ + } \ + g_free (val_strip); \ + g_strfreev (strv); \ + return success; \ + } + +/* 'eap' */ +DEFINE_SETTER_STR_LIST_MULTI (check_and_add_802_1X_eap, + NM_SETTING_802_1X, + nm_setting_802_1x_add_eap_method) +static gboolean +nmc_property_802_1X_set_eap (NMSetting *setting, const char *prop, const char *val, GError **error) +{ + const char *valid_eap[] = { "leap", "md5", "tls", "peap", "ttls", "sim", "fast", "pwd", NULL }; + return check_and_add_802_1X_eap (setting, prop, val, valid_eap, error); +} + +static gboolean +_validate_and_remove_eap_method (NMSetting8021x *setting, + const char *eap, + GError **error) +{ + gboolean ret; + + ret = nm_setting_802_1x_remove_eap_method_by_value(setting, eap); + if (!ret) + g_set_error (error, 1, 0, _("the property doesn't contain EAP method '%s'"), eap); + return ret; +} +DEFINE_REMOVER_INDEX_OR_VALUE (nmc_property_802_1X_remove_eap, + NM_SETTING_802_1X, + nm_setting_802_1x_get_num_eap_methods, + nm_setting_802_1x_remove_eap_method, + _validate_and_remove_eap_method) + +/* 'ca-cert' */ +DEFINE_SETTER_CERT (nmc_property_802_1X_set_ca_cert, nm_setting_802_1x_set_ca_cert) + +/* 'altsubject-matches' */ +DEFINE_SETTER_STR_LIST (nmc_property_802_1X_set_altsubject_matches, nm_setting_802_1x_add_altsubject_match) + +static gboolean +_validate_and_remove_altsubject_match (NMSetting8021x *setting, + const char *altsubject_match, + GError **error) +{ + gboolean ret; + + ret = nm_setting_802_1x_remove_altsubject_match_by_value (setting, altsubject_match); + if (!ret) + g_set_error (error, 1, 0, + _("the property doesn't contain alternative subject match '%s'"), + altsubject_match); + return ret; +} +DEFINE_REMOVER_INDEX_OR_VALUE (nmc_property_802_1X_remove_altsubject_matches, + NM_SETTING_802_1X, + nm_setting_802_1x_get_num_altsubject_matches, + nm_setting_802_1x_remove_altsubject_match, + _validate_and_remove_altsubject_match) + +/* 'client-cert' */ +DEFINE_SETTER_CERT (nmc_property_802_1X_set_client_cert, nm_setting_802_1x_set_client_cert) + +/* 'phase2-ca-cert' */ +DEFINE_SETTER_CERT (nmc_property_802_1X_set_phase2_ca_cert, nm_setting_802_1x_set_phase2_ca_cert) + +/* 'phase2-altsubject-matches' */ +DEFINE_SETTER_STR_LIST (nmc_property_802_1X_set_phase2_altsubject_matches, nm_setting_802_1x_add_phase2_altsubject_match) + +static gboolean +_validate_and_remove_phase2_altsubject_match (NMSetting8021x *setting, + const char *phase2_altsubject_match, + GError **error) +{ + gboolean ret; + + ret = nm_setting_802_1x_remove_phase2_altsubject_match_by_value (setting, phase2_altsubject_match); + if (!ret) + g_set_error (error, 1, 0, + _("the property doesn't contain \"phase2\" alternative subject match '%s'"), + phase2_altsubject_match); + return ret; +} +DEFINE_REMOVER_INDEX_OR_VALUE (nmc_property_802_1X_remove_phase2_altsubject_matches, + NM_SETTING_802_1X, + nm_setting_802_1x_get_num_phase2_altsubject_matches, + nm_setting_802_1x_remove_phase2_altsubject_match, + _validate_and_remove_phase2_altsubject_match) + +/* 'phase2-client-cert' */ +DEFINE_SETTER_CERT (nmc_property_802_1X_set_phase2_client_cert, nm_setting_802_1x_set_phase2_client_cert) + +/* 'private-key' */ +DEFINE_SETTER_PRIV_KEY (nmc_property_802_1X_set_private_key, + nm_setting_802_1x_get_private_key_password, + nm_setting_802_1x_set_private_key) + +/* 'phase2-private-key' */ +DEFINE_SETTER_PRIV_KEY (nmc_property_802_1X_set_phase2_private_key, + nm_setting_802_1x_get_phase2_private_key_password, + nm_setting_802_1x_set_phase2_private_key) + +static const char * +nmc_property_802_1X_describe_private_key (NMSetting *setting, const char *prop) +{ + return _("Enter path to a private key and the key password (if not set yet):\n" + " <file path> [<password>]\n" + "Example: /home/cimrman/jara-priv-key Dardanely\n"); +} + +/* 'phase1-peapver' */ +static const char *_802_1X_valid_phase1_peapvers[] = { "0", "1", NULL }; + +static gboolean +nmc_property_802_1X_set_phase1_peapver (NMSetting *setting, const char *prop, const char *val, GError **error) +{ + return check_and_set_string (setting, prop, val, _802_1X_valid_phase1_peapvers, error); +} + +DEFINE_ALLOWED_VAL_FUNC (nmc_property_802_1X_allowed_phase1_peapver, _802_1X_valid_phase1_peapvers) + +/* 'phase1-peaplabel' */ +static const char *_802_1X_valid_phase1_peaplabels[] = { "0", "1", NULL }; + +static gboolean +nmc_property_802_1X_set_phase1_peaplabel (NMSetting *setting, const char *prop, const char *val, GError **error) +{ + return check_and_set_string (setting, prop, val, _802_1X_valid_phase1_peaplabels, error); +} + +DEFINE_ALLOWED_VAL_FUNC (nmc_property_802_1X_allowed_phase1_peaplabel, _802_1X_valid_phase1_peaplabels) + +/* 'phase1-fast-provisioning' */ +static const char *_802_1X_valid_phase1_fast_provisionings[] = { "0", "1", "2", "3", NULL }; + +static gboolean +nmc_property_802_1X_set_phase1_fast_provisioning (NMSetting *setting, const char *prop, const char *val, GError **error) +{ + return check_and_set_string (setting, prop, val, _802_1X_valid_phase1_fast_provisionings, error); +} + +DEFINE_ALLOWED_VAL_FUNC (nmc_property_802_1X_allowed_phase1_fast_provisioning, _802_1X_valid_phase1_fast_provisionings) + +/* 'phase2-auth' */ +static const char *_802_1X_valid_phase2_auths[] = + { "pap", "chap", "mschap", "mschapv2", "gtc", "otp", "md5", "tls", NULL }; + +static gboolean +nmc_property_802_1X_set_phase2_auth (NMSetting *setting, const char *prop, const char *val, GError **error) +{ + return check_and_set_string (setting, prop, val, _802_1X_valid_phase2_auths, error); +} + +DEFINE_ALLOWED_VAL_FUNC (nmc_property_802_1X_allowed_phase2_auth, _802_1X_valid_phase2_auths) + +/* 'phase2-autheap' */ +static const char *_802_1X_valid_phase2_autheaps[] = { "md5", "mschapv2", "otp", "gtc", "tls", NULL }; +static gboolean +nmc_property_802_1X_set_phase2_autheap (NMSetting *setting, const char *prop, const char *val, GError **error) +{ + return check_and_set_string (setting, prop, val, _802_1X_valid_phase2_autheaps, error); +} + +DEFINE_ALLOWED_VAL_FUNC (nmc_property_802_1X_allowed_phase2_autheap, _802_1X_valid_phase2_autheaps) + +/* 'password-raw' */ +static gboolean +nmc_property_802_1X_set_password_raw (NMSetting *setting, const char *prop, const char *val, GError **error) +{ + return nmc_property_set_byte_array (setting, prop, val, error); +} + +static const char * +nmc_property_802_1X_describe_password_raw (NMSetting *setting, const char *prop) +{ + return _("Enter bytes as a list of hexadecimal values.\n" + "Two formats are accepted:\n" + "(a) a string of hexadecimal digits, where each two digits represent one byte\n" + "(b) space-separated list of bytes written as hexadecimal digits " + "(with optional 0x/0X prefix, and optional leading 0).\n\n" + "Examples: ab0455a6ea3a74C2\n" + " ab 4 55 0xa6 ea 3a 74 C2\n"); +} + +/* --- NM_SETTING_ADSL_SETTING_NAME property setter functions --- */ +/* 'protocol' */ +static const char *adsl_valid_protocols[] = { + NM_SETTING_ADSL_PROTOCOL_PPPOA, + NM_SETTING_ADSL_PROTOCOL_PPPOE, + NM_SETTING_ADSL_PROTOCOL_IPOATM, + NULL +}; + +static gboolean +nmc_property_adsl_set_protocol (NMSetting *setting, const char *prop, const char *val, GError **error) +{ + return check_and_set_string (setting, prop, val, adsl_valid_protocols, error); +} + +DEFINE_ALLOWED_VAL_FUNC (nmc_property_adsl_allowed_protocol, adsl_valid_protocols) + +/* 'encapsulation' */ +static const char *adsl_valid_encapsulations[] = { + NM_SETTING_ADSL_ENCAPSULATION_VCMUX, + NM_SETTING_ADSL_ENCAPSULATION_LLC, + NULL +}; + +static gboolean +nmc_property_adsl_set_encapsulation (NMSetting *setting, const char *prop, const char *val, GError **error) +{ + return check_and_set_string (setting, prop, val, adsl_valid_encapsulations, error); +} + +DEFINE_ALLOWED_VAL_FUNC (nmc_property_adsl_allowed_encapsulation, adsl_valid_encapsulations) + +/* --- NM_SETTING_BLUETOOTH_SETTING_NAME property setter functions --- */ +/* 'type' */ +static gboolean +nmc_property_bluetooth_set_type (NMSetting *setting, const char *prop, const char *val, GError **error) +{ + const char *types[] = { + NM_SETTING_BLUETOOTH_TYPE_DUN, + NM_SETTING_BLUETOOTH_TYPE_PANU, + NULL }; + + return check_and_set_string (setting, prop, val, types, error); +} + +/* --- NM_SETTING_BOND_SETTING_NAME property setter functions --- */ +/* 'options' */ +/* example: miimon=100,mode=balance-rr, updelay=5 */ +static gboolean +_validate_and_remove_bond_option (NMSettingBond *setting, const char *option) +{ + const char *opt; + const char **valid_options; + + valid_options = nm_setting_bond_get_valid_options (setting); + opt = nmc_string_is_valid (option, valid_options, NULL); + + if (opt) + return nm_setting_bond_remove_option (setting, opt); + else + return FALSE; +} + +/* Validate bonding 'options' values */ +static const char * +_validate_bond_option_value (const char *option, const char *value, GError **error) +{ + if (!g_strcmp0 (option, NM_SETTING_BOND_OPTION_MODE)) + return nmc_bond_validate_mode (value, error); + + return value; +} + +DEFINE_SETTER_OPTIONS (nmc_property_bond_set_options, + NM_SETTING_BOND, + NMSettingBond, + nm_setting_bond_add_option, + nm_setting_bond_get_valid_options, + _validate_bond_option_value) +DEFINE_REMOVER_OPTION (nmc_property_bond_remove_option_options, + NM_SETTING_BOND, + _validate_and_remove_bond_option) + +static const char * +nmc_property_bond_describe_options (NMSetting *setting, const char *prop) +{ + static char *desc = NULL; + const char **valid_options; + char *options_str; + + if (G_UNLIKELY (desc == NULL)) { + valid_options = nm_setting_bond_get_valid_options (NM_SETTING_BOND (setting)); + options_str = g_strjoinv (", ", (char **) valid_options); + + desc = g_strdup_printf (_("Enter a list of bonding options formatted as:\n" + " option = <value>, option = <value>,... \n" + "Valid options are: %s\n" + "'mode' can be provided as a name or a number:\n" + "balance-rr = 0\n" + "active-backup = 1\n" + "balance-xor = 2\n" + "broadcast = 3\n" + "802.3ad = 4\n" + "balance-tlb = 5\n" + "balance-alb = 6\n\n" + "Example: mode=2,miimon=120\n"), options_str); + g_free (options_str); + } + return desc; +} + +static const char * +nmc_property_bond_allowed_options (NMSetting *setting, const char *prop) +{ + const char **valid_options; + static char *allowed_vals = NULL; + + if (G_UNLIKELY (allowed_vals == NULL)) { + valid_options = nm_setting_bond_get_valid_options (NM_SETTING_BOND (setting)); + allowed_vals = g_strjoinv (", ", (char **) valid_options); + } + return allowed_vals; +} + +/* --- NM_SETTING_INFINIBAND_SETTING_NAME property setter functions --- */ +/* 'mac-addresss' */ +static gboolean +nmc_property_ib_set_mac (NMSetting *setting, const char *prop, const char *val, GError **error) +{ + g_return_val_if_fail (error == NULL || *error == NULL, FALSE); + + if (!nm_utils_hwaddr_valid (val, INFINIBAND_ALEN)) { + g_set_error (error, 1, 0, _("'%s' is not a valid InfiniBand MAC"), val); + return FALSE; + } + + g_object_set (setting, prop, val, NULL); + return TRUE; +} + +/* 'transport-mode' */ +static const char *ib_valid_transport_modes[] = { "datagram", "connected", NULL }; + +static gboolean +nmc_property_ib_set_transport_mode (NMSetting *setting, const char *prop, const char *val, GError **error) +{ + return check_and_set_string (setting, prop, val, ib_valid_transport_modes, error); +} + +DEFINE_ALLOWED_VAL_FUNC (nmc_property_ib_allowed_transport_mode, ib_valid_transport_modes) + +/* 'p-key' */ +static gboolean +nmc_property_ib_set_p_key (NMSetting *setting, const char *prop, const char *val, GError **error) +{ + gboolean p_key_valid = FALSE; + long p_key_int; + + g_return_val_if_fail (error == NULL || *error == NULL, FALSE); + + if (!strncasecmp (val, "0x", 2)) + p_key_valid = nmc_string_to_int_base (val + 2, 16, TRUE, 0, G_MAXUINT16, &p_key_int); + else + p_key_valid = nmc_string_to_int (val, TRUE, -1, G_MAXUINT16, &p_key_int); + + if (!p_key_valid) { + if (strcmp (val, "default") == 0) + p_key_int = -1; + else { + g_set_error (error, 1, 0, _("'%s' is not a valid IBoIP P_Key"), val); + return FALSE; + } + } + g_object_set (setting, prop, (gint) p_key_int, NULL); + return TRUE; +} + +/* --- IP4 / IP6 shared functions --- */ +static NMIPAddress * +_parse_ip_address (int family, const char *address, GError **error) +{ + char *value = g_strdup (address); + NMIPAddress *ipaddr; + + ipaddr = nmc_parse_and_build_address (family, g_strstrip (value), error); + g_free (value); + return ipaddr; +} + +static NMIPRoute * +_parse_ip_route (int family, const char *route, GError **error) +{ + char *value = g_strdup (route); + char **routev; + guint len; + NMIPRoute *iproute = NULL; + + routev = nmc_strsplit_set (g_strstrip (value), " \t", 0); + len = g_strv_length (routev); + if (len < 1 || len > 3) { + g_set_error (error, 1, 0, _("'%s' is not valid (the format is: ip[/prefix] [next-hop] [metric])"), + route); + goto finish; + } + iproute = nmc_parse_and_build_route (family, routev[0], routev[1], len >= 2 ? routev[2] : NULL, error); + +finish: + g_free (value); + g_strfreev (routev); + return iproute; +} + +/* --- NM_SETTING_IP4_CONFIG_SETTING_NAME property setter functions --- */ +/* 'method' */ +static const char *ipv4_valid_methods[] = { + NM_SETTING_IP4_CONFIG_METHOD_AUTO, + NM_SETTING_IP4_CONFIG_METHOD_LINK_LOCAL, + NM_SETTING_IP4_CONFIG_METHOD_MANUAL, + NM_SETTING_IP4_CONFIG_METHOD_SHARED, + NM_SETTING_IP4_CONFIG_METHOD_DISABLED, + NULL +}; + +static gboolean +nmc_property_ipv4_set_method (NMSetting *setting, const char *prop, const char *val, GError **error) +{ + /* Silently accept "static" and convert to "manual" */ + if (val && strlen (val) > 1 && matches (val, "static") == 0) + val = NM_SETTING_IP4_CONFIG_METHOD_MANUAL; + + return check_and_set_string (setting, prop, val, ipv4_valid_methods, error); +} + +DEFINE_ALLOWED_VAL_FUNC (nmc_property_ipv4_allowed_method, ipv4_valid_methods) + +/* 'dns' */ +static gboolean +nmc_property_ipv4_set_dns (NMSetting *setting, const char *prop, const char *val, GError **error) +{ + char **strv = NULL, **iter, *addr; + guint32 ip4_addr; + + g_return_val_if_fail (error == NULL || *error == NULL, FALSE); + + strv = nmc_strsplit_set (val, " \t,", 0); + for (iter = strv; iter && *iter; iter++) { + addr = g_strstrip (*iter); + if (inet_pton (AF_INET, addr, &ip4_addr) < 1) { + g_set_error (error, 1, 0, _("invalid IPv4 address '%s'"), addr); + g_strfreev (strv); + return FALSE; + } + nm_setting_ip_config_add_dns (NM_SETTING_IP_CONFIG (setting), addr); + } + g_strfreev (strv); + return TRUE; +} + +static gboolean +_validate_and_remove_ipv4_dns (NMSettingIPConfig *setting, + const char *dns, + GError **error) +{ + guint32 ip4_addr; + gboolean ret; + + if (inet_pton (AF_INET, dns, &ip4_addr) < 1) { + g_set_error (error, 1, 0, _("invalid IPv4 address '%s'"), dns); + return FALSE; + } + + ret = nm_setting_ip_config_remove_dns_by_value (setting, dns); + if (!ret) + g_set_error (error, 1, 0, _("the property doesn't contain DNS server '%s'"), dns); + return ret; +} +DEFINE_REMOVER_INDEX_OR_VALUE (nmc_property_ipv4_remove_dns, + NM_SETTING_IP_CONFIG, + nm_setting_ip_config_get_num_dns, + nm_setting_ip_config_remove_dns, + _validate_and_remove_ipv4_dns) + +static const char * +nmc_property_ipv4_describe_dns (NMSetting *setting, const char *prop) +{ + return _("Enter a list of IPv4 addresses of DNS servers.\n\n" + "Example: 8.8.8.8, 8.8.4.4\n"); +} + +/* 'dns-search' */ +static gboolean +nmc_property_ipv4_set_dns_search (NMSetting *setting, const char *prop, const char *val, GError **error) +{ + char **strv = NULL; + guint i = 0; + + g_return_val_if_fail (error == NULL || *error == NULL, FALSE); + + strv = nmc_strsplit_set (val, " \t,", 0); + if (!verify_string_list (strv, prop, nmc_util_is_domain, error)) { + g_strfreev (strv); + return FALSE; + } + + while (strv && strv[i]) + nm_setting_ip_config_add_dns_search (NM_SETTING_IP_CONFIG (setting), strv[i++]); + g_strfreev (strv); + + return TRUE; +} + +static gboolean +_validate_and_remove_ipv4_dns_search (NMSettingIPConfig *setting, + const char *dns_search, + GError **error) +{ + gboolean ret; + + ret = nm_setting_ip_config_remove_dns_search_by_value (setting, dns_search); + if (!ret) + g_set_error (error, 1, 0, + _("the property doesn't contain DNS search domain '%s'"), + dns_search); + return ret; +} +DEFINE_REMOVER_INDEX_OR_VALUE (nmc_property_ipv4_remove_dns_search, + NM_SETTING_IP_CONFIG, + nm_setting_ip_config_get_num_dns_searches, + nm_setting_ip_config_remove_dns_search, + _validate_and_remove_ipv4_dns_search) + +/* 'addresses' */ +static NMIPAddress * +_parse_ipv4_address (const char *address, GError **error) +{ + return _parse_ip_address (AF_INET, address, error); +} + +static gboolean +nmc_property_ipv4_set_addresses (NMSetting *setting, const char *prop, const char *val, GError **error) +{ + char **strv = NULL, **iter; + NMIPAddress *ip4addr; + + g_return_val_if_fail (error == NULL || *error == NULL, FALSE); + + strv = nmc_strsplit_set (val, ",", 0); + for (iter = strv; iter && *iter; iter++) { + ip4addr = _parse_ipv4_address (*iter, error); + if (!ip4addr) { + g_strfreev (strv); + return FALSE; + } + nm_setting_ip_config_add_address (NM_SETTING_IP_CONFIG (setting), ip4addr); + nm_ip_address_unref (ip4addr); + } + g_strfreev (strv); + return TRUE; +} + +static gboolean +_validate_and_remove_ipv4_address (NMSettingIPConfig *setting, + const char *address, + GError **error) +{ + NMIPAddress *ip4addr; + gboolean ret; + + ip4addr = _parse_ipv4_address (address, error); + if (!ip4addr) + return FALSE; + + ret = nm_setting_ip_config_remove_address_by_value (setting, ip4addr); + if (!ret) + g_set_error (error, 1, 0, + _("the property doesn't contain IP address '%s'"), address); + nm_ip_address_unref (ip4addr); + return ret; +} +DEFINE_REMOVER_INDEX_OR_VALUE (nmc_property_ipv4_remove_addresses, + NM_SETTING_IP_CONFIG, + nm_setting_ip_config_get_num_addresses, + nm_setting_ip_config_remove_address, + _validate_and_remove_ipv4_address) + +static const char * +nmc_property_ipv4_describe_addresses (NMSetting *setting, const char *prop) +{ + return _("Enter a list of IPv4 addresses formatted as:\n" + " ip[/prefix], ip[/prefix],...\n" + "Missing prefix is regarded as prefix of 32.\n\n" + "Example: 192.168.1.5/24, 10.0.0.11/24\n"); +} + +/* 'gateway' */ +static gboolean +nmc_property_ipv4_set_gateway (NMSetting *setting, const char *prop, const char *val, GError **error) +{ + NMIPAddress *ip4addr; + + g_return_val_if_fail (error == NULL || *error == NULL, FALSE); + + if (strchr (val, '/')) { + g_set_error (error, 1, 0, + _("invalid gateway address '%s'"), val); + return FALSE; + } + ip4addr = _parse_ipv4_address (val, error); + if (!ip4addr) + return FALSE; + + g_object_set (setting, prop, val, NULL); + nm_ip_address_unref (ip4addr); + return TRUE; +} + +/* 'routes' */ +static NMIPRoute * +_parse_ipv4_route (const char *route, GError **error) +{ + return _parse_ip_route (AF_INET, route, error); +} + +static gboolean +nmc_property_ipv4_set_routes (NMSetting *setting, const char *prop, const char *val, GError **error) +{ + char **strv = NULL, **iter; + NMIPRoute *ip4route; + + g_return_val_if_fail (error == NULL || *error == NULL, FALSE); + + strv = nmc_strsplit_set (val, ",", 0); + for (iter = strv; iter && *iter; iter++) { + ip4route = _parse_ipv4_route (*iter, error); + if (!ip4route) { + g_strfreev (strv); + return FALSE; + } + nm_setting_ip_config_add_route (NM_SETTING_IP_CONFIG (setting), ip4route); + nm_ip_route_unref (ip4route); + } + g_strfreev (strv); + return TRUE; +} + +static gboolean +_validate_and_remove_ipv4_route (NMSettingIPConfig *setting, + const char *route, + GError **error) +{ + NMIPRoute *ip4route; + gboolean ret; + + ip4route = _parse_ipv4_route (route, error); + if (!ip4route) + return FALSE; + + ret = nm_setting_ip_config_remove_route_by_value (setting, ip4route); + if (!ret) + g_set_error (error, 1, 0, _("the property doesn't contain route '%s'"), route); + nm_ip_route_unref (ip4route); + return ret; +} +DEFINE_REMOVER_INDEX_OR_VALUE (nmc_property_ipv4_remove_routes, + NM_SETTING_IP_CONFIG, + nm_setting_ip_config_get_num_routes, + nm_setting_ip_config_remove_route, + _validate_and_remove_ipv4_route) + +static const char * +nmc_property_ipv4_describe_routes (NMSetting *setting, const char *prop) +{ + return _("Enter a list of IPv4 routes formatted as:\n" + " ip[/prefix] [next-hop] [metric],...\n\n" + "Missing prefix is regarded as a prefix of 32.\n" + "Missing next-hop is regarded as 0.0.0.0.\n" + "Missing metric means default (NM/kernel will set a default value).\n\n" + "Examples: 192.168.2.0/24 192.168.2.1 3, 10.1.0.0/16 10.0.0.254\n" + " 10.1.2.0/24\n"); +} + +static char * +nmc_property_out2in_routes (const char *out_format) +{ + GRegex *regex; + GString *str; + char **strv; + int i; + + str = g_string_sized_new (128); + regex = g_regex_new ("\\{ dst = ([^/]+)/([^,]+), nh = ([^,]+), mt = ([^ ]+) \\}", 0, 0, NULL); + + strv = g_regex_split (regex, out_format, 0); + for (i = 1; strv && strv[i] && strv[i+1] && strv[i+2] && strv[i+3]; i=i+5) { + g_string_append (str, strv[i]); /* IP */ + g_string_append_c (str, '/'); + g_string_append (str, strv[i+1]); /* prefix */ + g_string_append_c (str, ' '); + g_string_append (str, strv[i+2]); /* next hop */ + g_string_append_c (str, ' '); + g_string_append (str, strv[i+3]); /* metric */ + g_string_append (str, ", "); + } + if (str->len > 0) + g_string_truncate (str, str->len - 2); + + g_strfreev (strv); + g_regex_unref (regex); + + return g_string_free (str, FALSE); +} + +/* --- NM_SETTING_IP6_CONFIG_SETTING_NAME property setter functions --- */ +/* 'method' */ +static const char *ipv6_valid_methods[] = { + NM_SETTING_IP6_CONFIG_METHOD_IGNORE, + NM_SETTING_IP6_CONFIG_METHOD_AUTO, + NM_SETTING_IP6_CONFIG_METHOD_DHCP, + NM_SETTING_IP6_CONFIG_METHOD_LINK_LOCAL, + NM_SETTING_IP6_CONFIG_METHOD_MANUAL, + NM_SETTING_IP6_CONFIG_METHOD_SHARED, + NULL +}; + +static gboolean +nmc_property_ipv6_set_method (NMSetting *setting, const char *prop, const char *val, GError **error) +{ + /* Silently accept "static" and convert to "manual" */ + if (val && strlen (val) > 1 && matches (val, "static") == 0) + val = NM_SETTING_IP6_CONFIG_METHOD_MANUAL; + + return check_and_set_string (setting, prop, val, ipv6_valid_methods, error); +} + +DEFINE_ALLOWED_VAL_FUNC (nmc_property_ipv6_allowed_method, ipv6_valid_methods) + +/* 'dns' */ +static gboolean +nmc_property_ipv6_set_dns (NMSetting *setting, const char *prop, const char *val, GError **error) +{ + char **strv = NULL, **iter, *addr; + struct in6_addr ip6_addr; + + g_return_val_if_fail (error == NULL || *error == NULL, FALSE); + + strv = nmc_strsplit_set (val, " \t,", 0); + for (iter = strv; iter && *iter; iter++) { + addr = g_strstrip (*iter); + if (inet_pton (AF_INET6, addr, &ip6_addr) < 1) { + g_set_error (error, 1, 0, _("invalid IPv6 address '%s'"), addr); + g_strfreev (strv); + return FALSE; + } + nm_setting_ip_config_add_dns (NM_SETTING_IP_CONFIG (setting), addr); + } + g_strfreev (strv); + return TRUE; +} + +static gboolean +_validate_and_remove_ipv6_dns (NMSettingIPConfig *setting, + const char *dns, + GError **error) +{ + struct in6_addr ip6_addr; + gboolean ret; + + if (inet_pton (AF_INET6, dns, &ip6_addr) < 1) { + g_set_error (error, 1, 0, _("invalid IPv6 address '%s'"), dns); + return FALSE; + } + + ret = nm_setting_ip_config_remove_dns_by_value (setting, dns); + if (!ret) + g_set_error (error, 1, 0, _("the property doesn't contain DNS server '%s'"), dns); + return ret; +} +DEFINE_REMOVER_INDEX_OR_VALUE (nmc_property_ipv6_remove_dns, + NM_SETTING_IP_CONFIG, + nm_setting_ip_config_get_num_dns, + nm_setting_ip_config_remove_dns, + _validate_and_remove_ipv6_dns) + +static const char * +nmc_property_ipv6_describe_dns (NMSetting *setting, const char *prop) +{ + return _("Enter a list of IPv6 addresses of DNS servers. If the IPv6 " + "configuration method is 'auto' these DNS servers are appended " + "to those (if any) returned by automatic configuration. DNS " + "servers cannot be used with the 'shared' or 'link-local' IPv6 " + "configuration methods, as there is no upstream network. In " + "all other IPv6 configuration methods, these DNS " + "servers are used as the only DNS servers for this connection.\n\n" + "Example: 2607:f0d0:1002:51::4, 2607:f0d0:1002:51::1\n"); +} + +/* 'dns-search' */ +static gboolean +nmc_property_ipv6_set_dns_search (NMSetting *setting, const char *prop, const char *val, GError **error) +{ + char **strv = NULL; + guint i = 0; + + g_return_val_if_fail (error == NULL || *error == NULL, FALSE); + + strv = nmc_strsplit_set (val, " \t,", 0); + if (!verify_string_list (strv, prop, nmc_util_is_domain, error)) { + g_strfreev (strv); + return FALSE; + } + + while (strv && strv[i]) + nm_setting_ip_config_add_dns_search (NM_SETTING_IP_CONFIG (setting), strv[i++]); + g_strfreev (strv); + + return TRUE; +} + +static gboolean +_validate_and_remove_ipv6_dns_search (NMSettingIPConfig *setting, + const char *dns_search, + GError **error) +{ + gboolean ret; + + ret = nm_setting_ip_config_remove_dns_search_by_value (setting, dns_search); + if (!ret) + g_set_error (error, 1, 0, + _("the property doesn't contain DNS search domain '%s'"), + dns_search); + return ret; +} +DEFINE_REMOVER_INDEX_OR_VALUE (nmc_property_ipv6_remove_dns_search, + NM_SETTING_IP_CONFIG, + nm_setting_ip_config_get_num_dns_searches, + nm_setting_ip_config_remove_dns_search, + _validate_and_remove_ipv6_dns_search) + +/* 'addresses' */ +static NMIPAddress * +_parse_ipv6_address (const char *address, GError **error) +{ + return _parse_ip_address (AF_INET6, address, error); +} + +static gboolean +nmc_property_ipv6_set_addresses (NMSetting *setting, const char *prop, const char *val, GError **error) +{ + char **strv = NULL, **iter; + NMIPAddress *ip6addr; + + g_return_val_if_fail (error == NULL || *error == NULL, FALSE); + + strv = nmc_strsplit_set (val, ",", 0); + for (iter = strv; iter && *iter; iter++) { + ip6addr = _parse_ipv6_address (*iter, error); + if (!ip6addr) { + g_strfreev (strv); + return FALSE; + } + nm_setting_ip_config_add_address (NM_SETTING_IP_CONFIG (setting), ip6addr); + nm_ip_address_unref (ip6addr); + } + g_strfreev (strv); + return TRUE; +} + +static gboolean +_validate_and_remove_ipv6_address (NMSettingIPConfig *setting, + const char *address, + GError **error) +{ + NMIPAddress *ip6addr; + gboolean ret; + + ip6addr = _parse_ipv6_address (address, error); + if (!ip6addr) + return FALSE; + + ret = nm_setting_ip_config_remove_address_by_value (setting, ip6addr); + if (!ret) + g_set_error (error, 1, 0, _("the property doesn't contain IP address '%s'"), address); + nm_ip_address_unref (ip6addr); + return ret; +} +DEFINE_REMOVER_INDEX_OR_VALUE (nmc_property_ipv6_remove_addresses, + NM_SETTING_IP_CONFIG, + nm_setting_ip_config_get_num_addresses, + nm_setting_ip_config_remove_address, + _validate_and_remove_ipv6_address) + +static const char * +nmc_property_ipv6_describe_addresses (NMSetting *setting, const char *prop) +{ + return _("Enter a list of IPv6 addresses formatted as:\n" + " ip[/prefix], ip[/prefix],...\n" + "Missing prefix is regarded as prefix of 128.\n\n" + "Example: 2607:f0d0:1002:51::4/64, 1050:0:0:0:5:600:300c:326b\n"); +} + +/* 'gateway' */ +static gboolean +nmc_property_ipv6_set_gateway (NMSetting *setting, const char *prop, const char *val, GError **error) +{ + NMIPAddress *ip6addr; + + g_return_val_if_fail (error == NULL || *error == NULL, FALSE); + + if (strchr (val, '/')) { + g_set_error (error, 1, 0, + _("invalid gateway address '%s'"), val); + return FALSE; + } + ip6addr = _parse_ipv6_address (val, error); + if (!ip6addr) + return FALSE; + + g_object_set (setting, prop, val, NULL); + nm_ip_address_unref (ip6addr); + return TRUE; +} + +/* 'routes' */ +static NMIPRoute * +_parse_ipv6_route (const char *route, GError **error) +{ + return _parse_ip_route (AF_INET6, route, error); +} + +static gboolean +nmc_property_ipv6_set_routes (NMSetting *setting, const char *prop, const char *val, GError **error) +{ + char **strv = NULL, **iter; + NMIPRoute *ip6route; + + g_return_val_if_fail (error == NULL || *error == NULL, FALSE); + + strv = nmc_strsplit_set (val, ",", 0); + for (iter = strv; iter && *iter; iter++) { + ip6route = _parse_ipv6_route (*iter, error); + if (!ip6route) { + g_strfreev (strv); + return FALSE; + } + nm_setting_ip_config_add_route (NM_SETTING_IP_CONFIG (setting), ip6route); + nm_ip_route_unref (ip6route); + } + g_strfreev (strv); + return TRUE; +} + +static gboolean +_validate_and_remove_ipv6_route (NMSettingIPConfig *setting, + const char *route, + GError **error) +{ + NMIPRoute *ip6route; + gboolean ret; + + ip6route = _parse_ipv6_route (route, error); + if (!ip6route) + return FALSE; + + ret = nm_setting_ip_config_remove_route_by_value (setting, ip6route); + if (!ret) + g_set_error (error, 1, 0, _("the property doesn't contain route '%s'"), route); + nm_ip_route_unref (ip6route); + return ret; +} +DEFINE_REMOVER_INDEX_OR_VALUE (nmc_property_ipv6_remove_routes, + NM_SETTING_IP_CONFIG, + nm_setting_ip_config_get_num_routes, + nm_setting_ip_config_remove_route, + _validate_and_remove_ipv6_route) + +static const char * +nmc_property_ipv6_describe_routes (NMSetting *setting, const char *prop) +{ + return _("Enter a list of IPv6 routes formatted as:\n" + " ip[/prefix] [next-hop] [metric],...\n\n" + "Missing prefix is regarded as a prefix of 128.\n" + "Missing next-hop is regarded as \"::\".\n" + "Missing metric means default (NM/kernel will set a default value).\n\n" + "Examples: 2001:db8:beef:2::/64 2001:db8:beef::2, 2001:db8:beef:3::/64 2001:db8:beef::3 2\n" + " abbe::/64 55\n"); +} + +static gboolean +nmc_property_ipv6_set_ip6_privacy (NMSetting *setting, const char *prop, const char *val, GError **error) +{ + unsigned long val_int; + + g_return_val_if_fail (error == NULL || *error == NULL, FALSE); + + if (!nmc_string_to_uint (val, FALSE, 0, 0, &val_int)) { + g_set_error (error, 1, 0, _("'%s' is not a number"), val); + return FALSE; + } + + if ( val_int != NM_SETTING_IP6_CONFIG_PRIVACY_DISABLED + && val_int != NM_SETTING_IP6_CONFIG_PRIVACY_PREFER_PUBLIC_ADDR + && val_int != NM_SETTING_IP6_CONFIG_PRIVACY_PREFER_TEMP_ADDR) { + g_set_error (error, 1, 0, _("'%s' is not valid; use 0, 1, or 2"), val); + return FALSE; + } + + g_object_set (setting, prop, val_int, NULL); + return TRUE; +} + +/* --- NM_SETTING_OLPC_MESH_SETTING_NAME property setter functions --- */ +static gboolean +nmc_property_olpc_set_channel (NMSetting *setting, const char *prop, const char *val, GError **error) +{ + unsigned long chan_int; + + g_return_val_if_fail (error == NULL || *error == NULL, FALSE); + + if (!nmc_string_to_uint (val, TRUE, 1, 13, &chan_int)) { + g_set_error (error, 1, 0, _("'%s' is not a valid channel; use <1-13>"), val); + return FALSE; + } + g_object_set (setting, prop, chan_int, NULL); + return TRUE; +} + + +/* --- NM_SETTING_SERIAL_SETTING_NAME property setter functions --- */ +static char * +nmc_property_serial_get_parity (NMSetting *setting) +{ + NMSettingSerial *s_serial = NM_SETTING_SERIAL (setting); + + switch (nm_setting_serial_get_parity (s_serial)) { + case NM_SETTING_SERIAL_PARITY_EVEN: + return g_strdup ("even"); + case NM_SETTING_SERIAL_PARITY_ODD: + return g_strdup ("odd"); + default: + case NM_SETTING_SERIAL_PARITY_NONE: + return g_strdup ("none"); + } +} + +static gboolean +nmc_property_serial_set_parity (NMSetting *setting, const char *prop, const char *val, GError **error) +{ + NMSettingSerialParity parity; + + if (val[0] == 'E' || val[0] == 'e') + parity = NM_SETTING_SERIAL_PARITY_EVEN; + else if (val[0] == 'O' || val[0] == 'o') + parity = NM_SETTING_SERIAL_PARITY_ODD; + else if (val[0] == 'N' || val[0] == 'n') + parity = NM_SETTING_SERIAL_PARITY_NONE; + else { + g_set_error (error, 1, 0, _("'%s' is not valid; use [e, o, n]"), val); + return FALSE; + } + + g_object_set (setting, prop, parity, NULL); + return TRUE; +} + +/* --- NM_SETTING_TEAM_SETTING_NAME property functions --- */ +/* --- NM_SETTING_TEAM_PORT_SETTING_NAME property functions --- */ +static gboolean +nmc_property_team_set_config (NMSetting *setting, const char *prop, const char *val, GError **error) +{ + char *json = NULL; + + g_return_val_if_fail (error == NULL || *error == NULL, FALSE); + + if (!nmc_team_check_config (val, &json, error)) { + return FALSE; + } + g_object_set (setting, prop, json, NULL); + g_free (json); + return TRUE; +} + +static const char * +nmc_property_team_describe_config (NMSetting *setting, const char *prop) +{ + return _("nmcli can accepts both direct JSON configuration data and a file name containing " + "the configuration. In the latter case the file is read and the contents is put " + "into this property.\n\n" + "Examples: set team.config " + "{ \"device\": \"team0\", \"runner\": {\"name\": \"roundrobin\"}, \"ports\": {\"eth1\": {}, \"eth2\": {}} }\n" + " set team.config /etc/my-team.conf\n"); +} + +/* --- NM_SETTING_VLAN_SETTING_NAME property setter functions --- */ +static gboolean +nmc_property_vlan_set_prio_map (NMSetting *setting, + const char *prop, + const char *val, + NMVlanPriorityMap map_type, + GError **error) +{ + char **prio_map, **p; + + prio_map = nmc_vlan_parse_priority_maps (val, map_type, error); + if (!prio_map) + return FALSE; + + for (p = prio_map; p && *p; p++) + nm_setting_vlan_add_priority_str (NM_SETTING_VLAN (setting), map_type, *p); + + g_strfreev (prio_map); + return TRUE; +} + +static gboolean +nmc_property_vlan_remove_prio_map (NMSetting *setting, + const char *prop, + guint32 idx, + NMVlanPriorityMap map_type, + GError **error) +{ + guint32 num; + + num = nm_setting_vlan_get_num_priorities (NM_SETTING_VLAN (setting), map_type); + if (num == 0) { + g_set_error_literal (error, 1, 0, _("no priority to remove")); + return FALSE; + } + if (idx >= num) { + g_set_error (error, 1, 0, _("index '%d' is not in the range of <0-%d>"), + idx, num - 1); + return FALSE; + } + + nm_setting_vlan_remove_priority (NM_SETTING_VLAN (setting), map_type, idx); + return TRUE; +} + +static gboolean +nmc_property_vlan_set_ingress_priority_map (NMSetting *setting, const char *prop, const char *val, GError **error) +{ + return nmc_property_vlan_set_prio_map (setting, prop, val, NM_VLAN_INGRESS_MAP, error); +} + +static gboolean +nmc_property_vlan_set_egress_priority_map (NMSetting *setting, const char *prop, const char *val, GError **error) +{ + return nmc_property_vlan_set_prio_map (setting, prop, val, NM_VLAN_EGRESS_MAP, error); +} + +static gboolean +nmc_property_vlan_remove_priority_map (NMSetting *setting, + const char *prop, + const char *value, + guint32 idx, + NMVlanPriorityMap map, + GError **error) +{ + /* If value != NULL, remove by value */ + if (value) { + gboolean ret; + char **prio_map; + char *val = g_strdup (value); + + prio_map = nmc_vlan_parse_priority_maps (val, map, error); + if (!prio_map) + return FALSE; + if (prio_map[1]) + g_print (_("Warning: only one mapping at a time is supported; taking the first one (%s)\n"), + prio_map[0]); + ret = nm_setting_vlan_remove_priority_str_by_value (NM_SETTING_VLAN (setting), + map, + prio_map[0]); + + if (!ret) + g_set_error (error, 1, 0, _("the property doesn't contain mapping '%s'"), prio_map[0]); + g_free (val); + g_strfreev (prio_map); + return ret; + } + + /* Else remove by index */ + return nmc_property_vlan_remove_prio_map (setting, prop, idx, map, error); +} + +static gboolean +nmc_property_vlan_remove_ingress_priority_map (NMSetting *setting, + const char *prop, + const char *value, + guint32 idx, + GError **error) +{ + return nmc_property_vlan_remove_priority_map (setting, + prop, + value, + idx, + NM_VLAN_INGRESS_MAP, + error); +} + +static gboolean +nmc_property_vlan_remove_egress_priority_map (NMSetting *setting, + const char *prop, + const char *value, + guint32 idx, + GError **error) +{ + return nmc_property_vlan_remove_priority_map (setting, + prop, + value, + idx, + NM_VLAN_EGRESS_MAP, + error); +} + +/* --- NM_SETTING_VPN_SETTING_NAME property setter functions --- */ +/* 'data' */ +DEFINE_SETTER_OPTIONS (nmc_property_vpn_set_data, + NM_SETTING_VPN, + NMSettingVpn, + nm_setting_vpn_add_data_item, + NULL, + NULL) +DEFINE_REMOVER_OPTION (nmc_property_vpn_remove_option_data, + NM_SETTING_VPN, + nm_setting_vpn_remove_data_item) + +/* 'secrets' */ +DEFINE_SETTER_OPTIONS (nmc_property_vpn_set_secrets, + NM_SETTING_VPN, + NMSettingVpn, + nm_setting_vpn_add_secret, + NULL, + NULL) +DEFINE_REMOVER_OPTION (nmc_property_vpn_remove_option_secret, + NM_SETTING_VPN, + nm_setting_vpn_remove_secret) + +/* --- NM_SETTING_WIMAX_SETTING_NAME property setter functions --- */ +/* No specific functions */ + +/* --- NM_SETTING_WIRED_SETTING_NAME property setter functions --- */ +#if 0 +/* + * Do not allow setting 'port' and 'duplex' for now. They are not implemented in + * NM core, nor in ifcfg-rh plugin. Enable this when it gets done. + */ +/* 'port' */ +static const char *wired_valid_ports[] = { "tp", "aui", "bnc", "mii", NULL }; + +static gboolean +nmc_property_wired_set_port (NMSetting *setting, const char *prop, const char *val, GError **error) +{ + return check_and_set_string (setting, prop, val, wired_valid_ports, error); +} + +DEFINE_ALLOWED_VAL_FUNC (nmc_property_wired_allowed_port, wired_valid_ports) + +/* 'duplex' */ +static const char *wired_valid_duplexes[] = { "half", "full", NULL }; + +static gboolean +nmc_property_wired_set_duplex (NMSetting *setting, const char *prop, const char *val, GError **error) +{ + return check_and_set_string (setting, prop, val, wired_valid_duplexes, error); +} + +DEFINE_ALLOWED_VAL_FUNC (nmc_property_wired_allowed_duplex, wired_valid_duplexes) +#endif + +/* 'mac-address-blacklist' */ +DEFINE_SETTER_MAC_BLACKLIST (nmc_property_wired_set_mac_address_blacklist, + NM_SETTING_WIRED, + nm_setting_wired_add_mac_blacklist_item) + +static gboolean +_validate_and_remove_wired_mac_blacklist_item (NMSettingWired *setting, + const char *mac, + GError **error) +{ + gboolean ret; + guint8 buf[32]; + + if (!nm_utils_hwaddr_aton (mac, buf, ETH_ALEN)) { + g_set_error (error, 1, 0, _("'%s' is not a valid MAC address"), mac); + return FALSE; + } + + ret = nm_setting_wired_remove_mac_blacklist_item_by_value (setting, mac); + if (!ret) + g_set_error (error, 1, 0, _("the property doesn't contain MAC address '%s'"), mac); + return ret; +} +DEFINE_REMOVER_INDEX_OR_VALUE (nmc_property_wired_remove_mac_address_blacklist, + NM_SETTING_WIRED, + nm_setting_wired_get_num_mac_blacklist_items, + nm_setting_wired_remove_mac_blacklist_item, + _validate_and_remove_wired_mac_blacklist_item) + +/* 's390-subchannels' */ +static gboolean +nmc_property_wired_set_s390_subchannels (NMSetting *setting, const char *prop, const char *val, GError **error) +{ + char **strv = NULL; + int len; + + strv = nmc_strsplit_set (val, " ,\t", 0); + len = g_strv_length (strv); + if (len != 2 && len != 3) { + g_set_error (error, 1, 0, _("'%s' is not valid; 2 or 3 strings should be provided"), + val); + g_strfreev (strv); + return FALSE; + } + + g_object_set (setting, prop, strv, NULL); + g_strfreev (strv); + return TRUE; +} + +static const char * +nmc_property_wired_describe_s390_subchannels (NMSetting *setting, const char *prop) +{ + return _("Enter a list of subchannels (comma or space separated).\n\n" + "Example: 0.0.0e20 0.0.0e21 0.0.0e22\n"); +} + +/* 's390-nettype' */ +static const char *wired_valid_s390_nettypes[] = { "qeth", "lcs", "ctc", NULL }; + +static gboolean +nmc_property_wired_set_s390_nettype (NMSetting *setting, const char *prop, const char *val, GError **error) +{ + return check_and_set_string (setting, prop, val, wired_valid_s390_nettypes, error); +} + +DEFINE_ALLOWED_VAL_FUNC (nmc_property_wired_allowed_s390_nettype, wired_valid_s390_nettypes) + +/* 's390-options' */ +DEFINE_SETTER_OPTIONS (nmc_property_wired_set_s390_options, + NM_SETTING_WIRED, + NMSettingWired, + nm_setting_wired_add_s390_option, + nm_setting_wired_get_valid_s390_options, + NULL) +DEFINE_REMOVER_OPTION (nmc_property_wired_remove_option_s390_options, + NM_SETTING_WIRED, + nm_setting_wired_remove_s390_option) + +static const char * +nmc_property_wired_allowed_s390_options (NMSetting *setting, const char *prop) +{ + const char **valid_options; + static char *allowed_vals = NULL; + + if (G_UNLIKELY (allowed_vals == NULL)) { + valid_options = nm_setting_wired_get_valid_s390_options (NM_SETTING_WIRED (setting)); + allowed_vals = g_strjoinv (", ", (char **) valid_options); + } + return allowed_vals; +} + +static const char * +nmc_property_wired_describe_s390_options (NMSetting *setting, const char *prop) +{ + static char *desc = NULL; + const char **valid_options; + char *options_str; + + if (G_UNLIKELY (desc == NULL)) { + valid_options = nm_setting_wired_get_valid_s390_options (NM_SETTING_WIRED (setting)); + options_str = g_strjoinv (", ", (char **) valid_options); + + desc = g_strdup_printf (_("Enter a list of S/390 options formatted as:\n" + " option = <value>, option = <value>,...\n" + "Valid options are: %s\n"), + options_str); + g_free (options_str); + } + return desc; +} + +/* --- NM_SETTING_WIRELESS_SETTING_NAME property setter functions --- */ +/* 'mode' */ +static const char *wifi_valid_modes[] = { + NM_SETTING_WIRELESS_MODE_INFRA, + NM_SETTING_WIRELESS_MODE_ADHOC, + NM_SETTING_WIRELESS_MODE_AP, + NULL +}; + +static gboolean +nmc_property_wifi_set_mode (NMSetting *setting, const char *prop, const char *val, GError **error) +{ + return check_and_set_string (setting, prop, val, wifi_valid_modes, error); +} + +DEFINE_ALLOWED_VAL_FUNC (nmc_property_wifi_allowed_mode, wifi_valid_modes) + +/* 'band' */ +static const char *wifi_valid_bands[] = { "a", "bg", NULL }; + +static gboolean +nmc_property_wifi_set_band (NMSetting *setting, const char *prop, const char *val, GError **error) +{ + return check_and_set_string (setting, prop, val, wifi_valid_bands, error); +} + +DEFINE_ALLOWED_VAL_FUNC (nmc_property_wifi_allowed_band, wifi_valid_bands) + +/* 'channel' */ +static gboolean +nmc_property_wifi_set_channel (NMSetting *setting, const char *prop, const char *val, GError **error) +{ + unsigned long chan_int; + + g_return_val_if_fail (error == NULL || *error == NULL, FALSE); + + if (!nmc_string_to_uint (val, FALSE, 0, 0, &chan_int)) { + g_set_error (error, 1, 0, _("'%s' is not a valid channel"), val); + return FALSE; + } + + if ( !nm_utils_wifi_is_channel_valid (chan_int, "a") + && !nm_utils_wifi_is_channel_valid (chan_int, "bg")) { + g_set_error (error, 1, 0, _("'%ld' is not a valid channel"), chan_int); + return FALSE; + } + + g_object_set (setting, prop, chan_int, NULL); + return TRUE; +} + +/* 'mac-address-blacklist' */ +DEFINE_SETTER_MAC_BLACKLIST (nmc_property_wireless_set_mac_address_blacklist, + NM_SETTING_WIRELESS, + nm_setting_wireless_add_mac_blacklist_item) + +static gboolean +_validate_and_remove_wifi_mac_blacklist_item (NMSettingWireless *setting, + const char *mac, + GError **error) +{ + gboolean ret; + guint8 buf[32]; + + if (!nm_utils_hwaddr_aton (mac, buf, ETH_ALEN)) { + g_set_error (error, 1, 0, _("'%s' is not a valid MAC address"), mac); + return FALSE; + } + + ret = nm_setting_wireless_remove_mac_blacklist_item_by_value (setting, mac); + if (!ret) + g_set_error (error, 1, 0, _("the property doesn't contain MAC address '%s'"), mac); + return ret; +} +DEFINE_REMOVER_INDEX_OR_VALUE (nmc_property_wireless_remove_mac_address_blacklist, + NM_SETTING_WIRELESS, + nm_setting_wireless_get_num_mac_blacklist_items, + nm_setting_wireless_remove_mac_blacklist_item, + _validate_and_remove_wifi_mac_blacklist_item) + +/* --- NM_SETTING_WIRELESS_SECURITY_SETTING_NAME property setter functions --- */ +/* 'key-mgmt' */ +static const char *wifi_sec_valid_key_mgmts[] = { "none", "ieee8021x", "wpa-none", "wpa-psk", "wpa-eap", NULL }; + +static gboolean +nmc_property_wifi_sec_set_key_mgmt (NMSetting *setting, const char *prop, const char *val, GError **error) +{ + return check_and_set_string (setting, prop, val, wifi_sec_valid_key_mgmts, error); +} + +DEFINE_ALLOWED_VAL_FUNC (nmc_property_wifi_sec_allowed_key_mgmt, wifi_sec_valid_key_mgmts) + +/* 'auth-alg' */ +static const char *wifi_sec_valid_auth_algs[] = { "open", "shared", "leap", NULL }; + +static gboolean +nmc_property_wifi_sec_set_auth_alg (NMSetting *setting, const char *prop, const char *val, GError **error) +{ + return check_and_set_string (setting, prop, val, wifi_sec_valid_auth_algs, error); +} + +DEFINE_SETTER_STR_LIST_MULTI (check_and_add_wifi_sec_proto, + NM_SETTING_WIRELESS_SECURITY, + nm_setting_wireless_security_add_proto) +DEFINE_ALLOWED_VAL_FUNC (nmc_property_wifi_sec_allowed_auth_alg, wifi_sec_valid_auth_algs) + +/* 'proto' */ +static const char *wifi_sec_valid_protos[] = { "wpa", "rsn", NULL }; + +static gboolean +nmc_property_wifi_sec_set_proto (NMSetting *setting, const char *prop, const char *val, GError **error) +{ + return check_and_add_wifi_sec_proto (setting, prop, val, wifi_sec_valid_protos, error); +} + +static gboolean +_validate_and_remove_wifi_sec_proto (NMSettingWirelessSecurity *setting, + const char *proto, + GError **error) +{ + gboolean ret; + const char *valid; + + valid = nmc_string_is_valid (proto, wifi_sec_valid_protos, error); + if (!valid) + return FALSE; + + ret = nm_setting_wireless_security_remove_proto_by_value (setting, proto); + if (!ret) + g_set_error (error, 1, 0, + _("the property doesn't contain protocol '%s'"), proto); + return ret; +} +DEFINE_REMOVER_INDEX_OR_VALUE (nmc_property_wifi_sec_remove_proto, + NM_SETTING_WIRELESS_SECURITY, + nm_setting_wireless_security_get_num_protos, + nm_setting_wireless_security_remove_proto, + _validate_and_remove_wifi_sec_proto) + +DEFINE_SETTER_STR_LIST_MULTI (check_and_add_wifi_sec_pairwise, + NM_SETTING_WIRELESS_SECURITY, + nm_setting_wireless_security_add_pairwise) +DEFINE_ALLOWED_VAL_FUNC (nmc_property_wifi_sec_allowed_proto, wifi_sec_valid_protos) + +/* 'pairwise' */ +static const char *wifi_sec_valid_pairwises[] = { "tkip", "ccmp", NULL }; + +static gboolean +nmc_property_wifi_sec_set_pairwise (NMSetting *setting, const char *prop, const char *val, GError **error) +{ + return check_and_add_wifi_sec_pairwise (setting, prop, val, wifi_sec_valid_pairwises, error); +} + +static gboolean +_validate_and_remove_wifi_sec_pairwise (NMSettingWirelessSecurity *setting, + const char *pairwise, + GError **error) +{ + gboolean ret; + const char *valid; + + valid = nmc_string_is_valid (pairwise, wifi_sec_valid_pairwises, error); + if (!valid) + return FALSE; + + ret = nm_setting_wireless_security_remove_pairwise_by_value (setting, pairwise); + if (!ret) + g_set_error (error, 1, 0, + _("the property doesn't contain protocol '%s'"), pairwise); + return ret; +} +DEFINE_REMOVER_INDEX_OR_VALUE (nmc_property_wifi_sec_remove_pairwise, + NM_SETTING_WIRELESS_SECURITY, + nm_setting_wireless_security_get_num_pairwise, + nm_setting_wireless_security_remove_pairwise, + _validate_and_remove_wifi_sec_pairwise) + +DEFINE_SETTER_STR_LIST_MULTI (check_and_add_wifi_sec_group, + NM_SETTING_WIRELESS_SECURITY, + nm_setting_wireless_security_add_group) +DEFINE_ALLOWED_VAL_FUNC (nmc_property_wifi_sec_allowed_pairwise, wifi_sec_valid_pairwises) + +/* 'group' */ +static const char *wifi_sec_valid_groups[] = { "wep40", "wep104", "tkip", "ccmp", NULL }; + +static gboolean +nmc_property_wifi_sec_set_group (NMSetting *setting, const char *prop, const char *val, GError **error) +{ + return check_and_add_wifi_sec_group (setting, prop, val, wifi_sec_valid_groups, error); +} + +static gboolean +_validate_and_remove_wifi_sec_group (NMSettingWirelessSecurity *setting, + const char *group, + GError **error) +{ + gboolean ret; + const char *valid; + + valid = nmc_string_is_valid (group, wifi_sec_valid_groups, error); + if (!valid) + return FALSE; + + ret = nm_setting_wireless_security_remove_group_by_value (setting, group); + if (!ret) + g_set_error (error, 1, 0, + _("the property doesn't contain protocol '%s'"), group); + return ret; +} +DEFINE_REMOVER_INDEX_OR_VALUE (nmc_property_wifi_sec_remove_group, + NM_SETTING_WIRELESS_SECURITY, + nm_setting_wireless_security_get_num_groups, + nm_setting_wireless_security_remove_group, + _validate_and_remove_wifi_sec_group) +DEFINE_ALLOWED_VAL_FUNC (nmc_property_wifi_sec_allowed_group, wifi_sec_valid_groups) + +/* 'wep-key' */ +static gboolean +nmc_property_wifi_set_wep_key (NMSetting *setting, const char *prop, const char *val, GError **error) +{ + NMWepKeyType guessed_type = NM_WEP_KEY_TYPE_UNKNOWN; + NMWepKeyType type; + guint32 prev_idx, idx; + + g_return_val_if_fail (error == NULL || *error == NULL, FALSE); + + /* Get currently set type */ + type = nm_setting_wireless_security_get_wep_key_type (NM_SETTING_WIRELESS_SECURITY (setting)); + + /* Guess key type */ + if (nm_utils_wep_key_valid (val, NM_WEP_KEY_TYPE_KEY)) + guessed_type = NM_WEP_KEY_TYPE_KEY; + else if (nm_utils_wep_key_valid (val, NM_WEP_KEY_TYPE_PASSPHRASE)) + guessed_type = NM_WEP_KEY_TYPE_PASSPHRASE; + + if (guessed_type == NM_WEP_KEY_TYPE_UNKNOWN) { + g_set_error (error, 1, 0, _("'%s' is not valid"), val); + return FALSE; + } + + if (type != NM_WEP_KEY_TYPE_UNKNOWN && type != guessed_type) { + if (nm_utils_wep_key_valid (val, type)) + guessed_type = type; + else { + g_set_error (error, 1, 0, + _("'%s' not compatible with %s '%s', please change the key or set the right %s first."), + val, NM_SETTING_WIRELESS_SECURITY_WEP_KEY_TYPE, wep_key_type_to_string (type), + NM_SETTING_WIRELESS_SECURITY_WEP_KEY_TYPE); + return FALSE; + } + } + prev_idx = nm_setting_wireless_security_get_wep_tx_keyidx (NM_SETTING_WIRELESS_SECURITY (setting)); + idx = prop[strlen (prop) - 1] - '0'; + g_print (_("WEP key is guessed to be of '%s'\n"), wep_key_type_to_string (guessed_type)); + if (idx != prev_idx) + g_print (_("WEP key index set to '%d'\n"), idx); + + g_object_set (setting, prop, val, NULL); + g_object_set (setting, NM_SETTING_WIRELESS_SECURITY_WEP_KEY_TYPE, guessed_type, NULL); + if (idx != prev_idx) + g_object_set (setting, NM_SETTING_WIRELESS_SECURITY_WEP_TX_KEYIDX, idx, NULL); + return TRUE; +} + +/* 'wep-key-type' */ +static gboolean +nmc_property_wifi_set_wep_key_type (NMSetting *setting, const char *prop, const char *val, GError **error) +{ + unsigned long type_int; + const char *valid_wep_types[] = { "unknown", "key", "passphrase", NULL }; + const char *type_str = NULL; + const char *key0, *key1,* key2, *key3; + NMWepKeyType type = NM_WEP_KEY_TYPE_UNKNOWN; + + g_return_val_if_fail (error == NULL || *error == NULL, FALSE); + + if (!nmc_string_to_uint (val, TRUE, 0, 2, &type_int)) { + if (!(type_str = nmc_string_is_valid (val, valid_wep_types, NULL))) { + g_set_error (error, 1, 0, _("'%s' not among [0 (unknown), 1 (key), 2 (passphrase)]"), val); + return FALSE; + } + if (type_str == valid_wep_types[1]) + type = NM_WEP_KEY_TYPE_KEY; + else if (type_str == valid_wep_types[2]) + type = NM_WEP_KEY_TYPE_PASSPHRASE; + } else + type = (NMWepKeyType) type_int; + + /* Check type compatibility with set keys */ + key0 = nm_setting_wireless_security_get_wep_key (NM_SETTING_WIRELESS_SECURITY (setting), 0); + key1 = nm_setting_wireless_security_get_wep_key (NM_SETTING_WIRELESS_SECURITY (setting), 1); + key2 = nm_setting_wireless_security_get_wep_key (NM_SETTING_WIRELESS_SECURITY (setting), 2); + key3 = nm_setting_wireless_security_get_wep_key (NM_SETTING_WIRELESS_SECURITY (setting), 3); + if (key0 && !nm_utils_wep_key_valid (key0, type)) + g_print (_("Warning: '%s' is not compatible with '%s' type, please change or delete the key.\n"), + NM_SETTING_WIRELESS_SECURITY_WEP_KEY0, wep_key_type_to_string (type)); + if (key1 && !nm_utils_wep_key_valid (key1, type)) + g_print (_("Warning: '%s' is not compatible with '%s' type, please change or delete the key.\n"), + NM_SETTING_WIRELESS_SECURITY_WEP_KEY1, wep_key_type_to_string (type)); + if (key2 && !nm_utils_wep_key_valid (key2, type)) + g_print (_("Warning: '%s' is not compatible with '%s' type, please change or delete the key.\n"), + NM_SETTING_WIRELESS_SECURITY_WEP_KEY2, wep_key_type_to_string (type)); + if (key3 && !nm_utils_wep_key_valid (key3, type)) + g_print (_("Warning: '%s' is not compatible with '%s' type, please change or delete the key.\n"), + NM_SETTING_WIRELESS_SECURITY_WEP_KEY3, wep_key_type_to_string (type)); + + g_object_set (setting, prop, type, NULL); + return TRUE; +} + +static const char * +nmc_property_wifi_describe_wep_key_type (NMSetting *setting, const char *prop) +{ + static char *desc = NULL; + + if (G_UNLIKELY (desc == NULL)) { + desc = g_strdup_printf (_("Enter the type of WEP keys. The accepted values are: " + "0 or unknown, 1 or key, and 2 or passphrase.\n")); + } + return desc; +} + +/* 'psk' */ +static gboolean +nmc_property_wifi_set_psk (NMSetting *setting, const char *prop, const char *val, GError **error) +{ + g_return_val_if_fail (error == NULL || *error == NULL, FALSE); + + if (!nm_utils_wpa_psk_valid (val)) { + g_set_error (error, 1, 0, _("'%s' is not a valid PSK"), val); + return FALSE; + } + g_object_set (setting, prop, val, NULL); + return TRUE; +} + +#define DCB_ALL_FLAGS (NM_SETTING_DCB_FLAG_ENABLE | NM_SETTING_DCB_FLAG_ADVERTISE | NM_SETTING_DCB_FLAG_WILLING) + +/* DCB stuff */ +static gboolean +nmc_property_dcb_set_flags (NMSetting *setting, const char *prop, const char *val, GError **error) +{ + char **strv = NULL, **iter; + NMSettingDcbFlags flags = NM_SETTING_DCB_FLAG_NONE; + long int t; + + g_return_val_if_fail (error == NULL || *error == NULL, FALSE); + + /* Check for overall hex numeric value */ + if (nmc_string_to_int_base (val, 0, TRUE, 0, DCB_ALL_FLAGS, &t)) + flags = (guint) t; + else { + /* Check for individual flag numbers */ + strv = nmc_strsplit_set (val, " \t,", 0); + for (iter = strv; iter && *iter; iter++) { + if (!nmc_string_to_int_base (*iter, 0, TRUE, 0, DCB_ALL_FLAGS, &t)) + t = -1; + + if ( g_ascii_strcasecmp (*iter, "enable") == 0 + || g_ascii_strcasecmp (*iter, "enabled") == 0 + || t == NM_SETTING_DCB_FLAG_ENABLE) + flags |= NM_SETTING_DCB_FLAG_ENABLE; + else if ( g_ascii_strcasecmp (*iter, "advertise") == 0 + || t == NM_SETTING_DCB_FLAG_ADVERTISE) + flags |= NM_SETTING_DCB_FLAG_ADVERTISE; + else if ( g_ascii_strcasecmp (*iter, "willing") == 0 + || t == NM_SETTING_DCB_FLAG_WILLING) + flags |= NM_SETTING_DCB_FLAG_WILLING; + else if ( g_ascii_strcasecmp (*iter, "disable") == 0 + || g_ascii_strcasecmp (*iter, "disabled") == 0 + || t == 0) { + /* pass */ + } else { + g_set_error (error, 1, 0, _("'%s' is not a valid DCB flag"), *iter); + return FALSE; + } + } + g_strfreev (strv); + } + + /* Validate the number according to the property spec */ + if (!validate_uint (setting, prop, (guint) flags, error)) + return FALSE; + + g_object_set (setting, prop, (guint) flags, NULL); + return TRUE; +} + +static gboolean +nmc_property_dcb_set_priority (NMSetting *setting, const char *prop, const char *val, GError **error) +{ + long int priority = 0; + + g_return_val_if_fail (error == NULL || *error == NULL, FALSE); + + if (!nmc_string_to_int (val, FALSE, -1, 7, &priority)) { + g_set_error (error, 1, 0, _("'%s' is not a DCB app priority"), val); + return FALSE; + } + + /* Validate the number according to the property spec */ + if (!validate_int (setting, prop, (gint) priority, error)) + return FALSE; + + g_object_set (setting, prop, (gint) priority, NULL); + return TRUE; +} + +static gboolean +dcb_parse_uint_array (const char *val, + guint max, + guint other, + guint *out_array, + GError **error) +{ + char **items, **iter; + guint i = 0; + + g_return_val_if_fail (out_array != NULL, FALSE); + + items = g_strsplit_set (val, ",", -1); + if (g_strv_length (items) != 8) { + g_set_error_literal (error, 1, 0, _("must contain 8 comma-separated numbers")); + goto error; + } + + for (iter = items; iter && *iter; iter++) { + long int num = 0; + gboolean success; + + *iter = g_strstrip (*iter); + success = nmc_string_to_int_base (*iter, 10, TRUE, 0, other ? other : max, &num); + + /* If number is greater than 'max' it must equal 'other' */ + if (success && other && (num > max) && (num != other)) + success = FALSE; + + if (!success) { + if (other) { + g_set_error (error, 1, 0, _("'%s' not a number between 0 and %u (inclusive) or %u"), + *iter, max, other); + } else { + g_set_error (error, 1, 0, _("'%s' not a number between 0 and %u (inclusive)"), + *iter, max); + } + goto error; + } + out_array[i++] = (guint) num; + } + + return TRUE; + +error: + g_strfreev (items); + return FALSE; +} + +static void +dcb_check_feature_enabled (NMSettingDcb *s_dcb, const char *flags_prop) +{ + NMSettingDcbFlags flags = NM_SETTING_DCB_FLAG_NONE; + + g_object_get (s_dcb, flags_prop, &flags, NULL); + if (!(flags & NM_SETTING_DCB_FLAG_ENABLE)) + g_print (_("Warning: changes will have no effect until '%s' includes 1 (enabled)\n\n"), flags_prop); +} + +static gboolean +nmc_property_dcb_set_pfc (NMSetting *setting, const char *prop, const char *val, GError **error) +{ + guint i = 0; + guint nums[8] = { 0, 0, 0, 0, 0, 0, 0, 0 }; + + g_return_val_if_fail (error == NULL || *error == NULL, FALSE); + + if (!dcb_parse_uint_array (val, 1, 0, nums, error)) + return FALSE; + + for (i = 0; i < 8; i++) + nm_setting_dcb_set_priority_flow_control (NM_SETTING_DCB (setting), i, !!nums[i]); + + dcb_check_feature_enabled (NM_SETTING_DCB (setting), NM_SETTING_DCB_PRIORITY_FLOW_CONTROL_FLAGS); + return TRUE; +} + +static gboolean +nmc_property_dcb_set_pg_group_id (NMSetting *setting, const char *prop, const char *val, GError **error) +{ + guint i = 0; + guint nums[8] = { 0, 0, 0, 0, 0, 0, 0, 0 }; + + g_return_val_if_fail (error == NULL || *error == NULL, FALSE); + + if (!dcb_parse_uint_array (val, 7, 15, nums, error)) + return FALSE; + + for (i = 0; i < 8; i++) + nm_setting_dcb_set_priority_group_id (NM_SETTING_DCB (setting), i, nums[i]); + + dcb_check_feature_enabled (NM_SETTING_DCB (setting), NM_SETTING_DCB_PRIORITY_GROUP_FLAGS); + return TRUE; +} + +static gboolean +nmc_property_dcb_set_pg_group_bandwidth (NMSetting *setting, const char *prop, const char *val, GError **error) +{ + guint i = 0, sum = 0; + guint nums[8] = { 0, 0, 0, 0, 0, 0, 0, 0 }; + + g_return_val_if_fail (error == NULL || *error == NULL, FALSE); + + if (!dcb_parse_uint_array (val, 100, 0, nums, error)) + return FALSE; + + for (i = 0; i < 8; i++) + sum += nums[i]; + if (sum != 100) { + g_set_error_literal (error, 1, 0, _("bandwidth percentages must total 100%%")); + return FALSE; + } + + for (i = 0; i < 8; i++) + nm_setting_dcb_set_priority_group_bandwidth (NM_SETTING_DCB (setting), i, nums[i]); + + dcb_check_feature_enabled (NM_SETTING_DCB (setting), NM_SETTING_DCB_PRIORITY_GROUP_FLAGS); + return TRUE; +} + +static gboolean +nmc_property_dcb_set_pg_bandwidth (NMSetting *setting, const char *prop, const char *val, GError **error) +{ + guint i = 0; + guint nums[8] = { 0, 0, 0, 0, 0, 0, 0, 0 }; + + g_return_val_if_fail (error == NULL || *error == NULL, FALSE); + + if (!dcb_parse_uint_array (val, 100, 0, nums, error)) + return FALSE; + + for (i = 0; i < 8; i++) + nm_setting_dcb_set_priority_bandwidth (NM_SETTING_DCB (setting), i, nums[i]); + + dcb_check_feature_enabled (NM_SETTING_DCB (setting), NM_SETTING_DCB_PRIORITY_GROUP_FLAGS); + return TRUE; +} + +static gboolean +nmc_property_dcb_set_pg_strict (NMSetting *setting, const char *prop, const char *val, GError **error) +{ + guint i = 0; + guint nums[8] = { 0, 0, 0, 0, 0, 0, 0, 0 }; + + g_return_val_if_fail (error == NULL || *error == NULL, FALSE); + + if (!dcb_parse_uint_array (val, 1, 0, nums, error)) + return FALSE; + + for (i = 0; i < 8; i++) + nm_setting_dcb_set_priority_strict_bandwidth (NM_SETTING_DCB (setting), i, !!nums[i]); + + dcb_check_feature_enabled (NM_SETTING_DCB (setting), NM_SETTING_DCB_PRIORITY_GROUP_FLAGS); + return TRUE; +} + +static gboolean +nmc_property_dcb_set_pg_traffic_class (NMSetting *setting, const char *prop, const char *val, GError **error) +{ + guint i = 0; + guint nums[8] = { 0, 0, 0, 0, 0, 0, 0, 0 }; + + g_return_val_if_fail (error == NULL || *error == NULL, FALSE); + + if (!dcb_parse_uint_array (val, 7, 0, nums, error)) + return FALSE; + + for (i = 0; i < 8; i++) + nm_setting_dcb_set_priority_traffic_class (NM_SETTING_DCB (setting), i, nums[i]); + + dcb_check_feature_enabled (NM_SETTING_DCB (setting), NM_SETTING_DCB_PRIORITY_GROUP_FLAGS); + return TRUE; +} + +/* 'app-fcoe-mode' */ +static const char *_dcb_valid_fcoe_modes[] = { NM_SETTING_DCB_FCOE_MODE_FABRIC, + NM_SETTING_DCB_FCOE_MODE_VN2VN, + NULL }; + +static gboolean +nmc_property_dcb_set_app_fcoe_mode (NMSetting *setting, const char *prop, const char *val, GError **error) +{ + return check_and_set_string (setting, prop, val, _dcb_valid_fcoe_modes, error); +} + +DEFINE_ALLOWED_VAL_FUNC (nmc_property_dcb_allowed_app_fcoe_modes, _dcb_valid_fcoe_modes) + +/*----------------------------------------------------------------------------*/ + +static void +nmc_add_prop_funcs (char *key, + NmcPropertyGetFunc get_func, + NmcPropertySetFunc set_func, + NmcPropertyRemoveFunc remove_func, + NmcPropertyDescribeFunc describe_func, + NmcPropertyValuesFunc values_func, + NmcPropertyOut2InFunc out2in_func) +{ + NmcPropertyFuncs *item = g_malloc0 (sizeof (NmcPropertyFuncs)); + item->get_func = get_func; + item->set_func = set_func; + item->remove_func = remove_func; + item->describe_func = describe_func; + item->values_func = values_func; + item->out2in_func = out2in_func; + + g_hash_table_insert (nmc_properties, key, item); +} + +/* concatenate setting name and property name */ +#define GLUE(A,B) (g_strconcat ((NM_SETTING_##A##_SETTING_NAME),(NM_SETTING_##A##_##B), NULL)) +#define GLUE_IP(A,B) (g_strconcat ((NM_SETTING_IP##A##_CONFIG_SETTING_NAME),(NM_SETTING_IP_CONFIG_##B), NULL)) + +void +nmc_properties_init (void) +{ + if (G_LIKELY (nmc_properties)) + return; + + /* create properties hash table */ + nmc_properties = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free); + + /* Add editable properties for NM_SETTING_802_1X_SETTING_NAME */ + nmc_add_prop_funcs (GLUE (802_1X, EAP), + nmc_property_802_1X_get_eap, + nmc_property_802_1X_set_eap, + nmc_property_802_1X_remove_eap, + NULL, + NULL, + NULL); + nmc_add_prop_funcs (GLUE (802_1X, IDENTITY), + nmc_property_802_1X_get_identity, + nmc_property_set_string, + NULL, + NULL, + NULL, + NULL); + nmc_add_prop_funcs (GLUE (802_1X, ANONYMOUS_IDENTITY), + nmc_property_802_1X_get_anonymous_identity, + nmc_property_set_string, + NULL, + NULL, + NULL, + NULL); + nmc_add_prop_funcs (GLUE (802_1X, PAC_FILE), + nmc_property_802_1X_get_pac_file, + nmc_property_set_string, + NULL, + NULL, + NULL, + NULL); + nmc_add_prop_funcs (GLUE (802_1X, CA_CERT), + nmc_property_802_1X_get_ca_cert, + nmc_property_802_1X_set_ca_cert, + NULL, + NULL, + NULL, + NULL); + nmc_add_prop_funcs (GLUE (802_1X, CA_PATH), + nmc_property_802_1X_get_ca_path, + nmc_property_set_string, + NULL, + NULL, + NULL, + NULL); + nmc_add_prop_funcs (GLUE (802_1X, SUBJECT_MATCH), + nmc_property_802_1X_get_subject_match, + nmc_property_set_string, + NULL, + NULL, + NULL, + NULL); + nmc_add_prop_funcs (GLUE (802_1X, ALTSUBJECT_MATCHES), + nmc_property_802_1X_get_altsubject_matches, + nmc_property_802_1X_set_altsubject_matches, + nmc_property_802_1X_remove_altsubject_matches, + NULL, + NULL, + NULL); + nmc_add_prop_funcs (GLUE (802_1X, CLIENT_CERT), + nmc_property_802_1X_get_client_cert, + nmc_property_802_1X_set_client_cert, + NULL, + NULL, + NULL, + NULL); + nmc_add_prop_funcs (GLUE (802_1X, PHASE1_PEAPVER), + nmc_property_802_1X_get_phase1_peapver, + nmc_property_802_1X_set_phase1_peapver, + NULL, + NULL, + nmc_property_802_1X_allowed_phase1_peapver, + NULL); + nmc_add_prop_funcs (GLUE (802_1X, PHASE1_PEAPLABEL), + nmc_property_802_1X_get_phase1_peaplabel, + nmc_property_802_1X_set_phase1_peaplabel, + NULL, + NULL, + nmc_property_802_1X_allowed_phase1_peaplabel, + NULL); + nmc_add_prop_funcs (GLUE (802_1X, PHASE1_FAST_PROVISIONING), + nmc_property_802_1X_get_phase1_fast_provisioning, + nmc_property_802_1X_set_phase1_fast_provisioning, + NULL, + NULL, + nmc_property_802_1X_allowed_phase1_fast_provisioning, + NULL); + nmc_add_prop_funcs (GLUE (802_1X, PHASE2_AUTH), + nmc_property_802_1X_get_phase2_auth, + nmc_property_802_1X_set_phase2_auth, + NULL, + NULL, + nmc_property_802_1X_allowed_phase2_auth, + NULL); + nmc_add_prop_funcs (GLUE (802_1X, PHASE2_AUTHEAP), + nmc_property_802_1X_get_phase2_autheap, + nmc_property_802_1X_set_phase2_autheap, + NULL, + NULL, + nmc_property_802_1X_allowed_phase2_autheap, + NULL); + nmc_add_prop_funcs (GLUE (802_1X, PHASE2_CA_CERT), + nmc_property_802_1X_get_phase2_ca_cert, + nmc_property_802_1X_set_phase2_ca_cert, + NULL, + NULL, + NULL, + NULL); + nmc_add_prop_funcs (GLUE (802_1X, PHASE2_CA_PATH), + nmc_property_802_1X_get_phase2_ca_path, + nmc_property_set_string, + NULL, + NULL, + NULL, + NULL); + nmc_add_prop_funcs (GLUE (802_1X, PHASE2_SUBJECT_MATCH), + nmc_property_802_1X_get_phase2_subject_match, + nmc_property_set_string, + NULL, + NULL, + NULL, + NULL); + nmc_add_prop_funcs (GLUE (802_1X, PHASE2_ALTSUBJECT_MATCHES), + nmc_property_802_1X_get_phase2_altsubject_matches, + nmc_property_802_1X_set_phase2_altsubject_matches, + nmc_property_802_1X_remove_phase2_altsubject_matches, + NULL, + NULL, + NULL); + nmc_add_prop_funcs (GLUE (802_1X, PHASE2_CLIENT_CERT), + nmc_property_802_1X_get_phase2_client_cert, + nmc_property_802_1X_set_phase2_client_cert, + NULL, + NULL, + NULL, + NULL); + nmc_add_prop_funcs (GLUE (802_1X, PASSWORD), + nmc_property_802_1X_get_password, + nmc_property_set_string, + NULL, + NULL, + NULL, + NULL); + nmc_add_prop_funcs (GLUE (802_1X, PASSWORD_FLAGS), + nmc_property_802_1X_get_password_flags, + nmc_property_set_secret_flags, + NULL, + NULL, + NULL, + nmc_property_out2in_cut_paren); + nmc_add_prop_funcs (GLUE (802_1X, PASSWORD_RAW), + nmc_property_802_1X_get_password_raw, + nmc_property_802_1X_set_password_raw, + NULL, + nmc_property_802_1X_describe_password_raw, + NULL, + NULL); + nmc_add_prop_funcs (GLUE (802_1X, PASSWORD_RAW_FLAGS), + nmc_property_802_1X_get_password_raw_flags, + nmc_property_set_secret_flags, + NULL, + NULL, + NULL, + nmc_property_out2in_cut_paren); + nmc_add_prop_funcs (GLUE (802_1X, PRIVATE_KEY), + nmc_property_802_1X_get_private_key, + nmc_property_802_1X_set_private_key, + NULL, + nmc_property_802_1X_describe_private_key, + NULL, + NULL); + nmc_add_prop_funcs (GLUE (802_1X, PRIVATE_KEY_PASSWORD), + nmc_property_802_1X_get_private_key_password, + nmc_property_set_string, + NULL, + NULL, + NULL, + NULL); + nmc_add_prop_funcs (GLUE (802_1X, PRIVATE_KEY_PASSWORD_FLAGS), + nmc_property_802_1X_get_private_key_password_flags, + nmc_property_set_secret_flags, + NULL, + NULL, + NULL, + nmc_property_out2in_cut_paren); + nmc_add_prop_funcs (GLUE (802_1X, PHASE2_PRIVATE_KEY), + nmc_property_802_1X_get_phase2_private_key, + nmc_property_802_1X_set_phase2_private_key, + NULL, + nmc_property_802_1X_describe_private_key, + NULL, + NULL); + nmc_add_prop_funcs (GLUE (802_1X, PHASE2_PRIVATE_KEY_PASSWORD), + nmc_property_802_1X_get_phase2_private_key_password, + nmc_property_set_string, + NULL, + NULL, + NULL, + NULL); + nmc_add_prop_funcs (GLUE (802_1X, PHASE2_PRIVATE_KEY_PASSWORD_FLAGS), + nmc_property_802_1X_get_phase2_private_key_password_flags, + nmc_property_set_secret_flags, + NULL, + NULL, + NULL, + nmc_property_out2in_cut_paren); + nmc_add_prop_funcs (GLUE (802_1X, PIN), + nmc_property_802_1X_get_pin, + nmc_property_set_string, + NULL, + NULL, + NULL, + NULL); + nmc_add_prop_funcs (GLUE (802_1X, PIN_FLAGS), + nmc_property_802_1X_get_pin_flags, + nmc_property_set_secret_flags, + NULL, + NULL, + NULL, + nmc_property_out2in_cut_paren); + nmc_add_prop_funcs (GLUE (802_1X, SYSTEM_CA_CERTS), + nmc_property_802_1X_get_system_ca_certs, + nmc_property_set_bool, + NULL, + NULL, + NULL, + NULL); + + /* Add editable properties for NM_SETTING_ADSL_SETTING_NAME */ + nmc_add_prop_funcs (GLUE (ADSL, USERNAME), + nmc_property_adsl_get_username, + nmc_property_set_string, + NULL, + NULL, + NULL, + NULL); + nmc_add_prop_funcs (GLUE (ADSL, PASSWORD), + nmc_property_adsl_get_password, + nmc_property_set_string, + NULL, + NULL, + NULL, + NULL); + nmc_add_prop_funcs (GLUE (ADSL, PASSWORD_FLAGS), + nmc_property_adsl_get_password_flags, + nmc_property_set_secret_flags, + NULL, + NULL, + NULL, + nmc_property_out2in_cut_paren); + nmc_add_prop_funcs (GLUE (ADSL, PROTOCOL), + nmc_property_adsl_get_protocol, + nmc_property_adsl_set_protocol, + NULL, + NULL, + nmc_property_adsl_allowed_protocol, + NULL); + nmc_add_prop_funcs (GLUE (ADSL, ENCAPSULATION), + nmc_property_adsl_get_encapsulation, + nmc_property_adsl_set_encapsulation, + NULL, + NULL, + nmc_property_adsl_allowed_encapsulation, + NULL); + nmc_add_prop_funcs (GLUE (ADSL, VPI), + nmc_property_adsl_get_vpi, + nmc_property_set_uint, + NULL, + NULL, + NULL, + NULL); + nmc_add_prop_funcs (GLUE (ADSL, VCI), + nmc_property_adsl_get_vci, + nmc_property_set_uint, + NULL, + NULL, + NULL, + NULL); + + /* Add editable properties for NM_SETTING_BLUETOOTH_SETTING_NAME */ + nmc_add_prop_funcs (GLUE (BLUETOOTH, BDADDR), + nmc_property_bluetooth_get_bdaddr, + nmc_property_set_mac, + NULL, + NULL, + NULL, + NULL); + nmc_add_prop_funcs (GLUE (BLUETOOTH, TYPE), + nmc_property_bluetooth_get_type, + nmc_property_bluetooth_set_type, + NULL, + NULL, + NULL, + NULL); + + /* Add editable properties for NM_SETTING_BOND_SETTING_NAME */ + nmc_add_prop_funcs (GLUE (BOND, OPTIONS), + nmc_property_bond_get_options, + nmc_property_bond_set_options, + nmc_property_bond_remove_option_options, + nmc_property_bond_describe_options, + nmc_property_bond_allowed_options, + NULL); + + /* Add editable properties for NM_SETTING_BRIDGE_SETTING_NAME */ + nmc_add_prop_funcs (GLUE (BRIDGE, MAC_ADDRESS), + nmc_property_bridge_get_mac_address, + nmc_property_set_mac, + NULL, + NULL, + NULL, + NULL); + nmc_add_prop_funcs (GLUE (BRIDGE, STP), + nmc_property_bridge_get_stp, + nmc_property_set_bool, + NULL, + NULL, + NULL, + NULL); + nmc_add_prop_funcs (GLUE (BRIDGE, PRIORITY), + nmc_property_bridge_get_priority, + nmc_property_set_uint, + NULL, + NULL, + NULL, + NULL); + nmc_add_prop_funcs (GLUE (BRIDGE, FORWARD_DELAY), + nmc_property_bridge_get_forward_delay, + nmc_property_set_uint, + NULL, + NULL, + NULL, + NULL); + nmc_add_prop_funcs (GLUE (BRIDGE, HELLO_TIME), + nmc_property_bridge_get_hello_time, + nmc_property_set_uint, + NULL, + NULL, + NULL, + NULL); + nmc_add_prop_funcs (GLUE (BRIDGE, MAX_AGE), + nmc_property_bridge_get_max_age, + nmc_property_set_uint, + NULL, + NULL, + NULL, + NULL); + nmc_add_prop_funcs (GLUE (BRIDGE, AGEING_TIME), + nmc_property_bridge_get_ageing_time, + nmc_property_set_uint, + NULL, + NULL, + NULL, + NULL); + + /* Add editable properties for NM_SETTING_BRIDGE_PORT_SETTING_NAME */ + nmc_add_prop_funcs (GLUE (BRIDGE_PORT, PRIORITY), + nmc_property_bridge_port_get_priority, + nmc_property_set_uint, + NULL, + NULL, + NULL, + NULL); + nmc_add_prop_funcs (GLUE (BRIDGE_PORT, PATH_COST), + nmc_property_bridge_port_get_path_cost, + nmc_property_set_uint, + NULL, + NULL, + NULL, + NULL); + nmc_add_prop_funcs (GLUE (BRIDGE_PORT, HAIRPIN_MODE), + nmc_property_bridge_port_get_hairpin_mode, + nmc_property_set_bool, + NULL, + NULL, + NULL, + NULL); + + /* Add editable properties for NM_SETTING_CDMA_SETTING_NAME */ + nmc_add_prop_funcs (GLUE (CDMA, NUMBER), + nmc_property_cdma_get_number, + nmc_property_set_string, + NULL, + NULL, + NULL, + NULL); + nmc_add_prop_funcs (GLUE (CDMA, USERNAME), + nmc_property_cdma_get_username, + nmc_property_set_string, + NULL, + NULL, + NULL, + NULL); + nmc_add_prop_funcs (GLUE (CDMA, PASSWORD), + nmc_property_cdma_get_password, + nmc_property_set_string, + NULL, + NULL, + NULL, + NULL); + nmc_add_prop_funcs (GLUE (CDMA, PASSWORD_FLAGS), + nmc_property_cdma_get_password_flags, + nmc_property_set_secret_flags, + NULL, + NULL, + NULL, + nmc_property_out2in_cut_paren); + + /* Add editable properties for NM_SETTING_CONNECTION_SETTING_NAME */ + nmc_add_prop_funcs (GLUE (CONNECTION, ID), + nmc_property_connection_get_id, + nmc_property_set_string, + NULL, + NULL, + NULL, + NULL); + nmc_add_prop_funcs (GLUE (CONNECTION, UUID), + nmc_property_connection_get_uuid, + NULL, /* forbid setting/removing UUID */ + NULL, + NULL, + NULL, + NULL); + nmc_add_prop_funcs (GLUE (CONNECTION, INTERFACE_NAME), + nmc_property_connection_get_interface_name, + nmc_property_set_ifname, + NULL, + NULL, + NULL, + NULL); + nmc_add_prop_funcs (GLUE (CONNECTION, TYPE), + nmc_property_connection_get_type, + NULL, /* read-only */ + NULL, + NULL, + NULL, + NULL); + nmc_add_prop_funcs (GLUE (CONNECTION, AUTOCONNECT), + nmc_property_connection_get_autoconnect, + nmc_property_set_bool, + NULL, + NULL, + NULL, + NULL); + nmc_add_prop_funcs (GLUE (CONNECTION, AUTOCONNECT_PRIORITY), + nmc_property_connection_get_autoconnect_priority, + nmc_property_set_int, + NULL, + NULL, + NULL, + NULL); + nmc_add_prop_funcs (GLUE (CONNECTION, TIMESTAMP), + nmc_property_connection_get_timestamp, + NULL, /* read-only */ + NULL, + NULL, + NULL, + NULL); + nmc_add_prop_funcs (GLUE (CONNECTION, READ_ONLY), + nmc_property_connection_get_read_only, + NULL, /* 'read-only' is read-only :-) */ + NULL, + NULL, + NULL, + NULL); + nmc_add_prop_funcs (GLUE (CONNECTION, PERMISSIONS), + nmc_property_connection_get_permissions, + nmc_property_connection_set_permissions, + nmc_property_connection_remove_permissions, + nmc_property_connection_describe_permissions, + NULL, + NULL); + nmc_add_prop_funcs (GLUE (CONNECTION, ZONE), + nmc_property_connection_get_zone, + nmc_property_set_string, + NULL, + NULL, + NULL, + NULL); + nmc_add_prop_funcs (GLUE (CONNECTION, MASTER), + nmc_property_connection_get_master, + nmc_property_con_set_master, + NULL, + NULL, + NULL, + NULL); + nmc_add_prop_funcs (GLUE (CONNECTION, SLAVE_TYPE), + nmc_property_connection_get_slave_type, + nmc_property_con_set_slave_type, + NULL, + NULL, + nmc_property_con_allowed_slave_type, + NULL); + nmc_add_prop_funcs (GLUE (CONNECTION, SECONDARIES), + nmc_property_connection_get_secondaries, + nmc_property_connection_set_secondaries, + nmc_property_connection_remove_secondaries, + nmc_property_connection_describe_secondaries, + NULL, + NULL); + nmc_add_prop_funcs (GLUE (CONNECTION, GATEWAY_PING_TIMEOUT), + nmc_property_connection_get_gateway_ping_timeout, + nmc_property_set_uint, + NULL, + NULL, + NULL, + NULL); + + /* Add editable properties for NM_SETTING_DCB_SETTING_NAME */ + nmc_add_prop_funcs (GLUE (DCB, APP_FCOE_FLAGS), + nmc_property_dcb_get_app_fcoe_flags, + nmc_property_dcb_set_flags, + NULL, + NULL, + NULL, + NULL); + nmc_add_prop_funcs (GLUE (DCB, APP_FCOE_MODE), + nmc_property_dcb_get_app_fcoe_mode, + nmc_property_dcb_set_app_fcoe_mode, + NULL, + NULL, + nmc_property_dcb_allowed_app_fcoe_modes, + NULL); + nmc_add_prop_funcs (GLUE (DCB, APP_FCOE_PRIORITY), + nmc_property_dcb_get_app_fcoe_priority, + nmc_property_dcb_set_priority, + NULL, + NULL, + NULL, + NULL); + nmc_add_prop_funcs (GLUE (DCB, APP_ISCSI_FLAGS), + nmc_property_dcb_get_app_iscsi_flags, + nmc_property_dcb_set_flags, + NULL, + NULL, + NULL, + NULL); + nmc_add_prop_funcs (GLUE (DCB, APP_ISCSI_PRIORITY), + nmc_property_dcb_get_app_iscsi_priority, + nmc_property_dcb_set_priority, + NULL, + NULL, + NULL, + NULL); + nmc_add_prop_funcs (GLUE (DCB, APP_FIP_FLAGS), + nmc_property_dcb_get_app_fip_flags, + nmc_property_dcb_set_flags, + NULL, + NULL, + NULL, + NULL); + nmc_add_prop_funcs (GLUE (DCB, APP_FIP_PRIORITY), + nmc_property_dcb_get_app_fip_priority, + nmc_property_dcb_set_priority, + NULL, + NULL, + NULL, + NULL); + nmc_add_prop_funcs (GLUE (DCB, PRIORITY_FLOW_CONTROL_FLAGS), + nmc_property_dcb_get_pfc_flags, + nmc_property_dcb_set_flags, + NULL, + NULL, + NULL, + NULL); + nmc_add_prop_funcs (GLUE (DCB, PRIORITY_FLOW_CONTROL), + nmc_property_dcb_get_pfc, + nmc_property_dcb_set_pfc, + NULL, + NULL, + NULL, + NULL); + nmc_add_prop_funcs (GLUE (DCB, PRIORITY_GROUP_FLAGS), + nmc_property_dcb_get_pg_flags, + nmc_property_dcb_set_flags, + NULL, + NULL, + NULL, + NULL); + nmc_add_prop_funcs (GLUE (DCB, PRIORITY_GROUP_ID), + nmc_property_dcb_get_pg_group_id, + nmc_property_dcb_set_pg_group_id, + NULL, + NULL, + NULL, + NULL); + nmc_add_prop_funcs (GLUE (DCB, PRIORITY_GROUP_BANDWIDTH), + nmc_property_dcb_get_pg_group_bandwidth, + nmc_property_dcb_set_pg_group_bandwidth, + NULL, + NULL, + NULL, + NULL); + nmc_add_prop_funcs (GLUE (DCB, PRIORITY_BANDWIDTH), + nmc_property_dcb_get_pg_bandwidth, + nmc_property_dcb_set_pg_bandwidth, + NULL, + NULL, + NULL, + NULL); + nmc_add_prop_funcs (GLUE (DCB, PRIORITY_STRICT_BANDWIDTH), + nmc_property_dcb_get_pg_strict, + nmc_property_dcb_set_pg_strict, + NULL, + NULL, + NULL, + NULL); + nmc_add_prop_funcs (GLUE (DCB, PRIORITY_TRAFFIC_CLASS), + nmc_property_dcb_get_pg_traffic_class, + nmc_property_dcb_set_pg_traffic_class, + NULL, + NULL, + NULL, + NULL); + + /* Add editable properties for NM_SETTING_GSM_SETTING_NAME */ + nmc_add_prop_funcs (GLUE (GSM, NUMBER), + nmc_property_gsm_get_number, + nmc_property_set_string, + NULL, + NULL, + NULL, + NULL); + nmc_add_prop_funcs (GLUE (GSM, USERNAME), + nmc_property_gsm_get_username, + nmc_property_set_string, + NULL, + NULL, + NULL, + NULL); + nmc_add_prop_funcs (GLUE (GSM, PASSWORD), + nmc_property_gsm_get_password, + nmc_property_set_string, + NULL, + NULL, + NULL, + NULL); + nmc_add_prop_funcs (GLUE (GSM, PASSWORD_FLAGS), + nmc_property_gsm_get_password_flags, + nmc_property_set_secret_flags, + NULL, + NULL, + NULL, + nmc_property_out2in_cut_paren); + nmc_add_prop_funcs (GLUE (GSM, APN), + nmc_property_gsm_get_apn, + nmc_property_set_string, + NULL, + NULL, + NULL, + NULL); + nmc_add_prop_funcs (GLUE (GSM, NETWORK_ID), + nmc_property_gsm_get_network_id, + nmc_property_set_string, + NULL, + NULL, + NULL, + NULL); + nmc_add_prop_funcs (GLUE (GSM, PIN), + nmc_property_gsm_get_pin, + nmc_property_set_string, + NULL, + NULL, + NULL, + NULL); + nmc_add_prop_funcs (GLUE (GSM, PIN_FLAGS), + nmc_property_gsm_get_pin_flags, + nmc_property_set_secret_flags, + NULL, + NULL, + NULL, + nmc_property_out2in_cut_paren); + nmc_add_prop_funcs (GLUE (GSM, HOME_ONLY), + nmc_property_gsm_get_home_only, + nmc_property_set_bool, + NULL, + NULL, + NULL, + NULL); + + /* Add editable properties for NM_SETTING_INFINIBAND_SETTING_NAME */ + nmc_add_prop_funcs (GLUE (INFINIBAND, MAC_ADDRESS), + nmc_property_ib_get_mac_address, + nmc_property_ib_set_mac, + NULL, + NULL, + NULL, + NULL); + nmc_add_prop_funcs (GLUE (INFINIBAND, MTU), + nmc_property_ib_get_mtu, + nmc_property_set_mtu, + NULL, + NULL, + NULL, + NULL); + nmc_add_prop_funcs (GLUE (INFINIBAND, TRANSPORT_MODE), + nmc_property_ib_get_transport_mode, + nmc_property_ib_set_transport_mode, + NULL, + NULL, + nmc_property_ib_allowed_transport_mode, + NULL); + nmc_add_prop_funcs (GLUE (INFINIBAND, P_KEY), + nmc_property_ib_get_p_key, + nmc_property_ib_set_p_key, + NULL, + NULL, + NULL, + NULL); + nmc_add_prop_funcs (GLUE (INFINIBAND, PARENT), + nmc_property_ib_get_parent, + nmc_property_set_ifname, + NULL, + NULL, + NULL, + NULL); + + /* Add editable properties for NM_SETTING_IP4_CONFIG_SETTING_NAME */ + nmc_add_prop_funcs (GLUE_IP (4, METHOD), + nmc_property_ipv4_get_method, + nmc_property_ipv4_set_method, + NULL, + NULL, + nmc_property_ipv4_allowed_method, + NULL); + nmc_add_prop_funcs (GLUE_IP (4, DNS), + nmc_property_ipv4_get_dns, + nmc_property_ipv4_set_dns, + nmc_property_ipv4_remove_dns, + nmc_property_ipv4_describe_dns, + NULL, + NULL); + nmc_add_prop_funcs (GLUE_IP (4, DNS_SEARCH), + nmc_property_ipv4_get_dns_search, + nmc_property_ipv4_set_dns_search, + nmc_property_ipv4_remove_dns_search, + NULL, + NULL, + NULL); + nmc_add_prop_funcs (GLUE_IP (4, ADDRESSES), + nmc_property_ip_get_addresses, + nmc_property_ipv4_set_addresses, + nmc_property_ipv4_remove_addresses, + nmc_property_ipv4_describe_addresses, + NULL, + NULL); + nmc_add_prop_funcs (GLUE_IP (4, GATEWAY), + nmc_property_ipv4_get_gateway, + nmc_property_ipv4_set_gateway, + NULL, + NULL, + NULL, + NULL); + nmc_add_prop_funcs (GLUE_IP (4, ROUTES), + nmc_property_ipv4_get_routes, + nmc_property_ipv4_set_routes, + nmc_property_ipv4_remove_routes, + nmc_property_ipv4_describe_routes, + NULL, + nmc_property_out2in_routes); + nmc_add_prop_funcs (GLUE_IP (4, ROUTE_METRIC), + nmc_property_ipv4_get_route_metric, + nmc_property_set_int64, + NULL, + NULL, + NULL, + NULL); + nmc_add_prop_funcs (GLUE_IP (4, IGNORE_AUTO_ROUTES), + nmc_property_ipv4_get_ignore_auto_routes, + nmc_property_set_bool, + NULL, + NULL, + NULL, + NULL); + nmc_add_prop_funcs (GLUE_IP (4, IGNORE_AUTO_DNS), + nmc_property_ipv4_get_ignore_auto_dns, + nmc_property_set_bool, + NULL, + NULL, + NULL, + NULL); + nmc_add_prop_funcs (GLUE (IP4_CONFIG, DHCP_CLIENT_ID), + nmc_property_ipv4_get_dhcp_client_id, + nmc_property_set_string, + NULL, + NULL, + NULL, + NULL); + nmc_add_prop_funcs (GLUE_IP (4, DHCP_SEND_HOSTNAME), + nmc_property_ipv4_get_dhcp_send_hostname, + nmc_property_set_bool, + NULL, + NULL, + NULL, + NULL); + nmc_add_prop_funcs (GLUE_IP (4, DHCP_HOSTNAME), + nmc_property_ipv4_get_dhcp_hostname, + nmc_property_set_string, + NULL, + NULL, + NULL, + NULL); + nmc_add_prop_funcs (GLUE_IP (4, NEVER_DEFAULT), + nmc_property_ipv4_get_never_default, + nmc_property_set_bool, + NULL, + NULL, + NULL, + NULL); + nmc_add_prop_funcs (GLUE_IP (4, MAY_FAIL), + nmc_property_ipv4_get_may_fail, + nmc_property_set_bool, + NULL, + NULL, + NULL, + NULL); + + /* Add editable properties for NM_SETTING_IP6_CONFIG_SETTING_NAME */ + nmc_add_prop_funcs (GLUE_IP (6, METHOD), + nmc_property_ipv6_get_method, + nmc_property_ipv6_set_method, + NULL, + NULL, + nmc_property_ipv6_allowed_method, + NULL); + nmc_add_prop_funcs (GLUE_IP (6, DNS), + nmc_property_ipv6_get_dns, + nmc_property_ipv6_set_dns, + nmc_property_ipv6_remove_dns, + nmc_property_ipv6_describe_dns, + NULL, + NULL); + nmc_add_prop_funcs (GLUE_IP (6, DNS_SEARCH), + nmc_property_ipv6_get_dns_search, + nmc_property_ipv6_set_dns_search, + nmc_property_ipv6_remove_dns_search, + NULL, + NULL, + NULL); + nmc_add_prop_funcs (GLUE_IP (6, ADDRESSES), + nmc_property_ip_get_addresses, + nmc_property_ipv6_set_addresses, + nmc_property_ipv6_remove_addresses, + nmc_property_ipv6_describe_addresses, + NULL, + NULL); + nmc_add_prop_funcs (GLUE_IP (6, GATEWAY), + nmc_property_ipv6_get_gateway, + nmc_property_ipv6_set_gateway, + NULL, + NULL, + NULL, + NULL); + nmc_add_prop_funcs (GLUE_IP (6, ROUTES), + nmc_property_ipv6_get_routes, + nmc_property_ipv6_set_routes, + nmc_property_ipv6_remove_routes, + nmc_property_ipv6_describe_routes, + NULL, + nmc_property_out2in_routes); + nmc_add_prop_funcs (GLUE_IP (6, ROUTE_METRIC), + nmc_property_ipv6_get_route_metric, + nmc_property_set_int64, + NULL, + NULL, + NULL, + NULL); + nmc_add_prop_funcs (GLUE_IP (6, IGNORE_AUTO_ROUTES), + nmc_property_ipv6_get_ignore_auto_routes, + nmc_property_set_bool, + NULL, + NULL, + NULL, + NULL); + nmc_add_prop_funcs (GLUE_IP (6, IGNORE_AUTO_DNS), + nmc_property_ipv6_get_ignore_auto_dns, + nmc_property_set_bool, + NULL, + NULL, + NULL, + NULL); + nmc_add_prop_funcs (GLUE_IP (6, NEVER_DEFAULT), + nmc_property_ipv6_get_never_default, + nmc_property_set_bool, + NULL, + NULL, + NULL, + NULL); + nmc_add_prop_funcs (GLUE_IP (6, MAY_FAIL), + nmc_property_ipv6_get_may_fail, + nmc_property_set_bool, + NULL, + NULL, + NULL, + NULL); + nmc_add_prop_funcs (GLUE (IP6_CONFIG, IP6_PRIVACY), + nmc_property_ipv6_get_ip6_privacy, + nmc_property_ipv6_set_ip6_privacy, + NULL, + NULL, + NULL, + nmc_property_out2in_cut_paren); + nmc_add_prop_funcs (GLUE_IP (6, DHCP_SEND_HOSTNAME), + nmc_property_ipv6_get_dhcp_send_hostname, + nmc_property_set_bool, + NULL, + NULL, + NULL, + NULL); + nmc_add_prop_funcs (GLUE_IP (6, DHCP_HOSTNAME), + nmc_property_ipv6_get_dhcp_hostname, + nmc_property_set_string, + NULL, + NULL, + NULL, + NULL); + + /* Add editable properties for NM_SETTING_OLPC_MESH_SETTING_NAME */ + nmc_add_prop_funcs (GLUE (OLPC_MESH, SSID), + nmc_property_olpc_get_ssid, + nmc_property_set_ssid, + NULL, + NULL, + NULL, + NULL); + nmc_add_prop_funcs (GLUE (OLPC_MESH, CHANNEL), + nmc_property_olpc_get_channel, + nmc_property_olpc_set_channel, + NULL, + NULL, + NULL, + NULL); + nmc_add_prop_funcs (GLUE (OLPC_MESH, DHCP_ANYCAST_ADDRESS), + nmc_property_olpc_get_anycast_address, + nmc_property_set_mac, + NULL, + NULL, + NULL, + NULL); + + /* Add editable properties for NM_SETTING_PPP_SETTING_NAME */ + nmc_add_prop_funcs (GLUE (PPP, NOAUTH), + nmc_property_ppp_get_noauth, + nmc_property_set_bool, + NULL, + NULL, + NULL, + NULL); + nmc_add_prop_funcs (GLUE (PPP, REFUSE_EAP), + nmc_property_ppp_get_refuse_eap, + nmc_property_set_bool, + NULL, + NULL, + NULL, + NULL); + nmc_add_prop_funcs (GLUE (PPP, REFUSE_PAP), + nmc_property_ppp_get_refuse_pap, + nmc_property_set_bool, + NULL, + NULL, + NULL, + NULL); + nmc_add_prop_funcs (GLUE (PPP, REFUSE_CHAP), + nmc_property_ppp_get_refuse_chap, + nmc_property_set_bool, + NULL, + NULL, + NULL, + NULL); + nmc_add_prop_funcs (GLUE (PPP, REFUSE_MSCHAP), + nmc_property_ppp_get_refuse_mschap, + nmc_property_set_bool, + NULL, + NULL, + NULL, + NULL); + nmc_add_prop_funcs (GLUE (PPP, REFUSE_MSCHAPV2), + nmc_property_ppp_get_refuse_mschapv2, + nmc_property_set_bool, + NULL, + NULL, + NULL, + NULL); + nmc_add_prop_funcs (GLUE (PPP, NOBSDCOMP), + nmc_property_ppp_get_nobsdcomp, + nmc_property_set_bool, + NULL, + NULL, + NULL, + NULL); + nmc_add_prop_funcs (GLUE (PPP, NODEFLATE), + nmc_property_ppp_get_nodeflate, + nmc_property_set_bool, + NULL, + NULL, + NULL, + NULL); + nmc_add_prop_funcs (GLUE (PPP, NO_VJ_COMP), + nmc_property_ppp_get_no_vj_comp, + nmc_property_set_bool, + NULL, + NULL, + NULL, + NULL); + nmc_add_prop_funcs (GLUE (PPP, REQUIRE_MPPE), + nmc_property_ppp_get_require_mppe, + nmc_property_set_bool, + NULL, + NULL, + NULL, + NULL); + nmc_add_prop_funcs (GLUE (PPP, REQUIRE_MPPE_128), + nmc_property_ppp_get_require_mppe_128, + nmc_property_set_bool, + NULL, + NULL, + NULL, + NULL); + nmc_add_prop_funcs (GLUE (PPP, MPPE_STATEFUL), + nmc_property_ppp_get_mppe_stateful, + nmc_property_set_bool, + NULL, + NULL, + NULL, + NULL); + nmc_add_prop_funcs (GLUE (PPP, CRTSCTS), + nmc_property_ppp_get_crtscts, + nmc_property_set_bool, + NULL, + NULL, + NULL, + NULL); + nmc_add_prop_funcs (GLUE (PPP, BAUD), + nmc_property_ppp_get_baud, + nmc_property_set_uint, + NULL, + NULL, + NULL, + NULL); + nmc_add_prop_funcs (GLUE (PPP, MRU), + nmc_property_ppp_get_mru, + nmc_property_set_uint, + NULL, + NULL, + NULL, + NULL); + nmc_add_prop_funcs (GLUE (PPP, MTU), + nmc_property_ppp_get_mtu, + nmc_property_set_mtu, + NULL, + NULL, + NULL, + NULL); + nmc_add_prop_funcs (GLUE (PPP, LCP_ECHO_FAILURE), + nmc_property_ppp_get_lcp_echo_failure, + nmc_property_set_uint, + NULL, + NULL, + NULL, + NULL); + nmc_add_prop_funcs (GLUE (PPP, LCP_ECHO_INTERVAL), + nmc_property_ppp_get_lcp_echo_interval, + nmc_property_set_uint, + NULL, + NULL, + NULL, + NULL); + + /* Add editable properties for NM_SETTING_PPPOE_SETTING_NAME */ + nmc_add_prop_funcs (GLUE (PPPOE, SERVICE), + nmc_property_pppoe_get_service, + nmc_property_set_string, + NULL, + NULL, + NULL, + NULL); + nmc_add_prop_funcs (GLUE (PPPOE, USERNAME), + nmc_property_pppoe_get_username, + nmc_property_set_string, + NULL, + NULL, + NULL, + NULL); + nmc_add_prop_funcs (GLUE (PPPOE, PASSWORD), + nmc_property_pppoe_get_password, + nmc_property_set_string, + NULL, + NULL, + NULL, + NULL); + nmc_add_prop_funcs (GLUE (PPPOE, PASSWORD_FLAGS), + nmc_property_pppoe_get_password_flags, + nmc_property_set_secret_flags, + NULL, + NULL, + NULL, + nmc_property_out2in_cut_paren); + + /* Add editable properties for NM_SETTING_SERIAL_SETTING_NAME */ + nmc_add_prop_funcs (GLUE (SERIAL, BAUD), + nmc_property_serial_get_baud, + nmc_property_set_uint, + NULL, + NULL, + NULL, + NULL); + nmc_add_prop_funcs (GLUE (SERIAL, BITS), + nmc_property_serial_get_bits, + nmc_property_set_uint, + NULL, + NULL, + NULL, + NULL); + nmc_add_prop_funcs (GLUE (SERIAL, PARITY), + nmc_property_serial_get_parity, + nmc_property_serial_set_parity, + NULL, + NULL, + NULL, + NULL); + nmc_add_prop_funcs (GLUE (SERIAL, STOPBITS), + nmc_property_serial_get_stopbits, + nmc_property_set_uint, + NULL, + NULL, + NULL, + NULL); + nmc_add_prop_funcs (GLUE (SERIAL, SEND_DELAY), + nmc_property_serial_get_send_delay, + nmc_property_set_uint, + NULL, + NULL, + NULL, + NULL); + + /* Add editable properties for NM_SETTING_TEAM_SETTING_NAME */ + nmc_add_prop_funcs (GLUE (TEAM, CONFIG), + nmc_property_team_get_config, + nmc_property_team_set_config, + NULL, + nmc_property_team_describe_config, + NULL, + NULL); + + /* Add editable properties for NM_SETTING_TEAM_PORT_SETTING_NAME */ + nmc_add_prop_funcs (GLUE (TEAM_PORT, CONFIG), + nmc_property_team_port_get_config, + nmc_property_team_set_config, + NULL, + nmc_property_team_describe_config, + NULL, + NULL); + + /* Add editable properties for NM_SETTING_VLAN_SETTING_NAME */ + nmc_add_prop_funcs (GLUE (VLAN, PARENT), + nmc_property_vlan_get_parent, + nmc_property_set_string, + NULL, + NULL, + NULL, + NULL); + nmc_add_prop_funcs (GLUE (VLAN, ID), + nmc_property_vlan_get_id, + nmc_property_set_uint, + NULL, + NULL, + NULL, + NULL); + nmc_add_prop_funcs (GLUE (VLAN, FLAGS), + nmc_property_vlan_get_flags, + nmc_property_set_uint, + NULL, + NULL, + NULL, + NULL); + nmc_add_prop_funcs (GLUE (VLAN, INGRESS_PRIORITY_MAP), + nmc_property_vlan_get_ingress_priority_map, + nmc_property_vlan_set_ingress_priority_map, + nmc_property_vlan_remove_ingress_priority_map, + NULL, + NULL, + NULL); + nmc_add_prop_funcs (GLUE (VLAN, EGRESS_PRIORITY_MAP), + nmc_property_vlan_get_egress_priority_map, + nmc_property_vlan_set_egress_priority_map, + nmc_property_vlan_remove_egress_priority_map, + NULL, + NULL, + NULL); + + /* Add editable properties for NM_SETTING_VPN_SETTING_NAME */ + nmc_add_prop_funcs (GLUE (VPN, SERVICE_TYPE), + nmc_property_vpn_get_service_type, + nmc_property_set_string, + NULL, + NULL, + NULL, + NULL); + nmc_add_prop_funcs (GLUE (VPN, USER_NAME), + nmc_property_vpn_get_user_name, + nmc_property_set_string, + NULL, + NULL, + NULL, + NULL); + nmc_add_prop_funcs (GLUE (VPN, DATA), + nmc_property_vpn_get_data, + nmc_property_vpn_set_data, + nmc_property_vpn_remove_option_data, + NULL, + NULL, + NULL); + nmc_add_prop_funcs (GLUE (VPN, SECRETS), + nmc_property_vpn_get_secrets, + nmc_property_vpn_set_secrets, + nmc_property_vpn_remove_option_secret, + NULL, + NULL, + NULL); + nmc_add_prop_funcs (GLUE (VPN, PERSISTENT), + nmc_property_vpn_get_persistent, + nmc_property_set_bool, + NULL, + NULL, + NULL, + NULL); + + /* Add editable properties for NM_SETTING_WIMAX_SETTING_NAME */ + nmc_add_prop_funcs (GLUE (WIMAX, NETWORK_NAME), + nmc_property_wimax_get_network_name, + nmc_property_set_string, + NULL, + NULL, + NULL, + NULL); + nmc_add_prop_funcs (GLUE (WIMAX, MAC_ADDRESS), + nmc_property_wimax_get_mac_address, + nmc_property_set_mac, + NULL, + NULL, + NULL, + NULL); + + /* Add editable properties for NM_SETTING_WIRED_SETTING_NAME */ + nmc_add_prop_funcs (GLUE (WIRED, PORT), + nmc_property_wired_get_port, + NULL, /*nmc_property_wired_set_port,*/ + NULL, + NULL, + NULL, /*nmc_property_wired_allowed_port,*/ + NULL); + nmc_add_prop_funcs (GLUE (WIRED, SPEED), + nmc_property_wired_get_speed, + NULL, + NULL, + NULL, + NULL, + NULL); + nmc_add_prop_funcs (GLUE (WIRED, DUPLEX), + nmc_property_wired_get_duplex, + NULL, /*nmc_property_wired_set_duplex,*/ + NULL, + NULL, + NULL, + NULL); /*nmc_property_wired_allowed_duplex);*/ + nmc_add_prop_funcs (GLUE (WIRED, AUTO_NEGOTIATE), + nmc_property_wired_get_auto_negotiate, + NULL, + NULL, + NULL, + NULL, + NULL); + nmc_add_prop_funcs (GLUE (WIRED, MAC_ADDRESS), + nmc_property_wired_get_mac_address, + nmc_property_set_mac, + NULL, + NULL, + NULL, + NULL); + nmc_add_prop_funcs (GLUE (WIRED, CLONED_MAC_ADDRESS), + nmc_property_wired_get_cloned_mac_address, + nmc_property_set_mac, + NULL, + NULL, + NULL, + NULL); + nmc_add_prop_funcs (GLUE (WIRED, MAC_ADDRESS_BLACKLIST), + nmc_property_wired_get_mac_address_blacklist, + nmc_property_wired_set_mac_address_blacklist, + nmc_property_wired_remove_mac_address_blacklist, + NULL, + NULL, + NULL); + nmc_add_prop_funcs (GLUE (WIRED, MTU), + nmc_property_wired_get_mtu, + nmc_property_set_mtu, + NULL, + NULL, + NULL, + NULL); + nmc_add_prop_funcs (GLUE (WIRED, S390_SUBCHANNELS), + nmc_property_wired_get_s390_subchannels, + nmc_property_wired_set_s390_subchannels, + NULL, + nmc_property_wired_describe_s390_subchannels, + NULL, + NULL); + nmc_add_prop_funcs (GLUE (WIRED, S390_NETTYPE), + nmc_property_wired_get_s390_nettype, + nmc_property_wired_set_s390_nettype, + NULL, + NULL, + nmc_property_wired_allowed_s390_nettype, + NULL); + nmc_add_prop_funcs (GLUE (WIRED, S390_OPTIONS), + nmc_property_wired_get_s390_options, + nmc_property_wired_set_s390_options, + nmc_property_wired_remove_option_s390_options, + nmc_property_wired_describe_s390_options, + nmc_property_wired_allowed_s390_options, + NULL); + + /* Add editable properties for NM_SETTING_WIRELESS_SETTING_NAME */ + nmc_add_prop_funcs (GLUE (WIRELESS, SSID), + nmc_property_wireless_get_ssid, + nmc_property_set_ssid, + NULL, + NULL, + NULL, + NULL); + nmc_add_prop_funcs (GLUE (WIRELESS, MODE), + nmc_property_wireless_get_mode, + nmc_property_wifi_set_mode, + NULL, + NULL, + nmc_property_wifi_allowed_mode, + NULL); + nmc_add_prop_funcs (GLUE (WIRELESS, BAND), + nmc_property_wireless_get_band, + nmc_property_wifi_set_band, + NULL, + NULL, + nmc_property_wifi_allowed_band, + NULL); + nmc_add_prop_funcs (GLUE (WIRELESS, CHANNEL), + nmc_property_wireless_get_channel, + nmc_property_wifi_set_channel, + NULL, + NULL, + NULL, + NULL); + nmc_add_prop_funcs (GLUE (WIRELESS, BSSID), + nmc_property_wireless_get_bssid, + nmc_property_set_mac, + NULL, + NULL, + NULL, + NULL); + /* + * Do not allow setting 'rate' and 'tx-power'. They are not implemented in + * NM core, nor in ifcfg-rh plugin (thus not preserved over re-reading). + */ + nmc_add_prop_funcs (GLUE (WIRELESS, RATE), + nmc_property_wireless_get_rate, + NULL, /* editing rate disabled */ + NULL, + NULL, + NULL, + NULL); + nmc_add_prop_funcs (GLUE (WIRELESS, TX_POWER), + nmc_property_wireless_get_tx_power, + NULL, /* editing tx-power disabled */ + NULL, + NULL, + NULL, + NULL); + nmc_add_prop_funcs (GLUE (WIRELESS, MAC_ADDRESS), + nmc_property_wireless_get_mac_address, + nmc_property_set_mac, + NULL, + NULL, + NULL, + NULL); + nmc_add_prop_funcs (GLUE (WIRELESS, CLONED_MAC_ADDRESS), + nmc_property_wireless_get_cloned_mac_address, + nmc_property_set_mac, + NULL, + NULL, + NULL, + NULL); + nmc_add_prop_funcs (GLUE (WIRELESS, MAC_ADDRESS_BLACKLIST), + nmc_property_wireless_get_mac_address_blacklist, + nmc_property_wireless_set_mac_address_blacklist, + nmc_property_wireless_remove_mac_address_blacklist, + NULL, + NULL, + NULL); + nmc_add_prop_funcs (GLUE (WIRELESS, SEEN_BSSIDS), + nmc_property_wireless_get_seen_bssids, + NULL, /* read-only */ + NULL, + NULL, + NULL, + NULL); + nmc_add_prop_funcs (GLUE (WIRELESS, MTU), + nmc_property_wireless_get_mtu, + nmc_property_set_mtu, + NULL, + NULL, + NULL, + NULL); + nmc_add_prop_funcs (GLUE (WIRELESS, HIDDEN), + nmc_property_wireless_get_hidden, + nmc_property_set_bool, + NULL, + NULL, + NULL, + NULL); + + /* Add editable properties for NM_SETTING_WIRELESS_SECURITY_SETTING_NAME */ + nmc_add_prop_funcs (GLUE (WIRELESS_SECURITY, KEY_MGMT), + nmc_property_wifi_sec_get_key_mgmt, + nmc_property_wifi_sec_set_key_mgmt, + NULL, + NULL, + nmc_property_wifi_sec_allowed_key_mgmt, + NULL); + nmc_add_prop_funcs (GLUE (WIRELESS_SECURITY, WEP_TX_KEYIDX), + nmc_property_wifi_sec_get_wep_tx_keyidx, + nmc_property_set_uint, + NULL, + NULL, + NULL, + NULL); + nmc_add_prop_funcs (GLUE (WIRELESS_SECURITY, AUTH_ALG), + nmc_property_wifi_sec_get_auth_alg, + nmc_property_wifi_sec_set_auth_alg, + NULL, + NULL, + nmc_property_wifi_sec_allowed_auth_alg, + NULL); + nmc_add_prop_funcs (GLUE (WIRELESS_SECURITY, PROTO), + nmc_property_wifi_sec_get_proto, + nmc_property_wifi_sec_set_proto, + nmc_property_wifi_sec_remove_proto, + NULL, + nmc_property_wifi_sec_allowed_proto, + NULL); + nmc_add_prop_funcs (GLUE (WIRELESS_SECURITY, PAIRWISE), + nmc_property_wifi_sec_get_pairwise, + nmc_property_wifi_sec_set_pairwise, + nmc_property_wifi_sec_remove_pairwise, + NULL, + nmc_property_wifi_sec_allowed_pairwise, + NULL); + nmc_add_prop_funcs (GLUE (WIRELESS_SECURITY, GROUP), + nmc_property_wifi_sec_get_group, + nmc_property_wifi_sec_set_group, + nmc_property_wifi_sec_remove_group, + NULL, + nmc_property_wifi_sec_allowed_group, + NULL); + nmc_add_prop_funcs (GLUE (WIRELESS_SECURITY, LEAP_USERNAME), + nmc_property_wifi_sec_get_leap_username, + nmc_property_set_string, + NULL, + NULL, + NULL, + NULL); + nmc_add_prop_funcs (GLUE (WIRELESS_SECURITY, WEP_KEY0), + nmc_property_wifi_sec_get_wep_key0, + nmc_property_wifi_set_wep_key, + NULL, + NULL, + NULL, + NULL); + nmc_add_prop_funcs (GLUE (WIRELESS_SECURITY, WEP_KEY1), + nmc_property_wifi_sec_get_wep_key1, + nmc_property_wifi_set_wep_key, + NULL, + NULL, + NULL, + NULL); + nmc_add_prop_funcs (GLUE (WIRELESS_SECURITY, WEP_KEY2), + nmc_property_wifi_sec_get_wep_key2, + nmc_property_wifi_set_wep_key, + NULL, + NULL, + NULL, + NULL); + nmc_add_prop_funcs (GLUE (WIRELESS_SECURITY, WEP_KEY3), + nmc_property_wifi_sec_get_wep_key3, + nmc_property_wifi_set_wep_key, + NULL, + NULL, + NULL, + NULL); + nmc_add_prop_funcs (GLUE (WIRELESS_SECURITY, WEP_KEY_FLAGS), + nmc_property_wifi_sec_get_wep_key_flags, + nmc_property_set_secret_flags, + NULL, + NULL, + NULL, + nmc_property_out2in_cut_paren); + nmc_add_prop_funcs (GLUE (WIRELESS_SECURITY, WEP_KEY_TYPE), + nmc_property_wifi_sec_get_wep_key_type, + nmc_property_wifi_set_wep_key_type, + NULL, + nmc_property_wifi_describe_wep_key_type, + NULL, + NULL); + nmc_add_prop_funcs (GLUE (WIRELESS_SECURITY, PSK), + nmc_property_wifi_sec_get_psk, + nmc_property_wifi_set_psk, + NULL, + NULL, + NULL, + NULL); + nmc_add_prop_funcs (GLUE (WIRELESS_SECURITY, PSK_FLAGS), + nmc_property_wifi_sec_get_psk_flags, + nmc_property_set_secret_flags, + NULL, + NULL, + NULL, + nmc_property_out2in_cut_paren); + nmc_add_prop_funcs (GLUE (WIRELESS_SECURITY, LEAP_PASSWORD), + nmc_property_wifi_sec_get_leap_password, + nmc_property_set_string, + NULL, + NULL, + NULL, + NULL); + nmc_add_prop_funcs (GLUE (WIRELESS_SECURITY, LEAP_PASSWORD_FLAGS), + nmc_property_wifi_sec_get_leap_password_flags, + nmc_property_set_secret_flags, + NULL, + NULL, + NULL, + nmc_property_out2in_cut_paren); +} + +void +nmc_properties_cleanup () +{ + if (nmc_properties) + g_hash_table_destroy (nmc_properties); +} + +static const NmcPropertyFuncs * +nmc_properties_find (const char *s_name, const char *p_name) +{ + NmcPropertyFuncs *item = NULL; + char *key; + + /* FIXME: maybe it's better to init it globally */ + nmc_properties_init (); + + key = g_strdup_printf ("%s%s", s_name, p_name); + item = (NmcPropertyFuncs *) g_hash_table_lookup (nmc_properties, key); + g_free (key); + + return item; +} + +static char * +get_property_val (NMSetting *setting, const char *prop, gboolean convert, GError **error) +{ + const NmcPropertyFuncs *item; + + g_return_val_if_fail (NM_IS_SETTING (setting), FALSE); + g_return_val_if_fail (error == NULL || *error == NULL, FALSE); + + item = nmc_properties_find (nm_setting_get_name (setting), prop); + if (item && item->get_func) { + char *prop_val = item->get_func (setting); + if (convert && item->out2in_func) { + char *converted = item->out2in_func (prop_val); + g_free (prop_val); + return converted; + } else + return prop_val; + } + + g_set_error_literal (error, 1, 0, _("don't know how to get the property value")); + return NULL; +} + +/* + * Generic function for getting property value. + * + * Gets property value as a string by calling specialized functions. + * + * Returns: current property value. The caller must free the returned string. + */ +char * +nmc_setting_get_property (NMSetting *setting, const char *prop, GError **error) +{ + return get_property_val (setting, prop, FALSE, error); +} + +/* + * The same as nmc_setting_get_property(), but in addition converts + * usual output format into a simpler one, used as input in the editor. + */ +char * +nmc_setting_get_property_out2in (NMSetting *setting, const char *prop, GError **error) +{ + return get_property_val (setting, prop, TRUE, error); +} + +/* + * Generic function for setting property value. + * + * Sets property=val in setting by calling specialized functions. + * If val is NULL then default property value is set. + * + * Returns: TRUE on success; FALSE on failure and sets error + */ +gboolean +nmc_setting_set_property (NMSetting *setting, const char *prop, const char *val, GError **error) +{ + const NmcPropertyFuncs *item; + + g_return_val_if_fail (NM_IS_SETTING (setting), FALSE); + g_return_val_if_fail (error == NULL || *error == NULL, FALSE); + + item = nmc_properties_find (nm_setting_get_name (setting), prop); + if (item && item->set_func) { + if (!val) { + /* No value argument sets default value */ + nmc_property_set_default_value (setting, prop); + return TRUE; + } + return item->set_func (setting, prop, val, error); + } + + g_set_error_literal (error, 1, 0, _("the property can't be changed")); + return FALSE; +} + +void +nmc_property_set_default_value (NMSetting *setting, const char *prop) +{ + GValue value = G_VALUE_INIT; + GParamSpec *param_spec; + + param_spec = g_object_class_find_property (G_OBJECT_GET_CLASS (G_OBJECT (setting)), prop); + if (param_spec) { + g_value_init (&value, G_PARAM_SPEC_VALUE_TYPE (param_spec)); + g_param_value_set_default (param_spec, &value); + g_object_set_property (G_OBJECT (setting), prop, &value); + } +} + +/* + * Generic function for reseting (single value) properties. + * + * The function resets the property value to the default one. It respects + * nmcli restrictions for changing properties. So if 'set_func' is NULL, + * reseting the value is denied. + * + * Returns: TRUE on success; FALSE on failure and sets error + */ +gboolean +nmc_setting_reset_property (NMSetting *setting, const char *prop, GError **error) +{ + const NmcPropertyFuncs *item; + + g_return_val_if_fail (NM_IS_SETTING (setting), FALSE); + g_return_val_if_fail (error == NULL || *error == NULL, FALSE); + + item = nmc_properties_find (nm_setting_get_name (setting), prop); + if (item && item->set_func) { + nmc_property_set_default_value (setting, prop); + return TRUE; + } + g_set_error_literal (error, 1, 0, _("the property can't be changed")); + return FALSE; +} + +/* + * Generic function for removing items for collection-type properties. + * + * If 'option' is not NULL, it tries to remove it, otherwise 'idx' is used. + * For single-value properties (not having specialized remove function) this + * function does nothing and just returns TRUE. + * + * Returns: TRUE on success; FALSE on failure and sets error + */ +gboolean +nmc_setting_remove_property_option (NMSetting *setting, + const char *prop, + const char *option, + guint32 idx, + GError **error) +{ + const NmcPropertyFuncs *item; + + g_return_val_if_fail (NM_IS_SETTING (setting), FALSE); + g_return_val_if_fail (error == NULL || *error == NULL, FALSE); + + item = nmc_properties_find (nm_setting_get_name (setting), prop); + if (item && item->remove_func) + return item->remove_func (setting, prop, option, idx, error); + + return TRUE; +} + +/* + * Get valid property names for a setting. + * + * Returns: string array with the properties or NULL on failure. + * The returned value should be freed with g_strfreev() + */ +char ** +nmc_setting_get_valid_properties (NMSetting *setting) +{ + char **valid_props = NULL; + GParamSpec **props, **iter; + guint num; + int i; + + /* Iterate through properties */ + i = 0; + props = g_object_class_list_properties (G_OBJECT_GET_CLASS (G_OBJECT (setting)), &num); + valid_props = g_malloc0 (sizeof (char*) * (num + 1)); + for (iter = props; iter && *iter; iter++) { + const char *key_name = g_param_spec_get_name (*iter); + + /* Add all properties except for "name" that is non-editable */ + if (g_strcmp0 (key_name, "name") != 0) + valid_props[i++] = g_strdup (key_name); + } + valid_props[i] = NULL; + + return valid_props; +} + +/* + * Return allowed values for 'prop' as a string. + */ +const char * +nmc_setting_get_property_allowed_values (NMSetting *setting, const char *prop) +{ + + const NmcPropertyFuncs *item; + + g_return_val_if_fail (NM_IS_SETTING (setting), FALSE); + + item = nmc_properties_find (nm_setting_get_name (setting), prop); + if (item && item->values_func) + return item->values_func (setting, prop); + + return NULL; +} + +#if defined (BUILD_SETTING_DOCS) || defined (HAVE_SETTING_DOCS) +#include "settings-docs.c" +#else +#define nmc_setting_get_property_doc(setting, prop) _("(not available)") +#endif + +/* + * Create a description string for a property. + * + * It returns a description got from property documentation, concatenated with + * nmcli specific description (if it exists). + * + * Returns: property description or NULL on failure. The caller must free the string. + */ +char * +nmc_setting_get_property_desc (NMSetting *setting, const char *prop) +{ + const NmcPropertyFuncs *item; + const char *setting_desc = NULL; + const char *setting_desc_title = ""; + const char *nmcli_desc = NULL; + const char *nmcli_desc_title = ""; + const char *nmcli_nl = ""; + + g_return_val_if_fail (NM_IS_SETTING (setting), FALSE); + + setting_desc = nmc_setting_get_property_doc (setting, prop); + if (setting_desc) + setting_desc_title = _("[NM property description]"); + + item = nmc_properties_find (nm_setting_get_name (setting), prop); + if (item && item->describe_func) { + nmcli_desc = item->describe_func (setting, prop); + nmcli_desc_title = _("[nmcli specific description]"); + nmcli_nl = "\n"; + } + + return g_strdup_printf ("%s\n%s\n%s%s%s%s", + setting_desc_title, + setting_desc ? setting_desc : "", + nmcli_nl, nmcli_desc_title, nmcli_nl, + nmcli_desc ? nmcli_desc : ""); +} + +/* + * Gets setting:prop property value and returns it in 'value'. + * Caller is responsible for freeing the GValue resources using g_value_unset() + */ +gboolean +nmc_property_get_gvalue (NMSetting *setting, const char *prop, GValue *value) +{ + GParamSpec *param_spec; + + param_spec = g_object_class_find_property (G_OBJECT_GET_CLASS (G_OBJECT (setting)), prop); + if (param_spec) { + memset (value, 0, sizeof (GValue)); + g_value_init (value, G_PARAM_SPEC_VALUE_TYPE (param_spec)); + g_object_get_property (G_OBJECT (setting), prop, value); + return TRUE; + } + return FALSE; +} + +/* + * Sets setting:prop property value from 'value'. + */ +gboolean +nmc_property_set_gvalue (NMSetting *setting, const char *prop, GValue *value) +{ + GParamSpec *param_spec; + + param_spec = g_object_class_find_property (G_OBJECT_GET_CLASS (G_OBJECT (setting)), prop); + if (param_spec && G_VALUE_TYPE (value) == G_PARAM_SPEC_VALUE_TYPE (param_spec)) { + g_object_set_property (G_OBJECT (setting), prop, value); + return TRUE; + } + return FALSE; +} + +/*----------------------------------------------------------------------------*/ + +#define GET_SECRET(show, setting, func) \ + (show ? func (setting) : g_strdup (_("<hidden>"))) + +static gboolean +setting_connection_details (NMSetting *setting, NmCli *nmc, const char *one_prop, gboolean secrets) +{ + NMSettingConnection *s_con = NM_SETTING_CONNECTION (setting); + NmcOutputField *tmpl, *arr; + size_t tmpl_len; + + g_return_val_if_fail (NM_IS_SETTING_CONNECTION (s_con), FALSE); + + tmpl = nmc_fields_setting_connection; + tmpl_len = sizeof (nmc_fields_setting_connection); + nmc->print_fields.indices = parse_output_fields (one_prop ? one_prop : NMC_FIELDS_SETTING_CONNECTION_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); + + arr = nmc_dup_fields_array (tmpl, tmpl_len, NMC_OF_FLAG_SECTION_PREFIX); + set_val_str (arr, 0, g_strdup (nm_setting_get_name (setting))); + set_val_str (arr, 1, nmc_property_connection_get_id (setting)); + set_val_str (arr, 2, nmc_property_connection_get_uuid (setting)); + set_val_str (arr, 3, nmc_property_connection_get_interface_name (setting)); + set_val_str (arr, 4, nmc_property_connection_get_type (setting)); + set_val_str (arr, 5, nmc_property_connection_get_autoconnect (setting)); + set_val_str (arr, 6, nmc_property_connection_get_autoconnect_priority (setting)); + set_val_str (arr, 7, nmc_property_connection_get_timestamp (setting)); + set_val_str (arr, 8, nmc_property_connection_get_read_only (setting)); + set_val_str (arr, 9, nmc_property_connection_get_permissions (setting)); + set_val_str (arr, 10, nmc_property_connection_get_zone (setting)); + set_val_str (arr, 11, nmc_property_connection_get_master (setting)); + set_val_str (arr, 12, nmc_property_connection_get_slave_type (setting)); + set_val_str (arr, 13, nmc_property_connection_get_secondaries (setting)); + set_val_str (arr, 14, nmc_property_connection_get_gateway_ping_timeout (setting)); + g_ptr_array_add (nmc->output_data, arr); + + print_data (nmc); /* Print all data */ + + return TRUE; +} + +static gboolean +setting_wired_details (NMSetting *setting, NmCli *nmc, const char *one_prop, gboolean secrets) +{ + NMSettingWired *s_wired = NM_SETTING_WIRED (setting); + NmcOutputField *tmpl, *arr; + size_t tmpl_len; + + g_return_val_if_fail (NM_IS_SETTING_WIRED (s_wired), FALSE); + + tmpl = nmc_fields_setting_wired; + tmpl_len = sizeof (nmc_fields_setting_wired); + nmc->print_fields.indices = parse_output_fields (one_prop ? one_prop : NMC_FIELDS_SETTING_WIRED_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); + + arr = nmc_dup_fields_array (tmpl, tmpl_len, NMC_OF_FLAG_SECTION_PREFIX); + set_val_str (arr, 0, g_strdup (nm_setting_get_name (setting))); + set_val_str (arr, 1, nmc_property_wired_get_port (setting)); + set_val_str (arr, 2, nmc_property_wired_get_speed (setting)); + set_val_str (arr, 3, nmc_property_wired_get_duplex (setting)); + set_val_str (arr, 4, nmc_property_wired_get_auto_negotiate (setting)); + set_val_str (arr, 5, nmc_property_wired_get_mac_address (setting)); + set_val_str (arr, 6, nmc_property_wired_get_cloned_mac_address (setting)); + set_val_str (arr, 7, nmc_property_wired_get_mac_address_blacklist (setting)); + set_val_str (arr, 8, nmc_property_wired_get_mtu (setting)); + set_val_str (arr, 9, nmc_property_wired_get_s390_subchannels (setting)); + set_val_str (arr, 10, nmc_property_wired_get_s390_nettype (setting)); + set_val_str (arr, 11, nmc_property_wired_get_s390_options (setting)); + g_ptr_array_add (nmc->output_data, arr); + + print_data (nmc); /* Print all data */ + + return TRUE; +} + +static gboolean +setting_802_1X_details (NMSetting *setting, NmCli *nmc, const char *one_prop, gboolean secrets) +{ + NMSetting8021x *s_8021x = NM_SETTING_802_1X (setting); + NmcOutputField *tmpl, *arr; + size_t tmpl_len; + + g_return_val_if_fail (NM_IS_SETTING_802_1X (s_8021x), FALSE); + + tmpl = nmc_fields_setting_8021X; + tmpl_len = sizeof (nmc_fields_setting_8021X); + nmc->print_fields.indices = parse_output_fields (one_prop ? one_prop : NMC_FIELDS_SETTING_802_1X_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); + + arr = nmc_dup_fields_array (tmpl, tmpl_len, NMC_OF_FLAG_SECTION_PREFIX); + set_val_str (arr, 0, g_strdup (nm_setting_get_name (setting))); + set_val_str (arr, 1, nmc_property_802_1X_get_eap (setting)); + set_val_str (arr, 2, nmc_property_802_1X_get_identity (setting)); + set_val_str (arr, 3, nmc_property_802_1X_get_anonymous_identity (setting)); + set_val_str (arr, 4, nmc_property_802_1X_get_pac_file (setting)); + set_val_str (arr, 5, nmc_property_802_1X_get_ca_cert (setting)); + set_val_str (arr, 6, nmc_property_802_1X_get_ca_path (setting)); + set_val_str (arr, 7, nmc_property_802_1X_get_subject_match (setting)); + set_val_str (arr, 8, nmc_property_802_1X_get_altsubject_matches (setting)); + set_val_str (arr, 9, nmc_property_802_1X_get_client_cert (setting)); + set_val_str (arr, 10, nmc_property_802_1X_get_phase1_peapver (setting)); + set_val_str (arr, 11, nmc_property_802_1X_get_phase1_peaplabel (setting)); + set_val_str (arr, 12, nmc_property_802_1X_get_phase1_fast_provisioning (setting)); + set_val_str (arr, 13, nmc_property_802_1X_get_phase2_auth (setting)); + set_val_str (arr, 14, nmc_property_802_1X_get_phase2_autheap (setting)); + set_val_str (arr, 15, nmc_property_802_1X_get_phase2_ca_cert (setting)); + set_val_str (arr, 16, nmc_property_802_1X_get_phase2_ca_path (setting)); + set_val_str (arr, 17, nmc_property_802_1X_get_phase2_subject_match (setting)); + set_val_str (arr, 18, nmc_property_802_1X_get_phase2_altsubject_matches (setting)); + set_val_str (arr, 19, nmc_property_802_1X_get_phase2_client_cert (setting)); + set_val_str (arr, 20, GET_SECRET (secrets, setting, nmc_property_802_1X_get_password)); + set_val_str (arr, 21, nmc_property_802_1X_get_password_flags (setting)); + set_val_str (arr, 22, GET_SECRET (secrets, setting, nmc_property_802_1X_get_password_raw)); + set_val_str (arr, 23, nmc_property_802_1X_get_password_raw_flags (setting)); + set_val_str (arr, 24, nmc_property_802_1X_get_private_key (setting)); + set_val_str (arr, 25, GET_SECRET (secrets, setting, nmc_property_802_1X_get_private_key_password)); + set_val_str (arr, 26, nmc_property_802_1X_get_private_key_password_flags (setting)); + set_val_str (arr, 27, nmc_property_802_1X_get_phase2_private_key (setting)); + set_val_str (arr, 28, GET_SECRET (secrets, setting, nmc_property_802_1X_get_phase2_private_key_password)); + set_val_str (arr, 29, nmc_property_802_1X_get_phase2_private_key_password_flags (setting)); + set_val_str (arr, 30, GET_SECRET (secrets, setting, nmc_property_802_1X_get_pin)); + set_val_str (arr, 31, nmc_property_802_1X_get_pin_flags (setting)); + set_val_str (arr, 32, nmc_property_802_1X_get_system_ca_certs (setting)); + g_ptr_array_add (nmc->output_data, arr); + + print_data (nmc); /* Print all data */ + + return TRUE; +} + +static gboolean +setting_wireless_details (NMSetting *setting, NmCli *nmc, const char *one_prop, gboolean secrets) +{ + NMSettingWireless *s_wireless = NM_SETTING_WIRELESS (setting); + NmcOutputField *tmpl, *arr; + size_t tmpl_len; + + g_return_val_if_fail (NM_IS_SETTING_WIRELESS (s_wireless), FALSE); + + tmpl = nmc_fields_setting_wireless; + tmpl_len = sizeof (nmc_fields_setting_wireless); + nmc->print_fields.indices = parse_output_fields (one_prop ? one_prop : NMC_FIELDS_SETTING_WIRELESS_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); + + arr = nmc_dup_fields_array (tmpl, tmpl_len, NMC_OF_FLAG_SECTION_PREFIX); + set_val_str (arr, 0, g_strdup (nm_setting_get_name (setting))); + set_val_str (arr, 1, nmc_property_wireless_get_ssid (setting)); + set_val_str (arr, 2, nmc_property_wireless_get_mode (setting)); + set_val_str (arr, 3, nmc_property_wireless_get_band (setting)); + set_val_str (arr, 4, nmc_property_wireless_get_channel (setting)); + set_val_str (arr, 5, nmc_property_wireless_get_bssid (setting)); + set_val_str (arr, 6, nmc_property_wireless_get_rate (setting)); + set_val_str (arr, 7, nmc_property_wireless_get_tx_power (setting)); + set_val_str (arr, 8, nmc_property_wireless_get_mac_address (setting)); + set_val_str (arr, 9, nmc_property_wireless_get_cloned_mac_address (setting)); + set_val_str (arr, 10, nmc_property_wireless_get_mac_address_blacklist (setting)); + set_val_str (arr, 11, nmc_property_wireless_get_mtu (setting)); + set_val_str (arr, 12, nmc_property_wireless_get_seen_bssids (setting)); + set_val_str (arr, 13, nmc_property_wireless_get_hidden (setting)); + g_ptr_array_add (nmc->output_data, arr); + + print_data (nmc); /* Print all data */ + + return TRUE; +} + +static gboolean +setting_wireless_security_details (NMSetting *setting, NmCli *nmc, const char *one_prop, gboolean secrets) +{ + NMSettingWirelessSecurity *s_wireless_sec = NM_SETTING_WIRELESS_SECURITY (setting); + NmcOutputField *tmpl, *arr; + size_t tmpl_len; + + g_return_val_if_fail (NM_IS_SETTING_WIRELESS_SECURITY (s_wireless_sec), FALSE); + + tmpl = nmc_fields_setting_wireless_security; + tmpl_len = sizeof (nmc_fields_setting_wireless_security); + nmc->print_fields.indices = parse_output_fields (one_prop ? one_prop : NMC_FIELDS_SETTING_WIRELESS_SECURITY_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); + + arr = nmc_dup_fields_array (tmpl, tmpl_len, NMC_OF_FLAG_SECTION_PREFIX); + set_val_str (arr, 0, g_strdup (nm_setting_get_name (setting))); + set_val_str (arr, 1, nmc_property_wifi_sec_get_key_mgmt (setting)); + set_val_str (arr, 2, nmc_property_wifi_sec_get_wep_tx_keyidx (setting)); + set_val_str (arr, 3, nmc_property_wifi_sec_get_auth_alg (setting)); + set_val_str (arr, 4, nmc_property_wifi_sec_get_proto (setting)); + set_val_str (arr, 5, nmc_property_wifi_sec_get_pairwise (setting)); + set_val_str (arr, 6, nmc_property_wifi_sec_get_group (setting)); + set_val_str (arr, 7, nmc_property_wifi_sec_get_leap_username (setting)); + set_val_str (arr, 8, GET_SECRET (secrets, setting, nmc_property_wifi_sec_get_wep_key0)); + set_val_str (arr, 9, GET_SECRET (secrets, setting, nmc_property_wifi_sec_get_wep_key1)); + set_val_str (arr, 10, GET_SECRET (secrets, setting, nmc_property_wifi_sec_get_wep_key2)); + set_val_str (arr, 11, GET_SECRET (secrets, setting, nmc_property_wifi_sec_get_wep_key3)); + set_val_str (arr, 12, nmc_property_wifi_sec_get_wep_key_flags (setting)); + set_val_str (arr, 13, nmc_property_wifi_sec_get_wep_key_type (setting)); + set_val_str (arr, 14, GET_SECRET (secrets, setting, nmc_property_wifi_sec_get_psk)); + set_val_str (arr, 15, nmc_property_wifi_sec_get_psk_flags (setting)); + set_val_str (arr, 16, GET_SECRET (secrets, setting, nmc_property_wifi_sec_get_leap_password)); + set_val_str (arr, 17, nmc_property_wifi_sec_get_leap_password_flags (setting)); + g_ptr_array_add (nmc->output_data, arr); + + print_data (nmc); /* Print all data */ + + return TRUE; +} + +static gboolean +setting_ip4_config_details (NMSetting *setting, NmCli *nmc, const char *one_prop, gboolean secrets) +{ + NMSettingIPConfig *s_ip4 = NM_SETTING_IP_CONFIG (setting); + NmcOutputField *tmpl, *arr; + size_t tmpl_len; + + g_return_val_if_fail (NM_IS_SETTING_IP4_CONFIG (s_ip4), FALSE); + + tmpl = nmc_fields_setting_ip4_config; + tmpl_len = sizeof (nmc_fields_setting_ip4_config); + nmc->print_fields.indices = parse_output_fields (one_prop ? one_prop : NMC_FIELDS_SETTING_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); + + arr = nmc_dup_fields_array (tmpl, tmpl_len, NMC_OF_FLAG_SECTION_PREFIX); + set_val_str (arr, 0, g_strdup (nm_setting_get_name (setting))); + set_val_str (arr, 1, nmc_property_ipv4_get_method (setting)); + set_val_str (arr, 2, nmc_property_ipv4_get_dns (setting)); + set_val_str (arr, 3, nmc_property_ipv4_get_dns_search (setting)); + set_val_str (arr, 4, nmc_property_ip_get_addresses (setting)); + set_val_str (arr, 5, nmc_property_ipv4_get_gateway (setting)); + set_val_str (arr, 6, nmc_property_ipv4_get_routes (setting)); + set_val_str (arr, 7, nmc_property_ipv4_get_route_metric (setting)); + set_val_str (arr, 8, nmc_property_ipv4_get_ignore_auto_routes (setting)); + set_val_str (arr, 9, nmc_property_ipv4_get_ignore_auto_dns (setting)); + set_val_str (arr, 10, nmc_property_ipv4_get_dhcp_client_id (setting)); + set_val_str (arr, 11, nmc_property_ipv4_get_dhcp_send_hostname (setting)); + set_val_str (arr, 12, nmc_property_ipv4_get_dhcp_hostname (setting)); + set_val_str (arr, 13, nmc_property_ipv4_get_never_default (setting)); + set_val_str (arr, 14, nmc_property_ipv4_get_may_fail (setting)); + g_ptr_array_add (nmc->output_data, arr); + + print_data (nmc); /* Print all data */ + + return TRUE; +} + +static gboolean +setting_ip6_config_details (NMSetting *setting, NmCli *nmc, const char *one_prop, gboolean secrets) +{ + NMSettingIPConfig *s_ip6 = NM_SETTING_IP_CONFIG (setting); + NmcOutputField *tmpl, *arr; + size_t tmpl_len; + + g_return_val_if_fail (NM_IS_SETTING_IP6_CONFIG (s_ip6), FALSE); + + tmpl = nmc_fields_setting_ip6_config; + tmpl_len = sizeof (nmc_fields_setting_ip6_config); + nmc->print_fields.indices = parse_output_fields (one_prop ? one_prop : NMC_FIELDS_SETTING_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); + + arr = nmc_dup_fields_array (tmpl, tmpl_len, NMC_OF_FLAG_SECTION_PREFIX); + set_val_str (arr, 0, g_strdup (nm_setting_get_name (setting))); + set_val_str (arr, 1, nmc_property_ipv6_get_method (setting)); + set_val_str (arr, 2, nmc_property_ipv6_get_dns (setting)); + set_val_str (arr, 3, nmc_property_ipv6_get_dns_search (setting)); + set_val_str (arr, 4, nmc_property_ip_get_addresses (setting)); + set_val_str (arr, 5, nmc_property_ipv6_get_gateway (setting)); + set_val_str (arr, 6, nmc_property_ipv6_get_routes (setting)); + set_val_str (arr, 7, nmc_property_ipv6_get_route_metric (setting)); + set_val_str (arr, 8, nmc_property_ipv6_get_ignore_auto_routes (setting)); + set_val_str (arr, 9, nmc_property_ipv6_get_ignore_auto_dns (setting)); + set_val_str (arr, 10, nmc_property_ipv6_get_never_default (setting)); + set_val_str (arr, 11, nmc_property_ipv6_get_may_fail (setting)); + set_val_str (arr, 12, nmc_property_ipv6_get_ip6_privacy (setting)); + set_val_str (arr, 13, nmc_property_ipv6_get_dhcp_send_hostname (setting)); + set_val_str (arr, 14, nmc_property_ipv6_get_dhcp_hostname (setting)); + g_ptr_array_add (nmc->output_data, arr); + + print_data (nmc); /* Print all data */ + + return TRUE; +} + +static gboolean +setting_serial_details (NMSetting *setting, NmCli *nmc, const char *one_prop, gboolean secrets) +{ + NMSettingSerial *s_serial = NM_SETTING_SERIAL (setting); + NmcOutputField *tmpl, *arr; + size_t tmpl_len; + + g_return_val_if_fail (NM_IS_SETTING_SERIAL (s_serial), FALSE); + + tmpl = nmc_fields_setting_serial; + tmpl_len = sizeof (nmc_fields_setting_serial); + nmc->print_fields.indices = parse_output_fields (one_prop ? one_prop : NMC_FIELDS_SETTING_SERIAL_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); + + arr = nmc_dup_fields_array (tmpl, tmpl_len, NMC_OF_FLAG_SECTION_PREFIX); + set_val_str (arr, 0, g_strdup (nm_setting_get_name (setting))); + set_val_str (arr, 1, nmc_property_serial_get_baud (setting)); + set_val_str (arr, 2, nmc_property_serial_get_bits (setting)); + set_val_str (arr, 3, nmc_property_serial_get_parity (setting)); + set_val_str (arr, 4, nmc_property_serial_get_stopbits (setting)); + set_val_str (arr, 5, nmc_property_serial_get_send_delay (setting)); + g_ptr_array_add (nmc->output_data, arr); + + print_data (nmc); /* Print all data */ + + return TRUE; +} + +static gboolean +setting_ppp_details (NMSetting *setting, NmCli *nmc, const char *one_prop, gboolean secrets) +{ + NMSettingPpp *s_ppp = NM_SETTING_PPP (setting); + NmcOutputField *tmpl, *arr; + size_t tmpl_len; + + g_return_val_if_fail (NM_IS_SETTING_PPP (s_ppp), FALSE); + + tmpl = nmc_fields_setting_ppp; + tmpl_len = sizeof (nmc_fields_setting_ppp); + nmc->print_fields.indices = parse_output_fields (one_prop ? one_prop : NMC_FIELDS_SETTING_PPP_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); + + arr = nmc_dup_fields_array (tmpl, tmpl_len, NMC_OF_FLAG_SECTION_PREFIX); + set_val_str (arr, 0, g_strdup (nm_setting_get_name (setting))); + set_val_str (arr, 1, nmc_property_ppp_get_noauth (setting)); + set_val_str (arr, 2, nmc_property_ppp_get_refuse_eap (setting)); + set_val_str (arr, 3, nmc_property_ppp_get_refuse_pap (setting)); + set_val_str (arr, 4, nmc_property_ppp_get_refuse_chap (setting)); + set_val_str (arr, 5, nmc_property_ppp_get_refuse_mschap (setting)); + set_val_str (arr, 6, nmc_property_ppp_get_refuse_mschapv2 (setting)); + set_val_str (arr, 7, nmc_property_ppp_get_nobsdcomp (setting)); + set_val_str (arr, 8, nmc_property_ppp_get_nodeflate (setting)); + set_val_str (arr, 9, nmc_property_ppp_get_no_vj_comp (setting)); + set_val_str (arr, 10, nmc_property_ppp_get_require_mppe (setting)); + set_val_str (arr, 11, nmc_property_ppp_get_require_mppe_128 (setting)); + set_val_str (arr, 12, nmc_property_ppp_get_mppe_stateful (setting)); + set_val_str (arr, 13, nmc_property_ppp_get_crtscts (setting)); + set_val_str (arr, 14, nmc_property_ppp_get_baud (setting)); + set_val_str (arr, 15, nmc_property_ppp_get_mru (setting)); + set_val_str (arr, 16, nmc_property_ppp_get_mtu (setting)); + set_val_str (arr, 17, nmc_property_ppp_get_lcp_echo_failure (setting)); + set_val_str (arr, 18, nmc_property_ppp_get_lcp_echo_interval (setting)); + g_ptr_array_add (nmc->output_data, arr); + + print_data (nmc); /* Print all data */ + + return TRUE; +} + +static gboolean +setting_pppoe_details (NMSetting *setting, NmCli *nmc, const char *one_prop, gboolean secrets) +{ + NMSettingPppoe *s_pppoe = NM_SETTING_PPPOE (setting); + NmcOutputField *tmpl, *arr; + size_t tmpl_len; + + g_return_val_if_fail (NM_IS_SETTING_PPPOE (s_pppoe), FALSE); + + tmpl = nmc_fields_setting_pppoe; + tmpl_len = sizeof (nmc_fields_setting_pppoe); + nmc->print_fields.indices = parse_output_fields (one_prop ? one_prop : NMC_FIELDS_SETTING_PPPOE_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); + + arr = nmc_dup_fields_array (tmpl, tmpl_len, NMC_OF_FLAG_SECTION_PREFIX); + set_val_str (arr, 0, g_strdup (nm_setting_get_name (setting))); + set_val_str (arr, 1, nmc_property_pppoe_get_service (setting)); + set_val_str (arr, 2, nmc_property_pppoe_get_username (setting)); + set_val_str (arr, 3, GET_SECRET (secrets, setting, nmc_property_pppoe_get_password)); + set_val_str (arr, 4, nmc_property_pppoe_get_password_flags (setting)); + g_ptr_array_add (nmc->output_data, arr); + + print_data (nmc); /* Print all data */ + + return TRUE; +} + +static gboolean +setting_gsm_details (NMSetting *setting, NmCli *nmc, const char *one_prop, gboolean secrets) +{ + NMSettingGsm *s_gsm = NM_SETTING_GSM (setting); + NmcOutputField *tmpl, *arr; + size_t tmpl_len; + + g_return_val_if_fail (NM_IS_SETTING_GSM (s_gsm), FALSE); + + tmpl = nmc_fields_setting_gsm; + tmpl_len = sizeof (nmc_fields_setting_gsm); + nmc->print_fields.indices = parse_output_fields (one_prop ? one_prop : NMC_FIELDS_SETTING_GSM_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); + + arr = nmc_dup_fields_array (tmpl, tmpl_len, NMC_OF_FLAG_SECTION_PREFIX); + set_val_str (arr, 0, g_strdup (nm_setting_get_name (setting))); + set_val_str (arr, 1, nmc_property_gsm_get_number (setting)); + set_val_str (arr, 2, nmc_property_gsm_get_username (setting)); + set_val_str (arr, 3, GET_SECRET (secrets, setting, nmc_property_gsm_get_password)); + set_val_str (arr, 4, nmc_property_gsm_get_password_flags (setting)); + set_val_str (arr, 5, nmc_property_gsm_get_apn (setting)); + set_val_str (arr, 6, nmc_property_gsm_get_network_id (setting)); + set_val_str (arr, 7, GET_SECRET (secrets, setting, nmc_property_gsm_get_pin)); + set_val_str (arr, 8, nmc_property_gsm_get_pin_flags (setting)); + set_val_str (arr, 9, nmc_property_gsm_get_home_only (setting)); + g_ptr_array_add (nmc->output_data, arr); + + print_data (nmc); /* Print all data */ + + return TRUE; +} + +static gboolean +setting_cdma_details (NMSetting *setting, NmCli *nmc, const char *one_prop, gboolean secrets) +{ + NMSettingCdma *s_cdma = NM_SETTING_CDMA (setting); + NmcOutputField *tmpl, *arr; + size_t tmpl_len; + + g_return_val_if_fail (NM_IS_SETTING_CDMA (s_cdma), FALSE); + + tmpl = nmc_fields_setting_cdma; + tmpl_len = sizeof (nmc_fields_setting_cdma); + nmc->print_fields.indices = parse_output_fields (one_prop ? one_prop : NMC_FIELDS_SETTING_CDMA_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); + + arr = nmc_dup_fields_array (tmpl, tmpl_len, NMC_OF_FLAG_SECTION_PREFIX); + set_val_str (arr, 0, g_strdup (nm_setting_get_name (setting))); + set_val_str (arr, 1, nmc_property_cdma_get_number (setting)); + set_val_str (arr, 2, nmc_property_cdma_get_username (setting)); + set_val_str (arr, 3, GET_SECRET (secrets, setting, nmc_property_cdma_get_password)); + set_val_str (arr, 4, nmc_property_cdma_get_password_flags (setting)); + g_ptr_array_add (nmc->output_data, arr); + + print_data (nmc); /* Print all data */ + + return TRUE; +} + +static gboolean +setting_bluetooth_details (NMSetting *setting, NmCli *nmc, const char *one_prop, gboolean secrets) +{ + NMSettingBluetooth *s_bluetooth = NM_SETTING_BLUETOOTH (setting); + NmcOutputField *tmpl, *arr; + size_t tmpl_len; + + g_return_val_if_fail (NM_IS_SETTING_BLUETOOTH (s_bluetooth), FALSE); + + tmpl = nmc_fields_setting_bluetooth; + tmpl_len = sizeof (nmc_fields_setting_bluetooth); + nmc->print_fields.indices = parse_output_fields (one_prop ? one_prop : NMC_FIELDS_SETTING_BLUETOOTH_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); + + arr = nmc_dup_fields_array (tmpl, tmpl_len, NMC_OF_FLAG_SECTION_PREFIX); + set_val_str (arr, 0, g_strdup (nm_setting_get_name (setting))); + set_val_str (arr, 1, nmc_property_bluetooth_get_bdaddr (setting)); + set_val_str (arr, 2, nmc_property_bluetooth_get_type (setting)); + g_ptr_array_add (nmc->output_data, arr); + + print_data (nmc); /* Print all data */ + + return TRUE; +} + +static gboolean +setting_olpc_mesh_details (NMSetting *setting, NmCli *nmc, const char *one_prop, gboolean secrets) +{ + NMSettingOlpcMesh *s_olpc_mesh = NM_SETTING_OLPC_MESH (setting); + NmcOutputField *tmpl, *arr; + size_t tmpl_len; + + g_return_val_if_fail (NM_IS_SETTING_OLPC_MESH (s_olpc_mesh), FALSE); + + tmpl = nmc_fields_setting_olpc_mesh; + tmpl_len = sizeof (nmc_fields_setting_olpc_mesh); + nmc->print_fields.indices = parse_output_fields (one_prop ? one_prop : NMC_FIELDS_SETTING_OLPC_MESH_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); + + arr = nmc_dup_fields_array (tmpl, tmpl_len, NMC_OF_FLAG_SECTION_PREFIX); + set_val_str (arr, 0, g_strdup (nm_setting_get_name (setting))); + set_val_str (arr, 1, nmc_property_olpc_get_ssid (setting)); + set_val_str (arr, 2, nmc_property_olpc_get_channel (setting)); + set_val_str (arr, 3, nmc_property_olpc_get_anycast_address (setting)); + g_ptr_array_add (nmc->output_data, arr); + + print_data (nmc); /* Print all data */ + + return TRUE; +} + +static gboolean +setting_vpn_details (NMSetting *setting, NmCli *nmc, const char *one_prop, gboolean secrets) +{ + NMSettingVpn *s_vpn = NM_SETTING_VPN (setting); + NmcOutputField *tmpl, *arr; + size_t tmpl_len; + + g_return_val_if_fail (NM_IS_SETTING_VPN (s_vpn), FALSE); + + tmpl = nmc_fields_setting_vpn; + tmpl_len = sizeof (nmc_fields_setting_vpn); + nmc->print_fields.indices = parse_output_fields (one_prop ? one_prop : NMC_FIELDS_SETTING_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); + + arr = nmc_dup_fields_array (tmpl, tmpl_len, NMC_OF_FLAG_SECTION_PREFIX); + set_val_str (arr, 0, g_strdup (nm_setting_get_name (setting))); + set_val_str (arr, 1, nmc_property_vpn_get_service_type (setting)); + set_val_str (arr, 2, nmc_property_vpn_get_user_name (setting)); + set_val_str (arr, 3, nmc_property_vpn_get_data (setting)); + set_val_str (arr, 4, GET_SECRET (secrets, setting, nmc_property_vpn_get_secrets)); + set_val_str (arr, 5, nmc_property_vpn_get_persistent (setting)); + g_ptr_array_add (nmc->output_data, arr); + + print_data (nmc); /* Print all data */ + + return TRUE; +} + +static gboolean +setting_wimax_details (NMSetting *setting, NmCli *nmc, const char *one_prop, gboolean secrets) +{ + NMSettingWimax *s_wimax = NM_SETTING_WIMAX (setting); + NmcOutputField *tmpl, *arr; + size_t tmpl_len; + + g_return_val_if_fail (NM_IS_SETTING_WIMAX (s_wimax), FALSE); + + tmpl = nmc_fields_setting_wimax; + tmpl_len = sizeof (nmc_fields_setting_wimax); + nmc->print_fields.indices = parse_output_fields (one_prop ? one_prop : NMC_FIELDS_SETTING_WIMAX_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); + + arr = nmc_dup_fields_array (tmpl, tmpl_len, NMC_OF_FLAG_SECTION_PREFIX); + set_val_str (arr, 0, g_strdup (nm_setting_get_name (setting))); + set_val_str (arr, 1, nmc_property_wimax_get_mac_address (setting)); + set_val_str (arr, 2, nmc_property_wimax_get_network_name (setting)); + g_ptr_array_add (nmc->output_data, arr); + + print_data (nmc); /* Print all data */ + + return TRUE; +} + +static gboolean +setting_infiniband_details (NMSetting *setting, NmCli *nmc, const char *one_prop, gboolean secrets) +{ + NMSettingInfiniband *s_infiniband = NM_SETTING_INFINIBAND (setting); + NmcOutputField *tmpl, *arr; + size_t tmpl_len; + + g_return_val_if_fail (NM_IS_SETTING_INFINIBAND (s_infiniband), FALSE); + + tmpl = nmc_fields_setting_infiniband; + tmpl_len = sizeof (nmc_fields_setting_infiniband); + nmc->print_fields.indices = parse_output_fields (one_prop ? one_prop : NMC_FIELDS_SETTING_INFINIBAND_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); + + arr = nmc_dup_fields_array (tmpl, tmpl_len, NMC_OF_FLAG_SECTION_PREFIX); + set_val_str (arr, 0, g_strdup (nm_setting_get_name (setting))); + set_val_str (arr, 1, nmc_property_ib_get_mac_address (setting)); + set_val_str (arr, 2, nmc_property_ib_get_mtu (setting)); + set_val_str (arr, 3, nmc_property_ib_get_transport_mode (setting)); + set_val_str (arr, 4, nmc_property_ib_get_p_key (setting)); + set_val_str (arr, 5, nmc_property_ib_get_parent (setting)); + g_ptr_array_add (nmc->output_data, arr); + + print_data (nmc); /* Print all data */ + + return TRUE; +} + +static gboolean +setting_bond_details (NMSetting *setting, NmCli *nmc, const char *one_prop, gboolean secrets) +{ + NMSettingBond *s_bond = NM_SETTING_BOND (setting); + NmcOutputField *tmpl, *arr; + size_t tmpl_len; + + g_return_val_if_fail (NM_IS_SETTING_BOND (s_bond), FALSE); + + tmpl = nmc_fields_setting_bond; + tmpl_len = sizeof (nmc_fields_setting_bond); + nmc->print_fields.indices = parse_output_fields (one_prop ? one_prop : NMC_FIELDS_SETTING_BOND_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); + + arr = nmc_dup_fields_array (tmpl, tmpl_len, NMC_OF_FLAG_SECTION_PREFIX); + set_val_str (arr, 0, g_strdup (nm_setting_get_name (setting))); + set_val_str (arr, 1, nmc_property_bond_get_options (setting)); + g_ptr_array_add (nmc->output_data, arr); + + print_data (nmc); /* Print all data */ + + return TRUE; +} + +static gboolean +setting_vlan_details (NMSetting *setting, NmCli *nmc, const char *one_prop, gboolean secrets) +{ + NMSettingVlan *s_vlan = NM_SETTING_VLAN (setting); + NmcOutputField *tmpl, *arr; + size_t tmpl_len; + + g_return_val_if_fail (NM_IS_SETTING_VLAN (s_vlan), FALSE); + + tmpl = nmc_fields_setting_vlan; + tmpl_len = sizeof (nmc_fields_setting_vlan); + nmc->print_fields.indices = parse_output_fields (one_prop ? one_prop : NMC_FIELDS_SETTING_VLAN_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); + + arr = nmc_dup_fields_array (tmpl, tmpl_len, NMC_OF_FLAG_SECTION_PREFIX); + set_val_str (arr, 0, g_strdup (nm_setting_get_name (setting))); + set_val_str (arr, 1, nmc_property_vlan_get_parent (setting)); + set_val_str (arr, 2, nmc_property_vlan_get_id (setting)); + set_val_str (arr, 3, nmc_property_vlan_get_flags (setting)); + set_val_str (arr, 4, nmc_property_vlan_get_ingress_priority_map (setting)); + set_val_str (arr, 5, nmc_property_vlan_get_egress_priority_map (setting)); + g_ptr_array_add (nmc->output_data, arr); + + print_data (nmc); /* Print all data */ + + return TRUE; +} + +static gboolean +setting_adsl_details (NMSetting *setting, NmCli *nmc, const char *one_prop, gboolean secrets) +{ + NMSettingAdsl *s_adsl = NM_SETTING_ADSL (setting); + NmcOutputField *tmpl, *arr; + size_t tmpl_len; + + g_return_val_if_fail (NM_IS_SETTING_ADSL (s_adsl), FALSE); + + tmpl = nmc_fields_setting_adsl; + tmpl_len = sizeof (nmc_fields_setting_adsl); + nmc->print_fields.indices = parse_output_fields (one_prop ? one_prop : NMC_FIELDS_SETTING_ADSL_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); + + arr = nmc_dup_fields_array (tmpl, tmpl_len, NMC_OF_FLAG_SECTION_PREFIX); + set_val_str (arr, 0, g_strdup (nm_setting_get_name (setting))); + set_val_str (arr, 1, nmc_property_adsl_get_username (setting)); + set_val_str (arr, 2, GET_SECRET (secrets, setting, nmc_property_adsl_get_password)); + set_val_str (arr, 3, nmc_property_adsl_get_password_flags (setting)); + set_val_str (arr, 4, nmc_property_adsl_get_protocol (setting)); + set_val_str (arr, 5, nmc_property_adsl_get_encapsulation (setting)); + set_val_str (arr, 6, nmc_property_adsl_get_vpi (setting)); + set_val_str (arr, 7, nmc_property_adsl_get_vci (setting)); + g_ptr_array_add (nmc->output_data, arr); + + print_data (nmc); /* Print all data */ + + return TRUE; +} + +static gboolean +setting_bridge_details (NMSetting *setting, NmCli *nmc, const char *one_prop, gboolean secrets) +{ + NMSettingBridge *s_bridge = NM_SETTING_BRIDGE (setting); + NmcOutputField *tmpl, *arr; + size_t tmpl_len; + + g_return_val_if_fail (NM_IS_SETTING_BRIDGE (s_bridge), FALSE); + + tmpl = nmc_fields_setting_bridge; + tmpl_len = sizeof (nmc_fields_setting_bridge); + nmc->print_fields.indices = parse_output_fields (one_prop ? one_prop : NMC_FIELDS_SETTING_BRIDGE_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); + + arr = nmc_dup_fields_array (tmpl, tmpl_len, NMC_OF_FLAG_SECTION_PREFIX); + set_val_str (arr, 0, g_strdup (nm_setting_get_name (setting))); + set_val_str (arr, 1, nmc_property_bridge_get_mac_address (setting)); + set_val_str (arr, 2, nmc_property_bridge_get_stp (setting)); + set_val_str (arr, 3, nmc_property_bridge_get_priority (setting)); + set_val_str (arr, 4, nmc_property_bridge_get_forward_delay (setting)); + set_val_str (arr, 5, nmc_property_bridge_get_hello_time (setting)); + set_val_str (arr, 6, nmc_property_bridge_get_max_age (setting)); + set_val_str (arr, 7, nmc_property_bridge_get_ageing_time (setting)); + g_ptr_array_add (nmc->output_data, arr); + + print_data (nmc); /* Print all data */ + + return TRUE; +} + +static gboolean +setting_bridge_port_details (NMSetting *setting, NmCli *nmc, const char *one_prop, gboolean secrets) +{ + NMSettingBridgePort *s_bridge_port = NM_SETTING_BRIDGE_PORT (setting); + NmcOutputField *tmpl, *arr; + size_t tmpl_len; + + g_return_val_if_fail (NM_IS_SETTING_BRIDGE_PORT (s_bridge_port), FALSE); + + tmpl = nmc_fields_setting_bridge_port; + tmpl_len = sizeof (nmc_fields_setting_bridge_port); + nmc->print_fields.indices = parse_output_fields (one_prop ? one_prop : NMC_FIELDS_SETTING_BRIDGE_PORT_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); + + arr = nmc_dup_fields_array (tmpl, tmpl_len, NMC_OF_FLAG_SECTION_PREFIX); + set_val_str (arr, 0, g_strdup (nm_setting_get_name (setting))); + set_val_str (arr, 1, nmc_property_bridge_port_get_priority (setting)); + set_val_str (arr, 2, nmc_property_bridge_port_get_path_cost (setting)); + set_val_str (arr, 3, nmc_property_bridge_port_get_hairpin_mode (setting)); + g_ptr_array_add (nmc->output_data, arr); + + print_data (nmc); /* Print all data */ + + return TRUE; +} + +static gboolean +setting_team_details (NMSetting *setting, NmCli *nmc, const char *one_prop, gboolean secrets) +{ + NMSettingTeam *s_team = NM_SETTING_TEAM (setting); + NmcOutputField *tmpl, *arr; + size_t tmpl_len; + + g_return_val_if_fail (NM_IS_SETTING_TEAM (s_team), FALSE); + + tmpl = nmc_fields_setting_team; + tmpl_len = sizeof (nmc_fields_setting_team); + nmc->print_fields.indices = parse_output_fields (one_prop ? one_prop : NMC_FIELDS_SETTING_TEAM_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); + + arr = nmc_dup_fields_array (tmpl, tmpl_len, NMC_OF_FLAG_SECTION_PREFIX); + set_val_str (arr, 0, g_strdup (nm_setting_get_name (setting))); + set_val_str (arr, 1, nmc_property_team_get_config (setting)); + g_ptr_array_add (nmc->output_data, arr); + + print_data (nmc); /* Print all data */ + + return TRUE; +} + +static gboolean +setting_team_port_details (NMSetting *setting, NmCli *nmc, const char *one_prop, gboolean secrets) +{ + NMSettingTeamPort *s_team_port = NM_SETTING_TEAM_PORT (setting); + NmcOutputField *tmpl, *arr; + size_t tmpl_len; + + g_return_val_if_fail (NM_IS_SETTING_TEAM_PORT (s_team_port), FALSE); + + tmpl = nmc_fields_setting_team_port; + tmpl_len = sizeof (nmc_fields_setting_team_port); + nmc->print_fields.indices = parse_output_fields (one_prop ? one_prop : NMC_FIELDS_SETTING_TEAM_PORT_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); + + arr = nmc_dup_fields_array (tmpl, tmpl_len, NMC_OF_FLAG_SECTION_PREFIX); + set_val_str (arr, 0, g_strdup (nm_setting_get_name (setting))); + set_val_str (arr, 1, nmc_property_team_port_get_config (setting)); + g_ptr_array_add (nmc->output_data, arr); + + print_data (nmc); /* Print all data */ + + return TRUE; +} + +static gboolean +setting_dcb_details (NMSetting *setting, NmCli *nmc, const char *one_prop, gboolean secrets) +{ + NMSettingDcb *s_dcb = NM_SETTING_DCB (setting); + NmcOutputField *tmpl, *arr; + size_t tmpl_len; + + g_return_val_if_fail (NM_IS_SETTING_DCB (s_dcb), FALSE); + + tmpl = nmc_fields_setting_dcb; + tmpl_len = sizeof (nmc_fields_setting_dcb); + nmc->print_fields.indices = parse_output_fields (one_prop ? one_prop : NMC_FIELDS_SETTING_DCB_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); + + arr = nmc_dup_fields_array (tmpl, tmpl_len, NMC_OF_FLAG_SECTION_PREFIX); + set_val_str (arr, 0, g_strdup (nm_setting_get_name (setting))); + set_val_str (arr, 1, nmc_property_dcb_get_app_fcoe_flags (setting)); + set_val_str (arr, 2, nmc_property_dcb_get_app_fcoe_priority (setting)); + set_val_str (arr, 3, nmc_property_dcb_get_app_fcoe_mode (setting)); + set_val_str (arr, 4, nmc_property_dcb_get_app_iscsi_flags (setting)); + set_val_str (arr, 5, nmc_property_dcb_get_app_iscsi_priority (setting)); + set_val_str (arr, 6, nmc_property_dcb_get_app_fip_flags (setting)); + set_val_str (arr, 7, nmc_property_dcb_get_app_fip_priority (setting)); + set_val_str (arr, 8, nmc_property_dcb_get_pfc_flags (setting)); + set_val_str (arr, 9, nmc_property_dcb_get_pfc (setting)); + set_val_str (arr, 10, nmc_property_dcb_get_pg_flags (setting)); + set_val_str (arr, 11, nmc_property_dcb_get_pg_group_id (setting)); + set_val_str (arr, 12, nmc_property_dcb_get_pg_group_bandwidth (setting)); + set_val_str (arr, 13, nmc_property_dcb_get_pg_bandwidth (setting)); + set_val_str (arr, 14, nmc_property_dcb_get_pg_strict (setting)); + set_val_str (arr, 15, nmc_property_dcb_get_pg_traffic_class (setting)); + g_ptr_array_add (nmc->output_data, arr); + + print_data (nmc); /* Print all data */ + + return TRUE; +} + +typedef struct { + const char *sname; + gboolean (*func) (NMSetting *setting, NmCli *nmc, const char *one_prop, gboolean secrets); +} SettingDetails; + +static const SettingDetails detail_printers[] = { + { NM_SETTING_CONNECTION_SETTING_NAME, setting_connection_details }, + { NM_SETTING_WIRED_SETTING_NAME, setting_wired_details }, + { NM_SETTING_802_1X_SETTING_NAME, setting_802_1X_details }, + { NM_SETTING_WIRELESS_SETTING_NAME, setting_wireless_details }, + { NM_SETTING_WIRELESS_SECURITY_SETTING_NAME, setting_wireless_security_details }, + { NM_SETTING_IP4_CONFIG_SETTING_NAME, setting_ip4_config_details }, + { NM_SETTING_IP6_CONFIG_SETTING_NAME, setting_ip6_config_details }, + { NM_SETTING_SERIAL_SETTING_NAME, setting_serial_details }, + { NM_SETTING_PPP_SETTING_NAME, setting_ppp_details }, + { NM_SETTING_PPPOE_SETTING_NAME, setting_pppoe_details }, + { NM_SETTING_GSM_SETTING_NAME, setting_gsm_details }, + { NM_SETTING_CDMA_SETTING_NAME, setting_cdma_details }, + { NM_SETTING_BLUETOOTH_SETTING_NAME, setting_bluetooth_details }, + { NM_SETTING_OLPC_MESH_SETTING_NAME, setting_olpc_mesh_details }, + { NM_SETTING_VPN_SETTING_NAME, setting_vpn_details }, + { NM_SETTING_WIMAX_SETTING_NAME, setting_wimax_details }, + { NM_SETTING_INFINIBAND_SETTING_NAME, setting_infiniband_details }, + { NM_SETTING_BOND_SETTING_NAME, setting_bond_details }, + { NM_SETTING_VLAN_SETTING_NAME, setting_vlan_details }, + { NM_SETTING_ADSL_SETTING_NAME, setting_adsl_details }, + { NM_SETTING_BRIDGE_SETTING_NAME, setting_bridge_details }, + { NM_SETTING_BRIDGE_PORT_SETTING_NAME, setting_bridge_port_details }, + { NM_SETTING_TEAM_SETTING_NAME, setting_team_details }, + { NM_SETTING_TEAM_PORT_SETTING_NAME, setting_team_port_details }, + { NM_SETTING_DCB_SETTING_NAME, setting_dcb_details }, + { NULL }, +}; + +gboolean +setting_details (NMSetting *setting, NmCli *nmc, const char *one_prop, gboolean secrets) +{ + const SettingDetails *iter = &detail_printers[0]; + + g_return_val_if_fail (NM_IS_SETTING (setting), FALSE); + + while (iter->sname) { + if (nm_setting_lookup_type (iter->sname) == G_OBJECT_TYPE (setting)) + return iter->func (setting, nmc, one_prop, secrets); + iter++; + } + + g_assert_not_reached (); + return FALSE; +} + diff --git a/clients/cli/settings.h b/clients/cli/settings.h new file mode 100644 index 00000000..3c4c814f --- /dev/null +++ b/clients/cli/settings.h @@ -0,0 +1,66 @@ +/* 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. + */ + +#ifndef NMC_SETTINGS_H +#define NMC_SETTINGS_H + +#include "nmcli.h" +#include "utils.h" + +/* --- Functions --- */ + +void nmc_properties_init (void); +void nmc_properties_cleanup (void); + +NMSetting *nmc_setting_new_for_name (const char *name); +void nmc_setting_custom_init (NMSetting *setting); +void nmc_setting_ip4_connect_handlers (NMSettingIPConfig *setting); +void nmc_setting_ip6_connect_handlers (NMSettingIPConfig *setting); +void nmc_setting_wireless_connect_handlers (NMSettingWireless *setting); +void nmc_setting_connection_connect_handlers (NMSettingConnection *setting, NMConnection *connection); + +char **nmc_setting_get_valid_properties (NMSetting *setting); +char *nmc_setting_get_property_desc (NMSetting *setting, const char *prop); +const char *nmc_setting_get_property_allowed_values (NMSetting *setting, const char *prop); +char *nmc_setting_get_property (NMSetting *setting, + const char *prop, + GError **error); +char *nmc_setting_get_property_out2in (NMSetting *setting, + const char *prop, + GError **error); +gboolean nmc_setting_set_property (NMSetting *setting, + const char *prop, + const char *val, + GError **error); +gboolean nmc_setting_reset_property (NMSetting *setting, + const char *prop, + GError **error); +gboolean nmc_setting_remove_property_option (NMSetting *setting, + const char *prop, + const char *option, + guint32 idx, + GError **error); +void nmc_property_set_default_value (NMSetting *setting, const char *prop); + +gboolean nmc_property_get_gvalue (NMSetting *setting, const char *prop, GValue *value); +gboolean nmc_property_set_gvalue (NMSetting *setting, const char *prop, GValue *value); + +gboolean setting_details (NMSetting *setting, NmCli *nmc, const char *one_prop, gboolean secrets); + +#endif /* NMC_SETTINGS_H */ diff --git a/clients/cli/utils.c b/clients/cli/utils.c new file mode 100644 index 00000000..60cf9f93 --- /dev/null +++ b/clients/cli/utils.c @@ -0,0 +1,1151 @@ +/* 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. + * + * (C) Copyright 2010 - 2014 Red Hat, Inc. + */ + +/* Generated configuration file */ +#include "config.h" + +#include <stdio.h> +#include <string.h> +#include <stdlib.h> +#include <errno.h> + +#include <sys/socket.h> +#include <netinet/in.h> +#include <arpa/inet.h> + +#include <glib.h> +#include <glib/gi18n.h> + +#include "utils.h" + +int +matches (const char *cmd, const char *pattern) +{ + size_t len = strlen (cmd); + if (!len || len > strlen (pattern)) + return -1; + return memcmp (pattern, cmd, len); +} + +int +next_arg (int *argc, char ***argv) +{ + int arg_num = *argc; + + if (arg_num > 0) { + (*argc)--; + (*argv)++; + } + if (arg_num <= 1) + return -1; + + return 0; +} + +gboolean +nmc_arg_is_help (const char *arg) +{ + if (!arg) + return FALSE; + if ( matches (arg, "help") == 0 + || (g_str_has_prefix (arg, "-") && matches (arg+1, "help") == 0) + || (g_str_has_prefix (arg, "--") && matches (arg+2, "help") == 0)) { + return TRUE; + } + return FALSE; +} + +gboolean +nmc_arg_is_option (const char *str, const char *opt_name) +{ + const char *p; + + if (!str || !*str) + return FALSE; + + if (str[0] != '-') + return FALSE; + + p = (str[1] == '-') ? str + 2 : str + 1; + + return (*p ? (matches (p, opt_name) == 0) : FALSE); +} + + +/* + * Helper function to parse command-line arguments. + * arg_arr: description of arguments to look for + * last: whether these are last expected arguments + * argc: command-line argument array + * argv: command-line argument array size + * error: error set on a failure (when FALSE is returned) + * Returns: TRUE on success, FALSE on an error and sets 'error' + */ +gboolean +nmc_parse_args (nmc_arg_t *arg_arr, gboolean last, int *argc, char ***argv, GError **error) +{ + nmc_arg_t *p; + gboolean found; + gboolean have_mandatory; + + g_return_val_if_fail (arg_arr != NULL, FALSE); + g_return_val_if_fail (error == NULL || *error == NULL, FALSE); + + while (*argc > 0) { + found = FALSE; + + for (p = arg_arr; p->name; p++) { + if (strcmp (**argv, p->name) == 0) { + + if (p->found) { + /* Don't allow repeated arguments, because the argument of the same + * name could be used later on the line for another purpose. Assume + * that's the case and return. + */ + return TRUE; + } + + if (p->has_value) { + if (next_arg (argc, argv) != 0) { + g_set_error (error, NMCLI_ERROR, NMC_RESULT_ERROR_USER_INPUT, + _("Error: value for '%s' argument is required."), *(*argv-1)); + return FALSE; + } + *(p->value) = **argv; + } + p->found = TRUE; + found = TRUE; + break; + } + } + + if (!found) { + have_mandatory = TRUE; + for (p = arg_arr; p->name; p++) { + if (p->mandatory && !p->found) { + have_mandatory = FALSE; + break; + } + } + + if (have_mandatory && !last) + return TRUE; + + if (p->name) + g_set_error (error, NMCLI_ERROR, NMC_RESULT_ERROR_USER_INPUT, + _("Error: Argument '%s' was expected, but '%s' provided."), p->name, **argv); + else + g_set_error (error, NMCLI_ERROR, NMC_RESULT_ERROR_USER_INPUT, + _("Error: Unexpected argument '%s'"), **argv); + return FALSE; + } + + next_arg (argc, argv); + } + + return TRUE; +} + +/* + * Convert SSID to a hex string representation. + * Caller has to free the returned string using g_free() + */ +char * +ssid_to_hex (const char *str, gsize len) +{ + GString *printable; + char *printable_str; + int i; + + if (str == NULL || len == 0) + return NULL; + + printable = g_string_new (NULL); + for (i = 0; i < len; i++) { + g_string_append_printf (printable, "%02X", (unsigned char) str[i]); + } + printable_str = g_string_free (printable, FALSE); + return printable_str; +} + +/* + * Converts IPv4 address from guint32 in network-byte order to text representation. + * Returns: text form of the IP or NULL (then error is set) + */ +char * +nmc_ip4_address_as_string (guint32 ip, GError **error) +{ + guint32 tmp_addr; + char buf[INET_ADDRSTRLEN]; + + g_return_val_if_fail (error == NULL || *error == NULL, NULL); + + memset (&buf, '\0', sizeof (buf)); + tmp_addr = ip; + + if (inet_ntop (AF_INET, &tmp_addr, buf, INET_ADDRSTRLEN)) { + return g_strdup (buf); + } else { + g_set_error (error, NMCLI_ERROR, 0, _("Error converting IP4 address '0x%X' to text form"), + ntohl (tmp_addr)); + return NULL; + } +} + +/* + * Converts IPv6 address in in6_addr structure to text representation. + * Returns: text form of the IP or NULL (then error is set) + */ +char * +nmc_ip6_address_as_string (const struct in6_addr *ip, GError **error) +{ + char buf[INET6_ADDRSTRLEN]; + + g_return_val_if_fail (error == NULL || *error == NULL, NULL); + + memset (&buf, '\0', sizeof (buf)); + + if (inet_ntop (AF_INET6, ip, buf, INET6_ADDRSTRLEN)) { + return g_strdup (buf); + } else { + if (error) { + int j; + GString *ip6_str = g_string_new (NULL); + g_string_append_printf (ip6_str, "%02X", ip->s6_addr[0]); + for (j = 1; j < 16; j++) + g_string_append_printf (ip6_str, " %02X", ip->s6_addr[j]); + g_set_error (error, NMCLI_ERROR, 0, _("Error converting IP6 address '%s' to text form"), + ip6_str->str); + g_string_free (ip6_str, TRUE); + } + return NULL; + } +} + +/* + * Erase terminal line using ANSI escape sequences. + * It prints <ESC>[2K sequence to erase the line and then \r to return back + * to the beginning of the line. + * + * http://www.termsys.demon.co.uk/vtansi.htm + */ +void +nmc_terminal_erase_line (void) +{ + /* We intentionally use printf(), not g_print() here, to ensure that + * GLib doesn't mistakenly try to convert the string. + */ + printf ("\33[2K\r"); + fflush (stdout); +} + +/* + * Print animated progress for an operation. + * Repeated calls of the function will show rotating slash in terminal followed + * by the string passed in 'str' argument. + */ +void +nmc_terminal_show_progress (const char *str) +{ + static int idx = 0; + const char slashes[4] = {'|', '/', '-', '\\'}; + + nmc_terminal_erase_line (); + g_print ("%c %s", slashes[idx++], str ? str : ""); + fflush (stdout); + if (idx == 4) + idx = 0; +} + +const char * +nmc_term_color_sequence (NmcTermColor color) +{ + switch (color) { + case NMC_TERM_COLOR_BLACK: + return "\33[30m"; + break; + case NMC_TERM_COLOR_RED: + return "\33[31m"; + break; + case NMC_TERM_COLOR_GREEN: + return "\33[32m"; + break; + case NMC_TERM_COLOR_YELLOW: + return "\33[33m"; + break; + case NMC_TERM_COLOR_BLUE: + return "\33[34m"; + break; + case NMC_TERM_COLOR_MAGENTA: + return "\33[35m"; + break; + case NMC_TERM_COLOR_CYAN: + return "\33[36m"; + break; + case NMC_TERM_COLOR_WHITE: + return "\33[37m"; + break; + default: + return ""; + break; + } +} + +char * +nmc_colorize (NmcTermColor color, const char *fmt, ...) +{ + va_list args; + char *str, *colored; + const char *ansi_color, *color_end; + + va_start (args, fmt); + str = g_strdup_vprintf (fmt, args); + va_end (args); + + ansi_color = nmc_term_color_sequence (color); + if (*ansi_color) + color_end = "\33[0m"; + else + color_end = ""; + + colored = g_strdup_printf ("%s%s%s", ansi_color, str, color_end); + g_free (str); + return colored; +} + +/* + * Convert string to signed integer. + * If required, the resulting number is checked to be in the <min,max> range. + */ +gboolean +nmc_string_to_int_base (const char *str, + int base, + gboolean range_check, + long int min, + long int max, + long int *value) +{ + char *end; + long int tmp; + + errno = 0; + tmp = strtol (str, &end, base); + if (errno || *end != '\0' || (range_check && (tmp < min || tmp > max))) { + return FALSE; + } + *value = tmp; + return TRUE; +} + +/* + * Convert string to unsigned integer. + * If required, the resulting number is checked to be in the <min,max> range. + */ +gboolean +nmc_string_to_uint_base (const char *str, + int base, + gboolean range_check, + unsigned long int min, + unsigned long int max, + unsigned long int *value) +{ + char *end; + unsigned long int tmp; + + errno = 0; + tmp = strtoul (str, &end, base); + if (errno || *end != '\0' || (range_check && (tmp < min || tmp > max))) { + return FALSE; + } + *value = tmp; + return TRUE; +} + +gboolean +nmc_string_to_int (const char *str, + gboolean range_check, + long int min, + long int max, + long int *value) +{ + return nmc_string_to_int_base (str, 10, range_check, min, max, value); +} + +gboolean +nmc_string_to_uint (const char *str, + gboolean range_check, + unsigned long int min, + unsigned long int max, + unsigned long int *value) +{ + return nmc_string_to_uint_base (str, 10, range_check, min, max, value); +} + +gboolean +nmc_string_to_bool (const char *str, gboolean *val_bool, GError **error) +{ + const char *s_true[] = { "true", "yes", "on", NULL }; + const char *s_false[] = { "false", "no", "off", NULL }; + + g_return_val_if_fail (error == NULL || *error == NULL, FALSE); + + if (g_strcmp0 (str, "o") == 0) { + g_set_error (error, 1, 0, + _("'%s' is ambiguous (on x off)"), str); + return FALSE; + } + + if (nmc_string_is_valid (str, s_true, NULL)) + *val_bool = TRUE; + else if (nmc_string_is_valid (str, s_false, NULL)) + *val_bool = FALSE; + else { + g_set_error (error, 1, 0, + _("'%s' is not valid; use [%s] or [%s]"), + str, "true, yes, on", "false, no, off"); + return FALSE; + } + return TRUE; +} + +/* + * Ask user for input and return the string. + * The caller is responsible for freeing the returned string. + */ +char * +nmc_get_user_input (const char *ask_str) +{ + char *line = NULL; + size_t line_ln = 0; + ssize_t num; + + g_print ("%s", ask_str); + num = getline (&line, &line_ln, stdin); + + /* Remove newline from the string */ + if (num < 1 || (num == 1 && line[0] == '\n')) { + g_free (line); + line = NULL; + } else { + if (line[num-1] == '\n') + line[num-1] = '\0'; + } + + return line; +} + +/* + * Split string in 'line' according to 'delim' to (argument) array. + */ +int +nmc_string_to_arg_array (const char *line, const char *delim, char ***argv, int *argc) +{ + int i = 0; + char **arr; + + arr = g_strsplit_set (line ? line : "", delim ? delim : " \t", 0); + while (arr && arr[i]) + i++; + + *argc = i; + *argv = arr; + + return 0; +} + +/* + * Check whether 'input' is contained in 'allowed' array. It performs case + * insensitive comparison and supports shortcut strings if they are unique. + * Returns: a pointer to found string in allowed array on success or NULL. + * On failure: error->code : 0 - string not found; 1 - string is ambiguous + */ +const char * +nmc_string_is_valid (const char *input, const char **allowed, GError **error) +{ + const char **p; + size_t input_ln, p_len; + gboolean prev_match = FALSE; + const char *ret = NULL; + + g_return_val_if_fail (error == NULL || *error == NULL, NULL); + + if (!input || !*input) + goto finish; + + input_ln = strlen (input); + for (p = allowed; p && *p; p++) { + p_len = strlen (*p); + if (g_ascii_strncasecmp (input, *p, input_ln) == 0) { + if (input_ln == p_len) { + ret = *p; + break; + } + if (!prev_match) + ret = *p; + else { + g_set_error (error, 1, 1, _("'%s' is ambiguous (%s x %s)"), + input, ret, *p); + return NULL; + } + prev_match = TRUE; + } + } + +finish: + if (ret == NULL) { + char *valid_vals = g_strjoinv (", ", (char **) allowed); + if (!input || !*input) + g_set_error (error, 1, 0, _("missing name, try one of [%s]"), valid_vals); + else + g_set_error (error, 1, 0, _("'%s' not among [%s]"), input, valid_vals); + + g_free (valid_vals); + } + return ret; +} + +/* + * Convert string array (char **) to GSList. + * + * Returns: pointer to newly created GSList. Caller should free it. + */ +GSList * +nmc_util_strv_to_slist (char **strv) +{ + GSList *list = NULL; + guint i = 0; + + while (strv && strv[i]) + list = g_slist_prepend (list, g_strdup (strv[i++])); + + return g_slist_reverse (list); +} + +/* + * Convert string array (char **) to description string in the form of: + * "[string1, string2, ]" + * + * Returns: a newly allocated string. Caller must free it with g_free(). + */ +char * +nmc_util_strv_for_display (const char **strv) +{ + GString *result; + guint i = 0; + + result = g_string_sized_new (150); + g_string_append_c (result, '['); + while (strv && strv[i]) { + if (result->len > 1) + g_string_append (result, ", "); + g_string_append (result, strv[i]); + i++; + } + g_string_append_c (result, ']'); + + return g_string_free (result, FALSE); +} + +/* + * Wrapper function for g_strsplit_set() that removes empty strings + * from the vector as they are not useful in most cases. + */ +char ** +nmc_strsplit_set (const char *str, const char *delimiter, int max_tokens) +{ + char **result; + uint i; + uint j; + + result = g_strsplit_set (str, delimiter, max_tokens); + + /* remove empty strings */ + for (i = 0; result && result[i]; i++) { + if (*(result[i]) == '\0') { + g_free (result[i]); + for (j = i; result[j]; j++) + result[j] = result[j + 1]; + i--; + } + } + return result; +} + +/* + * Find out how many columns an UTF-8 string occupies on the screen + */ +int +nmc_string_screen_width (const char *start, const char *end) +{ + int width = 0; + + if (end == NULL) + end = start + strlen (start); + + while (start < end) { + width += g_unichar_iswide (g_utf8_get_char (start)) ? 2 : g_unichar_iszerowidth (g_utf8_get_char (start)) ? 0 : 1; + start = g_utf8_next_char (start); + } + return width; +} + +void +set_val_str (NmcOutputField fields_array[], guint32 idx, char *value) +{ + fields_array[idx].value = value; + fields_array[idx].value_is_array = FALSE; + fields_array[idx].free_value = TRUE; +} + +void +set_val_strc (NmcOutputField fields_array[], guint32 idx, const char *value) +{ + fields_array[idx].value = (char *) value; + fields_array[idx].value_is_array = FALSE; + fields_array[idx].free_value = FALSE; +} + +void +set_val_arr (NmcOutputField fields_array[], guint32 idx, char **value) +{ + fields_array[idx].value = value; + fields_array[idx].value_is_array = TRUE; + fields_array[idx].free_value = TRUE; +} + +void +set_val_arrc (NmcOutputField fields_array[], guint32 idx, const char **value) +{ + fields_array[idx].value = (char **) value; + fields_array[idx].value_is_array = TRUE; + fields_array[idx].free_value = FALSE; +} + +/* + * Free 'value' members in array of NmcOutputField + */ +void +nmc_free_output_field_values (NmcOutputField fields_array[]) +{ + NmcOutputField *iter = fields_array; + + while (iter && iter->name) { + if (iter->free_value) { + if (iter->value_is_array) + g_strfreev ((char **) iter->value); + else + g_free ((char *) iter->value); + iter->value = NULL; + } + iter++; + } +} + +/** + * parse_output_fields: + * @field_str: comma-separated field names to parse + * @fields_array: array of allowed fields + * @parse_groups: whether the fields can contain group prefix (e.g. general.driver) + * @group_fields: (out) (allow-none): array of field names for particular groups + * @error: (out) (allow-none): location to store error, or %NULL + * + * Parses comma separated fields in @fields_str according to @fields_array. + * When @parse_groups is %TRUE, fields can be in the form 'group.field'. Then + * @group_fields will be filled with the required field for particular group. + * @group_fields array corresponds to the returned array. + * Examples: + * @field_str: "type,name,uuid" | "ip4,general.device" | "ip4.address,ip6" + * returned array: 2 0 1 | 7 0 | 7 9 + * @group_fields: NULL NULL NULL | NULL "device" | "address" NULL + * + * Returns: #GArray with indices representing fields in @fields_array. + * Caller is responsible for freeing the array. + */ +GArray * +parse_output_fields (const char *fields_str, + const NmcOutputField fields_array[], + gboolean parse_groups, + GPtrArray **group_fields, + GError **error) +{ + char **fields, **iter; + GArray *array; + int i, j; + + g_return_val_if_fail (error == NULL || *error == NULL, NULL); + g_return_val_if_fail (group_fields == NULL || *group_fields == NULL, NULL); + + array = g_array_new (FALSE, FALSE, sizeof (int)); + if (parse_groups && group_fields) + *group_fields = g_ptr_array_new_full (20, (GDestroyNotify) g_free); + + /* Split supplied fields string */ + fields = g_strsplit_set (fields_str, ",", -1); + for (iter = fields; iter && *iter; iter++) { + int idx = -1; + + g_strstrip (*iter); + if (parse_groups) { + /* e.g. "general.device,general.driver,ip4,ip6" */ + gboolean found = FALSE; + char *left = *iter; + char *right = strchr (*iter, '.'); + + if (right) + *right++ = '\0'; + + for (i = 0; fields_array[i].name; i++) { + if (strcasecmp (left, fields_array[i].name) == 0) { + NmcOutputField *valid_names = fields_array[i].group; + idx = i; + if (!right && !valid_names) { + found = TRUE; + break; + } + for (j = 0; valid_names && valid_names[j].name; j++) { + if (!right || strcasecmp (right, valid_names[j].name) == 0) { + found = TRUE; + break; + } + } + if (found) + break; + } + } + if (found) { + /* Add index to array, and field name (or NULL) to group_fields array */ + g_array_append_val (array, idx); + if (group_fields && *group_fields) + g_ptr_array_add (*group_fields, g_strdup (right)); + } + if (right) + *(right-1) = '.'; /* Restore the original string */ + } else { + /* e.g. "general,ip4,ip6" */ + for (i = 0; fields_array[i].name; i++) { + if (strcasecmp (*iter, fields_array[i].name) == 0) { + g_array_append_val (array, i); + break; + } + } + } + + /* Field was not found - error case */ + if (fields_array[i].name == NULL) { + /* Set GError */ + if (!strcasecmp (*iter, "all") || !strcasecmp (*iter, "common")) + g_set_error (error, NMCLI_ERROR, 0, _("field '%s' has to be alone"), *iter); + else { + char *allowed_fields = nmc_get_allowed_fields (fields_array, idx); + g_set_error (error, NMCLI_ERROR, 1, _("invalid field '%s'; allowed fields: %s"), + *iter, allowed_fields); + g_free (allowed_fields); + } + + /* Free arrays on error */ + g_array_free (array, TRUE); + array = NULL; + if (group_fields && *group_fields) { + g_ptr_array_free (*group_fields, TRUE); + *group_fields = NULL; + } + goto done; + } + } +done: + if (fields) + g_strfreev (fields); + return array; +} + +/** +* nmc_get_allowed_fields: +* @fields_array: array of fields +* @group_idx: index to the array (for second-level array in 'group' member), +* or -1 +* +* Returns: string of allowed fields names. +* Caller is responsible for freeing the array. +*/ +char * +nmc_get_allowed_fields (const NmcOutputField fields_array[], int group_idx) +{ + GString *allowed_fields = g_string_sized_new (256); + int i; + + if (group_idx != -1 && fields_array[group_idx].group) { + NmcOutputField *second_level = fields_array[group_idx].group; + for (i = 0; second_level[i].name; i++) + g_string_append_printf (allowed_fields, "%s.%s,", + fields_array[group_idx].name, second_level[i].name); + } else { + for (i = 0; fields_array[i].name; i++) + g_string_append_printf (allowed_fields, "%s,", fields_array[i].name); + } + g_string_truncate (allowed_fields, allowed_fields->len - 1); + + return g_string_free (allowed_fields, FALSE); +} + +gboolean +nmc_terse_option_check (NMCPrintOutput print_output, const char *fields, GError **error) +{ + g_return_val_if_fail (error == NULL || *error == NULL, FALSE); + + if (print_output == NMC_PRINT_TERSE) { + if (!fields) { + g_set_error_literal (error, NMCLI_ERROR, 0, _("Option '--terse' requires specifying '--fields'")); + return FALSE; + } else if ( !strcasecmp (fields, "all") + || !strcasecmp (fields, "common")) { + g_set_error (error, NMCLI_ERROR, 0, _("Option '--terse' requires specific '--fields' option values , not '%s'"), fields); + return FALSE; + } + } + return TRUE; +} + +NmcOutputField * +nmc_dup_fields_array (NmcOutputField fields[], size_t size, guint32 flags) +{ + NmcOutputField *row; + + row = g_malloc0 (size); + memcpy (row, fields, size); + row[0].flags = flags; + + return row; +} + +void +nmc_empty_output_fields (NmCli *nmc) +{ + guint i; + + /* Free values in field structure */ + for (i = 0; i < nmc->output_data->len; i++) { + NmcOutputField *fld_arr = g_ptr_array_index (nmc->output_data, i); + nmc_free_output_field_values (fld_arr); + } + + /* Empty output_data array */ + if (nmc->output_data->len > 0) + g_ptr_array_remove_range (nmc->output_data, 0, nmc->output_data->len); + + if (nmc->print_fields.indices) { + g_array_free (nmc->print_fields.indices, TRUE); + nmc->print_fields.indices = NULL; + } +} + +static char * +get_value_to_print (NmcOutputField *fields, + gboolean field_name, + const char *not_set_str, + gboolean *dealloc) +{ + gboolean is_array = fields->value_is_array; + char *value; + + if (field_name) + value = _(fields->name_l10n); + else + value = fields->value ? + (is_array ? g_strjoinv (" | ", (char **) fields->value) : + (char *) fields->value) : + (char *) not_set_str; + *dealloc = fields->value && is_array && !field_name; + return value; +} + +/* + * Print both headers or values of 'field_values' array. + * Entries to print and their order are specified via indices in + * 'nmc->print_fields.indices' array. + * Various flags influencing the output of fields are set up in the first item + * of 'field_values' array. + */ +void +print_required_fields (NmCli *nmc, const NmcOutputField field_values[]) +{ + GString *str; + int width1, width2; + int table_width = 0; + char *line = NULL; + char *indent_str; + const char *not_set_str = "--"; + int i; + const NmcPrintFields fields = nmc->print_fields; + gboolean multiline = nmc->multiline_output; + gboolean terse = (nmc->print_output == NMC_PRINT_TERSE); + gboolean pretty = (nmc->print_output == NMC_PRINT_PRETTY); + gboolean escape = nmc->escape_values; + gboolean main_header_add = field_values[0].flags & NMC_OF_FLAG_MAIN_HEADER_ADD; + gboolean main_header_only = field_values[0].flags & NMC_OF_FLAG_MAIN_HEADER_ONLY; + gboolean field_names = field_values[0].flags & NMC_OF_FLAG_FIELD_NAMES; + gboolean section_prefix = field_values[0].flags & NMC_OF_FLAG_SECTION_PREFIX; + gboolean main_header = main_header_add || main_header_only; + + /* No headers are printed in terse mode: + * - neither main header nor field (column) names + */ + if ((main_header_only || field_names) && terse) + return; + + if (multiline) { + /* --- Multiline mode --- */ + enum { ML_HEADER_WIDTH = 79 }; + enum { ML_VALUE_INDENT = 40 }; + if (main_header && pretty) { + /* Print the main header */ + int header_width = nmc_string_screen_width (fields.header_name, NULL) + 4; + table_width = header_width < ML_HEADER_WIDTH ? ML_HEADER_WIDTH : header_width; + + line = g_strnfill (ML_HEADER_WIDTH, '='); + width1 = strlen (fields.header_name); + width2 = nmc_string_screen_width (fields.header_name, NULL); + g_print ("%s\n", line); + g_print ("%*s\n", (table_width + width2)/2 + width1 - width2, fields.header_name); + g_print ("%s\n", line); + g_free (line); + } + + /* Print values */ + if (!main_header_only && !field_names) { + for (i = 0; i < fields.indices->len; i++) { + char *tmp; + int idx = g_array_index (fields.indices, int, i); + gboolean is_array = field_values[idx].value_is_array; + + /* section prefix can't be an array */ + g_assert (!is_array || !section_prefix || idx != 0); + + if (section_prefix && idx == 0) /* The first field is section prefix */ + continue; + + if (is_array) { + /* value is a null-terminated string array */ + const char **p; + int j; + + for (p = (const char **) field_values[idx].value, j = 1; p && *p; p++, j++) { + tmp = g_strdup_printf ("%s%s%s[%d]:", + section_prefix ? (const char*) field_values[0].value : "", + section_prefix ? "." : "", + _(field_values[idx].name_l10n), + j); + width1 = strlen (tmp); + width2 = nmc_string_screen_width (tmp, NULL); + g_print ("%-*s%s\n", terse ? 0 : ML_VALUE_INDENT+width1-width2, tmp, + *p ? *p : not_set_str); + g_free (tmp); + } + } else { + /* value is a string */ + const char *hdr_name = (const char*) field_values[0].value; + const char *val = (const char*) field_values[idx].value; + + tmp = g_strdup_printf ("%s%s%s:", + section_prefix ? hdr_name : "", + section_prefix ? "." : "", + _(field_values[idx].name_l10n)); + width1 = strlen (tmp); + width2 = nmc_string_screen_width (tmp, NULL); + g_print ("%-*s%s\n", terse ? 0 : ML_VALUE_INDENT+width1-width2, tmp, + val ? val : not_set_str); + g_free (tmp); + } + } + if (pretty) { + line = g_strnfill (ML_HEADER_WIDTH, '-'); + g_print ("%s\n", line); + g_free (line); + } + } + return; + } + + /* --- Tabular mode: each line = one object --- */ + str = g_string_new (NULL); + + for (i = 0; i < fields.indices->len; i++) { + int idx = g_array_index (fields.indices, int, i); + gboolean dealloc; + char *value = get_value_to_print ((NmcOutputField *) field_values+idx, field_names, not_set_str, &dealloc); + + if (terse) { + if (escape) { + const char *p = value; + while (*p) { + if (*p == ':' || *p == '\\') + g_string_append_c (str, '\\'); /* Escaping by '\' */ + g_string_append_c (str, *p); + p++; + } + } + else + g_string_append_printf (str, "%s", value); + g_string_append_c (str, ':'); /* Column separator */ + } else { + width1 = strlen (value); + width2 = nmc_string_screen_width (value, NULL); /* Width of the string (in screen colums) */ + g_string_append_printf (str, "%-*s", field_values[idx].width + width1 - width2, strlen (value) > 0 ? value : "--"); + g_string_append_c (str, ' '); /* Column separator */ + table_width += field_values[idx].width + width1 - width2 + 1; + } + + if (dealloc) + g_free (value); + } + + /* Print the main table header */ + if (main_header && pretty) { + int header_width = nmc_string_screen_width (fields.header_name, NULL) + 4; + table_width = table_width < header_width ? header_width : table_width; + + line = g_strnfill (table_width, '='); + width1 = strlen (fields.header_name); + width2 = nmc_string_screen_width (fields.header_name, NULL); + g_print ("%s\n", line); + g_print ("%*s\n", (table_width + width2)/2 + width1 - width2, fields.header_name); + g_print ("%s\n", line); + g_free (line); + } + + /* Print actual values */ + if (!main_header_only && str->len > 0) { + g_string_truncate (str, str->len-1); /* Chop off last column separator */ + if (fields.indent > 0) { + indent_str = g_strnfill (fields.indent, ' '); + g_string_prepend (str, indent_str); + g_free (indent_str); + } + g_print ("%s\n", str->str); + } + + /* Print horizontal separator */ + if (!main_header_only && field_names && pretty) { + if (str->len > 0) { + line = g_strnfill (table_width, '-'); + g_print ("%s\n", line); + g_free (line); + } + } + + g_string_free (str, TRUE); +} + +/* + * Print nmc->output_data + * + * It first finds out maximal string length in columns and fill the value to + * 'width' member of NmcOutputField, so that columns in tabular output are + * properly aligned. Then each object (row in tabular) is printed using + * print_required_fields() function. + */ +void +print_data (NmCli *nmc) +{ + int i, j; + size_t len; + NmcOutputField *row; + int num_fields = 0; + + if (!nmc->output_data || nmc->output_data->len < 1) + return; + + /* How many fields? */ + row = g_ptr_array_index (nmc->output_data, 0); + while (row->name) { + num_fields++; + row++; + } + + /* Find out maximal string lengths */ + for (i = 0; i < num_fields; i++) { + size_t max_width = 0; + for (j = 0; j < nmc->output_data->len; j++) { + gboolean field_names, dealloc; + char *value; + row = g_ptr_array_index (nmc->output_data, j); + field_names = row[0].flags & NMC_OF_FLAG_FIELD_NAMES; + value = get_value_to_print (row+i, field_names, "--", &dealloc); + len = nmc_string_screen_width (value, NULL); + max_width = len > max_width ? len : max_width; + if (dealloc) + g_free (value); + } + for (j = 0; j < nmc->output_data->len; j++) { + row = g_ptr_array_index (nmc->output_data, j); + row[i].width = max_width + 1; + } + } + + /* Now we can print the data. */ + for (i = 0; i < nmc->output_data->len; i++) { + row = g_ptr_array_index (nmc->output_data, i); + print_required_fields (nmc, row); + } +} + +/* +* Compare versions of nmcli and NM daemon. +* Return: TRUE - the versions match (when only major and minor match, print a warning) +* FALSE - versions mismatch +*/ +gboolean +nmc_versions_match (NmCli *nmc) +{ + const char *nm_ver = NULL; + const char *dot; + gboolean match = FALSE; + + g_return_val_if_fail (nmc != NULL, FALSE); + + /* --nocheck option - don't compare the versions */ + if (nmc->nocheck_ver) + return TRUE; + + nmc->get_client (nmc); + nm_ver = nm_client_get_version (nmc->client); + if (nm_ver) { + if (!strcmp (nm_ver, VERSION)) + match = TRUE; + else { + dot = strchr (nm_ver, '.'); + if (dot) { + dot = strchr (dot + 1, '.'); + if (dot && !strncmp (nm_ver, VERSION, dot-nm_ver)) { + g_printerr (_("Warning: nmcli (%s) and NetworkManager (%s) versions don't match. Use --nocheck to suppress the warning.\n"), + VERSION, nm_ver); + match = TRUE; + } + } + } + } + + if (!match) { + g_string_printf (nmc->return_text, _("Error: nmcli (%s) and NetworkManager (%s) versions don't match. Force execution using --nocheck, but the results are unpredictable."), + VERSION, nm_ver ? nm_ver : _("unknown")); + nmc->return_value = NMC_RESULT_ERROR_VERSIONS_MISMATCH; + } + + return match; +} + diff --git a/clients/cli/utils.h b/clients/cli/utils.h new file mode 100644 index 00000000..fc43b0ed --- /dev/null +++ b/clients/cli/utils.h @@ -0,0 +1,96 @@ +/* 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. + */ + +#ifndef NMC_UTILS_H +#define NMC_UTILS_H + +#include "nmcli.h" + +/* === Types === */ + +typedef struct { + const char *name; + gboolean has_value; + const char **value; + gboolean mandatory; + gboolean found; +} nmc_arg_t; + +/* === Functions === */ +int matches (const char *cmd, const char *pattern); +int next_arg (int *argc, char ***argv); +gboolean nmc_arg_is_help (const char *arg); +gboolean nmc_arg_is_option (const char *arg, const char *opt_name); +gboolean nmc_parse_args (nmc_arg_t *arg_arr, gboolean last, int *argc, char ***argv, GError **error); +char *ssid_to_hex (const char *str, gsize len); +gboolean nmc_string_to_int_base (const char *str, + int base, + gboolean range_check, + long int min, + long int max, + long int *value); +gboolean nmc_string_to_uint_base (const char *str, + int base, + gboolean range_check, + unsigned long int min, + unsigned long int max, + unsigned long int *value); +gboolean nmc_string_to_int (const char *str, + gboolean range_check, + long int min, + long int max, + long int *value); +gboolean nmc_string_to_uint (const char *str, + gboolean range_check, + unsigned long int min, + unsigned long int max, + unsigned long int *value); +gboolean nmc_string_to_bool (const char *str, gboolean *val_bool, GError **error); +char *nmc_ip4_address_as_string (guint32 ip, GError **error); +char *nmc_ip6_address_as_string (const struct in6_addr *ip, GError **error); +void nmc_terminal_erase_line (void); +void nmc_terminal_show_progress (const char *str); +const char *nmc_term_color_sequence (NmcTermColor color); +char *nmc_colorize (NmcTermColor color, const char * fmt, ...); +char *nmc_get_user_input (const char *ask_str); +int nmc_string_to_arg_array (const char *line, const char *delim, char ***argv, int *argc); +const char *nmc_string_is_valid (const char *input, const char **allowed, GError **error); +GSList *nmc_util_strv_to_slist (char **strv); +char * nmc_util_strv_for_display (const char **strv); +char **nmc_strsplit_set (const char *str, const char *delimiter, int max_tokens); +int nmc_string_screen_width (const char *start, const char *end); +void set_val_str (NmcOutputField fields_array[], guint32 index, char *value); +void set_val_strc (NmcOutputField fields_array[], guint32 index, const char *value); +void set_val_arr (NmcOutputField fields_array[], guint32 index, char **value); +void set_val_arrc (NmcOutputField fields_array[], guint32 index, const char **value); +void nmc_free_output_field_values (NmcOutputField fields_array[]); +GArray *parse_output_fields (const char *fields_str, + const NmcOutputField fields_array[], + gboolean parse_groups, + GPtrArray **group_fields, + GError **error); +char *nmc_get_allowed_fields (const NmcOutputField fields_array[], int group_idx); +gboolean nmc_terse_option_check (NMCPrintOutput print_output, const char *fields, GError **error); +NmcOutputField *nmc_dup_fields_array (NmcOutputField fields[], size_t size, guint32 flags); +void nmc_empty_output_fields (NmCli *nmc); +void print_required_fields (NmCli *nmc, const NmcOutputField field_values[]); +void print_data (NmCli *nmc); +gboolean nmc_versions_match (NmCli *nmc); + +#endif /* NMC_UTILS_H */ |