import amtterm-1.0
authorMichael Gernoth <michael@gernoth.net>
Mon, 12 May 2008 13:28:28 +0000 (15:28 +0200)
committerMichael Gernoth <michael@gernoth.net>
Mon, 12 May 2008 13:28:28 +0000 (15:28 +0200)
24 files changed:
COPYING [new file with mode: 0644]
GNUmakefile [new file with mode: 0644]
INSTALL [new file with mode: 0644]
RedirectionConstants.h [new file with mode: 0644]
VERSION [new file with mode: 0644]
amt-howto.man [new file with mode: 0644]
amtterm.c [new file with mode: 0644]
amtterm.man [new file with mode: 0644]
amttool [new file with mode: 0755]
amttool.man [new file with mode: 0644]
gamt.c [new file with mode: 0644]
gamt.desktop [new file with mode: 0644]
gamt.man [new file with mode: 0644]
list.h [new file with mode: 0644]
mk/Autoconf.mk [new file with mode: 0644]
mk/Compile.mk [new file with mode: 0644]
mk/Maintainer.mk [new file with mode: 0644]
mk/Variables.mk [new file with mode: 0644]
parseconfig.c [new file with mode: 0644]
parseconfig.h [new file with mode: 0644]
redir.c [new file with mode: 0644]
redir.h [new file with mode: 0644]
tcp.c [new file with mode: 0644]
tcp.h [new file with mode: 0644]

