aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitignore5
-rw-r--r--COPYING503
-rw-r--r--Makefile84
-rw-r--r--README112
-rw-r--r--clitest.c344
-rw-r--r--clitest.txt1
-rw-r--r--libcli.c2353
-rw-r--r--libcli.h132
-rw-r--r--libcli.spec156
9 files changed, 3690 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..b0c2baf
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,5 @@
+*.o
+*.a
+*.so
+*.so.*
+clitest
diff --git a/COPYING b/COPYING
new file mode 100644
index 0000000..e8c3f50
--- /dev/null
+++ b/COPYING
@@ -0,0 +1,503 @@
+ 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.
+
+ <one line to give the library's name and a brief idea of what it does.>
+ Copyright (C) <year> <name of author>
+
+ 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.
+
+ <signature of Ty Coon>, 1 April 1990
+ Ty Coon, President of Vice
+
+That's all there is to it!
+
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..3062635
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,84 @@
+# 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 = 9
+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
+
+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
+
+rpm:
+ mkdir libcli-$(MAJOR).$(MINOR).$(REVISION)
+ cp -R *.c *.h Makefile Doc README *.spec 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 -ta libcli-$(MAJOR).$(MINOR).$(REVISION).tar.gz --clean
diff --git a/README b/README
new file mode 100644
index 0000000..a39e115
--- /dev/null
+++ b/README
@@ -0,0 +1,112 @@
+libcli
+
+libcli emulates a cisco style telnet command-line interface.
+
+To compile:
+
+ make
+ make install
+
+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 <libcli.h> 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:
+
+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.
+
+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.
+
+
+- David Parrish (david@dparrish.com)
diff --git a/clitest.c b/clitest.c
new file mode 100644
index 0000000..ace87bc
--- /dev/null
+++ b/clitest.c
@@ -0,0 +1,344 @@
+#include <stdio.h>
+#include <sys/types.h>
+#ifdef WIN32
+#include <winsock2.h>
+#include <windows.h>
+#else
+#include <netinet/in.h>
+#include <sys/socket.h>
+#include <arpa/inet.h>
+#endif
+#include <signal.h>
+#include <strings.h>
+#include <string.h>
+#include <stdlib.h>
+#include <unistd.h>
+#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);
+}
+
+int main()
+{
+ struct cli_command *c;
+ struct cli_def *cli;
+ 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
+
+ // 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);
+ cli_regular_interval(cli, 5); // Defaults to 1 second
+ cli_set_idle_timeout_callback(cli, 60, idle_timeout); // 60 second 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");
+
+ // 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");
+
+ 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);
+ }
+ }
+
+ if ((s = socket(AF_INET, SOCK_STREAM, 0)) < 0)
+ {
+ perror("socket");
+ return 1;
+ }
+ setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on));
+
+ 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);
+ cli_loop(cli, x);
+ exit(0);
+#else
+ cli_loop(cli, x);
+ shutdown(x, SD_BOTH);
+ close(x);
+#endif
+ }
+
+ cli_done(cli);
+ return 0;
+}
diff --git a/clitest.txt b/clitest.txt
new file mode 100644
index 0000000..6d7632f
--- /dev/null
+++ b/clitest.txt
@@ -0,0 +1 @@
+show counters
diff --git a/libcli.c b/libcli.c
new file mode 100644
index 0000000..3893b2a
--- /dev/null
+++ b/libcli.c
@@ -0,0 +1,2353 @@
+#ifdef WIN32
+#include <winsock2.h>
+#include <windows.h>
+#endif
+
+#define _GNU_SOURCE
+#include <stdio.h>
+#include <errno.h>
+#include <stdarg.h>
+#include <stdlib.h>
+#include <memory.h>
+#if !defined(__APPLE__) && !defined(__FreeBSD__)
+#include <malloc.h>
+#endif
+#include <string.h>
+#include <ctype.h>
+#include <unistd.h>
+#include <time.h>
+#ifndef WIN32
+#include <regex.h>
+#endif
+#include "libcli.h"
+
+// vim:sw=4 tw=120 et
+
+#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;
+
+ size = vsnprintf(NULL, 0, fmt, args);
+ 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)
+
+int cli_match_filter_init(struct cli_def *cli, int argc, char **argv, struct cli_filter *filt);
+int cli_range_filter_init(struct cli_def *cli, int argc, char **argv, struct cli_filter *filt);
+int cli_count_filter_init(struct cli_def *cli, int argc, char **argv, struct cli_filter *filt);
+int cli_match_filter(struct cli_def *cli, const char *string, void *data);
+int cli_range_filter(struct cli_def *cli, const char *string, void *data);
+int cli_count_filter(struct cli_def *cli, const char *string, void *data);
+
+static struct cli_filter_cmds filter_cmds[] =
+{
+ { "begin", "Begin with lines that match" },
+ { "between", "Between lines that match" },
+ { "count", "Count of lines" },
+ { "exclude", "Exclude lines that match" },
+ { "include", "Include lines that match" },
+ { "grep", "Include lines that match regex (options: -v, -i, -e)" },
+ { "egrep", "Include lines that match extended regex" },
+ { NULL, NULL}
+};
+
+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_command_name(struct cli_def *cli, struct cli_command *command)
+{
+ char *name = cli->commandname;
+ char *o;
+
+ if (name) free(name);
+ 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);
+ }
+ cli->commandname = name;
+ return 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 ((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(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, *p;
+
+ if (!command) return NULL;
+ if (!(c = calloc(sizeof(struct cli_command), 1))) return NULL;
+
+ c->callback = callback;
+ c->next = NULL;
+ if (!(c->command = strdup(command)))
+ return NULL;
+ c->parent = parent;
+ c->privilege = privilege;
+ c->mode = mode;
+ if (help && !(c->help = strdup(help)))
+ return NULL;
+
+ if (parent)
+ {
+ if (!parent->children)
+ {
+ parent->children = c;
+ }
+ else
+ {
+ for (p = parent->children; p && p->next; p = p->next);
+ if (p) p->next = c;
+ }
+ }
+ else
+ {
+ if (!cli->commands)
+ {
+ cli->commands = c;
+ }
+ else
+ {
+ for (p = cli->commands; p && p->next; p = p->next);
+ if (p) p->next = c;
+ }
+ }
+ return c;
+}
+
+static void cli_free_command(struct cli_command *cmd)
+{
+ struct cli_command *c, *p;
+
+ for (c = cmd->children; c;)
+ {
+ p = c->next;
+ cli_free_command(c);
+ c = p;
+ }
+
+ free(cmd->command);
+ if (cmd->help) free(cmd->help);
+ free(cmd);
+}
+
+int cli_unregister_command(struct cli_def *cli, const char *command)
+{
+ struct cli_command *c, *p = NULL;
+
+ if (!command) return -1;
+ if (!cli->commands) return CLI_OK;
+
+ for (c = cli->commands; c; c = c->next)
+ {
+ if (strcmp(c->command, command) == 0)
+ {
+ if (p)
+ p->next = c->next;
+ else
+ cli->commands = c->next;
+
+ cli_free_command(c);
+ return CLI_OK;
+ }
+ p = c;
+ }
+
+ return CLI_OK;
+}
+
+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_int_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_int_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_int_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_int_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_int_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_int_exit(struct cli_def *cli, const char *command, char *argv[], int argc)
+{
+ if (cli->mode == MODE_EXEC)
+ return cli_int_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)))
+ {
+ free_z(cli);
+ return 0;
+ }
+ cli->telnet_protocol = 1;
+
+ cli_register_command(cli, 0, "help", cli_int_help, PRIVILEGE_UNPRIVILEGED, MODE_ANY, "Show available commands");
+ cli_register_command(cli, 0, "quit", cli_int_quit, PRIVILEGE_UNPRIVILEGED, MODE_ANY, "Disconnect");
+ cli_register_command(cli, 0, "logout", cli_int_quit, PRIVILEGE_UNPRIVILEGED, MODE_ANY, "Disconnect");
+ cli_register_command(cli, 0, "exit", cli_int_exit, PRIVILEGE_UNPRIVILEGED, MODE_ANY, "Exit from current mode");
+ cli_register_command(cli, 0, "history", cli_int_history, PRIVILEGE_UNPRIVILEGED, MODE_ANY,
+ "Show a list of previously run commands");
+ cli_register_command(cli, 0, "enable", cli_int_enable, PRIVILEGE_UNPRIVILEGED, MODE_EXEC,
+ "Turn on privileged commands");
+ cli_register_command(cli, 0, "disable", cli_int_disable, PRIVILEGE_PRIVILEGED, MODE_EXEC,
+ "Turn off privileged commands");
+
+ c = cli_register_command(cli, 0, "configure", 0, PRIVILEGE_PRIVILEGED, MODE_EXEC, "Enter configuration mode");
+ cli_register_command(cli, c, "terminal", cli_int_configure_terminal, PRIVILEGE_PRIVILEGED, MODE_EXEC,
+ "Configure from the terminal");
+
+ 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_all(struct cli_def *cli, struct cli_command *command)
+{
+ struct cli_command *c, *p = NULL;
+
+ if (!command) command = cli->commands;
+ if (!command) return;
+
+ for (c = command; c; )
+ {
+ p = c->next;
+
+ // Unregister all child commands
+ if (c->children)
+ cli_unregister_all(cli, c->children);
+
+ if (c->command) free(c->command);
+ if (c->help) free(c->help);
+ free(c);
+
+ c = p;
+ }
+}
+
+int cli_done(struct cli_def *cli)
+{
+ struct unp *u = cli->users, *n;
+
+ if (!cli) return CLI_OK;
+ 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;
+ }
+
+ /* free all commands */
+ cli_unregister_all(cli, 0);
+
+ free_z(cli->commandname);
+ 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 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 == inquote || (word_start && !inquote && (isspace(*p) || *p == '|')))
+ {
+ if (word_start)
+ {
+ int len = p - word_start;
+
+ memcpy(words[nwords] = malloc(len + 1), word_start, len);
+ words[nwords++][len] = 0;
+ }
+
+ if (!*p)
+ break;
+
+ if (inquote)
+ p++; /* skip over trailing quote */
+
+ inquote = 0;
+ word_start = 0;
+ }
+ else if (*p == '"' || *p == '\'')
+ {
+ 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);
+ p[0] = 0;
+
+ for (i = 0; i < argc; i++)
+ {
+ if (i)
+ strcat(p, " ");
+
+ strcat(p, argv[i]);
+ }
+
+ return p;
+}
+
+static int cli_find_command(struct cli_def *cli, struct cli_command *commands, int num_words, char *words[],
+ int start_word, int filters[])
+{
+ struct cli_command *c, *again_config = NULL, *again_any = NULL;
+ int c_words = num_words;
+
+ if (filters[0])
+ c_words = filters[0];
+
+ // Deal with ? for help
+ if (!words[start_word])
+ return CLI_ERROR;
+
+ if (words[start_word][strlen(words[start_word]) - 1] == '?')
+ {
+ int l = strlen(words[start_word])-1;
+
+ if (commands->parent && commands->parent->callback)
+ cli_error(cli, "%-20s %s", cli_command_name(cli, commands->parent),
+ (commands->parent->help != NULL ? commands->parent->help : ""));
+
+ for (c = commands; c; c = c->next)
+ {
+ if (strncasecmp(c->command, words[start_word], l) == 0
+ && (c->callback || c->children)
+ && cli->privilege >= c->privilege
+ && (c->mode == cli->mode || c->mode == MODE_ANY))
+ cli_error(cli, " %-20s %s", c->command, (c->help != NULL ? c->help : ""));
+ }
+
+ return CLI_OK;
+ }
+
+ for (c = commands; c; c = c->next)
+ {
+ if (cli->privilege < c->privilege)
+ continue;
+
+ if (strncasecmp(c->command, words[start_word], c->unique_len))
+ continue;
+
+ if (strncasecmp(c->command, words[start_word], strlen(words[start_word])))
+ continue;
+
+ AGAIN:
+ if (c->mode == cli->mode || (c->mode == MODE_ANY && again_any != NULL))
+ {
+ int rc = CLI_OK;
+ int f;
+ struct cli_filter **filt = &cli->filters;
+
+ // Found a word!
+ if (!c->children)
+ {
+ // Last word
+ if (!c->callback)
+ {
+ 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_find_command(cli, c->children, num_words, words, start_word + 1, filters);
+ if (rc == CLI_ERROR_ARG)
+ {
+ if (c->callback)
+ {
+ rc = CLI_OK;
+ goto CORRECT_CHECKS;
+ }
+ else
+ {
+ cli_error(cli, "Invalid %s \"%s\"", commands->parent ? "argument" : "command",
+ words[start_word]);
+ }
+ }
+ return rc;
+ }
+
+ if (!c->callback)
+ {
+ cli_error(cli, "Internal server error processing \"%s\"", cli_command_name(cli, c));
+ return CLI_ERROR;
+ }
+
+ CORRECT_CHECKS:
+ for (f = 0; rc == CLI_OK && filters[f]; f++)
+ {
+ int n = num_words;
+ char **argv;
+ int argc;
+ int len;
+
+ if (filters[f+1])
+ n = filters[f+1];
+
+ if (filters[f] == n - 1)
+ {
+ cli_error(cli, "Missing filter");
+ return CLI_ERROR;
+ }
+
+ argv = words + filters[f] + 1;
+ argc = n - (filters[f] + 1);
+ len = strlen(argv[0]);
+ if (argv[argc - 1][strlen(argv[argc - 1]) - 1] == '?')
+ {
+ if (argc == 1)
+ {
+ int i;
+ for (i = 0; filter_cmds[i].cmd; i++)
+ cli_error(cli, " %-20s %s", filter_cmds[i].cmd, filter_cmds[i].help );
+ }
+ else
+ {
+ if (argv[0][0] != 'c') // count
+ cli_error(cli, " WORD");
+
+ if (argc > 2 || argv[0][0] == 'c') // count
+ cli_error(cli, " <cr>");
+ }
+
+ return CLI_OK;
+ }
+
+ if (argv[0][0] == 'b' && len < 3) // [beg]in, [bet]ween
+ {
+ cli_error(cli, "Ambiguous filter \"%s\" (begin, between)", argv[0]);
+ return CLI_ERROR;
+ }
+ *filt = calloc(sizeof(struct cli_filter), 1);
+
+ if (!strncmp("include", argv[0], len) || !strncmp("exclude", argv[0], len) ||
+ !strncmp("grep", argv[0], len) || !strncmp("egrep", argv[0], len))
+ rc = cli_match_filter_init(cli, argc, argv, *filt);
+ else if (!strncmp("begin", argv[0], len) || !strncmp("between", argv[0], len))
+ rc = cli_range_filter_init(cli, argc, argv, *filt);
+ else if (!strncmp("count", argv[0], len))
+ rc = cli_count_filter_init(cli, argc, argv, *filt);
+ else
+ {
+ cli_error(cli, "Invalid filter \"%s\"", argv[0]);
+ rc = CLI_ERROR;
+ }
+
+ if (rc == CLI_OK)
+ {
+ filt = &(*filt)->next;
+ }
+ else
+ {
+ free(*filt);
+ *filt = 0;
+ }
+ }
+
+ if (rc == CLI_OK)
+ rc = c->callback(cli, cli_command_name(cli, c), words + start_word + 1, c_words - start_word - 1);
+
+ while (cli->filters)
+ {
+ struct cli_filter *filt = cli->filters;
+
+ // call one last time to clean up
+ filt->filter(cli, NULL, filt->data);
+ cli->filters = filt->next;
+ free(filt);
+ }
+
+ 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;
+ }
+
+ if (start_word == 0)
+ cli_error(cli, "Invalid %s \"%s\"", commands->parent ? "argument" : "command", words[start_word]);
+
+ return CLI_ERROR_ARG;
+}
+
+int cli_run_command(struct cli_def *cli, const char *command)
+{
+ int r;
+ unsigned int num_words, i, f;
+ char *words[CLI_MAX_LINE_WORDS] = {0};
+ int filters[CLI_MAX_LINE_WORDS] = {0};
+
+ if (!command) return CLI_ERROR;
+ while (isspace(*command))
+ command++;
+
+ if (!*command) return CLI_OK;
+
+ num_words = cli_parse_line(command, words, CLI_MAX_LINE_WORDS);
+ for (i = f = 0; i < num_words && f < CLI_MAX_LINE_WORDS - 1; i++)
+ {
+ if (words[i][0] == '|')
+ filters[f++] = i;
+ }
+
+ filters[f] = 0;
+
+ if (num_words)
+ r = cli_find_command(cli, cli->commands, num_words, words, 0, filters);
+ else
+ r = CLI_ERROR;
+
+ for (i = 0; i < num_words; i++)
+ free(words[i]);
+
+ if (r == CLI_QUIT)
+ return r;
+
+ return CLI_OK;
+}
+
+static int cli_get_completions(struct cli_def *cli, const char *command, char **completions, int max_completions)
+{
+ struct cli_command *c;
+ struct cli_command *n;
+ int num_words, save_words, i, k=0;
+ char *words[CLI_MAX_LINE_WORDS] = {0};
+ int filter = 0;
+
+ if (!command) return 0;
+ while (isspace(*command))
+ command++;
+
+ save_words = num_words = cli_parse_line(command, words, sizeof(words)/sizeof(words[0]));
+ if (!command[0] || command[strlen(command)-1] == ' ')
+ num_words++;
+
+ if (!num_words)
+ goto out;
+
+ for (i = 0; i < num_words; i++)
+ {
+ if (words[i] && words[i][0] == '|')
+ filter = i;
+ }
+
+ if (filter) // complete filters
+ {
+ unsigned len = 0;
+
+ if (filter < num_words - 1) // filter already completed
+ goto out;
+
+ if (filter == num_words - 1)
+ len = strlen(words[num_words-1]);
+
+ for (i = 0; filter_cmds[i].cmd && k < max_completions; i++)
+ {
+ if (!len || (len < strlen(filter_cmds[i].cmd) && !strncmp(filter_cmds[i].cmd, words[num_words - 1], len)))
+ completions[k++] = (char *)filter_cmds[i].cmd;
+ }
+
+ completions[k] = NULL;
+ goto out;
+ }
+
+ for (c = cli->commands, i = 0; c && i < num_words && k < max_completions; c = n)
+ {
+ n = c->next;
+
+ if (cli->privilege < c->privilege)
+ continue;
+
+ if (c->mode != cli->mode && c->mode != MODE_ANY)
+ continue;
+
+ if (words[i] && strncasecmp(c->command, words[i], strlen(words[i])))
+ continue;
+
+ if (i < num_words - 1)
+ {
+ if (strlen(words[i]) < c->unique_len)
+ continue;
+
+ n = c->children;
+ i++;
+ continue;
+ }
+
+ completions[k++] = c->command;
+ }
+
+out:
+ for (i = 0; i < save_words; i++)
+ free(words[i]);
+
+ return k;
+}
+
+static void cli_clear_line(int sockfd, char *cmd, int l, int cursor)
+{
+ int i;
+ if (cursor < l)
+ {
+ for (i = 0; i < (l - cursor); i++)
+ _write(sockfd, " ", 1);
+ }
+ for (i = 0; i < l; i++)
+ cmd[i] = '\b';
+ for (; i < l * 2; i++)
+ cmd[i] = ' ';
+ for (; i < l * 3; i++)
+ cmd[i] = '\b';
+ _write(sockfd, cmd, i);
+ memset((char *)cmd, 0, i);
+ l = cursor = 0;
+}
+
+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$"
+
+static int pass_matches(const char *pass, const char *try)
+{
+ int des;
+ if ((des = !strncasecmp(pass, DES_PREFIX, sizeof(DES_PREFIX)-1)))
+ pass += sizeof(DES_PREFIX)-1;
+
+#ifndef WIN32
+ /*
+ * TODO - find a small crypt(3) function for use on windows
+ */
+ if (des || !strncmp(pass, MD5_PREFIX, sizeof(MD5_PREFIX)-1))
+ try = crypt(try, pass);
+#endif
+
+ return !strcmp(pass, try);
+}
+
+#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));
+
+ return len + write(sockfd, cli->promptchar, strlen(cli->promptchar));
+}
+
+int cli_loop(struct cli_def *cli, int sockfd)
+{
+ unsigned char c;
+ int n, l, oldl = 0, is_telnet_option = 0, skip = 0, esc = 0;
+ int cursor = 0, insertmode = 1;
+ 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+")))
+ 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;
+ int lastchar = 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;
+ fd_set r;
+ 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;
+ }
+
+ FD_ZERO(&r);
+ FD_SET(sockfd, &r);
+
+ if ((sr = select(sockfd + 1, &r, NULL, NULL, &tm)) < 0)
+ {
+ /* select error */
+ if (errno == EINTR)
+ continue;
+
+ perror("select");
+ 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;
+ cursor--;
+ if (cli->state != STATE_PASSWORD && cli->state != STATE_ENABLE_PASSWORD)
+ {
+ for (i = cursor; i <= l; i++) cmd[i] = cmd[i+1];
+ _write(sockfd, "\b", 1);
+ _write(sockfd, cmd + cursor, strlen(cmd + cursor));
+ _write(sockfd, " ", 1);
+ for (i = 0; i <= (int)strlen(cmd + cursor); i++)
+ _write(sockfd, "\b", 1);
+ }
+ }
+ 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 c;
+ for (c = cursor; c < l; c++)
+ _write(sockfd, " ", 1);
+
+ for (c = cursor; c < l; c++)
+ _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)
+ {
+ cli_clear_line(sockfd, cmd, l, cursor);
+ cli_set_configmode(cli, MODE_EXEC, NULL);
+ cli->showprompt = 1;
+ }
+
+ continue;
+ }
+
+ /* TAB completion */
+ if (c == CTRL('I'))
+ {
+ char *completions[CLI_MAX_LINE_WORDS];
+ int num_completions = 0;
+
+ if (cli->state == STATE_LOGIN || cli->state == STATE_PASSWORD || cli->state == STATE_ENABLE_PASSWORD)
+ continue;
+
+ if (cursor != l) continue;
+
+ num_completions = cli_get_completions(cli, cmd, completions, CLI_MAX_LINE_WORDS);
+ if (num_completions == 0)
+ {
+ _write(sockfd, "\a", 1);
+ }
+ else if (num_completions == 1)
+ {
+ // Single completion
+ for (; l > 0; l--, cursor--)
+ {
+ if (cmd[l-1] == ' ' || cmd[l-1] == '|')
+ break;
+ _write(sockfd, "\b", 1);
+ }
+ strcpy((cmd + l), completions[0]);
+ l += strlen(completions[0]);
+ cmd[l++] = ' ';
+ cursor = l;
+ _write(sockfd, completions[0], strlen(completions[0]));
+ _write(sockfd, " ", 1);
+ }
+ else if (lastchar == CTRL('I'))
+ {
+ // double tab
+ int i;
+ _write(sockfd, "\r\n", 2);
+ for (i = 0; i < num_completions; i++)
+ {
+ _write(sockfd, completions[i], strlen(completions[i]));
+ if (i % 4 == 3)
+ _write(sockfd, "\r\n", 2);
+ else
+ _write(sockfd, " ", 1);
+ }
+ if (i % 4 != 3) _write(sockfd, "\r\n", 2);
+ cli->showprompt = 1;
+ }
+ else
+ {
+ // More than one completion
+ lastchar = c;
+ _write(sockfd, "\a", 1);
+ }
+ 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;
+ }
+
+ /* start of line */
+ if (c == CTRL('A'))
+ {
+ if (cursor)
+ {
+ if (cli->state != STATE_PASSWORD && cli->state != STATE_ENABLE_PASSWORD)
+ {
+ _write(sockfd, "\r", 1);
+ show_prompt(cli, sockfd);
+ }
+
+ cursor = 0;
+ }
+
+ continue;
+ }
+
+ /* end of line */
+ if (c == CTRL('E'))
+ {
+ if (cursor < l)
+ {
+ if (cli->state != STATE_PASSWORD && cli->state != STATE_ENABLE_PASSWORD)
+ _write(sockfd, &cmd[cursor], l - cursor);
+
+ cursor = l;
+ }
+
+ continue;
+ }
+
+ /* normal character typed */
+ if (cursor == l)
+ {
+ /* append to end of line */
+ cmd[cursor] = c;
+ if (l < CLI_MAX_LINE_LENGTH - 1)
+ {
+ l++;
+ cursor++;
+ }
+ else
+ {
+ _write(sockfd, "\a", 1);
+ continue;
+ }
+ }
+ else
+ {
+ // Middle of text
+ if (insertmode)
+ {
+ int i;
+ // Move everything one character to the right
+ if (l >= CLI_MAX_LINE_LENGTH - 2) l--;
+ for (i = l; i >= cursor; i--)
+ cmd[i + 1] = cmd[i];
+ // Write what we've just added
+ cmd[cursor] = c;
+
+ _write(sockfd, &cmd[cursor], l - cursor + 1);
+ for (i = 0; i < (l - cursor + 1); i++)
+ _write(sockfd, "\b", 1);
+ l++;
+ }
+ else
+ {
+ cmd[cursor] = c;
+ }
+ 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
+ {
+ if (l == 0) continue;
+ if (cmd[l - 1] != '?' && strcasecmp(cmd, "history") != 0)
+ cli_add_history(cli, cmd);
+
+ if (cli_run_command(cli, cmd) == 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;
+
+ if (fgets(buf, CLI_MAX_LINE_LENGTH - 1, fh) == NULL)
+ break; /* end of file */
+
+ 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 /* didn't save desc */);
+
+ return CLI_OK;
+}
+
+static void _print(struct cli_def *cli, int print_mode, const char *format, va_list ap)
+{
+ va_list aq;
+ int n;
+ char *p;
+
+ if (!cli) return; // sanity check
+
+ while (1)
+ {
+ va_copy(aq, ap);
+ if ((n = vsnprintf(cli->buffer, cli->buf_size, format, ap)) == -1)
+ return;
+
+ if ((unsigned)n >= cli->buf_size)
+ {
+ cli->buf_size = n + 1;
+ cli->buffer = realloc(cli->buffer, cli->buf_size);
+ if (!cli->buffer)
+ return;
+ va_end(ap);
+ va_copy(ap, aq);
+ continue;
+ }
+ break;
+ }
+
+
+ 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_match_filter_init(struct cli_def *cli, int argc, char **argv, struct cli_filter *filt)
+{
+ struct cli_match_filter_state *state;
+ int rflags;
+ int i;
+ char *p;
+
+ if (argc < 2)
+ {
+ if (cli->client)
+ fprintf(cli->client, "Match filter requires an argument\r\n");
+
+ return CLI_ERROR;
+ }
+
+ filt->filter = cli_match_filter;
+ filt->data = state = calloc(sizeof(struct cli_match_filter_state), 1);
+
+ if (argv[0][0] == 'i' || (argv[0][0] == 'e' && argv[0][1] == 'x')) // include/exclude
+ {
+ if (argv[0][0] == 'e')
+ state->flags = MATCH_INVERT;
+
+ state->match.string = join_words(argc-1, argv+1);
+ return CLI_OK;
+ }
+
+#ifdef WIN32
+ /*
+ * No regex functions in windows, so return an error
+ */
+ return CLI_ERROR;
+#endif
+
+ state->flags = MATCH_REGEX;
+
+ // grep/egrep
+ rflags = REG_NOSUB;
+ if (argv[0][0] == 'e') // egrep
+ rflags |= REG_EXTENDED;
+
+ i = 1;
+ while (i < argc - 1 && argv[i][0] == '-' && argv[i][1])
+ {
+ int last = 0;
+ p = &argv[i][1];
+
+ if (strspn(p, "vie") != strlen(p))
+ break;
+
+ while (*p)
+ {
+ switch (*p++)
+ {
+ case 'v':
+ state->flags |= MATCH_INVERT;
+ break;
+
+ case 'i':
+ rflags |= REG_ICASE;
+ break;
+
+ case 'e':
+ last++;
+ break;
+ }
+ }
+
+ i++;
+ if (last)
+ break;
+ }
+
+ p = join_words(argc-i, argv+i);
+ if ((i = regcomp(&state->match.re, p, rflags)))
+ {
+ if (cli->client)
+ fprintf(cli->client, "Invalid pattern \"%s\"\r\n", p);
+
+ free_z(p);
+ return CLI_ERROR;
+ }
+
+ free_z(p);
+ 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) // clean up
+ {
+ if (state->flags & MATCH_REGEX)
+ regfree(&state->match.re);
+ else
+ free(state->match.string);
+
+ 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 = 0;
+ char *to = 0;
+
+ if (!strncmp(argv[0], "bet", 3)) // between
+ {
+ if (argc < 3)
+ {
+ if (cli->client)
+ fprintf(cli->client, "Between filter requires 2 arguments\r\n");
+
+ return CLI_ERROR;
+ }
+
+ if (!(from = strdup(argv[1])))
+ return CLI_ERROR;
+ to = join_words(argc-2, argv+2);
+ }
+ else // begin
+ {
+ if (argc < 2)
+ {
+ if (cli->client)
+ fprintf(cli->client, "Begin filter requires an argument\r\n");
+
+ return CLI_ERROR;
+ }
+
+ from = join_words(argc-1, argv+1);
+ }
+
+ filt->filter = cli_range_filter;
+ filt->data = state = calloc(sizeof(struct cli_range_filter_state), 1);
+
+ state->from = from;
+ state->to = to;
+
+ return CLI_OK;
+}
+
+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) // clean up
+ {
+ free_z(state->from);
+ free_z(state->to);
+ 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) // clean up
+ {
+ // print count
+ if (cli->client)
+ fprintf(cli->client, "%d\r\n", *count);
+
+ free(count);
+ return CLI_OK;
+ }
+
+ while (isspace(*string))
+ string++;
+
+ if (*string)
+ (*count)++; // only count non-blank lines
+
+ return CLI_ERROR; // no output
+}
+
+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;
+}
diff --git a/libcli.h b/libcli.h
new file mode 100644
index 0000000..e978526
--- /dev/null
+++ b/libcli.h
@@ -0,0 +1,132 @@
+#ifndef __LIBCLI_H__
+#define __LIBCLI_H__
+
+// vim:sw=4 tw=120 et
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <stdio.h>
+#include <stdarg.h>
+#include <sys/time.h>
+
+#define CLI_OK 0
+#define CLI_ERROR -1
+#define CLI_QUIT -2
+#define CLI_ERROR_ARG -3
+
+#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_filter {
+ int (*filter)(struct cli_def *cli, const char *string, void *data);
+ void *data;
+ struct cli_filter *next;
+};
+
+struct cli_command {
+ char *command;
+ int (*callback)(struct cli_def *, const char *, char **, int);
+ unsigned int unique_len;
+ char *help;
+ int privilege;
+ int mode;
+ struct cli_command *next;
+ struct cli_command *children;
+ struct cli_command *parent;
+};
+
+struct cli_def *cli_init();
+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 *));
+
+// 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);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/libcli.spec b/libcli.spec
new file mode 100644
index 0000000..4d459a3
--- /dev/null
+++ b/libcli.spec
@@ -0,0 +1,156 @@
+Version: 1.9.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 <david@dparrish.com>
+BuildRoot: %{_tmppath}/%{name}-%{version}-%(%__id -un)
+%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.
+
+%prep
+%setup
+
+%build
+make
+
+%install
+rm -rf $RPM_BUILD_ROOT
+make DESTDIR=$RPM_BUILD_ROOT PREFIX=/usr install
+find $RPM_BUILD_ROOT/usr ! -type d -print | grep -v '\/(README|\.html)$' | \
+ sed "s@^$RPM_BUILD_ROOT@@g" | sed "s/^\(.*\)$/\1\*/" > %{name}-%{version}-filelist
+
+%post
+ldconfig
+
+%clean
+rm -rf $RPM_BUILD_ROOT
+
+%files -f %{name}-%{version}-filelist
+%defattr(-, root, root)
+
+%changelog
+* Mon Feb 1 2010 David Parrish <david@dparrish.com> 1.9.7-1
+- Fix memory leak in cli_get_completions - fengxj325@gmail.com
+
+* Tue Jun 5 2012 Teemu Karimerto <teemu.karimerto@steo.fi> 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 <david@dparrish.com> 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 <david@dparrish.com> 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
+
+* Fri Jul 28 2008 David Parrish <david@dparrish.com> 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 Jun 2 2008 David Parrish <david@dparrish.com> 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 <bod@optus.net> 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 <david@dparrish.com> 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
+
+* Mon Jan 19 2007 David Parrish <david@dparrish.com> 1.8.8-1
+- Fix broken auth_callback logic - Thanks Ben Menchaca
+
+* Sat Jun 17 2006 Brendan O'Dea <bod@optus.net> 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 <david@dparrish.com> 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 <bod@optus.net> 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 <bod@optusnet.com.au> 1.8.5-1
+- Add cli_error function which does not filter output.
+
+* Wed Jan 5 2005 Brendan O'Dea <bod@optusnet.com.au> 1.8.4-1
+- Add printf attribute to cli_print prototype
+
+* Fri Nov 19 2004 Brendan O'Dea <bod@optusnet.com.au> 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 <bod@optusnet.com.au> 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 <bod@optusnet.com.au> 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 <bod@optusnet.com.au> 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 <bod@optusnet.com.au> 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 <bod@optusnet.com.au> 1.6.2-1
+- Small cosmetic changes to output.
+- Exiting configure/^Z shouldn't disable.
+- Support encrypted password.
+
+* Fri Jun 25 2004 David Parrish <david@dparrish.com> 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 <david@dparrish.com>
+- 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 <bod@optusnet.com.au> 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 <david@dparrish.com>
+- Add cli_regular to enable regular processing while cli is connected
+
+* Wed Jun 25 2003 David Parrish <david@dparrish.com>
+- Stop random stack smashing in cli_command_name.
+- Stop memory leak by allocating static variable in cli_command_name.