[Cryptech-Commits] [user/ft/libcli] 01/02: Import of libcli from https://github.com/dparrish/libcli.git

git at cryptech.is git at cryptech.is
Mon May 16 09:13:05 UTC 2016


This is an automated email from the git hooks/post-receive script.

fredrik at thulin.net pushed a commit to branch master
in repository user/ft/libcli.

commit 5ad2af0a9c1a3fab20a3809cc2fdce66876ff456
Author: Fredrik Thulin <fredrik at thulin.net>
AuthorDate: Mon May 16 11:04:47 2016 +0200

    Import of libcli from https://github.com/dparrish/libcli.git
    
    Upstream commit 958e44e7a69d3c71e89908fa8ee15232c55a821a
---
 .gitignore  |    5 +
 COPYING     |  503 +++++++++++++
 Makefile    |   84 +++
 README      |  112 +++
 clitest.c   |  344 +++++++++
 clitest.txt |    1 +
 libcli.c    | 2353 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 libcli.h    |  132 ++++
 libcli.spec |  156 ++++
 9 files changed, 3690 insertions(+)

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 at 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 at 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 at dparrish.com> 1.9.7-1
+- Fix memory leak in cli_get_completions - fengxj325 at gmail.com
+
+* Tue Jun  5 2012 Teemu Karimerto <teemu.karimerto at 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 at 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 at 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 at 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 at 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 at 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 at 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 at dparrish.com> 1.8.8-1
+- Fix broken auth_callback logic - Thanks Ben Menchaca
+
+* Sat Jun 17 2006 Brendan O'Dea <bod at 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 at 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 at 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 at optusnet.com.au> 1.8.5-1
+- Add cli_error function which does not filter output.
+
+* Wed Jan  5 2005 Brendan O'Dea <bod at optusnet.com.au> 1.8.4-1
+- Add printf attribute to cli_print prototype
+
+* Fri Nov 19 2004 Brendan O'Dea <bod at 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 at 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 at 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 at 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 at 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 at 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 at 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 at 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 at 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 at dparrish.com>
+- Add cli_regular to enable regular processing while cli is connected
+
+* Wed Jun 25 2003 David Parrish <david at dparrish.com>
+- Stop random stack smashing in cli_command_name.
+- Stop memory leak by allocating static variable in cli_command_name.



More information about the Commits mailing list