From: Michael Gernoth Date: Mon, 12 May 2008 13:28:28 +0000 (+0200) Subject: import amtterm-1.0 X-Git-Url: http://git.zerfleddert.de/cgi-bin/gitweb.cgi/amt/commitdiff_plain/402f63cd3bf64377c6fed357a2c4856aa4864c69 import amtterm-1.0 --- 402f63cd3bf64377c6fed357a2c4856aa4864c69 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);