From 402f63cd3bf64377c6fed357a2c4856aa4864c69 Mon Sep 17 00:00:00 2001 From: Michael Gernoth Date: Mon, 12 May 2008 15:28:28 +0200 Subject: [PATCH] import amtterm-1.0 --- COPYING | 339 +++++++++++++++ GNUmakefile | 68 +++ INSTALL | 59 +++ RedirectionConstants.h | 79 ++++ VERSION | 1 + amt-howto.man | 130 ++++++ amtterm.c | 265 ++++++++++++ amtterm.man | 43 ++ amttool | 375 +++++++++++++++++ amttool.man | 72 ++++ gamt.c | 834 +++++++++++++++++++++++++++++++++++++ gamt.desktop | 8 + gamt.man | 48 +++ list.h | 168 ++++++++ mk/Autoconf.mk | 150 +++++++ mk/Compile.mk | 88 ++++ mk/Maintainer.mk | 28 ++ mk/Variables.mk | 54 +++ parseconfig.c | 915 +++++++++++++++++++++++++++++++++++++++++ parseconfig.h | 65 +++ redir.c | 406 ++++++++++++++++++ redir.h | 52 +++ tcp.c | 173 ++++++++ tcp.h | 11 + 24 files changed, 4431 insertions(+) create mode 100644 COPYING create mode 100644 GNUmakefile create mode 100644 INSTALL create mode 100644 RedirectionConstants.h create mode 100644 VERSION create mode 100644 amt-howto.man create mode 100644 amtterm.c create mode 100644 amtterm.man create mode 100755 amttool create mode 100644 amttool.man create mode 100644 gamt.c create mode 100644 gamt.desktop create mode 100644 gamt.man create mode 100644 list.h create mode 100644 mk/Autoconf.mk create mode 100644 mk/Compile.mk create mode 100644 mk/Maintainer.mk create mode 100644 mk/Variables.mk create mode 100644 parseconfig.c create mode 100644 parseconfig.h create mode 100644 redir.c create mode 100644 redir.h create mode 100644 tcp.c create mode 100644 tcp.h diff --git a/COPYING b/COPYING new file mode 100644 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. + + + Copyright (C) + + 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. + + , 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 index 0000000..bfc3f0e --- /dev/null +++ b/GNUmakefile @@ -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 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 diff --git a/RedirectionConstants.h b/RedirectionConstants.h new file mode 100644 index 0000000..994f04e --- /dev/null +++ b/RedirectionConstants.h @@ -0,0 +1,79 @@ +#ifndef __REDIRECTION_CONSTANTS__ +#define __REDIRECTION_CONSTANTS__ + +#define STATUS_SUCCESS 0x00 +#define SOL_FIRMWARE_REV_MAJOR 0x01 +#define SOL_FIRMWARE_REV_MINOR 0x00 + +//Session Manager Messages Formats +#define START_REDIRECTION_SESSION 0x10 +#define START_REDIRECTION_SESSION_REPLY 0x11 +#define END_REDIRECTION_SESSION 0x12 +#define AUTHENTICATE_SESSION 0x13 +#define AUTHENTICATE_SESSION_REPLY 0x14 + +#define START_REDIRECTION_SESSION_LENGTH 8 +#define START_REDIRECTION_SESSION_REPLY_LENGTH 13 +#define END_REDIRECTION_SESSION_LENGTH 4 + +//SOL Messages Formats +#define START_SOL_REDIRECTION 0x20 +#define START_SOL_REDIRECTION_REPLY 0x21 +#define END_SOL_REDIRECTION 0x22 +#define END_SOL_REDIRECTION_REPLY 0x23 +#define SOL_KEEP_ALIVE_PING 0x24 //Console to Host +#define SOL_KEEP_ALIVE_PONG 0x25 //Host to Console +#define SOL_DATA_TO_HOST 0x28 //Console to host +#define SOL_DATA_FROM_HOST 0x2A //Host to Console +#define SOL_HEARTBEAT 0x2B + +#define HEARTBEAT_LENGTH 8 +#define START_SOL_REDIRECTION_LENGTH 24 +#define START_SOL_REDIRECTION_REPLY_LENGTH 23 //TODO: There is a OEM Defined data field that we are assuming to be 0 bytes.. +#define END_SOL_REDIRECTION_LENGTH 8 +#define END_SOL_REDIRECTION_REPLY_LENGTH 8 + +//IDER Messages Formats +#define START_IDER_REDIRECTION 0x40 +#define START_IDER_REDIRECTION_REPLY 0x41 +#define END_IDER_REDIRECTION 0x42 +#define END_IDER_REDIRECTION_REPLY 0x43 +#define IDER_KEEP_ALIVE_PING 0x44 //Console to Host +#define IDER_KEEP_ALIVE_PONG 0x45 //Host to Console +#define IDER_RESET_OCCURED 0x46 +#define IDER_RESET_OCCURED_RESPONSE 0x47 +#define IDER_DISABLE_ENABLE_FEATURES 0x48 +#define IDER_DISABLE_ENABLE_FEATURES_REPLY 0x49 +#define IDER_HEARTBEAT 0x4B +#define IDER_COMMAND_WRITTEN 0x50 +#define IDER_COMMAND_END_RESPONSE 0x51 +#define IDER_GET_DATA_FROM_HOST 0x52 +#define IDER_DATA_FROM_HOST 0x53 +#define IDER_DATA_TO_HOST 0x54 + +#define START_IDER_REDIRECTION_LENGTH 18 +#define START_IDER_REDIRECTION_REPLY_LENGTH 30 //TODO: There is a OEM Defined data field that we are assuming to be 0 bytes.. +#define END_IDER_REDIRECTION_LENGTH 8 +#define END_IDER_REDIRECTION_REPLY_LENGTH 8 +#define IDER_RESET_OCCURED_LENGTH 9 +#define IDER_RESET_OCCURED_RESPONSE_LENGTH 8 +#define IDER_DISABLE_ENABLE_FEATURES_REPLY_LENGTH 13 +#define IDER_COMMAND_END_RESPONSE_LENGTH 31 +#define IDER_GET_DATA_FROM_HOST_LENGTH 31 + +static const unsigned int SOL_SESSION = 0x204C4F53; +static const unsigned int IDER_SESSION = 0x52454449; + +static const unsigned short MAX_TRANSMIT_BUFFER = 1000; +static const unsigned short TRANSMIT_BUFFER_TIMEOUT = 100; +static const unsigned short TRANSMIT_OVERFLOW_TIMEOUT = 0; +static const unsigned short HOST_SESSION_RX_TIMEOUT = 10000; +static const unsigned short HOST_FIFO_RX_FLUSH_TIMEOUT = 0; +static const unsigned short HEARTBEAT_INTERVAL = 5000; + +static const unsigned int SESSION_MANAGER_OEM_IANA_NUMBER = 0x5555; //TODO: Test +static const unsigned int SOL_OEM_IANA_NUMBER = 0x6666; //TODO: Test + +static const unsigned short RECEIVE_BUFFER_SIZE = 0x100; + +#endif diff --git a/VERSION b/VERSION new file mode 100644 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 index 0000000..253154a --- /dev/null +++ b/amt-howto.man @@ -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 diff --git a/amtterm.c b/amtterm.c new file mode 100644 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 +#include +#include +#include +#include +#include +#include +#include +#include + +#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 \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 index 0000000..ee99dd7 --- /dev/null +++ b/amtterm.man @@ -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 +Specify username, defaults to "admin". +.TP +.B -p +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 diff --git a/amttool b/amttool new file mode 100755 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 < [ ] +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 - 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 index 0000000..e687881 --- /dev/null +++ b/amttool.man @@ -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 diff --git a/gamt.c b/gamt.c new file mode 100644 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 +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#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 ", 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[] = +"\n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +"\n"; + +static char hosts_xml_start[] = +"\n" +" \n" +" \n"; + +static char hosts_xml_end[] = +" \n" +" \n" +"\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, + " \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 \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 index 0000000..a406fb1 --- /dev/null +++ b/gamt.desktop @@ -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 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 +Specify username, defaults to "admin". +.TP +.B -p +Specify password. +.B gamt +will prompt you if unspecified. +.TP +.B -f +Specify terminal font, defaults to "monospace 12". +.TP +.B -c +Specify terminal text color, defaults to "gray". +.TP +.B -b +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 diff --git a/list.h b/list.h new file mode 100644 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 index 0000000..65cd915 --- /dev/null +++ b/mk/Autoconf.mk @@ -0,0 +1,150 @@ +# +# simple autoconf system for GNU make +# +# (c) 2002-2006 Gerd Hoffmann +# +# 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 index 0000000..da14d58 --- /dev/null +++ b/mk/Compile.mk @@ -0,0 +1,88 @@ +# +# some rules to compile stuff ... +# +# (c) 2002-2006 Gerd Hoffmann +# +# 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 index 0000000..23446ae --- /dev/null +++ b/mk/Maintainer.mk @@ -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 index 0000000..fdb1653 --- /dev/null +++ b/mk/Variables.mk @@ -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 index 0000000..79e2457 --- /dev/null +++ b/parseconfig.c @@ -0,0 +1,915 @@ +/* + * functions to read and write config files + * + * Copyright (C) 2007 Gerd Hoffmann +#include +#include +#include +#include + +#include +#include + +#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(§ion->entries); + list_add_tail(§ion->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,§ion->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,§ion->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(§ion->next); + while (!list_empty(§ion->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 ",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,§ion->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 = §ion->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 = §ion->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 = §ion->entries; + if (item->next == §ion->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 == §ion->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 == §ion->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,§ion->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,§ion->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,§ion->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 index 0000000..da87571 --- /dev/null +++ b/parseconfig.h @@ -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 index 0000000..9731715 --- /dev/null +++ b/redir.c @@ -0,0 +1,406 @@ +/* + * Intel AMT tcp redirection protocol helper functions. + * + * Copyright (C) 2007 Gerd Hoffmann +#include +#include +#include +#include +#include + +#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 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 index 0000000..aec2647 --- /dev/null +++ b/tcp.c @@ -0,0 +1,173 @@ +/* + * TCP helper functions. + * + * Copyright (C) 2007 Gerd Hoffmann +#include +#include +#include +#include +#include + +#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 index 0000000..f0224c0 --- /dev/null +++ b/tcp.h @@ -0,0 +1,11 @@ +#include +#include +#include + +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); -- 2.39.5