pax_global_header00006660000000000000000000000064140157521020014507gustar00rootroot0000000000000052 comment=0f8d257612a5c62000a55fd00e79a35f813f0375 libcli-1.10.7/000077500000000000000000000000001401575210200130335ustar00rootroot00000000000000libcli-1.10.7/.clang-format000066400000000000000000000001351401575210200154050ustar00rootroot00000000000000BasedOnStyle: Google IndentWidth: 2 ColumnLimit: 120 AllowShortFunctionsOnASingleLine: Empty libcli-1.10.7/.clang-tidy000066400000000000000000000025261401575210200150740ustar00rootroot00000000000000--- Checks: 'clang-diagnostic-*,clang-analyzer-*' WarningsAsErrors: '' HeaderFilterRegex: '' AnalyzeTemporaryDtors: false FormatStyle: none User: dparrish CheckOptions: - key: cert-dcl16-c.NewSuffixes value: 'L;LL;LU;LLU' - key: cppcoreguidelines-non-private-member-variables-in-classes.IgnoreClassesWithAllMemberVariablesBeingPublic value: '1' - key: google-readability-braces-around-statements.ShortStatementLines value: '1' - key: google-readability-function-size.StatementThreshold value: '800' - key: google-readability-namespace-comments.ShortNamespaceLines value: '10' - key: google-readability-namespace-comments.SpacesBeforeComments value: '2' - key: modernize-loop-convert.MaxCopySize value: '16' - key: modernize-loop-convert.MinConfidence value: reasonable - key: modernize-loop-convert.NamingStyle value: CamelCase - key: modernize-pass-by-value.IncludeStyle value: llvm - key: modernize-replace-auto-ptr.IncludeStyle value: llvm - key: modernize-use-nullptr.NullMacros value: 'NULL' ... libcli-1.10.7/.github/000077500000000000000000000000001401575210200143735ustar00rootroot00000000000000libcli-1.10.7/.github/workflows/000077500000000000000000000000001401575210200164305ustar00rootroot00000000000000libcli-1.10.7/.github/workflows/c.yml000066400000000000000000000003341401575210200173750ustar00rootroot00000000000000name: C CI on: push: branches: - stable pull_request: branches: - stable jobs: build: runs-on: ubuntu-latest steps: - uses: actions/checkout@v1 - name: make run: make libcli-1.10.7/.gitignore000066400000000000000000000000621401575210200150210ustar00rootroot00000000000000*.o *.a *.so *.so.* clitest compile_commands.json libcli-1.10.7/.travis.yml000066400000000000000000000000751401575210200151460ustar00rootroot00000000000000os: linux arch: - amd64 - ppc64le language: c script: make libcli-1.10.7/COPYING000066400000000000000000000636321401575210200141000ustar00rootroot00000000000000 GNU LESSER GENERAL PUBLIC LICENSE Version 2.1, February 1999 Copyright (C) 1991, 1999 Free Software Foundation, Inc. 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. [This is the first released version of the Lesser GPL. It also counts as the successor of the GNU Library Public License, version 2, hence the version number 2.1.] Preamble The licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public Licenses are intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users. This license, the Lesser General Public License, applies to some specially designated software packages--typically libraries--of the Free Software Foundation and other authors who decide to use it. You can use it too, but we suggest you first think carefully about whether this license or the ordinary General Public License is the better strategy to use in any particular case, based on the explanations below. When we speak of free software, we are referring to freedom of use, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for this service if you wish); that you receive source code or can get it if you want it; that you can change the software and use pieces of it in new free programs; and that you are informed that you can do these things. To protect your rights, we need to make restrictions that forbid distributors to deny you these rights or to ask you to surrender these rights. These restrictions translate to certain responsibilities for you if you distribute copies of the library or if you modify it. For example, if you distribute copies of the library, whether gratis or for a fee, you must give the recipients all the rights that we gave you. You must make sure that they, too, receive or can get the source code. If you link other code with the library, you must provide complete object files to the recipients, so that they can relink them with the library after making changes to the library and recompiling it. And you must show them these terms so they know their rights. We protect your rights with a two-step method: (1) we copyright the library, and (2) we offer you this license, which gives you legal permission to copy, distribute and/or modify the library. To protect each distributor, we want to make it very clear that there is no warranty for the free library. Also, if the library is modified by someone else and passed on, the recipients should know that what they have is not the original version, so that the original author's reputation will not be affected by problems that might be introduced by others. Finally, software patents pose a constant threat to the existence of any free program. We wish to make sure that a company cannot effectively restrict the users of a free program by obtaining a restrictive license from a patent holder. Therefore, we insist that any patent license obtained for a version of the library must be consistent with the full freedom of use specified in this license. Most GNU software, including some libraries, is covered by the ordinary GNU General Public License. This license, the GNU Lesser General Public License, applies to certain designated libraries, and is quite different from the ordinary General Public License. We use this license for certain libraries in order to permit linking those libraries into non-free programs. When a program is linked with a library, whether statically or using a shared library, the combination of the two is legally speaking a combined work, a derivative of the original library. The ordinary General Public License therefore permits such linking only if the entire combination fits its criteria of freedom. The Lesser General Public License permits more lax criteria for linking other code with the library. We call this license the "Lesser" General Public License because it does Less to protect the user's freedom than the ordinary General Public License. It also provides other free software developers Less of an advantage over competing non-free programs. These disadvantages are the reason we use the ordinary General Public License for many libraries. However, the Lesser license provides advantages in certain special circumstances. For example, on rare occasions, there may be a special need to encourage the widest possible use of a certain library, so that it becomes a de-facto standard. To achieve this, non-free programs must be allowed to use the library. A more frequent case is that a free library does the same job as widely used non-free libraries. In this case, there is little to gain by limiting the free library to free software only, so we use the Lesser General Public License. In other cases, permission to use a particular library in non-free programs enables a greater number of people to use a large body of free software. For example, permission to use the GNU C Library in non-free programs enables many more people to use the whole GNU operating system, as well as its variant, the GNU/Linux operating system. Although the Lesser General Public License is Less protective of the users' freedom, it does ensure that the user of a program that is linked with the Library has the freedom and the wherewithal to run that program using a modified version of the Library. The precise terms and conditions for copying, distribution and modification follow. Pay close attention to the difference between a "work based on the library" and a "work that uses the library". The former contains code derived from the library, whereas the latter must be combined with the library in order to run. GNU LESSER GENERAL PUBLIC LICENSE TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 0. This License Agreement applies to any software library or other program which contains a notice placed by the copyright holder or other authorized party saying it may be distributed under the terms of this Lesser General Public License (also called "this License"). Each licensee is addressed as "you". A "library" means a collection of software functions and/or data prepared so as to be conveniently linked with application programs (which use some of those functions and data) to form executables. The "Library", below, refers to any such software library or work which has been distributed under these terms. A "work based on the Library" means either the Library or any derivative work under copyright law: that is to say, a work containing the Library or a portion of it, either verbatim or with modifications and/or translated straightforwardly into another language. (Hereinafter, translation is included without limitation in the term "modification".) "Source code" for a work means the preferred form of the work for making modifications to it. For a library, complete source code means all the source code for all modules it contains, plus any associated interface definition files, plus the scripts used to control compilation and installation of the library. Activities other than copying, distribution and modification are not covered by this License; they are outside its scope. The act of running a program using the Library is not restricted, and output from such a program is covered only if its contents constitute a work based on the Library (independent of the use of the Library in a tool for writing it). Whether that is true depends on what the Library does and what the program that uses the Library does. 1. You may copy and distribute verbatim copies of the Library's complete source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice and disclaimer of warranty; keep intact all the notices that refer to this License and to the absence of any warranty; and distribute a copy of this License along with the Library. You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee. 2. You may modify your copy or copies of the Library or any portion of it, thus forming a work based on the Library, and copy and distribute such modifications or work under the terms of Section 1 above, provided that you also meet all of these conditions: a) The modified work must itself be a software library. b) You must cause the files modified to carry prominent notices stating that you changed the files and the date of any change. c) You must cause the whole of the work to be licensed at no charge to all third parties under the terms of this License. d) If a facility in the modified Library refers to a function or a table of data to be supplied by an application program that uses the facility, other than as an argument passed when the facility is invoked, then you must make a good faith effort to ensure that, in the event an application does not supply such function or table, the facility still operates, and performs whatever part of its purpose remains meaningful. (For example, a function in a library to compute square roots has a purpose that is entirely well-defined independent of the application. Therefore, Subsection 2d requires that any application-supplied function or table used by this function must be optional: if the application does not supply it, the square root function must still compute square roots.) These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Library, and can be reasonably considered independent and separate works in themselves, then this License, and its terms, do not apply to those sections when you distribute them as separate works. But when you distribute the same sections as part of a whole which is a work based on the Library, the distribution of the whole must be on the terms of this License, whose permissions for other licensees extend to the entire whole, and thus to each and every part regardless of who wrote it. Thus, it is not the intent of this section to claim rights or contest your rights to work written entirely by you; rather, the intent is to exercise the right to control the distribution of derivative or collective works based on the Library. In addition, mere aggregation of another work not based on the Library with the Library (or with a work based on the Library) on a volume of a storage or distribution medium does not bring the other work under the scope of this License. 3. You may opt to apply the terms of the ordinary GNU General Public License instead of this License to a given copy of the Library. To do this, you must alter all the notices that refer to this License, so that they refer to the ordinary GNU General Public License, version 2, instead of to this License. (If a newer version than version 2 of the ordinary GNU General Public License has appeared, then you can specify that version instead if you wish.) Do not make any other change in these notices. Once this change is made in a given copy, it is irreversible for that copy, so the ordinary GNU General Public License applies to all subsequent copies and derivative works made from that copy. This option is useful when you wish to copy part of the code of the Library into a program that is not a library. 4. You may copy and distribute the Library (or a portion or derivative of it, under Section 2) in object code or executable form under the terms of Sections 1 and 2 above provided that you accompany it with the complete corresponding machine-readable source code, which must be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange. If distribution of object code is made by offering access to copy from a designated place, then offering equivalent access to copy the source code from the same place satisfies the requirement to distribute the source code, even though third parties are not compelled to copy the source along with the object code. 5. A program that contains no derivative of any portion of the Library, but is designed to work with the Library by being compiled or linked with it, is called a "work that uses the Library". Such a work, in isolation, is not a derivative work of the Library, and therefore falls outside the scope of this License. However, linking a "work that uses the Library" with the Library creates an executable that is a derivative of the Library (because it contains portions of the Library), rather than a "work that uses the library". The executable is therefore covered by this License. Section 6 states terms for distribution of such executables. When a "work that uses the Library" uses material from a header file that is part of the Library, the object code for the work may be a derivative work of the Library even though the source code is not. Whether this is true is especially significant if the work can be linked without the Library, or if the work is itself a library. The threshold for this to be true is not precisely defined by law. If such an object file uses only numerical parameters, data structure layouts and accessors, and small macros and small inline functions (ten lines or less in length), then the use of the object file is unrestricted, regardless of whether it is legally a derivative work. (Executables containing this object code plus portions of the Library will still fall under Section 6.) Otherwise, if the work is a derivative of the Library, you may distribute the object code for the work under the terms of Section 6. Any executables containing that work also fall under Section 6, whether or not they are linked directly with the Library itself. 6. As an exception to the Sections above, you may also combine or link a "work that uses the Library" with the Library to produce a work containing portions of the Library, and distribute that work under terms of your choice, provided that the terms permit modification of the work for the customer's own use and reverse engineering for debugging such modifications. You must give prominent notice with each copy of the work that the Library is used in it and that the Library and its use are covered by this License. You must supply a copy of this License. If the work during execution displays copyright notices, you must include the copyright notice for the Library among them, as well as a reference directing the user to the copy of this License. Also, you must do one of these things: a) Accompany the work with the complete corresponding machine-readable source code for the Library including whatever changes were used in the work (which must be distributed under Sections 1 and 2 above); and, if the work is an executable linked with the Library, with the complete machine-readable "work that uses the Library", as object code and/or source code, so that the user can modify the Library and then relink to produce a modified executable containing the modified Library. (It is understood that the user who changes the contents of definitions files in the Library will not necessarily be able to recompile the application to use the modified definitions.) b) Use a suitable shared library mechanism for linking with the Library. A suitable mechanism is one that (1) uses at run time a copy of the library already present on the user's computer system, rather than copying library functions into the executable, and (2) will operate properly with a modified version of the library, if the user installs one, as long as the modified version is interface-compatible with the version that the work was made with. c) Accompany the work with a written offer, valid for at least three years, to give the same user the materials specified in Subsection 6a, above, for a charge no more than the cost of performing this distribution. d) If distribution of the work is made by offering access to copy from a designated place, offer equivalent access to copy the above specified materials from the same place. e) Verify that the user has already received a copy of these materials or that you have already sent this user a copy. For an executable, the required form of the "work that uses the Library" must include any data and utility programs needed for reproducing the executable from it. However, as a special exception, the materials to be distributed need not include anything that is normally distributed (in either source or binary form) with the major components (compiler, kernel, and so on) of the operating system on which the executable runs, unless that component itself accompanies the executable. It may happen that this requirement contradicts the license restrictions of other proprietary libraries that do not normally accompany the operating system. Such a contradiction means you cannot use both them and the Library together in an executable that you distribute. 7. You may place library facilities that are a work based on the Library side-by-side in a single library together with other library facilities not covered by this License, and distribute such a combined library, provided that the separate distribution of the work based on the Library and of the other library facilities is otherwise permitted, and provided that you do these two things: a) Accompany the combined library with a copy of the same work based on the Library, uncombined with any other library facilities. This must be distributed under the terms of the Sections above. b) Give prominent notice with the combined library of the fact that part of it is a work based on the Library, and explaining where to find the accompanying uncombined form of the same work. 8. You may not copy, modify, sublicense, link with, or distribute the Library except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense, link with, or distribute the Library is void, and will automatically terminate your rights under this License. However, parties who have received copies, or rights, from you under this License will not have their licenses terminated so long as such parties remain in full compliance. 9. You are not required to accept this License, since you have not signed it. However, nothing else grants you permission to modify or distribute the Library or its derivative works. These actions are prohibited by law if you do not accept this License. Therefore, by modifying or distributing the Library (or any work based on the Library), you indicate your acceptance of this License to do so, and all its terms and conditions for copying, distributing or modifying the Library or works based on it. 10. Each time you redistribute the Library (or any work based on the Library), the recipient automatically receives a license from the original licensor to copy, distribute, link with or modify the Library subject to these terms and conditions. You may not impose any further restrictions on the recipients' exercise of the rights granted herein. You are not responsible for enforcing compliance by third parties with this License. 11. If, as a consequence of a court judgment or allegation of patent infringement or for any other reason (not limited to patent issues), conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot distribute so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not distribute the Library at all. For example, if a patent license would not permit royalty-free redistribution of the Library by all those who receive copies directly or indirectly through you, then the only way you could satisfy both it and this License would be to refrain entirely from distribution of the Library. If any portion of this section is held invalid or unenforceable under any particular circumstance, the balance of the section is intended to apply, and the section as a whole is intended to apply in other circumstances. It is not the purpose of this section to induce you to infringe any patents or other property right claims or to contest validity of any such claims; this section has the sole purpose of protecting the integrity of the free software distribution system which is implemented by public license practices. Many people have made generous contributions to the wide range of software distributed through that system in reliance on consistent application of that system; it is up to the author/donor to decide if he or she is willing to distribute software through any other system and a licensee cannot impose that choice. This section is intended to make thoroughly clear what is believed to be a consequence of the rest of this License. 12. If the distribution and/or use of the Library is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Library under this License may add an explicit geographical distribution limitation excluding those countries, so that distribution is permitted only in or among countries not thus excluded. In such case, this License incorporates the limitation as if written in the body of this License. 13. The Free Software Foundation may publish revised and/or new versions of the Lesser General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Library specifies a version number of this License which applies to it and "any later version", you have the option of following the terms and conditions either of that version or of any later version published by the Free Software Foundation. If the Library does not specify a license version number, you may choose any version ever published by the Free Software Foundation. 14. If you wish to incorporate parts of the Library into other free programs whose distribution conditions are incompatible with these, write to the author to ask for permission. For software which is copyrighted by the Free Software Foundation, write to the Free Software Foundation; we sometimes make exceptions for this. Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally. NO WARRANTY 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Libraries If you develop a new library, and you want it to be of the greatest possible use to the public, we recommend making it free software that everyone can redistribute and change. You can do so by permitting redistribution under these terms (or, alternatively, under the terms of the ordinary General Public License). To apply these terms, attach the following notices to the library. It is safest to attach them to the start of each source file to most effectively convey the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. Copyright (C) This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library 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 Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA Also add information on how to contact you by electronic and paper mail. You should also get your employer (if you work as a programmer) or your school, if any, to sign a "copyright disclaimer" for the library, if necessary. Here is a sample; alter the names: Yoyodyne, Inc., hereby disclaims all copyright interest in the library `Frob' (a library for tweaking knobs) written by James Random Hacker. , 1 April 1990 Ty Coon, President of Vice That's all there is to it! libcli-1.10.7/Makefile000066400000000000000000000051061401575210200144750ustar00rootroot00000000000000# Build dynamic library by default DYNAMIC_LIB ?= 1 # Build static library by default STATIC_LIB ?= 1 # Run tests by default TESTS ?= 1 UNAME = $(shell sh -c 'uname -s 2>/dev/null || echo not') DESTDIR = PREFIX = /usr/local MAJOR = 1 MINOR = 10 REVISION = 7 LIB = libcli.so LIB_STATIC = libcli.a CC = gcc AR = ar ARFLAGS = rcs DEBUG = -g OPTIM = -O3 override CFLAGS += $(DEBUG) $(OPTIM) -Wall -std=c99 -pedantic -Wformat-security -Wno-format-zero-length -Werror -Wwrite-strings -Wformat -fdiagnostics-show-option -Wextra -Wsign-compare -Wcast-align -Wno-unused-parameter override LDFLAGS += -shared override LIBPATH += -L. ifeq ($(UNAME),Darwin) override LDFLAGS += -Wl,-install_name,$(LIB).$(MAJOR).$(MINOR) else override LDFLAGS += -Wl,-soname,$(LIB).$(MAJOR).$(MINOR) LIBS = -lcrypt endif ifeq (1,$(DYNAMIC_LIB)) TARGET_LIBS += $(LIB) endif ifeq (1,$(STATIC_LIB)) TARGET_LIBS += $(LIB_STATIC) endif all: $(TARGET_LIBS) $(if $(filter 1,$(TESTS)),clitest) $(LIB): libcli.o $(CC) -o $(LIB).$(MAJOR).$(MINOR).$(REVISION) $^ $(LDFLAGS) $(LIBS) -rm -f $(LIB) $(LIB).$(MAJOR).$(MINOR) ln -s $(LIB).$(MAJOR).$(MINOR).$(REVISION) $(LIB).$(MAJOR).$(MINOR) ln -s $(LIB).$(MAJOR).$(MINOR) $(LIB) $(LIB_STATIC): libcli.o $(AR) $(ARFLAGS) $@ $^ %.o: %.c $(CC) $(CPPFLAGS) $(CFLAGS) -fPIC -o $@ -c $< libcli.o: libcli.h clitest: clitest.o $(LIB) $(CC) $(CPPFLAGS) $(CFLAGS) -o $@ $< -L. -lcli clitest.exe: clitest.c libcli.o $(CC) $(CPPFLAGS) $(CFLAGS) -o $@ $< libcli.o -lws2_32 clean: rm -f *.o $(LIB)* $(LIB_STATIC) clitest libcli-$(MAJOR).$(MINOR).$(REVISION).tar.gz install: $(TARGET_LIBS) install -d $(DESTDIR)$(PREFIX)/include $(DESTDIR)$(PREFIX)/lib install -m 0644 libcli.h $(DESTDIR)$(PREFIX)/include ifeq (1,$(STATIC_LIB)) install -m 0644 $(LIB_STATIC) $(DESTDIR)$(PREFIX)/lib endif ifeq (1,$(DYNAMIC_LIB)) install -m 0755 $(LIB).$(MAJOR).$(MINOR).$(REVISION) $(DESTDIR)$(PREFIX)/lib cd $(DESTDIR)$(PREFIX)/lib && \ ln -fs $(LIB).$(MAJOR).$(MINOR).$(REVISION) $(LIB).$(MAJOR).$(MINOR) && \ ln -fs $(LIB).$(MAJOR).$(MINOR) $(LIB) endif rpmprep: rm -rf libcli-$(MAJOR).$(MINOR).$(REVISION) mkdir libcli-$(MAJOR).$(MINOR).$(REVISION) cp -R libcli.c libcli.h libcli.spec clitest.c Makefile COPYING README.md doc libcli-$(MAJOR).$(MINOR).$(REVISION) tar zcvf libcli-$(MAJOR).$(MINOR).$(REVISION).tar.gz --exclude CVS --exclude *.tar.gz libcli-$(MAJOR).$(MINOR).$(REVISION) rm -rf libcli-$(MAJOR).$(MINOR).$(REVISION) rpm: rpmprep rpmbuild -ta libcli-$(MAJOR).$(MINOR).$(REVISION).tar.gz --define "debug_package %{nil}" --clean lint: clang-tidy -quiet -warnings-as-errors *.c *.h libcli-1.10.7/README.md000066400000000000000000000111171401575210200143130ustar00rootroot00000000000000Libcli provides a shared C library for including a Cisco-like command-line interface into other software. It’s a telnet interface which supports command-line editing, history, authentication and callbacks for a user-definable function tree. To compile: ```sh $ make $ make install ``` Note - as of version 1.10.5 you have a compile time decision on using select() or poll() in cli_loop(). The default is to use the legacy 'select()' call. If built with 'CFLAGS=-DLIBCLI_USE_POLL make' then the poll() system call will be used instead. One additional check is being made now in cli_loop() to ensure that the passed file descriptor is in range. If not, an error message will be sent and the cli_loop() will exit in the child process with CLI_ERROR. This will install `libcli.so` into `/usr/local/lib`. If you want to change the location, edit Makefile. There is a test application built called clitest. Run this and telnet to port 8000. By default, a single username and password combination is enabled. ``` Username: fred Password: nerk ``` Get help by entering `help` or hitting `?`. libcli provides support for using the arrow keys for command-line editing. Up and Down arrows will cycle through the command history, and Left & Right can be used for editing the current command line. libcli also works out the shortest way of entering a command, so if you have a command `show users | grep foobar` defined, you can enter `sh us | g foobar` if that is the shortest possible way of doing it. Enter `sh?` at the command line to get a list of commands starting with `sh` A few commands are defined in every libcli program: * `help` * `quit` * `exit` * `logout` * `history` Use in your own code: First of all, make sure you `#include ` in your C code, and link with `-lcli`. If you have any trouble with this, have a look at clitest.c for a demonstration. Start your program off with a `cli_init()`. This sets up the internal data structures required. When a user connects, they are presented with a greeting if one is set using the `cli_set_banner(banner)` function. By default, the command-line session is not authenticated, which means users will get full access as soon as they connect. As this may not be always the best thing, 2 methods of authentication are available. First, you can add username / password combinations with the `cli_allow_user(username, password)` function. When a user connects, they can connect with any of these username / password combinations. Secondly, you can add a callback using the `cli_set_auth_callback(callback)` function. This function is passed the username and password as `char *`, and must return `CLI_OK` if the user is to have access and `CLI_ERROR` if they are not. The library itself will take care of prompting the user for credentials. Commands are built using a tree-like structure. You define commands with the `cli_register_command(parent, command, callback, privilege, mode, help)` function. `parent` is a `cli_command *` reference to a previously added command. Using a parent you can build up complex commands. e.g. to provide commands `show users`, `show sessions` and `show people`, use the following sequence: ```c cli_command *c = cli_register_command(NULL, "show", NULL, PRIVILEGE_UNPRIVILEGED, MODE_EXEC, NULL); cli_register_command(c, "sessions", fn_sessions, PRIVILEGE_UNPRIVILEGED, MODE_EXEC, "Show the sessions connected"); cli_register_command(c, "users", fn_users, PRIVILEGE_UNPRIVILEGED, MODE_EXEC, "Show the users connected"); cli_register_command(c, "people", fn_people, PRIVILEGE_UNPRIVILEGED, MODE_EXEC, "Show a list of the people I like"); ``` If callback is `NULL`, the command can be used as part of a tree, but cannot be individually run. If you decide later that you don't want a command to be run, you can call `cli_unregister_command(command)`. You can use this to build dynamic command trees. It is possible to carry along a user-defined context to all command callbacks using `cli_set_context(cli, context)` and `cli_get_context(cli)` functions. You are responsible for accepting a TCP connection, and for creating a process or thread to run the cli. Once you are ready to process the connection, call `cli_loop(cli, sock)` to interact with the user on the given socket. Note that as mentioned above, if the select() call is used and sock is out of range (>= FD_SETSIZE) then cli_loop() will display an error in both the parent process and to the remote TCP connection before exiting that routine. This function will return when the user exits the cli, either by breaking the connection or entering `quit`. Call `cli_done()` to free the data structures. libcli-1.10.7/clitest.c000066400000000000000000000502331401575210200146510ustar00rootroot00000000000000#include #include #include #include #ifdef WIN32 #include #include #else #include #include #include #endif #include #include #include #include #include #include "libcli.h" // vim:sw=4 tw=120 et #define CLITEST_PORT 8000 #define MODE_CONFIG_INT 10 #ifdef __GNUC__ #define UNUSED(d) d __attribute__((unused)) #else #define UNUSED(d) d #endif unsigned int regular_count = 0; unsigned int debug_regular = 0; struct my_context { int value; char *message; }; #ifdef WIN32 typedef int socklen_t; int winsock_init() { WORD wVersionRequested; WSADATA wsaData; int err; // Start up sockets wVersionRequested = MAKEWORD(2, 2); err = WSAStartup(wVersionRequested, &wsaData); if (err != 0) { // Tell the user that we could not find a usable WinSock DLL. return 0; } /* * Confirm that the WinSock DLL supports 2.2 * Note that if the DLL supports versions greater than 2.2 in addition to * 2.2, it will still return 2.2 in wVersion since that is the version we * requested. * */ if (LOBYTE(wsaData.wVersion) != 2 || HIBYTE(wsaData.wVersion) != 2) { // Tell the user that we could not find a usable WinSock DLL. WSACleanup(); return 0; } return 1; } #endif int cmd_test(struct cli_def *cli, const char *command, char *argv[], int argc) { int i; cli_print(cli, "called %s with \"%s\"", __func__, command); cli_print(cli, "%d arguments:", argc); for (i = 0; i < argc; i++) cli_print(cli, " %s", argv[i]); return CLI_OK; } int cmd_set(struct cli_def *cli, UNUSED(const char *command), char *argv[], int argc) { if (argc < 2 || strcmp(argv[0], "?") == 0) { cli_print(cli, "Specify a variable to set"); return CLI_OK; } if (strcmp(argv[1], "?") == 0) { cli_print(cli, "Specify a value"); return CLI_OK; } if (strcmp(argv[0], "regular_interval") == 0) { unsigned int sec = 0; if (!argv[1] && !*argv[1]) { cli_print(cli, "Specify a regular callback interval in seconds"); return CLI_OK; } sscanf(argv[1], "%u", &sec); if (sec < 1) { cli_print(cli, "Specify a regular callback interval in seconds"); return CLI_OK; } cli->timeout_tm.tv_sec = sec; cli->timeout_tm.tv_usec = 0; cli_print(cli, "Regular callback interval is now %d seconds", sec); return CLI_OK; } cli_print(cli, "Setting \"%s\" to \"%s\"", argv[0], argv[1]); return CLI_OK; } int cmd_config_int(struct cli_def *cli, UNUSED(const char *command), char *argv[], int argc) { if (argc < 1) { cli_print(cli, "Specify an interface to configure"); return CLI_OK; } if (strcmp(argv[0], "?") == 0) cli_print(cli, " test0/0"); else if (strcasecmp(argv[0], "test0/0") == 0) cli_set_configmode(cli, MODE_CONFIG_INT, "test"); else cli_print(cli, "Unknown interface %s", argv[0]); return CLI_OK; } int cmd_config_int_exit(struct cli_def *cli, UNUSED(const char *command), UNUSED(char *argv[]), UNUSED(int argc)) { cli_set_configmode(cli, MODE_CONFIG, NULL); return CLI_OK; } int cmd_show_regular(struct cli_def *cli, UNUSED(const char *command), char *argv[], int argc) { cli_print(cli, "cli_regular() has run %u times", regular_count); return CLI_OK; } int cmd_debug_regular(struct cli_def *cli, UNUSED(const char *command), char *argv[], int argc) { debug_regular = !debug_regular; cli_print(cli, "cli_regular() debugging is %s", debug_regular ? "enabled" : "disabled"); return CLI_OK; } int cmd_context(struct cli_def *cli, UNUSED(const char *command), UNUSED(char *argv[]), UNUSED(int argc)) { struct my_context *myctx = (struct my_context *)cli_get_context(cli); cli_print(cli, "User context has a value of %d and message saying %s", myctx->value, myctx->message); return CLI_OK; } int check_auth(const char *username, const char *password) { if (strcasecmp(username, "fred") != 0) return CLI_ERROR; if (strcasecmp(password, "nerk") != 0) return CLI_ERROR; return CLI_OK; } int regular_callback(struct cli_def *cli) { regular_count++; if (debug_regular) { cli_print(cli, "Regular callback - %u times so far", regular_count); cli_reprompt(cli); } return CLI_OK; } int check_enable(const char *password) { return !strcasecmp(password, "topsecret"); } int idle_timeout(struct cli_def *cli) { cli_print(cli, "Custom idle timeout"); return CLI_QUIT; } void pc(UNUSED(struct cli_def *cli), const char *string) { printf("%s\n", string); } #define MODE_POLYGON_TRIANGLE 20 #define MODE_POLYGON_RECTANGLE 21 int cmd_perimeter(struct cli_def *cli, const char *command, char *argv[], int argc) { struct cli_optarg_pair *optargs = cli_get_all_found_optargs(cli); int i = 1, numSides = 0; int perimeter = 0; int verbose_count = 0; char *verboseArg; char *shapeName = NULL; cli_print(cli, "perimeter callback, with %d args", argc); for (; optargs; optargs = optargs->next) cli_print(cli, "%d, %s=%s", i++, optargs->name, optargs->value); verboseArg = NULL; while ((verboseArg = cli_get_optarg_value(cli, "verbose", verboseArg))) { verbose_count++; } cli_print(cli, "verbose argument was seen %d times", verbose_count); shapeName = cli_get_optarg_value(cli, "shape", NULL); if (!shapeName) { cli_error(cli, "No shape name given"); return CLI_ERROR; } else if (strcmp(shapeName, "triangle") == 0) { numSides = 3; } else if (strcmp(shapeName, "rectangle") == 0) { numSides = 4; } else { cli_error(cli, "Unrecognized shape given"); return CLI_ERROR; } for (i = 1; i <= numSides; i++) { char sidename[50], *value; int length; snprintf(sidename, 50, "side_%d", i); value = cli_get_optarg_value(cli, sidename, NULL); length = strtol(value, NULL, 10); perimeter += length; } cli_print(cli, "Perimeter is %d", perimeter); return CLI_OK; } const char *KnownShapes[] = {"rectangle", "triangle", NULL}; int shape_completor(struct cli_def *cli, const char *name, const char *value, struct cli_comphelp *comphelp) { const char **shape; int rc = CLI_OK; printf("shape_completor called with <%s>\n", value); for (shape = KnownShapes; *shape && (rc == CLI_OK); shape++) { if (!value || !strncmp(*shape, value, strlen(value))) { rc = cli_add_comphelp_entry(comphelp, *shape); } } return rc; } int shape_validator(struct cli_def *cli, const char *name, const char *value) { const char **shape; printf("shape_validator called with <%s>\n", value); for (shape = KnownShapes; *shape; shape++) { if (!strcmp(value, *shape)) return CLI_OK; } return CLI_ERROR; } int verbose_validator(struct cli_def *cli, const char *name, const char *value) { printf("verbose_validator called\n"); return CLI_OK; } // note that we're setting a 'custom' optarg tag/value pair as an example here int shape_transient_eval(struct cli_def *cli, const char *name, const char *value) { printf("shape_transient_eval called with <%s>\n", value); if (!strcmp(value, "rectangle")) { cli_set_transient_mode(cli, MODE_POLYGON_RECTANGLE); cli_set_optarg_value(cli, "duplicateShapeValue", value, 0); return CLI_OK; } else if (!strcmp(value, "triangle")) { cli_set_transient_mode(cli, MODE_POLYGON_TRIANGLE); cli_set_optarg_value(cli, "duplicateShapeValue", value, 0); return CLI_OK; } cli_error(cli, "unrecognized value for setting %s -> %s", name, value); return CLI_ERROR; } const char *KnownColors[] = {"black", "white", "gray", "red", "blue", "green", "lightred", "lightblue", "lightgreen", "darkred", "darkblue", "darkgreen", "lavender", "yellow", NULL}; int color_completor(struct cli_def *cli, const char *name, const char *word, struct cli_comphelp *comphelp) { // Attempt to show matches against the following color strings const char **color; int rc = CLI_OK; printf("color_completor called with <%s>\n", word); for (color = KnownColors; *color && (rc == CLI_OK); color++) { if (!word || !strncmp(*color, word, strlen(word))) { rc = cli_add_comphelp_entry(comphelp, *color); } } return rc; } int color_validator(struct cli_def *cli, const char *name, const char *value) { const char **color; int rc = CLI_ERROR; printf("color_validator called for %s\n", name); for (color = KnownColors; *color; color++) { if (!strcmp(value, *color)) return CLI_OK; } return rc; } int side_length_validator(struct cli_def *cli, const char *name, const char *value) { // Verify 'value' is a positive number long len; char *endptr; int rc = CLI_OK; printf("side_length_validator called\n"); errno = 0; len = strtol(value, &endptr, 10); if ((endptr == value) || (*endptr != '\0') || ((errno == ERANGE) && ((len == LONG_MIN) || (len == LONG_MAX)))) return CLI_ERROR; return rc; } int transparent_validator(struct cli_def *cli, const char *name, const char *value) { return strcasecmp("transparent", value) ? CLI_ERROR : CLI_OK; } int check1_validator(struct cli_def *cli, UNUSED(const char *name), UNUSED(const char *value)) { char *color; char *transparent; printf("check1_validator called \n"); color = cli_get_optarg_value(cli, "color", NULL); transparent = cli_get_optarg_value(cli, "transparent", NULL); if (!color && !transparent) { cli_error(cli, "\nMust supply either a color or transparent!"); return CLI_ERROR; } else if (color && !strcmp(color, "black") && transparent) { cli_error(cli, "\nCan not have a transparent black object!"); return CLI_ERROR; } return CLI_OK; } int cmd_deep_dive(struct cli_def *cli, const char *command, char *argv[], int argc) { cli_print(cli, "Raw commandline was <%s>", cli->pipeline->cmdline); return CLI_OK; } int int_validator(struct cli_def *cli, const char *name, const char *value) { // Verify 'value' is a positive number long len; char *endptr; int rc = CLI_OK; printf("int_validator called\n"); errno = 0; len = strtol(value, &endptr, 10); if ((endptr == value) || (*endptr != '\0') || ((errno == ERANGE) && ((len == LONG_MIN) || (len == LONG_MAX)))) return CLI_ERROR; return rc; } int cmd_string(struct cli_def *cli, const char *command, char *argv[], int argc) { int i; cli_print(cli, "Raw commandline was <%s>", cli->pipeline->cmdline); cli_print(cli, "Value for text argument is <%s>", cli_get_optarg_value(cli, "text", NULL)); cli_print(cli, "Found %d 'extra' arguments after 'text' argument was processed", argc); for (i = 0; i != argc; i++) { cli_print(cli, " Extra arg %d = <%s>", i + 1, argv[i]); } return CLI_OK; } int cmd_long_name(struct cli_def *cli, const char *command, char *argv[], int argc) { int i; cli_print(cli, "Raw commandline was <%s>", cli->pipeline->cmdline); cli_print(cli, "Value for text argument is <%s>", cli_get_optarg_value(cli, "text", NULL)); cli_print(cli, "Found %d 'extra' arguments after 'text' argument was processed", argc); for (i = 0; i != argc; i++) { cli_print(cli, " Extra arg %d = <%s>", i + 1, argv[i]); } return CLI_OK; } void run_child(int x) { struct cli_command *c; struct cli_def *cli; struct cli_optarg *o; // Prepare a small user context char mymessage[] = "I contain user data!"; struct my_context myctx; myctx.value = 5; myctx.message = mymessage; cli = cli_init(); cli_set_banner(cli, "libcli test environment"); cli_set_hostname(cli, "router"); cli_telnet_protocol(cli, 1); cli_regular(cli, regular_callback); // change regular update to 5 seconds rather than default of 1 second cli_regular_interval(cli, 5); // set 60 second idle timeout cli_set_idle_timeout_callback(cli, 60, idle_timeout); cli_register_command(cli, NULL, "test", cmd_test, PRIVILEGE_UNPRIVILEGED, MODE_EXEC, NULL); cli_register_command(cli, NULL, "simple", NULL, PRIVILEGE_UNPRIVILEGED, MODE_EXEC, NULL); cli_register_command(cli, NULL, "simon", NULL, PRIVILEGE_UNPRIVILEGED, MODE_EXEC, NULL); cli_register_command(cli, NULL, "set", cmd_set, PRIVILEGE_PRIVILEGED, MODE_EXEC, NULL); c = cli_register_command(cli, NULL, "show", NULL, PRIVILEGE_UNPRIVILEGED, MODE_EXEC, NULL); cli_register_command(cli, c, "regular", cmd_show_regular, PRIVILEGE_UNPRIVILEGED, MODE_EXEC, "Show the how many times cli_regular has run"); cli_register_command(cli, c, "counters", cmd_test, PRIVILEGE_UNPRIVILEGED, MODE_EXEC, "Show the counters that the system uses"); cli_register_command(cli, c, "junk", cmd_test, PRIVILEGE_UNPRIVILEGED, MODE_EXEC, NULL); cli_register_command(cli, NULL, "interface", cmd_config_int, PRIVILEGE_PRIVILEGED, MODE_CONFIG, "Configure an interface"); cli_register_command(cli, NULL, "exit", cmd_config_int_exit, PRIVILEGE_PRIVILEGED, MODE_CONFIG_INT, "Exit from interface configuration"); cli_register_command(cli, NULL, "address", cmd_test, PRIVILEGE_PRIVILEGED, MODE_CONFIG_INT, "Set IP address"); c = cli_register_command(cli, NULL, "debug", NULL, PRIVILEGE_UNPRIVILEGED, MODE_EXEC, NULL); cli_register_command(cli, c, "regular", cmd_debug_regular, PRIVILEGE_UNPRIVILEGED, MODE_EXEC, "Enable cli_regular() callback debugging"); // Register some commands/subcommands to demonstrate opt/arg and buildmode operations c = cli_register_command( cli, NULL, "perimeter", cmd_perimeter, PRIVILEGE_UNPRIVILEGED, MODE_EXEC, "Calculate perimeter of polygon\nhas embedded " "newline\nand_a_really_long_line_that_is_much_longer_than_80_columns_to_show_that_wrap_case"); o = cli_register_optarg(c, "transparent", CLI_CMD_OPTIONAL_FLAG, PRIVILEGE_UNPRIVILEGED, MODE_EXEC, "Set transparent flag", NULL, NULL, NULL); cli_optarg_addhelp(o, "transparent", "(any case)set to transparent"); cli_register_optarg( c, "verbose", CLI_CMD_OPTIONAL_FLAG | CLI_CMD_OPTION_MULTIPLE, PRIVILEGE_UNPRIVILEGED, MODE_EXEC, "Set verbose flag with some humongously long string \nwithout any embedded newlines in it to test with", NULL, NULL, NULL); o = cli_register_optarg(c, "color", CLI_CMD_OPTIONAL_ARGUMENT, PRIVILEGE_UNPRIVILEGED, MODE_EXEC, "Set color", color_completor, color_validator, NULL); cli_optarg_addhelp(o, "black", "the color 'black'"); cli_optarg_addhelp(o, "white", "the color 'white'"); cli_optarg_addhelp(o, "gray", "the color 'gray'"); cli_optarg_addhelp(o, "red", "the color 'red'"); cli_optarg_addhelp(o, "blue", "the color 'blue'"); cli_optarg_addhelp(o, "green", "the color 'green'"); cli_optarg_addhelp(o, "lightred", "the color 'lightred'"); cli_optarg_addhelp(o, "lightblue", "the color 'lightblue'"); cli_optarg_addhelp(o, "lightgreen", "the color 'lightgreen'"); cli_optarg_addhelp(o, "darkred", "the color 'darkred'"); cli_optarg_addhelp(o, "darkblue", "the color 'darkblue'"); cli_optarg_addhelp(o, "darkgreen", "the color 'darkgreen'"); cli_optarg_addhelp(o, "lavender", "the color 'lavender'"); cli_optarg_addhelp(o, "yellow", "the color 'yellow'"); cli_register_optarg(c, "__check1__", CLI_CMD_SPOT_CHECK, PRIVILEGE_UNPRIVILEGED, MODE_EXEC, NULL, NULL, check1_validator, NULL); o = cli_register_optarg(c, "shape", CLI_CMD_ARGUMENT | CLI_CMD_ALLOW_BUILDMODE, PRIVILEGE_UNPRIVILEGED, MODE_EXEC, "Specify shape(shows subtext on help)", shape_completor, shape_validator, shape_transient_eval); cli_optarg_addhelp(o, "triangle", "specify a triangle"); cli_optarg_addhelp(o, "rectangle", "specify a rectangle"); cli_register_optarg(c, "side_1", CLI_CMD_ARGUMENT, PRIVILEGE_UNPRIVILEGED, MODE_POLYGON_TRIANGLE, "Specify side 1 length", NULL, side_length_validator, NULL); cli_register_optarg(c, "side_1", CLI_CMD_ARGUMENT, PRIVILEGE_UNPRIVILEGED, MODE_POLYGON_RECTANGLE, "Specify side 1 length", NULL, side_length_validator, NULL); cli_register_optarg(c, "side_2", CLI_CMD_ARGUMENT, PRIVILEGE_UNPRIVILEGED, MODE_POLYGON_TRIANGLE, "Specify side 2 length", NULL, side_length_validator, NULL); cli_register_optarg(c, "side_2", CLI_CMD_ARGUMENT, PRIVILEGE_UNPRIVILEGED, MODE_POLYGON_RECTANGLE, "Specify side 2 length", NULL, side_length_validator, NULL); cli_register_optarg(c, "side_3", CLI_CMD_ARGUMENT, PRIVILEGE_UNPRIVILEGED, MODE_POLYGON_TRIANGLE, "Specify side 3 length", NULL, side_length_validator, NULL); cli_register_optarg(c, "side_3", CLI_CMD_ARGUMENT, PRIVILEGE_UNPRIVILEGED, MODE_POLYGON_RECTANGLE, "Specify side 3 length", NULL, side_length_validator, NULL); cli_register_optarg(c, "side_4", CLI_CMD_ARGUMENT, PRIVILEGE_UNPRIVILEGED, MODE_POLYGON_RECTANGLE, "Specify side 4 length", NULL, side_length_validator, NULL); c = cli_register_command(cli, NULL, "string", cmd_string, PRIVILEGE_UNPRIVILEGED, MODE_EXEC, "string input argument testing"); cli_register_optarg(c, "buildmode", CLI_CMD_OPTIONAL_FLAG | CLI_CMD_ALLOW_BUILDMODE, PRIVILEGE_UNPRIVILEGED, MODE_EXEC, "flag", NULL, NULL, NULL); cli_register_optarg(c, "text", CLI_CMD_ARGUMENT, PRIVILEGE_UNPRIVILEGED, MODE_EXEC, "text string", NULL, NULL, NULL); // Set user context and its command cli_set_context(cli, (void *)&myctx); cli_register_command(cli, NULL, "context", cmd_context, PRIVILEGE_UNPRIVILEGED, MODE_EXEC, "Test a user-specified context"); struct cli_command *d1, *d2, *d3; d1 = cli_register_command(cli, NULL, "deep", NULL, PRIVILEGE_UNPRIVILEGED, MODE_EXEC, "top level deep dive cmd"); d2 = cli_register_command(cli, d1, "dive", NULL, PRIVILEGE_UNPRIVILEGED, MODE_EXEC, "mid level dep dive cmd"); d3 = cli_register_command(cli, d2, "cmd", cmd_deep_dive, PRIVILEGE_UNPRIVILEGED, MODE_EXEC, "bottom level dep dive cmd"); o = cli_register_optarg(d3, "howdeep", CLI_CMD_ARGUMENT, PRIVILEGE_UNPRIVILEGED, MODE_EXEC, "Specify how deep", NULL, int_validator, NULL); o = cli_register_optarg(d3, "howlong", CLI_CMD_OPTIONAL_ARGUMENT, PRIVILEGE_UNPRIVILEGED, MODE_EXEC, "Specify how long", NULL, int_validator, NULL); c = cli_register_command( cli, NULL, "serioously_long_cammand_to_test_with", cmd_long_name, PRIVILEGE_UNPRIVILEGED, MODE_EXEC, "show long command name with " "newline\nand_a_really_long_line_that_is_much_longer_than_80_columns_to_show_that_wrap_case"); cli_set_auth_callback(cli, check_auth); cli_set_enable_callback(cli, check_enable); // Test reading from a file { FILE *fh; if ((fh = fopen("clitest.txt", "r"))) { // This sets a callback which just displays the cli_print() text to stdout cli_print_callback(cli, pc); cli_file(cli, fh, PRIVILEGE_UNPRIVILEGED, MODE_EXEC); cli_print_callback(cli, NULL); fclose(fh); } } cli_loop(cli, x); cli_done(cli); } int main() { int s, x; struct sockaddr_in addr; int on = 1; #ifndef WIN32 signal(SIGCHLD, SIG_IGN); #endif #ifdef WIN32 if (!winsock_init()) { printf("Error initialising winsock\n"); return 1; } #endif if ((s = socket(AF_INET, SOCK_STREAM, 0)) < 0) { perror("socket"); return 1; } if (setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on))) { perror("setsockopt"); } memset(&addr, 0, sizeof(addr)); addr.sin_family = AF_INET; addr.sin_addr.s_addr = htonl(INADDR_ANY); addr.sin_port = htons(CLITEST_PORT); if (bind(s, (struct sockaddr *)&addr, sizeof(addr)) < 0) { perror("bind"); return 1; } if (listen(s, 50) < 0) { perror("listen"); return 1; } printf("Listening on port %d\n", CLITEST_PORT); while ((x = accept(s, NULL, 0))) { #ifndef WIN32 int pid = fork(); if (pid < 0) { perror("fork"); return 1; } /* parent */ if (pid > 0) { socklen_t len = sizeof(addr); if (getpeername(x, (struct sockaddr *)&addr, &len) >= 0) printf(" * accepted connection from %s\n", inet_ntoa(addr.sin_addr)); close(x); continue; } /* child */ close(s); run_child(x); exit(0); #else run_child(x); shutdown(x, SD_BOTH); close(x); #endif } return 0; } libcli-1.10.7/clitest.txt000066400000000000000000000000161401575210200152400ustar00rootroot00000000000000show counters libcli-1.10.7/doc/000077500000000000000000000000001401575210200136005ustar00rootroot00000000000000libcli-1.10.7/doc/developers-guide.md000066400000000000000000000276651401575210200174050ustar00rootroot00000000000000## Introduction libcli provides a telnet command-line environment which can be embedded in other programs. This environment includes useful features such as automatic authentication, history, and command-line editing. This guide should show you everything you need to embed libcli into your program. If you have any corrections, suggestions or modifications, please email [David Parrish](mailto:david+libcli@dparrish.com). ## Authentication Two methods of authentcation are supported by libcli - internal and callback. Internal authentication is based on a list of username / password combinations that are set up before `cli_loop()` is called. Passwords may be clear text, MD5 encrypted, or DES encrypted (requires a `{crypt}` prefix to distinguish from clear text). Callback based authentication calls a callback with the username and password that the user enters, and must return either `CLI_OK` or `CLI_ERROR`. This can be used for checking passwords against some other database such as LDAP. If neither `cli_set_auth_callback()` or `cli_allow_user()` have been called before `cli_loop()`, then authentication will be disabled and the user will not be prompted for a username / password combination. Authentication for the privileged state can also be defined by a static password or by a callback. Use the `cli_set_enable_callback()` or `cli_allow_enable()` functions to set the enable password. ## Tutorial This section will guide you through implementing libcli in a basic server. ### Create a file libclitest.c ```c #include int main(int argc, char *argv[]) { struct sockaddr_in servaddr; struct cli_command *c; struct cli_def *cli; int on = 1, x, s; // Must be called first to setup data structures cli = cli_init(); // Set the hostname (shown in the the prompt) cli_set_hostname(cli, "test"); // Set the greeting cli_set_banner(cli, "Welcome to the CLI test program."); // Enable 2 username / password combinations cli_allow_user(cli, "fred", "nerk"); cli_allow_user(cli, "foo", "bar"); // Set up a few simple one-level commands cli_register_command(cli, NULL, "test", cmd_test, PRIVILEGE_UNPRIVILEGED, MODE_EXEC, NULL); cli_register_command(cli, NULL, "simple", cmd_test, PRIVILEGE_UNPRIVILEGED, MODE_EXEC, NULL); cli_register_command(cli, NULL, "simon", NULL, PRIVILEGE_UNPRIVILEGED, MODE_EXEC, NULL); // This command takes arguments, and requires privileged mode (enable) cli_register_command(cli, NULL, "set", cmd_set, PRIVILEGE_PRIVILEGED, MODE_EXEC, NULL); // Set up 2 commands "show counters" and "show junk" c = cli_register_command(cli, NULL, "show", NULL, PRIVILEGE_UNPRIVILEGED, MODE_EXEC, NULL); // Note how we store the previous command and use it as the parent for this one. cli_register_command(cli, c, "junk", cmd_test, PRIVILEGE_UNPRIVILEGED, MODE_EXEC, NULL); // This one has some help text cli_register_command(cli, c, "counters", cmd_test, PRIVILEGE_UNPRIVILEGED, MODE_EXEC, "Show the counters that the system uses"); // Create a socket s = socket(AF_INET, SOCK_STREAM, 0); setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)); // Listen on port 12345 memset(&servaddr, 0, sizeof(servaddr)); servaddr.sin_family = AF_INET; servaddr.sin_addr.s_addr = htonl(INADDR_ANY); servaddr.sin_port = htons(12345); bind(s, (struct sockaddr *)&servaddr, sizeof(servaddr)); // Wait for a connection listen(s, 50); while ((x = accept(s, NULL, 0))) { // Pass the connection off to libcli cli_loop(cli, x); close(x); } // Free data structures cli_done(cli); return 0; } ``` This code snippet is all that's required to enable a libcli program. However it's not yet compilable because we haven't created the callback functions. A few commands have been created: * `test` * `simple` * `set` * `show junk` * `show counters` Note that `simon` isn't on this list because `callback` was `NULL` when it was registered, so the command will not be available. Also, the standard libcli commands `help`, `exit`, `logout`, `quit` and `history` are also available automatically. Make this program complete by adding the callback functions ```c int cmd_test(struct cli_def *cli, char *command, char *argv[], int argc) { cli_print(cli, "called %s with %s\r\n", __FUNCTION__, command); return CLI_OK; } int cmd_set(struct cli_def *cli, char *command, char *argv[], int argc) { if (argc < 2) { cli_print(cli, "Specify a variable to set\r\n"); return CLI_OK; } cli_print(cli, "Setting %s to %s\r\n", argv[0], argv[1]); return CLI_OK; } ``` 2 callback functions are defined here, `cmd_test()` and `cmd_set()`. `cmd_test()` is called by many of the commands defined in the tutorial, although in reality you would usually use a callback for a single command. `cmd_test()` simply echos the command entered back to the client. Note that it shows the full expanded command, so you can enter "`te`" at the prompt and it will print back "`called with test`". `cmd_set()` handles the arguments given on the command line. This allows you to use a single callback to handle lots of arguments like: * `set colour green` * `set name David` * `set email "I don't have an e-mail address"` * etc... Compile the code ```sh gcc libclitest.c -o libclitest -lcli ``` You can now run the program with `./libclitest` and telnet to port 12345 to see your work in action. ## Function Reference ### cli\_init() This must be called before any other cli_xxx function. It sets up the internal data structures used for command-line processing. Returns a `struct cli_def *` which must be passed to all other `cli_xxx` functions. ### cli\_done(struct cli\_def \*cli) This is optional, but it's a good idea to call this when you are finished with libcli. This frees memory used by libcli. ### cli\_register\_command(struct cli\_def \*cli, struct cli\_command *parent, char *command, int (*callback)(struct cli\_def *, char *, char **, int), int privilege, int mode, char *help) Add a command to the internal command tree. Returns a `struct cli_command *`, which you can pass as parent to another call to `cli_register_command()`. When the command has been entered by the user, callback is checked. If it is not `NULL`, then the callback is called with: `struct cli_def *` - the handle of the cli structure. This must be passed to all cli functions, including `cli_print()`. `char *` - the entire command which was entered. This is after command expansion. `char **` - the list of arguments entered `int` - the number of arguments entered The callback must return `CLI_OK` if the command was successful, `CLI_ERROR` if processing wasn't successful and the next matching command should be tried (if any), or `CLI_QUIT` to drop the connection (e.g. on a fatal error). If parent is `NULL`, the command is added to the top level of commands, otherwise it is a subcommand of parent. privilege should be set to either PRIVILEGE\_PRIVILEGED or PRIVILEGE\_UNPRIVILEGED. If set to PRIVILEGE\_PRIVILEGED then the user must have entered enable before running this command. `mode` should be set to `MODE_EXEC` for no configuration mode, `MODE_CONFIG` for generic configuration commands, or your own config level. The user can enter the generic configuration level by entering configure terminal, and can return to `MODE_EXEC` by entering exit or CTRL-Z. You can define commands to enter your own configuration levels, which should call the `cli_set_configmode()` function. If help is provided, it is given to the user when the use the help command or press ?. ### cli\_unregister\_command(struct cli\_def \*cli, char *command) Remove a command command and all children. There is not provision yet for removing commands at lower than the top level. ### cli\_loop(struct cli\_def \*cli, int sockfd) The main loop of the command-line environment. This must be called with the FD of a socket open for bi-directional communication (sockfd). ### cli\_loop() handles the telnet negotiation and authentication. It returns only when the connection is finished, either by a server or client disconnect. Returns `CLI_OK`. ### cli\_set\_auth\_callback(struct cli\_def \*cli, int (*auth\_callback)(char *, char *)) Enables or disables callback based authentication. If `auth_callback` is not `NULL`, then authentication will be required on connection. `auth_callback` will be called with the username and password that the user enters. `auth_callback` must return a non-zero value if authentication is successful. If `auth_callback` is `NULL`, then callback based authentication will be disabled. ### cli\_allow\_user(struct cli\_def \*cli, char *username, char *password) Enables internal authentication, and adds username/password to the list of allowed users. The internal list of users will be checked before callback based authentication is tried. ### cli\_deny\_user(struct cli\_def \*cli, char *username) Removes username/password from the list of allowed users. If this is the last combination in the list, then internal authentication will be disabled. ### cli\_set\_banner(struct cli\_def \*cli, char *banner) Sets the greeting that clients will be presented with when they connect. This may be a security warning for example. If this function is not called or called with a `NULL` argument, no banner will be presented. ### cli\_set\_hostname(struct cli\_def \*cli, char *hostname) Sets the hostname to be displayed as the first part of the prompt. ### cli\_regular(struct cli\_def \*cli, int(*callback)(struct cli\_def *)) Adds a callback function which will be called every second that a user is connected to the cli. This can be used for regular processing such as debugging, time counting or implementing idle timeouts. Pass `NULL` as the callback function to disable this at runtime. If the callback function does not return `CLI_OK`, then the user will be disconnected. ### cli\_file(struct cli\_def \*cli, FILE *f, int privilege, int mode) This reads and processes every line read from f as if it were entered at the console. The privilege level will be set to privilege and mode set to mode during the processing of the file. ### cli\_print(struct cli\_def \*cli, char *format, ...) This function should be called for any output generated by a command callback. It takes a printf() style format string and a variable number of arguments. Be aware that any output generated by `cli_print()` will be passed through any filter currently being applied, and the output will be redirected to the `cli_print_callback()` if one has been specified. ### cli\_error(struct cli\_def \*cli, char *format, ...) A variant of `cli_print()` which does not have filters applied. ### cli\_print\_callback(struct cli\_def \*cli, void (*callback)(struct cli\_def *, char *)) Whenever `cli_print()` or `cli_error()` is called, the output generally goes to the user. If you specify a callback using this function, then the output will be sent to that callback. The function will be called once for each line, and it will be passed a single null-terminated string, without any newline characters. Specifying `NULL` as the callback parameter will make libcli use the default `cli_print()` function. ### cli\_set\_enable\_callback(struct cli\_def \*cli, void (*callback)(struct cli\_def *, char *)) Just like `cli_set_auth_callback, this takes a pointer to a callback function to authorize privileged access. However this callback only takes a single string - the password. ### cli\_allow\_enable(struct cli\_def \*cli, char *password) This will allow a static password to be used for the enable command. This static password will be checked before running any enable callbacks. Set this to `NULL` to not have a static enable password. ### cli\_set\_configmode(struct cli\_def \*cli, int mode, char *string) This will set the configuration mode. Once set, commands will be restricted to only ones in the selected configuration mode, plus any set to `MODE_ANY`. The previous mode value is returned. The string passed will be used to build the prompt in the set configuration mode. e.g. if you set the string `test`, the prompt will become: ``` hostname(config-test)# ``` libcli-1.10.7/libcli.c000066400000000000000000003502001401575210200144350ustar00rootroot00000000000000// vim:sw=2 tw=120 et #ifdef WIN32 #include #include #endif #define _GNU_SOURCE #include #include #include #include #include #if !defined(__APPLE__) && !defined(__FreeBSD__) #include #endif #include #include #include #include #ifndef WIN32 #include #endif #if defined(LIBCLI_USE_POLL) && !defined(WIN32) #include #define CLI_SOCKET_WAIT_PERROR "poll" #else #define CLI_SOCKET_WAIT_PERROR "select" #endif #include "libcli.h" #ifdef __GNUC__ #define UNUSED(d) d __attribute__((unused)) #else #define UNUSED(d) d #endif #define MATCH_REGEX 1 #define MATCH_INVERT 2 #ifdef WIN32 // Stupid windows has multiple namespaces for filedescriptors, with different read/write functions required for each .. int read(int fd, void *buf, unsigned int count) { return recv(fd, buf, count, 0); } int write(int fd, const void *buf, unsigned int count) { return send(fd, buf, count, 0); } int vasprintf(char **strp, const char *fmt, va_list args) { int size; va_list argCopy; // Do initial vsnprintf on a copy of the va_list va_copy(argCopy, args); size = vsnprintf(NULL, 0, fmt, argCopy); va_end(argCopy); if ((*strp = malloc(size + 1)) == NULL) { return -1; } size = vsnprintf(*strp, size + 1, fmt, args); return size; } int asprintf(char **strp, const char *fmt, ...) { va_list args; int size; va_start(args, fmt); size = vasprintf(strp, fmt, args); va_end(args); return size; } int fprintf(FILE *stream, const char *fmt, ...) { va_list args; int size; char *buf; va_start(args, fmt); size = vasprintf(&buf, fmt, args); if (size < 0) { goto out; } size = write(stream->_file, buf, size); free(buf); out: va_end(args); return size; } // Dummy definitions to allow compilation on Windows int regex_dummy() { return 0; }; #define regfree(...) regex_dummy() #define regexec(...) regex_dummy() #define regcomp(...) regex_dummy() #define regex_t int #define REG_NOSUB 0 #define REG_EXTENDED 0 #define REG_ICASE 0 #endif enum cli_states { STATE_LOGIN, STATE_PASSWORD, STATE_NORMAL, STATE_ENABLE_PASSWORD, STATE_ENABLE, }; struct unp { char *username; char *password; struct unp *next; }; struct cli_filter_cmds { const char *cmd; const char *help; }; // Free and zero (to avoid double-free) #define free_z(p) \ do { \ if (p) { \ free(p); \ (p) = 0; \ } \ } while (0) // Forward defines of *INTERNAL* library function as static here static int cli_search_flags_validator(struct cli_def *cli, const char *word, const char *value); static int cli_match_filter_init(struct cli_def *cli, int argc, char **argv, struct cli_filter *filt); static int cli_range_filter_init(struct cli_def *cli, int argc, char **argv, struct cli_filter *filt); static int cli_count_filter_init(struct cli_def *cli, int argc, char **argv, struct cli_filter *filt); static int cli_match_filter(struct cli_def *cli, const char *string, void *data); static int cli_range_filter(struct cli_def *cli, const char *string, void *data); static int cli_count_filter(struct cli_def *cli, const char *string, void *data); static void cli_int_parse_optargs(struct cli_def *cli, struct cli_pipeline_stage *stage, struct cli_command *cmd, char lastchar, struct cli_comphelp *comphelp); static int cli_int_enter_buildmode(struct cli_def *cli, struct cli_pipeline_stage *stage, char *mode_text); static char *cli_int_buildmode_extend_cmdline(char *, char *word); static void cli_int_free_buildmode(struct cli_def *cli); static void cli_free_command(struct cli_def *cli, struct cli_command *cmd); static int cli_int_unregister_command_core(struct cli_def *cli, const char *command, int command_type); static int cli_int_unregister_buildmode_command(struct cli_def *cli, const char *command) __attribute__((unused)); static struct cli_command *cli_int_register_buildmode_command(struct cli_def *cli, struct cli_command *parent, const char *command, int (*callback)(struct cli_def *cli, const char *, char **, int), int flags, int privilege, int mode, const char *help); static void cli_int_buildmode_reset_unset_help(struct cli_def *cli); static int cli_int_buildmode_cmd_cback(struct cli_def *cli, const char *command, char *argv[], int argc); static int cli_int_buildmode_flag_cback(struct cli_def *cli, const char *command, char *argv[], int argc); static int cli_int_buildmode_flag_multiple_cback(struct cli_def *cli, const char *command, char *argv[], int argc); static int cli_int_buildmode_cancel_cback(struct cli_def *cli, const char *command, char *argv[], int argc); static int cli_int_buildmode_execute_cback(struct cli_def *cli, const char *command, char *argv[], int argc); static int cli_int_buildmode_show_cback(struct cli_def *cli, const char *command, char *argv[], int argc); static int cli_int_buildmode_unset_cback(struct cli_def *cli, const char *command, char *argv[], int argc); static int cli_int_buildmode_unset_completor(struct cli_def *cli, const char *name, const char *word, struct cli_comphelp *comphelp); static int cli_int_buildmode_unset_validator(struct cli_def *cli, const char *name, const char *value); static int cli_int_execute_buildmode(struct cli_def *cli); static void cli_int_free_found_optargs(struct cli_optarg_pair **optarg_pair); static void cli_int_unset_optarg_value(struct cli_def *cli, const char *name); static struct cli_pipeline *cli_int_generate_pipeline(struct cli_def *cli, const char *command); static int cli_int_validate_pipeline(struct cli_def *cli, struct cli_pipeline *pipeline); static int cli_int_execute_pipeline(struct cli_def *cli, struct cli_pipeline *pipeline); inline void cli_int_show_pipeline(struct cli_def *cli, struct cli_pipeline *pipeline); static void cli_int_free_pipeline(struct cli_pipeline *pipeline); static struct cli_command *cli_register_command_core(struct cli_def *cli, struct cli_command *parent, struct cli_command *c); static void cli_int_wrap_help_line(char *nameptr, char *helpptr, struct cli_comphelp *comphelp); static int cli_socket_wait(int sockfd, struct timeval *tm); static char DELIM_OPT_START[] = "["; static char DELIM_OPT_END[] = "]"; static char DELIM_ARG_START[] = "<"; static char DELIM_ARG_END[] = ">"; static char DELIM_NONE[] = ""; static ssize_t _write(int fd, const void *buf, size_t count) { size_t written = 0; ssize_t thisTime = 0; while (count != written) { thisTime = write(fd, (char *)buf + written, count - written); if (thisTime == -1) { if (errno == EINTR) continue; else return -1; } written += thisTime; } return written; } char *cli_int_command_name(struct cli_def *cli, struct cli_command *command) { char *name; char *o; if (command->full_command_name) { free(command->full_command_name); command->full_command_name = NULL; } if (!(name = calloc(1, 1))) return NULL; while (command) { o = name; if (asprintf(&name, "%s%s%s", command->command, *o ? " " : "", o) == -1) { fprintf(stderr, "Couldn't allocate memory for command_name: %s", strerror(errno)); free(o); return NULL; } command = command->parent; free(o); } return name; } char *cli_command_name(struct cli_def *cli, struct cli_command *command) { return command->full_command_name; } void cli_set_auth_callback(struct cli_def *cli, int (*auth_callback)(const char *, const char *)) { cli->auth_callback = auth_callback; } void cli_set_enable_callback(struct cli_def *cli, int (*enable_callback)(const char *)) { cli->enable_callback = enable_callback; } void cli_allow_user(struct cli_def *cli, const char *username, const char *password) { struct unp *u, *n; if (!(n = malloc(sizeof(struct unp)))) { fprintf(stderr, "Couldn't allocate memory for user: %s", strerror(errno)); return; } if (!(n->username = strdup(username))) { fprintf(stderr, "Couldn't allocate memory for username: %s", strerror(errno)); free(n); return; } if (!(n->password = strdup(password))) { fprintf(stderr, "Couldn't allocate memory for password: %s", strerror(errno)); free(n->username); free(n); return; } n->next = NULL; if (!cli->users) { cli->users = n; } else { for (u = cli->users; u && u->next; u = u->next) ; if (u) u->next = n; } } void cli_allow_enable(struct cli_def *cli, const char *password) { free_z(cli->enable_password); if (!(cli->enable_password = strdup(password))) { fprintf(stderr, "Couldn't allocate memory for enable password: %s", strerror(errno)); } } void cli_deny_user(struct cli_def *cli, const char *username) { struct unp *u, *p = NULL; if (!cli->users) return; for (u = cli->users; u; u = u->next) { if (strcmp(username, u->username) == 0) { if (p) p->next = u->next; else cli->users = u->next; free(u->username); free(u->password); free(u); break; } p = u; } } void cli_set_banner(struct cli_def *cli, const char *banner) { free_z(cli->banner); if (banner && *banner) cli->banner = strdup(banner); } void cli_set_hostname(struct cli_def *cli, const char *hostname) { free_z(cli->hostname); if (hostname && *hostname) cli->hostname = strdup(hostname); } void cli_set_promptchar(struct cli_def *cli, const char *promptchar) { free_z(cli->promptchar); cli->promptchar = strdup(promptchar); } static int cli_build_shortest(struct cli_def *cli, struct cli_command *commands) { struct cli_command *c, *p; char *cp, *pp; unsigned len; for (c = commands; c; c = c->next) { c->unique_len = strlen(c->command); if ((c->mode != MODE_ANY && c->mode != cli->mode) || c->privilege > cli->privilege) continue; c->unique_len = 1; for (p = commands; p; p = p->next) { if (c == p) continue; if (c->command_type != p->command_type) continue; if ((p->mode != MODE_ANY && p->mode != cli->mode) || p->privilege > cli->privilege) continue; cp = c->command; pp = p->command; len = 1; while (*cp && *pp && *cp++ == *pp++) len++; if (len > c->unique_len) c->unique_len = len; } if (c->children) cli_build_shortest(cli, c->children); } return CLI_OK; } int cli_set_privilege(struct cli_def *cli, int priv) { int old = cli->privilege; cli->privilege = priv; if (priv != old) { cli_set_promptchar(cli, priv == PRIVILEGE_PRIVILEGED ? "# " : "> "); cli_build_shortest(cli, cli->commands); } return old; } void cli_set_modestring(struct cli_def *cli, const char *modestring) { free_z(cli->modestring); if (modestring) cli->modestring = strdup(modestring); } int cli_set_configmode(struct cli_def *cli, int mode, const char *config_desc) { int old = cli->mode; cli->mode = mode; if (mode != old) { if (!cli->mode) { // Not config mode cli_set_modestring(cli, NULL); } else if (config_desc && *config_desc) { char string[64]; snprintf(string, sizeof(string), "(config-%s)", config_desc); cli_set_modestring(cli, string); } else { cli_set_modestring(cli, "(config)"); } cli_build_shortest(cli, cli->commands); } return old; } struct cli_command *cli_register_command_core(struct cli_def *cli, struct cli_command *parent, struct cli_command *c) { struct cli_command *p = NULL; if (!c) return NULL; c->parent = parent; /* Go build the 'full command name' now that told it who its parent is. * If this fails, clean it up and return a NULL w/o proceeding. */ if (!(c->full_command_name = cli_int_command_name(cli, c))) { cli_free_command(cli, c); return NULL; } /* * Figure out we have a chain, or would be the first element on it. * If we'd be the first element, assign as such. * Otherwise find the lead element so we can trace it below. */ if (parent) { if (!parent->children) { parent->children = c; } else { p = parent->children; } } else { if (!cli->commands) { cli->commands = c; } else { p = cli->commands; } } /* * If we have a chain (p is not null), run down to the last element and place this command at the end */ for (; p && p->next; p = p->next) ; if (p) { p->next = c; c->previous = p; } return c; } struct cli_command *cli_register_command(struct cli_def *cli, struct cli_command *parent, const char *command, int (*callback)(struct cli_def *cli, const char *, char **, int), int privilege, int mode, const char *help) { struct cli_command *c; if (!command) return NULL; if (!(c = calloc(sizeof(struct cli_command), 1))) return NULL; c->command_type = CLI_REGULAR_COMMAND; c->callback = callback; c->next = NULL; if (!(c->command = strdup(command))) { free(c); return NULL; } c->privilege = privilege; c->mode = mode; if (help && !(c->help = strdup(help))) { free(c->command); free(c); return NULL; } return cli_register_command_core(cli, parent, c); } static void cli_free_command(struct cli_def *cli, struct cli_command *cmd) { struct cli_command *c, *p; for (c = cmd->children; c;) { p = c->next; cli_free_command(cli, c); c = p; } free(cmd->command); if (cmd->help) free(cmd->help); if (cmd->optargs) cli_unregister_all_optarg(cmd); if (cmd->full_command_name) free(cmd->full_command_name); /* * Ok, update the pointers of anyone who pointed to us. * We have 3 pointers to worry about - parent, previous, and next. * We don't have to worry about children since they've been cleared above. * If both cli->command points to us we need to update cli->command to point to whatever command is 'next'. * Otherwise ensure that any item before/behind us points around us. * * Important - there is no provision for deleting a discrete subcommand. * For example, suppose we define foo, then bar with foo as the parent, then baz with bar as the parent. We cannot * delete 'bar' and have a new chain of foo -> baz. * The above freeing of children prevents this in the first place. */ if (cmd == cli->commands) { cli->commands = cmd->next; if (cmd->next) { cmd->next->parent = NULL; cmd->next->previous = NULL; } } else { if (cmd->previous) { cmd->previous->next = cmd->next; } if (cmd->next) { cmd->next->previous = cmd->previous; } } free(cmd); } int cli_int_unregister_command_core(struct cli_def *cli, const char *command, int command_type) { struct cli_command *c, *p = NULL; if (!command) return -1; if (!cli->commands) return CLI_OK; for (c = cli->commands; c;) { p = c->next; if (strcmp(c->command, command) == 0 && c->command_type == command_type) { cli_free_command(cli, c); return CLI_OK; } c = p; } return CLI_OK; } int cli_unregister_command(struct cli_def *cli, const char *command) { return cli_int_unregister_command_core(cli, command, CLI_REGULAR_COMMAND); } int cli_show_help(struct cli_def *cli, struct cli_command *c) { struct cli_command *p; for (p = c; p; p = p->next) { if (p->command && p->callback && cli->privilege >= p->privilege && (p->mode == cli->mode || p->mode == MODE_ANY)) { cli_error(cli, " %-20s %s", cli_command_name(cli, p), (p->help != NULL ? p->help : "")); } if (p->children) cli_show_help(cli, p->children); } return CLI_OK; } int cli_enable(struct cli_def *cli, UNUSED(const char *command), UNUSED(char *argv[]), UNUSED(int argc)) { if (cli->privilege == PRIVILEGE_PRIVILEGED) return CLI_OK; if (!cli->enable_password && !cli->enable_callback) { // No password required, set privilege immediately. cli_set_privilege(cli, PRIVILEGE_PRIVILEGED); cli_set_configmode(cli, MODE_EXEC, NULL); } else { // Require password entry cli->state = STATE_ENABLE_PASSWORD; } return CLI_OK; } int cli_disable(struct cli_def *cli, UNUSED(const char *command), UNUSED(char *argv[]), UNUSED(int argc)) { cli_set_privilege(cli, PRIVILEGE_UNPRIVILEGED); cli_set_configmode(cli, MODE_EXEC, NULL); return CLI_OK; } int cli_help(struct cli_def *cli, UNUSED(const char *command), UNUSED(char *argv[]), UNUSED(int argc)) { cli_error(cli, "\nCommands available:"); cli_show_help(cli, cli->commands); return CLI_OK; } int cli_history(struct cli_def *cli, UNUSED(const char *command), UNUSED(char *argv[]), UNUSED(int argc)) { int i; cli_error(cli, "\nCommand history:"); for (i = 0; i < MAX_HISTORY; i++) { if (cli->history[i]) cli_error(cli, "%3d. %s", i, cli->history[i]); } return CLI_OK; } int cli_quit(struct cli_def *cli, UNUSED(const char *command), UNUSED(char *argv[]), UNUSED(int argc)) { cli_set_privilege(cli, PRIVILEGE_UNPRIVILEGED); cli_set_configmode(cli, MODE_EXEC, NULL); return CLI_QUIT; } int cli_exit(struct cli_def *cli, const char *command, char *argv[], int argc) { if (cli->mode == MODE_EXEC) return cli_quit(cli, command, argv, argc); if (cli->mode > MODE_CONFIG) cli_set_configmode(cli, MODE_CONFIG, NULL); else cli_set_configmode(cli, MODE_EXEC, NULL); cli->service = NULL; return CLI_OK; } int cli_int_idle_timeout(struct cli_def *cli) { cli_print(cli, "Idle timeout"); return CLI_QUIT; } int cli_int_configure_terminal(struct cli_def *cli, UNUSED(const char *command), UNUSED(char *argv[]), UNUSED(int argc)) { cli_set_configmode(cli, MODE_CONFIG, NULL); return CLI_OK; } struct cli_def *cli_init() { struct cli_def *cli; struct cli_command *c; if (!(cli = calloc(sizeof(struct cli_def), 1))) return 0; cli->buf_size = 1024; if (!(cli->buffer = calloc(cli->buf_size, 1))) { cli_done(cli); return 0; } cli->telnet_protocol = 1; cli_register_command(cli, 0, "help", cli_help, PRIVILEGE_UNPRIVILEGED, MODE_ANY, "Show available commands"); cli_register_command(cli, 0, "quit", cli_quit, PRIVILEGE_UNPRIVILEGED, MODE_ANY, "Disconnect"); cli_register_command(cli, 0, "logout", cli_quit, PRIVILEGE_UNPRIVILEGED, MODE_ANY, "Disconnect"); cli_register_command(cli, 0, "exit", cli_exit, PRIVILEGE_UNPRIVILEGED, MODE_ANY, "Exit from current mode"); cli_register_command(cli, 0, "history", cli_history, PRIVILEGE_UNPRIVILEGED, MODE_ANY, "Show a list of previously run commands"); cli_register_command(cli, 0, "enable", cli_enable, PRIVILEGE_UNPRIVILEGED, MODE_EXEC, "Turn on privileged commands"); cli_register_command(cli, 0, "disable", cli_disable, PRIVILEGE_PRIVILEGED, MODE_EXEC, "Turn off privileged commands"); c = cli_register_command(cli, 0, "configure", 0, PRIVILEGE_PRIVILEGED, MODE_EXEC, "Enter configuration mode"); if (!c) { cli_done(cli); return 0; } cli_register_command(cli, c, "terminal", cli_int_configure_terminal, PRIVILEGE_PRIVILEGED, MODE_EXEC, "Conlfigure from the terminal"); // And now the built in filters c = cli_register_filter(cli, "begin", cli_range_filter_init, cli_range_filter, PRIVILEGE_UNPRIVILEGED, MODE_ANY, "Begin with lines that match"); if (!c) { cli_done(cli); return 0; } cli_register_optarg(c, "range_start", CLI_CMD_ARGUMENT | CLI_CMD_REMAINDER_OF_LINE, PRIVILEGE_UNPRIVILEGED, MODE_ANY, "Begin showing lines that match", NULL, NULL, NULL); c = cli_register_filter(cli, "between", cli_range_filter_init, cli_range_filter, PRIVILEGE_UNPRIVILEGED, MODE_ANY, "Between lines that match"); if (!c) { cli_done(cli); return 0; } cli_register_optarg(c, "range_start", CLI_CMD_ARGUMENT, PRIVILEGE_UNPRIVILEGED, MODE_ANY, "Begin showing lines that match", NULL, NULL, NULL); cli_register_optarg(c, "range_end", CLI_CMD_ARGUMENT | CLI_CMD_REMAINDER_OF_LINE, PRIVILEGE_UNPRIVILEGED, MODE_ANY, "Stop showing lines that match", NULL, NULL, NULL); cli_register_filter(cli, "count", cli_count_filter_init, cli_count_filter, PRIVILEGE_UNPRIVILEGED, MODE_ANY, "Count of lines"); c = cli_register_filter(cli, "exclude", cli_match_filter_init, cli_match_filter, PRIVILEGE_UNPRIVILEGED, MODE_ANY, "Exclude lines that match"); if (!c) { cli_done(cli); return 0; } cli_register_optarg(c, "search_pattern", CLI_CMD_ARGUMENT | CLI_CMD_REMAINDER_OF_LINE, PRIVILEGE_UNPRIVILEGED, MODE_ANY, "Search pattern", NULL, NULL, NULL); c = cli_register_filter(cli, "include", cli_match_filter_init, cli_match_filter, PRIVILEGE_UNPRIVILEGED, MODE_ANY, "Include lines that match"); if (!c) { cli_done(cli); return 0; } cli_register_optarg(c, "search_pattern", CLI_CMD_ARGUMENT | CLI_CMD_REMAINDER_OF_LINE, PRIVILEGE_UNPRIVILEGED, MODE_ANY, "Search pattern", NULL, NULL, NULL); c = cli_register_filter(cli, "grep", cli_match_filter_init, cli_match_filter, PRIVILEGE_UNPRIVILEGED, MODE_ANY, "Include lines that match regex (options: -v, -i, -e)"); if (!c) { cli_done(cli); return 0; } cli_register_optarg(c, "search_flags", CLI_CMD_HYPHENATED_OPTION, PRIVILEGE_UNPRIVILEGED, MODE_ANY, "Search flags (-[ivx]", NULL, cli_search_flags_validator, NULL); cli_register_optarg(c, "search_pattern", CLI_CMD_ARGUMENT | CLI_CMD_REMAINDER_OF_LINE, PRIVILEGE_UNPRIVILEGED, MODE_ANY, "Search pattern", NULL, NULL, NULL); c = cli_register_filter(cli, "egrep", cli_match_filter_init, cli_match_filter, PRIVILEGE_UNPRIVILEGED, MODE_ANY, "Include lines that match extended regex"); if (!c) { cli_done(cli); return 0; } cli_register_optarg(c, "search_flags", CLI_CMD_HYPHENATED_OPTION, PRIVILEGE_UNPRIVILEGED, MODE_ANY, "Search flags (-[ivx]", NULL, cli_search_flags_validator, NULL); cli_register_optarg(c, "search_pattern", CLI_CMD_ARGUMENT | CLI_CMD_REMAINDER_OF_LINE, PRIVILEGE_UNPRIVILEGED, MODE_ANY, "Search pattern", NULL, NULL, NULL); cli->privilege = cli->mode = -1; cli_set_privilege(cli, PRIVILEGE_UNPRIVILEGED); cli_set_configmode(cli, MODE_EXEC, 0); // Default to 1 second timeout intervals cli->timeout_tm.tv_sec = 1; cli->timeout_tm.tv_usec = 0; // Set default idle timeout callback, but no timeout cli_set_idle_timeout_callback(cli, 0, cli_int_idle_timeout); return cli; } void cli_unregister_tree(struct cli_def *cli, struct cli_command *command, int command_type) { struct cli_command *c, *p = NULL; if (!command) command = cli->commands; for (c = command; c;) { p = c->next; if (c->command_type == command_type || command_type == CLI_ANY_COMMAND) { if (c == cli->commands) cli->commands = c->next; // Unregister all child commands cli_free_command(cli, c); } c = p; } } void cli_unregister_all(struct cli_def *cli, struct cli_command *command) { cli_unregister_tree(cli, command, CLI_REGULAR_COMMAND); } int cli_done(struct cli_def *cli) { if (!cli) return CLI_OK; struct unp *u = cli->users, *n; cli_free_history(cli); // Free all users while (u) { if (u->username) free(u->username); if (u->password) free(u->password); n = u->next; free(u); u = n; } if (cli->buildmode) cli_int_free_buildmode(cli); cli_unregister_tree(cli, cli->commands, CLI_ANY_COMMAND); free_z(cli->promptchar); free_z(cli->modestring); free_z(cli->banner); free_z(cli->promptchar); free_z(cli->hostname); free_z(cli->buffer); free_z(cli); return CLI_OK; } static int cli_add_history(struct cli_def *cli, const char *cmd) { int i; for (i = 0; i < MAX_HISTORY; i++) { if (!cli->history[i]) { if (i == 0 || strcasecmp(cli->history[i - 1], cmd)) if (!(cli->history[i] = strdup(cmd))) return CLI_ERROR; return CLI_OK; } } // No space found, drop one off the beginning of the list free(cli->history[0]); for (i = 0; i < MAX_HISTORY - 1; i++) cli->history[i] = cli->history[i + 1]; if (!(cli->history[MAX_HISTORY - 1] = strdup(cmd))) return CLI_ERROR; return CLI_OK; } void cli_free_history(struct cli_def *cli) { int i; for (i = 0; i < MAX_HISTORY; i++) { if (cli->history[i]) free_z(cli->history[i]); } } static char *cli_int_return_newword(const char *start, const char *end) { int len = end - start; char *to = NULL; char *newword = NULL; // allocate space (including terminal NULL, then go through and deal with escaping characters as we copy them if (!(newword = calloc(len + 1, 1))) return 0; to = newword; while (start != end) { if (*start == '\\') start++; else *to++ = *start++; } return newword; } static int cli_parse_line(const char *line, char *words[], int max_words) { int nwords = 0; const char *p = line; const char *word_start = 0; int inquote = 0; while (*p) { if (!isspace(*p)) { word_start = p; break; } p++; } while (nwords < max_words - 1) { if (*p == '\\' && *(p + 1)) { p += 2; } /* * a 'word' terminates at: * - end-of-string, whitespace (if not inside quotes) * - start of quoted section (if word_start != NULL) * - end of a quoted section * - whitespace/pipe unless inside quotes */ if (!*p || *p == inquote || (word_start && !inquote && (isspace(*p) || *p == '|'))) { // if we have a word start, extract from there to this character dealing with escapes if (word_start) { if (!(words[nwords++] = cli_int_return_newword(word_start, p))) return 0; } // now figure out how to proceed // if at end_of_string we're done if (!*p) break; // found matching quote, eat it if (inquote) p++; // Skip over trailing quote if we have one inquote = 0; word_start = 0; } else if (!inquote && (*p == '"' || *p == '\'')) { if (word_start && word_start != p) { if (!(words[nwords++] = cli_int_return_newword(word_start, p))) return 0; } inquote = *p++; word_start = p; } else { if (!word_start) { if (*p == '|') { if (!(words[nwords++] = strdup("|"))) return 0; } else if (!isspace(*p)) word_start = p; } p++; } } return nwords; } static char *join_words(int argc, char **argv) { char *p; int len = 0; int i; for (i = 0; i < argc; i++) { if (i) len += 1; len += strlen(argv[i]); } p = malloc(len + 1); if (!p) return NULL; p[0] = 0; for (i = 0; i < argc; i++) { if (i) strcat(p, " "); strcat(p, argv[i]); } return p; } int cli_run_command(struct cli_def *cli, const char *command) { int rc = CLI_ERROR; struct cli_pipeline *pipeline; // Split command into pipeline stages pipeline = cli_int_generate_pipeline(cli, command); // cli_int_validate_pipeline will deal with buildmode command setup, and return CLI_BUILDMODE_START if found. if (pipeline) rc = cli_int_validate_pipeline(cli, pipeline); if (rc == CLI_OK) { rc = cli_int_execute_pipeline(cli, pipeline); } cli_int_free_pipeline(pipeline); return rc; } void cli_get_completions(struct cli_def *cli, const char *command, char lastchar, struct cli_comphelp *comphelp) { struct cli_command *c = NULL; struct cli_command *n = NULL; int i; int command_type; struct cli_pipeline *pipeline = NULL; struct cli_pipeline_stage *stage; char *delim_start = DELIM_NONE; char *delim_end = DELIM_NONE; if (!(pipeline = cli_int_generate_pipeline(cli, command))) goto out; stage = &pipeline->stage[pipeline->num_stages - 1]; // Check to see if either *no* input, or if the lastchar is a tab. if ((!stage->words[0] || (command[strlen(command) - 1] == ' ')) && (stage->words[stage->num_words - 1])) stage->num_words++; if (cli->buildmode) command_type = CLI_BUILDMODE_COMMAND; else if (pipeline->num_stages == 1) command_type = CLI_REGULAR_COMMAND; else command_type = CLI_FILTER_COMMAND; for (c = cli->commands, i = 0; c && i < stage->num_words; c = n) { char *strptr = NULL; char *nameptr = NULL; n = c->next; if (c->command_type != command_type) continue; if (cli->privilege < c->privilege) continue; if (c->mode != cli->mode && c->mode != MODE_ANY) continue; if (stage->words[i] && strncasecmp(c->command, stage->words[i], strlen(stage->words[i]))) continue; // Special case for 'buildmode' - skip if the argument for this command was seen, unless MULTIPLE flag is set if (cli->buildmode) { struct cli_optarg *optarg; for (optarg = cli->buildmode->command->optargs; optarg; optarg = optarg->next) { if (!strcmp(optarg->name, c->command)) break; } if (optarg && cli_find_optarg_value(cli, optarg->name, NULL) && !(optarg->flags & (CLI_CMD_OPTION_MULTIPLE))) continue; } if (i < stage->num_words - 1) { if (stage->words[i] && (strlen(stage->words[i]) < c->unique_len) && strcmp(stage->words[i], c->command)) continue; n = c->children; // If we have no more children, we've matched the *command* - remember this if (!c->children) break; i++; continue; } if (lastchar == '?') { delim_start = DELIM_NONE; delim_end = DELIM_NONE; // Note that buildmode commands need to see if that command is some optinal value if (command_type == CLI_BUILDMODE_COMMAND) { if (c->flags & (CLI_CMD_OPTIONAL_FLAG | CLI_CMD_OPTIONAL_ARGUMENT)) { delim_start = DELIM_OPT_START; delim_end = DELIM_OPT_END; } } if (asprintf(&nameptr, "%s%s%s", delim_start, c->command, delim_end) != -1) { if (asprintf(&strptr, " %s", nameptr) != -1) { cli_int_wrap_help_line(strptr, c->help, comphelp); free_z(strptr); } free(nameptr); } } else { cli_add_comphelp_entry(comphelp, c->command); } } out: if (c) { // Advance past first word of stage i++; stage->command = c; stage->first_unmatched = i; if (c->optargs) { cli_int_parse_optargs(cli, stage, c, lastchar, comphelp); } else if (lastchar == '?') { // Special case for getting help with no defined optargs.... comphelp->num_entries = -1; } if (stage->status) { // if we had an error here we need to redraw the commandline cli_reprompt(cli); } } cli_int_free_pipeline(pipeline); } static void cli_clear_line(int sockfd, char *cmd, int l, int cursor) { // Use cmd as our buffer, and overwrite contents as needed. // Backspace to beginning memset((char *)cmd, '\b', cursor); _write(sockfd, cmd, cursor); // Overwrite existing cmd with spaces memset((char *)cmd, ' ', l); _write(sockfd, cmd, l); // ..and backspace again to beginning memset((char *)cmd, '\b', l); _write(sockfd, cmd, l); // Null cmd buffer memset((char *)cmd, 0, l); } void cli_reprompt(struct cli_def *cli) { if (!cli) return; cli->showprompt = 1; } void cli_regular(struct cli_def *cli, int (*callback)(struct cli_def *cli)) { if (!cli) return; cli->regular_callback = callback; } void cli_regular_interval(struct cli_def *cli, int seconds) { if (seconds < 1) seconds = 1; cli->timeout_tm.tv_sec = seconds; cli->timeout_tm.tv_usec = 0; } #define DES_PREFIX "{crypt}" // To distinguish clear text from DES crypted #define MD5_PREFIX "$1$" // returns 0 on fail/error, 1 if password checks out static int pass_matches(const char *pass, const char *attempt) { int des; if ((des = !strncasecmp(pass, DES_PREFIX, sizeof(DES_PREFIX) - 1))) pass += sizeof(DES_PREFIX) - 1; #ifndef WIN32 // TODO(dparrish): Find a small crypt(3) function for use on windows if (des || !strncmp(pass, MD5_PREFIX, sizeof(MD5_PREFIX) - 1)) attempt = crypt(attempt, pass); #endif if (!attempt) { // silent return here... return 0; } return !strcmp(pass, attempt); } #define CTRL(c) (c - '@') static int show_prompt(struct cli_def *cli, int sockfd) { int len = 0; if (cli->hostname) len += write(sockfd, cli->hostname, strlen(cli->hostname)); if (cli->modestring) len += write(sockfd, cli->modestring, strlen(cli->modestring)); if (cli->buildmode) { len += write(sockfd, "[", 1); len += write(sockfd, cli->buildmode->cname, strlen(cli->buildmode->cname)); len += write(sockfd, "...", 3); if (cli->buildmode->mode_text) len += write(sockfd, cli->buildmode->mode_text, strlen(cli->buildmode->mode_text)); len += write(sockfd, "]", 1); } return len + write(sockfd, cli->promptchar, strlen(cli->promptchar)); } int cli_loop(struct cli_def *cli, int sockfd) { int n, l, oldl = 0, is_telnet_option = 0, skip = 0, esc = 0, cursor = 0; char *cmd = NULL, *oldcmd = 0; char *username = NULL, *password = NULL; cli_build_shortest(cli, cli->commands); cli->state = STATE_LOGIN; cli_free_history(cli); if (cli->telnet_protocol) { static const char *negotiate = "\xFF\xFB\x03" "\xFF\xFB\x01" "\xFF\xFD\x03" "\xFF\xFD\x01"; _write(sockfd, negotiate, strlen(negotiate)); } if ((cmd = malloc(CLI_MAX_LINE_LENGTH)) == NULL) return CLI_ERROR; #ifdef WIN32 /* * OMG, HACK */ if (!(cli->client = fdopen(_open_osfhandle(sockfd, 0), "w+"))) return CLI_ERROR; cli->client->_file = sockfd; #else if (!(cli->client = fdopen(sockfd, "w+"))) { free(cmd); return CLI_ERROR; } #endif #ifndef LIBCLI_USE_POLL /* Do a range check *early*, and punt if we were passed a file descriptor * that is out of the valid range */ if (sockfd >= FD_SETSIZE) { fprintf(stderr, "CLI_LOOP() called with sockfd > FD_SETSIZE - aborting\n"); cli_error(cli, "CLI_LOOP() called with sockfd > FD_SETSIZE - exiting cli_loop\n"); return CLI_ERROR; } #endif setbuf(cli->client, NULL); if (cli->banner) cli_error(cli, "%s", cli->banner); // Set the last action now so we don't time immediately if (cli->idle_timeout) time(&cli->last_action); // Start off in unprivileged mode cli_set_privilege(cli, PRIVILEGE_UNPRIVILEGED); cli_set_configmode(cli, MODE_EXEC, NULL); // No auth required? if (!cli->users && !cli->auth_callback) cli->state = STATE_NORMAL; while (1) { signed int in_history = 0; unsigned char lastchar = '\0'; unsigned char c = '\0'; struct timeval tm; cli->showprompt = 1; if (oldcmd) { l = cursor = oldl; oldcmd[l] = 0; cli->showprompt = 1; oldcmd = NULL; oldl = 0; } else { memset(cmd, 0, CLI_MAX_LINE_LENGTH); l = 0; cursor = 0; } memcpy(&tm, &cli->timeout_tm, sizeof(tm)); while (1) { int sr; /* * Ensure our transient mode is reset to the starting mode on *each* loop traversal transient mode is valid only * while a command is being evaluated/executed. Also explicitly set the disallow_buildmode flag based on whether * or not cli->buildmode is NULL or not. The cli->buildmode flag can be changed during process, but the * enable/disable needs to be set before any processing is entered. */ cli->transient_mode = cli->mode; cli->disallow_buildmode = (cli->buildmode) ? 1 : 0; if (cli->showprompt) { if (cli->state != STATE_PASSWORD && cli->state != STATE_ENABLE_PASSWORD) _write(sockfd, "\r\n", 2); switch (cli->state) { case STATE_LOGIN: _write(sockfd, "Username: ", strlen("Username: ")); break; case STATE_PASSWORD: _write(sockfd, "Password: ", strlen("Password: ")); break; case STATE_NORMAL: case STATE_ENABLE: show_prompt(cli, sockfd); _write(sockfd, cmd, l); if (cursor < l) { int n = l - cursor; while (n--) _write(sockfd, "\b", 1); } break; case STATE_ENABLE_PASSWORD: _write(sockfd, "Password: ", strlen("Password: ")); break; } cli->showprompt = 0; } if ((sr = cli_socket_wait(sockfd, &tm)) < 0) { if (errno == EINTR) continue; perror(CLI_SOCKET_WAIT_PERROR); l = -1; break; } if (sr == 0) { // Timeout every second if (cli->regular_callback && cli->regular_callback(cli) != CLI_OK) { l = -1; break; } if (cli->idle_timeout) { if (time(NULL) - cli->last_action >= cli->idle_timeout) { if (cli->idle_timeout_callback) { // Call the callback and continue on if successful if (cli->idle_timeout_callback(cli) == CLI_OK) { // Reset the idle timeout counter time(&cli->last_action); continue; } } // Otherwise, break out of the main loop l = -1; break; } } memcpy(&tm, &cli->timeout_tm, sizeof(tm)); continue; } if ((n = read(sockfd, &c, 1)) < 0) { if (errno == EINTR) continue; perror("read"); l = -1; break; } if (cli->idle_timeout) time(&cli->last_action); if (n == 0) { l = -1; break; } if (skip) { skip--; continue; } if (c == 255 && !is_telnet_option) { is_telnet_option++; continue; } if (is_telnet_option) { if (c >= 251 && c <= 254) { is_telnet_option = c; continue; } if (c != 255) { is_telnet_option = 0; continue; } is_telnet_option = 0; } // Handle ANSI arrows if (esc) { if (esc == '[') { // Remap to readline control codes switch (c) { case 'A': // Up c = CTRL('P'); break; case 'B': // Down c = CTRL('N'); break; case 'C': // Right c = CTRL('F'); break; case 'D': // Left c = CTRL('B'); break; default: c = 0; } esc = 0; } else { esc = (c == '[') ? c : 0; continue; } } if (c == 0) continue; if (c == '\n') continue; if (c == '\r') { if (cli->state != STATE_PASSWORD && cli->state != STATE_ENABLE_PASSWORD) _write(sockfd, "\r\n", 2); break; } if (c == 27) { esc = 1; continue; } if (c == CTRL('C')) { _write(sockfd, "\a", 1); continue; } // Back word, backspace/delete if (c == CTRL('W') || c == CTRL('H') || c == 0x7f) { int back = 0; if (c == CTRL('W')) { // Word int nc = cursor; if (l == 0 || cursor == 0) continue; while (nc && cmd[nc - 1] == ' ') { nc--; back++; } while (nc && cmd[nc - 1] != ' ') { nc--; back++; } } else { // Char if (l == 0 || cursor == 0) { _write(sockfd, "\a", 1); continue; } back = 1; } if (back) { while (back--) { if (l == cursor) { cmd[--cursor] = 0; if (cli->state != STATE_PASSWORD && cli->state != STATE_ENABLE_PASSWORD) _write(sockfd, "\b \b", 3); } else { int i; if (cli->state != STATE_PASSWORD && cli->state != STATE_ENABLE_PASSWORD) { // Back up one space, then write current buffer followed by a space _write(sockfd, "\b", 1); _write(sockfd, cmd + cursor, l - cursor); _write(sockfd, " ", 1); // Move everything one char left memmove(cmd + cursor - 1, cmd + cursor, l - cursor); // Set former last char to null cmd[l - 1] = 0; // And reposition cursor for (i = l; i >= cursor; i--) _write(sockfd, "\b", 1); } cursor--; } l--; } continue; } } // Redraw if (c == CTRL('L')) { int i; int cursorback = l - cursor; if (cli->state == STATE_PASSWORD || cli->state == STATE_ENABLE_PASSWORD) continue; _write(sockfd, "\r\n", 2); show_prompt(cli, sockfd); _write(sockfd, cmd, l); for (i = 0; i < cursorback; i++) _write(sockfd, "\b", 1); continue; } // Clear line if (c == CTRL('U')) { if (cli->state == STATE_PASSWORD || cli->state == STATE_ENABLE_PASSWORD) memset(cmd, 0, l); else cli_clear_line(sockfd, cmd, l, cursor); l = cursor = 0; continue; } // Kill to EOL if (c == CTRL('K')) { if (cursor == l) continue; if (cli->state != STATE_PASSWORD && cli->state != STATE_ENABLE_PASSWORD) { int cptr; for (cptr = cursor; cptr < l; cptr++) _write(sockfd, " ", 1); for (cptr = cursor; cptr < l; cptr++) _write(sockfd, "\b", 1); } memset(cmd + cursor, 0, l - cursor); l = cursor; continue; } // EOT if (c == CTRL('D')) { if (cli->state == STATE_PASSWORD || cli->state == STATE_ENABLE_PASSWORD) break; if (l) continue; l = -1; break; } // Disable if (c == CTRL('Z')) { if (cli->mode != MODE_EXEC) { if (cli->buildmode) cli_int_free_buildmode(cli); cli_clear_line(sockfd, cmd, l, cursor); cli_set_configmode(cli, MODE_EXEC, NULL); l = cursor = 0; cli->showprompt = 1; } continue; } // TAB completion if (c == CTRL('I')) { struct cli_comphelp comphelp = {0}; if (cli->state == STATE_LOGIN || cli->state == STATE_PASSWORD || cli->state == STATE_ENABLE_PASSWORD) continue; if (cursor != l) continue; cli_get_completions(cli, cmd, c, &comphelp); if (comphelp.num_entries == 0) { _write(sockfd, "\a", 1); } else if (lastchar == CTRL('I')) { // Double tab int i; for (i = 0; i < comphelp.num_entries; i++) { if (i % 4 == 0) _write(sockfd, "\r\n", 2); else _write(sockfd, " ", 1); _write(sockfd, comphelp.entries[i], strlen(comphelp.entries[i])); } _write(sockfd, "\r\n", 2); cli->showprompt = 1; } else if (comphelp.num_entries == 1) { // Single completion - show *unless* the optional/required 'prefix' is present if (comphelp.entries[0][0] != '[' && comphelp.entries[0][0] != '<') { for (; l > 0; l--, cursor--) { if (cmd[l - 1] == ' ' || cmd[l - 1] == '|' || (comphelp.comma_separated && cmd[l - 1] == ',')) break; _write(sockfd, "\b", 1); } strcpy((cmd + l), comphelp.entries[0]); l += strlen(comphelp.entries[0]); cmd[l++] = ' '; cursor = l; _write(sockfd, comphelp.entries[0], strlen(comphelp.entries[0])); _write(sockfd, " ", 1); // And now forget the tab, since we just found a single match lastchar = '\0'; } else { // Yes, we had a match, but it wasn't required - remember the tab in case the user double tabs.... lastchar = CTRL('I'); } } else if (comphelp.num_entries > 1) { /* * More than one completion. * Show as many characters as we can until the completions start to differ. */ lastchar = c; int i, j, k = 0; char *tptr = comphelp.entries[0]; /* * Quickly try to see where our entries differ. * Corner cases: * - If all entries are optional, don't show *any* options unless user has provided a letter. * - If any entry starts with '<' then don't fill in anything. */ // Skip a leading '[' k = strlen(tptr); if (*tptr == '[') tptr++; else if (*tptr == '<') k = 0; for (i = 1; k != 0 && i < comphelp.num_entries; i++) { char *wptr = comphelp.entries[i]; if (*wptr == '[') wptr++; else if (*wptr == '<') k = 0; for (j = 0; (j < k) && (j < (int)strlen(wptr)); j++) { if (strncasecmp(tptr + j, wptr + j, 1)) break; } k = j; } // Try to show minimum match string if we have a non-zero k and the first letter of the last word is not '['. if (k && comphelp.entries[comphelp.num_entries - 1][0] != '[') { for (; l > 0; l--, cursor--) { if (cmd[l - 1] == ' ' || cmd[l - 1] == '|' || (comphelp.comma_separated && cmd[l - 1] == ',')) break; _write(sockfd, "\b", 1); } strncpy(cmd + l, tptr, k); l += k; cursor = l; _write(sockfd, tptr, k); } else { _write(sockfd, "\a", 1); } } cli_free_comphelp(&comphelp); continue; } // '?' at end of line - generate applicable 'help' messages if (c == '?' && cursor == l) { struct cli_comphelp comphelp = {0}; int i; int show_cr = 1; if (cli->state == STATE_LOGIN || cli->state == STATE_PASSWORD || cli->state == STATE_ENABLE_PASSWORD) continue; if (cursor != l) continue; cli_get_completions(cli, cmd, c, &comphelp); if (comphelp.num_entries == 0) { _write(sockfd, "\a", 1); } else if (comphelp.num_entries > 0) { cli->showprompt = 1; _write(sockfd, "\r\n", 2); for (i = 0; i < (int)comphelp.num_entries; i++) { if (comphelp.entries[i][2] != '[') show_cr = 0; cli_error(cli, "%s", comphelp.entries[i]); } if (show_cr) cli_error(cli, " "); } cli_free_comphelp(&comphelp); if (comphelp.num_entries >= 0) continue; } // History if (c == CTRL('P') || c == CTRL('N')) { int history_found = 0; if (cli->state == STATE_LOGIN || cli->state == STATE_PASSWORD || cli->state == STATE_ENABLE_PASSWORD) continue; if (c == CTRL('P')) { // Up in_history--; if (in_history < 0) { for (in_history = MAX_HISTORY - 1; in_history >= 0; in_history--) { if (cli->history[in_history]) { history_found = 1; break; } } } else { if (cli->history[in_history]) history_found = 1; } } else { // Down in_history++; if (in_history >= MAX_HISTORY || !cli->history[in_history]) { int i = 0; for (i = 0; i < MAX_HISTORY; i++) { if (cli->history[i]) { in_history = i; history_found = 1; break; } } } else { if (cli->history[in_history]) history_found = 1; } } if (history_found && cli->history[in_history]) { // Show history item cli_clear_line(sockfd, cmd, l, cursor); memset(cmd, 0, CLI_MAX_LINE_LENGTH); strncpy(cmd, cli->history[in_history], CLI_MAX_LINE_LENGTH - 1); l = cursor = strlen(cmd); _write(sockfd, cmd, l); } continue; } // Left/right cursor motion if (c == CTRL('B') || c == CTRL('F')) { if (c == CTRL('B')) { // Left if (cursor) { if (cli->state != STATE_PASSWORD && cli->state != STATE_ENABLE_PASSWORD) _write(sockfd, "\b", 1); cursor--; } } else { // Right if (cursor < l) { if (cli->state != STATE_PASSWORD && cli->state != STATE_ENABLE_PASSWORD) _write(sockfd, &cmd[cursor], 1); cursor++; } } continue; } if (c == CTRL('A')) { // Start of line if (cursor) { if (cli->state != STATE_PASSWORD && cli->state != STATE_ENABLE_PASSWORD) { _write(sockfd, "\r", 1); show_prompt(cli, sockfd); } cursor = 0; } continue; } if (c == CTRL('E')) { // End of line if (cursor < l) { if (cli->state != STATE_PASSWORD && cli->state != STATE_ENABLE_PASSWORD) _write(sockfd, &cmd[cursor], l - cursor); cursor = l; } continue; } if (cursor == l) { // Normal character typed. // Append to end of line if not at end-of-buffer. if (l < CLI_MAX_LINE_LENGTH - 1) { cmd[cursor] = c; l++; cursor++; } else { // End-of-buffer, ensure null terminated cmd[cursor] = 0; _write(sockfd, "\a", 1); continue; } } else { // Middle of text int i; // Move everything one character to the right memmove(cmd + cursor + 1, cmd + cursor, l - cursor); // Insert new character cmd[cursor] = c; // IMPORTANT - if at end of buffer, set last char to NULL and don't change length, otherwise bump length by 1 if (l == CLI_MAX_LINE_LENGTH - 1) { cmd[l] = 0; } else { l++; } // Write buffer, then backspace to where we were _write(sockfd, cmd + cursor, l - cursor); for (i = 0; i < (l - cursor); i++) _write(sockfd, "\b", 1); cursor++; } if (cli->state != STATE_PASSWORD && cli->state != STATE_ENABLE_PASSWORD) { if (c == '?' && cursor == l) { _write(sockfd, "\r\n", 2); oldcmd = cmd; oldl = cursor = l - 1; break; } _write(sockfd, &c, 1); } oldcmd = 0; oldl = 0; lastchar = c; } if (l < 0) break; if (cli->state == STATE_LOGIN) { if (l == 0) continue; // Require login free_z(username); if (!(username = strdup(cmd))) return 0; cli->state = STATE_PASSWORD; cli->showprompt = 1; } else if (cli->state == STATE_PASSWORD) { // Require password int allowed = 0; free_z(password); if (!(password = strdup(cmd))) return 0; if (cli->auth_callback) { if (cli->auth_callback(username, password) == CLI_OK) allowed++; } if (!allowed) { struct unp *u; for (u = cli->users; u; u = u->next) { if (!strcmp(u->username, username) && pass_matches(u->password, password)) { allowed++; break; } } } if (allowed) { cli_error(cli, " "); cli->state = STATE_NORMAL; } else { cli_error(cli, "\n\nAccess denied"); free_z(username); free_z(password); cli->state = STATE_LOGIN; } cli->showprompt = 1; } else if (cli->state == STATE_ENABLE_PASSWORD) { int allowed = 0; if (cli->enable_password) { // Check stored static enable password if (pass_matches(cli->enable_password, cmd)) allowed++; } if (!allowed && cli->enable_callback) { // Check callback if (cli->enable_callback(cmd)) allowed++; } if (allowed) { cli->state = STATE_ENABLE; cli_set_privilege(cli, PRIVILEGE_PRIVILEGED); } else { cli_error(cli, "\n\nAccess denied"); cli->state = STATE_NORMAL; } } else { int rc; if (l == 0) continue; if (cmd[l - 1] != '?' && strcasecmp(cmd, "history") != 0) cli_add_history(cli, cmd); rc = cli_run_command(cli, cmd); switch (rc) { case CLI_BUILDMODE_ERROR: // Unable to enter buildmode successfully cli_print(cli, "Failure entering build mode for '%s'", cli->buildmode->cname); cli_int_free_buildmode(cli); continue; case CLI_BUILDMODE_CANCEL: // Called if user enters 'cancel' cli_print(cli, "Canceling build mode for '%s'", cli->buildmode->cname); cli_int_free_buildmode(cli); break; case CLI_BUILDMODE_EXIT: // Called when user enters exit - rebuild *entire* command line. // Recall all located optargs cli->found_optargs = cli->buildmode->found_optargs; rc = cli_int_execute_buildmode(cli); break; case CLI_QUIT: break; case CLI_BUILDMODE_START: case CLI_BUILDMODE_EXTEND: default: break; } // Process is done if we get a CLI_QUIT, if (rc == CLI_QUIT) break; } // Update the last_action time now as the last command run could take a long time to return if (cli->idle_timeout) time(&cli->last_action); } cli_free_history(cli); free_z(username); free_z(password); free_z(cmd); fclose(cli->client); cli->client = 0; return CLI_OK; } int cli_file(struct cli_def *cli, FILE *fh, int privilege, int mode) { int oldpriv = cli_set_privilege(cli, privilege); int oldmode = cli_set_configmode(cli, mode, NULL); char buf[CLI_MAX_LINE_LENGTH]; while (1) { char *p; char *cmd; char *end; // End of file if (fgets(buf, CLI_MAX_LINE_LENGTH - 1, fh) == NULL) break; if ((p = strpbrk(buf, "#\r\n"))) *p = 0; cmd = buf; while (isspace(*cmd)) cmd++; if (!*cmd) continue; for (p = end = cmd; *p; p++) if (!isspace(*p)) end = p; *++end = 0; if (strcasecmp(cmd, "quit") == 0) break; if (cli_run_command(cli, cmd) == CLI_QUIT) break; } cli_set_privilege(cli, oldpriv); cli_set_configmode(cli, oldmode, NULL); return CLI_OK; } static void _print(struct cli_def *cli, int print_mode, const char *format, va_list ap) { int n; char *p = NULL; if (!cli) return; n = vasprintf(&p, format, ap); if (n < 0) return; if (cli->buffer) free(cli->buffer); cli->buffer = p; cli->buf_size = n; p = cli->buffer; do { char *next = strchr(p, '\n'); struct cli_filter *f = (print_mode & PRINT_FILTERED) ? cli->filters : 0; int print = 1; if (next) *next++ = 0; else if (print_mode & PRINT_BUFFERED) break; while (print && f) { print = (f->filter(cli, p, f->data) == CLI_OK); f = f->next; } if (print) { if (cli->print_callback) cli->print_callback(cli, p); else if (cli->client) fprintf(cli->client, "%s\r\n", p); } p = next; } while (p); if (p && *p) { if (p != cli->buffer) memmove(cli->buffer, p, strlen(p)); } else *cli->buffer = 0; } void cli_bufprint(struct cli_def *cli, const char *format, ...) { va_list ap; va_start(ap, format); _print(cli, PRINT_BUFFERED | PRINT_FILTERED, format, ap); va_end(ap); } void cli_vabufprint(struct cli_def *cli, const char *format, va_list ap) { _print(cli, PRINT_BUFFERED, format, ap); } void cli_print(struct cli_def *cli, const char *format, ...) { va_list ap; va_start(ap, format); _print(cli, PRINT_FILTERED, format, ap); va_end(ap); } void cli_error(struct cli_def *cli, const char *format, ...) { va_list ap; va_start(ap, format); _print(cli, PRINT_PLAIN, format, ap); va_end(ap); } struct cli_match_filter_state { int flags; union { char *string; regex_t re; } match; }; int cli_search_flags_validator(struct cli_def *cli, const char *word, const char *value) { // Valid search flags starts with a hyphen, then any number of i, v, or e characters. if ((*value++ == '-') && (*value) && (strspn(value, "vie") == strlen(value))) return CLI_OK; return CLI_ERROR; } int cli_match_filter_init(struct cli_def *cli, int argc, char **argv, struct cli_filter *filt) { struct cli_match_filter_state *state; char *search_pattern = cli_get_optarg_value(cli, "search_pattern", NULL); char *search_flags = cli_get_optarg_value(cli, "search_flags", NULL); filt->filter = cli_match_filter; filt->data = state = calloc(sizeof(struct cli_match_filter_state), 1); if (!state) return CLI_ERROR; if (!strcmp(cli->pipeline->current_stage->words[0], "include")) { state->match.string = search_pattern; } else if (!strcmp(cli->pipeline->current_stage->words[0], "exclude")) { state->match.string = search_pattern; state->flags = MATCH_INVERT; #ifndef WIN32 } else { int rflags = REG_NOSUB; if (!strcmp(cli->pipeline->current_stage->words[0], "grep")) { state->flags = MATCH_REGEX; } else if (!strcmp(cli->pipeline->current_stage->words[0], "egrep")) { state->flags = MATCH_REGEX; rflags |= REG_EXTENDED; } if (search_flags) { char *p = search_flags++; while (*p) { switch (*p++) { case 'v': state->flags |= MATCH_INVERT; break; case 'i': rflags |= REG_ICASE; break; case 'e': // Implies next term is search string, so stop processing flags break; } } } if (regcomp(&state->match.re, search_pattern, rflags)) { if (cli->client) fprintf(cli->client, "Invalid pattern \"%s\"\r\n", search_pattern); return CLI_ERROR; } } #else } else { // No regex functions in windows, so return an error. return CLI_ERROR; } #endif return CLI_OK; } int cli_match_filter(UNUSED(struct cli_def *cli), const char *string, void *data) { struct cli_match_filter_state *state = data; int r = CLI_ERROR; if (!string) { if (state->flags & MATCH_REGEX) regfree(&state->match.re); free(state); return CLI_OK; } if (state->flags & MATCH_REGEX) { if (!regexec(&state->match.re, string, 0, NULL, 0)) r = CLI_OK; } else { if (strstr(string, state->match.string)) r = CLI_OK; } if (state->flags & MATCH_INVERT) { if (r == CLI_OK) r = CLI_ERROR; else r = CLI_OK; } return r; } struct cli_range_filter_state { int matched; char *from; char *to; }; int cli_range_filter_init(struct cli_def *cli, int argc, char **argv, struct cli_filter *filt) { struct cli_range_filter_state *state; char *from = cli_get_optarg_value(cli, "range_start", NULL); char *to = cli_get_optarg_value(cli, "range_end", NULL); // Do not have to check from/to since we would not have gotten here if we were missing a required argument. // Note that since those pointers are not NULL, we don't have to strdup them or free them - just use the pointer // from the command line processing and continue filt->filter = cli_range_filter; filt->data = state = calloc(sizeof(struct cli_range_filter_state), 1); if (state) { state->from = from; state->to = to; return CLI_OK; } else { return CLI_ERROR; } } int cli_range_filter(UNUSED(struct cli_def *cli), const char *string, void *data) { struct cli_range_filter_state *state = data; int r = CLI_ERROR; if (!string) { free_z(state); return CLI_OK; } if (!state->matched) state->matched = !!strstr(string, state->from); if (state->matched) { r = CLI_OK; if (state->to && strstr(string, state->to)) state->matched = 0; } return r; } int cli_count_filter_init(struct cli_def *cli, int argc, UNUSED(char **argv), struct cli_filter *filt) { if (argc > 1) { if (cli->client) fprintf(cli->client, "Count filter does not take arguments\r\n"); return CLI_ERROR; } filt->filter = cli_count_filter; if (!(filt->data = calloc(sizeof(int), 1))) return CLI_ERROR; return CLI_OK; } int cli_count_filter(struct cli_def *cli, const char *string, void *data) { int *count = data; if (!string) { // Print count if (cli->client) fprintf(cli->client, "%d\r\n", *count); free(count); return CLI_OK; } while (isspace(*string)) string++; // Only count non-blank lines if (*string) (*count)++; return CLI_ERROR; } void cli_print_callback(struct cli_def *cli, void (*callback)(struct cli_def *, const char *)) { cli->print_callback = callback; } void cli_set_idle_timeout(struct cli_def *cli, unsigned int seconds) { if (seconds < 1) seconds = 0; cli->idle_timeout = seconds; time(&cli->last_action); } void cli_set_idle_timeout_callback(struct cli_def *cli, unsigned int seconds, int (*callback)(struct cli_def *)) { cli_set_idle_timeout(cli, seconds); cli->idle_timeout_callback = callback; } void cli_telnet_protocol(struct cli_def *cli, int telnet_protocol) { cli->telnet_protocol = !!telnet_protocol; } void cli_set_context(struct cli_def *cli, void *context) { cli->user_context = context; } void *cli_get_context(struct cli_def *cli) { return cli->user_context; } struct cli_command *cli_register_filter(struct cli_def *cli, const char *command, int (*init)(struct cli_def *cli, int, char **, struct cli_filter *), int (*filter)(struct cli_def *, const char *, void *), int privilege, int mode, const char *help) { struct cli_command *c; if (!command) return NULL; if (!(c = calloc(sizeof(struct cli_command), 1))) return NULL; c->command_type = CLI_FILTER_COMMAND; c->init = init; c->filter = filter; c->next = NULL; if (!(c->command = strdup(command))) { free(c); return NULL; } c->privilege = privilege; c->mode = mode; if (help && !(c->help = strdup(help))) { free(c->command); free(c); return NULL; } // Filters are all registered at the top level. return cli_register_command_core(cli, NULL, c); } int cli_unregister_filter(struct cli_def *cli, const char *command) { return cli_int_unregister_command_core(cli, command, CLI_FILTER_COMMAND); } void cli_int_free_found_optargs(struct cli_optarg_pair **optarg_pair) { struct cli_optarg_pair *c; if (!optarg_pair || !*optarg_pair) return; for (c = *optarg_pair; c;) { *optarg_pair = c->next; free_z(c->name); free_z(c->value); free_z(c); c = *optarg_pair; } } char *cli_find_optarg_value(struct cli_def *cli, char *name, char *find_after) { char *value = NULL; struct cli_optarg_pair *optarg_pair; if (!name || !cli->found_optargs) return NULL; for (optarg_pair = cli->found_optargs; optarg_pair && !value; optarg_pair = optarg_pair->next) { if (strcmp(optarg_pair->name, name) == 0) { if (find_after && find_after == optarg_pair->value) { find_after = NULL; continue; } value = optarg_pair->value; } } return value; } static void cli_optarg_build_shortest(struct cli_optarg *optarg) { struct cli_optarg *c, *p; char *cp, *pp; unsigned int len; for (c = optarg; c; c = c->next) { c->unique_len = 1; for (p = optarg; p; p = p->next) { if (c == p) continue; cp = c->name; pp = p->name; len = 1; while (*cp && *pp && *cp++ == *pp++) len++; if (len > c->unique_len) c->unique_len = len; } } } void cli_free_optarg(struct cli_optarg *optarg) { free_z(optarg->help); free_z(optarg->name); free_z(optarg); } int cli_optarg_addhelp(struct cli_optarg *optarg, const char *helpname, const char *helptext) { char *tstr; // put a vertical tab (\v), the new helpname, a horizontal tab (\t), and then the new help text if ((!optarg) || (asprintf(&tstr, "%s\v%s\t%s", optarg->help, helpname, helptext) == -1)) { return CLI_ERROR; } else { free(optarg->help); optarg->help = tstr; } return CLI_OK; } struct cli_optarg *cli_register_optarg(struct cli_command *cmd, const char *name, int flags, int privilege, int mode, const char *help, int (*get_completions)(struct cli_def *cli, const char *, const char *, struct cli_comphelp *), int (*validator)(struct cli_def *cli, const char *, const char *), int (*transient_mode)(struct cli_def *cli, const char *, const char *)) { struct cli_optarg *optarg = NULL; struct cli_optarg *lastopt = NULL; struct cli_optarg *ptr = NULL; int retval = CLI_ERROR; // Name must not already exist with this priv/mode for (ptr = cmd->optargs, lastopt = NULL; ptr; lastopt = ptr, ptr = ptr->next) { if (!strcmp(name, ptr->name) && ptr->mode == mode && ptr->privilege == privilege) { goto CLEANUP; } } if (!(optarg = calloc(sizeof(struct cli_optarg), 1))) goto CLEANUP; if (!(optarg->name = strdup(name))) goto CLEANUP; if (help && !(optarg->help = strdup(help))) goto CLEANUP; optarg->mode = mode; optarg->privilege = privilege; optarg->get_completions = get_completions; optarg->validator = validator; optarg->transient_mode = transient_mode; optarg->flags = flags; if (lastopt) lastopt->next = optarg; else cmd->optargs = optarg; cli_optarg_build_shortest(cmd->optargs); retval = CLI_OK; CLEANUP: if (retval != CLI_OK) { cli_free_optarg(optarg); optarg = NULL; } return optarg; } int cli_unregister_optarg(struct cli_command *cmd, const char *name) { struct cli_optarg *ptr; struct cli_optarg *lastptr; int retval = CLI_ERROR; // Iterate looking for this option name, stopping at end or if name matches for (lastptr = NULL, ptr = cmd->optargs; ptr && strcmp(ptr->name, name); lastptr = ptr, ptr = ptr->next) ; // If ptr, then we found the optarg to delete if (ptr) { if (lastptr) { // Not first optarg lastptr->next = ptr->next; ptr->next = NULL; } else { // First optarg cmd->optargs = ptr->next; ptr->next = NULL; } cli_free_optarg(ptr); cli_optarg_build_shortest(cmd->optargs); retval = CLI_OK; } return retval; } void cli_unregister_all_optarg(struct cli_command *c) { struct cli_optarg *o, *p; for (o = c->optargs; o; o = p) { p = o->next; cli_free_optarg(o); } } void cli_int_unset_optarg_value(struct cli_def *cli, const char *name) { struct cli_optarg_pair **p, *c; for (p = &cli->found_optargs, c = *p; *p;) { c = *p; if (!strcmp(c->name, name)) { *p = c->next; free_z(c->name); free_z(c->value); free_z(c); } else { p = &(*p)->next; } } } int cli_set_optarg_value(struct cli_def *cli, const char *name, const char *value, int allow_multiple) { struct cli_optarg_pair *optarg_pair, **anchor; int rc = CLI_ERROR; for (optarg_pair = cli->found_optargs, anchor = &cli->found_optargs; optarg_pair; anchor = &optarg_pair->next, optarg_pair = optarg_pair->next) { // Break if we found this name *and* allow_multiple is false if (!strcmp(optarg_pair->name, name) && !allow_multiple) { break; } } // If we *didn't* find this, then allocate a new entry before proceeding if (!optarg_pair) { optarg_pair = (struct cli_optarg_pair *)calloc(1, sizeof(struct cli_optarg_pair)); *anchor = optarg_pair; } // Set the value if (optarg_pair) { // Name is null only if we didn't find it if (!optarg_pair->name) optarg_pair->name = strdup(name); // Value may be overwritten, so free any old value. if (optarg_pair->value) free_z(optarg_pair->value); optarg_pair->value = strdup(value); rc = CLI_OK; } return rc; } struct cli_optarg_pair *cli_get_all_found_optargs(struct cli_def *cli) { if (cli) return cli->found_optargs; return NULL; } char *cli_get_optarg_value(struct cli_def *cli, const char *name, char *find_after) { char *value = NULL; struct cli_optarg_pair *optarg_pair; for (optarg_pair = cli->found_optargs; !value && optarg_pair; optarg_pair = optarg_pair->next) { // Check next entry if this isn't our name if (strcasecmp(optarg_pair->name, name)) continue; // Did we have a find_after, then ignore anything up until our find_after match if (find_after && optarg_pair->value == find_after) { find_after = NULL; continue; } else if (!find_after) { value = optarg_pair->value; } } return value; } void cli_int_free_buildmode(struct cli_def *cli) { if (!cli || !cli->buildmode) return; cli_unregister_tree(cli, cli->commands, CLI_BUILDMODE_COMMAND); cli->mode = cli->buildmode->mode; free_z(cli->buildmode->mode_text); cli_int_free_found_optargs(&cli->buildmode->found_optargs); free_z(cli->buildmode); } int cli_int_enter_buildmode(struct cli_def *cli, struct cli_pipeline_stage *stage, char *mode_text) { struct cli_optarg *optarg; struct cli_command *c; struct cli_optarg *o; struct cli_buildmode *buildmode; struct cli_optarg *buildmodeOptarg = NULL; int rc = CLI_BUILDMODE_START; if (!(buildmode = (struct cli_buildmode *)calloc(1, sizeof(struct cli_buildmode)))) { cli_error(cli, "Unable to build buildmode mode for command %s", stage->command->command); rc = CLI_BUILDMODE_ERROR; goto out; } // Clean up any shrapnel from earlier - shouldn't be any but.... if (cli->buildmode) cli_int_free_buildmode(cli); // Assign it so cli_int_register_buildmode_command() has something to work with cli->buildmode = buildmode; cli->buildmode->mode = cli->mode; cli->buildmode->transient_mode = cli->transient_mode; if (mode_text) cli->buildmode->mode_text = strdup(mode_text); // Need this to verify we have all *required* arguments cli->buildmode->command = stage->command; // Build new *limited* list of commands from this commands optargs // Currently we only allow a single entry point to a buildmode, so advance t that // optarg and proceed from there. for (buildmodeOptarg = stage->command->optargs; buildmodeOptarg && !(buildmodeOptarg->flags & CLI_CMD_ALLOW_BUILDMODE); buildmodeOptarg = buildmodeOptarg->next) ; // Now start at this argument and flesh out the rest of the commands available for this buildmode for (optarg = buildmodeOptarg; optarg; optarg = optarg->next) { // Don't allow anything that could redefine our mode or buildmode mode, or redefine exit/cancel/show/unset if (!strcmp(optarg->name, "cancel") || !strcmp(optarg->name, "execute") || !strcmp(optarg->name, "show") || !strcmp(optarg->name, "unset")) { cli_error(cli, "Default buildmode command conflicts with optarg named %s", optarg->name); rc = CLI_BUILDMODE_ERROR; goto out; } if (optarg->flags & (CLI_CMD_ALLOW_BUILDMODE | CLI_CMD_TRANSIENT_MODE | CLI_CMD_SPOT_CHECK)) continue; // accept the first optarg allowing buildmode, but reject any subsequent one if (optarg->flags & CLI_CMD_ALLOW_BUILDMODE && (optarg != buildmodeOptarg)) continue; if (optarg->mode != cli->mode && optarg->mode != cli->transient_mode) continue; else if (optarg->flags & (CLI_CMD_OPTIONAL_ARGUMENT | CLI_CMD_ARGUMENT)) { if ((c = cli_int_register_buildmode_command(cli, NULL, optarg->name, cli_int_buildmode_cmd_cback, optarg->flags, optarg->privilege, cli->mode, optarg->help))) { cli_register_optarg(c, optarg->name, CLI_CMD_ARGUMENT | (optarg->flags & CLI_CMD_OPTION_MULTIPLE), optarg->privilege, cli->mode, optarg->help, optarg->get_completions, optarg->validator, NULL); } else { rc = CLI_BUILDMODE_ERROR; goto out; } } else { if (optarg->flags & CLI_CMD_OPTION_MULTIPLE) { if (!cli_int_register_buildmode_command(cli, NULL, optarg->name, cli_int_buildmode_flag_multiple_cback, optarg->flags, optarg->privilege, cli->mode, optarg->help)) { rc = CLI_BUILDMODE_ERROR; goto out; } } else { if (!cli_int_register_buildmode_command(cli, NULL, optarg->name, cli_int_buildmode_flag_cback, optarg->flags, optarg->privilege, cli->mode, optarg->help)) { rc = CLI_BUILDMODE_ERROR; goto out; } } } } cli->buildmode->cname = cli_command_name(cli, stage->command); // Now add the four 'always there' commands to cancel current mode and to execute the command, show settings, and // unset c = cli_int_register_buildmode_command(cli, NULL, "cancel", cli_int_buildmode_cancel_cback, 0, PRIVILEGE_UNPRIVILEGED, cli->mode, "Cancel command"); if (!c) { rc = CLI_BUILDMODE_ERROR; goto out; } c = cli_int_register_buildmode_command(cli, NULL, "execute", cli_int_buildmode_execute_cback, 0, PRIVILEGE_UNPRIVILEGED, cli->mode, "Execute command"); if (!c) { rc = CLI_BUILDMODE_ERROR; goto out; } c = cli_int_register_buildmode_command(cli, NULL, "show", cli_int_buildmode_show_cback, 0, PRIVILEGE_UNPRIVILEGED, cli->mode, "Show current settings"); if (!c) { rc = CLI_BUILDMODE_ERROR; goto out; } c = cli_int_register_buildmode_command(cli, NULL, "unset", cli_int_buildmode_unset_cback, 0, PRIVILEGE_UNPRIVILEGED, cli->mode, "Unset a setting"); if (!c) { rc = CLI_BUILDMODE_ERROR; goto out; } o = cli_register_optarg(c, "setting", CLI_CMD_ARGUMENT | CLI_CMD_DO_NOT_RECORD, PRIVILEGE_UNPRIVILEGED, cli->mode, "setting to clear", cli_int_buildmode_unset_completor, cli_int_buildmode_unset_validator, NULL); if (!o) { rc = CLI_BUILDMODE_ERROR; goto out; } out: // And lastly set the initial help menu for the unset command cli_int_buildmode_reset_unset_help(cli); if (rc != CLI_BUILDMODE_START) cli_int_free_buildmode(cli); return rc; } int cli_int_unregister_buildmode_command(struct cli_def *cli, const char *command) { return cli_int_unregister_command_core(cli, command, CLI_BUILDMODE_COMMAND); } struct cli_command *cli_int_register_buildmode_command(struct cli_def *cli, struct cli_command *parent, const char *command, int (*callback)(struct cli_def *cli, const char *, char **, int), int flags, int privilege, int mode, const char *help) { struct cli_command *c; if (!command) return NULL; if (!(c = calloc(sizeof(struct cli_command), 1))) return NULL; c->flags = flags; c->callback = callback; c->next = NULL; if (!(c->command = strdup(command))) { free(c); return NULL; } c->command_type = CLI_BUILDMODE_COMMAND; c->privilege = privilege; c->mode = mode; if (help && !(c->help = strndup(help, strchrnul(help, '\v') - help))) { free(c->command); free(c); return NULL; } // Buildmode commmands are all registered at the top level return cli_register_command_core(cli, NULL, c); } int cli_int_execute_buildmode(struct cli_def *cli) { struct cli_optarg *optarg = NULL; int rc = CLI_OK; char *cmdline; char *value = NULL; cmdline = strdup(cli_command_name(cli, cli->buildmode->command)); if (!cmdline) { cli_error(cli, "Unable to allocate memory to process buildmode commandline"); rc = CLI_ERROR; } for (optarg = cli->buildmode->command->optargs; rc == CLI_OK && optarg; optarg = optarg->next) { value = NULL; do { if (cli->privilege < optarg->privilege) continue; if ((optarg->mode != cli->buildmode->mode) && (optarg->mode != cli->buildmode->transient_mode) && (optarg->mode != MODE_ANY)) continue; value = cli_get_optarg_value(cli, optarg->name, value); if (!value && optarg->flags & CLI_CMD_ARGUMENT) { cli_error(cli, "Missing required argument %s", optarg->name); rc = CLI_MISSING_ARGUMENT; } else if (value) { if (optarg->flags & (CLI_CMD_OPTIONAL_FLAG | CLI_CMD_ARGUMENT)) { if (!(cmdline = cli_int_buildmode_extend_cmdline(cmdline, value))) { cli_error(cli, "Unable to allocate memory to process buildmode commandline"); rc = CLI_ERROR; } } else { if (!(cmdline = cli_int_buildmode_extend_cmdline(cmdline, optarg->name))) { cli_error(cli, "Unable to allocate memory to process buildmode commandline"); rc = CLI_ERROR; } if (!(cmdline = cli_int_buildmode_extend_cmdline(cmdline, value))) { cli_error(cli, "Unable to allocate memory to process buildmode commandline"); rc = CLI_ERROR; } } } } while (rc == CLI_OK && value && optarg->flags & CLI_CMD_OPTION_MULTIPLE); } if (rc == CLI_OK) { cli_int_free_buildmode(cli); cli_add_history(cli, cmdline); // disallow processing of buildmode so we don't wind up in a potential loop // main loop will also set as required cli->disallow_buildmode = 1; rc = cli_run_command(cli, cmdline); } free_z(cmdline); return rc; } char *cli_int_buildmode_extend_cmdline(char *cmdline, char *word) { char *tptr = NULL; char *cptr = NULL; size_t oldlen = strlen(cmdline); size_t wordlen = strlen(word); char quoteChar[2] = ""; // by default we don't add quotes, but if the word is empty, we *must* to preserve that empty string // we also need to quote it if there is a space in the word, or any unescaped quotes, so we'll have // to walk the word.... cptr = word; if (!wordlen) { quoteChar[0] = '"'; } for (cptr = word; *cptr; cptr++) { if (*cptr == '\\' && *(cptr + 1)) { cptr++; // skip over escapes blindly } else if ((*cptr == ' ') && (quoteChar[0] == '\0')) { // if we found a space we need quotes, select double unless we've already selected something quoteChar[0] = '"'; } else if (*cptr == '"') { // if our first unescaped quote is a double, then we wrap the string in single quotes quoteChar[0] = '\''; break; } else if (*cptr == '\'') { // if our first unescaped quote is a single, then we wrap the string in double quotes quoteChar[0] = '"'; break; } } // Allocate enough space to hold the old string, a space, possible quote, the new string, // another possible quote, and the final null terminator). if ((tptr = (char *)realloc(cmdline, oldlen + 1 + 1 + wordlen + 1 + 1))) { strcat(tptr, " "); strcat(tptr, quoteChar); strcat(tptr, word); strcat(tptr, quoteChar); } return tptr; } // Any time we set or unset a buildmode setting, we need to regerate the 'help' menu for the unset command void cli_int_buildmode_reset_unset_help(struct cli_def *cli) { struct cli_command *cmd; // find the buildmode unset command for (cmd = cli->commands; cmd; cmd = cmd->next) { if ((cmd->command_type == CLI_BUILDMODE_COMMAND) && !strcmp(cmd->command, "unset")) break; } if (cmd) { struct cli_optarg *optarg; for (optarg = cmd->optargs; optarg && strcmp(optarg->name, "setting"); optarg = optarg->next) ; if (optarg) { char *endOfMainHelp; struct cli_optarg_pair *optarg_pair; /* * This will ensure that any previously added help is not propogated - this left over space will be freed by the * cli_optarg_addhelp() calls a few lines down */ if ((endOfMainHelp = strchr(optarg->help, '\v'))) *endOfMainHelp = '\0'; for (optarg_pair = cli->found_optargs; optarg_pair; optarg_pair = optarg_pair->next) { // Only show vars that are also current 'commands' struct cli_command *c = cli->commands; for (; c; c = c->next) { if (c->command_type != CLI_BUILDMODE_COMMAND) continue; if (!strcmp(c->command, optarg_pair->name)) { char *tmphelp; if (asprintf(&tmphelp, "unset %s", optarg_pair->name) >= 0) { cli_optarg_addhelp(optarg, optarg_pair->name, tmphelp); free_z(tmphelp); } } } } } } } int cli_int_buildmode_cmd_cback(struct cli_def *cli, const char *command, char *argv[], int argc) { int rc = CLI_BUILDMODE_EXTEND; if (argc) { cli_error(cli, "Extra arguments on command line, command ignored."); rc = CLI_ERROR; } cli_int_buildmode_reset_unset_help(cli); return rc; } // A 'flag' callback has no optargs, so we need to set it ourself based on *this* command int cli_int_buildmode_flag_cback(struct cli_def *cli, const char *command, char *argv[], int argc) { int rc = CLI_BUILDMODE_EXTEND; if (argc) { cli_error(cli, "Extra arguments on command line, command ignored."); rc = CLI_ERROR; } if (cli_set_optarg_value(cli, command, command, 0)) { cli_error(cli, "Problem setting value for optional flag %s", command); rc = CLI_ERROR; } cli_int_buildmode_reset_unset_help(cli); return rc; } // A 'flag' callback has no optargs, so we need to set it ourself based on *this* command int cli_int_buildmode_flag_multiple_cback(struct cli_def *cli, const char *command, char *argv[], int argc) { int rc = CLI_BUILDMODE_EXTEND; if (argc) { cli_error(cli, "Extra arguments on command line, command ignored."); rc = CLI_ERROR; } if (cli_set_optarg_value(cli, command, command, CLI_CMD_OPTION_MULTIPLE)) { cli_error(cli, "Problem setting value for optional flag %s", command); rc = CLI_ERROR; } cli_int_buildmode_reset_unset_help(cli); return rc; } int cli_int_buildmode_cancel_cback(struct cli_def *cli, const char *command, char *argv[], int argc) { int rc = CLI_BUILDMODE_CANCEL; if (argc > 0) { cli_error(cli, "Extra arguments on command line, command ignored."); rc = CLI_ERROR; } return rc; } int cli_int_buildmode_execute_cback(struct cli_def *cli, const char *command, char *argv[], int argc) { int rc = CLI_BUILDMODE_EXIT; if (argc > 0) { cli_error(cli, "Extra arguments on command line, command ignored."); rc = CLI_ERROR; } return rc; } int cli_int_buildmode_show_cback(struct cli_def *cli, const char *command, char *argv[], int argc) { struct cli_optarg_pair *optarg_pair; for (optarg_pair = cli->found_optargs; optarg_pair; optarg_pair = optarg_pair->next) { // Only show vars that are also current 'commands' struct cli_command *c = cli->commands; for (; c; c = c->next) { if (c->command_type != CLI_BUILDMODE_COMMAND) continue; if (!strcmp(c->command, optarg_pair->name)) { cli_print(cli, " %-20s = %s", optarg_pair->name, optarg_pair->value); break; } } } return CLI_OK; } int cli_int_buildmode_unset_cback(struct cli_def *cli, const char *command, char *argv[], int argc) { // Iterate over our 'set' variables to see if that variable is also a 'valid' command right now struct cli_command *c; // have to catch this one here due to how buildmode works if (!argv[0] || !*argv[0]) { cli_error(cli, "Incomplete command, missing required argument 'setting' for command 'unset'"); return CLI_ERROR; } // Is this 'optarg' to remove one of the current commands? for (c = cli->commands; c; c = c->next) { if (c->command_type != CLI_BUILDMODE_COMMAND) continue; if (cli->privilege < c->privilege) continue; if ((cli->buildmode->mode != c->mode) && (cli->buildmode->transient_mode != c->mode) && (c->mode != MODE_ANY)) continue; if (strcmp(c->command, argv[0])) continue; // Go fry anything by this name cli_int_unset_optarg_value(cli, argv[0]); cli_int_buildmode_reset_unset_help(cli); break; } return CLI_OK; } // Generate a list of variables that *have* been set int cli_int_buildmode_unset_completor(struct cli_def *cli, const char *name, const char *word, struct cli_comphelp *comphelp) { struct cli_optarg_pair *optarg_pair; for (optarg_pair = cli->found_optargs; optarg_pair; optarg_pair = optarg_pair->next) { // Only complete vars that could be set by current 'commands' struct cli_command *c = cli->commands; for (; c; c = c->next) { if (c->command_type != CLI_BUILDMODE_COMMAND) continue; if ((!strcmp(c->command, optarg_pair->name)) && (!word || !strncmp(word, optarg_pair->name, strlen(word)))) { cli_add_comphelp_entry(comphelp, optarg_pair->name); } } } return CLI_OK; } int cli_int_buildmode_unset_validator(struct cli_def *cli, const char *name, const char *value) { struct cli_optarg_pair *optarg_pair; if (!name || !*name) { cli_error(cli, "No setting given to unset"); return CLI_ERROR; } for (optarg_pair = cli->found_optargs; optarg_pair; optarg_pair = optarg_pair->next) { // Only complete vars that could be set by current 'commands' struct cli_command *c = cli->commands; for (; c; c = c->next) { if (c->command_type != CLI_BUILDMODE_COMMAND) continue; if (!strcmp(c->command, optarg_pair->name) && value && !strcmp(optarg_pair->name, value)) { return CLI_OK; } } } return CLI_ERROR; } void cli_set_transient_mode(struct cli_def *cli, int transient_mode) { cli->transient_mode = transient_mode; } int cli_add_comphelp_entry(struct cli_comphelp *comphelp, const char *entry) { int retval = CLI_ERROR; if (comphelp && entry) { char *dupelement = strdup(entry); char **duparray = (char **)realloc((void *)comphelp->entries, sizeof(char *) * (comphelp->num_entries + 1)); if (dupelement && duparray) { comphelp->entries = duparray; comphelp->entries[comphelp->num_entries++] = dupelement; retval = CLI_OK; } else { free_z(dupelement); free_z(duparray); } } return retval; } void cli_free_comphelp(struct cli_comphelp *comphelp) { if (comphelp) { int idx; for (idx = 0; idx < comphelp->num_entries; idx++) free_z(comphelp->entries[idx]); free_z(comphelp->entries); } } static int cli_int_locate_command(struct cli_def *cli, struct cli_command *commands, int command_type, int start_word, struct cli_pipeline_stage *stage) { struct cli_command *c, *again_config = NULL, *again_any = NULL; int c_words = stage->num_words; for (c = commands; c; c = c->next) { if (c->command_type != command_type) continue; if (cli->privilege < c->privilege) continue; if (strncasecmp(c->command, stage->words[start_word], c->unique_len)) continue; if (strncasecmp(c->command, stage->words[start_word], strlen(stage->words[start_word]))) continue; AGAIN: if (c->mode == cli->mode || (c->mode == MODE_ANY && again_any != NULL)) { int rc = CLI_OK; // Found a word! if (!c->children) { // Last word if (!c->callback && !c->filter) { cli_error(cli, "No callback for \"%s\"", cli_command_name(cli, c)); return CLI_ERROR; } } else { if (start_word == c_words - 1) { if (c->callback) goto CORRECT_CHECKS; cli_error(cli, "Incomplete command"); return CLI_ERROR; } rc = cli_int_locate_command(cli, c->children, command_type, start_word + 1, stage); if (rc == CLI_ERROR_ARG) { if (c->callback) { rc = CLI_OK; goto CORRECT_CHECKS; } // show the command from word 0 up until the 'bad' word at start_word+1 cli_error(cli, "Invalid command \"%s %s\"", cli_command_name(cli, c), stage->words[start_word + 1]); return CLI_ERROR; } return rc; } if (!c->callback && !c->filter) { cli_error(cli, "Internal server error processing \"%s\"", cli_command_name(cli, c)); return CLI_ERROR; } CORRECT_CHECKS: if (rc == CLI_OK) { stage->command = c; stage->first_unmatched = start_word + 1; stage->first_optarg = stage->first_unmatched; // cli_int_parse_optargs will display any detected errors... cli_int_parse_optargs(cli, stage, c, '\0', NULL); rc = stage->status; } return rc; } else if (cli->mode > MODE_CONFIG && c->mode == MODE_CONFIG) { // Command matched but from another mode, remember it if we fail to find correct command again_config = c; } else if (c->mode == MODE_ANY) { // Command matched but for any mode, remember it if we fail to find correct command again_any = c; } } // Drop out of config submode if we have matched command on MODE_CONFIG if (again_config) { c = again_config; cli_set_configmode(cli, MODE_CONFIG, NULL); goto AGAIN; } if (again_any) { c = again_any; goto AGAIN; } // display this if we matched against absolutely nothing.... if (start_word == 0) cli_error(cli, "Invalid command \"%s\"", stage->words[start_word]); return CLI_ERROR_ARG; } int cli_int_validate_pipeline(struct cli_def *cli, struct cli_pipeline *pipeline) { int i; int rc = CLI_OK; int command_type; if (!pipeline) return CLI_ERROR; cli->pipeline = pipeline; cli->found_optargs = NULL; // If the line is totally empty this is not an error, but we need to return // CLI_ERROR to avoid processing it if (pipeline->num_words == 0) return CLI_ERROR; for (i = 0; i < pipeline->num_stages; i++) { // And double check each stage for an empty line - this *is* an error if (pipeline->stage[i].num_words == 0) { cli_error(cli, "Empty command given"); return CLI_ERROR; } // In 'buildmode' we only have one pipeline, but we need to recall if we had started with any optargs if (cli->buildmode && i == 0) command_type = CLI_BUILDMODE_COMMAND; else if (i > 0) command_type = CLI_FILTER_COMMAND; else command_type = CLI_REGULAR_COMMAND; cli->pipeline->current_stage = &pipeline->stage[i]; if (cli->buildmode) cli->found_optargs = cli->buildmode->found_optargs; else cli->found_optargs = NULL; rc = cli_int_locate_command(cli, cli->commands, command_type, 0, &pipeline->stage[i]); // And save our found optargs for later use if (cli->buildmode) cli->buildmode->found_optargs = cli->found_optargs; else pipeline->stage[i].found_optargs = cli->found_optargs; if (rc != CLI_OK) break; } cli->pipeline = NULL; return rc; } void cli_int_free_pipeline(struct cli_pipeline *pipeline) { int i; if (!pipeline) return; for (i = 0; i < pipeline->num_stages; i++) cli_int_free_found_optargs(&pipeline->stage[i].found_optargs); for (i = 0; i < pipeline->num_words; i++) free_z(pipeline->words[i]); free_z(pipeline->cmdline); free_z(pipeline); } void cli_int_show_pipeline(struct cli_def *cli, struct cli_pipeline *pipeline) { int i, j; struct cli_pipeline_stage *stage; char **word; struct cli_optarg_pair *optarg_pair; for (i = 0, word = pipeline->words; i < pipeline->num_words; i++, word++) printf("[%s] ", *word); fprintf(stderr, "\n"); fprintf(stderr, "#stages=%d, #words=%d\n", pipeline->num_stages, pipeline->num_words); for (i = 0; i < pipeline->num_stages; i++) { stage = &(pipeline->stage[i]); fprintf(stderr, " #%d(%d words) first_unmatched=%d: ", i, stage->num_words, stage->first_unmatched); for (j = 0; j < stage->num_words; j++) { fprintf(stderr, " [%s]", stage->words[j]); } fprintf(stderr, "\n"); if (stage->command) { fprintf(stderr, " Command: %s\n", stage->command->command); } for (optarg_pair = stage->found_optargs; optarg_pair; optarg_pair = optarg_pair->next) { fprintf(stderr, " %s: %s\n", optarg_pair->name, optarg_pair->value); } } } // Take an array of words and return a pipeline, using '|' to split command into different 'stages'. // Pipeline is broken down by '|' characters and within each p. struct cli_pipeline *cli_int_generate_pipeline(struct cli_def *cli, const char *command) { int i; struct cli_pipeline_stage *stage; char **word; struct cli_pipeline *pipeline = NULL; cli->found_optargs = NULL; if (cli->buildmode) cli->found_optargs = cli->buildmode->found_optargs; if (!command) return NULL; while (*command && isspace(*command)) command++; if (!(pipeline = (struct cli_pipeline *)calloc(1, sizeof(struct cli_pipeline)))) return NULL; pipeline->cmdline = (char *)strdup(command); pipeline->num_words = cli_parse_line(command, pipeline->words, CLI_MAX_LINE_WORDS); pipeline->stage[0].num_words = 0; stage = &pipeline->stage[0]; word = pipeline->words; stage->words = word; for (i = 0; i < pipeline->num_words; i++, word++) { if (*word[0] == '|') { if (cli->buildmode) { // Can't allow filters in buildmode commands cli_int_free_pipeline(pipeline); cli_error(cli, "\nPipelines are not allowed in buildmode"); return NULL; } stage->stage_num = pipeline->num_stages; stage++; stage->num_words = 0; pipeline->num_stages++; stage->words = word + 1; // First word of the next stage is one past where we are (possibly NULL) } else { stage->num_words++; } } stage->stage_num = pipeline->num_stages; pipeline->num_stages++; return pipeline; } int cli_int_execute_pipeline(struct cli_def *cli, struct cli_pipeline *pipeline) { int stage_num; int rc = CLI_OK; struct cli_filter **filt = &cli->filters; if (!pipeline | !cli) return CLI_ERROR; cli->pipeline = pipeline; for (stage_num = 1; stage_num < pipeline->num_stages; stage_num++) { struct cli_pipeline_stage *stage = &pipeline->stage[stage_num]; pipeline->current_stage = stage; cli->found_optargs = stage->found_optargs; *filt = calloc(sizeof(struct cli_filter), 1); if (*filt) { if ((rc = stage->command->init(cli, stage->num_words, stage->words, *filt) != CLI_OK)) { break; } filt = &(*filt)->next; } } pipeline->current_stage = NULL; // Did everything init? If so, execute, otherwise skip execution if ((rc == CLI_OK) && pipeline->stage[0].command->callback) { struct cli_pipeline_stage *stage = &pipeline->stage[0]; pipeline->current_stage = &pipeline->stage[0]; if (pipeline->current_stage->command->command_type == CLI_BUILDMODE_COMMAND) cli->found_optargs = cli->buildmode->found_optargs; else cli->found_optargs = pipeline->stage[0].found_optargs; rc = stage->command->callback(cli, cli_command_name(cli, stage->command), stage->words + stage->first_unmatched, stage->num_words - stage->first_unmatched); if (pipeline->current_stage->command->command_type == CLI_BUILDMODE_COMMAND) cli->buildmode->found_optargs = cli->found_optargs; pipeline->current_stage = NULL; } // Now teardown any filters while (cli->filters) { struct cli_filter *filt = cli->filters; if (filt->filter) filt->filter(cli, NULL, cli->filters->data); cli->filters = filt->next; free_z(filt); } cli->found_optargs = NULL; cli->pipeline = NULL; return rc; } /* * Attempt quick dirty wrapping of helptext taking into account the offset from name, embedded * cr/lf in helptext, and trying to split on last white-text before the right margin. If there is * no identifiable whitespace to split on, then the split will be done on the last character to fit * that line (currently max line with is 80 characters). * The firstcolumn width will be a greater of 22 characters or the width of nameptr, which ever is * greater, and will be offset from the rest of the line by one space. However, if nameptr is * greater than 22 characters it will be put on a line by itself. The first column will be formatted * as spaces (22 of em) for all subsequent lines. . * This routine assumes any 'indenting' of the nameptr field has already been done, and is solely * concerned about wrapping the combination of nameptr and helpptr to look 'nice'. */ #define MAX(a,b) ((a) >(b) ? (a) : (b)) #define MAXWIDTHCOL1 22 void cli_int_wrap_help_line(char *nameptr, char *helpptr, struct cli_comphelp *comphelp) { int maxwidth = 80; // temporary assumption, to be fixed later when libcli 'understands' screen dimensions int availwidth; int namewidth; int toprint; char *crlf; char *line; char emptystring[] = ""; if (!helpptr) helpptr = emptystring; /* * Now we need to iterate one or more times to only print out at most * maxwidth - leftwidth characters of helpptr. Note that there are no * tabs in helpptr, so each 'char' displays as one char */ do { if ((nameptr != emptystring) && (strlen(nameptr) > MAXWIDTHCOL1)) { if (asprintf(&line, "%s", nameptr) < 0) break; cli_add_comphelp_entry(comphelp, line); free_z(line); nameptr = emptystring; namewidth = MAXWIDTHCOL1; } namewidth = MAX(MAXWIDTHCOL1,strlen(nameptr)); availwidth = maxwidth - namewidth -1; // subtract 1 for space separating col1 from rest of line toprint = strlen(helpptr); if (toprint > availwidth) { toprint = availwidth; while ((toprint >= 0) && !isspace(helpptr[toprint])) toprint--; if (toprint < 0) { // if we backed up and found no whitespace, dump as much as we can toprint = availwidth; } } // see if we might have an embedded carriage return or line feed if ((crlf = strpbrk(helpptr, "\n\r"))) { // crlf is a pointer - see if it is 'before' the toprint index if ((crlf - helpptr) < toprint) { // ok, crlf is before the wrap, so have line break here. toprint = (crlf - helpptr); } } if (asprintf(&line, "%-*.*s %.*s", namewidth, namewidth, nameptr, toprint, helpptr) < 0) break; cli_add_comphelp_entry(comphelp, line); free_z(line); nameptr = emptystring; helpptr += toprint; // regardless of how long the command is, indent by 20 chars on all following lines namewidth = 22; // advance to first non whitespace while (helpptr && isspace(*helpptr)) helpptr++; } while (*helpptr); } static void cli_get_optarg_comphelp(struct cli_def *cli, struct cli_optarg *optarg, struct cli_comphelp *comphelp, int num_candidates, const char lastchar, const char *anchor_word, const char *next_word) { int help_insert = 0; char *delim_start = DELIM_NONE; char *delim_end = DELIM_NONE; int (*get_completions)(struct cli_def *, const char *, const char *, struct cli_comphelp *) = NULL; char *tptr = NULL; // If we've already seen a value by this exact name, skip it, unless the multiple flag is set if (cli_find_optarg_value(cli, optarg->name, NULL) && !(optarg->flags & (CLI_CMD_OPTION_MULTIPLE))) return; get_completions = optarg->get_completions; if (optarg->flags & CLI_CMD_OPTIONAL_FLAG) { if (!(anchor_word && !strncmp(anchor_word, optarg->name, strlen(anchor_word)))) { delim_start = DELIM_OPT_START; delim_end = DELIM_OPT_END; get_completions = NULL; // No point, completor of field is the name itself } } else if (optarg->flags & CLI_CMD_HYPHENATED_OPTION) { delim_start = DELIM_OPT_START; delim_end = DELIM_OPT_END; } else if (optarg->flags & CLI_CMD_ARGUMENT) { delim_start = DELIM_ARG_START; delim_end = DELIM_ARG_END; } else if (optarg->flags & CLI_CMD_OPTIONAL_ARGUMENT) { /* * Optional args can match against the name or the value. * Here 'anchor_word' is the name, and 'next_word' is 'value' for said optional argument. * So if anchor_word==next_word we're looking at the 'name' of the optarg, otherwise we know the name and are going * against the value. */ if (anchor_word != next_word) { // Matching against optional argument 'value' help_insert = 0; if (!get_completions) { delim_start = DELIM_ARG_START; delim_end = DELIM_ARG_END; } } else { // Matching against optional argument 'name' help_insert = 1; get_completions = NULL; // Matching against the name, not the following field value if (!(anchor_word && !strncmp(anchor_word, optarg->name, strlen(anchor_word)))) { delim_start = DELIM_OPT_START; delim_end = DELIM_OPT_END; } } } // Fill in with help text or completor value(s) as indicated if (lastchar == '?') { /* * Note - help is a bit complex, and we could optimize it. But it isn't done often, * so we're always going to do it on the fly. * Help will consist of '\v' separated lines. Each line except the first is also '\t' * separated into the name/text fields. If a line does not have a '\t' separated then the * name will be the name of the optarg, and the help will be that entire line. The *first* * does get some tweaks to how the name and help is displayed. * The first pass through will be indented 2 spaces on the left with the formated name occupying * 20 spaces (expanding if more than 20). If the command is a 'buildmode' command the first * character of the 'text' will be an asterisk. The 'rest' of the line (assuming an 80 character ' * wide line for now) will be used to wrap the 'text' field honoring embedded newlines, and trying to * wrap on nearest preceeding whitespace when it hits a boundary. Subsequent lines will be indented * by an additional 2 spaces, and will drop the asterisk. */ char *working = NULL; char *nameptr = NULL; char *helpptr = NULL; char *lineptr = NULL; char *savelineptr = NULL; char *savetabptr = NULL; char *tname = NULL; int indent = 2; int helplen; char emptystring[] = ""; /* * Print out actual text into a working buffer that we can then call 'strtok_r' on it. This lets * us prepend some optional fields nice and easily. At this point it is one big string, so we can * iterate over it making changes (strtok_r) as needed. */ if (help_insert) { helplen = asprintf(&working, "%s%s%s%s%s", (optarg->flags & CLI_CMD_ALLOW_BUILDMODE) ? "* " : "", "type '", optarg->name, "' to select ", optarg->name); } else { helplen = asprintf(&working, "%s%s", (optarg->flags & CLI_CMD_ALLOW_BUILDMODE) ? "* " : "", optarg->help); } // pull the first line helpptr = strtok_r(working, "\v", &savelineptr); nameptr = optarg->name; if (helplen < 0) { helpptr = emptystring; working = NULL; } // break things up into tab separated entities - always show the first entry do { char *leftcolumn; if (asprintf(&tname, "%s%s%s", delim_start, nameptr, delim_end) == -1) break; if (asprintf(&leftcolumn, "%*.*s%s", indent, indent, "", tname) == -1) break; cli_int_wrap_help_line(leftcolumn, helpptr, comphelp); // clear out any delimiter settings and set indent for any subtext delim_start = DELIM_NONE; delim_end = DELIM_NONE; indent = 4; free_z(tname); free_z(leftcolumn); // we may not need to show all off the 'extra help', so loop here do { lineptr = strtok_r(NULL, "\v", &savelineptr); if (lineptr) { nameptr = strtok_r(lineptr, "\t", &savetabptr); helpptr = strtok_r(NULL, "\t", &savetabptr); } } while (lineptr && nameptr && helpptr && (next_word && (strncmp(next_word, nameptr, strlen(next_word))))); } while (lineptr && nameptr && helpptr); free_z(working); } else if (lastchar == CTRL('I')) { if (get_completions) { (*get_completions)(cli, optarg->name, next_word, comphelp); } else if ((!anchor_word || !strncmp(anchor_word, optarg->name, strlen(anchor_word))) && (asprintf(&tptr, "%s%s%s", delim_start, optarg->name, delim_end) != -1)) { cli_add_comphelp_entry(comphelp, tptr); free_z(tptr); } } } static void cli_int_parse_optargs(struct cli_def *cli, struct cli_pipeline_stage *stage, struct cli_command *cmd, char lastchar, struct cli_comphelp *comphelp) { struct cli_optarg *optarg = NULL, *oaptr = NULL; int word_idx, value_idx, word_incr, candidate_idx; struct cli_optarg *candidates[CLI_MAX_LINE_WORDS]; char *value; int num_candidates = 0; int is_last_word = 0; int (*validator)(struct cli_def *, const char *name, const char *value); if (cli->buildmode) cli->found_optargs = cli->buildmode->found_optargs; else cli->found_optargs = stage->found_optargs; /* * Tab completion and help are *only* allowed at end of string, but we need to process the entire command to know what * has already been found. There should be no ambiguities before the 'last' word. * Note specifically that for tab completions and help the *last* word can be a null pointer. */ stage->error_word = NULL; /* Start our optarg and word pointers at the beginning. * optarg will be incremented *only* when an argument is identified. * word_idx will be incremented either by 1 (optflag or argument) or 2 (optional argument). */ word_idx = stage->first_unmatched; optarg = cmd->optargs; num_candidates = 0; while (optarg && word_idx < stage->num_words && num_candidates <= 1) { num_candidates = 0; word_incr = 1; // Assume we're only incrementing by a word - if we match an optional argument bump to 2 /* * The initial loop here is to identify candidates based matching *this* word in order against: * - An exact match of the word to the optinal flag/argument name (yield exactly one match and exit the loop) * - A partial match for optional flag/argument name * - Candidate an argument. */ for (oaptr = optarg; oaptr; oaptr = oaptr->next) { // Skip this option unless it matches privileges, MODE_ANY, the current mode, or the transient_mode if (cli->privilege < oaptr->privilege) continue; if ((oaptr->mode != cli->mode) && (oaptr->mode != cli->transient_mode) && (oaptr->mode != MODE_ANY)) continue; /* * Special cases: * - spot check * - a hyphenated option a hyphenated option * - an optional flag without validator, but the word matches the optarg name * - an optional flag with a validator *and* the word passes the validator, * - an optional argument where the word matches the argument name * a hit on any of these special cases is an automatic *only* candidate. * * Otherwise if the word is 'blank', could be an argument, or matches 'enough' of an option/flag it is a * candidate. * Once we accept an argument as a candidate, we're done looking for candidates as straight arguments are * required. */ if (oaptr->flags & CLI_CMD_SPOT_CHECK && num_candidates == 0) { stage->status = (*oaptr->validator)(cli, NULL, NULL); if (stage->status != CLI_OK) { stage->error_word = stage->words[word_idx]; cli_reprompt(cli); goto done; } } else if (stage->words[word_idx] && stage->words[word_idx][0] == '-' && (oaptr->flags & (CLI_CMD_HYPHENATED_OPTION))) { candidates[0] = oaptr; num_candidates = 1; break; } else if (stage->words[word_idx] && (oaptr->flags & CLI_CMD_OPTIONAL_FLAG) && ((oaptr->validator && (oaptr->validator(cli, oaptr->name, stage->words[word_idx]) == CLI_OK)) || (!oaptr->validator && !strcmp(oaptr->name, stage->words[word_idx])))) { candidates[0] = oaptr; num_candidates = 1; break; } else if (stage->words[word_idx] && (oaptr->flags & CLI_CMD_OPTIONAL_ARGUMENT) && !strcmp(oaptr->name, stage->words[word_idx])) { candidates[0] = oaptr; num_candidates = 1; break; } else if (!stage->words[word_idx] || (oaptr->flags & CLI_CMD_ARGUMENT) || !strncasecmp(oaptr->name, stage->words[word_idx], strlen(stage->words[word_idx]))) { candidates[num_candidates++] = oaptr; } if (oaptr->flags & CLI_CMD_ARGUMENT) { break; } } /* * Iterate over the list of candidates for this word. There are several early exit cases to consider: * - If we have no candidates then we're done - any remaining words must be processed by the command callback * - If we have more than one candidate evaluating for execution punt hard after complaining. * - If we have more than one candidate and we're not at end-of-line ( */ if (num_candidates == 0) break; if (num_candidates > 1 && (lastchar == '\0' || word_idx < (stage->num_words - 1))) { stage->error_word = stage->words[word_idx]; stage->status = CLI_AMBIGUOUS; cli_error(cli, "\nAmbiguous option/argument for command %s", stage->command->command); goto done; } /* * So now we could have one or more candidates. We need to call get help/completions *only* if this is the * 'last-word'. * Remember that last word for optional arguments is last or next to last.... */ if (lastchar != '\0') { int called_comphelp = 0; for (candidate_idx = 0; candidate_idx < num_candidates; candidate_idx++) { oaptr = candidates[candidate_idx]; // Need to know *which* word we're trying to complete for optional_args, hence the difference calls if (((oaptr->flags & (CLI_CMD_OPTIONAL_FLAG | CLI_CMD_ARGUMENT)) && (word_idx == (stage->num_words - 1))) || (oaptr->flags & (CLI_CMD_OPTIONAL_ARGUMENT | CLI_CMD_HYPHENATED_OPTION) && word_idx == (stage->num_words - 1))) { cli_get_optarg_comphelp(cli, oaptr, comphelp, num_candidates, lastchar, stage->words[word_idx], stage->words[word_idx]); called_comphelp = 1; } else if (oaptr->flags & CLI_CMD_OPTIONAL_ARGUMENT && word_idx == (stage->num_words - 2)) { cli_get_optarg_comphelp(cli, oaptr, comphelp, num_candidates, lastchar, stage->words[word_idx], stage->words[word_idx + 1]); called_comphelp = 1; } } // If we were 'end-of-word' and looked for completions/help, return to user if (called_comphelp) { stage->status = CLI_OK; goto done; } } // Set some values for use later - makes code much easier to read value = stage->words[word_idx]; value_idx = word_idx; oaptr = candidates[0]; validator = oaptr->validator; if ((oaptr->flags & (CLI_CMD_OPTIONAL_FLAG | CLI_CMD_ARGUMENT) && word_idx == (stage->num_words - 1)) || (oaptr->flags & CLI_CMD_OPTIONAL_ARGUMENT && word_idx == (stage->num_words - 2))) { is_last_word = 1; } if (oaptr->flags & CLI_CMD_OPTIONAL_ARGUMENT) { word_incr = 2; if (!stage->words[word_idx + 1] && lastchar == '\0') { // Hit an optional argument that does not have a value with it cli_error(cli, "Optional argument %s requires a value", stage->words[word_idx]); stage->error_word = stage->words[word_idx]; stage->status = CLI_MISSING_VALUE; goto done; } value = stage->words[word_idx + 1]; value_idx = word_idx + 1; } /* * We're not at end of string and doing help/completions. * So see if our value is 'valid', to save it, and see if we have any extra processing to do such as a transient * mode check or enter build mode. */ if (!validator || (*validator)(cli, oaptr->name, value) == CLI_OK) { if (oaptr->flags & CLI_CMD_DO_NOT_RECORD) { // We want completion and validation, but then leave this 'value' to be seen - used *only* by buildmode as // argv[0] with argc=1 break; } else { // Need to combine remaining words if the CLI_CMD_REMAINDER_OF_LINE flag it set, then we're done processing int set_value_return = 0; if (oaptr->flags & CLI_CMD_REMAINDER_OF_LINE) { char *combined = NULL; combined = join_words(stage->num_words - word_idx, stage->words + word_idx); if (!combined) { cli_error(cli, "%sUnable to allocate memory for command processing", lastchar == '\0' ? "" : "\n"); cli_reprompt(cli); stage->error_word = stage->words[word_idx]; stage->status = CLI_ERROR; goto done; } set_value_return = cli_set_optarg_value(cli, oaptr->name, combined, 0); free_z(combined); } else { set_value_return = cli_set_optarg_value(cli, oaptr->name, value, oaptr->flags & CLI_CMD_OPTION_MULTIPLE); } if (set_value_return != CLI_OK) { cli_error(cli, "%sProblem setting value for command argument %s", lastchar == '\0' ? "" : "\n", stage->words[word_idx]); cli_reprompt(cli); stage->error_word = stage->words[word_idx]; stage->status = CLI_ERROR; goto done; } } } else { cli_error(cli, "%sProblem parsing command setting %s with value %s", lastchar == '\0' ? "" : "\n", oaptr->name, stage->words[value_idx]); cli_reprompt(cli); stage->error_word = stage->words[word_idx]; stage->status = CLI_ERROR; goto done; } // If this optarg can set the transient mode, then evaluate it if we're not at last word if (oaptr->transient_mode && oaptr->transient_mode(cli, oaptr->name, value)) { stage->error_word = stage->words[word_idx]; stage->status = CLI_ERROR; goto done; } // Only process CLI_CMD_ALLOW_BUILDMODE if we're not already in buildmode, parsing command (stage 0), and this is // the last word if (!cli->disallow_buildmode && (stage->status == CLI_OK) && (oaptr->flags & CLI_CMD_ALLOW_BUILDMODE) && is_last_word) { stage->status = cli_int_enter_buildmode(cli, stage, value); goto done; } // Optional flags and arguments can appear multiple times, and in any order. We only advance // from our starting optarg if the matching optarg is a true argument. if (oaptr->flags & CLI_CMD_ARGUMENT) { // Advance past this argument entry optarg = oaptr->next; } word_idx += word_incr; stage->first_unmatched = word_idx; } // If we're evaluating the command for execution, ensure we have all required arguments. if (lastchar == '\0') { for (; optarg; optarg = optarg->next) { if (cli->privilege < optarg->privilege) continue; if ((optarg->mode != cli->mode) && (optarg->mode != cli->transient_mode) && (optarg->mode != MODE_ANY)) continue; if (optarg->flags & CLI_CMD_DO_NOT_RECORD) continue; if (optarg->flags & CLI_CMD_ARGUMENT) { cli_error(cli, "Incomplete command, missing required argument '%s' for command '%s'", optarg->name, cmd->command); stage->status = CLI_MISSING_ARGUMENT; goto done; } } } done: if (cli->buildmode) cli->buildmode->found_optargs = cli->found_optargs; else stage->found_optargs = cli->found_optargs; return; } void cli_unregister_all_commands(struct cli_def *cli) { cli_unregister_tree(cli, cli->commands, CLI_REGULAR_COMMAND); } void cli_unregister_all_filters(struct cli_def *cli) { cli_unregister_tree(cli, cli->commands, CLI_FILTER_COMMAND); } /* * Several routines were declared as internal, but would be useful for external use also * Rename them so they can be exposed, but have original routines simply call the 'public' ones */ int cli_int_quit(struct cli_def *cli, UNUSED(const char *command), UNUSED(char *argv[]), UNUSED(int argc)) { return cli_quit(cli, command, argv, argc); } int cli_int_help(struct cli_def *cli, UNUSED(const char *command), UNUSED(char *argv[]), UNUSED(int argc)) { return cli_help(cli, command, argv, argc); } int cli_int_history(struct cli_def *cli, UNUSED(const char *command), UNUSED(char *argv[]), UNUSED(int argc)) { return cli_history(cli, command, argv, argc); } int cli_int_exit(struct cli_def *cli, const char *command, char *argv[], int argc) { return cli_exit(cli, command, argv, argc); } int cli_int_enable(struct cli_def *cli, UNUSED(const char *command), UNUSED(char *argv[]), UNUSED(int argc)) { return cli_enable(cli, command, argv, argc); } int cli_int_disable(struct cli_def *cli, UNUSED(const char *command), UNUSED(char *argv[]), UNUSED(int argc)) { return cli_disable(cli, command, argv, argc); } void cli_dump_optargs_and_args(struct cli_def *cli, const char *text, char *argv[], int argc) { int i; struct cli_optarg_pair *optargs; cli_print(cli, "%s: mode = %d, transient_mode = %d", text, cli->mode, cli->transient_mode); cli_print(cli, "Identified optargs"); for (optargs = cli_get_all_found_optargs(cli), i = 0; optargs; optargs = optargs->next, i++) cli_print(cli, "%2d %s=%s", i, optargs->name, optargs->value); cli_print(cli, "Extra args"); for (i = 0; i < argc; i++) cli_print(cli, "%2d %s", i, argv[i]); } static int cli_socket_wait(int sockfd, struct timeval *tm) { #if defined(LIBCLI_USE_POLL) && !defined(WIN32) struct pollfd pfd = { .fd = sockfd, .events = POLLIN, }; return poll(&pfd, 1, (tm->tv_sec * 1000) + (tm->tv_usec / 1000)); #else fd_set r; FD_ZERO(&r); FD_SET(sockfd, &r); return select(sockfd + 1, &r, NULL, NULL, tm); #endif } libcli-1.10.7/libcli.h000066400000000000000000000233211401575210200144430ustar00rootroot00000000000000#ifndef __LIBCLI_H__ #define __LIBCLI_H__ // vim:sw=4 tw=120 et #ifdef __cplusplus extern "C" { #endif #include #include #include #define LIBCLI_VERSION_MAJOR 1 #define LIBCLI_VERISON_MINOR 10 #define LIBCLI_VERISON_REVISION 6 #define LIBCLI_VERSION ((LIBCLI_VERSION_MAJOR << 16) | (LIBCLI_VERSION_MINOR << 8) | LIBCLI_VERSION_REVISION) #define CLI_OK 0 #define CLI_ERROR -1 #define CLI_QUIT -2 #define CLI_ERROR_ARG -3 #define CLI_AMBIGUOUS -4 #define CLI_UNRECOGNIZED -5 #define CLI_MISSING_ARGUMENT -6 #define CLI_MISSING_VALUE -7 #define CLI_BUILDMODE_START -8 #define CLI_BUILDMODE_ERROR -9 #define CLI_BUILDMODE_EXTEND -10 #define CLI_BUILDMODE_CANCEL -11 #define CLI_BUILDMODE_EXIT -12 #define CLI_INCOMPLETE_COMMAND -13 #define MAX_HISTORY 256 #define PRIVILEGE_UNPRIVILEGED 0 #define PRIVILEGE_PRIVILEGED 15 #define MODE_ANY -1 #define MODE_EXEC 0 #define MODE_CONFIG 1 #define LIBCLI_HAS_ENABLE 1 #define PRINT_PLAIN 0 #define PRINT_FILTERED 0x01 #define PRINT_BUFFERED 0x02 #define CLI_MAX_LINE_LENGTH 4096 #define CLI_MAX_LINE_WORDS 128 struct cli_def { int completion_callback; struct cli_command *commands; int (*auth_callback)(const char *, const char *); int (*regular_callback)(struct cli_def *cli); int (*enable_callback)(const char *); char *banner; struct unp *users; char *enable_password; char *history[MAX_HISTORY]; char showprompt; char *promptchar; char *hostname; char *modestring; int privilege; int mode; int state; struct cli_filter *filters; void (*print_callback)(struct cli_def *cli, const char *string); FILE *client; /* internal buffers */ void *conn; void *service; // char *commandname; // temporary buffer for cli_command_name() to prevent leak char *buffer; unsigned buf_size; struct timeval timeout_tm; time_t idle_timeout; int (*idle_timeout_callback)(struct cli_def *); time_t last_action; int telnet_protocol; void *user_context; struct cli_optarg_pair *found_optargs; int transient_mode; int disallow_buildmode; struct cli_pipeline *pipeline; struct cli_buildmode *buildmode; }; struct cli_filter { int (*filter)(struct cli_def *cli, const char *string, void *data); void *data; struct cli_filter *next; }; enum command_types { CLI_ANY_COMMAND, CLI_REGULAR_COMMAND, CLI_FILTER_COMMAND, CLI_BUILDMODE_COMMAND, }; struct cli_command { char *command; char *full_command_name; int (*callback)(struct cli_def *, const char *, char **, int); unsigned int unique_len; char *help; int privilege; int mode; struct cli_command *previous; struct cli_command *next; struct cli_command *children; struct cli_command *parent; struct cli_optarg *optargs; int (*filter)(struct cli_def *cli, const char *string, void *data); int (*init)(struct cli_def *cli, int, char **, struct cli_filter *filt); int command_type; int flags; }; struct cli_comphelp { int comma_separated; char **entries; int num_entries; }; enum optarg_flags { CLI_CMD_OPTIONAL_FLAG = 1 << 0, CLI_CMD_OPTIONAL_ARGUMENT = 1 << 1, CLI_CMD_ARGUMENT = 1 << 2, CLI_CMD_ALLOW_BUILDMODE = 1 << 3, CLI_CMD_OPTION_MULTIPLE = 1 << 4, CLI_CMD_OPTION_SEEN = 1 << 5, CLI_CMD_TRANSIENT_MODE = 1 << 6, CLI_CMD_DO_NOT_RECORD = 1 << 7, CLI_CMD_REMAINDER_OF_LINE = 1 << 8, CLI_CMD_HYPHENATED_OPTION = 1 << 9, CLI_CMD_SPOT_CHECK = 1 << 10, }; struct cli_optarg { char *name; int flags; char *help; int mode; int privilege; unsigned int unique_len; int (*get_completions)(struct cli_def *, const char *, const char *, struct cli_comphelp *); int (*validator)(struct cli_def *, const char *, const char *); int (*transient_mode)(struct cli_def *, const char *, const char *); struct cli_optarg *next; }; struct cli_optarg_pair { char *name; char *value; struct cli_optarg_pair *next; }; struct cli_pipeline_stage { struct cli_command *command; struct cli_optarg_pair *found_optargs; char **words; int num_words; int status; int first_unmatched; int first_optarg; int stage_num; char *error_word; }; struct cli_pipeline { char *cmdline; char *words[CLI_MAX_LINE_WORDS]; int num_words; int num_stages; struct cli_pipeline_stage stage[CLI_MAX_LINE_WORDS]; struct cli_pipeline_stage *current_stage; }; struct cli_buildmode { struct cli_command *command; struct cli_optarg_pair *found_optargs; char *cname; int mode; int transient_mode; char *mode_text; }; struct cli_def *cli_init(void); int cli_done(struct cli_def *cli); struct cli_command *cli_register_command(struct cli_def *cli, struct cli_command *parent, const char *command, int (*callback)(struct cli_def *, const char *, char **, int), int privilege, int mode, const char *help); int cli_unregister_command(struct cli_def *cli, const char *command); int cli_run_command(struct cli_def *cli, const char *command); int cli_loop(struct cli_def *cli, int sockfd); int cli_file(struct cli_def *cli, FILE *fh, int privilege, int mode); void cli_set_auth_callback(struct cli_def *cli, int (*auth_callback)(const char *, const char *)); void cli_set_enable_callback(struct cli_def *cli, int (*enable_callback)(const char *)); void cli_allow_user(struct cli_def *cli, const char *username, const char *password); void cli_allow_enable(struct cli_def *cli, const char *password); void cli_deny_user(struct cli_def *cli, const char *username); void cli_set_banner(struct cli_def *cli, const char *banner); void cli_set_hostname(struct cli_def *cli, const char *hostname); void cli_set_promptchar(struct cli_def *cli, const char *promptchar); void cli_set_modestring(struct cli_def *cli, const char *modestring); int cli_set_privilege(struct cli_def *cli, int privilege); int cli_set_configmode(struct cli_def *cli, int mode, const char *config_desc); void cli_reprompt(struct cli_def *cli); void cli_regular(struct cli_def *cli, int (*callback)(struct cli_def *cli)); void cli_regular_interval(struct cli_def *cli, int seconds); void cli_print(struct cli_def *cli, const char *format, ...) __attribute__((format(printf, 2, 3))); void cli_bufprint(struct cli_def *cli, const char *format, ...) __attribute__((format(printf, 2, 3))); void cli_vabufprint(struct cli_def *cli, const char *format, va_list ap); void cli_error(struct cli_def *cli, const char *format, ...) __attribute__((format(printf, 2, 3))); void cli_print_callback(struct cli_def *cli, void (*callback)(struct cli_def *, const char *)); void cli_free_history(struct cli_def *cli); void cli_set_idle_timeout(struct cli_def *cli, unsigned int seconds); void cli_set_idle_timeout_callback(struct cli_def *cli, unsigned int seconds, int (*callback)(struct cli_def *)); void cli_dump_optargs_and_args(struct cli_def *cli, const char *text, char *argv[], int argc); // Enable or disable telnet protocol negotiation. // Note that this is enabled by default and must be changed before cli_loop() is run. void cli_telnet_protocol(struct cli_def *cli, int telnet_protocol); // Set/get user context void cli_set_context(struct cli_def *cli, void *context); void *cli_get_context(struct cli_def *cli); void cli_free_comphelp(struct cli_comphelp *comphelp); int cli_add_comphelp_entry(struct cli_comphelp *comphelp, const char *entry); void cli_set_transient_mode(struct cli_def *cli, int transient_mode); struct cli_command *cli_register_filter(struct cli_def *cli, const char *command, int (*init)(struct cli_def *cli, int, char **, struct cli_filter *filt), int (*filter)(struct cli_def *, const char *, void *), int privilege, int mode, const char *help); int cli_unregister_filter(struct cli_def *cli, const char *command); struct cli_optarg *cli_register_optarg(struct cli_command *cmd, const char *name, int flags, int priviledge, int mode, const char *help, int (*get_completions)(struct cli_def *cli, const char *, const char *, struct cli_comphelp *), int (*validator)(struct cli_def *cli, const char *, const char *), int (*transient_mode)(struct cli_def *, const char *, const char *)); int cli_optarg_addhelp(struct cli_optarg *optarg, const char *helpname, const char *helptext); char *cli_find_optarg_value(struct cli_def *cli, char *name, char *find_after); struct cli_optarg_pair *cli_get_all_found_optargs(struct cli_def *cli); int cli_unregister_optarg(struct cli_command *cmd, const char *name); char *cli_get_optarg_value(struct cli_def *cli, const char *name, char *find_after); int cli_set_optarg_value(struct cli_def *cli, const char *name, const char *value, int allow_multiple); void cli_unregister_all_optarg(struct cli_command *c); void cli_unregister_all_filters(struct cli_def *cli); void cli_unregister_all_commands(struct cli_def *cli); void cli_unregister_all(struct cli_def *cli, struct cli_command *command); /* * Expose some previous internal routines. Just in case someone was using those * with an explicit reference, the original routines (cli_int_*) internally point * to the newly public routines. */ int cli_help(struct cli_def *cli, const char *command, char *argv[], int argc); int cli_history(struct cli_def *cli, const char *command, char *argv[], int argc); int cli_exit(struct cli_def *cli, const char *command, char *argv[], int argc); int cli_quit(struct cli_def *cli, const char *command, char *argv[], int argc); int cli_enable(struct cli_def *cli, const char *command, char *argv[], int argc); int cli_disable(struct cli_def *cli, const char *command, char *argv[], int argc); #ifdef __cplusplus } #endif #endif libcli-1.10.7/libcli.spec000066400000000000000000000340151401575210200151500ustar00rootroot00000000000000Version: 1.10.7 Summary: Cisco-like telnet command-line library Name: libcli Release: 1 License: LGPL Group: Library/Communication Source: %{name}-%{version}.tar.gz URL: http://code.google.com/p/libcli Packager: David Parrish BuildRoot: %{_tmppath}/%{name}-%{version}-%(%__id -un) %define verMajMin %(echo %{version} | cut -d '.' -f 1,2) %package devel Summary: Development files for libcli Group: Development/Libraries Requires: %{name} = %{version}-%{release} %description libcli provides a shared library for including a Cisco-like command-line interface into other software. It's a telnet interface which supports command-line editing, history, authentication and callbacks for a user-definable function tree. %description devel libcli provides a shared library for including a Cisco-like command-line interface into other software. It's a telnet interface which supports command-line editing, history, authentication and callbacks for a user-definable function tree. This package contains the devel files. %prep %setup -q %build make %install rm -rf $RPM_BUILD_ROOT install -d -p %{buildroot}%{_includedir} install -p -m 644 libcli*.h %{buildroot}%{_includedir}/ install -d -p %{buildroot}%{_libdir} install -p -m 755 libcli.so.%{version} %{buildroot}%{_libdir}/ install -p -m 755 libcli.a %{buildroot}%{_libdir}/ cd %{buildroot}%{_libdir} ln -s libcli.so.%{version} libcli.so.%{verMajMin} ln -s libcli.so.%{verMajMin} libcli.so %post -p /sbin/ldconfig %postun -p /sbin/ldconfig %clean rm -rf $RPM_BUILD_ROOT %files %doc COPYING %{_libdir}/*\.so.* %defattr(-, root, root) %files devel %doc README.md doc/developers-guide.md %{_libdir}/*.so* %{_libdir}/*.a %{_includedir}/*.h %defattr(-, root, root) %changelog * Wed Feb 24 2021 Rob Sanders 1.10.7 - Fix bug were an extra newline was being inserted on every line when help was being requested for options and arguments - Fix memory leak in linewrapping code for help items * Mon Feb 22 2021 Rob Sanders 1.10.6 - Fix bug when a command not found in the current mode, but is found in the CONFIG_MODE, which would resultin an an infinate loop. Bug reported by Stefan Mächler (github @machste). * Wed Jan 27 2021 Gerrit Huizenga 1.10.5 - Add ppc64le to travis builds * Wed Jan 27 2021 Rob Sanders 1.10.5 - Add additional range chack to cli_loop() if the 'select' call is used, and punt if sockfd is out of range - Add preprocessor check (LIBCLI_USE_POLL) to toggle between using poll or select in cli_loop(). Code submitted on github by @belge-sel. - Fix possible error where cli_command_name() returns a NULL by generating full command name when a command is registered. Note - removed cli->commandname member * Thu Jan 14 2021 Rob Sanders 1.10.5 - Fix issue where the help for 'long command name' winds up running into the actual help text w/o any spaces. Now a long command will be on a line by itself with the line having the indented help text. - Change error notification of command processing to make it clearer where an error was found when processing a command, rather than displaying all of the command elements from the offending word back to the top level command. - Fix incorrect internal version defined in libcli.h for the 1.10.4 release - Minor change in makefile to create rpm from source code - Minor additions to clitest to show 'fixed' behavior above. * Mon Jan 11 2021 Rob Sanders 1.10.5 - Fix display issue when optional argument fails validation, so it will show the invalid *value* rather than repeat the name * Sun Jun 7 2020 Danial Zaoui - Fix function prototype in libcli.h to avoid error when compiled with 'strict-prototypes' * Tue Mar 3 2020 Rob Sanders 1.10.4-1 - Fix segfault issue found during tab/help processing - Minor fix of version on previous changelog record * Fri Jan 10 2020 Rob Sanders 1.10.3-1 - Minor cosmetic change to how help messages are generated, minor edits to some comments, minor cosmetic change to clitest demo code * Fri Dec 6 2019 Rob Sanders 1.10.3-1 - Tweak to buildmode to only show optargs 'after' the point at which buildmode was entered. - Add new 'cli_dump_optargs_and_args() function for development/debug Designed to be called from a callback to show output of optarg and argument processing. * Thu Dec 5 2019 Rob Sanders 1.10.3-1 - Updated CLI_CMD_OPTIONAL_FLAG parsing to use an validator function (if provided) to determine if the word being looked is a match for the optional flag. If no validator function is provided then the word much match the name of the optional flag exactly. * Thu Nov 14 2019 Rob Sanders 1.10.3-1 - Enhance how cli_parse_line handles quotes when parsine the command line. This includs mixed single/double quotes, embedded quoted substrings, and handling 'escaped' quotes using the '\' character. - Ensure that buildmode preserves 'empty' strings (ex: "", or '') when regenerating the cmdline after the user 'executes' the command. * Fri Sep 7 2019 Rob Sanders 1.10.2-1 - Fix bug where 'extra help' added with cli_optarg_addhelp() were not being displayed for optional argumenets - Fix bug if 'unset' called in buildmode w/o an argument - Added completor/validator logic for buildmode 'unset' command - Fixed bug in how help was being created in buildmode that resulted in badly formatted lines - Add support so the buildmode unset command has dynamic help messages depending on what has already been set - Prevent spot check optargs from appearing in buildmode - BREAKING API CHANGE - the 'cli_register_optarg()' function is now returning a pointer to the newly added optarg or NULL instead of an integer return code. This is to fix a design bug where it was difficult to use the new 'cli_optarg_addhelp()' function. Only affects moving from 1.10.0 to 1.10.2 (1.10.1 was never released) * Tue Sep 3 2019 Rob Sanders 1.10.2-1 - Fix bug in cli_optarg_addhelp() - Change 'enter' to 'type' for buildmode commands autogenerated help - Alter 'buildmode' help settings to indicate if a buildmode setting is optional by enclosing in '[]' * Wed Aug 21 2019 Rob Sanders 1.10.2-1 - Bug for processing an empty line, or empty command after a pipe character - Bump version to 1.10.2-1 * Wed Aug 7 2019 Rob Sanders 1.10.1-1 - Rework how help text is formatted to allow for word wrapping and embedded newlines - Rework how 'buildmode' is show for help messages so it uses a single '*' as the first char of the help text. - Rework optarg help text to allow the optarg to have specific help messages based on user input - look at clitest at the 'shape' optarg examples. * Wed Jul 30 2019 Rob Sanders 1.10.1-1 - Bump version - Renamed cli_int_[help|history|exit|quit|enable|disable] to same routine minut '_int_', and exposed in libcli.h. Retained old command pointing to new command for backward compatability - Fix coerner case in buildmode where no extra settings specified and user enters 'exit' - Rename buildmode 'exit' command to 'execute' based on feedback * Tue Jul 23 2019 Rob Sanders 1.10.0-2 - Fix spec file and rpm build issues - Fix 2 memory leaks (tab completion and help formatting) - Expose cli_set_optarg_value() for external use * Tue Jul 16 2019 Rob Sanders 1.10.0-1 - Add support for named arguments, optional flags, and optional arguments - Support help and tab complete for options/arguments - Enable users to add custom 'filters', including support for options/arguments - Replaced current interal filters with equivalent 'user filters' - Added examples of options/arguments to clitest - Added support for 'building' longer commands one option/argument at a time (buildmode) - Additional minor Coverity/valgrind related fixes - Tab completion will show up until an ambiguity, or entire entry if only one found - Tab completion for options/arguments will show '[]' around optional items - Tab completion for options/arguments will show '<>' around required items - Restructured clitest.c so 'cli_init()' is done in child thread * Wed Sep 19 2018 Rob Sanders 1.9.8-4 - Update spac file to use relative links for libcli.so symlinks * Tue Sep 18 2018 Rob Sanders 1.9.8-3 - Update spec file similar to EPEL's for regular and devel pacakges - Update Makefile rpm target to build both regular and devel pacakges - Update changelog for new fixes) - Update changelog (fix dates on several commits to avoid rpmbuild complaint) * Sun Sep 16 2018 David Parrish 1.9.8-2 - Reformat patches with clang-format * Thu Sep 13 2018 Rob Sanders 1.9.8-1 - Fix segfaults processing long lines in cli_loop() - Fix Coverity identified issues at the 'low' aggressive level * Sun Jul 22 2012 David Parrish 1.9.7-1 - Fix memory leak in cli_get_completions - fengxj325@gmail.com * Tue Jun 5 2012 Teemu Karimerto 1.9.6-1 - Added a user-definable context to struct cli_def - Added cli_set_context/cli_get_context for user context handling - Added a test for user context * Mon Feb 1 2010 David Parrish 1.9.5-1 - Removed dependence on "quit" command - Added cli_set_idle_timeout_callback() for custom timeout handling - Fixed an error caused by vsnprintf() overwriting it's input data - Added #ifdef __cplusplus which should allow linking with C++ now * Thu Oct 9 2008 David Parrish 1.9.4-1 - cli_regular() failures now close client connections - Migrate development to Google Code - Remove docs as they were out of date and now migrated to Google Code wiki * Sun Jul 27 2008 David Parrish 1.9.3-1 - Add support for compiling on WIN32 (Thanks Hamish Coleman) - Fix cli_build_shortest() length handling - Don't call cli_build_shortest() when registering every command - Disable TAB completion during username entry * Fri May 2 2008 David Parrish 1.9.2-1 - Add configurable timeout for cli_regular() - defaults to 1 second - Add idle timeout support * Thu Jul 5 2007 Brendan O'Dea 1.9.1-1 - Revert callback argument passing to match 1.8.x - Recalculate unique_len on change of priv/mode - Fixes for tab completion * Thu Jun 07 2007 David Parrish 1.9.0-1 - Implemented tab completion - Thanks Marc Donner, Andrew Silent, Yuriy N. Shkandybin and others - Filters are now extendable - Rename internal functions to all be cli_xxxx() - Many code cleanups and optimisations - Fix memory leak calling cli_loop() repeatedly - Thanks Qiang Wu * Sun Feb 18 2007 David Parrish 1.8.8-1 - Fix broken auth_callback logic - Thanks Ben Menchaca * Thu Jun 22 2006 Brendan O'Dea 1.8.7-1 - Code cleanups. - Declare internal functions static. - Use private data in cli_def rather than static buffers for do_print and command_name functions. * Mon Mar 06 2006 David Parrish 1.8.6-1 - Fix file descriptor leak in cli_loop() - Thanks Liam Widdowson - Fix memory leak when calling cli_init() and cli_done() repeatedly. * Fri Nov 25 2005 Brendan O'Dea 1.8.5-2 - Apply spec changes from Charlie Brady: use License header, change BuildRoot to include username. * Mon May 2 2005 Brendan O'Dea 1.8.5-1 - Add cli_error function which does not filter output. * Wed Jan 5 2005 Brendan O'Dea 1.8.4-1 - Add printf attribute to cli_print prototype * Fri Nov 19 2004 Brendan O'Dea 1.8.3-1 - Free help if set in cli_unregister_command (reported by Jung-Che Vincent Li) - Correct auth_callback() documentation (reported by Serge B. Khvatov) * Thu Nov 11 2004 Brendan O'Dea 1.8.2-1 - Allow config commands to exit a submode - Make "exit" work in exec/config/submodes - Add ^K (kill to EOL) * Mon Jul 12 2004 Brendan O'Dea 1.8.1-1 - Documentation update. - Allow NULL or "" to be passed to cli_set_banner() and cli_set_hostname() to clear a previous value. * Sun Jul 11 2004 Brendan O'Dea 1.8.0-1 - Dropped prompt arg from cli_loop now that prompt is set by hostname/mode/priv level; bump soname. Fixes ^L and ^A. - Reworked parsing/filters to allow multiple filters (cmd|inc X|count). - Made "grep" use regex, added -i, -v and -e args. - Added "egrep" filter. - Added "exclude" filter. * Fri Jul 2 2004 Brendan O'Dea 1.7.0-1 - Add mode argument to cli_file(), bump soname. - Return old value from cli_set_privilege(), cli_set_configmode(). * Fri Jun 25 2004 Brendan O'Dea 1.6.2-1 - Small cosmetic changes to output. - Exiting configure/^Z shouldn't disable. - Support encrypted password. * Fri Jun 25 2004 David Parrish 1.6.0 - Add support for privilege levels and nested config levels. Thanks to Friedhelm Düsterhöft for most of the code. * Tue Feb 24 2004 David Parrish - Add cli_print_callback() for overloading the output - Don't pass around the FILE * handle anymore, it's in the cli_def struct anyway - Add cli_file() to execute every line read from a file handle - Add filter_count * Sat Feb 14 2004 Brendan O'Dea 1.4.0-1 - Add more line editing support: ^W, ^A, ^E, ^P, ^N, ^F, ^B - Modify cli_print() to add \r\n and to split on \n to allow inc/begin to work with multi-line output (note: API change, client code should not include trailing \r\n; version bump) - Use libcli.so.M.m as the soname * Fri Jul 25 2003 David Parrish - Add cli_regular to enable regular processing while cli is connected * Wed Jun 25 2003 David Parrish - Stop random stack smashing in cli_command_name. - Stop memory leak by allocating static variable in cli_command_name.