diff --git a/COPYING b/COPYING
new file mode 100644 (file)
index 0000000..d511905
--- /dev/null
+++ b/COPYING
@@ -0,0 +1,339 @@
+                   GNU GENERAL PUBLIC LICENSE
+                      Version 2, June 1991
+
+ Copyright (C) 1989, 1991 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.
+
+                           Preamble
+
+  The licenses for most software are designed to take away your
+freedom to share and change it.  By contrast, the GNU General Public
+License is intended to guarantee your freedom to share and change free
+software--to make sure the software is free for all its users.  This
+General Public License applies to most of the Free Software
+Foundation's software and to any other program whose authors commit to
+using it.  (Some other Free Software Foundation software is covered by
+the GNU Lesser General Public License instead.)  You can apply it to
+your programs, too.
+
+  When we speak of free software, we are referring to freedom, 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 or use pieces of it
+in new free programs; and that you know you can do these things.
+
+  To protect your rights, we need to make restrictions that forbid
+anyone to deny you these rights or to ask you to surrender the rights.
+These restrictions translate to certain responsibilities for you if you
+distribute copies of the software, or if you modify it.
+
+  For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must give the recipients all the rights that
+you have.  You must make sure that they, too, receive or can get the
+source code.  And you must show them these terms so they know their
+rights.
+
+  We protect your rights with two steps: (1) copyright the software, and
+(2) offer you this license which gives you legal permission to copy,
+distribute and/or modify the software.
+
+  Also, for each author's protection and ours, we want to make certain
+that everyone understands that there is no warranty for this free
+software.  If the software is modified by someone else and passed on, we
+want its recipients to know that what they have is not the original, so
+that any problems introduced by others will not reflect on the original
+authors' reputations.
+
+  Finally, any free program is threatened constantly by software
+patents.  We wish to avoid the danger that redistributors of a free
+program will individually obtain patent licenses, in effect making the
+program proprietary.  To prevent this, we have made it clear that any
+patent must be licensed for everyone's free use or not licensed at all.
+
+  The precise terms and conditions for copying, distribution and
+modification follow.
+
+                   GNU GENERAL PUBLIC LICENSE
+   TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+  0. This License applies to any program or other work which contains
+a notice placed by the copyright holder saying it may be distributed
+under the terms of this General Public License.  The "Program", below,
+refers to any such program or work, and a "work based on the Program"
+means either the Program or any derivative work under copyright law:
+that is to say, a work containing the Program or a portion of it,
+either verbatim or with modifications and/or translated into another
+language.  (Hereinafter, translation is included without limitation in
+the term "modification".)  Each licensee is addressed as "you".
+
+Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope.  The act of
+running the Program is not restricted, and the output from the Program
+is covered only if its contents constitute a work based on the
+Program (independent of having been made by running the Program).
+Whether that is true depends on what the Program does.
+
+  1. You may copy and distribute verbatim copies of the Program's
+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 give any other recipients of the Program a copy of this License
+along with the Program.
+
+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 Program or any portion
+of it, thus forming a work based on the Program, 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) You must cause the modified files to carry prominent notices
+    stating that you changed the files and the date of any change.
+
+    b) You must cause any work that you distribute or publish, that in
+    whole or in part contains or is derived from the Program or any
+    part thereof, to be licensed as a whole at no charge to all third
+    parties under the terms of this License.
+
+    c) If the modified program normally reads commands interactively
+    when run, you must cause it, when started running for such
+    interactive use in the most ordinary way, to print or display an
+    announcement including an appropriate copyright notice and a
+    notice that there is no warranty (or else, saying that you provide
+    a warranty) and that users may redistribute the program under
+    these conditions, and telling the user how to view a copy of this
+    License.  (Exception: if the Program itself is interactive but
+    does not normally print such an announcement, your work based on
+    the Program is not required to print an announcement.)
+
+These requirements apply to the modified work as a whole.  If
+identifiable sections of that work are not derived from the Program,
+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 Program, 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 Program.
+
+In addition, mere aggregation of another work not based on the Program
+with the Program (or with a work based on the Program) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+  3. You may copy and distribute the Program (or a work based on it,
+under Section 2) in object code or executable form under the terms of
+Sections 1 and 2 above provided that you also do one of the following:
+
+    a) 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; or,
+
+    b) Accompany it with a written offer, valid for at least three
+    years, to give any third party, for a charge no more than your
+    cost of physically performing source distribution, a complete
+    machine-readable copy of the corresponding source code, to be
+    distributed under the terms of Sections 1 and 2 above on a medium
+    customarily used for software interchange; or,
+
+    c) Accompany it with the information you received as to the offer
+    to distribute corresponding source code.  (This alternative is
+    allowed only for noncommercial distribution and only if you
+    received the program in object code or executable form with such
+    an offer, in accord with Subsection b above.)
+
+The source code for a work means the preferred form of the work for
+making modifications to it.  For an executable work, 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 executable.  However, as a
+special exception, the source code 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.
+
+If distribution of executable or 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 counts as
+distribution of the source code, even though third parties are not
+compelled to copy the source along with the object code.
+
+  4. You may not copy, modify, sublicense, or distribute the Program
+except as expressly provided under this License.  Any attempt
+otherwise to copy, modify, sublicense or distribute the Program 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.
+
+  5. 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 Program or its derivative works.  These actions are
+prohibited by law if you do not accept this License.  Therefore, by
+modifying or distributing the Program (or any work based on the
+Program), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Program or works based on it.
+
+  6. Each time you redistribute the Program (or any work based on the
+Program), the recipient automatically receives a license from the
+original licensor to copy, distribute or modify the Program 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 to
+this License.
+
+  7. 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 Program at all.  For example, if a patent
+license would not permit royalty-free redistribution of the Program 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 Program.
+
+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.
+
+  8. If the distribution and/or use of the Program is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Program 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.
+
+  9. The Free Software Foundation may publish revised and/or new versions
+of the 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 Program
+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 Program does not specify a version number of
+this License, you may choose any version ever published by the Free Software
+Foundation.
+
+  10. If you wish to incorporate parts of the Program into other free
+programs whose distribution conditions are different, 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
+
+  11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
+FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW.  EXCEPT WHEN
+OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
+PROVIDE THE PROGRAM "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 PROGRAM IS WITH YOU.  SHOULD THE
+PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
+REPAIR OR CORRECTION.
+
+  12. 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 PROGRAM 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 PROGRAM (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 PROGRAM TO OPERATE WITH ANY OTHER
+PROGRAMS), 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 Programs
+
+  If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+  To do so, attach the following notices to the program.  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 program's name and a brief idea of what it does.>
+    Copyright (C) <year>  <name of author>
+
+    This program is free software; you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation; either version 2 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License along
+    with this program; if not, write to the Free Software Foundation, Inc.,
+    51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+Also add information on how to contact you by electronic and paper mail.
+
+If the program is interactive, make it output a short notice like this
+when it starts in an interactive mode:
+
+    Gnomovision version 69, Copyright (C) year name of author
+    Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+    This is free software, and you are welcome to redistribute it
+    under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License.  Of course, the commands you use may
+be called something other than `show w' and `show c'; they could even be
+mouse-clicks or menu items--whatever suits your program.
+
+You should also get your employer (if you work as a programmer) or your
+school, if any, to sign a "copyright disclaimer" for the program, if
+necessary.  Here is a sample; alter the names:
+
+  Yoyodyne, Inc., hereby disclaims all copyright interest in the program
+  `Gnomovision' (which makes passes at compilers) written by James Hacker.
+
+  <signature of Ty Coon>, 1 April 1989
+  Ty Coon, President of Vice
+
+This General Public License does not permit incorporating your program into
+proprietary programs.  If your program is a subroutine library, you may
+consider it more useful to permit linking proprietary applications with the
+library.  If this is what you want to do, use the GNU Lesser General
+Public License instead of this License.
diff --git a/GNUmakefile b/GNUmakefile
new file mode 100644 (file)
index 0000000..bfc3f0e
--- /dev/null
@@ -0,0 +1,68 @@
+# config
+srcdir = .
+VPATH  = $(srcdir)
+-include Make.config
+include $(srcdir)/mk/Variables.mk
+
+CFLAGS += -Wall -Wno-pointer-sign
+CFLAGS += -DVERSION='"$(VERSION)"'
+
+TARGETS        := amtterm
+DESKTOP := $(wildcard *.desktop)
+
+all: build
+
+#################################################################
+# poor man's autoconf ;-)
+
+include mk/Autoconf.mk
+
+define make-config
+LIB            := $(LIB)
+HAVE_GTK       := $(call ac_pkg_config,gtk+-x11-2.0)
+HAVE_VTE       := $(call ac_pkg_config,vte)
+endef
+
+#################################################################
+
+# build gamt?
+ifeq ($(HAVE_GTK)$(HAVE_VTE),yesyes)
+  TARGETS += gamt
+  gamt : CFLAGS += -Wno-strict-prototypes
+  gamt : pkglst += gtk+-x11-2.0 vte
+endif
+
+CFLAGS += $(shell test "$(pkglst)" != "" && pkg-config --cflags $(pkglst))
+LDLIBS += $(shell test "$(pkglst)" != "" && pkg-config --libs   $(pkglst))
+
+#################################################################
+
+build: $(TARGETS)
+
+install: build
+       $(INSTALL_DIR) $(bindir) $(appdir) $(mandir)/man1 $(mandir)/man7
+       $(INSTALL_BINARY) $(TARGETS) $(bindir)
+       $(INSTALL_SCRIPT) amttool $(bindir)
+       $(INSTALL_DATA) $(DESKTOP) $(appdir)
+       $(INSTALL_DATA) gamt.man $(mandir)/man1/gamt.1
+       $(INSTALL_DATA) amtterm.man $(mandir)/man1/amtterm.1
+       $(INSTALL_DATA) amttool.man $(mandir)/man1/amttool.1
+       $(INSTALL_DATA) amt-howto.man $(mandir)/man7/amt-howto.7
+
+clean:
+       rm -f *.o *~
+       rm -f $(TARGETS)
+
+distclean: clean
+       rm -f Make.config
+
+#################################################################
+
+amtterm: amtterm.o redir.o tcp.o
+gamt: gamt.o redir.o tcp.o parseconfig.o
+
+#################################################################
+
+include mk/Compile.mk
+include mk/Maintainer.mk
+-include $(depfiles)
diff --git a/INSTALL b/INSTALL
new file mode 100644 (file)
index 0000000..8f99186
--- /dev/null
+++ b/INSTALL
@@ -0,0 +1,59 @@
+
+howto compile and install this package
+======================================
+
+
+really short install instructions
+---------------------------------
+
+       $ make
+       $ su -c "make install"
+
+
+
+the more detailed version
+-------------------------
+
+Make sure you use GNU make.  The file name "GNUmakefile" isn't a joke,
+this package really requires GNU make.
+
+As first step make will do some config checks on your system and write
+the results to Make.config.  If you want to have a look at Make.config
+before the actual build starts you can run this step separately using
+"make config".
+
+The Makefiles use the usual GNU-ish Makefile conventions for variable
+names and default values, i.e. prefix=/usr/local, ...
+
+The values for some frequently adapted variables are initialized from
+the enviroment.  Thus you can change the defaults simply by setting
+environment variables:
+
+       $ prefix="/usr"
+       $ CFLAGS="-O3 -mcpu=i686"
+       $ export prefix CFLAGS
+
+Almost any variable can be overridden on the make command line.  It is
+often used this way to install into some buildroot for packaging ...
+
+       $ su -c "make DESTDIR=/tmp/buildroot install"
+
+... but it works for most other variables equally well.  There are
+some exceptions through, it usually does _not_ work for CFLAGS for
+example.
+
+Try "make verbose=yes" if you want to see the complete command lines
+executed by make instead of the short messages (for trouble shooting,
+because you like this way, for whatever reason ...).  This also makes
+the config checks performed by "make config" more verbose.
+
+If you don't trust my Makefiles you can run "make -n install" to see
+what "make install" would do on your system.  It will produce
+human-readable output (unlike automake ...).
+
+Have fun,
+
+  Gerd
+
+-- 
+Gerd Hoffmann <kraxel@suse.de>
diff --git a/RedirectionConstants.h b/RedirectionConstants.h
new file mode 100644 (file)
index 0000000..994f04e
--- /dev/null
@@ -0,0 +1,79 @@
+#ifndef __REDIRECTION_CONSTANTS__\r
+#define __REDIRECTION_CONSTANTS__\r
+\r
+#define STATUS_SUCCESS                  0x00\r
+#define SOL_FIRMWARE_REV_MAJOR          0x01\r
+#define SOL_FIRMWARE_REV_MINOR          0x00\r
+\r
+//Session Manager Messages Formats\r
+#define START_REDIRECTION_SESSION       0x10\r
+#define START_REDIRECTION_SESSION_REPLY 0x11\r
+#define END_REDIRECTION_SESSION         0x12\r
+#define AUTHENTICATE_SESSION            0x13\r
+#define AUTHENTICATE_SESSION_REPLY      0x14\r
+\r
+#define START_REDIRECTION_SESSION_LENGTH        8\r
+#define START_REDIRECTION_SESSION_REPLY_LENGTH  13\r
+#define END_REDIRECTION_SESSION_LENGTH          4\r
+\r
+//SOL Messages Formats\r
+#define START_SOL_REDIRECTION               0x20\r
+#define START_SOL_REDIRECTION_REPLY         0x21\r
+#define END_SOL_REDIRECTION                 0x22\r
+#define END_SOL_REDIRECTION_REPLY           0x23\r
+#define SOL_KEEP_ALIVE_PING                 0x24  //Console to Host\r
+#define SOL_KEEP_ALIVE_PONG                 0x25  //Host to Console\r
+#define SOL_DATA_TO_HOST                    0x28  //Console to host\r
+#define SOL_DATA_FROM_HOST                  0x2A  //Host to Console\r
+#define SOL_HEARTBEAT                       0x2B\r
+\r
+#define HEARTBEAT_LENGTH                        8\r
+#define START_SOL_REDIRECTION_LENGTH           24\r
+#define START_SOL_REDIRECTION_REPLY_LENGTH     23 //TODO: There is a OEM Defined data field that we are assuming to be 0 bytes..\r
+#define END_SOL_REDIRECTION_LENGTH             8\r
+#define END_SOL_REDIRECTION_REPLY_LENGTH       8\r
+\r
+//IDER Messages Formats\r
+#define START_IDER_REDIRECTION              0x40\r
+#define START_IDER_REDIRECTION_REPLY        0x41\r
+#define END_IDER_REDIRECTION                0x42\r
+#define END_IDER_REDIRECTION_REPLY          0x43\r
+#define IDER_KEEP_ALIVE_PING                0x44  //Console to Host\r
+#define IDER_KEEP_ALIVE_PONG                0x45  //Host to Console\r
+#define IDER_RESET_OCCURED                  0x46\r
+#define IDER_RESET_OCCURED_RESPONSE         0x47\r
+#define IDER_DISABLE_ENABLE_FEATURES        0x48\r
+#define IDER_DISABLE_ENABLE_FEATURES_REPLY  0x49\r
+#define IDER_HEARTBEAT                      0x4B\r
+#define IDER_COMMAND_WRITTEN                0x50\r
+#define IDER_COMMAND_END_RESPONSE           0x51\r
+#define IDER_GET_DATA_FROM_HOST             0x52\r
+#define IDER_DATA_FROM_HOST                 0x53\r
+#define IDER_DATA_TO_HOST                   0x54\r
+\r
+#define START_IDER_REDIRECTION_LENGTH                 18\r
+#define START_IDER_REDIRECTION_REPLY_LENGTH           30 //TODO: There is a OEM Defined data field that we are assuming to be 0 bytes..\r
+#define END_IDER_REDIRECTION_LENGTH                   8\r
+#define END_IDER_REDIRECTION_REPLY_LENGTH             8\r
+#define IDER_RESET_OCCURED_LENGTH                     9\r
+#define IDER_RESET_OCCURED_RESPONSE_LENGTH            8\r
+#define IDER_DISABLE_ENABLE_FEATURES_REPLY_LENGTH     13\r
+#define IDER_COMMAND_END_RESPONSE_LENGTH              31\r
+#define IDER_GET_DATA_FROM_HOST_LENGTH                31\r
+\r
+static const unsigned int SOL_SESSION = 0x204C4F53;\r
+static const unsigned int IDER_SESSION = 0x52454449;\r
+\r
+static const unsigned short MAX_TRANSMIT_BUFFER = 1000;\r
+static const unsigned short TRANSMIT_BUFFER_TIMEOUT = 100;\r
+static const unsigned short TRANSMIT_OVERFLOW_TIMEOUT = 0;\r
+static const unsigned short HOST_SESSION_RX_TIMEOUT = 10000;\r
+static const unsigned short HOST_FIFO_RX_FLUSH_TIMEOUT = 0;\r
+static const unsigned short HEARTBEAT_INTERVAL = 5000;\r
+\r
+static const unsigned int SESSION_MANAGER_OEM_IANA_NUMBER = 0x5555; //TODO: Test \r
+static const unsigned int SOL_OEM_IANA_NUMBER = 0x6666;  //TODO: Test\r
+\r
+static const unsigned short RECEIVE_BUFFER_SIZE = 0x100;\r
+\r
+#endif\r
diff --git a/VERSION b/VERSION
new file mode 100644 (file)
index 0000000..d3827e7
--- /dev/null
+++ b/VERSION
@@ -0,0 +1 @@
+1.0
diff --git a/amt-howto.man b/amt-howto.man
new file mode 100644 (file)
index 0000000..253154a
--- /dev/null
@@ -0,0 +1,130 @@
+.TH amt-howto 7 "(c) 2007 Gerd Hoffmann"
+.SH SYNOPSIS
+Intel AMT with linux mini howto
+.SH DESCRIPTION
+
+.SS What is AMT and why I should care?
+AMT stands for "Active Management Technology".  It provides some
+remote management facilities.  They are handled by the hardware and
+firmware, thus they work independant from the operation system.
+Means: It works before Linux bootet up to the point where it activated
+the network interface.  It works even when your most recent test
+kernel deadlocked the machine.  Which makes it quite useful for
+development machines ...
+.P
+Intel AMT is part of the vPro Platform.  Recent intel-chipset based
+business machines should have it.  My fairly new Intel SDV machine has
+it too.
+
+.SS Documentation
+Look here for documentation beyond this mini howto:
+.br
+http://www.intel.com/technology/platform-technology/intel-amt/engage.htm
+.br
+Most useful to get started: "Intel AMT Deployment and Reference Guide"
+
+.SS Very short AMT enabling instructions.
+.TP
+Enter BIOS Setup.
+* Enable AMT
+.TP
+Enter ME (Management Extention) Setup.  Ctrl-P hotkey works for me.
+* Login, factory default password is "admin".
+.br
+* Change password.  Trivial ones don't work, must include upper-
+and lowercase letters, digits, special characters.
+.br
+* Enable AMT Managment.
+.TP
+Reboot, Enter ME Setup again with AMT enabled.
+* Configure AMT (hostname, network config, ...)
+.br
+* Use SMB (Small Business) management mode.  The other one
+(Enterprise) requires Active Directory Service Infrastructure,
+you don't want that, at least not for your first steps ...
+
+.SS Testing AMT
+Take your browser, point it to http://machine:16992/.  If you
+configured AMT to use DHCP (which is the default) the OS and the
+management stack share the same IP address.
+.P
+You must do that from a remote host as the NIC intercepts network
+packets for AMT, thus it doesn't work from the local machine as the
+packets never pass the NIC then.  If everything is fine you'll see a
+greeting page with a button for login.
+.P
+You can login now, using "admin" as username and the password
+configured during setup.  You'll see some pages with informations
+about the machine.  You can also change AMT settings here.
+
+.SS Control Machine
+You might have noticed already while browing the pages: There is a
+"Remote Control" page.  You can remotely reset and powercycle the
+machine there, thus recover the machine after booting a b0rken kernel,
+without having someone walk over to the machine and hit the reset
+button.
+
+.SS Serial-over-LAN (SOL) console
+AMT also provides a virtual serial port which can be accessed via
+network.  That gives you a serial console without a serial cable to
+another machine.
+.P
+If you have activated AMT and SOL the linux kernel should see an
+additional serial port, like this on my machine:
+.P
+.nf
+  [root@xeni ~]# dmesg | grep ttyS2
+  0000:00:03.3: ttyS2 at I/O 0xe000 (irq = 169) is a 16550A
+.fi
+.P
+Edit initab, add a line like this:
+.P
+.nf
+  S2:2345:respawn:/sbin/agetty ttyS2 115200 vt100-nav
+.fi
+.P
+You should add the serial port to /etc/securetty too so you are able
+to login as root.  Reload inittab ("init q").  Use amtterm to connect.
+Tap enter.  You should see a login prompt now and be able to login.
+.P
+You can also use that device as console for the linux kernel, using
+the usual "console=ttyS2,115200" kernel command line argument, so you
+see the boot messages (and kernel Oopses, if any).
+.P
+You can tell grub to use that serial device, so you can pick a working
+kernel for the next boot.  Usual commands from the grub manual, except
+that you need "--port=0xe000" instead of "--unit=0" due to the
+non-standard I/O port for the serial line (my machine, yours might use
+another port, check linux kernel boot messages).
+.P
+The magic command for the Xen kernel is "com1=115200,8n1,0xe000,0"
+(again, you might have to replace the I/O port).  The final '0'
+disables the IRQ, otherwise the Xen kernel hangs at boot after
+enabling interrupts.
+
+.SS Fun with Xen and AMT
+The AMT network stack seems to become slightly confused when running
+on a Xen host in DHCP mode.  Everything works fine as long as only
+Dom0 runs.  But if one starts a guest OS (with bridged networking) AMT
+suddenly changes the IP address to the one the guest aquired via DHCP.
+.P
+It is probably a good idea to assign a separate static IP address to
+AMT then.  I didn't manage to switch my machine from DHCP to static IP
+yet though, the BIOS refuses to accept the settings.  The error
+message doesn't indicate why.
+
+.SS More fun with AMT
+You might want to download the DTK (Developer Toolkit, source code is
+available too) and play with it.  The .exe is a self-extracting rar
+archive and can be unpacked on linux using the unrar utility.  The
+Switchbox comes with a linux binary (additionally to the Windows
+stuff).  The GUI tools are written in C#.  Trying to make them fly
+with mono didn't work for me though (mono version 1.2.3 as shipped
+with Fedora 7).
+
+.SH SEE ALSO
+amtterm(1), gamt(1), amttool(1)
+.P
+http://www.intel.com/technology/platform-technology/intel-amt/
+.SH WRITTEN BY
+Gerd Hoffmann <kraxel@redhat.com>
diff --git a/amtterm.c b/amtterm.c
new file mode 100644 (file)
index 0000000..bdafb7b
--- /dev/null
+++ b/amtterm.c
@@ -0,0 +1,265 @@
+/*
+ *  amtterm -- Intel AMT serial-over-lan client, console version.
+ *
+ *  Copyright (C) 2007 Gerd Hoffmann <kraxel@redhat.com
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License along
+ *  with this program; if not, write to the Free Software Foundation, Inc.,
+ *  51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <termios.h>
+#include <signal.h>
+#include <sys/ioctl.h>
+
+#include "redir.h"
+
+#define APPNAME "amtterm"
+#define BUFSIZE 512
+
+/* ------------------------------------------------------------------ */
+
+static int recv_tty(void *cb_data, unsigned char *buf, int len)
+{
+//    struct redir *r = cb_data;
+
+    return write(0, buf, len);
+}
+
+static void state_tty(void *cb_data, enum redir_state old, enum redir_state new)
+{
+    struct redir *r = cb_data;
+
+    if (r->verbose)
+       fprintf(stderr, APPNAME ": %s -> %s (%s)\n",
+               redir_state_name(old), redir_state_name(new),
+               redir_state_desc(new));
+    switch (new) {
+    case REDIR_RUN_SOL:
+       if (r->verbose)
+           fprintf(stderr,
+                   "serial-over-lan redirection ok\n"
+                   "connected now, use ^] to escape\n");
+       break;
+    case REDIR_ERROR:
+       fprintf(stderr, APPNAME ": ERROR: %s\n", r->err);
+       break;
+    default:
+       break;
+    }
+}
+
+static int redir_loop(struct redir *r)
+{
+    unsigned char buf[BUFSIZE+1];
+    struct timeval tv;
+    int rc;
+    fd_set set;
+
+    for(;;) {
+       if (r->state == REDIR_CLOSED ||
+           r->state == REDIR_ERROR)
+           break;
+
+       FD_ZERO(&set);
+       if (r->state == REDIR_RUN_SOL)
+           FD_SET(0,&set);
+       FD_SET(r->sock,&set);
+       tv.tv_sec  = HEARTBEAT_INTERVAL * 4 / 1000;
+       tv.tv_usec = 0;
+       switch (select(r->sock+1,&set,NULL,NULL,&tv)) {
+       case -1:
+           perror("select");
+           return -1;
+       case 0:
+           fprintf(stderr,"select: timeout\n");
+           return -1;
+       }
+       
+       if (FD_ISSET(0,&set)) {
+           /* stdin has data */
+           rc = read(0,buf,BUFSIZE);
+           switch (rc) {
+           case -1:
+               perror("read(stdin)");
+               return -1;
+           case 0:
+               fprintf(stderr,"EOF from stdin\n");
+               return -1;
+           default:
+               if (buf[0] == 0x1d) {
+                   if (r->verbose)
+                       fprintf(stderr, "\n" APPNAME ": saw ^], exiting\n");
+                   redir_sol_stop(r);
+               }
+               if (-1 == redir_sol_send(r, buf, rc))
+                   return -1;
+               break;
+           }
+       }
+
+       if (FD_ISSET(r->sock,&set)) {
+           if (-1 == redir_data(r))
+               return -1;
+       }
+    }
+    return 0;
+}
+
+/* ------------------------------------------------------------------ */
+
+struct termios  saved_attributes;
+int             saved_fl;
+
+static void tty_save(void)
+{
+    fcntl(0,F_GETFL,&saved_fl);
+    tcgetattr (0, &saved_attributes);
+}
+
+static void tty_noecho(void)
+{
+    struct termios tattr;
+    
+    memcpy(&tattr,&saved_attributes,sizeof(struct termios));
+    tattr.c_lflag &= ~(ECHO);
+    tcsetattr (0, TCSAFLUSH, &tattr);
+}
+
+static void tty_raw(void)
+{
+    struct termios tattr;
+    
+    fcntl(0,F_SETFL,O_NONBLOCK);
+    memcpy(&tattr,&saved_attributes,sizeof(struct termios));
+    tattr.c_lflag &= ~(ISIG|ICANON|ECHO);
+    tattr.c_cc[VMIN] = 1;
+    tattr.c_cc[VTIME] = 0;
+    tcsetattr (0, TCSAFLUSH, &tattr);
+}
+
+static void tty_restore(void)
+{
+    fcntl(0,F_SETFL,saved_fl);
+    tcsetattr (0, TCSANOW, &saved_attributes);
+}
+
+/* ------------------------------------------------------------------ */
+
+static void usage(FILE *fp)
+{
+    fprintf(fp,
+            "\n"
+           "This is " APPNAME ", release " VERSION ", I'll establish\n"
+           "serial-over-lan (sol) connections to your Intel AMT boxes.\n"
+            "\n"
+            "usage: " APPNAME " [options] host [port]\n"
+            "options:\n"
+            "   -h            print this text\n"
+            "   -v            verbose (default)\n"
+            "   -q            quiet\n"
+            "   -u user       username (default: admin)\n"
+            "   -p pass       password (default: $AMT_PASSWORD)\n"
+            "\n"
+            "By default port 16994 is used.\n"
+           "If no password is given " APPNAME " will ask for one.\n"
+            "\n"
+            "-- \n"
+            "(c) 2007 Gerd Hoffmann <kraxel@redhat.com>\n"
+           "\n");
+}
+
+int main(int argc, char *argv[])
+{
+    struct redir r;
+    char *h;
+    int c;
+
+    memset(&r, 0, sizeof(r));
+    r.verbose = 1;
+    memcpy(r.type, "SOL ", 4);
+    strcpy(r.user, "admin");
+
+    r.cb_data  = &r;
+    r.cb_recv  = recv_tty;
+    r.cb_state = state_tty;
+
+    if (NULL != (h = getenv("AMT_PASSWORD")))
+       snprintf(r.pass, sizeof(r.pass), "%s", h);
+
+    for (;;) {
+        if (-1 == (c = getopt(argc, argv, "hvqu:p:")))
+            break;
+        switch (c) {
+       case 'v':
+           r.verbose = 1;
+           break;
+       case 'q':
+           r.verbose = 0;
+           break;
+       case 'u':
+           snprintf(r.user, sizeof(r.user), "%s", optarg);
+           break;
+       case 'p':
+           snprintf(r.pass, sizeof(r.pass), "%s", optarg);
+           memset(optarg,'*',strlen(optarg)); /* rm passwd from ps list */
+           break;
+
+        case 'h':
+            usage(stdout);
+            exit(0);
+        default:
+            usage(stderr);
+            exit(1);
+        }
+    }
+
+    if (optind < argc)
+       snprintf(r.host, sizeof(r.host), "%s", argv[optind]);
+    if (optind+1 < argc)
+       snprintf(r.port, sizeof(r.port), "%s", argv[optind+1]);
+    if (0 == strlen(r.host)) {
+       usage(stderr);
+       exit(1);
+    }
+
+    tty_save();
+    if (0 == strlen(r.pass)) {
+       tty_noecho();
+       fprintf(stderr, "AMT password for host %s: ", r.host);
+       fgets(r.pass, sizeof(r.pass), stdin);
+       fprintf(stderr, "\n");
+       if (NULL != (h = strchr(r.pass, '\r')))
+           *h = 0;
+       if (NULL != (h = strchr(r.pass, '\n')))
+           *h = 0;
+    }
+
+    if (-1 == redir_connect(&r)) {
+       tty_restore();
+       exit(1);
+    }
+
+    tty_raw();
+    redir_start(&r);
+    redir_loop(&r);
+    tty_restore();
+    
+    exit(0);
+}
diff --git a/amtterm.man b/amtterm.man
new file mode 100644 (file)
index 0000000..ee99dd7
--- /dev/null
@@ -0,0 +1,43 @@
+.TH amtterm 1 "(c) 2007 Gerd Hoffmann"
+.SH NAME
+amtterm - Intel AMT serial-over-lan (sol) client.
+.SH SYNOPSIS
+.B amtterm [ options ] host [ port ]
+.SH DESCRIPTION
+.B amtterm
+provides access to the serial-over-lan port of Intel AMT managed
+machines.
+.B host
+is the hostname or IP address of the machine amtterm should connect
+to.
+.B port
+is the tcp port to use and defaults to 16994 (standard AMT redirection
+port) if unspecified.
+.P
+For more inforamtions on Intel AMT check amt-howto(7).
+.SH OPTIONS
+.TP
+.B -h
+Display help text.
+.TP
+.B -v
+Be verbose (default).
+.TP
+.B -q
+Be quiet.
+.TP
+.B -u <user>
+Specify username, defaults to "admin".
+.TP
+.B -p <pass>
+Specify password.
+.B amtterm
+will prompt on the terminal if unspecified.
+.SH ENVIRONMENT
+.TP
+.B AMT_PASSWORD
+Default value for the password.
+.SH SEE ALSO
+gamt(1), amttool(1), amt-howto(7)
+.SH AUTHOR
+(c) 2007 Gerd Hoffmann <kraxel@redhat.com>
diff --git a/amttool b/amttool
new file mode 100755 (executable)
index 0000000..d1b0f48
--- /dev/null
+++ b/amttool
@@ -0,0 +1,375 @@
+#!/usr/bin/perl
+use strict;
+use warnings;
+use SOAP::Lite;
+#use SOAP::Lite +trace => 'all';
+
+my $amt_host = shift;
+my $amt_port = "16992";
+$main::amt_user = "admin";
+$main::amt_pass = $ENV{'AMT_PASSWORD'};
+my $amt_debug = 0;
+$amt_debug = $ENV{'AMT_DEBUG'} if defined($ENV{'AMT_DEBUG'});
+
+my $amt_command = shift;
+$amt_command = "info" if !defined($amt_command);
+
+my $amt_version;
+
+#############################################################################
+# data
+
+my @ps = ("S0", "S1", "S2", "S3", "S4", "S5 (soft-off)", "S4/S5", "Off");
+my %rcc = (
+       "reset"      => "16",
+       "powerup"    => "17",
+       "powerdown"  => "18",
+       "powercycle" => "19",
+);
+
+# incomplete list
+my %pt_status = (
+       0x0  =>  "success",
+       0x1  =>  "internal error",
+       0x3  =>  "invalid pt_mode",
+       0xc  =>  "invalid name",
+       0xf  =>  "invalid byte_count",
+       0x10  =>  "not permitted",
+       0x17  =>  "max limit_reached",
+       0x18  =>  "invalid auth_type",
+       0x1a  =>  "invalid dhcp_mode",
+       0x1b  =>  "invalid ip_address",
+       0x1c  =>  "invalid domain_name",
+       0x20  =>  "invalid provisioning_state",
+       0x22  =>  "invalid time",
+       0x23  =>  "invalid index",
+       0x24  =>  "invalid parameter",
+       0x25  =>  "invalid netmask",
+       0x26  =>  "flash write_limit_exceeded",
+       0x800  =>  "network if_error_base",
+       0x801  =>  "unsupported oem_number",
+       0x802  =>  "unsupported boot_option",
+       0x803  =>  "invalid command",
+       0x804  =>  "invalid special_command",
+       0x805  =>  "invalid handle",
+       0x806  =>  "invalid password",
+       0x807  =>  "invalid realm",
+       0x808  =>  "storage acl_entry_in_use",
+       0x809  =>  "data missing",
+       0x80a  =>  "duplicate",
+       0x80b  =>  "eventlog frozen",
+       0x80c  =>  "pki missing_keys",
+       0x80d  =>  "pki generating_keys",
+       0x80e  =>  "invalid key",
+       0x80f  =>  "invalid cert",
+       0x810  =>  "cert key_not_match",
+       0x811  =>  "max kerb_domain_reached",
+       0x812  =>  "unsupported",
+       0x813  =>  "invalid priority",
+       0x814  =>  "not found",
+       0x815  =>  "invalid credentials",
+       0x816  =>  "invalid passphrase",
+       0x818  =>  "no association",
+);
+
+
+#############################################################################
+# soap setup
+
+my ($nas, $sas, $rcs);
+
+sub SOAP::Transport::HTTP::Client::get_basic_credentials {
+       return $main::amt_user => $main::amt_pass;
+}
+
+sub soap_init() {
+       my $proxybase = "http://$amt_host:$amt_port";
+       my $schemabase = "http://schemas.intel.com/platform/client";
+
+       $nas = SOAP::Lite->new(
+               proxy      => "$proxybase/NetworkAdministrationService",
+               default_ns => "$schemabase/NetworkAdministration/2004/01");
+       $sas = SOAP::Lite->new(
+               proxy      => "$proxybase/SecurityAdministrationService",
+               default_ns => "$schemabase/SecurityAdministration/2004/01");
+       $rcs = SOAP::Lite->new(
+               proxy      => "$proxybase/RemoteControlService",
+               default_ns => "$schemabase/RemoteControl/2004/01");
+
+       $nas->autotype(0);
+       $sas->autotype(0);
+       $rcs->autotype(0);
+
+       $amt_version = $sas->GetCoreVersion()->paramsout;
+}
+
+
+#############################################################################
+# functions
+
+sub usage() {
+       print STDERR <<EOF;
+
+This utility can talk to Intel AMT managed machines.
+
+usage: amttool <hostname> [ <command> ]
+commands:
+   info            - print some machine info (default).
+   reset           - reset machine.
+   powerup         - turn on machine.
+   powerdown       - turn off machine.
+   powercycle      - powercycle machine.
+
+AMT 2.5+ only:
+   netinfo         - print network config.
+   netconf <args>  - configure network (check manpage).
+
+Password is passed via AMT_PASSWORD environment variable.
+
+EOF
+}
+
+sub print_result($) {
+       my $ret = shift;
+       my $rc = $ret->result;
+       my $msg;
+
+       if (!defined($rc)) {
+               $msg = "soap failure";
+       } elsif (!defined($pt_status{$rc})) {
+               $msg = sprintf("unknown pt_status code: 0x%x", $rc);
+       } else {
+               $msg = "pt_status: " . $pt_status{$rc};
+       }
+       printf "result: %s\n", $msg;
+}
+
+sub print_paramsout($) {
+       my $ret = shift;
+       my @paramsout = $ret->paramsout;
+       print "params: " . join(", ", @paramsout) . "\n";
+}
+
+sub print_hash {
+       my $hash = shift;
+       my $in = shift;
+       my $wi = shift;
+
+       foreach my $item (sort keys %{$hash}) {
+               if (ref($hash->{$item}) eq "HASH") {
+#                      printf "%*s%s\n", $in, "", $item;
+                       next;
+               }
+               printf "%*s%-*s%s\n", $in, "", $wi, $item, $hash->{$item};
+       }
+}
+
+sub print_hash_ipv4 {
+       my $hash = shift;
+       my $in = shift;
+       my $wi = shift;
+
+       foreach my $item (sort keys %{$hash}) {
+               my $addr = sprintf("%d.%d.%d.%d",
+                       $hash->{$item} / 256 / 256 / 256,
+                       $hash->{$item} / 256 / 256 % 256,
+                       $hash->{$item} / 256 % 256,
+                       $hash->{$item} % 256);
+               printf "%*s%-*s%s\n", $in, "", $wi, $item, $addr;
+       }
+}
+
+sub do_soap {
+       my $soap = shift;
+       my $name = shift;
+       my @args = @_;
+       my $method;
+
+       $method = SOAP::Data->name($name)
+                           ->attr( { xmlns => $soap->ns } );
+
+       if ($amt_debug) {
+               print "-- \n";
+               open XML, "| xmllint --format -";
+               print XML $soap->serializer->envelope(method => $method, @_);
+               close XML;
+               print "-- \n";
+       }
+
+       my $ret = $soap->call($method, @args);
+       print_result($ret);
+       return $ret;
+}
+
+sub check_amt_version {
+       my $major = shift;
+       my $minor = shift;
+
+       $amt_version =~ m/^(\d+).(\d+)/;
+       return if $1 > $major;
+       return if $1 == $major && $2 >= $minor;
+       die "version mismatch (need >= $major.$minor, have $amt_version)";
+}
+
+sub print_general_info() {
+       printf "### AMT info on machine '%s' ###\n", $amt_host;
+
+       printf "AMT version:  %s\n", $amt_version;
+       
+       my $hostname = $nas->GetHostName()->paramsout;
+       my $domainname = $nas->GetDomainName()->paramsout;
+       printf "Hostname:     %s.%s\n", $hostname, $domainname;
+
+       my $powerstate = $rcs->GetSystemPowerState()->paramsout;
+       printf "Powerstate:   %s\n", $ps [ $powerstate & 0x0f ];
+}
+       
+sub print_remote_info() {
+       my @rccaps = $rcs->GetRemoteControlCapabilities()->paramsout;
+       printf "Remote Control Capabilities:\n";
+       printf "    IanaOemNumber                   %x\n", $rccaps[0];
+       printf "    OemDefinedCapabilities          %s%s%s%s%s\n",
+               $rccaps[1] & 0x01 ? "IDER "        : "",
+               $rccaps[1] & 0x02 ? "SOL "         : "",
+               $rccaps[1] & 0x04 ? "BiosReflash " : "",
+               $rccaps[1] & 0x08 ? "BiosSetup "   : "",
+               $rccaps[1] & 0x10 ? "BiosPause "   : "";
+
+       printf "    SpecialCommandsSupported        %s%s%s%s%s\n",
+               $rccaps[2] & 0x0100 ? "PXE-boot "         : "",
+               $rccaps[2] & 0x0200 ? "HD-boot "          : "",
+               $rccaps[2] & 0x0400 ? "HD-boot-safemode " : "",
+               $rccaps[2] & 0x0800 ? "diag-boot "        : "",
+               $rccaps[2] & 0x1000 ? "cd-boot "          : "";
+
+       printf "    SystemCapabilitiesSupported     %s%s%s%s\n",
+               $rccaps[3] & 0x01 ? "powercycle " : "",
+               $rccaps[3] & 0x02 ? "powerdown "  : "",
+               $rccaps[3] & 0x03 ? "powerup "    : "",
+               $rccaps[3] & 0x04 ? "reset "      : "";
+
+       printf "    SystemFirmwareCapabilities      %x\n", $rccaps[4];
+}
+
+sub print_network_info() {
+       my $ret;
+
+       $ret = $nas->EnumerateInterfaces();
+       my @if = $ret->paramsout;
+       foreach my $if (@if) {
+               printf "Network Interface %s:\n", $if;
+               my $arg = SOAP::Data->name('InterfaceHandle' => $if);
+               $ret = $nas->GetInterfaceSettings($arg);
+               my $desc = $ret->paramsout;
+               print_hash($ret->paramsout, 4, 32);
+               print_hash_ipv4($ret->paramsout->{'IPv4Parameters'}, 8, 28);
+       }
+}
+
+sub remote_control($) {
+       my $command = shift;
+       my @args;
+
+       my $hostname = $nas->GetHostName()->paramsout;
+       my $domainname = $nas->GetDomainName()->paramsout;
+       printf "host %s.%s, %s [y/N] ? ", $hostname, $domainname, $command;
+       my $reply = <>;
+       if ($reply =~ m/^(y|yes)$/i) {
+               printf "execute: %s\n", $command;
+               push (@args, SOAP::Data->name('Command' => $rcc{$command}));
+               push (@args, SOAP::Data->name('IanaOemNumber' => 4542));
+               do_soap($rcs, "RemoteControl", @args);
+       } else {
+               printf "canceled\n";
+       }
+}
+
+sub ipv4_addr($$) {
+       my $name = shift;
+       my $ipv4 = shift;
+
+       $ipv4 =~ m/(\d+).(\d+).(\d+).(\d+)/ or die "parse ipv4 address: $ipv4";
+       my $num = $1 * 256 * 256 * 256 +
+               $2 * 256 * 246 +
+               $3 * 256 +
+               $4;
+       printf STDERR "ipv4 %-24s: %-16s -> %d\n", $name, $ipv4, $num
+               if $amt_debug;
+       return SOAP::Data->name($name => $num);
+}
+
+sub configure_network {
+       my $if = shift;
+       my $link = shift;
+       my $ip = shift;
+       my $mask = shift;
+       my $gw = shift;
+       my $dns1 = shift;
+       my $dns2 = shift;
+
+       my $mode;
+       my @ifdesc;
+       my @ipv4;
+
+       my $method;
+       my @args;
+
+       # build argument structs ...
+       die "no interface" if !defined($if);
+       die "no linkpolicy" if !defined($link);
+       if (defined($ip)) {
+               $mode = "SEPARATE_MAC_ADDRESS";
+               die "no ip mask"     if !defined($mask);
+               die "no default gw"  if !defined($gw);
+               $dns1 = $gw          if !defined($dns1);
+               $dns2 = "0.0.0.0"    if !defined($dns2);
+               push (@ipv4, ipv4_addr("LocalAddress", $ip));
+               push (@ipv4, ipv4_addr("SubnetMask", $mask));
+               push (@ipv4, ipv4_addr("DefaultGatewayAddress", $gw));
+               push (@ipv4, ipv4_addr("PrimaryDnsAddress", $dns1));
+               push (@ipv4, ipv4_addr("SecondaryDnsAddress", $dns2));
+       } else {
+               $mode = "SHARED_MAC_ADDRESS";
+               # no ip info -- use DHCP
+       }
+
+       push (@ifdesc, SOAP::Data->name("InterfaceMode" => $mode));
+       push (@ifdesc, SOAP::Data->name("LinkPolicy" => $link));
+       push (@ifdesc, SOAP::Data->name("IPv4Parameters" => 
+               \SOAP::Data->value(@ipv4)))
+                       if @ipv4;
+
+       push (@args, SOAP::Data->name("InterfaceHandle" => $if));
+       push (@args, SOAP::Data->name("InterfaceDescriptor" =>
+               \SOAP::Data->value(@ifdesc)));
+
+       # perform call
+       do_soap($nas, "SetInterfaceSettings", @args);
+}
+
+
+#############################################################################
+# main
+
+if (!defined($amt_host)) {
+       usage();
+       exit 1;
+}
+
+soap_init;
+
+if ($amt_command eq "info") {
+       print_general_info;
+       print_remote_info;
+} elsif ($amt_command eq "netinfo") {
+       check_amt_version(2,5);
+       print_network_info;
+} elsif ($amt_command eq "netconf") {
+       check_amt_version(2,5);
+       configure_network(@ARGV);
+} elsif ($amt_command =~ m/^(reset|powerup|powerdown|powercycle)$/) {
+       remote_control($amt_command);
+} else {
+       print "unknown command: $amt_command\n";
+}
+
diff --git a/amttool.man b/amttool.man
new file mode 100644 (file)
index 0000000..e687881
--- /dev/null
@@ -0,0 +1,72 @@
+.TH amttool 1 "(c) 2007 Gerd Hoffmann"
+.SH NAME
+amttool - remotely control Intel AMT managed machines.
+.SH SYNOPSIS
+.B amttool host [ command ]
+.SH DESCRIPTION
+.B amttool
+is a perl script which speaks SOAP to Intel AMT managed machines.
+It can query informations about the machine in question and also
+send some commands for basic remote control.
+.P
+.B host
+is the hostname or IP address of the machine amttool should
+control.
+.B command
+is an optional command.
+.P
+You must set fill AMT_PASSWORD environment variable with the AMT
+password.
+.P
+For more inforamtions on Intel AMT check amt-howto(7).
+.SH COMMANDS
+.TP
+.B info
+gather information (default).
+.TP
+.B reset
+reset machine.
+.TP
+.B powerup
+turn on machine.
+.TP
+.B powerdown
+turn off machine.
+.TP
+.B powercycle
+powercycle machine.
+.TP
+.B netinfo
+print network configuration (requires AMT 2.5+).
+.TP
+.B netconf if link [ ip mask gw [ dns1 [ dns2 ]]]
+configure network interface (requires AMT 2.5+).
+.B if
+is the interface handle,
+.B link
+the link policy.  If in doubt just feed in what the netinfo command
+prints.
+.B ip
+is the IPv4 address,
+.B mask
+the netmask,
+.B gw
+the default gateway,
+.B dns1
+and
+.B dns2
+are the DNS Servers.  If no IP configuration is specified the tool
+tries to configure the machine in shared mac address mode with dhcp,
+otherwise in separate mac address mode with static IP address.
+Default for dns1 is the gateway address, for dns2 it is 0.0.0.0.
+.SH ENVIRONMENT
+.TP
+.B AMT_PASSWORD
+AMT password.
+.TP
+.B AMT_DEBUG
+Enable debug output.
+.SH SEE ALSO
+amtterm(1), gamt(1), amt-howto(7)
+.SH AUTHOR
+(c) 2007 Gerd Hoffmann <kraxel@redhat.com>
diff --git a/gamt.c b/gamt.c
new file mode 100644 (file)
index 0000000..e883c3d
--- /dev/null
+++ b/gamt.c
@@ -0,0 +1,834 @@
+/*
+ *  amtterm -- Intel AMT serial-over-lan client, gtk version.
+ *
+ *  Copyright (C) 2007 Gerd Hoffmann <kraxel@redhat.com
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License along
+ *  with this program; if not, write to the Free Software Foundation, Inc.,
+ *  51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <locale.h>
+#include <signal.h>
+#include <ctype.h>
+
+#include <gdk/gdk.h>
+#include <gdk/gdkx.h>
+#include <gtk/gtk.h>
+#include <vte/vte.h>
+
+#include "parseconfig.h"
+#include "redir.h"
+
+#define APPNAME "gamt"
+
+struct gamt_window {
+    /* gtk stuff */
+    GtkActionGroup *ag;
+    GtkUIManager   *ui;
+    GtkWidget      *win;
+    GtkWidget      *vte;
+    GtkWidget      *status;
+    GtkWidget      *icon;
+
+    GtkActionGroup *hosts_ag;
+    guint          hosts_id;
+
+    /* sol stuff */
+    struct redir   redir;
+    GIOChannel     *ch;
+    guint          id;
+};
+
+static const char *state_stock[] = {
+    [ REDIR_NONE      ] = GTK_STOCK_DISCONNECT,
+#if 0
+    [ REDIR_CONNECT   ] = GTK_STOCK_,
+    [ REDIR_INIT      ] = GTK_STOCK_,
+    [ REDIR_AUTH      ] = GTK_STOCK_,
+    [ REDIR_INIT_SOL  ] = GTK_STOCK_,
+#endif
+    [ REDIR_RUN_SOL   ] = GTK_STOCK_CONNECT,
+#if 0
+    [ REDIR_INIT_IDER ] = GTK_STOCK_,
+    [ REDIR_RUN_IDER  ] = GTK_STOCK_,
+    [ REDIR_CLOSING   ] = GTK_STOCK_,
+#endif
+    [ REDIR_CLOSED    ] = GTK_STOCK_DISCONNECT,
+    [ REDIR_ERROR     ] = GTK_STOCK_DISCONNECT,
+};
+
+static char amt_host[64];
+static char amt_port[16];
+static char amt_user[32] = "admin";
+static char amt_pass[32];
+static int amt_trace;
+static int amt_debug;
+
+static int gamt_getstring(GtkWidget *window, char *title, char *message,
+                         char *dest, int dlen, int hide);
+static int gamt_connect(struct gamt_window *gamt);
+static void gamt_rebuild_hosts(struct gamt_window *gamt);
+
+/* ------------------------------------------------------------------ */
+
+#define CFG_SECTION       "config", "config"
+#define CFG_FONT          CFG_SECTION, "font"
+#define CFG_FOREGROUND    CFG_SECTION, "foreground"
+#define CFG_BACKGROUND    CFG_SECTION, "background"
+#define CFG_BLINK         CFG_SECTION, "blink-cursor"
+
+/* ------------------------------------------------------------------ */
+
+static void menu_cb_connect(GtkAction *action, void *data)
+{
+    struct gamt_window *gamt = data;
+    int rc;
+
+    if (gamt->redir.state != REDIR_NONE &&
+       gamt->redir.state != REDIR_CLOSED &&
+       gamt->redir.state != REDIR_ERROR)
+       /* already have an active connection */
+       return;
+
+    rc = gamt_getstring(gamt->win, "Connecting",
+                       "Connect to host ?",
+                       amt_host, sizeof(amt_host), 0);
+    if (0 != rc)
+       return;
+
+    gamt_connect(gamt);
+}
+
+static void menu_cb_connect_to(GtkAction *action, void *data)
+{
+    struct gamt_window *gamt = data;
+    
+    if (gamt->redir.state != REDIR_NONE &&
+       gamt->redir.state != REDIR_CLOSED &&
+       gamt->redir.state != REDIR_ERROR)
+       /* already have an active connection */
+       return;
+
+    if (1 != sscanf(gtk_action_get_name(action), "ConnectTo_%s", amt_host))
+       return;
+    gamt_connect(gamt);
+}
+
+static void menu_cb_disconnect(GtkAction *action, void *data)
+{
+    struct gamt_window *gamt = data;
+
+    if (gamt->redir.state != REDIR_RUN_SOL)
+       return;
+    redir_sol_stop(&gamt->redir);
+}
+
+static void menu_cb_config_font(GtkAction *action, void *data)
+{
+    struct gamt_window *gamt = data;
+    GtkWidget *dialog;
+    char *fontname;
+
+    dialog = gtk_font_selection_dialog_new("Terminal font");
+    fontname = cfg_get_str(CFG_FONT);
+    gtk_font_selection_dialog_set_font_name
+       (GTK_FONT_SELECTION_DIALOG(dialog), fontname);
+
+    gtk_widget_show_all(dialog);
+    switch (gtk_dialog_run(GTK_DIALOG(dialog))) {
+    case GTK_RESPONSE_OK:
+       fontname = gtk_font_selection_dialog_get_font_name
+           (GTK_FONT_SELECTION_DIALOG(dialog));
+       vte_terminal_set_font_from_string(VTE_TERMINAL(gamt->vte), fontname);
+       cfg_set_str(CFG_FONT, fontname);
+       break;
+    }
+    gtk_widget_destroy(dialog);
+}
+
+static int pickcolor(char *title, GdkColor *color)
+{
+    GtkWidget *dialog;
+    GtkColorSelection *csel;
+    int rc = -1;
+
+    dialog = gtk_color_selection_dialog_new(title);
+    csel = GTK_COLOR_SELECTION(GTK_COLOR_SELECTION_DIALOG(dialog)->colorsel);
+    gtk_color_selection_set_has_opacity_control(csel, FALSE);
+    gtk_color_selection_set_current_color(csel, color);
+    
+    gtk_widget_show_all(dialog);
+    switch (gtk_dialog_run(GTK_DIALOG(dialog))) {
+    case GTK_RESPONSE_OK:
+       gtk_color_selection_get_current_color(csel, color);
+       rc = 0;
+    }
+    gtk_widget_destroy(dialog);
+    return rc;
+}
+
+static void menu_cb_config_fg(GtkAction *action, void *data)
+{
+    struct gamt_window *gamt = data;
+    GdkColor color = {0,0,0,0};
+    char name[16];
+
+    gdk_color_parse(cfg_get_str(CFG_FOREGROUND), &color);
+    if (0 != pickcolor("Text color", &color))
+       return;
+    vte_terminal_set_color_foreground(VTE_TERMINAL(gamt->vte), &color);
+    snprintf(name, sizeof(name), "#%04x%04x%04x",
+            color.red, color.green, color.blue);
+    cfg_set_str(CFG_FOREGROUND, name);
+}
+
+static void menu_cb_config_bg(GtkAction *action, void *data)
+{
+    struct gamt_window *gamt = data;
+    GdkColor color = {0,0,0,0};
+    char name[16];
+
+    gdk_color_parse(cfg_get_str(CFG_BACKGROUND), &color);
+    if (0 != pickcolor("Background color", &color))
+       return;
+    vte_terminal_set_color_background(VTE_TERMINAL(gamt->vte), &color);
+    snprintf(name, sizeof(name), "#%04x%04x%04x",
+            color.red, color.green, color.blue);
+    cfg_set_str(CFG_BACKGROUND, name);
+}
+
+static void menu_cb_blink_cursor(GtkToggleAction *action, gpointer user_data)
+{
+    struct gamt_window *gamt = user_data;
+    gboolean state = gtk_toggle_action_get_active(action);
+
+    if (amt_debug)
+       fprintf(stderr, "%s: %s\n", __FUNCTION__, state ? "on" : "off");
+    cfg_set_bool(CFG_BLINK, state);
+    vte_terminal_set_cursor_blinks(VTE_TERMINAL(gamt->vte), state);
+}
+
+static void menu_cb_quit(GtkAction *action, void *data)
+{
+    struct gamt_window *gamt = data;
+
+    gtk_widget_destroy(gamt->win);
+}
+
+static void show_manpage(char *page, char *section)
+{
+    char buf[64];
+
+
+    switch(fork()) {
+    case -1:
+       perror("fork");
+       break;
+    case 0:
+       /* child: try xdg-open first ... */
+       snprintf(buf, sizeof(buf), "man:%s(%s)", page, section);
+       execlp("xdg-open", "xdg-open", buf, NULL);
+       perror("execlp(xdg-open)");
+       /* ... fallback is an xterm with man */
+       snprintf(buf, sizeof(buf), "manual page: %s(%s)", page, section);
+       execlp("xterm", "xterm",
+              "-title", buf, 
+              "-e", "man", section, page,
+              NULL);
+       perror("execlp(xterm)");
+       exit(1);
+       break;
+    default:
+       /* parent */
+       break;
+    }
+}
+
+static void menu_cb_man_gamt(GtkAction *action, void *data)
+{
+    show_manpage("gamt", "1");
+}
+
+static void menu_cb_man_amt_howto(GtkAction *action, void *data)
+{
+    show_manpage("amt-howto", "7");
+}
+
+static void menu_cb_about(GtkAction *action, void *data)
+{
+    static char *comments = "Intel AMT serial-over-lan client";
+    static char *copyright = "(c) 2007 Gerd Hoffmann";
+    static char *website = "http://dl.bytesex.org/releases/amtterm/";
+    static char *authors[] = { "Gerd Hoffmann <kraxel@redhat.com>", NULL };
+    struct gamt_window *gamt = data;
+
+    gtk_show_about_dialog(GTK_WINDOW(gamt->win),
+                          "authors",         authors,
+                          "comments",        comments,
+                          "copyright",       copyright,
+                          "logo-icon-name",  GTK_STOCK_ABOUT,
+                          "version",         VERSION,
+                         "website",         website,
+//                       "license",         "GPLv2+",
+                          NULL);
+}
+
+static void destroy_cb(GtkWidget *widget, gpointer data)
+{
+    struct gamt_window *gamt = data;
+
+    gtk_main_quit();
+    free(gamt);
+}
+
+/* ------------------------------------------------------------------ */
+
+static int recv_gtk(void *cb_data, unsigned char *buf, int len)
+{
+    struct gamt_window *gamt = cb_data;
+    vte_terminal_feed(VTE_TERMINAL(gamt->vte), buf, len);
+    return 0;
+}
+
+static void state_gtk(void *cb_data, enum redir_state old, enum redir_state new)
+{
+    struct gamt_window *gamt = cb_data;
+    unsigned char buf[128];
+    int last;
+
+    switch (new) {
+    case REDIR_ERROR:
+#if 0
+       snprintf(buf, sizeof(buf), "%s: %s FAILED (%s)", gamt->redir.host,
+                redir_state_desc(old), gamt->redir.err);
+#else
+       snprintf(buf, sizeof(buf), "%s: ERROR: %s", gamt->redir.host,
+                gamt->redir.err);
+#endif
+       if (old == REDIR_AUTH) {
+           /* ask for a new password next time ... */
+           strcpy(amt_pass, "");
+       }
+       break;
+    case REDIR_RUN_SOL:
+       last = cfg_get_int("config", "hosts", gamt->redir.host, 0);
+       cfg_set_int("config", "hosts", gamt->redir.host, time(NULL));
+       if (!last)
+           gamt_rebuild_hosts(gamt);
+       /* fall through */
+    default:
+       snprintf(buf, sizeof(buf), "%s: %s", gamt->redir.host,
+                redir_state_desc(new));
+       break;
+    }
+    if (state_stock[new])
+       gtk_image_set_from_stock(GTK_IMAGE(gamt->icon), state_stock[new],
+                                GTK_ICON_SIZE_SMALL_TOOLBAR);
+    gtk_label_set_text(GTK_LABEL(gamt->status), buf);
+}
+
+static void user_input(VteTerminal *vte, gchar *buf, guint len,
+                      gpointer data)
+{
+    struct gamt_window *gamt = data;
+
+    if (gamt->redir.state == REDIR_RUN_SOL)
+       redir_sol_send(&gamt->redir, buf, len);
+}
+
+/* ------------------------------------------------------------------ */
+
+static const GtkActionEntry entries[] = {
+    {
+       .name        = "FileMenu",
+       .label       = "_File",
+    },{
+       .name        = "ConfigMenu",
+       .label       = "_Options",
+    },{
+       .name        = "HostMenu",
+       .label       = "Ho_sts",
+    },{
+       .name        = "HelpMenu",
+       .label       = "_Help",
+    },{
+
+       /* File menu */
+       .name        = "Connect",
+       .stock_id    = GTK_STOCK_CONNECT,
+       .label       = "_Connect ...",
+       .callback    = G_CALLBACK(menu_cb_connect),
+    },{
+       .name        = "Disconnect",
+       .stock_id    = GTK_STOCK_DISCONNECT,
+       .label       = "_Disconnect",
+       .callback    = G_CALLBACK(menu_cb_disconnect),
+    },{
+       .name        = "Quit",
+       .stock_id    = GTK_STOCK_QUIT,
+       .label       = "_Quit",
+       .callback    = G_CALLBACK(menu_cb_quit),
+    },{
+
+       /* Config menu */
+       .name        = "VteFont",
+       .stock_id    = GTK_STOCK_SELECT_FONT,
+       .label       = "Terminal _font ...",
+       .callback    = G_CALLBACK(menu_cb_config_font),
+    },{
+       .name        = "VteForeground",
+//     .stock_id    = GTK_STOCK_SELECT_COLOR,
+       .label       = "_Text Color ...",
+       .callback    = G_CALLBACK(menu_cb_config_fg),
+    },{
+       .name        = "VteBackground",
+       .label       = "_Background Color ...",
+       .callback    = G_CALLBACK(menu_cb_config_bg),
+    },{
+
+       /* Help menu */
+       .name        = "ManGamt1",
+       .stock_id    = GTK_STOCK_HELP,
+       .label       = "Manual Page",
+       .callback    = G_CALLBACK(menu_cb_man_gamt),
+    },{
+       .name        = "ManAmtHowto7",
+       .stock_id    = GTK_STOCK_INFO,
+       .label       = "AMT HowTo",
+       .callback    = G_CALLBACK(menu_cb_man_amt_howto),
+    },{
+       .name        = "About",
+       .stock_id    = GTK_STOCK_ABOUT,
+       .label       = "_About ...",
+       .callback    = G_CALLBACK(menu_cb_about),
+    }
+};
+
+static const GtkToggleActionEntry tentries[] = {
+    {
+       .name        = "BlinkCursor",
+       .label       = "Blinking cursor",
+       .callback    = G_CALLBACK(menu_cb_blink_cursor),
+    }
+};
+
+static char ui_xml[] =
+"<ui>\n"
+"  <menubar action='MainMenu'>\n"
+"    <menu action='FileMenu'>\n"
+"      <menuitem action='Connect'/>\n"
+"      <menuitem action='Disconnect'/>\n"
+"      <separator/>\n"
+"      <menuitem action='Quit'/>\n"
+"    </menu>\n"
+"    <menu action='HostMenu'>\n"
+"    </menu>\n"
+"    <menu action='ConfigMenu'>\n"
+"      <menuitem action='VteFont'/>\n"
+"      <menuitem action='VteForeground'/>\n"
+"      <menuitem action='VteBackground'/>\n"
+"      <separator/>\n"
+"      <menuitem action='BlinkCursor'/>\n"
+"    </menu>\n"
+"    <menu action='HelpMenu'>\n"
+"      <menuitem action='ManGamt1'/>\n"
+"      <menuitem action='ManAmtHowto7'/>\n"
+"      <separator/>\n"
+"      <menuitem action='About'/>\n"
+"    </menu>\n"
+"  </menubar>\n"
+"  <toolbar action='ToolBar'>\n"
+"    <toolitem action='Quit'/>\n"
+"    <toolitem action='Connect'/>\n"
+"    <toolitem action='Disconnect'/>\n"
+"  </toolbar>\n"
+"</ui>\n";
+
+static char hosts_xml_start[] =
+"<ui>\n"
+"  <menubar name='MainMenu'>\n"
+"    <menu action='HostMenu'>\n";
+
+static char hosts_xml_end[] =
+"    </menu>\n"
+"  </menubar>\n"
+"</ui>\n";
+
+/* ------------------------------------------------------------------ */
+
+static int gamt_getstring(GtkWidget *window, char *title, char *message,
+                         char *dest, int dlen, int hide)
+{
+    GtkWidget *dialog, *label, *entry;
+    const char *txt;
+    int retval;
+   
+    /* Create the widgets */
+    dialog = gtk_dialog_new_with_buttons(title,
+                                        GTK_WINDOW(window),
+                                         GTK_DIALOG_DESTROY_WITH_PARENT,
+                                        GTK_STOCK_OK,
+                                        GTK_RESPONSE_ACCEPT,
+                                        GTK_STOCK_CANCEL,
+                                        GTK_RESPONSE_REJECT,
+                                         NULL);
+    gtk_dialog_set_default_response(GTK_DIALOG(dialog), GTK_RESPONSE_ACCEPT);
+    
+    label = gtk_label_new(message);
+    gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
+
+    entry = gtk_entry_new();
+    gtk_entry_set_text(GTK_ENTRY(entry), dest);
+    gtk_entry_set_activates_default(GTK_ENTRY(entry), TRUE);
+    if (hide)
+       gtk_entry_set_visibility(GTK_ENTRY(entry), FALSE);
+
+    gtk_container_add(GTK_CONTAINER(GTK_DIALOG(dialog)->vbox), label);
+    gtk_container_add(GTK_CONTAINER(GTK_DIALOG(dialog)->vbox), entry);
+    gtk_box_set_spacing(GTK_BOX(GTK_DIALOG(dialog)->vbox), 10);
+#if 0 /* FIXME: doesn't work ... */
+    gtk_container_set_border_width(GTK_CONTAINER(GTK_DIALOG(dialog)->vbox), 10);
+#endif
+
+    /* show and wait for response */
+    gtk_widget_show_all(dialog);
+    switch (gtk_dialog_run(GTK_DIALOG(dialog))) {
+    case GTK_RESPONSE_ACCEPT:
+       txt = gtk_entry_get_text(GTK_ENTRY(entry));
+       snprintf(dest, dlen, "%s", txt);
+       retval = 0;
+       break;
+    default:
+       retval = -1;
+       break;
+    }
+    gtk_widget_destroy(dialog);
+    return retval;
+}
+
+static gboolean gamt_data(GIOChannel *source, GIOCondition condition,
+                         gpointer data)
+{
+    struct gamt_window *gamt = data;
+
+    redir_data(&gamt->redir);
+
+    if (gamt->redir.state == REDIR_CLOSED ||
+       gamt->redir.state == REDIR_ERROR) {
+       g_source_destroy(g_main_context_find_source_by_id
+                         (g_main_context_default(), gamt->id));
+       gamt->id = 0;
+       gamt->ch = NULL;
+    }
+    return TRUE;
+}
+
+static void gamt_rebuild_hosts(struct gamt_window *gamt)
+{
+    int count, size, pos;
+    char *hosts_xml, *host, action[128];
+    GtkActionEntry entry;
+    GError *err = NULL;
+
+    /* remove */
+    if (gamt->hosts_id) {
+       gtk_ui_manager_remove_ui(gamt->ui, gamt->hosts_id);
+       gamt->hosts_id = 0;
+    }
+    if (gamt->hosts_ag) {
+       gtk_ui_manager_remove_action_group(gamt->ui, gamt->hosts_ag);
+       g_object_unref(gamt->hosts_ag);
+       gamt->hosts_ag = NULL;
+    }
+
+    /* build */
+    memset(&entry, 0, sizeof(entry));
+    entry.callback = G_CALLBACK(menu_cb_connect_to);
+    gamt->hosts_ag = gtk_action_group_new("HostActions");
+    count = cfg_entries_count("config", "hosts");
+    size = 128 * count + sizeof(hosts_xml_start) + sizeof(hosts_xml_end);
+    hosts_xml = malloc(size); pos = 0;
+    pos += sprintf(hosts_xml+pos, "%s", hosts_xml_start);
+    for (host = cfg_entries_first("config", "hosts");
+        NULL != host;
+        host = cfg_entries_next("config", "hosts", host)) {
+       snprintf(action, sizeof(action), "ConnectTo_%s", host);
+       pos += snprintf(hosts_xml+pos, 128,
+                       "      <menuitem action='%s'/>\n",
+                       action);
+       entry.name = action;
+       entry.label = host;
+       gtk_action_group_add_actions(gamt->hosts_ag, &entry, 1, gamt);
+    }
+    pos += sprintf(hosts_xml+pos, "%s", hosts_xml_end);
+    
+    /* add */
+    gtk_ui_manager_insert_action_group(gamt->ui, gamt->hosts_ag, 1);
+    gamt->hosts_id = gtk_ui_manager_add_ui_from_string(gamt->ui, hosts_xml, -1, &err);
+    if (!gamt->hosts_id) {
+       g_message("building host menu failed: %s", err->message);
+       g_error_free(err);
+    }
+}
+
+static int gamt_connect(struct gamt_window *gamt)
+{
+    int rc;
+    
+    if (0 == strlen(amt_pass)) {
+       char msg[128];
+
+       snprintf(msg, sizeof(msg), "AMT password for %s@%s ?",
+                amt_user, amt_host);
+       rc = gamt_getstring(gamt->win, "Authentication", msg,
+                           amt_pass, sizeof(amt_pass), 1);
+       if (0 != rc)
+           return -1;
+    }
+
+    memset(&gamt->redir, 0, sizeof(gamt->redir));
+    memcpy(&gamt->redir.type, "SOL ", 4);
+
+    snprintf(gamt->redir.host, sizeof(gamt->redir.host), "%s", amt_host);
+    snprintf(gamt->redir.port, sizeof(gamt->redir.port), "%s", amt_port);
+    snprintf(gamt->redir.user, sizeof(gamt->redir.user), "%s", amt_user);
+    snprintf(gamt->redir.pass, sizeof(gamt->redir.pass), "%s", amt_pass);
+
+    gamt->redir.verbose  = 1;
+    gamt->redir.trace    = amt_trace;
+    gamt->redir.cb_data  = gamt;
+    gamt->redir.cb_recv  = recv_gtk;
+    gamt->redir.cb_state = state_gtk;
+
+    if (-1 == redir_connect(&gamt->redir))
+       return -1;
+
+    fcntl(gamt->redir.sock, F_SETFD, FD_CLOEXEC);
+    vte_terminal_reset(VTE_TERMINAL(gamt->vte), TRUE, TRUE);
+    gamt->ch = g_io_channel_unix_new(gamt->redir.sock);
+    gamt->id = g_io_add_watch(gamt->ch, G_IO_IN, gamt_data, gamt);
+    redir_start(&gamt->redir);
+    return 0;
+}
+
+static struct gamt_window *gamt_window()
+{
+    GtkWidget *vbox, *hbox, *frame, *item;
+    GdkColor color;
+    GError *err;
+    gboolean state;
+    struct gamt_window *gamt;
+    char *str;
+    
+    gamt = malloc(sizeof(*gamt));
+    if (NULL == gamt)
+       return NULL;
+    memset(gamt,0,sizeof(*gamt));
+
+    /* gtk toplevel */
+    gamt->win = gtk_window_new(GTK_WINDOW_TOPLEVEL);
+    g_signal_connect(G_OBJECT(gamt->win), "destroy",
+                     G_CALLBACK(destroy_cb), gamt);
+
+    /* menu + toolbar */
+    gamt->ui = gtk_ui_manager_new();
+    gamt->ag = gtk_action_group_new("MenuActions");
+    gtk_action_group_add_actions(gamt->ag, entries, G_N_ELEMENTS(entries), gamt);
+    gtk_action_group_add_toggle_actions(gamt->ag, tentries,
+                                       G_N_ELEMENTS(tentries), gamt);
+    gtk_ui_manager_insert_action_group(gamt->ui, gamt->ag, 0);
+#if 0
+    GtkAccelGroup *accel = gtk_ui_manager_get_accel_group(gamt->ui);
+    gtk_window_add_accel_group(GTK_WINDOW(gamt->win), accel);
+#endif
+
+    err = NULL;
+    if (!gtk_ui_manager_add_ui_from_string(gamt->ui, ui_xml, -1, &err)) {
+       g_message("building menus failed: %s", err->message);
+       g_error_free(err);
+       exit(1);
+    }
+    gamt_rebuild_hosts(gamt);
+
+    /* vte terminal */
+    gamt->vte = vte_terminal_new();
+    g_signal_connect(gamt->vte, "commit", G_CALLBACK(user_input), gamt);
+    vte_terminal_set_scrollback_lines(VTE_TERMINAL(gamt->vte), 4096);
+    str = cfg_get_str(CFG_FONT);
+    vte_terminal_set_font_from_string(VTE_TERMINAL(gamt->vte), str);
+
+    /* FIXME: make configurable */
+    vte_terminal_set_backspace_binding(VTE_TERMINAL(gamt->vte),
+                                      VTE_ERASE_ASCII_BACKSPACE);
+    vte_terminal_set_delete_binding(VTE_TERMINAL(gamt->vte),
+                                   VTE_ERASE_AUTO);
+
+    item = gtk_ui_manager_get_widget(gamt->ui, "/MainMenu/ConfigMenu/BlinkCursor");
+    state = cfg_get_bool(CFG_BLINK, 0);
+    gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(item), state);
+    vte_terminal_set_cursor_blinks(VTE_TERMINAL(gamt->vte), state);
+    
+    /* other widgets */
+    gamt->status = gtk_label_new("idle");
+    gtk_misc_set_alignment(GTK_MISC(gamt->status), 0, 0.5);
+    gtk_misc_set_padding(GTK_MISC(gamt->status), 3, 1);
+    gamt->icon = gtk_image_new_from_stock(GTK_STOCK_DISCONNECT,
+                                         GTK_ICON_SIZE_SMALL_TOOLBAR);
+    
+    /* Make a vbox and put stuff in */
+    vbox = gtk_vbox_new(FALSE, 1);
+    hbox = gtk_hbox_new(FALSE, 1);
+    gtk_container_set_border_width(GTK_CONTAINER(vbox), 1);
+    gtk_container_add(GTK_CONTAINER(gamt->win), vbox);
+    item = gtk_ui_manager_get_widget(gamt->ui, "/MainMenu");
+    gtk_box_pack_start(GTK_BOX(vbox), item, FALSE, FALSE, 0);
+#if 0
+    item = gtk_ui_manager_get_widget(gamt->ui, "/ToolBar");
+    gtk_box_pack_start(GTK_BOX(vbox), item, FALSE, FALSE, 0);
+#endif
+    gtk_box_pack_start(GTK_BOX(vbox), gamt->vte, TRUE, TRUE, 0);
+    gtk_box_pack_end(GTK_BOX(vbox), hbox, FALSE, TRUE, 0);
+
+    frame = gtk_frame_new(NULL);
+    gtk_container_add(GTK_CONTAINER(frame), gamt->status);
+    gtk_box_pack_start(GTK_BOX(hbox), frame, TRUE, TRUE, 0);
+
+    frame = gtk_frame_new(NULL);
+    gtk_container_add(GTK_CONTAINER(frame), gamt->icon);
+    gtk_box_pack_end(GTK_BOX(hbox), frame, FALSE, TRUE, 0);
+
+    /* display window */
+    gtk_widget_show_all(gamt->win);
+    
+    str = cfg_get_str(CFG_FOREGROUND);
+    if (str) {
+       gdk_color_parse(str, &color);
+       vte_terminal_set_color_foreground(VTE_TERMINAL(gamt->vte), &color);
+    }
+    str = cfg_get_str(CFG_BACKGROUND);
+    if (str) {
+       gdk_color_parse(str, &color);
+       vte_terminal_set_color_background(VTE_TERMINAL(gamt->vte), &color);
+    }
+
+    return gamt;
+}
+
+/* ------------------------------------------------------------------ */
+
+static void usage(FILE *fp)
+{
+    fprintf(fp,
+            "\n"
+           "This is " APPNAME ", release " VERSION ", I'll establish\n"
+           "serial-over-lan (sol) connections to your Intel AMT boxes.\n"
+            "\n"
+            "usage: " APPNAME " [options] host\n"
+            "options:\n"
+            "   -h            print this text\n"
+            "   -u user       username (default: admin)\n"
+            "   -p pass       password (default: $AMT_PASSWORD)\n"
+            "   -f font       terminal font\n"
+            "   -c color      text color\n"
+            "   -b color      backgrounf color\n"
+            "\n"
+            "By default port 16994 is used.\n"
+           "If no password is given " APPNAME " will ask for one.\n"
+            "\n"
+            "-- \n"
+            "(c) 2007 Gerd Hoffmann <kraxel@redhat.com>\n"
+           "\n");
+}
+
+int
+main(int argc, char *argv[])
+{
+    Display *dpy;
+    struct gamt_window *gamt;
+    char configfile[256];
+    char *h;
+    int c;
+
+    if (NULL != (h = getenv("AMT_PASSWORD")))
+       snprintf(amt_pass, sizeof(amt_pass), "%s", h);
+
+    /* read config, make sure we have sane defaults */
+    snprintf(configfile, sizeof(configfile), "%s/.gamtrc", getenv("HOME"));
+    cfg_parse_file("config", configfile);
+    if (!cfg_get_str(CFG_FONT))
+       cfg_set_str(CFG_FONT, "monospace 12");
+    if (!cfg_get_str(CFG_FOREGROUND))
+       cfg_set_str(CFG_FOREGROUND, "gray");
+    if (!cfg_get_str(CFG_BACKGROUND))
+       cfg_set_str(CFG_BACKGROUND, "black");
+
+    gtk_init(&argc, &argv);
+    dpy = gdk_x11_display_get_xdisplay(gdk_display_get_default());
+    fcntl(ConnectionNumber(dpy),F_SETFD,FD_CLOEXEC);
+
+    for (;;) {
+        if (-1 == (c = getopt(argc, argv, "hdtu:p:f:c:b:")))
+            break;
+        switch (c) {
+       case 'd':
+           amt_debug++;
+           break;
+       case 't':
+           amt_trace++;
+           break;
+       case 'u':
+           snprintf(amt_user, sizeof(amt_user), "%s", optarg);
+           break;
+       case 'p':
+           snprintf(amt_pass, sizeof(amt_pass), "%s", optarg);
+           memset(optarg,'*',strlen(optarg)); /* rm passwd from ps list */
+           break;
+       case 'f':
+           cfg_set_str(CFG_FONT, optarg);
+           break;
+       case 'c':
+           cfg_set_str(CFG_FOREGROUND, optarg);
+           break;
+       case 'b':
+           cfg_set_str(CFG_BACKGROUND, optarg);
+           break;
+
+        case 'h':
+            usage(stdout);
+            exit(0);
+        default:
+            usage(stderr);
+            exit(1);
+        }
+    }
+
+    gamt = gamt_window();
+    if (NULL == gamt)
+       exit(1);
+
+    if (optind+1 <= argc) {
+       snprintf(amt_host, sizeof(amt_host), "%s", argv[optind]);
+       gamt_connect(gamt);
+    }
+    
+    gtk_main();
+    cfg_write_file("config", configfile);
+    exit(0);
+}
diff --git a/gamt.desktop b/gamt.desktop
new file mode 100644 (file)
index 0000000..a406fb1
--- /dev/null
@@ -0,0 +1,8 @@
+[Desktop Entry]
+Encoding=UTF-8
+Name=gamt
+Comment=Intel AMT serial-over-lan client.
+Exec=gamt
+Terminal=false
+Type=Application
+Categories=GTK;System;Network;Utility;
diff --git a/gamt.man b/gamt.man
new file mode 100644 (file)
index 0000000..5e5de0d
--- /dev/null
+++ b/gamt.man
@@ -0,0 +1,48 @@
+.TH gamt 1 "(c) 2007 Gerd Hoffmann"
+.SH NAME
+gamt - Intel AMT serial-over-lan (sol) client.
+.SH SYNOPSIS
+.B gamt [ options ] [ host ]
+.SH DESCRIPTION
+.B gamt
+provides access to the serial-over-lan port of Intel AMT managed
+machines.
+.B host
+is the hostname or IP address of the machine gamt should connect
+to.
+.P
+For more inforamtions on Intel AMT check amt-howto(7).
+.SH OPTIONS
+.TP
+.B -h
+Display help text.
+.TP
+.B -u <user>
+Specify username, defaults to "admin".
+.TP
+.B -p <pass>
+Specify password.
+.B gamt
+will prompt you if unspecified.
+.TP
+.B -f <font>
+Specify terminal font, defaults to "monospace 12".
+.TP
+.B -c <color>
+Specify terminal text color, defaults to "gray".
+.TP
+.B -b <color>
+Specify terminal background color, defaults to "black".
+.P
+Font, colors can also be changed using menu options.  These settings
+are also written to the
+.B ~/.gamtrc
+config file, so they persistent.
+.SH ENVIRONMENT
+.TP
+.B AMT_PASSWORD
+Default value for the password.
+.SH SEE ALSO
+amtterm(1), amttool(1), amt-howto(7)
+.SH AUTHOR
+(c) 2007 Gerd Hoffmann <kraxel@redhat.com>
diff --git a/list.h b/list.h
new file mode 100644 (file)
index 0000000..614d34b
--- /dev/null
+++ b/list.h
@@ -0,0 +1,168 @@
+#ifndef _LIST_H_
+#define _LIST_H_
+/*
+ * Simple doubly linked list implementation.
+ *     -- shameless stolen from the linux kernel sources
+ *
+ * Some of the internal functions ("__xxx") are useful when
+ * manipulating whole lists rather than single entries, as
+ * sometimes we already know the next/prev entries and we can
+ * generate better code by using them directly rather than
+ * using the generic single-entry routines.
+ */
+
+struct list_head {
+       struct list_head *next, *prev;
+};
+
+#define LIST_HEAD_INIT(name) { &(name), &(name) }
+
+#define LIST_HEAD(name) \
+       struct list_head name = LIST_HEAD_INIT(name)
+
+#define INIT_LIST_HEAD(ptr) do { \
+       (ptr)->next = (ptr); (ptr)->prev = (ptr); \
+} while (0)
+
+/*
+ * Insert a new entry between two known consecutive entries. 
+ *
+ * This is only for internal list manipulation where we know
+ * the prev/next entries already!
+ */
+static __inline__ void __list_add(struct list_head * new,
+       struct list_head * prev,
+       struct list_head * next)
+{
+       next->prev = new;
+       new->next = next;
+       new->prev = prev;
+       prev->next = new;
+}
+
+/**
+ * list_add - add a new entry
+ * @new: new entry to be added
+ * @head: list head to add it after
+ *
+ * Insert a new entry after the specified head.
+ * This is good for implementing stacks.
+ */
+static __inline__ void list_add(struct list_head *new, struct list_head *head)
+{
+       __list_add(new, head, head->next);
+}
+
+/**
+ * list_add_tail - add a new entry
+ * @new: new entry to be added
+ * @head: list head to add it before
+ *
+ * Insert a new entry before the specified head.
+ * This is useful for implementing queues.
+ */
+static __inline__ void list_add_tail(struct list_head *new, struct list_head *head)
+{
+       __list_add(new, head->prev, head);
+}
+
+/*
+ * Delete a list entry by making the prev/next entries
+ * point to each other.
+ *
+ * This is only for internal list manipulation where we know
+ * the prev/next entries already!
+ */
+static __inline__ void __list_del(struct list_head * prev,
+                                 struct list_head * next)
+{
+       next->prev = prev;
+       prev->next = next;
+}
+
+/**
+ * list_del - deletes entry from list.
+ * @entry: the element to delete from the list.
+ * Note: list_empty on entry does not return true after this, the entry is in an undefined state.
+ */
+static __inline__ void list_del(struct list_head *entry)
+{
+       __list_del(entry->prev, entry->next);
+}
+
+/**
+ * list_del_init - deletes entry from list and reinitialize it.
+ * @entry: the element to delete from the list.
+ */
+static __inline__ void list_del_init(struct list_head *entry)
+{
+       __list_del(entry->prev, entry->next);
+       INIT_LIST_HEAD(entry); 
+}
+
+/**
+ * list_empty - tests whether a list is empty
+ * @head: the list to test.
+ */
+static __inline__ int list_empty(struct list_head *head)
+{
+       return head->next == head;
+}
+
+/**
+ * list_splice - join two lists
+ * @list: the new list to add.
+ * @head: the place to add it in the first list.
+ */
+static __inline__ void list_splice(struct list_head *list, struct list_head *head)
+{
+       struct list_head *first = list->next;
+
+       if (first != list) {
+               struct list_head *last = list->prev;
+               struct list_head *at = head->next;
+
+               first->prev = head;
+               head->next = first;
+
+               last->next = at;
+               at->prev = last;
+       }
+}
+
+/**
+ * list_entry - get the struct for this entry
+ * @ptr:       the &struct list_head pointer.
+ * @type:      the type of the struct this is embedded in.
+ * @member:    the name of the list_struct within the struct.
+ */
+#define list_entry(ptr, type, member) \
+       ((type *)((char *)(ptr)-(unsigned long)(&((type *)0)->member)))
+
+/**
+ * list_for_each       -       iterate over a list
+ * @pos:       the &struct list_head to use as a loop counter.
+ * @head:      the head for your list.
+ */
+#define list_for_each(pos, head) \
+       for (pos = (head)->next; pos != (head); pos = pos->next)
+               
+/**
+ * list_for_each_safe  -       iterate over a list safe against removal of list entry
+ * @pos:       the &struct list_head to use as a loop counter.
+ * @n:         another &struct list_head to use as temporary storage
+ * @head:      the head for your list.
+ */
+#define list_for_each_safe(pos, n, head) \
+       for (pos = (head)->next, n = pos->next; pos != (head); \
+               pos = n, n = pos->next)
+
+/**
+ * list_for_each_prev  -       iterate over a list in reverse order
+ * @pos:       the &struct list_head to use as a loop counter.
+ * @head:      the head for your list.
+ */
+#define list_for_each_prev(pos, head) \
+       for (pos = (head)->prev; pos != (head); pos = pos->prev)
+
+#endif /* _LIST_H_ */
diff --git a/mk/Autoconf.mk b/mk/Autoconf.mk
new file mode 100644 (file)
index 0000000..65cd915
--- /dev/null
@@ -0,0 +1,150 @@
+#
+# simple autoconf system for GNU make
+#
+# (c) 2002-2006 Gerd Hoffmann <kraxel@suse.de>
+#
+# credits for creating this one go to the autotools people because
+# they managed it to annoy lots of developers and users (including
+# me) with version incompatibilities.
+#
+# This file is public domain.  No warranty.  If it breaks you keep
+# both pieces.
+#
+########################################################################
+
+# verbose yes/no
+verbose                ?= no
+
+# some stuff used by the tests
+ifneq ($(verbose),no)
+  # verbose (for debug)
+  ac_init      = echo "checking $(1) ... " >&2; rc=no
+  ac_b_cmd     = echo "run: $(1)" >&2; $(1) >/dev/null && rc=yes
+  ac_s_cmd     = echo "run: $(1)" >&2; rc=`$(1)`
+  ac_fini      = echo "... result is $${rc}" >&2; echo >&2; echo "$${rc}"
+else
+  # normal
+  ac_init      = echo -n "checking $(1) ... " >&2; rc=no
+  ac_b_cmd     = $(1) >/dev/null 2>&1 && rc=yes
+  ac_s_cmd     = rc=`$(1) 2>/dev/null`
+  ac_fini      = echo "$${rc}" >&2; echo "$${rc}"
+endif
+
+# some helpers to build cflags and related variables
+ac_def_cflags_1 = $(if $(filter yes,$($(1))),-D$(1))
+ac_lib_cflags  = $(foreach lib,$(1),$(call ac_def_cflags_1,HAVE_LIB$(lib)))
+ac_inc_cflags  = $(foreach inc,$(1),$(call ac_def_cflags_1,HAVE_$(inc)))
+ac_lib_mkvar_1 = $(if $(filter yes,$(HAVE_LIB$(1))),$($(1)_$(2)))
+ac_lib_mkvar   = $(foreach lib,$(1),$(call ac_lib_mkvar_1,$(lib),$(2)))
+
+
+########################################################################
+# the tests ...
+
+# get uname
+ac_uname = $(shell \
+       $(call ac_init,for system);\
+       $(call ac_s_cmd,uname -s | tr 'A-Z' 'a-z');\
+       $(call ac_fini))
+
+# check for some header file
+# args: header file
+ac_header = $(shell \
+       $(call ac_init,for $(1));\
+       $(call ac_b_cmd,echo '\#include <$(1)>' |\
+               $(CC) $(CFLAGS) -E -);\
+       $(call ac_fini))
+
+# check for some function
+# args: function [, additional libs ]
+ac_func = $(shell \
+       $(call ac_init,for $(1));\
+       echo 'void $(1)(void); int main(void) {$(1)();return 0;}' \
+               > __actest.c;\
+       $(call ac_b_cmd,$(CC) $(CFLAGS) $(LDFLAGS) -o \
+               __actest __actest.c $(2));\
+       rm -f __actest __actest.c;\
+       $(call ac_fini))
+
+# check for some library
+# args: function, library [, additional libs ]
+ac_lib = $(shell \
+       $(call ac_init,for $(1) in $(2));\
+       echo 'void $(1)(void); int main(void) {$(1)();return 0;}' \
+               > __actest.c;\
+       $(call ac_b_cmd,$(CC) $(CFLAGS) $(LDFLAGS) -o \
+               __actest __actest.c -l$(2) $(3));\
+       rm -f __actest __actest.c;\
+       $(call ac_fini))
+
+# check if some compiler flag works
+# args: compiler flag
+ac_cflag = $(shell \
+       $(call ac_init,if $(CC) supports $(1));\
+       echo 'int main() {return 0;}' > __actest.c;\
+       $(call ac_b_cmd,$(CC) $(CFLAGS) $(1) $(LDFLAGS) -o \
+               __actest __actest.c);\
+       rm -f __actest __actest.c;\
+       $(call ac_fini))
+
+# check for some binary
+# args: binary name
+ac_binary = $(shell \
+       $(call ac_init,for $(1));\
+       $(call ac_s_cmd,which $(1));\
+       bin="$$rc";rc="no";\
+       $(call ac_b_cmd,test -x "$$$$bin");\
+       $(call ac_fini))
+
+# check if lib64 is used
+ac_lib64 = $(shell \
+       $(call ac_init,for libdir name);\
+       $(call ac_s_cmd,$(CC) -print-search-dirs | grep -q lib64 &&\
+               echo "lib64" || echo "lib");\
+       $(call ac_fini))
+
+# check for x11 ressource dir prefix
+ac_resdir = $(shell \
+       $(call ac_init,for X11 app-defaults prefix);\
+       $(call ac_s_cmd, for dir in \
+               /etc/X11/app-defaults \
+               /usr/X11R6/lib/X11/app-defaults \
+               /usr/share/X11/app-defaults \
+               /usr/lib/X11/app-defaults \
+               ; do test -d "$$dir" || continue;\
+               dirname "$$dir"; break; done);\
+       $(call ac_fini))
+
+# check if package is installed, via pkg-config
+# args: pkg name
+ac_pkg_config = $(shell \
+       $(call ac_init,for $(1) (using pkg-config));\
+       $(call ac_b_cmd, pkg-config $(1));\
+       $(call ac_fini))
+
+
+########################################################################
+# build Make.config
+
+define newline
+
+
+endef
+make-config-q  = $(subst $(newline),\n,$(make-config))
+
+ifeq ($(filter config,$(MAKECMDGOALS)),config)
+.PHONY: Make.config
+  LIB := $(call ac_lib64)
+else
+  LIB ?= $(call ac_lib64)
+  LIB := $(LIB)
+endif
+.PHONY: config
+config: Make.config
+       @true
+
+Make.config: $(srcdir)/GNUmakefile
+       @echo -e "$(make-config-q)" > $@
+       @echo
+       @echo "Make.config written, edit if needed"
+       @echo
diff --git a/mk/Compile.mk b/mk/Compile.mk
new file mode 100644 (file)
index 0000000..da14d58
--- /dev/null
@@ -0,0 +1,88 @@
+#
+# some rules to compile stuff ...
+#
+# (c) 2002-2006 Gerd Hoffmann <kraxel@suse.de>
+#
+# main features:
+#  * autodependencies via "cpp -MD"
+#  * fancy, non-verbose output
+#
+# This file is public domain.  No warranty.  If it breaks you keep
+# both pieces.
+#
+########################################################################
+
+# verbose yes/no
+verbose                ?= no
+
+# dependency files
+tmpdep         = mk/$(subst /,_,$*).tmp
+depfile                = mk/$(subst /,_,$*).dep
+depfiles       = mk/*.dep
+
+compile_c      = $(CC) $(CFLAGS) -Wp,-MD,$(tmpdep) -c -o $@ $<
+compile_cc     = $(CXX) $(CXXFLAGS) -Wp,-MD,$(tmpdep) -c -o $@ $<
+fixup_deps     = sed -e "s|.*\.o:|$@:|" < $(tmpdep) > $(depfile) && rm -f $(tmpdep)
+cc_makedirs    = mkdir -p $(dir $@) $(dir $(depfile))
+
+link_app       = $(CC) $(LDFLAGS) -o $@  $^ $(LDLIBS)
+link_so                = $(CC) $(LDFLAGS) -shared -Wl,-soname,$(@F) -o $@ $^ $(LDLIBS)
+ar_lib         = rm -f $@ && ar -r $@ $^ && ranlib $@
+
+moc_h          = $(MOC) $< -o $@
+msgfmt_po      = msgfmt -o $@ $<
+
+# non-verbose output
+ifeq ($(verbose),no)
+  echo_compile_c       = echo "  CC     " $@
+  echo_compile_cc      = echo "  CXX    " $@
+  echo_link_app                = echo "  LD     " $@
+  echo_link_so         = echo "  LD     " $@
+  echo_ar_lib          = echo "  AR     " $@
+  echo_moc_h           = echo "  MOC    " $@
+  echo_msgfmt_po        = echo "  MSGFMT " $@
+else
+  echo_compile_c       = echo $(compile_c)
+  echo_compile_cc      = echo $(compile_cc)
+  echo_link_app                = echo $(link_app)
+  echo_link_so         = echo $(link_so)
+  echo_ar_lib          = echo $(ar_lib)
+  echo_moc_h           = echo $(moc_h)
+  echo_msgfmt_po       = echo $(msgfmt_po)
+endif
+
+%.o: %.c
+       @$(cc_makedirs)
+       @$(echo_compile_c)
+       @$(compile_c)
+       @$(fixup_deps)
+
+%.o: %.cc
+       @$(cc_makedirs)
+       @$(echo_compile_cc)
+       @$(compile_cc)
+       @$(fixup_deps)
+
+%.o: %.cpp
+       @$(cc_makedirs)
+       @$(echo_compile_cc)
+       @$(compile_cc)
+       @$(fixup_deps)
+
+
+%.so: %.o
+       @$(echo_link_so)
+       @$(link_so)
+
+%: %.o
+       @$(echo_link_app)
+       @$(link_app)
+
+%.moc : %.h
+       @$(echo_moc_h)
+       @$(moc_h)
+
+%.mo : %.po
+       @$(echo_msgfmt_po)
+       @$(msgfmt_po)
+
diff --git a/mk/Maintainer.mk b/mk/Maintainer.mk
new file mode 100644 (file)
index 0000000..23446ae
--- /dev/null
@@ -0,0 +1,28 @@
+# just some maintainer stuff for me ...
+########################################################################
+
+make-sync-dir = $(HOME)/projects/gnu-makefiles
+
+.PHONY: sync
+sync:: distclean
+       test -d $(make-sync-dir)
+       rm -f $(srcdir)/INSTALL $(srcdir)/mk/*.mk
+       cp -v $(make-sync-dir)/INSTALL $(srcdir)/.
+       cp -v $(make-sync-dir)/*.mk $(srcdir)/mk
+       chmod 444 $(srcdir)/INSTALL $(srcdir)/mk/*.mk
+
+
+repository = $(shell cat CVS/Repository)
+release-dir ?= $(HOME)/projects/Releases
+release-pub ?= goldbach@me.in-berlin.de:dl.bytesex.org/releases/$(repository)
+tarball = $(release-dir)/$(repository)-$(VERSION).tar.gz
+
+.PHONY: release
+release:
+       cvs tag $(RELTAG)
+       cvs export -r $(RELTAG) -d "$(repository)-$(VERSION)" "$(repository)"
+       find "$(repository)-$(VERSION)" -name .cvsignore -exec rm -fv "{}" ";"
+       tar -c -z -f "$(tarball)" "$(repository)-$(VERSION)"
+       rm -rf "$(repository)-$(VERSION)"
+       scp $(tarball) $(release-pub)
+
diff --git a/mk/Variables.mk b/mk/Variables.mk
new file mode 100644 (file)
index 0000000..fdb1653
--- /dev/null
@@ -0,0 +1,54 @@
+# common variables ...
+########################################################################
+
+# directories
+DESTDIR        =
+srcdir ?= .
+prefix ?= /usr/local
+bindir =  $(DESTDIR)$(prefix)/bin
+libdir  =  $(DESTDIR)$(prefix)/$(LIB)
+shrdir  =  $(DESTDIR)$(prefix)/share
+mandir =  $(shrdir)/man
+locdir  =  $(shrdir)/locale
+appdir  =  $(shrdir)/applications
+
+# package + version
+empty  :=
+space  := $(empty) $(empty)
+ifneq ($(wildcard $(srcdir)/VERSION),)
+  VERSION := $(shell cat $(srcdir)/VERSION)
+else
+  VERSION := 42
+endif
+RELTAG := v$(subst .,_,$(VERSION))
+
+# programs
+CC             ?= gcc
+CXX            ?= g++
+MOC             ?= $(if $(QTDIR),$(QTDIR)/bin/moc,moc)
+
+STRIP          ?= -s
+INSTALL                ?= install
+INSTALL_BINARY  := $(INSTALL) $(STRIP)
+INSTALL_SCRIPT  := $(INSTALL)
+INSTALL_DATA   := $(INSTALL) -m 644
+INSTALL_DIR    := $(INSTALL) -d
+
+# cflags
+CFLAGS         ?= -g -O2
+CXXFLAGS       ?= $(CFLAGS)
+CFLAGS         += -Wall -Wmissing-prototypes -Wstrict-prototypes \
+                  -Wpointer-arith -Wunused
+CXXFLAGS       += -Wall -Wpointer-arith -Wunused
+
+# add /usr/local to the search path if something is in there ...
+ifneq ($(wildcard /usr/local/include/*.h),)
+  CFLAGS  += -I/usr/local/include
+  LDFLAGS += -L/usr/local/$(LIB)
+endif
+
+# fixup include path for $(srcdir) != "."
+ifneq ($(srcdir),.)
+  CFLAGS  += -I. -I$(srcdir)
+endif
+
diff --git a/parseconfig.c b/parseconfig.c
new file mode 100644 (file)
index 0000000..79e2457
--- /dev/null
@@ -0,0 +1,915 @@
+/*
+ *  functions to read and write config files
+ *
+ *  Copyright (C) 2007 Gerd Hoffmann <kraxel@redhat.com
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License along
+ *  with this program; if not, write to the Free Software Foundation, Inc.,
+ *  51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <errno.h>
+
+#include <sys/stat.h>
+#include <sys/types.h>
+
+#include "list.h"
+#include "parseconfig.h"
+
+struct cfg_entry {
+    struct list_head  next;
+    char              *name;
+    unsigned int      flags;
+    char              *value;
+};
+
+struct cfg_section {
+    struct list_head  next;
+    char              *name;
+    unsigned int      flags;
+    struct list_head  entries;
+};
+
+struct cfg_domain {
+    struct list_head  next;
+    char              *name;
+    struct list_head  sections;
+};
+
+LIST_HEAD(domains);
+
+/* ------------------------------------------------------------------------ */
+/* internal stuff                                                           */
+
+static struct cfg_domain  *d_last;
+static struct cfg_section *s_last;
+static struct cfg_entry   *e_last;
+
+static struct cfg_domain*
+cfg_find_domain(char *dname)
+{
+    struct list_head *item;
+    struct cfg_domain *domain;
+
+    if (d_last && 0 == strcmp(d_last->name,dname))
+       return d_last;
+    d_last = NULL;
+    s_last = NULL;
+    e_last = NULL;
+
+    list_for_each(item,&domains) {
+       domain = list_entry(item, struct cfg_domain, next);
+       if (0 == strcasecmp(domain->name,dname)) {
+           d_last = domain;
+           return domain;
+       }
+    }
+    return NULL;
+}
+
+static struct cfg_domain*
+cfg_get_domain(char *dname)
+{
+    struct cfg_domain *domain;
+
+    domain = cfg_find_domain(dname);
+    if (NULL == domain) {
+       domain = malloc(sizeof(*domain));
+       memset(domain,0,sizeof(*domain));
+       domain->name = strdup(dname);
+       INIT_LIST_HEAD(&domain->sections);
+       list_add_tail(&domain->next,&domains);
+    }
+    d_last = domain;
+    return domain;
+}
+
+static struct cfg_section*
+cfg_find_section(struct cfg_domain *domain, char *sname)
+{
+    struct list_head *item;
+    struct cfg_section *section;
+
+    if (s_last && 0 == strcmp(s_last->name,sname))
+       return s_last;
+    s_last = NULL;
+    e_last = NULL;
+
+    list_for_each(item,&domain->sections) {
+       section = list_entry(item, struct cfg_section, next);
+       if (0 == strcasecmp(section->name,sname)) {
+           s_last = section;
+           return section;
+       }
+    }
+    return NULL;
+}
+
+static struct cfg_section*
+cfg_get_section(struct cfg_domain *domain, char *sname)
+{
+    struct cfg_section *section;
+
+    section = cfg_find_section(domain,sname);
+    if (NULL == section) {
+       section = malloc(sizeof(*section));
+       memset(section,0,sizeof(*section));
+       section->name = strdup(sname);
+       INIT_LIST_HEAD(&section->entries);
+       list_add_tail(&section->next,&domain->sections);
+    }
+    s_last = section;
+    return section;
+}
+
+static struct cfg_entry*
+cfg_find_entry(struct cfg_section *section, char *ename)
+{
+    struct list_head *item;
+    struct cfg_entry *entry;
+
+    if (e_last && 0 == strcmp(e_last->name,ename))
+       return e_last;
+    e_last = NULL;
+
+    list_for_each(item,&section->entries) {
+       entry = list_entry(item, struct cfg_entry, next);
+       if (0 == strcasecmp(entry->name,ename)) {
+           e_last = entry;
+           return entry;
+       }
+    }
+    return NULL;
+}
+
+static struct cfg_entry*
+cfg_get_entry(struct cfg_section *section, char *ename)
+{
+    struct cfg_entry *entry;
+
+    entry = cfg_find_entry(section,ename);
+    if (NULL == entry) {
+       entry = malloc(sizeof(*entry));
+       memset(entry,0,sizeof(*entry));
+       entry->name = strdup(ename);
+       list_add_tail(&entry->next,&section->entries);
+    }
+    e_last = entry;
+    return entry;
+}
+
+static void
+cfg_set_entry(struct cfg_section *section, char *name, const char *value)
+{
+    struct cfg_entry *entry;
+    
+    entry = cfg_get_entry(section,name);
+    if (entry->value)
+       free(entry->value);
+    entry->value = strdup(value);
+}
+
+static struct cfg_section*
+cfg_get_sec(char *dname, char *sname)
+{
+    struct cfg_domain  *domain;
+
+    domain = cfg_find_domain(dname);
+    if (NULL == domain)
+       return NULL;
+    return cfg_find_section(domain,sname);
+}
+
+static struct cfg_entry*
+cfg_get_ent(char *dname, char *sname, char *ename)
+{
+    struct cfg_section *section;
+
+    section = cfg_get_sec(dname,sname);
+    if (NULL == section)
+       return NULL;
+    return cfg_find_entry(section,ename);
+}
+
+/* ------------------------------------------------------------------------ */
+/* import / add / del config data                                           */
+
+int
+cfg_parse_file(char *dname, char *filename)
+{
+    struct cfg_domain  *domain  = NULL;
+    struct cfg_section *section = NULL;
+    char line[256],tag[64],value[192];
+    FILE *fp;
+    int nr;
+    
+    if (NULL == (fp = fopen(filename,"r")))
+       return -1;
+
+    nr = 0;
+    domain = cfg_get_domain(dname);
+    while (NULL != fgets(line,255,fp)) {
+       nr++;
+       if (1 == sscanf(line,"# include \"%[^\"]\"",value)) {
+           /* includes */
+           char *h,*inc;
+           inc = malloc(strlen(filename)+strlen(value));
+           strcpy(inc,filename);
+           h = strrchr(inc,'/');
+           if (h)
+               h++;
+           else
+               h = inc;
+           strcpy(h,value);
+           cfg_parse_file(dname,inc);
+           free(inc);
+           continue;
+       }
+       if (line[0] == '\n' || line[0] == '#' || line[0] == '%')
+           continue;
+       if (1 == sscanf(line,"[%99[^]]]",value)) {
+           /* [section] */
+           section = cfg_get_section(domain,value);
+       } else if (2 == sscanf(line," %63[^= ] = %191[^\n]",tag,value)) {
+           /* foo = bar */
+           if (NULL == section) {
+               fprintf(stderr,"%s:%d: error: no section\n",filename,nr);
+           } else {
+               char *c = value + strlen(value)-1;
+               while (c > value  &&  (*c == ' ' || *c == '\t'))
+                   *(c--) = 0;
+               cfg_set_entry(section,tag,value);
+           }
+       } else {
+           /* Huh ? */
+           fprintf(stderr,"%s:%d: syntax error\n",filename,nr);
+       }
+    }
+    fclose(fp);
+    return 0;
+}
+
+void
+cfg_set_str(char *dname, char *sname, char *ename, const char *value)
+{
+    struct cfg_domain  *domain  = NULL;
+    struct cfg_section *section = NULL;
+
+    if (NULL == value) {
+       cfg_del_entry(dname, sname, ename);
+    } else {
+       domain  = cfg_get_domain(dname);
+       section = cfg_get_section(domain,sname);
+       cfg_set_entry(section,ename,value);
+    }
+}
+
+void
+cfg_set_int(char *dname, char *sname, char *ename, int value)
+{
+    char str[32];
+
+    snprintf(str,sizeof(str),"%d",value);
+    cfg_set_str(dname,sname,ename,str);
+}
+
+void
+cfg_set_bool(char *dname, char *sname, char *ename, int value)
+{
+    cfg_set_str(dname,sname,ename, value ? "true" : "false");
+}
+
+void
+cfg_del_section(char *dname, char *sname)
+{
+    struct cfg_section  *section;
+    struct cfg_entry    *entry;
+
+    section= cfg_get_sec(dname,sname);
+    if (!section)
+       return;
+    list_del(&section->next);
+    while (!list_empty(&section->entries)) {
+       entry = list_entry(section->entries.next, struct cfg_entry, next);
+       list_del(&entry->next);
+       free(entry->name);
+       free(entry->value);
+       free(entry);
+    }
+    s_last = NULL;
+    e_last = NULL;
+    free(section->name);
+    free(section);
+}
+
+void
+cfg_del_entry(char *dname, char *sname, char *ename)
+{
+    struct cfg_entry *entry;
+
+    entry = cfg_get_ent(dname,sname,ename);
+    if (!entry)
+       return;
+    e_last = NULL;
+    list_del(&entry->next);
+    free(entry->name);
+    free(entry->value);
+    free(entry);
+}
+
+void
+cfg_parse_cmdline(int *argc, char **argv, struct cfg_cmdline *opt)
+{
+    int i,j,o,shift,len;
+    char sopt,*lopt;
+
+    for (i = 1; i < *argc;) {
+       if (argv[i][0] != '-') {
+           i++;
+           continue;
+       }
+       if (argv[i][1] == 0) {
+           i++;
+           continue;
+       }
+
+       sopt = 0;
+       lopt = NULL;
+       if (argv[i][1] != '-' &&
+           argv[i][2] == 0) {
+           /* short option: -f */
+           sopt = argv[i][1];
+       }
+       if (argv[i][1] != '-') {
+           /* long option: -foo */
+           lopt = argv[i]+1;
+       } else {
+           /* also accept gnu-style: --foo */
+           lopt = argv[i]+2;
+       }
+       
+       for (shift = 0, o = 0;
+            0 == shift && opt[o].cmdline != NULL;
+            o++) {
+           len = strlen(opt[o].cmdline);
+
+           if (opt[o].yesno && sopt && sopt == opt[o].letter) {
+               /* yesno: -f */
+               cfg_set_bool(opt[o].option.domain,
+                            opt[o].option.section,
+                            opt[o].option.entry,
+                            1);
+               shift = 1;
+
+           } else if (opt[o].needsarg && sopt && sopt == opt[o].letter &&
+                      i+1 < *argc) {
+               /* arg: -f bar */
+               cfg_set_str(opt[o].option.domain,
+                           opt[o].option.section,
+                           opt[o].option.entry,
+                           argv[i+1]);
+               shift = 2;
+               
+           } else if (opt[o].value && sopt && sopt == opt[o].letter) {
+               /* -f sets fixed value */
+               cfg_set_str(opt[o].option.domain,
+                           opt[o].option.section,
+                           opt[o].option.entry,
+                           opt[o].value);
+               shift = 1;
+
+           } else if (opt[o].yesno && lopt && 
+                      0 == strcmp(lopt,opt[o].cmdline)) {
+               /* yesno: -foo */
+               cfg_set_bool(opt[o].option.domain,
+                            opt[o].option.section,
+                            opt[o].option.entry,
+                            1);
+               shift = 1;
+
+           } else if (opt[o].yesno && lopt && 
+                      0 == strncmp(lopt,"no",2) &&
+                      0 == strcmp(lopt+2,opt[o].cmdline)) {
+               /* yesno: -nofoo */
+               cfg_set_bool(opt[o].option.domain,
+                            opt[o].option.section,
+                            opt[o].option.entry,
+                            0);
+               shift = 1;
+
+           } else if (opt[o].needsarg && lopt && 
+                      0 == strcmp(lopt,opt[o].cmdline) &&
+                      i+1 < *argc) {
+               /* arg: -foo bar */
+               cfg_set_str(opt[o].option.domain,
+                           opt[o].option.section,
+                           opt[o].option.entry,
+                           argv[i+1]);
+               shift = 2;
+
+           } else if (opt[o].needsarg && lopt && 
+                      0 == strncmp(lopt,opt[o].cmdline,len) &&
+                      0 == strncmp(lopt+len,"=",1)) {
+               /* arg: -foo=bar */
+               cfg_set_str(opt[o].option.domain,
+                           opt[o].option.section,
+                           opt[o].option.entry,
+                           argv[i]+2+len);
+               shift = 1;
+
+           } else if (opt[o].value && lopt &&
+                      0 == strcmp(lopt,opt[o].cmdline)) {
+               /* -foo sets some fixed value */
+               cfg_set_str(opt[o].option.domain,
+                           opt[o].option.section,
+                           opt[o].option.entry,
+                           opt[o].value);
+               shift = 1;
+           }
+       }
+
+       if (shift) {
+           /* remove processed args */
+           for (j = i; j < *argc+1-shift; j++)
+               argv[j] = argv[j+shift];
+           (*argc) -= shift;
+       } else
+           i++;
+    }
+}
+
+void
+cfg_help_cmdline(FILE *fp, struct cfg_cmdline *opt, int w1, int w2, int w3)
+{
+    char *val;
+    int o,len;
+    
+    for (o = 0; opt[o].cmdline != NULL; o++) {
+       fprintf(fp,"%*s",w1,"");
+       if (opt[o].letter) {
+           fprintf(fp,"-%c  ",opt[o].letter);
+       } else {
+           fprintf(fp,"    ");
+       }
+
+       if (opt[o].yesno) {
+           len = fprintf(fp,"-(no)%s ",opt[o].cmdline);
+       } else if (opt[o].needsarg) {
+           len = fprintf(fp,"-%s <arg> ",opt[o].cmdline);
+       } else {
+           len = fprintf(fp,"-%s ",opt[o].cmdline);
+       }
+       if (len < w2)
+           fprintf(fp,"%*s",w2-len,"");
+
+       len = fprintf(fp,"%s ",opt[o].desc);
+
+       if (w3) {
+           if (len < w3)
+               fprintf(fp,"%*s",w3-len,"");
+           val = cfg_get_str(opt[o].option.domain,
+                             opt[o].option.section,
+                             opt[o].option.entry);
+           if (val)
+               fprintf(fp,"[%s]",val);
+       }
+       fprintf(fp,"\n");
+    }
+}
+
+/* ------------------------------------------------------------------------ */
+/* export config data                                                       */
+
+static int cfg_mkdir(char *filename)
+{
+    char *h;
+    int rc;
+
+    h = strrchr(filename,'/');
+    if (!h  ||  h == filename)
+       return -1;
+    *h = '\0';
+    rc = mkdir(filename,0777);
+    if (-1 == rc && ENOENT == errno) {
+       cfg_mkdir(filename);
+       rc = mkdir(filename,0777);
+    }
+    if (-1 == rc)
+       fprintf(stderr,"mkdir(%s): %s\n",filename,strerror(errno));
+    *h = '/';
+    return rc;
+}
+
+int
+cfg_write_file(char *dname, char *filename)
+{
+    struct list_head   *item1,*item2;
+    struct cfg_domain  *domain;
+    struct cfg_section *section;
+    struct cfg_entry   *entry;
+    char *bfile, *tfile;
+    int len;
+    FILE *fp;
+
+    len = strlen(filename)+10;
+    bfile = malloc(len);
+    tfile = malloc(len);
+    sprintf(bfile,"%s~",filename);
+    sprintf(tfile,"%s.$$$",filename);
+
+    fp = fopen(tfile,"w");
+    if (NULL == fp  &&  ENOENT == errno) {
+       cfg_mkdir(tfile);
+       fp = fopen(tfile,"w");
+    }
+    if (NULL == fp) {
+       fprintf(stderr,"open(%s): %s\n",tfile,strerror(errno));
+       return -1;
+    }
+
+    domain = cfg_find_domain(dname);
+    if (NULL != domain) {
+       list_for_each(item1,&domain->sections) {
+           section = list_entry(item1, struct cfg_section, next);
+           fprintf(fp,"[%s]\n",section->name);
+           list_for_each(item2,&section->entries) {
+               entry = list_entry(item2, struct cfg_entry, next);
+               fprintf(fp,"%s = %s\n",entry->name,entry->value);
+           }
+           fprintf(fp,"\n");
+       }
+    }
+    fclose(fp);
+
+    if (-1 == unlink(bfile) && ENOENT != errno) {
+       fprintf(stderr,"unlink(%s): %s\n",bfile,strerror(errno));
+       return -1;
+    }
+    if (-1 == rename(filename,bfile) && ENOENT != errno) {
+       fprintf(stderr,"rename(%s,%s): %s\n",filename,bfile,strerror(errno));
+       return -1;
+    }
+    if (-1 == rename(tfile,filename)) {
+       fprintf(stderr,"rename(%s,%s): %s\n",tfile,filename,strerror(errno));
+       return -1;
+    }
+    return 0;
+}
+
+/* ------------------------------------------------------------------------ */
+/* list / search config data                                                */
+
+char*
+cfg_sections_first(char *dname)
+{
+    struct list_head   *item;
+    struct cfg_domain  *domain;
+    struct cfg_section *section;
+
+    domain = cfg_find_domain(dname);
+    if (NULL == domain)
+       return NULL;
+    
+    item = &domain->sections;
+    if (item->next == &domain->sections)
+       return NULL;
+    section = list_entry(item->next, struct cfg_section, next);
+    s_last  = section;
+    e_last  = NULL;
+    return section->name;
+}
+
+char*
+cfg_sections_next(char *dname, char *current)
+{
+    struct list_head   *item;
+    struct cfg_domain  *domain;
+    struct cfg_section *section;
+
+    domain = cfg_find_domain(dname);
+    if (NULL == domain)
+       return NULL;
+    section = cfg_find_section(domain,current);
+    if (NULL == section)
+       return NULL;
+    item = &section->next;
+
+    if (item->next == &domain->sections)
+       return NULL;
+    section = list_entry(item->next, struct cfg_section, next);
+    s_last  = section;
+    e_last  = NULL;
+    return section->name;
+}
+
+char*
+cfg_sections_prev(char *dname, char *current)
+{
+    struct list_head   *item;
+    struct cfg_domain  *domain;
+    struct cfg_section *section;
+
+    domain = cfg_find_domain(dname);
+    if (NULL == domain)
+       return NULL;
+    section = cfg_find_section(domain,current);
+    if (NULL == section)
+       return NULL;
+    item = &section->next;
+    
+    if (item->prev == &domain->sections)
+       return NULL;
+    section = list_entry(item->prev, struct cfg_section, next);
+    s_last  = section;
+    e_last  = NULL;
+    return section->name;
+}
+
+unsigned int cfg_sections_count(char *dname)
+{
+    struct list_head   *item;
+    struct cfg_domain  *domain;
+    int count = 0;
+
+    domain = cfg_find_domain(dname);
+    if (NULL != domain)
+       list_for_each(item,&domain->sections)
+           count++;
+    return count;
+}
+
+char* cfg_sections_index(char *dname, int i)
+{
+    struct list_head   *item;
+    struct cfg_domain  *domain;
+    struct cfg_section *section;
+    int count = 0;
+
+    domain = cfg_find_domain(dname);
+    if (NULL == domain)
+       return NULL;
+
+    list_for_each(item,&domain->sections) {
+       if (i == count) {
+           section = list_entry(item, struct cfg_section, next);
+           s_last  = section;
+           e_last  = NULL;
+           return section->name;
+       }
+       count++;
+    }
+    return NULL;
+}
+
+char*
+cfg_entries_first(char *dname, char *sname)
+{
+    struct list_head   *item;
+    struct cfg_section *section;
+    struct cfg_entry   *entry;
+
+    section = cfg_get_sec(dname,sname);
+    if (NULL == section)
+       return NULL;
+    
+    item = &section->entries;
+    if (item->next == &section->entries)
+       return NULL;
+    entry  = list_entry(item->next, struct cfg_entry, next);
+    e_last = entry;
+    return entry->name;
+}
+
+char*
+cfg_entries_next(char *dname, char *sname, char *current)
+{
+    struct list_head   *item;
+    struct cfg_section *section;
+    struct cfg_entry   *entry;
+
+    section = cfg_get_sec(dname,sname);
+    if (NULL == section)
+       return NULL;
+    entry = cfg_find_entry(section,current);
+    if (NULL == entry)
+       return NULL;
+    item = &entry->next;
+
+    if (item->next == &section->entries)
+       return NULL;
+    entry  = list_entry(item->next, struct cfg_entry, next);
+    e_last = entry;
+    return entry->name;
+}
+
+char*
+cfg_entries_prev(char *dname, char *sname, char *current)
+{
+    struct list_head   *item;
+    struct cfg_section *section;
+    struct cfg_entry   *entry;
+
+    section = cfg_get_sec(dname,sname);
+    if (NULL == section)
+       return NULL;
+    entry = cfg_find_entry(section,current);
+    if (NULL == entry)
+       return NULL;
+    item = &entry->next;
+
+    if (item->prev == &section->entries)
+       return NULL;
+    entry  = list_entry(item->prev, struct cfg_entry, next);
+    e_last = entry;
+    return entry->name;
+}
+
+unsigned int cfg_entries_count(char *dname, char *sname)
+{
+    struct list_head   *item;
+    struct cfg_section *section;
+    int count = 0;
+
+    section = cfg_get_sec(dname,sname);
+    if (NULL != section)
+       list_for_each(item,&section->entries)
+           count++;
+    return count;
+}
+
+char* cfg_entries_index(char *dname, char *sname, int i)
+{
+    struct list_head   *item;
+    struct cfg_section *section;
+    struct cfg_entry   *entry;
+    int count = 0;
+
+    section = cfg_get_sec(dname,sname);
+    if (NULL == section)
+       return NULL;
+    
+    list_for_each(item,&section->entries) {
+       if (i == count) {
+           entry  = list_entry(item, struct cfg_entry, next);
+           e_last = entry;
+           return entry->name;
+       }
+       count++;
+    }
+    return NULL;
+}
+
+char* cfg_search(char *dname, char *sname, char *ename, char *value)
+{
+    struct list_head   *item1,*item2;
+    struct cfg_domain  *domain;
+    struct cfg_section *section;
+    struct cfg_entry   *entry;
+
+    domain = cfg_find_domain(dname);
+    if (NULL == domain)
+       return NULL;
+    list_for_each(item1,&domain->sections) {
+       section = list_entry(item1, struct cfg_section, next);
+       if (sname && 0 != strcasecmp(section->name,sname))
+           continue;
+       if (!ename)
+           return section->name;
+       list_for_each(item2,&section->entries) {
+           entry = list_entry(item2, struct cfg_entry, next);
+           if (0 != strcasecmp(entry->name,ename))
+               continue;
+           if (0 == strcasecmp(entry->value,value))
+               return section->name;
+       }
+    }
+    return NULL;
+}
+
+/* ------------------------------------------------------------------------ */
+/* get config data                                                          */
+
+char*
+cfg_get_str(char *dname, char *sname, char *ename)
+{
+    struct cfg_entry   *entry;
+
+    entry = cfg_get_ent(dname, sname, ename);
+    if (NULL == entry)
+       return NULL;
+    return entry->value;
+}
+
+unsigned int
+cfg_get_int(char *dname, char *sname, char *ename, unsigned int def)
+{
+    char *val;
+
+    val = cfg_get_str(dname,sname,ename);
+    if (NULL == val)
+       return def;
+    return atoi(val);
+}
+
+signed int
+cfg_get_signed_int(char *dname, char *sname, char *ename, signed int def)
+{
+    char *val;
+
+    val = cfg_get_str(dname,sname,ename);
+    if (NULL == val)
+       return def;
+    return atoi(val);
+}
+
+float
+cfg_get_float(char *dname, char *sname, char *ename, float def)
+{
+    char *val;
+
+    val = cfg_get_str(dname,sname,ename);
+    if (NULL == val)
+       return def;
+    return atof(val);
+}
+
+int
+cfg_get_bool(char *dname, char *sname, char *ename, int def)
+{
+    static char *yes[] = { "true",  "yes", "on",  "1" };
+    char *val;
+    int i;
+    int retval = 0;
+
+    val = cfg_get_str(dname,sname,ename);
+    if (NULL == val)
+       return def;
+    for (i = 0; i < sizeof(yes)/sizeof(yes[0]); i++)
+       if (0 == strcasecmp(val,yes[i]))
+           retval = 1;
+    return retval;
+}
+
+/* ------------------------------------------------------------------------ */
+/* get/set flags                                                            */
+
+unsigned int cfg_get_sflags(char *dname, char *sname)
+{
+    struct cfg_section *section;
+
+    section = cfg_get_sec(dname, sname);
+    if (NULL == section)
+       return 0;
+    return section->flags;
+}
+
+unsigned int cfg_get_eflags(char *dname, char *sname, char *ename)
+{
+    struct cfg_entry   *entry;
+
+    entry = cfg_get_ent(dname, sname, ename);
+    if (NULL == entry)
+       return 0;
+    return entry->flags;
+}
+
+unsigned int cfg_set_sflags(char *dname, char *sname,
+                           unsigned int mask, unsigned int bits)
+{
+    struct cfg_section *section;
+
+    section = cfg_get_sec(dname, sname);
+    if (NULL == section)
+       return 0;
+    section->flags &= ~mask;
+    section->flags |= bits;
+    return section->flags;
+}
+
+unsigned int cfg_set_eflags(char *dname, char *sname, char *ename,
+                           unsigned int mask, unsigned int bits)
+{
+    struct cfg_entry   *entry;
+
+    entry = cfg_get_ent(dname, sname, ename);
+    if (NULL == entry)
+       return 0;
+    entry->flags &= ~mask;
+    entry->flags |= bits;
+    return entry->flags;
+}
diff --git a/parseconfig.h b/parseconfig.h
new file mode 100644 (file)
index 0000000..da87571
--- /dev/null
@@ -0,0 +1,65 @@
+/* config options */
+struct cfg_option {
+    char *domain;
+    char *section;
+    char *entry;
+};    
+struct cfg_cmdline {
+    char               letter;
+    char               *cmdline;
+    struct cfg_option  option;
+    char               *value;
+    char               *desc;
+    int                needsarg:1;
+    int                yesno:1;
+};
+void   cfg_parse_cmdline(int *argc, char **argv, struct cfg_cmdline *opt);
+void   cfg_help_cmdline(FILE *fp, struct cfg_cmdline *opt, int w1, int w2, int w3);
+
+/* file I/O */
+int    cfg_parse_file(char *dname, char *filename);
+int    cfg_write_file(char *dname, char *filename);
+
+/* update */
+void   cfg_set_str(char *dname, char *sname, char *ename,
+                  const char *value);
+void   cfg_set_int(char *dname, char *sname, char *ename, int value);
+void   cfg_set_bool(char *dname, char *sname, char *ename, int value);
+
+void   cfg_del_section(char *dname, char *sname);
+void   cfg_del_entry(char *dname, char *sname, char *ename);
+
+/* search */
+char*  cfg_sections_first(char *dname);
+char*  cfg_sections_next(char *dname, char *current);
+char*  cfg_sections_prev(char *dname, char *current);
+char*  cfg_sections_index(char *dname, int i);
+unsigned int cfg_sections_count(char *dname);
+
+char*  cfg_entries_first(char *dname, char *sname);
+char*  cfg_entries_next(char *dname, char *sname, char *current);
+char*  cfg_entries_prev(char *dname, char *sname, char *current);
+char*  cfg_entries_index(char *dname, char *sname, int i);
+unsigned int cfg_entries_count(char *dname, char *sname);
+
+#define cfg_sections_for_each(dname, item) \
+       for (item = cfg_sections_first(dname); NULL != item; \
+            item = cfg_sections_next(dname,item))
+
+char*  cfg_search(char *dname, char *sname, char *ename, char *value);
+
+/* read */
+char*  cfg_get_str(char *dname, char *sname, char *ename);
+unsigned int cfg_get_int(char *dname, char *sname,
+                        char *ename, unsigned int def);
+signed int  cfg_get_signed_int(char *dname, char *sname,
+                              char *ename, signed int def);
+float  cfg_get_float(char *dname, char *sname, char *ename, float def);
+int    cfg_get_bool(char *dname, char *sname, char *ename, int def);
+
+unsigned int    cfg_get_sflags(char *dname, char *sname);
+unsigned int    cfg_get_eflags(char *dname, char *sname, char *ename);
+unsigned int    cfg_set_sflags(char *dname, char *sname,
+                              unsigned int mask, unsigned int bits);
+unsigned int    cfg_set_eflags(char *dname, char *sname, char *ename,
+                              unsigned int mask, unsigned int bits);
diff --git a/redir.c b/redir.c
new file mode 100644 (file)
index 0000000..9731715
--- /dev/null
+++ b/redir.c
@@ -0,0 +1,406 @@
+/*
+ *  Intel AMT tcp redirection protocol helper functions.
+ *
+ *  Copyright (C) 2007 Gerd Hoffmann <kraxel@redhat.com
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License along
+ *  with this program; if not, write to the Free Software Foundation, Inc.,
+ *  51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <ctype.h>
+#include <errno.h>
+
+#include "tcp.h"
+#include "redir.h"
+
+static const char *state_name[] = {
+    [ REDIR_NONE      ] = "NONE",
+    [ REDIR_CONNECT   ] = "CONNECT",
+    [ REDIR_INIT      ] = "INIT",
+    [ REDIR_AUTH      ] = "AUTH",
+    [ REDIR_INIT_SOL  ] = "INIT_SOL",
+    [ REDIR_RUN_SOL   ] = "RUN_SOL",
+    [ REDIR_INIT_IDER ] = "INIT_IDER",
+    [ REDIR_RUN_IDER  ] = "RUN_IDER",
+    [ REDIR_CLOSING   ] = "CLOSING",
+    [ REDIR_CLOSED    ] = "CLOSED",
+    [ REDIR_ERROR     ] = "ERROR",
+};
+
+static const char *state_desc[] = {
+    [ REDIR_NONE      ] = "disconnected",
+    [ REDIR_CONNECT   ] = "connection to host",
+    [ REDIR_INIT      ] = "redirection initialization",
+    [ REDIR_AUTH      ] = "session authentication",
+    [ REDIR_INIT_SOL  ] = "serial-over-lan initialization",
+    [ REDIR_RUN_SOL   ] = "serial-over-lan active",
+    [ REDIR_INIT_IDER ] = "IDE redirect initialization",
+    [ REDIR_RUN_IDER  ] = "IDE redirect active",
+    [ REDIR_CLOSING   ] = "redirection shutdown",
+    [ REDIR_CLOSED    ] = "connection closed",
+    [ REDIR_ERROR     ] = "failure",
+};
+
+/* ------------------------------------------------------------------ */
+
+static void hexdump(const char *prefix, const unsigned char *data, size_t size)
+{
+    char ascii[17];
+    int i;
+
+    for (i = 0; i < size; i++) {
+       if (0 == (i%16)) {
+           fprintf(stderr,"%s%s%04x:",
+                   prefix ? prefix : "",
+                   prefix ? ": "   : "",
+                   i);
+           memset(ascii,0,sizeof(ascii));
+       }
+       if (0 == (i%4))
+           fprintf(stderr," ");
+       fprintf(stderr," %02x",data[i]);
+       ascii[i%16] = isprint(data[i]) ? data[i] : '.';
+       if (15 == (i%16))
+           fprintf(stderr,"  %s\n",ascii);
+    }
+    if (0 != (i%16)) {
+       while (0 != (i%16)) {
+           if (0 == (i%4))
+               fprintf(stderr," ");
+           fprintf(stderr,"   ");
+           i++;
+       };
+       fprintf(stderr," %s\n",ascii);
+    }
+}
+
+static ssize_t redir_write(struct redir *r, const char *buf, size_t count)
+{
+    int rc;
+    
+    if (r->trace)
+       hexdump("out", buf, count);
+    rc = write(r->sock, buf, count);
+    if (-1 == rc)
+       snprintf(r->err, sizeof(r->err), "write(socket): %s", strerror(errno));
+    return rc;
+}
+
+static void redir_state(struct redir *r, enum redir_state new)
+{
+    enum redir_state old = r->state;
+
+    r->state = new;
+    if (r->cb_state)
+       r->cb_state(r->cb_data, old, new);
+}
+
+/* ------------------------------------------------------------------ */
+
+const char *redir_state_name(enum redir_state state)
+{
+    const char *name = NULL;
+
+    if (state < sizeof(state_name)/sizeof(state_name[0]))
+       name = state_name[state];
+    if (NULL == name)
+       name = "unknown";
+    return name;
+}
+
+const char *redir_state_desc(enum redir_state state)
+{
+    const char *desc = NULL;
+
+    if (state < sizeof(state_desc)/sizeof(state_desc[0]))
+       desc = state_desc[state];
+    if (NULL == desc)
+       desc = "unknown";
+    return desc;
+}
+
+int redir_connect(struct redir *r)
+{
+    static unsigned char *defport = "16994";
+    struct addrinfo ai;
+
+    memset(&ai, 0, sizeof(ai));
+    ai.ai_socktype = SOCK_STREAM;
+    ai.ai_family = PF_UNSPEC;
+    tcp_verbose = r->verbose;
+    redir_state(r, REDIR_CONNECT);
+    r->sock = tcp_connect(&ai, NULL, NULL, r->host,
+                         strlen(r->port) ? r->port : defport);
+    if (-1 == r->sock) {
+       redir_state(r, REDIR_ERROR);
+       return -1;
+    }
+    return 0;
+}
+
+int redir_start(struct redir *r)
+{
+    unsigned char request[START_REDIRECTION_SESSION_LENGTH] = {
+       START_REDIRECTION_SESSION, 0, 0, 0,  0, 0, 0, 0
+    };
+
+    memcpy(request+4, r->type, 4);
+    redir_state(r, REDIR_INIT);
+    return redir_write(r, request, sizeof(request));
+}
+
+int redir_stop(struct redir *r)
+{
+    unsigned char request[END_REDIRECTION_SESSION_LENGTH] = {
+       END_REDIRECTION_SESSION, 0, 0, 0
+    };
+
+    redir_state(r, REDIR_CLOSED);
+    redir_write(r, request, sizeof(request));
+    close(r->sock);
+    return 0;
+}
+
+int redir_auth(struct redir *r)
+{
+    int ulen = strlen(r->user);
+    int plen = strlen(r->pass);
+    int len = 11+ulen+plen;
+    int rc;
+    unsigned char *request = malloc(len);
+
+    memset(request, 0, len);
+    request[0] = AUTHENTICATE_SESSION;
+    request[4] = 0x01;
+    request[5] = ulen+plen+2;
+    request[9] = ulen;
+    memcpy(request + 10, r->user, ulen);
+    request[10 + ulen] = plen;
+    memcpy(request + 11 + ulen, r->pass, plen);
+    redir_state(r, REDIR_AUTH);
+    rc = redir_write(r, request, len);
+    free(request);
+    return rc;
+}
+
+int redir_sol_start(struct redir *r)
+{
+    unsigned char request[START_SOL_REDIRECTION_LENGTH] = {
+       START_SOL_REDIRECTION, 0, 0, 0,
+       0, 0, 0, 0,
+       MAX_TRANSMIT_BUFFER & 0xff,
+       MAX_TRANSMIT_BUFFER >> 8,
+       TRANSMIT_BUFFER_TIMEOUT & 0xff,
+       TRANSMIT_BUFFER_TIMEOUT >> 8,
+       TRANSMIT_OVERFLOW_TIMEOUT & 0xff,       TRANSMIT_OVERFLOW_TIMEOUT >> 8,
+       HOST_SESSION_RX_TIMEOUT & 0xff,
+       HOST_SESSION_RX_TIMEOUT >> 8,
+       HOST_FIFO_RX_FLUSH_TIMEOUT & 0xff,
+       HOST_FIFO_RX_FLUSH_TIMEOUT >> 8,
+       HEARTBEAT_INTERVAL & 0xff,
+       HEARTBEAT_INTERVAL >> 8,
+       0, 0, 0, 0
+    };
+
+    redir_state(r, REDIR_INIT_SOL);
+    return redir_write(r, request, sizeof(request));
+}
+
+int redir_sol_stop(struct redir *r)
+{
+    unsigned char request[END_SOL_REDIRECTION_LENGTH] = {
+       END_SOL_REDIRECTION, 0, 0, 0,
+       0, 0, 0, 0,
+    };
+
+    redir_state(r, REDIR_CLOSING);
+    return redir_write(r, request, sizeof(request));
+}
+
+int redir_sol_send(struct redir *r, unsigned char *buf, int blen)
+{
+    int len = 10+blen;
+    int rc;
+    unsigned char *request = malloc(len);
+
+    memset(request, 0, len);
+    request[0] = SOL_DATA_TO_HOST;
+    request[8] = blen & 0xff;
+    request[9] = blen >> 8;
+    memcpy(request + 10, buf, blen);
+    rc = redir_write(r, request, len);
+    free(request);
+    return rc;
+}
+
+int redir_sol_recv(struct redir *r)
+{
+    unsigned char msg[64];
+    int count, len, bshift;
+
+    len = r->buf[8] + (r->buf[9] << 8);
+    count = r->blen - 10;
+    if (count > len)
+       count = len;
+    bshift = count + 10;
+    if (r->cb_recv)
+       r->cb_recv(r->cb_data, r->buf + 10, count);
+    len -= count;
+
+    while (len) {
+       if (r->trace)
+           fprintf(stderr, "in+: need %d more data bytes\n", len);
+       count = sizeof(msg);
+       if (count > len)
+           count = len;
+       count = read(r->sock, msg, count);
+       switch (count) {
+       case -1:
+           snprintf(r->err, sizeof(r->err), "read(socket): %s", strerror(errno));
+           return -1;
+       case 0:
+           snprintf(r->err, sizeof(r->err), "EOF from socket");
+           return -1;
+       default:
+           if (r->trace)
+               hexdump("in+", msg, count);
+           if (r->cb_recv)
+               r->cb_recv(r->cb_data, msg, count);
+           len -= count;
+       }
+    }
+
+    return bshift;
+}
+
+int redir_data(struct redir *r)
+{
+    int rc, bshift;
+
+    if (r->trace) {
+       fprintf(stderr, "in --\n");
+       if (r->blen)
+           fprintf(stderr, "in : already have %d\n", r->blen);
+    }
+    rc = read(r->sock, r->buf + r->blen, sizeof(r->buf) - r->blen);
+    switch (rc) {
+    case -1:
+       snprintf(r->err, sizeof(r->err), "read(socket): %s", strerror(errno));
+       goto err;
+    case 0:
+       snprintf(r->err, sizeof(r->err), "EOF from socket");
+       goto err;
+    default:
+       if (r->trace)
+           hexdump("in ", r->buf + r->blen, rc);
+       r->blen += rc;
+    }
+
+    for (;;) {
+       if (r->blen < 4)
+           goto again;
+       bshift = 0;
+
+       switch (r->buf[0]) {
+       case START_REDIRECTION_SESSION_REPLY:
+           bshift = START_REDIRECTION_SESSION_REPLY_LENGTH;
+           if (r->blen < bshift)
+               goto again;
+           if (r->buf[1] != STATUS_SUCCESS) {
+               snprintf(r->err, sizeof(r->err), "redirection session start failed");
+               goto err;
+           }
+           if (-1 == redir_auth(r))
+               goto err;
+           break;
+       case AUTHENTICATE_SESSION_REPLY:
+           bshift = r->blen; /* FIXME */
+           if (r->blen < bshift)
+               goto again;
+           if (r->buf[1] != STATUS_SUCCESS) {
+               snprintf(r->err, sizeof(r->err), "session authentication failed");
+               goto err;
+           }
+           if (-1 == redir_sol_start(r))
+               goto err;
+           break;
+       case START_SOL_REDIRECTION_REPLY:
+           bshift = r->blen; /* FIXME */
+           if (r->blen < bshift)
+               goto again;
+           if (r->buf[1] != STATUS_SUCCESS) {
+               snprintf(r->err, sizeof(r->err), "serial-over-lan redirection failed");
+               goto err;
+           }
+           redir_state(r, REDIR_RUN_SOL);
+           break;
+       case SOL_HEARTBEAT:
+       case SOL_KEEP_ALIVE_PING:
+       case IDER_HEARTBEAT:
+       case IDER_KEEP_ALIVE_PING:
+           bshift = HEARTBEAT_LENGTH;
+           if (r->blen < bshift)
+               goto again;
+           if (HEARTBEAT_LENGTH != redir_write(r, r->buf, HEARTBEAT_LENGTH))
+               goto err;
+           break;
+       case SOL_DATA_FROM_HOST:
+           if (r->blen < 10) /* header length */
+               goto again;
+           bshift = redir_sol_recv(r);
+           if (bshift < 0)
+               goto err;
+           break;
+       case END_SOL_REDIRECTION_REPLY:
+           bshift = r->blen; /* FIXME */
+           if (r->blen < bshift)
+               goto again;
+           redir_stop(r);
+           break;
+       default:
+           snprintf(r->err, sizeof(r->err), "%s: unknown r->buf 0x%02x",
+                    __FUNCTION__, r->buf[0]);
+           goto err;
+       }
+
+       if (bshift == r->blen) {
+           r->blen = 0;
+           break;
+       }
+
+       /* have more data, shift by bshift */
+       if (r->trace)
+           fprintf(stderr, "in : shift by %d\n", bshift);
+       memmove(r->buf, r->buf + bshift, r->blen - bshift);
+       r->blen -= bshift;
+    }
+    return 0;
+
+again:
+    /* need more data, jump back into poll/select loop */
+    if (r->trace)
+       fprintf(stderr, "in : need more data\n");
+    return 0;
+
+err:
+    if (r->trace)
+       fprintf(stderr, "in : ERROR (%s)\n", r->err);
+    redir_state(r, REDIR_ERROR);
+    close(r->sock);
+    return -1;
+}
diff --git a/redir.h b/redir.h
new file mode 100644 (file)
index 0000000..92a4bda
--- /dev/null
+++ b/redir.h
@@ -0,0 +1,52 @@
+#include "RedirectionConstants.h"
+
+enum redir_state {
+    REDIR_NONE      =  0,
+    REDIR_CONNECT   =  1,
+    REDIR_INIT      =  2,
+    REDIR_AUTH      =  3,
+    REDIR_INIT_SOL  = 10,
+    REDIR_RUN_SOL   = 11,
+    REDIR_INIT_IDER = 20,
+    REDIR_RUN_IDER  = 21,
+    REDIR_CLOSING   = 30,
+    REDIR_CLOSED    = 31,
+    REDIR_ERROR     = 40,
+};
+
+struct redir {
+    /* host connection */
+    unsigned char     host[64];
+    unsigned char     port[16];
+    unsigned char     user[16];
+    unsigned char     pass[16];
+
+    /* serial-over-lan */
+    unsigned char     type[4];
+    int               verbose;
+    int               trace;
+    enum redir_state  state;
+    unsigned char     err[128]; // state == REDIR_ERROR
+
+    int               sock;
+    unsigned char     buf[64];
+    unsigned int      blen;
+
+    /* callbacks */
+    void *cb_data;
+    void (*cb_state)(void *cb_data, enum redir_state old, enum redir_state new);
+    int (*cb_recv)(void *cb_data, unsigned char *buf, int len);
+};
+
+const char *redir_state_name(enum redir_state state);
+const char *redir_state_desc(enum redir_state state);
+
+int redir_connect(struct redir *r);
+int redir_start(struct redir *r);
+int redir_stop(struct redir *r);
+int redir_auth(struct redir *r);
+int redir_sol_start(struct redir *r);
+int redir_sol_stop(struct redir *r);
+int redir_sol_send(struct redir *r, unsigned char *buf, int blen);
+int redir_sol_recv(struct redir *r);
+int redir_data(struct redir *r);
diff --git a/tcp.c b/tcp.c
new file mode 100644 (file)
index 0000000..aec2647
--- /dev/null
+++ b/tcp.c
@@ -0,0 +1,173 @@
+/*
+ *  TCP helper functions.
+ *
+ *  Copyright (C) 2007 Gerd Hoffmann <kraxel@redhat.com
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License along
+ *  with this program; if not, write to the Free Software Foundation, Inc.,
+ *  51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <string.h>
+#include <unistd.h>
+#include <fcntl.h>
+
+#include "tcp.h"
+
+int tcp_verbose;
+
+/* ------------------------------------------------------------------ */
+
+static char *strfamily(int family)
+{
+    switch (family) {
+    case PF_INET6: return "ipv6";
+    case PF_INET:  return "ipv4";
+    case PF_UNIX:  return "unix";
+    }
+    return "????";
+}
+
+int tcp_connect(struct addrinfo *ai,
+               char *addr, char *port,
+               char *host, char *serv)
+{
+    struct addrinfo *res,*e;
+    struct addrinfo *lres, ask;
+    char uaddr[INET6_ADDRSTRLEN+1];
+    char uport[33];
+    char uhost[INET6_ADDRSTRLEN+1];
+    char userv[33];
+    int sock,rc,opt=1;
+
+    /* lookup peer */
+    ai->ai_flags = AI_CANONNAME;
+    if (0 != (rc = getaddrinfo(host, serv, ai, &res))) {
+       if (tcp_verbose)
+           fprintf(stderr,"getaddrinfo (peer): %s\n", gai_strerror(rc));
+       return -1;
+    }
+    for (e = res; e != NULL; e = e->ai_next) {
+       if (0 != getnameinfo((struct sockaddr*)e->ai_addr,e->ai_addrlen,
+                            uhost,INET6_ADDRSTRLEN,userv,32,
+                            NI_NUMERICHOST | NI_NUMERICSERV)) {
+           if (tcp_verbose)
+               fprintf(stderr,"getnameinfo (peer): oops\n");
+           continue;
+       }
+       if (-1 == (sock = socket(e->ai_family, e->ai_socktype,
+                                e->ai_protocol))) {
+           if (tcp_verbose)
+               fprintf(stderr,"socket (%s): %s\n",
+                       strfamily(e->ai_family),strerror(errno));
+           continue;
+       }
+        setsockopt(sock,SOL_SOCKET,SO_REUSEADDR,&opt,sizeof(opt));
+       if (NULL != addr || NULL != port) {
+           /* bind local port */
+           memset(&ask,0,sizeof(ask));
+           ask.ai_flags    = AI_PASSIVE;
+           ask.ai_family   = e->ai_family;
+           ask.ai_socktype = e->ai_socktype;
+           if (0 != (rc = getaddrinfo(addr, port, &ask, &lres))) {
+               if (tcp_verbose)
+                   fprintf(stderr,"getaddrinfo (local): %s\n",
+                           gai_strerror(rc));
+               continue;
+           }
+           if (0 != getnameinfo((struct sockaddr*)lres->ai_addr,
+                                lres->ai_addrlen,
+                                uaddr,INET6_ADDRSTRLEN,uport,32,
+                                NI_NUMERICHOST | NI_NUMERICSERV)) {
+               if (tcp_verbose)
+                   fprintf(stderr,"getnameinfo (local): oops\n");
+               continue;
+           }
+           if (-1 == bind(sock, lres->ai_addr, lres->ai_addrlen)) {
+               if (tcp_verbose)
+                   fprintf(stderr,"%s [%s] %s bind: %s\n",
+                           strfamily(lres->ai_family),uaddr,uport,
+                           strerror(errno));
+               continue;
+           }
+       }
+       /* connect to peer */
+       if (-1 == connect(sock,e->ai_addr,e->ai_addrlen)) {
+           if (tcp_verbose)
+               fprintf(stderr,"%s %s [%s] %s connect: %s\n",
+                       strfamily(e->ai_family),e->ai_canonname,uhost,userv,
+                       strerror(errno));
+           close(sock);
+           continue;
+       }
+       if (tcp_verbose)
+           fprintf(stderr,"%s %s [%s] %s open\n",
+                   strfamily(e->ai_family),e->ai_canonname,uhost,userv);
+       fcntl(sock,F_SETFL,O_NONBLOCK);
+       return sock;
+    }
+    return -1;
+}
+
+int tcp_listen(struct addrinfo *ai, char *addr, char *port)
+{
+    struct addrinfo *res,*e;
+    char uaddr[INET6_ADDRSTRLEN+1];
+    char uport[33];
+    int slisten,rc,opt=1;
+    
+    /* lookup */
+    ai->ai_flags = AI_PASSIVE;
+    if (0 != (rc = getaddrinfo(addr, port, ai, &res))) {
+       if (tcp_verbose)
+           fprintf(stderr,"getaddrinfo: %s\n",gai_strerror(rc));
+       exit(1);
+    }
+
+    /* create socket + bind */
+    for (e = res; e != NULL; e = e->ai_next) {
+       getnameinfo((struct sockaddr*)e->ai_addr,e->ai_addrlen,
+                   uaddr,INET6_ADDRSTRLEN,uport,32,
+                   NI_NUMERICHOST | NI_NUMERICSERV);
+       if (-1 == (slisten = socket(e->ai_family, e->ai_socktype,
+                                   e->ai_protocol))) {
+           if (tcp_verbose)
+               fprintf(stderr,"socket (%s): %s\n",
+                       strfamily(e->ai_family),strerror(errno));
+           continue;
+       }
+       opt = 1;
+        setsockopt(slisten,SOL_SOCKET,SO_REUSEADDR,&opt,sizeof(opt));
+       if (-1 == bind(slisten, e->ai_addr, e->ai_addrlen)) {
+           if (tcp_verbose)
+               fprintf(stderr,"%s [%s] %s bind: %s\n",
+                       strfamily(e->ai_family),uaddr,uport,
+                       strerror(errno));
+           continue;
+       }
+       listen(slisten,1);
+       break;
+    }
+    if (NULL == e)
+       return -1;
+
+    /* wait for a incoming connection */
+    if (tcp_verbose)
+       fprintf(stderr,"listen on %s [%s] %s ...\n",
+               strfamily(e->ai_family),uaddr,uport);
+    fcntl(slisten,F_SETFL,O_NONBLOCK);
+    return slisten;
+}
diff --git a/tcp.h b/tcp.h
new file mode 100644 (file)
index 0000000..f0224c0
--- /dev/null
+++ b/tcp.h
@@ -0,0 +1,11 @@
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <netdb.h>
+
+extern int tcp_verbose;
+
+int tcp_connect(struct addrinfo *ai,
+               char *addr, char *port,
+               char *host, char *serv);
+
+int tcp_listen(struct addrinfo *ai, char *addr, char *port);
Impressum, Datenschutz