pax_global_header00006660000000000000000000000064146122116170014513gustar00rootroot0000000000000052 comment=03610a03a960f9c39c5f13759dcc4d9b4ef4a902 ser2net-4.6.2/000077500000000000000000000000001461221161700131065ustar00rootroot00000000000000ser2net-4.6.2/.editorconfig000066400000000000000000000002771461221161700155710ustar00rootroot00000000000000root = true [*] end_of_line = lf trim_trailing_whitespace = true insert_final_newline = true [{*.c,*.h}] indent_style = space indent_size = 4 [*.yaml] indent_style = space indent_size = 2 ser2net-4.6.2/.gitignore000066400000000000000000000003361461221161700151000ustar00rootroot00000000000000config.* configure Makefile Makefile.in tags ser2net.spec missing aclocal.m4 libtool ltmain.sh depcomp install-sh /.deps/ /autom4te.cache/ *.o *~ *.pyc *.cmd .tmp_versions Module.* modules.* *.ko *.mod.c *.ur-safe ser2net ser2net-4.6.2/AUTHORS000066400000000000000000000001041461221161700141510ustar00rootroot00000000000000Corey Minyard - Original (and still only) author. ser2net-4.6.2/COPYING000066400000000000000000000432541461221161700141510ustar00rootroot00000000000000 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. ser2net-4.6.2/ChangeLog000066400000000000000000000537631461221161700146760ustar00rootroot000000000000002014-01-24 Corey Minyard * ser2net.h, Makefile.am: Move some things into a new ser2net.h file. * devcfg.c, controller.c, dataxfer.c, ser2net.c, utils.c: Add the ser2net.h include file. * dataxfer.c: Fix some EAGAIN handling. 2014-01-23 Corey Minyard * utils.h, readconfig.c, dataxfer.c: Rework the find_xxx functions to return a strdup of the string. * dataxfer.c, dataxfer.h, devcfg.h: Remove the dinfo structure and move the contents into the port structure, and remove the now empty devcfg.h. * configure.ac: Minor rework. 2014-01-22 Corey Minyard * io.h, devcfg.h, devcfg.c, dataxfer.c, buffer.c, buffer.h: Abstract the device I/O operations. * utils.c, utils.h, controller.h, controller.c: Fix some const correctness issues * conroller.c, controller.h: Add printf-style output. * dataxfer.h, dataxfer.c, devcfg.h, devcfg.c, io.h, readconfig: Rework the error and output handling to make it more flexible, so errors can be output with useful information from the lower levels. 2013-08-08 Corey Minyard * utils.c, utils.h, dataxfer.c, controller.c: Rework the handling of addresses to work properly with all the return values of getaddrinfo, and to prefer IPV6 over IPV4 when opening the socket. This means if you specify a number, it will open the IPV6 ::,port address first. This was a big cleanup, and moved a lot of the base socket handling into utils.h. * dataxfer.c: Rework the stdio handling so it doesn't rely on the port number in the address, since that is now gone. * dataxfer.c: Set port->closestr to NULL when freeing it to avoid a double-free. * readconfig.c, ser2net.8: Rework the input handling so an IPV6 can be specified as part of the portnum parameter. This was hard because IPV6 addresses have ':' in them. Basically, if there is a "," in the port specification, assume that everything from the beginning of the line to the ':' after the ',' is the port number. * utils.c, utils.h, dataxfer.c, controller.c: Modified address handling so that ser2net opens all the addresses returned by getaddrinfo, not just the first one. That should make things work correctly in a lot more cases. 2013-08-07 Corey Minyard * utils.c, controller.c, dataxfer.c: Return an errno from scan_tcp_port() so it can return an out of memory error. * controller.c, controller.h, ser2net.c: Add an out of memory return code from controller_init(). 2013-07-31 Longshine * utils.c: Copy input string to a temporary buffer before calling strtok_r in scan_tcp_port. 2013-07-28 Corey Minyard * configure.ac: Move to version 2.9.1. There was an issue with the configure setup. So autoreconf and redistribute. 2013-07-26 Corey Minyard * dataxfer.c: Fix a bug in format strings where hex element (\x) would have the wrong values for a-f. Pointed out by Galen Seitz. 2013-07-26 Corey Minyard * configure.ac: Version 2.9. 2013-07-25 Yegor Yefremov * Make RFC2217 signature configurable: Add '-s' option to configure default signature, that can be requested via RFC2217 protocol (option 0). Add per port signature via SIGNATURE keyword. 2013-06-29 Yegor Yefremov * dataxfer.c: The baudrate, that will be sent to the client, was converted twice: first with htonl() and then via copying to the buffer. As result 38400 was transfered as 6500960000 instead of 6500009600. 2013-05-07 Corey Minyard * buffer.c: Add a buffer wrap that was missed, identified by Brian Rzycki. 2013-03-01 Corey Minyard * Makefile.am: Added 'ACLOCAL_AMFLAGS = -I m4' as suggested by libtoolize. * configure.in -> configure.ac * configure.ac: Add 'AC_CONFIG_MACRO_DIR([m4])' as suggested by libtoolize. * configure.ac: Version 2.8. 2013-02-28 Corey Minyard * dataxfer.c, readconfig.c: Properly handle an empty string passed as a banner, openstr, or closestr. 2013-01-29 Corey Minyard * utils.h, utils.c: Have scan_tcp_port() take the domain. * dataxfer.c, controller.c: If the socket open fails with EAFNOSUPPORT, retry the address with IF_INET (IPV4-only). This way the call will still work even if IPV6 is disabled. 2012-12-19 Corey Minyard * README: Add info on how to generate configure and friends. 2012-12-13 Jordi Riera-Babures , Corey Minyard * devcfg.c: Add support for a bunch of high-speed baud rates. 2012-12-07 Corey Minyard * utils.c: Change hints.ai_family from AF_INET6 to AF_UNSPEC. That way it won't force IPV6, but it will return either. I think that's right. 2012-12-04 Corey Minyard * utils.c, utils.h: Modify scan_tcp_port to return the length of the socket info returned by getaddrinfo(). * controller.c, dataxfer.c: Use the length returned by scan_tcp_port(), not the size of sockaddr_storage, for the length of the socket information. 2012-09-26 Corey Minyard * configure.in: Move to 2.8-rc3 * dataxfer.c, dataxfer.h, selector.c, selector.h, ser2net.c: Add clean shutdown handling so locks are properly removed when the program is killed with SIGINT. * dataxfer.c: Modify the lock files to be Linux FHS compiliant. 2012-09-10 Corey Minyard * dataxfer.c: Fix a bug reported by Jan-Benedict Glaw, where port->dinfo.openstr may be NULL, but srdup is called on it without checking. Add a check. 2012-07-23 qchats lee * dataxfer.c: My change for this did not match Qchats' change, so fix that. * utils.c: Fix the write_full() function to handle the return result properly; the parenthesis in the if statement ended in the wrong place. 2012-07-22 qchats lee * dataxfer.c: Add a missing ")" after the "OPEN(..." when header tracing. 2012-04-18 Corey Minyard * buffer.c, buffer.h, Makefile.am: Added a new type and set of operations for buffer handling. This simplifies and allows abstraction of other parts of the code. * dataxfer.c, telnet.c, telnet.h, controller.c: Switch out_telnet_cmd and associated parts to be the new buffer type. * dataxfer.c: Switch the banner over to the new buffer type. * dataxfer.c: Consolidate banner and filename string handling. Note that there was an issue with \s, as it was something different in banners and filenames. Added new options, \S (for seconds) and \B (for serial port parameters) to resolve this issue, and \s is backwards compatible but deprecated. * ser2net.8, ser2net.conf: Since the consolidated banner/filename processing allows all the \ options to be available for both banners and filenames, rework the documentation. * dataxfer.c: Convert the main I/O buffers for data transfer to the new buffer type. * devcfg.h, devcfg.c, readconfig.c, utils.h: Added a new OPENSTR and CLOSESTR option for the configuration file. These are strings that will be transmitted to the device when a port is opened and/or closed. * ser2net.8, ser2net.conf: Added docs for the OPENSTR and CLOSESTR options. * dataxfer.c: Handle the new OPENSTR and CLOSESTR options. This required adding a new state, PORT_CLOSING, for the tcp_to_dev_state to say that the port is closing but data is going out, and splitting up the shutdown handling. * utils.c: Fixed a bug that cause the "port in use" error on the TCP connection to be printed strangely. * dataxfer.c: Since the banner handling no longer depends on being able to put the whole banner in the output buffer, reduce the port buffer sizes to 64 bytes to reduce memory usage * selector.c: maxfd was not initialized for a selector, fix that. * configure.in: Move to 2.8-rc2. 2012-04-04 Corey Minyard * dataxfer.c: Turn on the tcp write handler at first for raw sockets, otherwise the banner won't come out till some data comes in on the port. 2012-04-03 Corey Minyard * devcfg.c: Add 600 baud. 2012-03-27 Corey Minyard * ser2net.c, readconfig.c, ser2net.h, controller.c, ser2net.conf: Add a method to specify the control port in the ser2net config file. * dataxfer.c, controller.c: Add SO_KEEPALIVE to socket connections so the connection will drop if the other end just goes away. * configure.in: Move to 2.8-rc1. 2011-08-12 Corey Minyard * dataxfer.c: Fix some unaligned write issues in telnet options. * dataxfer.c: Fix some signed/unsigned comparison issues and a typo. 2011-06-22 Corey Minyard * utils.h, utils.c, controller.c, dataxfer.c: Fix some warnings about the write() return value not being checked. These were almost all cases where a failure didn't matter (tracing), except for writing the pid file, where it should have been a handled failure. * dataxfer.c: Cleaned up some style issues in the UUCP locking code. * ser2net.c: Fixed a warning about not checking the chdir() return code, and added some improved logging information to syslog. 2010-03-11 Mats Erik Andersson , Corey Minyard * controller.c, dataxfer.c, utils.c, utils.h: Add IPV6 address compatability. 2009-11-12 Corey Minyard * ser2net.spec, ser2net.spec.in: Fix the version in the spec file to be updated automatically. 2009-11-12 Philip Gwyn * dataxfer.c, devcfg.c, devcfg.h, ser2net.8: Add the ability to dump trace files in a binary format with timestamps. 2009-11-03 Corey Minyard * configure.in: Move version to 2.7 for release. 2009-10-23 Corey Minyard * configure.in: Move to 2.7-rc3. * dataxfer.c, devcfg.c, devcfg.h, ser2net.h: Modified the tb= directive to work separately from tw= and tr=, so you can do all three at the same time independently. * readconfig.c: Improved an error report. 2009-10-22 Corey Minyard * ser2net.c: Set up routing errors to stderr before reading the config so config errors come out properly to stdout. * ser2net.8, ser2net.conf: Fix the documentation, it's tw=, not tw: for the tracefile stuff. * configure.in: Move to 2.7-rc2. 2009-10-15 Corey Minyard * dataxfer.c, devcfg.c, readconfig.c, ser2net.8, ser2net.conf: Add the ability to trace data read written to trace files. 2009-07-19 Geoff Simmons * ser2net.conf: Some documentation cleanup, added missing docs for the timeout parameter. * README - Minor doc cleanup. 2009-07-15 Corey Minyard * configure.in: Move to version 2.6. 2009-06-30 Corey Minyard * dataxfer.c, devcfg.c, devcfg.h, readconfig.h, utils.h: Add the a TRACEFILE option to trace all I/O to a file., and separate out the defconfig into a separate structure to avoid passing a boatload of things to the config. 2009-06-30 Sebastian Andrzej Siewior * controller.c: Fix use after free in controller The controller will use its dynamically allocated data after it got free() in error path. What we see in syslog is: Jun 30 10:26:38 consrv3 ser2net[3073]: read error for controller port: Connection reset by peer Jun 30 10:26:39 consrv3 ser2net[3073]: The tcp write for controller had error: Bad file descriptor The first error is "legal" because the destitnation decided to close its socket a little to early than expected. The second error is allready bad because it tries to use allready deallocated fd. Later we segfault. 2007-11-16 Corey Minyard * configure.in: Move to version 2.5. 2007-11-16 Harry Flick, Corey Minyard * devcfg.c, devcfg.h, dataxfer.c, ser2net.conf, ser2net.8: Allow a "NOBREAK" option to disable automatically messing with the break operations on the serial port. Useful for systems with a broken break. 2007-11-16 Robert Edmonds ,Corey Minyard * ser2net.c, ser2net.8: Add a '-P' option to specific that a pidfile be created. 2007-05-03 Corey Minyard * dataxfer.c: Change the rfc2217 signature response code from 0 to 100. Best guess on what this should be, as the RFC doesn't give the correct value and says "text". 2007-04-26 Corey Minyard * dataxfer.c: Disable the Nagle algorithm (enable TCP_NODELAY) on the TCP ports to avoid long delays on short pieces of data. * configure.in: Move to version 2.4. 2007-03-19 Corey Minyard * dataxfer.c: Use the proper value for modem data notifies sent to the client, it was 7, use 107 instead. 2006-12-01 Corey Minyard * readconfig.c: Handle an ending banner that does not have a '\n' in the last line. 2006-01-16 Corey Minyard * ser2net.8: Fix the documentation for control ports. 2006-01-09 Corey Minyard * dataxfer.c: Don't kill the session if TIOCCBRK doesn't work. 2005-12-29 Corey Minyard * dataxfer.c, devcfg.c, devcfg.h, ser2net.conf, ser2net.8: Added a '\s' banner token for printing out the serial parameters. * ser2net.conf: Cleaned up the ugly banner3. * Makefile.am: Add ser2net.init to EXTRA_DIST. * configure.in: Move to version 2.3. 2005-12-29 Aurelien Jarno * devcfg.c: Don't include "termio.h", but do include "sys/ioctl.h". 2005-10-20 Corey Minyard * dataxfer.c: Fixed a problem with rfc2217 stop bit size setting. It was interpreting the value of "3" as 2 stop bits, but 3 is for 1.5 stop bits, and 2 is for 2 stop bits. * controller.c: Fixed some warnings dealing with char/unsigned char problems. * dataxfer.c: ditto * telnet.c: ditto 2005-08-25 Corey Minyard * dataxfer.c: Fix a problem wih EAGAIN just returning and not retrying the operation on writing data that has just been read. Thanks to Shinlun Hsieh for pointing this out. 2005-07-07 G Goodwill * dataxfer.c, devcfg.c: Added support for cygwin. 2005-07-07 Rajiv Aaron Manglani * configure.in: Make the use of tcp wrappers optional. 2005-06-13 Yura Kalinichenko * controller.c, devcfg.c, ser2net.8, ser2net.conf: Added 57600 baud support. * dataxfer.c: Don't to breaks on printer lines. 2005-02-12 Corey Minyard * configure.in: Move to version 2.2. 2005-02-11 Corey Minyard * dataxfer.c: Add a missing break statement after case 7 in the com_port_handler() function. 2004-11-29 Corey Minyard * dataxfer.c: Remove UUCP lock if the setup of the device fails. 2004-05-08 Marc Haber * ser2net.h: Set the man page section to the right value. 2004-01-24 Corey Minyard * dataxfer.c: Don't attempt to disconnect an unconnected port. 2003-12-20 Corey Minyard * dataxfer.c, ser2net.8: Added the ability to have port 0 mean use standard in/out. This way, you can invoke ser2net from inetd. * devcfg.c: Allow "," to be used as an option separator. * readconfig.c: Fix handling of lines coming from "-C". * ser2net.c: Move creation of the selector to before the option processing, for support of "-C'. 2003-12-19 Corey Minyard * dataxfer.c: Handle receive IACs from the terminal to the TCP port, duplicating them so they will come through right. * telnet.c: Reset the telnet protocol state after two IACs are received. * readconfig.c, readconfig.h, ser2net.c, ser2net.8: Added a way to specify individual config lines on the command line so that no config file is necessary. 2003-12-04 Corey Minyard * dataxfer.c: Add responses for all the telnet com port control commands that we handle. * telnet.c: Fixed IAC processing in suboption to be able to handle a stream of IACs properly. 2003-12-04 Corey Minyard * configure.in: Moved to version 2.1. * dataxfer.c: Have the telnet option responses use the 1xx responses to the com port control options. I believe this is wrong, but it is consistent with other products already in the field. * dataxfer.c, ser2net.c, telnet.h: Added support for setting the use of Cisco IOS baud rates instead of RFC 2217 ones, by command option. * selector.c, ser2net.c: Cleaned up some compile warnings. 2003-10-14 Corey Minyard * configure.in: Moved to version 2.0. * datafer.c, devcfg.c, devcfg.h, readconfig.c, utils.h: Added banner support. * ser2net.h, ser2net.conf: Added documentation for banner support. * dataxfer.c, telnet.c, telnet.h: Created a more generic telnet command handler and split it off from dataxfer.c * controller.c: Moved to the new telnet command handler. * dataxfer.c: Added watching the CD, RI, etc. lines. 2003-10-13 Corey Minyard * dataxfer.c: Added a lot of the com port control handling. 2003-10-12 Corey Minyard * dataxfer.c: Added real telnet option processing infrastructure, getting read for com port control handling. 2003-04-22 Corey Minyard * configure.in, ser2net.spec: Move to version 1.9. * selector.c: Turned off debugging code that accidentally got left on. 2003-02-14 Corey Minyard * configure.in, ser2net.spec: Move to version 1.8. * dataxfer.c: Do a tcflush(fd, TCOFLUSH) before closing the serial device, this avoids blocking on close. 2002-11-29 Corey Minyard * selector.c: More timer heap bugs. 2002-10-01 Corey Minyard * selector.c: Fixed bugs in the timer heaps. 2002-09-23 Corey Minyard * selector.c: Fixed a bug in the timer heaps. 2002-09-09 Corey Minyard * all: Reworked the selector code to make timers more general. 2002-07-24 Corey Minyard * dataxfer.c: Fixed a bug with handling config errors, when an error occurs in the wrong place it will segv. 2002-04-04 Corey Minyard * util.c: added the final return value to scan_tcp_port. 2002-02-20 Corey Minyard * controller.c, dataxfer.c, dataxfer.h, ser2net.8: Added a "short" display, where each port is shown on one line. * dataxfer.c, dataxfer.h, readconfig.c, selector.c, selector.h, ser2net.8, ser2net.c: Added the ability to reread the configuration file on a SIGHUP. * configure.in, ser2net.spec: Moved to version 1.6 2002-02-20 Przemyslaw Czerpak (druzus@polbox.com), Corey Minyard * dataxfer.c, dataxfer.h, configure.in, ser2net.c, ser2net.8: Added UUCP-style locking for ports. 2002-02-19 Corey Minyard * controller.c, controller.h, dataxfer.c, devcfg.c, ser2net.c, ser2net.conf, ser2net.8, utils.c utils.h: Added support for specifying the IP address with the port to bind to specific interfaces. * dataxfer.c: Added counting the input and output bytes. 2002-02-19 Przemyslaw Czerpak (druzus@polbox.com) * controller.c, dataxfer.c, devcfg.c, ser2net.c, ser2net.conf, ser2net.8: Added support for turning on/off RTS/CTS, XON/XOFF, and LOCAL. Also added support for raw LP devices. 2001-10-05 Przemyslaw Czerpak (druzus@polbox.com) * controller.c, dataxfer.c, devcfg.c, ser2net.init, ser2net.spec: Added an init, and cleaned up a bunch of type warnings. * devcfg.c, ser2net.8, ser2net.conf: Added support for RTS/CTS. 2001-08-11 Corey Minyard * devcfg.h, devcfg.c, controller.c, dataxfer.c, dataxfer.h, ser2net.8: Ben Adams sent a patch to control the DTR and RTS lines, I rewrote it to make a new command to do this (instead of using devconfig, which didn't really match the devconfig's operation). * configure.in, ser2net.spec: Updated to version 1.5 2001-07-26 Corey Minyard * devcfg.c - Added CREAD to the cflags so the serial port will work, patch from Andreas Pfaller. * dataxfer.c - Added binary transmission to the list of telnet opeions, again from Andreas Pfaller. 2001-07-23 Corey Minyard * devcfg.c, ser2net.8, ser2net.conf - Added a patch from Martin Boese to add CLOCAL and XON/XOFF support as serial port options. 2001-07-03 Corey Minyard * Makefile.am, ser2net.1, ser2net.8, ser2net.spec - Moved ser2net to /usr/sbin, renamed the makefile to have a .8 extension and put it in /usr/share/man. * controller.c - Fixed problems with telnet handling in the control interface. * Updated to version 1.4 2001-06-29 Corey Minyard * ser2net.spec - Actually put the changed version number in the file, set the prefix to '/usr'. * configure.in - Added libnsl for Redhat systems, changed to AC_CHECK_LIB. * all - changed to version 1.3. 2001-06-28 Corey Minyard * dataxfer.c - Added the ability to convert a telnet break to a serial port break. * configure.in - Updated to version 1.3 * ser2net.spec - Added this file for RPM building, from Ivan Francolin Martinez. * dataxfer.c - Added a tcp_wrappers implementation, from Ivan Francolin Martinez. However, some configure work needs to be done to make it actually work properly. * dataxfer.c, controller.c, configure.in, ser2net.1 - Fixed the tcp_wrappers stuff to work properly, did the config stuff for it, and added it to the control port as well. 2001-06-15 Corey Minyard * controller.c - Made sure the monitor port was set to null when the port exited. * ser2net.c, ser2net.1 - Added a "-d" option to send debug output to the console and modified "-n" to just not detach. 2001-06-05 Corey Minyard * ser2net.c, controller.c - added a way to query the version from the command line and from the controller port. * controller.c, controller.h, dataxfer.c, dataxfer.h - added monitoring of data flowing into a port. It's somewhat primitive yet, but still useful * controller.c, dataxfer.c, dataxfer.h - added a way to disconnect a port from the control port. 2001-06-04 Corey Minyard * Initial creation. ser2net-4.6.2/INSTALL000066400000000000000000000002631461221161700141400ustar00rootroot00000000000000This program follows the standard GNU installation procedure. To install, do: ./configure make make install That's pretty simple. Thanks, FSF, for the cool automake stuff. ser2net-4.6.2/Makefile.am000066400000000000000000000015551461221161700151500ustar00rootroot00000000000000sbin_PROGRAMS = ser2net ACLOCAL_AMFLAGS = -I m4 AM_CFLAGS=-Wall -I$(top_srcdir) AM_CPPFLAGS = -DSYSCONFDIR="\"${sysconfdir}\"" -DDATAROOT="\"${datarootdir}\"" ser2net_SOURCES = controller.c dataxfer.c readconfig.c port.c \ ser2net.c led.c led_sysfs.c yamlconf.c auth.c gbuf.c trace.c \ portconfig.c ser2net_str.c portinfo.c rotator.c defaults.c \ addsysattrs.c mdns.c timeproc.c fileio.c noinst_HEADERS = controller.h dataxfer.h readconfig.h defaults.h \ ser2net.h led.h led_sysfs.h absout.h gbuf.h port.h mdns.h \ timeproc.h fileio.h man_MANS = ser2net.8 ser2net.yaml.5 EXTRA_DIST = $(man_MANS) ser2net.yaml ser2net.spec ser2net.init reconf \ ser2net.service.in SUBDIRS = tests DIST_SUBDIRS = $(SUBDIRS) noinst_DATA = ser2net.service ser2net.service: ${srcdir}/ser2net.service.in sed "s%@ser2netbindir@%${sbindir}%" $< >$@ clean-local: rm -f ser2net.service ser2net-4.6.2/NEWS000066400000000000000000000005541461221161700136110ustar00rootroot00000000000000 Oct 14, 2003: ser2net now supports RFC 2217, the ability to control the serial port with telnet options. So, for instance, you can connect to a ser2net session with a client that has this option and control the baud rate, stopbits, parity, etc. The only client I know of that supports this is c-kermit, available at http://www.columbia.edu/kermit ser2net-4.6.2/README000066400000000000000000000000171461221161700137640ustar00rootroot00000000000000See README.rst ser2net-4.6.2/README.rst000066400000000000000000000214651461221161700146050ustar00rootroot00000000000000======= ser2net ======= This is ser2net, a program for allowing connections between gensio accepters and gensio connectors. Generally, this would be a network connections to serial ports or IPMI Serial Over Lan (SOL) connections, but there are lots of gensios and lots of options. See gensio(5) for information on gensios. ======== Building ======== You need two libraries to build ser2net: libyaml and gensio. On Ubuntu you can install libyaml with: apt install libyaml-dev The gensio library may be available on your distro, or it may not, or it may be old and missing things you need. It is available as a tarball in the ser2net sourceforge files, or you can get it from github at https://github.com/cminyard/gensio. A lot of the capabilities of ser2net (crypto, mdns, IPMI) come from gensio, so it must be compiled correctly for those. This is a normal autoconf system, nothing special. Note that if you get this directly from git, you won't have the build infrastructure included. There is a script named "reconf" in the main directory that will create it for you. If you don't know about autoconf, the INSTALL file has some info, or google it. ================= Docker Containers ================= Docker container support is available at: https://github.com/jippi/docker-ser2net ===== Using ===== See the man page ser2net(8) for information about using the program. Also see ser2net.yaml(5) for information on the configuration file. An example configuration file is provided in ser2net.yaml. Since gensios support encryption and authentication, these are also available to ser2net. The gensiot and gtlssh programs that are part of gensio can do this encryption. The telnet-ssl program can also be used for this. This is documented in ser2net.yaml(5). Note that ser2net supports RFC 2217 (remote control of serial port parameters) via the telnet gensio, but you must have a compliant client. The gensiot client can do this, as well as gtlssh. The gensio library provides programmatic access for C and Python. Beyond that, the only one I know of is kermit (http://www.columbia.edu/kermit). ser2net supports making connections to IPMI SOL (serial over LAN) capable systems. This way, if you have a system with SOL, you can use it with programs that speak sockets and avoid having to run a serial cable to the system. It uses OpenIPMI for this, so you have to know how to make an OpenIPMI connection to the remote system. That can be rather complicated, but for a simple example, add a connection like:: connection: &ipmicon1 accepter: telnet,tcp,3022 connector: ipmisol,lan -U -P ,115200 Obviously, use the IPMI BMC userid and password here. Depending on your system there are a lot of other options, and configuration of IPMI on the remote system is not for the faint of heart. And also, if you put passwords in the ser2net.conf file, it becomes a security issue and you should make it readable only by the user that runs ser2net. There are ways to insert data from files, too, so the password doesn't have to be in the clear in the main configuration file, see the ser2net.yaml(5) manpage for details. ser2net also supports threading. By default it runs with a single thread but you can add '-t ' and it will spawn the given number of threads. On modern Linux systems it uses epoll to avoid the "thundering herd" issue, so it should be quite scalable. Also, it runs reconfigurations in a separate thread to avoid a reconfig blocking things up. If you don't want to compile with threads, you can add "--with-pthreads=no" to the configure line. If you want the opposite of ser2net (you want to connect to a "local" serial port device that is really remote) then Cyclades has provided a tool for this at https://sourceforge.net/projects/cyclades-serial/. It is capable of connecting to ser2net using RFC2217. ============= Running Tests ============= There are a number of tests for ser2net. They currently only run on Linux and require some external tools. They require the serialsim kernel module and python interface. These are at https://github.com/cminyard/serialsim and allow the tests to use a simulated serial port to read modem control line, inject errors, etc. They require the gensio python module. They also require the ipmi_sim program from the OpenIPMI library at https://github.com/cminyard/openipmi to run the ipmisol tests. ================================== A Complete Encrypted Example Setup ================================== Lets suppose you have a server with serial port /dev/ttyUSB0 that you want to make available on the network, and you want the connection encrypted. Here an example, after installing gensio and ser2net. Note that this is for use with gensio's gtlssh, *not* with normal ssh. Normal ssh does not currently work with ser2net. I looked at doing ssh, and it turned out to be hard to do, as ssh isn't a neatly layered protocol with easily separable authentication, and the current ssh libraries available are not suitable at all for gensio. Anyway, to do this, edit the ser2net configuration file:: sudo mkdir /etc/ser2net sudo vi /etc/ser2net/ser2net.yaml The contents of ser2net.yaml should be:: %YAML 1.1 --- define: &banner Connected to port \N(\d)\r\n default: name: local value: true class: serialdev default: name: mdns value: true default: name: mdns-sysattrs value: true connection: &my-console accepter: telnet(rfc2217),mux,certauth(),ssl,tcp,3001 connector: serialdev,/dev/ttyUSB0,115200N81 options: banner: *banner Create a user for ser2net to run as:: sudo useradd -r -M -d /usr/share/ser2net -G dialout ser2net sudo mkdir /usr/share/ser2net sudo chown ser2net.ser2net /usr/share/ser2net You don't want to run ser2net as root, that's a bad security practice. Now generate the server keys:: sudo gtlssh-keygen --keydir /etc/ser2net serverkey ser2net sudo chown ser2net.ser2net /etc/ser2net/* ser2net's authentication directory is in /usr/share/ser2net/auth:: sudo -u ser2net mkdir /usr/share/ser2net/auth Now we must create the keys for logging in to the server. You do this on your host system with gtlssh-keygen, assuming you haven't already done so. Assume your userid is myuser, and you are logged in on the host system (not the server). Generate the key:: gtlssh-keygen keygen And copy $HOME/.gtlssh/default.crt to the server. You will put it in /usr/share/ser2net/auth/myuser/allowed_certs, and you want to give it a meaningful name. General best practice is to have a separate key for every client system and put each key onto the target, so using the client name is good practice. Note: Do not copy the .key file anywhere else. That is the file you need to keep secret. Just copy the .crt file. So here we go (after the default.crt file is copied to the server):: sudo -u ser2net mkdir -p /usr/share/ser2net/auth/myuser/allowed_certs sudo -u ser2net cp default.crt \ /usr/share/ser2net/auth/myuser/allowed_certs/client.crt sudo -u ser2net gtlssh-keygen rehash \ /usr/share/ser2net/auth/myuser/allowed_certs Don't forget the rehash step. If you add or remove a key from allowed_certs, you have to rehash. Then start (or restart) ser2net and you should be set. Make sure it runs as the user ser2net, like:: sudo -u ser2net ser2net From myuser on client, you can connect to the port:: gtlssh --telnet -p 3001 server If you have avahi enabled (it's usually on by default on modern systems) you can use mdns. You may notice that mdns is configured in the ser2net configuration, so the name of the connection (my-console in this case) is available via mdns. So you can just do:: gtlssh -m my-console and gtlssh will look up the mdns name, the port, if telnet is enabled, etc. and make the connection. This only works on a local network, though, if you are bridged it won't work. =============== Windows Support =============== You can build ser2net for windows. You need a gensio built for Windows, of course, and that's supported. It should just build under MINGW64. Beyond gensio, you will also need mingw-w64-x86_64-libyaml installed. The sysconfdir and datarootdir do not work on Windows, instead it uses a file relative to the executable's dectory, ../etc/ser2net and ../share/ser2net. Other than that, everything pretty much works the same. For installation, use the following configuration:: ../configure --sbindir=/Ser2Net/bin --libexecdir=/Ser2Net/bin --mandir=/Ser2Net/man \ --includedir=/Ser2Net/include --prefix=/Ser2Net \ CPPFLAGS=-I$HOME/install/Gensio/include LDFLAGS=-L$HOME/install/Gensio/lib Where gensio is already installed there, and then do:: make install DESTDIR=$HOME/install You can then use the Inno Setup Compiler to compile ser2net into an executable installer using the ser2net.iss file. ser2net-4.6.2/absout.h000066400000000000000000000027651461221161700145660ustar00rootroot00000000000000/* * ser2net - A program for allowing telnet connection to serial ports * Copyright (C) 2001-2020 Corey Minyard * * SPDX-License-Identifier: GPL-2.0-only * * In addition, as a special exception, the copyright holders of * ser2net give you permission to combine ser2net with free software * programs or libraries that are released under the GNU LGPL and * with code included in the standard release of OpenSSL under the * OpenSSL license (or modified versions of such code, with unchanged * license). You may copy and distribute such a system following the * terms of the GNU GPL for ser2net and the licenses of the other code * concerned, provided that you include the source code of that * other code when and as the GNU GPL requires distribution of source * code. * * Note that people who make modified versions of ser2net are not * obligated to grant this special exception for their modified * versions; it is their choice whether to do so. The GNU General * Public License gives permission to release a modified version * without this exception; this exception also makes it possible to * release a modified version which carries forward this exception. */ #ifndef ABSOUT_H #define ABSOUT_H #include struct absout { int (*out)(struct absout *e, const char *str, ...); int (*vout)(struct absout *e, const char *str, va_list ap); void *data; }; #define abspr(abs, fmt, ...) \ abs->out(abs, fmt, ##__VA_ARGS__) #endif /* ABSOUT_H */ ser2net-4.6.2/addsysattrs.c000066400000000000000000000165411461221161700156260ustar00rootroot00000000000000/* * ser2net - A program for allowing telnet connection to serial ports * Copyright (C) 2015-2020 Corey Minyard * Copyright (C) 2015 I2SE GmbH * Copyright (C) 2016 Michael Heimpold * * SPDX-License-Identifier: GPL-2.0-only * * In addition, as a special exception, the copyright holders of * ser2net give you permission to combine ser2net with free software * programs or libraries that are released under the GNU LGPL and * with code included in the standard release of OpenSSL under the * OpenSSL license (or modified versions of such code, with unchanged * license). You may copy and distribute such a system following the * terms of the GNU GPL for ser2net and the licenses of the other code * concerned, provided that you include the source code of that * other code when and as the GNU GPL requires distribution of source * code. * * Note that people who make modified versions of ser2net are not * obligated to grant this special exception for their modified * versions; it is their choice whether to do so. The GNU General * Public License gives permission to release a modified version * without this exception; this exception also makes it possible to * release a modified version which carries forward this exception. */ #include #include "absout.h" #ifdef linux #include #include #include #include #include #include #include #include #include #include "ser2net.h" #include "port.h" #define SYSFS_TTY_BASE "/sys/class/tty/" #define SYSFS_TTY_BASE_LEN 15 static const char * get_base_str(const char *devname, unsigned int *len) { const char *e, *s; s = strstr(devname, ",serialdev"); if (s) { s++; s = strchr(s, ','); if (s) { s++; while (isspace(*s)) s++; } } else { s = strstr(devname, "/dev/"); } if (s) { e = strchr(s, ','); if (e) *len = e - s; else *len = strlen(s); while (*len > 0 && isspace(s[(*len) - 1])) (*len)--; } return s; } static void add_attr(struct absout *eout, const char *portname, const char *path, const char *sysfsname, const char ***txt, gensiods *args, gensiods *argc) { char *s, buf[1024]; ssize_t rv; int fd; s = gensio_alloc_sprintf(so, "%s/%s", path, sysfsname); if (!s) { eout->out(eout, "Device %s: Unable to allocate path for %s: out of memory\n", portname, sysfsname); return; } fd = open(s, O_RDONLY); so->free(so, s); if (fd < 0) /* Some of these are options, just ignore open errors. */ return; retry: rv = read(fd, buf, sizeof(buf) - 1); if (rv < 0) { if (errno == EINTR) goto retry; eout->out(eout, "Device %s: Unable to read contents of %s: %s\n", portname, sysfsname, strerror(errno)); goto out; } while (rv > 0 && isspace(buf[rv - 1])) rv--; buf[rv] = '\0'; rv = gensio_argv_sappend(so, txt, args, argc, "%s=%s", sysfsname, buf); if (rv < 0) eout->out(eout, "Device %s: Unable add txt contents for %s: %s\n", portname, sysfsname, gensio_err_to_str(rv)); out: close(fd); } void add_usb_attrs(struct absout *eout, const char *portname, const char *devstr, char *path, const char ***txt, gensiods *args, gensiods *argc) { int rv; char *p, *s; rv = gensio_argv_sappend(so, txt, args, argc, "devicetype=serialusb"); if (rv < 0) eout->out(eout, "Device %s: Unable add txt devicetype for %s\n", portname, gensio_err_to_str(rv)); /* * Search backwards to the device. Depending on the specific * device, there should be various levels of directories named * "tty" or "ttyxxxx". Like "tty/ttyACM0" or * "ttyUSB0/tty/ttyUSB0". */ s = p = strrchr(path, '/'); while (p && p > path && strncmp(p + 1, "tty", 3) == 0) { s = p; p--; while (p > path && *p != '/') p--; } if (!p) { eout->out(eout, "Device %s: usb path is not valid: %s\n", portname, path); return; } *s = '\0'; add_attr(eout, portname, path, "bInterfaceNumber", txt, args, argc); add_attr(eout, portname, path, "interface", txt, args, argc); p = strrchr(path, '/'); if (!p || p == path) { eout->out(eout, "Device %s: usb path(2) is not valid: %s\n", portname, path); return; } *p = '\0'; add_attr(eout, portname, path, "idProduct", txt, args, argc); add_attr(eout, portname, path, "idVendor", txt, args, argc); add_attr(eout, portname, path, "serial", txt, args, argc); add_attr(eout, portname, path, "manufacturer", txt, args, argc); add_attr(eout, portname, path, "product", txt, args, argc); } static int follow_symlink(char *path) { char path2[PATH_MAX], *s, *t; int rv; do { rv = readlink(path, path2, sizeof(path2) - 1); if (rv < 0) { if (errno == EINVAL) break; goto out_err; } path2[rv] = '\0'; s = path2; if (*s == '/') { t = path; } else { t = path + strlen(path); while (t > path && *(t - 1) != '/') t--; while (strcmp(s, "../") == 0) { if (t > path) t--; while (t > path && *(t - 1) != '/') t--; s += 3; } if (t == path) goto out_err; if (strlen(s) + (t - path) > PATH_MAX) goto out_err; } strcpy(t, s); } while(true); return 0; out_err: return -EINVAL; } void add_sys_attrs(struct absout *eout, const char *portname, const char *devname, const char ***txt, gensiods *args, gensiods *argc) { const char *d, *s, *t; unsigned int len; char path[PATH_MAX], path2[PATH_MAX], devstr[128]; ssize_t rv; /* Find the /dev/xxx string in the device name. */ d = get_base_str(devname, &len); if (!d) return; if (len == 0 || len > sizeof(path) - 1) { eout->out(eout, "Device %s: device name too long.\n", portname); return; } memcpy(path, d, len); path[len] = 0; if (follow_symlink(path)) { eout->out(eout, "Device %s: Could not follow symlink: %s\n", path, portname); return; } /* Find the name used in sysfs, usually what is after /dev. */ t = s = path + strlen(path); while (s != path && *s != '/') s--; len = t - s; if (len == 0 || len > sizeof(devstr) - 1) { eout->out(eout, "Device %s: base device name size invalid.\n", portname); return; } memcpy(devstr, s + 1, len); devstr[len] = '\0'; /* Find the tty class link at /sys/class/tty/ */ snprintf(path2, sizeof(path2), "%s%s", SYSFS_TTY_BASE, devstr); /* The tty class is a link, find the real location. */ memcpy(path, SYSFS_TTY_BASE, SYSFS_TTY_BASE_LEN); rv = readlink(path2, path + SYSFS_TTY_BASE_LEN, sizeof(path) - SYSFS_TTY_BASE_LEN - 1); if (rv < 0) { eout->out(eout, "Device %s: Unable to get symlink path at %s: %s\n", portname, path2, strerror(errno)); return; } path[rv + SYSFS_TTY_BASE_LEN] = '\0'; if (strstr(path, "/usb")) { add_usb_attrs(eout, portname, devstr, path, txt, args, argc); } else { rv = gensio_argv_sappend(so, txt, args, argc, "devicetype=serial"); if (rv < 0) eout->out(eout, "Device %s: Unable add txt devicetype for %s\n", portname, gensio_err_to_str(rv)); } } #else void add_sys_attrs(struct absout *eout, const char *portname, const char *devname, const char ***txt, gensiods *args, gensiods *argc) { } #endif ser2net-4.6.2/auth.c000066400000000000000000000241111461221161700142120ustar00rootroot00000000000000/* * ser2net - A program for allowing telnet connection to serial ports * Copyright (C) 2001=2020 Corey Minyard * * SPDX-License-Identifier: GPL-2.0-only * * In addition, as a special exception, the copyright holders of * ser2net give you permission to combine ser2net with free software * programs or libraries that are released under the GNU LGPL and * with code included in the standard release of OpenSSL under the * OpenSSL license (or modified versions of such code, with unchanged * license). You may copy and distribute such a system following the * terms of the GNU GPL for ser2net and the licenses of the other code * concerned, provided that you include the source code of that * other code when and as the GNU GPL requires distribution of source * code. * * Note that people who make modified versions of ser2net are not * obligated to grant this special exception for their modified * versions; it is their choice whether to do so. The GNU General * Public License gives permission to release a modified version * without this exception; this exception also makes it possible to * release a modified version which carries forward this exception. */ #include #include #include #include #include #include #include #include "ser2net.h" #if defined(USE_PAM) #include #include #endif /* * Ambiguity in spec: is it an array of pointers or a pointer to an array? * Stolen from openssh. */ #ifdef PAM_SUN_CODEBASE # define PAM_MSG_MEMBER(msg, n, member) ((*(msg))[(n)].member) #else # define PAM_MSG_MEMBER(msg, n, member) ((msg)[(n)]->member) #endif struct user { struct gensio_link link; char *name; }; int add_allowed_users(struct gensio_list **users, const char *istr, struct absout *eout) { char *name, *strtok_data, *str = NULL; if (!*users) { *users = malloc(sizeof(**users)); if (!*users) goto out_nomem; gensio_list_init(*users); } str = strdup(istr); if (!str) goto out_nomem; name = strtok_r(str, " \t", &strtok_data); while (name) { struct user *user = malloc(sizeof(*user)); if (!user) goto out_nomem; memset(user, 0, sizeof(*user)); user->name = strdup(name); if (!user->name) { free(user); goto out_nomem; } gensio_list_add_tail(*users, &user->link); name = strtok_r(NULL, " \t", &strtok_data); } free(str); return 0; out_nomem: if (str) free(str); eout->out(eout, "Out of memory allocating allowed user list"); return GE_NOMEM; } static bool is_user_present(const struct gensio_list *users, char *name) { struct gensio_link *u; if (!users) return true; gensio_list_for_each(users, u) { struct user *user = gensio_container_of(u, struct user, link); if (strcmp(user->name, name) == 0) return true; } return false; } void free_user_list(struct gensio_list *users) { struct gensio_link *u, *u2; if (!users) return; gensio_list_for_each_safe(users, u, u2) { struct user *user = gensio_container_of(u, struct user, link); gensio_list_rm(users, u); free(user->name); free(user); } free(users); } /* * The next few functions are for authentication handling. */ static int handle_auth_begin(struct gensio *net, const char *authdir, const char *pamauth, const struct gensio_list *allowed_users) { gensiods len; char username[100]; int err; len = sizeof(username); err = gensio_control(net, 0, true, GENSIO_CONTROL_USERNAME, username, &len); if (err) { seout.out(&seout, "No username provided by remote: %s", gensio_err_to_str(err)); return GE_AUTHREJECT; } if (!is_user_present(allowed_users, username)) { seout.out(&seout, "Username not allowed for this connection: %s", username); return GE_AUTHREJECT; } #if defined(USE_PAM) /* set user-specific authdir if it exists. */ if (pamauth) { char userauthdir[1000]; DIR *dir; struct passwd *pw; pw = getpwnam(username); if (pw) { len = snprintf(userauthdir, sizeof(userauthdir), "%s/.gtlssh/allowed_certs/", pw->pw_dir); dir = opendir(userauthdir); if (dir) { closedir(dir); err = gensio_control(net, 0, GENSIO_CONTROL_SET, GENSIO_CONTROL_CERT_AUTH, userauthdir, &len); if (err) { seout.out(&seout, "Could not set authdir %s: %s", userauthdir, gensio_err_to_str(err)); return GE_NOTSUP; } } } } #endif return GE_NOTSUP; } static int handle_precert(struct gensio *net, const char *authdir) { gensiods len; char username[100]; char filename[PATH_MAX]; int err; char *s = username; len = sizeof(username); err = gensio_control(net, 0, true, GENSIO_CONTROL_USERNAME, username, &len); if (err) { /* Try to get the username from the cert common name. */ snprintf(username, sizeof(username), "-1,CN"); len = sizeof(username); err = gensio_control(net, 0, true, GENSIO_CONTROL_GET_PEER_CERT_NAME, username, &len); if (err) { seout.out(&seout, "No username provided by remote or cert: %s", gensio_err_to_str(err)); return GE_AUTHREJECT; } /* Skip over the ,CN, in the username output. */ s = strchr(username, ','); if (s) s = strchr(s + 1, ','); if (!s) { seout.out(&seout, "Got invalid username: %s", username); return GE_AUTHREJECT; } s++; /* Set the username so it's available later. */ err = gensio_control(net, 0, false, GENSIO_CONTROL_USERNAME, s, NULL); if (err) { seout.out(&seout, "Unable to set username to %s: %s", s, gensio_err_to_str(err)); return GE_AUTHREJECT; } } snprintf(filename, sizeof(filename), "%s%c%s%callowed_certs%c", authdir, DIRSEP, s, DIRSEP, DIRSEP); err = gensio_control(net, 0, false, GENSIO_CONTROL_CERT_AUTH, filename, &len); if (err && err != GE_CERTNOTFOUND) { seout.out(&seout, "Unable to set authdir to %s: %s", filename, gensio_err_to_str(err)); } return GE_NOTSUP; } static int handle_password(struct gensio *net, const char *authdir, const char *password) { gensiods len; char username[100]; char filename[PATH_MAX]; FILE *pwfile; char readpw[100], *s; int err; len = sizeof(username); err = gensio_control(net, 0, true, GENSIO_CONTROL_USERNAME, username, &len); if (err) { seout.out(&seout, "No username provided by remote: %s", gensio_err_to_str(err)); return GE_AUTHREJECT; } snprintf(filename, sizeof(filename), "%s/%s/password", authdir, username); pwfile = fopen(filename, "r"); if (!pwfile) { seout.out(&seout, "Can't open password file %s", filename); return GE_AUTHREJECT; } s = fgets(readpw, sizeof(readpw), pwfile); fclose(pwfile); if (!s) { seout.out(&seout, "Can't read password file %s", filename); return GE_AUTHREJECT; } s = strchr(readpw, '\n'); if (s) *s = '\0'; if (strcmp(readpw, password) == 0) return 0; return GE_NOTSUP; } #if defined(USE_PAM) static int pam_conversation_cb(int num_msg, const struct pam_message **msg, struct pam_response **pam_response, void *appdata_ptr) { int i; const char *password = appdata_ptr; struct pam_response *resp = NULL; if (password == NULL) { return PAM_CONV_ERR; } resp = calloc(num_msg, sizeof(struct pam_response)); if (resp == NULL) { return PAM_BUF_ERR; } for (i = 0; i < num_msg; i++) { resp[i].resp_retcode = 0; switch(PAM_MSG_MEMBER(msg, i, msg_style)) { case PAM_PROMPT_ECHO_ON: case PAM_PROMPT_ECHO_OFF: resp[i].resp = strdup(password); if(resp[i].resp == NULL) { goto error; } } } *pam_response = resp; return PAM_SUCCESS; error: if (resp) { for (i = 0; i < num_msg; i++) { free(resp[i].resp); } free(resp); } return PAM_BUF_ERR; } static int handle_password_pam(struct gensio *net, const char *pamauth, const char *password) { int ret = GE_AUTHREJECT; char username[100]; gensiods len; int err, pam_err = 0; pam_handle_t *pamh = NULL; struct pam_conv pam_conv; len = sizeof(username); err = gensio_control(net, 0, true, GENSIO_CONTROL_USERNAME, username, &len); if (err) { seout.out(&seout, "No username provided by remote: %s", gensio_err_to_str(err)); goto exit; } pam_conv.conv = pam_conversation_cb; pam_conv.appdata_ptr = (char *)password; pam_err = pam_start(pamauth, username, &pam_conv, &pamh); if (pam_err != PAM_SUCCESS) { seout.out(&seout, "Unable to start PAM transaction: %s", pam_strerror(pamh, pam_err)); goto exit; } pam_err = pam_authenticate(pamh, PAM_SILENT); if (pam_err != PAM_SUCCESS) { seout.out(&seout, "PAM authentication failed: %s", pam_strerror(pamh, pam_err) ); goto exit; } else { seout.out(&seout, "Accepted password for %s\n", username); } pam_err = pam_acct_mgmt(pamh, 0); if (pam_err == PAM_NEW_AUTHTOK_REQD) { seout.out(&seout, "user %s password expired", username); goto exit; } if (pam_err != PAM_SUCCESS) { seout.out(&seout, "pam_acct_mgmt failed for %s: %s", username, pam_strerror(pamh, pam_err)); goto exit; } ret = 0; exit: if (pamh) { pam_end(pamh, pam_err); } return ret; } #endif int handle_acc_auth_event(const char *authdir, const char *pamauth, const struct gensio_list *allowed_users, int event, void *data) { switch (event) { case GENSIO_ACC_EVENT_AUTH_BEGIN: if (!authdir) return 0; return handle_auth_begin(data, authdir, pamauth, allowed_users); case GENSIO_ACC_EVENT_PRECERT_VERIFY: if (!authdir) return 0; return handle_precert(data, authdir); case GENSIO_ACC_EVENT_PASSWORD_VERIFY: { struct gensio_acc_password_verify_data *pwdata; pwdata = (struct gensio_acc_password_verify_data *) data; #if defined(USE_PAM) if (pamauth) { return handle_password_pam(pwdata->io, pamauth, pwdata->password); } else #endif if (authdir) { return handle_password(pwdata->io, authdir, pwdata->password); } else { return 0; } } default: return GE_NOTSUP; } } ser2net-4.6.2/configure.ac000066400000000000000000000047351461221161700154050ustar00rootroot00000000000000AC_INIT([ser2net],[4.6.2],[minyard@acm.org]) AM_INIT_AUTOMAKE([-Wall]) AC_PROG_CC AM_PROG_AR LT_INIT use_pthreads=yes use_pthreads_set=false AC_ARG_WITH(pthreads, [ --with-pthreads=yes|no Argument is ignore.], AC_MSG_WARN([--with-pthreads is no longer used]) ) use_pam=check AC_ARG_WITH(pam, [ --with-pam=yes|no Support PAM authentication or not.], if test "x$withval" = "xyes"; then use_pam=yes elif test "x$withval" = "xno"; then use_pam=no else [AC_MSG_FAILURE([Unknown option to --with-pam, use yes or no])] fi, ) if test "$use_pam" != "no"; then have_pam=yes AC_CHECK_HEADER(security/pam_appl.h, [], [have_pam=no]) if test "$have_pam" = "yes"; then AC_CHECK_LIB(pam, pam_start, [], [have_pam=no]) fi if test "$use_pam" = "yes" -a "$have_pam" = "no"; then AC_MSG_ERROR([Pam enabled, but no pam support found]) fi use_pam=$have_pam fi if test "x$use_pam" != "xno"; then LIBS="$LIBS -lpam" AC_DEFINE([USE_PAM], [], [Enable PAM support]) fi AC_ARG_WITH(sysfs-led-support, [ --with-sysfs-led-support Enable LED support (Linux only)], sysfs_led_support_flag="$withval", sysfs_led_support_flag=check) if test "x$sysfs_led_support_flag" = "xcheck"; then case "$host_os" in linux*) sysfs_led_support_flag=yes ;; *) sysfs_led_support_flag=no ;; esac fi if test "x$sysfs_led_support_flag" = "xyes"; then AC_DEFINE(USE_SYSFS_LED_FEATURE) fi # enable silent build m4_ifdef([AM_SILENT_RULES], [AM_SILENT_RULES([yes])]) AC_CONFIG_MACRO_DIR([m4]) AC_CHECK_LIB(nsl,main) AC_CHECK_HEADERS([wordexp.h]) AC_CHECK_FUNCS(wordexp) AC_CHECK_HEADER(gensio/gensio.h, [], [AC_MSG_ERROR([gensio.h not found, please install gensio dev package])]) AC_CHECK_LIB(gensio, str_to_gensio, [], [AC_MSG_ERROR([libgensio won't link, please install gensio dev package])]) AC_CHECK_LIB(gensio, gensio_list_add_tail, [], [AC_CHECK_LIB(gensioosh, gensio_list_add_tail, [], [AC_MSG_ERROR([libgensioosh won't link, please install gensio dev package])])]) AC_CHECK_LIB(gensio, gensio_alloc_mdns, [], [AC_CHECK_LIB(gensiomdns, gensio_alloc_mdns, [], [AC_MSG_ERROR([libgensiomdns won't link, please install gensio dev package])])]) AC_CHECK_HEADER(yaml.h, [], [AC_MSG_ERROR([yaml.h not found, please install libyaml dev package])]) AC_CHECK_LIB(yaml, yaml_document_initialize, [], [AC_MSG_ERROR([libyaml won't link, please install libyaml dev package])]) AC_CONFIG_FILES([Makefile tests/Makefile]) AC_OUTPUT ser2net-4.6.2/controller.c000066400000000000000000001030201461221161700154310ustar00rootroot00000000000000/* * ser2net - A program for allowing telnet connection to serial ports * Copyright (C) 2001=2020 Corey Minyard * * SPDX-License-Identifier: GPL-2.0-only * * In addition, as a special exception, the copyright holders of * ser2net give you permission to combine ser2net with free software * programs or libraries that are released under the GNU LGPL and * with code included in the standard release of OpenSSL under the * OpenSSL license (or modified versions of such code, with unchanged * license). You may copy and distribute such a system following the * terms of the GNU GPL for ser2net and the licenses of the other code * concerned, provided that you include the source code of that * other code when and as the GNU GPL requires distribution of source * code. * * Note that people who make modified versions of ser2net are not * obligated to grant this special exception for their modified * versions; it is their choice whether to do so. The GNU General * Public License gives permission to release a modified version * without this exception; this exception also makes it possible to * release a modified version which carries forward this exception. */ #include #include #include #include #include #include #include "ser2net.h" #include "controller.h" #include "dataxfer.h" #include "defaults.h" #include "mdns.h" /* This file holds the code that runs the control port. */ static struct gensio_lock *cntlr_lock; static struct gensio_accepter *controller_accepter; static char *controller_authdir; static char *controller_pamauth; static struct gensio_waiter *accept_waiter; static int max_controller_ports = 4; /* How many control connections do we allow at a time. */ static int num_controller_ports = 0; /* How many control connections are currently active. */ #define INBUF_SIZE 2048 /* The size of the maximum input command or YAML doc. */ char *prompt = "-> "; /* This data structure is kept for each control connection. */ typedef struct controller_info { struct gensio_lock *lock; int in_shutdown; struct gensio *net; unsigned char inbuf[INBUF_SIZE + 1];/* Buffer to receive command on. */ int inbuf_count; /* The number of bytes currently in the inbuf. */ bool echo_off; struct gensio_lock *outlock; bool yaml; /* Am I in YAML output mode? */ char *outbuf; /* The output buffer, NULL if no output. */ int outbufsize; /* Total size of the memory allocated in outbuf. */ int outbuf_pos; /* The current position in the output buffer. */ int outbuf_count; /* The number of bytes (starting at outbuf_pos) left to transmit. */ unsigned int indent; bool yamlin; /* Am I in YAML input mode? */ yaml_parser_t parser; size_t parse_pos; size_t read_pos; unsigned int match_len; yaml_document_t doc; void *monitor_port_id; /* When port monitoring, this is the id given when the monitoring is started. It is used to stop monitoring. */ struct controller_info *next; /* Used to keep these items in a linked list. */ void (*shutdown_complete)(void *); void *shutdown_complete_cb_data; } controller_info_t; static struct gensio_waiter *controller_shutdown_waiter; /* List of current control connections. */ controller_info_t *controllers = NULL; static void controller_close_done(struct gensio *net, void *cb_data) { controller_info_t *cntlr = gensio_get_user_data(net); controller_info_t *prev; controller_info_t *curr; void (*shutdown_complete)(void *); void *shutdown_complete_cb_data; gensio_free(net); so->free_lock(cntlr->lock); so->free_lock(cntlr->outlock); if (cntlr->outbuf != NULL) { free(cntlr->outbuf); } cntlr->outbuf = NULL; /* Remove it from the linked list. */ prev = NULL; so->lock(cntlr_lock); curr = controllers; while (curr != NULL) { if (cntlr == curr) { if (prev == NULL) { controllers = controllers->next; } else { prev->next = curr->next; } num_controller_ports--; break; } prev = curr; curr = curr->next; } so->unlock(cntlr_lock); shutdown_complete = cntlr->shutdown_complete; shutdown_complete_cb_data = cntlr->shutdown_complete_cb_data; free(cntlr); if (shutdown_complete) shutdown_complete(shutdown_complete_cb_data); } /* Shut down a control connection and remove it from the list of controllers. */ static void shutdown_controller(controller_info_t *cntlr) { if (cntlr->in_shutdown) { so->unlock(cntlr->lock); return; } if (cntlr->monitor_port_id != NULL) { data_monitor_stop(cntlr, cntlr->monitor_port_id); cntlr->monitor_port_id = NULL; } cntlr->in_shutdown = 1; so->unlock(cntlr->lock); gensio_close(cntlr->net, controller_close_done, NULL); } void controller_indent(struct controller_info *cntlr, int amount) { cntlr->indent += amount; } /* Send some output to the control connection. This allocates and free a buffer in blocks of 1024 and increases the size of the buffer as necessary. */ static void controller_raw_output(struct controller_info *cntlr, const char *data, int count) { if (cntlr->outbuf != NULL) { /* Already outputting data, just add more onto it. */ int new_size = cntlr->outbuf_count + count; if (new_size <= cntlr->outbufsize) { /* It will fit into the current buffer, just move things around and append it. */ if (cntlr->outbuf_pos > 0) { int i; for (i = 0; i < cntlr->outbuf_count; i++) { cntlr->outbuf[i] = cntlr->outbuf[cntlr->outbuf_pos + i]; } } memcpy(&(cntlr->outbuf[cntlr->outbuf_count]), data, count); } else { /* We need to allocate a larger buffer. */ char *newbuf; /* Allocate the next even multiple of 1024 bytes. */ new_size = ((new_size / 1024) * 1024) + 1024; newbuf = malloc(new_size); if (newbuf == NULL) { /* Out of memory, just ignore the request */ return; } cntlr->outbufsize = new_size; /* Copy all the data into a new buffer. */ memcpy(newbuf, &(cntlr->outbuf[cntlr->outbuf_pos]), cntlr->outbuf_count); memcpy(newbuf + cntlr->outbuf_count, data, count); free(cntlr->outbuf); cntlr->outbuf = newbuf; } cntlr->outbuf_pos = 0; cntlr->outbuf_count += count; } else { /* We are starting a new buffer, just get it. */ char *newbuf; int new_size = ((count / 1024) * 1024) + 1024; newbuf = malloc(new_size); if (newbuf == NULL) { /* Out of memory, just ignore the request */ return; } cntlr->outbufsize = new_size; memcpy(newbuf, data, count); cntlr->outbuf = newbuf; cntlr->outbuf_pos = 0; cntlr->outbuf_count = count; gensio_set_read_callback_enable(cntlr->net, false); gensio_set_write_callback_enable(cntlr->net, true); } } static void controller_output(struct controller_info *cntlr, const char *field, const char *tag, const char *data, int count) { so->lock(cntlr->outlock); if (field) { unsigned int i; for (i = 0; i < cntlr->indent; i++) controller_raw_output(cntlr, " ", 2); controller_raw_output(cntlr, field, strlen(field)); controller_raw_output(cntlr, ": ", 2); if (cntlr->yaml && tag) controller_raw_output(cntlr, tag, strlen(tag)); } controller_raw_output(cntlr, data, count); if (field) controller_raw_output(cntlr, "\r\n", 2); so->unlock(cntlr->outlock); } static unsigned int expand_quotes(char *out, const char *in, unsigned int outsize) { unsigned int outpos = 1; *out++ = '\''; while (*in) { if (*in == '\'') { if (outpos + 2 >= outsize) break; *out++ = '\\'; outpos++; } if (outpos + 1 >= outsize) break; *out++ = *in++; outpos++; } *out++ = '\''; return outpos + 1; } int controller_voutputf(struct controller_info *cntlr, const char *field, const char *str, va_list ap) { char buffer[1024], buffer2[2048 + 2]; int rv; rv = vsnprintf(buffer, sizeof(buffer) / 2, str, ap); if (strcmp(str, "%d") == 0 || strcmp(str, "%lu") == 0) { controller_output(cntlr, field, "!!int ", buffer, rv); } else if (cntlr->yaml && field) { unsigned int len = expand_quotes(buffer2, buffer, sizeof(buffer2)); controller_output(cntlr, field, "!!str ", buffer2, len); } else { controller_output(cntlr, field, "!!str ", buffer, rv); } return rv; } int controller_outputf(struct controller_info *cntlr, const char *field, const char *str, ...) { va_list ap; int rv; va_start(ap, str); rv = controller_voutputf(cntlr, field, str, ap); va_end(ap); return rv; } void controller_outs(struct controller_info *cntlr, const char *field, const char *s) { if (!s) { controller_output(cntlr, field, NULL, "", 0); } else if (cntlr->yaml && field) { char buffer[2048 + 2]; unsigned int len = expand_quotes(buffer, s, sizeof(buffer)); controller_output(cntlr, field, "!!str ", buffer, len); } else { controller_output(cntlr, field, "!!str ", s, strlen(s)); } } /* Write some data directly to the controllers output port. */ void controller_write(struct controller_info *cntlr, const char *data, gensiods count) { gensio_write(cntlr->net, NULL, data, count, NULL); } static char *help_str = "exit - leave the program.\r\n" "help - display this help.\r\n" "version - display the version of this program.\r\n" "yaml - Go into yaml output mode. In this mode there is no echo or\r\n" " line processing is done. Some commands are disabled. Output\r\n" " is yaml, beginning with --- and ending with ... for each\r\n" " response to a command.\r\n" "monitor - display all the input for a given port on\r\n" " the calling control port. Only one direction may be monitored\r\n" " at a time. The type field may be 'tcp' or 'term' and specifies\r\n" " whether to monitor data from the net port or from the serial port\r\n" " Note that data monitoring is best effort, if the controller port\r\n" " cannot keep up the data will be silently dropped. A controller\r\n" " may only monitor one thing and a port may only be monitored by\r\n" " one controller.\r\n" "monitor stop - stop the current monitor.\r\n" "disconnect - disconnect the tcp connection on the port.\r\n" "showport [] - Show information about a port. If no port is\r\n" " given, all ports are displayed.\r\n" "showshortport [] - Show information about a port in a one-line\r\n" " format. If no port is given, all ports are displayed.\r\n" "setporttimeout - Set the amount of time in seconds\r\n" " before the port connection will be shut down if no activity\r\n" " has been seen on the port.\r\n" "setportcontrol \r\n" " Dynamically modify the characteristics of the port. These are\r\n" " immediate and won't live between connections. Valid controls are\r\n" " DTRHI, DTRLO, RTSHI, and RTSLO.\r\n" "setportenable - Sets the port operation state.\r\n" " Valid states are:\r\n" " off - The port is shut down\r\n" " on - The port is up and all I/O is transferred\r\n" "reload - Reload the configuration file.\r\n"; void cntlr_report_conchange(const char *type, const char *con, const char *remaddr) { controller_info_t *cntlr; /* No controller port set up. */ if (!cntlr_lock) return; so->lock(cntlr_lock); for (cntlr = controllers; cntlr; cntlr = cntlr->next) { if (!cntlr->yaml) continue; so->lock(cntlr->lock); start_maint_op(); controller_outs(cntlr, NULL, "\r\n%YAML 1.1\r\n---\r\n"); controller_outs(cntlr, type, NULL); controller_indent(cntlr, 1); controller_outputf(cntlr, "name", con); controller_outputf(cntlr, "remaddr", remaddr); controller_indent(cntlr, -1); controller_outputf(cntlr, NULL, "...\r\n"); end_maint_op(); so->unlock(cntlr->lock); } so->unlock(cntlr_lock); } static int process_command(controller_info_t *cntlr, const char *cmd, const char *id, int nparms, char * const parms[]) { bool yaml = cntlr->yaml; if (yaml) { controller_outs(cntlr, NULL, "\r\n%YAML 1.1\r\n---\r\n"); controller_outs(cntlr, "response", NULL); controller_indent(cntlr, 1); controller_outputf(cntlr, "name", cmd); if (id) controller_outputf(cntlr, "id", id); } if (strcmp(cmd, "exit") == 0) { shutdown_controller(cntlr); return 1; /* We don't want a prompt any more. */ } else if (strcmp(cmd, "quit") == 0) { shutdown_controller(cntlr); return 1; /* We don't want a prompt any more. */ } else if (!cntlr->yaml && strcmp(cmd, "help") == 0) { controller_outs(cntlr, NULL, help_str); } else if (strcmp(cmd, "version") == 0) { controller_outputf(cntlr, "version", "%s", VERSION); } else if (!cntlr->yaml && strcmp(cmd, "yaml") == 0) { cntlr->yaml = true; } else if (strcmp(cmd, "showport") == 0) { start_maint_op(); showports(cntlr, parms[0], cntlr->yaml); end_maint_op(); } else if (!cntlr->yaml && strcmp(cmd, "showshortport") == 0) { start_maint_op(); showshortports(cntlr, parms[0]); end_maint_op(); } else if (!cntlr->yaml && strcmp(cmd, "monitor") == 0) { if (parms[0] == NULL) { controller_outs(cntlr, "error", "No monitor type given\r\n"); goto out; } if (strcmp(parms[0], "stop") == 0) { if (cntlr->monitor_port_id != NULL) { start_maint_op(); data_monitor_stop(cntlr, cntlr->monitor_port_id); end_maint_op(); cntlr->monitor_port_id = NULL; } } else { if (cntlr->monitor_port_id != NULL) { controller_outs(cntlr, "error", "Already monitoring a port"); goto out; } if (parms[1] == NULL) { controller_outs(cntlr, "error", "No monitor port given"); goto out; } start_maint_op(); cntlr->monitor_port_id = data_monitor_start(cntlr, parms[0], parms[1]); end_maint_op(); } } else if (strcmp(cmd, "disconnect") == 0) { if (parms[0] == NULL) { controller_outs(cntlr, "error", "No port given"); goto out; } start_maint_op(); disconnect_port(cntlr, parms[0]); end_maint_op(); } else if (strcmp(cmd, "setporttimeout") == 0) { if (parms[0] == NULL) { controller_outs(cntlr, "error", "No port given"); goto out; } if (parms[1] == NULL) { controller_outs(cntlr, "error", "No timeout given"); goto out; } start_maint_op(); setporttimeout(cntlr, parms[0], parms[1]); end_maint_op(); } else if (strcmp(cmd, "setportenable") == 0) { if (parms[0] == NULL) { controller_outs(cntlr, "error", "No port given"); goto out; } if (parms[1] == NULL) { controller_outs(cntlr, "error", "No enable given"); goto out; } start_maint_op(); setportenable(cntlr, parms[0], parms[1]); end_maint_op(); } else if (strcmp(cmd, "setportcontrol") == 0) { if (parms[0] == NULL) { controller_outs(cntlr, "error", "No port given"); goto out; } if (parms[1] == NULL) { controller_outs(cntlr, "error", "No device controls"); goto out; } start_maint_op(); setportcontrol(cntlr, parms[0], parms + 1); end_maint_op(); } else if (strcmp(cmd, "reload") == 0) { int rv; start_maint_op(); rv = reread_config_file("admin request", &seout); end_maint_op(); if (!rv) { controller_outs(cntlr, "error", "reload done"); } else { controller_outputf(cntlr, "error", "reload error - %s", strerror(rv)); } } else { controller_outputf(cntlr, "error", "Unknown command - %s", cmd); } out: if (yaml) { controller_indent(cntlr, -1); controller_outputf(cntlr, NULL, "...\r\n"); } return 0; } /* Process a line of input. This scans for commands, reads any parameters, then calls the actual code to handle the command. */ static int process_input_line(controller_info_t *cntlr) { char *strtok_data; char *tok; char *parms[5]; int nparms; int rv = 0; tok = strtok_r((char *) cntlr->inbuf, " \t", &strtok_data); if (tok == NULL) /* Empty line, just ignore it. */ goto out_noend; for (nparms = 0; nparms < 4; nparms++) { parms[nparms] = strtok_r(NULL, " \t", &strtok_data); if (!parms[nparms]) break; } parms[nparms] = NULL; rv = process_command(cntlr, tok, NULL, nparms, parms); out_noend: if (!cntlr->yaml && !rv) controller_outs(cntlr, NULL, prompt); return rv; } /* Removes one or more characters starting at pos and going backwards. So, for instance, if inbuf holds "abcde", pos points to d, and count is 2, the new inbuf will be "abe". This is used for backspacing. */ static int remove_chars(controller_info_t *cntlr, int pos, int count) { int j; for (j = pos-count + 1; j < (cntlr->inbuf_count - count); j++) cntlr->inbuf[j] = cntlr->inbuf[j + count]; cntlr->inbuf_count -= count; pos -= count; return pos; } //#define YAML_DEBUG #ifdef YAML_DEBUG static void print_node(yaml_document_t *doc, yaml_node_t *n, unsigned int indent) { unsigned int i; yaml_node_pair_t *p; yaml_node_item_t *t; yaml_node_t *key, *value; switch(n->type) { case YAML_SCALAR_NODE: printf("!!str "); printf("%s", n->data.scalar.value); break; case YAML_SEQUENCE_NODE: printf("!!seq "); for (t = n->data.sequence.items.start; t < n->data.sequence.items.top; t++) { printf("\n"); for (i = 0; i < indent; i++) printf(" "); value = yaml_document_get_node(doc, *t); print_node(doc, value, indent + 1); } break; case YAML_MAPPING_NODE: printf("!!map "); for (p = n->data.mapping.pairs.start; p < n->data.mapping.pairs.top; p++) { printf("\n"); for (i = 0; i < indent; i++) printf(" "); key = yaml_document_get_node(doc, p->key); value = yaml_document_get_node(doc, p->value); print_node(doc, key, indent + 1); printf(": "); print_node(doc, value, indent + 1); } break; default: printf("?"); } } #endif static int handle_yaml_doc(struct controller_info *cntlr) { yaml_document_t *doc = &cntlr->doc; yaml_node_t *n, *n2, *n3, *k, *v; yaml_node_pair_t *p; yaml_node_item_t *t; char *name = NULL, *parms[5], *id = NULL; int nparms = 0; int rv; memset(parms, 0, sizeof(parms)); #ifdef YAML_DEBUG printf("Got yaml doc\n"); #endif n = yaml_document_get_root_node(doc); if (!n || n->type == YAML_NO_NODE || (n->type == YAML_SCALAR_NODE && !n->data.scalar.value[0])) { #ifdef YAML_DEBUG printf("Empty document\n"); #endif return 1; } #ifdef YAML_DEBUG printf("Root node: "); print_node(doc, n, 1); printf("\n"); #endif if (n->type != YAML_MAPPING_NODE) goto out_err; k = yaml_document_get_node(doc, n->data.mapping.pairs.start->key); if (k->type != YAML_SCALAR_NODE) goto out_err; if (strcmp((char *) k->data.scalar.value, "command") != 0) goto out_err; n2 = yaml_document_get_node(doc, n->data.mapping.pairs.start->value); if (n2->type != YAML_MAPPING_NODE) goto out_err; for (p = n2->data.mapping.pairs.start; p < n2->data.mapping.pairs.top; p++) { k = yaml_document_get_node(doc, p->key); if (k->type != YAML_SCALAR_NODE) goto out_err; if (strcmp((char *) k->data.scalar.value, "name") == 0) { v = yaml_document_get_node(doc, p->value); if (v->type != YAML_SCALAR_NODE) goto out_err; name = (char *) v->data.scalar.value; } else if (strcmp((char *) k->data.scalar.value, "parms") == 0) { n3 = yaml_document_get_node(doc, p->value); if (n3->type != YAML_SEQUENCE_NODE) goto out_err; for (t = n3->data.sequence.items.start; t < n3->data.sequence.items.top; t++) { v = yaml_document_get_node(doc, *t); if (nparms < 4) { if (v->type != YAML_SCALAR_NODE) goto out_err; parms[nparms] = (char *) v->data.scalar.value; nparms++; } } } else if (strcmp((char *) k->data.scalar.value, "id") == 0) { v = yaml_document_get_node(doc, p->value); if (v->type != YAML_SCALAR_NODE) goto out_err; id = (char *) v->data.scalar.value; } else { goto out_err; } } if (!name) goto out_err; rv = !process_command(cntlr, name, id, nparms, parms); yaml_document_delete(doc); return rv; out_err: controller_outs(cntlr, NULL, "\r\n%YAML 1.1\r\n---\r\n"); controller_outs(cntlr, "response", NULL); controller_indent(cntlr, 1); if (name) controller_outputf(cntlr, "name", name); if (id) controller_outputf(cntlr, "id", id); controller_outputf(cntlr, "error", "Invalid yaml command"); controller_indent(cntlr, -1); yaml_document_delete(doc); return 1; } static int yaml_cntlr_read(void *data, unsigned char *buffer, size_t size, size_t *size_read) { struct controller_info *cntlr = data; size_t left = cntlr->parse_pos - cntlr->read_pos; if (left == 0) /* This shouldn't happen, we supplied a whole document. */ return 0; if (size > left) size = left; memcpy(buffer, cntlr->inbuf + cntlr->read_pos, size); cntlr->read_pos += size; *size_read = size; return 1; } static int parse_yaml(struct controller_info *cntlr) { if (!yaml_parser_load(&cntlr->parser, &cntlr->doc)) return 0; return handle_yaml_doc(cntlr); } static int process_yaml(struct controller_info *cntlr) { int rv = 1; while (cntlr->parse_pos < cntlr->inbuf_count) { char c = cntlr->inbuf[cntlr->parse_pos]; cntlr->parse_pos++; /* Looking for \n...\n to mark a document end. */ if (cntlr->match_len == 4) { if (c == '\n' || c == '\r') { if (!parse_yaml(cntlr)) { shutdown_controller(cntlr); rv = 0; } else { /* Copy the rest of the buffer to the beginning. */ cntlr->inbuf_count -= cntlr->parse_pos; memmove(cntlr->inbuf, cntlr->inbuf + cntlr->parse_pos, cntlr->inbuf_count); cntlr->parse_pos = 0; cntlr->match_len = 0; cntlr->read_pos = 0; } break; } cntlr->match_len = 0; } if (c == '\n' || c == '\r') { cntlr->match_len = 1; } else if (cntlr->match_len > 0 && c == '.') { cntlr->match_len++; } else { cntlr->match_len = 0; } } return rv; } static int init_yaml(struct controller_info *cntlr) { cntlr->yaml = true; cntlr->yamlin = true; yaml_parser_initialize(&cntlr->parser); yaml_parser_set_input(&cntlr->parser, yaml_cntlr_read, cntlr); return 1; } /* Data is ready to read on the TCP port. */ static void controller_read(struct gensio *net, int err, unsigned char *buf, gensiods *ibuflen) { controller_info_t *cntlr = gensio_get_user_data(net); int read_start, i; gensiods buflen = 0; so->lock(cntlr->lock); if (cntlr->in_shutdown) /* Can get here on a race condition, just return. */ goto out_unlock; if (err) { /* Got an error on the read, shut down the port. */ if (err != GE_REMCLOSE) seout.out(&seout, "read error for controller port: %s", gensio_err_to_str(err)); shutdown_controller(cntlr); /* Releases the lock */ goto out_return; } buflen = *ibuflen; if (cntlr->inbuf_count == INBUF_SIZE) { controller_outs(cntlr, NULL, "Input line too long\r\n"); cntlr->inbuf_count = 0; goto out_unlock; } read_start = cntlr->inbuf_count; if (buflen > INBUF_SIZE - read_start) buflen = INBUF_SIZE - read_start; memcpy(cntlr->inbuf + read_start, buf, buflen); cntlr->inbuf_count += buflen; if (cntlr->yamlin) { handle_yaml: if (!process_yaml(cntlr)) goto out; goto out_unlock; } for (i = read_start; i < cntlr->inbuf_count; i++) { if (cntlr->inbuf[i] == 0x0) { /* Ignore nulls. */ i = remove_chars(cntlr, i, 1); } else if (!cntlr->yaml && (cntlr->inbuf[i] == '\b' || cntlr->inbuf[i] == 0x7f)) { /* Got a backspace. */ if (i == 0) { /* We ignore backspaces at the beginning of the line. */ i = remove_chars(cntlr, i, 1); } else { i = remove_chars(cntlr, i, 2); if (!cntlr->echo_off) controller_outs(cntlr, NULL, "\b \b"); } } else if (i == 0 && cntlr->inbuf[i] == '%') { /* Turn off echo for this command. */ cntlr->echo_off = true; } else if (cntlr->inbuf[i] == '\r' || cntlr->inbuf[i] == '\n') { /* We got a newline, process the command. */ int j; if (strncmp((char *) cntlr->inbuf, "%YAML", 5) == 0) { if (!init_yaml(cntlr)) goto out; goto handle_yaml; } cntlr->inbuf[i] = '\0'; if (!cntlr->yaml) controller_outs(cntlr, NULL, "\r\n"); if (process_input_line(cntlr)) goto out; /* Controller was shut down. */ cntlr->echo_off = false; /* Now copy any leftover data to the beginning of the buffer. */ /* Don't use memcpy or strcpy because the memory might overlap */ i++; cntlr->inbuf_count -= i; for (j = 0; j < cntlr->inbuf_count; i++, j++) { cntlr->inbuf[j] = cntlr->inbuf[i]; } i = -1; } else if (!cntlr->echo_off && !cntlr->yaml) { /* It's a normal character, just echo it. */ controller_output(cntlr, NULL, NULL, (char *) &(cntlr->inbuf[i]), 1); } } out_unlock: so->unlock(cntlr->lock); out: *ibuflen = buflen; out_return: return; } /* The TCP port has room to write some data. This is only activated if a write fails to complete, it is deactivated as soon as writing is available again. */ static void controller_write_ready(struct gensio *net) { controller_info_t *cntlr = gensio_get_user_data(net); int err; gensiods write_count; so->lock(cntlr->outlock); if (cntlr->in_shutdown) goto out; err = gensio_write(net, &write_count, &(cntlr->outbuf[cntlr->outbuf_pos]), cntlr->outbuf_count, NULL); if (err == GE_REMCLOSE) { goto out_fail; } else if (err) { /* Some other bad error. */ seout.out(&seout, "The tcp write for controller had error: %s", gensio_err_to_str(err)); goto out_fail; } cntlr->outbuf_count -= write_count; if (cntlr->outbuf_count != 0) { /* We didn't write all the data, continue writing. */ cntlr->outbuf_pos += write_count; } else { /* We are done writing, turn the reader back on. */ free(cntlr->outbuf); cntlr->outbuf = NULL; gensio_set_read_callback_enable(net, true); gensio_set_write_callback_enable(net, false); } out: so->unlock(cntlr->outlock); return; out_fail: /* Let the read handle the error. */ gensio_set_read_callback_enable(net, true); gensio_set_write_callback_enable(net, false); so->unlock(cntlr->outlock); } static int controller_io_event(struct gensio *net, void *user_data, int event, int err, unsigned char *buf, gensiods *buflen, const char *const *auxdata) { switch (event) { case GENSIO_EVENT_READ: controller_read(net, err, buf, buflen); return 0; case GENSIO_EVENT_WRITE_READY: controller_write_ready(net); return 0; #ifdef GENSIO_EVENT_PARMLOG case GENSIO_EVENT_PARMLOG: { struct gensio_parmlog_data *d = (struct gensio_parmlog_data *) buf; seout.vout(&seout, d->log, d->args); return 0; } #endif } return GE_NOTSUP; } static int controller_acc_new_child(struct gensio *net) { controller_info_t *cntlr; char *err = NULL; so->lock(cntlr_lock); if (num_controller_ports >= max_controller_ports) { err = "Too many controller ports\r\n"; goto errout; } else { cntlr = malloc(sizeof(*cntlr)); if (cntlr == NULL) { err = "Could not allocate controller port\r\n"; goto errout; } memset(cntlr, 0, sizeof(*cntlr)); } cntlr->lock = so->alloc_lock(so); if (!cntlr->lock) { free(cntlr); err = "Out of memory allocating lock"; goto errout; } cntlr->outlock = so->alloc_lock(so); if (!cntlr->outlock) { so->free_lock(cntlr->lock); free(cntlr); err = "Out of memory allocating lock"; goto errout; } cntlr->net = net; gensio_set_callback(net, controller_io_event, cntlr); cntlr->inbuf_count = 0; cntlr->outbuf = NULL; cntlr->monitor_port_id = NULL; cntlr->parse_pos = 1; /* Assume we start with a \n. */ controller_outs(cntlr, NULL, prompt); cntlr->next = controllers; controllers = cntlr; num_controller_ports++; so->unlock(cntlr_lock); return 0; errout: so->unlock(cntlr_lock); /* We have a problem so refuse this one. */ gensio_write(net, NULL, err, strlen(err), NULL); gensio_free(net); return 0; } /* A connection request has come in for the control port. */ static int controller_acc_child_event(struct gensio_accepter *accepter, void *user_data, int event, void *data) { switch (event) { case GENSIO_ACC_EVENT_NEW_CONNECTION: return controller_acc_new_child(data); #ifdef GENSIO_ACC_EVENT_PARMLOG case GENSIO_ACC_EVENT_PARMLOG: { struct gensio_parmlog_data *d = (struct gensio_parmlog_data *) data; seout.vout(&seout, d->log, d->args); return 0; } #endif default: return handle_acc_auth_event( controller_authdir, controller_pamauth, NULL, event, data ); } } static void controller_shutdown_done(struct gensio_accepter *net, void *cb_data) { so->wake(accept_waiter); } static char *admin_name; #ifdef DO_MDNS static struct mdns_info admin_mdns_info; #endif /* DO_MDNS */ static int controller_handle_options(const char * const *options, struct absout *eout) { unsigned int i; const char *val; if (find_default_str("authdir-admin", &controller_authdir)) { eout->out(eout, "Can't get default value for authdir-admin:" " out of memeory"); return -1; } if (find_default_str("pamauth-admin", &controller_pamauth)) { eout->out(eout, "Can't get default value for pamauth-admin:" " out of memory"); return -1; } #ifdef DO_MDNS if (mdns_info_getdefaults(&admin_mdns_info, "admin", eout)) return -1; #endif for (i = 0; options && options[i]; i++) { if (gensio_check_keyvalue(options[i], "authdir-admin", &val) > 0) { char *s = strdup(val); if (!s) { eout->out(eout, "Can't get value for authdir-admin:" " out of memeory"); return -1; } if (controller_authdir) free(controller_authdir); controller_authdir = s; continue; } if (gensio_check_keyvalue(options[i], "pamauth-admin", &val) > 0) { char *s = strdup(val); if (!s) { eout->out(eout, "Can't get value for pamauth-admin:" " out of memeory"); return -1; } if (controller_pamauth) free(controller_pamauth); controller_pamauth = s; continue; } #ifdef DO_MDNS if (mdns_checkoption(options[i], &admin_mdns_info, "admin", eout) > 0) continue; #endif eout->out(eout, "Invalid option to admin port: %s", options[i]); return -1; } return 0; } /* Set up the controller port to accept connections. */ void controller_init(char *controller_port, const char *name, const char * const *options, struct absout *eout) { int rv; #ifdef GENSIO_ACC_CONTROL_TCPDNAME char progname[1]; gensiods len; #endif if (controller_accepter) { eout->out(eout, "Admin port already configured"); return; } if (name) { admin_name = strdup(name); if (!admin_name) { eout->out(eout, "Unable to allocate admin name"); return; } } if (controller_handle_options(options, eout)) return; if (!cntlr_lock) { cntlr_lock = so->alloc_lock(so); if (!cntlr_lock) goto out_nomem; } if (!controller_shutdown_waiter) { controller_shutdown_waiter = so->alloc_waiter(so); if (!controller_shutdown_waiter) goto out_nomem; } if (!accept_waiter) { accept_waiter = so->alloc_waiter(so); if (!accept_waiter) { eout->out(eout, "Unable to allocate controller accept waiter"); goto out; } } rv = str_to_gensio_accepter(controller_port, so, controller_acc_child_event, NULL, &controller_accepter); if (rv) { eout->out(eout, "Unable to allocate controller accepter: %s", gensio_err_to_str(rv)); goto out; } #ifdef GENSIO_ACC_CONTROL_TCPDNAME len = 0; rv = gensio_acc_control(controller_accepter, GENSIO_CONTROL_DEPTH_FIRST, true, GENSIO_ACC_CONTROL_TCPDNAME, progname, &len); if (rv == GE_NOTSUP) { /* No TCP in the stack, doesn't matter. */ rv = 0; } else { if (rv == GE_NODATA) { /* The user didn't set it. */ rv = gensio_acc_control(controller_accepter, GENSIO_CONTROL_DEPTH_FIRST, false, GENSIO_ACC_CONTROL_TCPDNAME, "ser2net-control", NULL); } } if (rv) eout->out(eout, "Error setting controller tcpdname: %s", gensio_err_to_str(rv)); #endif rv = gensio_acc_startup(controller_accepter); if (rv) eout->out(eout, "Unable to start controller accepter: %s", gensio_err_to_str(rv)); #ifdef DO_MDNS mdns_setup(&admin_mdns_info, admin_name, controller_accepter, eout); #endif out: return; out_nomem: eout->out(eout, "Unable to allocate memory for controller"); return; } void controller_shutdown(void) { if (controller_accepter) { #ifdef DO_MDNS mdns_shutdown(&admin_mdns_info); #endif if (admin_name) { free(admin_name); admin_name = NULL; } gensio_acc_shutdown(controller_accepter, controller_shutdown_done, NULL); so->wait(accept_waiter, 1, NULL); gensio_acc_free(controller_accepter); controller_accepter = NULL; if (controller_authdir) free(controller_authdir); controller_authdir = NULL; } } static void shutdown_controller_done(void *cb_data) { struct gensio_waiter *waiter = cb_data; so->wake(waiter); } void free_controllers(void) { controller_shutdown(); while (controllers) { controllers->shutdown_complete = shutdown_controller_done; controllers->shutdown_complete_cb_data = controller_shutdown_waiter; so->lock(controllers->lock); shutdown_controller(controllers); /* Releases the lock. */ so->wait(controller_shutdown_waiter, 1, NULL); } if (controller_shutdown_waiter) so->free_waiter(controller_shutdown_waiter); if (accept_waiter) so->free_waiter(accept_waiter); } ser2net-4.6.2/controller.h000066400000000000000000000050671461221161700154520ustar00rootroot00000000000000/* * ser2net - A program for allowing telnet connection to serial ports * Copyright (C) 2001-2020 Corey Minyard * * SPDX-License-Identifier: GPL-2.0-only * * In addition, as a special exception, the copyright holders of * ser2net give you permission to combine ser2net with free software * programs or libraries that are released under the GNU LGPL and * with code included in the standard release of OpenSSL under the * OpenSSL license (or modified versions of such code, with unchanged * license). You may copy and distribute such a system following the * terms of the GNU GPL for ser2net and the licenses of the other code * concerned, provided that you include the source code of that * other code when and as the GNU GPL requires distribution of source * code. * * Note that people who make modified versions of ser2net are not * obligated to grant this special exception for their modified * versions; it is their choice whether to do so. The GNU General * Public License gives permission to release a modified version * without this exception; this exception also makes it possible to * release a modified version which carries forward this exception. */ #ifndef CONTROLLER #define CONTROLLER #include /* Initialize the controller code. We continue on failure. */ void controller_init(char *controller_port, const char *name, const char * const *options, struct absout *eout); /* Disable the control port. */ void controller_shutdown(void); /* Clean everything up. */ void free_controllers(void); struct controller_info; /* Send some output to a controller port. The data field is the data to write, the count field is the number of bytes to write. */ int controller_outputf(struct controller_info *cntlr, const char *field, const char *str, ...); /* Send some output to a controller port. The data field is the data to write, the count field is the number of bytes to write. */ int controller_voutputf(struct controller_info *cntlr, const char *field, const char *str, va_list ap); /* Write some data directly to the controllers output port. */ void controller_write(struct controller_info *cntlr, const char *data, gensiods count); /* output a string */ void controller_outs(struct controller_info *cntlr, const char *field, const char *s); /* increase or decrease the indent with 1, or -1 */ void controller_indent(struct controller_info *cntlr, int amount); void cntlr_report_conchange(const char *type, const char *con, const char *remaddr); #endif /* CONTROLLER */ ser2net-4.6.2/dataxfer.c000066400000000000000000001223471461221161700150610ustar00rootroot00000000000000/* * ser2net - A program for allowing telnet connection to serial ports * Copyright (C) 2001-2020 Corey Minyard * * SPDX-License-Identifier: GPL-2.0-only * * In addition, as a special exception, the copyright holders of * ser2net give you permission to combine ser2net with free software * programs or libraries that are released under the GNU LGPL and * with code included in the standard release of OpenSSL under the * OpenSSL license (or modified versions of such code, with unchanged * license). You may copy and distribute such a system following the * terms of the GNU GPL for ser2net and the licenses of the other code * concerned, provided that you include the source code of that * other code when and as the GNU GPL requires distribution of source * code. * * Note that people who make modified versions of ser2net are not * obligated to grant this special exception for their modified * versions; it is their choice whether to do so. The GNU General * Public License gives permission to release a modified version * without this exception; this exception also makes it possible to * release a modified version which carries forward this exception. */ /* This code handles the actual transfer of data between the serial ports and the TCP ports. */ #include #include #include #include #include #include #ifndef GENSIO_EVENT_SER_MODEMSTATE #include #endif #include "port.h" #include "ser2net.h" #include "dataxfer.h" #include "readconfig.h" #include "led.h" /* For tracing. */ #define SERIAL "term" #define NET "tcp " static void setup_port(port_info_t *port, net_info_t *netcon); static int handle_net_event(struct gensio *net, void *user_data, int event, int err, unsigned char *buf, gensiods *buflen, const char *const *auxdata); static int all_net_connectbacks_done(port_info_t *port) { net_info_t *netcon; for_each_connection(port, netcon) { if (netcon->connect_back && !netcon->net) return false; } return true; } static bool any_net_data_to_write(port_info_t *port) { net_info_t *netcon; for_each_connection(port, netcon) { if (!netcon->net) continue; if (netcon->write_pos < port->dev_to_net.cursize) return true; } return false; } static void start_net_send(port_info_t *port) { net_info_t *netcon; if (port->dev_to_net_state == PORT_WAITING_OUTPUT_CLEAR) return; gensio_set_read_callback_enable(port->io, false); for_each_connection(port, netcon) { if (!netcon->net) continue; netcon->write_pos = 0; gensio_set_write_callback_enable(netcon->net, true); } port->dev_to_net_state = PORT_WAITING_OUTPUT_CLEAR; } static void disable_all_net_read(port_info_t *port) { net_info_t *netcon; for_each_connection(port, netcon) { if (netcon->net) gensio_set_read_callback_enable(netcon->net, false); } } static void enable_all_net_read(port_info_t *port) { net_info_t *netcon; for_each_connection(port, netcon) { if (netcon->net) gensio_set_read_callback_enable(netcon->net, true); } } static void report_newcon(port_info_t *port, net_info_t *netcon) { if (!net_raddr_str(netcon->net, netcon->remaddr, sizeof(netcon->remaddr))) strcpy(netcon->remaddr, "*unknown*"); cntlr_report_conchange("new connection", port->name, netcon->remaddr); } void report_disconnect(port_info_t *port, net_info_t *netcon) { cntlr_report_conchange("disconnect", port->name, netcon->remaddr); } static void connect_back_done(struct gensio *net, int err, void *cb_data) { net_info_t *netcon = cb_data; port_info_t *port = netcon->port; so->lock(port->lock); if (err) { netcon->net = NULL; gensio_free(net); } else { report_newcon(port, netcon); setup_port(port, netcon); } assert(port->num_waiting_connect_backs > 0); port->num_waiting_connect_backs--; if (port->num_waiting_connect_backs == 0) { if (!all_net_connectbacks_done(port)) /* Not all connections back could be made. */ port->nocon_read_enable_time_left = port->accepter_retry_time; else gensio_set_read_callback_enable(port->io, true); } so->unlock(port->lock); } static int port_check_connect_backs(port_info_t *port) { net_info_t *netcon; bool tried = false; if (!port->connbacks) return 0; if (port->net_to_dev_state == PORT_CLOSING) { /* * Some data came in while we were shutting down the port. * Just ignore it for now, when the port is opened back up we * wills tart the connections. */ return 1; } for_each_connection(port, netcon) { if (netcon->connect_back && !netcon->net) { int err; tried = true; err = gensio_acc_str_to_gensio(port->accepter, netcon->remote_str, handle_net_event, netcon, &netcon->net); if (err) { seout.out(&seout, "Unable to allocate connect back port %s," " addr %s: %s\n", port->name, netcon->remote_str, gensio_err_to_str(err)); continue; } err = gensio_open(netcon->net, connect_back_done, netcon); if (err) { gensio_free(netcon->net); netcon->net = NULL; seout.out(&seout, "Unable to open connect back port %s," " addr %s: %s\n", port->name, netcon->remote_str, gensio_err_to_str(err)); continue; } port->num_waiting_connect_backs++; } } if (tried && !port->num_waiting_connect_backs && !num_connected_net(port)) { /* * This is kind of a bad situation. We got some data, attempted * connects, but failed. Shut down the read enable for a while. */ port->nocon_read_enable_time_left = port->accepter_retry_time; } return port->num_waiting_connect_backs; } /* Data is ready to read on the serial port. */ static int handle_dev_read(port_info_t *port, int err, unsigned char *buf, gensiods buflen) { gensiods count = 0; bool send_now = false; int nr_handlers = 0; so->lock(port->lock); if (port->dev_to_net_state != PORT_WAITING_INPUT) { gensio_set_read_callback_enable(port->io, false); goto out_unlock; } if (err) { if (port->dev_to_net.cursize) { /* Let the output drain before shutdown. */ count = 0; send_now = true; goto do_send; } /* Got an error on the read, shut down the port. */ seout.out(&seout, "dev read error for device on port %s: %s", port->name, gensio_err_to_str(err)); shutdown_port(port, "dev read error"); } nr_handlers = port_check_connect_backs(port); if (nr_handlers > 0) { gensio_set_read_callback_enable(port->io, false); goto out_unlock; } if (port->no_dev_to_net) { count = buflen; goto out_unlock; } if (gbuf_room_left(&port->dev_to_net) < buflen) buflen = gbuf_room_left(&port->dev_to_net); count = buflen; if (count == 0) { gensio_set_read_callback_enable(port->io, false); goto out_unlock; } if (port->closeon) { int i; for (i = 0; i < count; i++) { if (buf[i] == port->closeon[port->closeon_pos]) { port->closeon_pos++; if (port->closeon_pos >= port->closeon_len) { net_info_t *netcon; for_each_connection(port, netcon) netcon->close_on_output_done = true; /* Ignore everything after the closeon string */ count = i + 1; break; } } else { port->closeon_pos = 0; } } } if (port->tr) /* Do read tracing, ignore errors. */ do_trace(port, port->tr, buf, count, SERIAL); if (port->tb) /* Do both tracing, ignore errors. */ do_trace(port, port->tb, buf, count, SERIAL); if (port->led_rx) led_flash(port->led_rx); if (port->dev_monitor != NULL) controller_write(port->dev_monitor, (char *) buf, count); do_send: if (nr_handlers < 0) /* Nobody to handle the data. */ goto out_unlock; if (port->sendon_len != 0) { int i; for (i = 0; i < count; i++) { if (buf[i] == port->sendon[port->sendon_pos]) { port->sendon_pos++; if (port->sendon_pos >= port->sendon_len) { count = i + 1; send_now = true; port->sendon_pos = 0; break; } } else { port->sendon_pos = 0; } } } gbuf_append(&port->dev_to_net, buf, count); port->dev_bytes_received += count; if (send_now || gbuf_room_left(&port->dev_to_net) == 0 || port->chardelay == 0) { send_it: start_net_send(port); } else { gensio_time then; int delay; so->get_monotonic_time(so, &then); if (port->send_timer_running) { so->stop_timer(port->send_timer); } else { port->send_time = then; add_usec_to_time(&port->send_time, port->chardelay_max); } delay = sub_time(&port->send_time, &then); if (delay > port->chardelay) delay = port->chardelay; else if (delay < 0) { port->send_timer_running = false; goto send_it; } add_usec_to_time(&then, delay); so->start_timer_abs(port->send_timer, &then); port->send_timer_running = true; } out_unlock: so->unlock(port->lock); return count; } static void handle_dev_write_ready(port_info_t *port) { so->lock(port->lock); port->dev_write_handler(port); so->unlock(port->lock); } static void handle_ser_modemstate(port_info_t *port, net_info_t *netcon); static void handle_ser_linestate(port_info_t *port, net_info_t *netcon); int handle_dev_event(struct gensio *io, void *user_data, int event, int err, unsigned char *buf, gensiods *buflen, const char *const *auxdata) { port_info_t *port = user_data; net_info_t *netcon; gensiods len = 0; if (buflen) len = *buflen; switch (event) { case GENSIO_EVENT_READ: if (gensio_str_in_auxdata(auxdata, "oob")) /* Ignore out of bound data. */ return 0; len = handle_dev_read(port, err, buf, len); if (buflen) *buflen = len; return 0; case GENSIO_EVENT_WRITE_READY: handle_dev_write_ready(port); return 0; case GENSIO_EVENT_SER_MODEMSTATE: so->lock(port->lock); port->last_modemstate = *((unsigned int *) buf); for_each_connection(port, netcon) handle_ser_modemstate(port, netcon); so->unlock(port->lock); return 0; case GENSIO_EVENT_SER_LINESTATE: so->lock(port->lock); port->last_linestate = *((unsigned int *) buf); for_each_connection(port, netcon) handle_ser_linestate(port, netcon); so->unlock(port->lock); return 0; #ifdef GENSIO_EVENT_PARMLOG case GENSIO_EVENT_PARMLOG: { struct gensio_parmlog_data *d = (struct gensio_parmlog_data *) buf; seout.vout(&seout, d->log, d->args); return 0; } #endif default: return GE_NOTSUP; } } void port_send_timeout(struct gensio_timer *timer, void *data) { port_info_t *port = (port_info_t *) data; so->lock(port->lock); port->send_timer_running = false; if (port->dev_to_net_state == PORT_CLOSING || port->dev_to_net_state == PORT_CLOSED) { so->unlock(port->lock); return; } if (port->dev_to_net.cursize) start_net_send(port); so->unlock(port->lock); } int gbuf_write(port_info_t *port, struct gbuf *buf) { int err; gensiods written; err = gensio_write(port->io, &written, buf->buf + buf->pos, buf->cursize - buf->pos, NULL); if (err) return err; buf->pos += written; port->dev_bytes_sent += written; if (buf->pos >= buf->cursize) gbuf_reset(buf); return 0; } /* The serial port has room to write some data. This is only activated if a write fails to complete, it is deactivated as soon as writing is available again. */ static void dev_fd_write(port_info_t *port, struct gbuf *buf) { int err; err = gbuf_write(port, buf); if (err) { seout.out(&seout, "The dev write for port %s had error: %s", port->name, gensio_err_to_str(err)); shutdown_port(port, "dev write error"); return; } if (gbuf_cursize(buf) == 0) { /* We are done writing, turn the reader back on. */ enable_all_net_read(port); gensio_set_write_callback_enable(port->io, false); port->net_to_dev_state = PORT_WAITING_INPUT; } } static void handle_dev_fd_normal_write(port_info_t *port) { dev_fd_write(port, &port->net_to_dev); } /* Output the devstr buffer */ static void handle_dev_fd_devstr_write(port_info_t *port) { dev_fd_write(port, port->devstr); if (gbuf_cursize(port->devstr) == 0) { port->dev_write_handler = handle_dev_fd_normal_write; gbuf_free(port->devstr); port->devstr = NULL; /* Send out any data we got on the TCP port. */ handle_dev_fd_normal_write(port); } } /* Data is ready to read on the network port. */ static gensiods handle_net_fd_read(net_info_t *netcon, int readerr, unsigned char *buf, gensiods buflen) { port_info_t *port = netcon->port; gensiods rv = 0; char *reason; int err; so->lock(port->lock); if (port->net_to_dev_state == PORT_WAITING_OUTPUT_CLEAR) /* Catch a race here. */ goto out_unlock; if (readerr) { if (readerr == GE_REMCLOSE) { reason = "network read close"; } else { /* Got an error on the read, shut down the port. */ seout.out(&seout, "read error for port %s: %s", port->name, gensio_err_to_str(readerr)); reason = "network read error"; } goto out_shutdown; } if (port->no_net_to_dev) { rv = buflen; goto out_unlock; } if (buflen > port->net_to_dev.maxsize) buflen = port->net_to_dev.maxsize; netcon->bytes_received += buflen; if (port->net_monitor != NULL) controller_write(port->net_monitor, (char *) buf, buflen); if (port->tw) /* Do write tracing, ignore errors. */ do_trace(port, port->tw, buf, buflen, NET); if (port->tb) /* Do both tracing, ignore errors. */ do_trace(port, port->tb, buf, buflen, NET); memcpy(port->net_to_dev.buf, buf, buflen); port->net_to_dev.cursize = buflen; port->net_to_dev.pos = 0; /* * Don't write anything to the device until devstr is written. * This can happen on UDP ports, we get the first packet before * the port is enabled, so there will be data in the output buffer * but there will also possibly be devstr data. We want the * devstr data to go out first. */ if (port->devstr) goto stop_read_start_write; err = gbuf_write(port, &port->net_to_dev); if (err) { seout.out(&seout, "The dev write(2) for port %s had error: %s", port->name, gensio_err_to_str(err)); shutdown_port(port, "dev write error"); rv = buflen; goto out_unlock; } else { if (port->led_tx) led_flash(port->led_tx); } if (gbuf_cursize(&port->net_to_dev)) { /* We didn't write all the data, shut off the reader and start the write monitor. */ stop_read_start_write: disable_all_net_read(port); gensio_set_write_callback_enable(port->io, true); port->net_to_dev_state = PORT_WAITING_OUTPUT_CLEAR; } reset_timer(netcon); rv = buflen; out_unlock: so->unlock(port->lock); return rv; out_shutdown: shutdown_one_netcon(netcon, reason); goto out_unlock; } /* * Write some network data from a buffer. Returns -1 on something * causing the netcon to shut down, 0 if the write was incomplete, and * 1 if the write was completed. */ static int net_fd_write(port_info_t *port, net_info_t *netcon, struct gbuf *buf, gensiods *pos) { int reterr, to_send; gensiods count = 0; to_send = buf->cursize - *pos; if (to_send <= 0) /* Don't send empty packets, that can confuse UDP clients. */ return 1; /* Can't use buffer send operation here, multiple writers can send from the buffers. */ reterr = gensio_write(netcon->net, &count, buf->buf + *pos, to_send, NULL); if (reterr == GE_REMCLOSE) { shutdown_one_netcon(netcon, "Remote closed"); return -1; } else if (reterr) { /* Some other bad error. */ seout.out(&seout, "The network write for port %s had error: %s", port->name, gensio_err_to_str(reterr)); shutdown_one_netcon(netcon, "network write error"); return -1; } *pos += count; netcon->bytes_sent += count; if (*pos < buf->cursize) return 0; return 1; } static void finish_dev_to_net_write(port_info_t *port) { if (any_net_data_to_write(port)) return; port->dev_to_net.cursize = 0; port->dev_to_net.pos = 0; if (port->net_to_dev_state != PORT_CLOSING) { /* We are done writing on this port, turn the reader back on. */ gensio_set_read_callback_enable(port->io, true); port->dev_to_net_state = PORT_WAITING_INPUT; } } /* The network fd has room to write some data. This is only activated if a write fails to complete, it is deactivated as soon as writing is available again. */ static void handle_net_fd_write_ready(net_info_t *netcon) { port_info_t *port = netcon->port; int rv = 1; so->lock(port->lock); if (netcon->banner) { rv = net_fd_write(port, netcon, netcon->banner, &netcon->banner->pos); if (rv <= 0) goto out_unlock; gbuf_free(netcon->banner); netcon->banner = NULL; } if (port->dev_to_net_state == PORT_WAITING_OUTPUT_CLEAR) { rv = net_fd_write(port, netcon, &port->dev_to_net, &netcon->write_pos); if (rv == 0) goto out_unlock; if (netcon->close_on_output_done) { shutdown_one_netcon(netcon, "port closing"); rv = -1; } finish_dev_to_net_write(port); } out_unlock: if (rv != 0) gensio_set_write_callback_enable(netcon->net, false); if (rv >= 0) reset_timer(netcon); so->unlock(port->lock); } enum s2n_ser_ops { S2N_BAUD = 0, S2N_DATASIZE, S2N_PARITY, S2N_STOPBITS, S2N_FLOWCONTROL, S2N_IFLOWCONTROL, S2N_BREAK, S2N_DTR, S2N_RTS }; #ifdef GENSIO_ACONTROL_SER_BAUD static void handle_ser_modemstate(port_info_t *port, net_info_t *netcon) { if (!netcon->net) return; /* * The 0xf below is non-standard, but the spec makes no * sense in this case. From what I can tell, the * modemstate top 4 bits is the settings, and the bottom 4 * bits is telling you what changed. So you don't want to * report a value unless something changed, and only if it * was in the modemstate mask. */ if (port->last_modemstate & netcon->modemstate_mask & 0xf) { char s[20]; gensiods len = snprintf(s, sizeof(s), "%d", (port->last_modemstate & netcon->modemstate_mask)); gensio_control(netcon->net, GENSIO_CONTROL_DEPTH_FIRST, GENSIO_CONTROL_SET, GENSIO_CONTROL_SER_MODEMSTATE, s, &len); } } static void handle_ser_linestate(port_info_t *port, net_info_t *netcon) { if (!netcon->net) return; if (port->last_linestate & netcon->linestate_mask) { char s[20]; gensiods len = snprintf(s, sizeof(s), "%d", (port->last_linestate & netcon->linestate_mask)); gensio_control(netcon->net, GENSIO_CONTROL_DEPTH_FIRST, GENSIO_CONTROL_SET, GENSIO_CONTROL_SER_LINESTATE, s, &len); } } static void ser_control_set(struct gensio *io, int err, const char *val, gensiods len, void *cb_data) { port_info_t *port = gensio_get_user_data(io); enum s2n_ser_ops op = (intptr_t) cb_data; net_info_t *netcon; so->lock(port->lock); for_each_connection(port, netcon) { struct gensio *io = netcon->net; if (!io) continue; switch (op) { case S2N_BAUD: port->bps = strtol(val, NULL, 0); gensio_acontrol(netcon->net, GENSIO_CONTROL_DEPTH_FIRST, GENSIO_CONTROL_SET, GENSIO_ACONTROL_SER_BAUD, val, strlen(val), NULL, NULL, NULL); break; case S2N_DATASIZE: port->bpc = strtol(val, NULL, 0); gensio_acontrol(netcon->net, GENSIO_CONTROL_DEPTH_FIRST, GENSIO_CONTROL_SET, GENSIO_ACONTROL_SER_DATASIZE, val, strlen(val), NULL, NULL, NULL); break; case S2N_PARITY: if (strcmp(val, "none") == 0) port->paritybits = 0; else port->paritybits = 1; gensio_acontrol(netcon->net, GENSIO_CONTROL_DEPTH_FIRST, GENSIO_CONTROL_SET, GENSIO_ACONTROL_SER_PARITY, val, strlen(val), NULL, NULL, NULL); break; case S2N_STOPBITS: port->stopbits = strtol(val, NULL, 0); gensio_acontrol(netcon->net, GENSIO_CONTROL_DEPTH_FIRST, GENSIO_CONTROL_SET, GENSIO_ACONTROL_SER_STOPBITS, val, strlen(val), NULL, NULL, NULL); break; case S2N_FLOWCONTROL: gensio_acontrol(netcon->net, GENSIO_CONTROL_DEPTH_FIRST, GENSIO_CONTROL_SET, GENSIO_ACONTROL_SER_FLOWCONTROL, val, strlen(val), NULL, NULL, NULL); break; case S2N_IFLOWCONTROL: gensio_acontrol(netcon->net, GENSIO_CONTROL_DEPTH_FIRST, GENSIO_CONTROL_SET, GENSIO_ACONTROL_SER_IFLOWCONTROL, val, strlen(val), NULL, NULL, NULL); break; case S2N_BREAK: gensio_acontrol(netcon->net, GENSIO_CONTROL_DEPTH_FIRST, GENSIO_CONTROL_SET, GENSIO_ACONTROL_SER_SBREAK, val, strlen(val), NULL, NULL, NULL); break; case S2N_DTR: gensio_acontrol(netcon->net, GENSIO_CONTROL_DEPTH_FIRST, GENSIO_CONTROL_SET, GENSIO_ACONTROL_SER_DTR, val, strlen(val), NULL, NULL, NULL); break; case S2N_RTS: gensio_acontrol(netcon->net, GENSIO_CONTROL_DEPTH_FIRST, GENSIO_CONTROL_SET, GENSIO_ACONTROL_SER_RTS, val, strlen(val), NULL, NULL, NULL); break; } } so->unlock(port->lock); } static void s2n_modemstate(net_info_t *netcon, struct gensio *io, unsigned int modemstate) { port_info_t *port = netcon->port; char s[20]; gensiods len; netcon->modemstate_mask = modemstate; #ifdef GENSIO_ACONTROL_SER_SET_MODEMSTATE_MASK len = snprintf(s, sizeof(s), "%d", modemstate); gensio_acontrol(netcon->net, GENSIO_CONTROL_DEPTH_FIRST, GENSIO_CONTROL_SET, GENSIO_ACONTROL_SER_SET_MODEMSTATE_MASK, s, len, NULL, NULL, NULL); len = snprintf(s, sizeof(s), "%d", port->last_modemstate & netcon->modemstate_mask); gensio_control(netcon->net, GENSIO_CONTROL_DEPTH_FIRST, GENSIO_CONTROL_SET, GENSIO_CONTROL_SER_SEND_MODEMSTATE, s, &len); #else len = snprintf(s, sizeof(s), "%d", port->last_modemstate & netcon->modemstate_mask); gensio_control(netcon->net, GENSIO_CONTROL_DEPTH_FIRST, GENSIO_CONTROL_SET, GENSIO_CONTROL_SER_MODEMSTATE, s, &len); #endif } static void s2n_linestate(net_info_t *netcon, struct gensio *io, unsigned int linestate) { port_info_t *port = netcon->port; char s[20]; gensiods len; netcon->linestate_mask = linestate; #ifdef GENSIO_ACONTROL_SER_SET_LINESTATE_MASK len = snprintf(s, sizeof(s), "%d", linestate); gensio_acontrol(netcon->net, GENSIO_CONTROL_DEPTH_FIRST, GENSIO_CONTROL_SET, GENSIO_ACONTROL_SER_SET_LINESTATE_MASK, s, len, NULL, NULL, NULL); len = snprintf(s, sizeof(s), "%d", port->last_linestate & netcon->linestate_mask); gensio_control(netcon->net, GENSIO_CONTROL_DEPTH_FIRST, GENSIO_CONTROL_SET, GENSIO_CONTROL_SER_SEND_LINESTATE, s, &len); #else len = snprintf(s, sizeof(s), "%d", port->last_linestate & netcon->linestate_mask); gensio_control(netcon->net, GENSIO_CONTROL_DEPTH_FIRST, GENSIO_CONTROL_SET, GENSIO_CONTROL_SER_LINESTATE, s, &len); #endif } static void s2n_flowcontrol_state(net_info_t *netcon, bool val) { char s[20]; gensiods len; len = snprintf(s, sizeof(s), "%d", val); gensio_control(netcon->net, GENSIO_CONTROL_DEPTH_FIRST, GENSIO_CONTROL_SET, GENSIO_CONTROL_SER_FLOWCONTROL_STATE, s, &len); } #ifndef GENSIO_SER_FLUSH_BOTH /* This was missed in versions of gensio before 2.8.2. */ const char * gensio_flush_to_str(unsigned int ival) { switch(ival) { case 0: return "0"; case 1: return "recv"; case 2: return "xmit"; case 3: return "both"; default: return "?"; } } #endif static void s2n_flush(net_info_t *netcon, int val) { char s[20]; gensiods len; strncpy(s, gensio_flush_to_str(val), sizeof(s) - 1); len = strlen(s); if (netcon->port->io) gensio_control(netcon->port->io, GENSIO_CONTROL_DEPTH_FIRST, GENSIO_CONTROL_SET, GENSIO_CONTROL_SER_FLUSH, s, &len); if (netcon->net) gensio_control(netcon->net, GENSIO_CONTROL_DEPTH_FIRST, GENSIO_CONTROL_SET, GENSIO_CONTROL_SER_FLUSH, s, &len); } static void s2n_baud(net_info_t *netcon, int baud) { struct gensio *io = netcon->port->io; char s[20]; gensiods len; if (!io) return; len = snprintf(s, sizeof(s), "%d", baud); gensio_acontrol(io, GENSIO_CONTROL_DEPTH_FIRST, GENSIO_CONTROL_SET, GENSIO_ACONTROL_SER_BAUD, s, len, ser_control_set, (void *) (long) S2N_BAUD, NULL); } static void s2n_datasize(net_info_t *netcon, int datasize) { struct gensio *io = netcon->port->io; char s[20]; gensiods len; if (!io) return; len = snprintf(s, sizeof(s), "%d", datasize); gensio_acontrol(io, GENSIO_CONTROL_DEPTH_FIRST, GENSIO_CONTROL_SET, GENSIO_ACONTROL_SER_DATASIZE, s, len, ser_control_set, (void *) (long) S2N_DATASIZE, NULL); } static void s2n_parity(net_info_t *netcon, int parity) { struct gensio *io = netcon->port->io; char s[20]; gensiods len; if (!io) return; len = snprintf(s, sizeof(s), "%s", gensio_parity_to_str(parity)); gensio_acontrol(io, GENSIO_CONTROL_DEPTH_FIRST, GENSIO_CONTROL_SET, GENSIO_ACONTROL_SER_PARITY, s, len, ser_control_set, (void *) (long) S2N_PARITY, NULL); } static void s2n_stopbits(net_info_t *netcon, int stopbits) { struct gensio *io = netcon->port->io; char s[20]; gensiods len; if (!io) return; len = snprintf(s, sizeof(s), "%d", stopbits); gensio_acontrol(io, GENSIO_CONTROL_DEPTH_FIRST, GENSIO_CONTROL_SET, GENSIO_ACONTROL_SER_STOPBITS, s, len, ser_control_set, (void *) (long) S2N_STOPBITS, NULL); } static void s2n_flowcontrol(net_info_t *netcon, int flowcontrol) { struct gensio *io = netcon->port->io; char s[20]; gensiods len; if (!io) return; len = snprintf(s, sizeof(s), "%s", gensio_flowcontrol_to_str(flowcontrol)); gensio_acontrol(io, GENSIO_CONTROL_DEPTH_FIRST, GENSIO_CONTROL_SET, GENSIO_ACONTROL_SER_FLOWCONTROL, s, len, ser_control_set, (void *) (long) S2N_FLOWCONTROL, NULL); } static void s2n_iflowcontrol(net_info_t *netcon, int iflowcontrol) { struct gensio *io = netcon->port->io; char s[20]; gensiods len; if (!io) return; len = snprintf(s, sizeof(s), "%s", gensio_flowcontrol_to_str(iflowcontrol)); gensio_acontrol(io, GENSIO_CONTROL_DEPTH_FIRST, GENSIO_CONTROL_SET, GENSIO_ACONTROL_SER_IFLOWCONTROL, s, len, ser_control_set, (void *) (long) S2N_IFLOWCONTROL, NULL); } static void s2n_sbreak(net_info_t *netcon, int breakv) { struct gensio *io = netcon->port->io; char s[20]; gensiods len; if (!io) return; len = snprintf(s, sizeof(s), "%s", gensio_onoff_to_str(breakv)); gensio_acontrol(io, GENSIO_CONTROL_DEPTH_FIRST, GENSIO_CONTROL_SET, GENSIO_ACONTROL_SER_SBREAK, s, len, ser_control_set, (void *) (long) S2N_BREAK, NULL); } static void s2n_dtr(net_info_t *netcon, int dtr) { struct gensio *io = netcon->port->io; char s[20]; gensiods len; if (!io) return; len = snprintf(s, sizeof(s), "%s", gensio_onoff_to_str(dtr)); gensio_acontrol(io, GENSIO_CONTROL_DEPTH_FIRST, GENSIO_CONTROL_SET, GENSIO_ACONTROL_SER_DTR, s, len, ser_control_set, (void *) (long) S2N_DTR, NULL); } static void s2n_rts(net_info_t *netcon, int rts) { struct gensio *io = netcon->port->io; char s[20]; gensiods len; if (!io) return; len = snprintf(s, sizeof(s), "%s", gensio_onoff_to_str(rts)); gensio_acontrol(io, GENSIO_CONTROL_DEPTH_FIRST, GENSIO_CONTROL_SET, GENSIO_ACONTROL_SER_RTS, s, len, ser_control_set, (void *) (long) S2N_RTS, NULL); } static void s2n_signature(net_info_t *netcon, struct gensio *io, char *sig, unsigned int sig_len) { if (!io) return; gensio_acontrol(io, GENSIO_CONTROL_DEPTH_FIRST, GENSIO_CONTROL_SET, GENSIO_ACONTROL_SER_SIGNATURE, sig, sig_len, NULL, NULL, NULL); } static void s2n_sync(net_info_t *netcon) { struct gensio *io = netcon->port->io; port_info_t *port = netcon->port; if (!io) return; if (port->telnet_brk_on_sync) gensio_acontrol(io, GENSIO_CONTROL_DEPTH_FIRST, GENSIO_CONTROL_SET, GENSIO_CONTROL_SER_SEND_BREAK, "", 0, NULL, NULL, NULL); } static void s2n_break(net_info_t *netcon) { struct gensio *io = netcon->port->io; if (!io) return; gensio_acontrol(io, GENSIO_CONTROL_DEPTH_FIRST, GENSIO_CONTROL_SET, GENSIO_CONTROL_SER_SEND_BREAK, "", 0, NULL, NULL, NULL); } #else #include static void handle_ser_modemstate(port_info_t *port, net_info_t *netcon) { struct sergensio *sio; if (!netcon->net) return; sio = gensio_to_sergensio(netcon->net); if (!sio) return; /* * The 0xf below is non-standard, but the spec makes no * sense in this case. From what I can tell, the * modemstate top 4 bits is the settings, and the bottom 4 * bits is telling you what changed. So you don't want to * report a value unless something changed, and only if it * was in the modemstate mask. */ if (port->last_modemstate & netcon->modemstate_mask & 0xf) sergensio_modemstate(sio, (port->last_modemstate & netcon->modemstate_mask)); } static void handle_ser_linestate(port_info_t *port, net_info_t *netcon) { struct sergensio *sio; if (!netcon->net) return; sio = gensio_to_sergensio(netcon->net); if (!sio) return; if (port->last_linestate & netcon->linestate_mask) sergensio_linestate(sio, (port->last_linestate & netcon->linestate_mask)); } static void sergensio_val_set(struct sergensio *sio, int err, unsigned int val, void *cb_data) { port_info_t *port = sergensio_get_user_data(sio); enum s2n_ser_ops op = (intptr_t) cb_data; net_info_t *netcon; so->lock(port->lock); for_each_connection(port, netcon) { struct sergensio *rsio; if (!netcon->net) continue; rsio = gensio_to_sergensio(netcon->net); if (!rsio) continue; switch (op) { case S2N_BAUD: port->bps = val; sergensio_baud(rsio, val, NULL, NULL); break; case S2N_DATASIZE: port->bpc = val; sergensio_datasize(rsio, val, NULL, NULL); break; case S2N_PARITY: if (val == SERGENSIO_PARITY_NONE) port->paritybits = 0; else port->paritybits = 1; sergensio_parity(rsio, val, NULL, NULL); break; case S2N_STOPBITS: port->stopbits = val; sergensio_stopbits(rsio, val, NULL, NULL); break; case S2N_FLOWCONTROL: sergensio_flowcontrol(rsio, val, NULL, NULL); break; case S2N_IFLOWCONTROL: sergensio_iflowcontrol(rsio, val, NULL, NULL); break; case S2N_BREAK: sergensio_sbreak(rsio, val, NULL, NULL); break; case S2N_DTR: sergensio_dtr(rsio, val, NULL, NULL); break; case S2N_RTS: sergensio_rts(rsio, val, NULL, NULL); break; } } so->unlock(port->lock); } static void s2n_modemstate(net_info_t *netcon, struct gensio *io, unsigned int modemstate) { struct sergensio *sio = gensio_to_sergensio(io); port_info_t *port = netcon->port; if (!sio) return; netcon->modemstate_mask = modemstate; sergensio_modemstate(sio, port->last_modemstate & netcon->modemstate_mask); } static void s2n_linestate(net_info_t *netcon, struct gensio *io, unsigned int linestate) { struct sergensio *sio = gensio_to_sergensio(io); port_info_t *port = netcon->port; if (!sio) return; netcon->linestate_mask = linestate; sergensio_linestate(sio, port->last_linestate & netcon->linestate_mask); } static void s2n_flowcontrol_state(net_info_t *netcon, bool val) { struct sergensio *rsio = gensio_to_sergensio(netcon->port->io); if (!rsio) return; sergensio_flowcontrol_state(rsio, val); } static void s2n_flush(net_info_t *netcon, int val) { struct sergensio *port_rsio = gensio_to_sergensio(netcon->port->io); struct sergensio *net_rsio = gensio_to_sergensio(netcon->net); if (port_rsio) sergensio_flush(port_rsio, val); if (net_rsio) sergensio_flush(net_rsio, val); } static void s2n_baud(net_info_t *netcon, int baud) { struct sergensio *rsio = gensio_to_sergensio(netcon->port->io); if (!rsio) return; sergensio_baud(rsio, baud, sergensio_val_set, (void *) (long) S2N_BAUD); } static void s2n_datasize(net_info_t *netcon, int datasize) { struct sergensio *rsio = gensio_to_sergensio(netcon->port->io); if (!rsio) return; sergensio_datasize(rsio, datasize, sergensio_val_set, (void *) (long) S2N_DATASIZE); } static void s2n_parity(net_info_t *netcon, int parity) { struct sergensio *rsio = gensio_to_sergensio(netcon->port->io); if (!rsio) return; sergensio_parity(rsio, parity, sergensio_val_set, (void *) (long) S2N_PARITY); } static void s2n_stopbits(net_info_t *netcon, int stopbits) { struct sergensio *rsio = gensio_to_sergensio(netcon->port->io); if (!rsio) return; sergensio_stopbits(rsio, stopbits, sergensio_val_set, (void *) (long) S2N_STOPBITS); } static void s2n_flowcontrol(net_info_t *netcon, int flowcontrol) { struct sergensio *rsio = gensio_to_sergensio(netcon->port->io); if (!rsio) return; sergensio_flowcontrol(rsio, flowcontrol, sergensio_val_set, (void *) (long) S2N_FLOWCONTROL); } static void s2n_iflowcontrol(net_info_t *netcon, int iflowcontrol) { struct sergensio *rsio = gensio_to_sergensio(netcon->port->io); if (!rsio) return; sergensio_iflowcontrol(rsio, iflowcontrol, sergensio_val_set, (void *) (long) S2N_IFLOWCONTROL); } static void s2n_sbreak(net_info_t *netcon, int breakv) { struct sergensio *rsio = gensio_to_sergensio(netcon->port->io); if (!rsio) return; sergensio_sbreak(rsio, breakv, sergensio_val_set, (void *) (long) S2N_BREAK); } static void s2n_dtr(net_info_t *netcon, int dtr) { struct sergensio *rsio = gensio_to_sergensio(netcon->port->io); if (!rsio) return; sergensio_dtr(rsio, dtr, sergensio_val_set, (void *) (long) S2N_DTR); } static void s2n_rts(net_info_t *netcon, int rts) { struct sergensio *rsio = gensio_to_sergensio(netcon->port->io); if (!rsio) return; sergensio_rts(rsio, rts, sergensio_val_set, (void *) (long) S2N_RTS); } static void s2n_signature(net_info_t *netcon, struct gensio *io, char *sig, unsigned int sig_len) { struct sergensio *sio = gensio_to_sergensio(io); port_info_t *port = netcon->port; if (!sio) return; sig = port->signaturestr; if (!sig) sig = rfc2217_signature; sig_len = strlen(sig); sergensio_signature(sio, sig, sig_len, NULL, NULL); } static void s2n_sync(net_info_t *netcon) { struct sergensio *rsio = gensio_to_sergensio(netcon->port->io); port_info_t *port = netcon->port; if (!rsio) return; if (port->telnet_brk_on_sync) sergensio_send_break(rsio); } static void s2n_break(net_info_t *netcon) { struct sergensio *rsio = gensio_to_sergensio(netcon->port->io); if (!rsio) return; sergensio_send_break(rsio); } #endif static int handle_net_event(struct gensio *net, void *user_data, int event, int err, unsigned char *buf, gensiods *buflen, const char *const *auxdata) { net_info_t *netcon = user_data; gensiods len = 0; if (buflen) len = *buflen; switch (event) { case GENSIO_EVENT_READ: len = handle_net_fd_read(netcon, err, buf, len); if (buflen) *buflen = len; return 0; case GENSIO_EVENT_WRITE_READY: handle_net_fd_write_ready(netcon); return 0; case GENSIO_EVENT_SEND_BREAK: s2n_break(netcon); return 0; #ifdef GENSIO_EVENT_SER_MODEMSTATE_MASK case GENSIO_EVENT_SER_MODEMSTATE_MASK: #else case GENSIO_EVENT_SER_MODEMSTATE: #endif s2n_modemstate(netcon, net, *((unsigned int *) buf)); return 0; #ifdef GENSIO_EVENT_SER_LINESTATE_MASK case GENSIO_EVENT_SER_LINESTATE_MASK: #else case GENSIO_EVENT_SER_LINESTATE: #endif s2n_linestate(netcon, net, *((unsigned int *) buf)); return 0; case GENSIO_EVENT_SER_SIGNATURE: s2n_signature(netcon, net, (char *) buf, len); return 0; case GENSIO_EVENT_SER_FLOW_STATE: s2n_flowcontrol_state(netcon, *((int *) buf)); return 0; case GENSIO_EVENT_SER_FLUSH: s2n_flush(netcon, *((int *) buf)); return 0; case GENSIO_EVENT_SER_SYNC: s2n_sync(netcon); return 0; case GENSIO_EVENT_SER_BAUD: s2n_baud(netcon, *((int *) buf)); return 0; case GENSIO_EVENT_SER_DATASIZE: s2n_datasize(netcon, *((int *) buf)); return 0; case GENSIO_EVENT_SER_PARITY: s2n_parity(netcon, *((int *) buf)); return 0; case GENSIO_EVENT_SER_STOPBITS: s2n_stopbits(netcon, *((int *) buf)); return 0; case GENSIO_EVENT_SER_FLOWCONTROL: s2n_flowcontrol(netcon, *((int *) buf)); return 0; case GENSIO_EVENT_SER_IFLOWCONTROL: s2n_iflowcontrol(netcon, *((int *) buf)); return 0; case GENSIO_EVENT_SER_SBREAK: s2n_sbreak(netcon, *((int *) buf)); return 0; case GENSIO_EVENT_SER_DTR: s2n_dtr(netcon, *((int *) buf)); return 0; case GENSIO_EVENT_SER_RTS: s2n_rts(netcon, *((int *) buf)); return 0; #ifdef GENSIO_EVENT_PARMLOG case GENSIO_EVENT_PARMLOG: { struct gensio_parmlog_data *d = (struct gensio_parmlog_data *) buf; seout.vout(&seout, d->log, d->args); return 0; } #endif default: return GE_NOTSUP; } } static void recalc_port_chardelay(port_info_t *port) { unsigned int bpc = port->bpc + port->stopbits + port->paritybits + 1; /* delay is (((1 / bps) * bpc) * scale) seconds */ if (!port->enable_chardelay) { port->chardelay = 0; return; } /* We are working in microseconds here. */ port->chardelay = (bpc * 100000 * port->chardelay_scale) / port->bps; if (port->chardelay < port->chardelay_min) port->chardelay = port->chardelay_min; } static void finish_setup_net(port_info_t *port, net_info_t *netcon) { gensio_set_callback(netcon->net, handle_net_event, netcon); gensio_set_read_callback_enable(netcon->net, true); gensio_set_write_callback_enable(netcon->net, true); header_trace(port, netcon); reset_timer(netcon); } static void extract_bps_bpc(port_info_t *port) { char buf[1024], *s, *speed; if (net_raddr_str(port->io, buf, sizeof(buf)) == 0) goto out_broken; s = strchr(buf, ','); if (!s) goto out_broken; speed = s; while (isdigit(*s)) s++; if (s == speed) goto out_broken; port->bps = strtoul(speed, NULL, 10); if (*s == 'N') port->paritybits = 0; else port->paritybits = 1; if (*s) s++; if (isdigit(*s)) port->bpc = *s = '0'; else port->bpc = 8; if (*s) s++; if (*s == '2') port->stopbits = 2; else port->stopbits = 1; return; out_broken: port->bps = 9600; port->paritybits = 0; port->stopbits = 1; port->bpc = 8; } static void port_dev_open_done(struct gensio *io, int err, void *cb_data) { port_info_t *port = cb_data; net_info_t *netcon; so->lock(port->lock); if (err) { char errstr[200]; port->io_open = false; snprintf(errstr, sizeof(errstr), "Device open failure: %s\r\n", gensio_err_to_str(err)); for_each_connection(port, netcon) { if (!netcon->net) continue; gensio_write(netcon->net, NULL, errstr, strlen(errstr), NULL); report_disconnect(port, netcon); gensio_free(netcon->net); netcon->net = NULL; } shutdown_port(port, "Device open failure"); goto out_unlock; } extract_bps_bpc(port); recalc_port_chardelay(port); if (port->devstr) gbuf_free(port->devstr); port->devstr = process_str_to_buf(port, NULL, port->openstr, &seout); if (port->devstr) port->dev_write_handler = handle_dev_fd_devstr_write; else port->dev_write_handler = handle_dev_fd_normal_write; if (port->devstr) gensio_set_write_callback_enable(port->io, true); gensio_set_read_callback_enable(port->io, true); setup_trace(port, &seout); port_start_timer(port); for_each_connection(port, netcon) { if (!netcon->net) continue; finish_setup_net(port, netcon); } port->net_to_dev_state = PORT_WAITING_INPUT; out_unlock: so->unlock(port->lock); } int port_dev_enable(port_info_t *port) { int err; char auxdata[2] = "1"; err = gensio_open(port->io, port_dev_open_done, port); if (err) return err; port->dev_to_net_state = PORT_WAITING_INPUT; port->io_open = true; err = gensio_control(port->io, GENSIO_CONTROL_DEPTH_ALL, false, GENSIO_CONTROL_NODELAY, auxdata, NULL); if (err) seout.out(&seout, "Could not enable NODELAY on port %s: %s", port->name, gensio_err_to_str(err)); return 0; } /* Called when a new user is added to the port. */ static void setup_port(port_info_t *port, net_info_t *netcon) { int err; char auxdata[2] = "1"; err = gensio_control(netcon->net, GENSIO_CONTROL_DEPTH_ALL, false, GENSIO_CONTROL_NODELAY, auxdata, NULL); if (err) seout.out(&seout, "Could not enable NODELAY on socket %s: %s", port->name, gensio_err_to_str(err)); if (netcon->banner) gbuf_free(netcon->banner); netcon->banner = process_str_to_buf(port, netcon, port->bannerstr, &seout); if (num_connected_net(port) == 1 && (!port->connbacks || !port->io_open)) { /* We are first, set things up on the device. */ err = port_dev_enable(port); if (err) { char errstr[200]; snprintf(errstr, sizeof(errstr), "Device open failure: %s\r\n", gensio_err_to_str(err)); gensio_write(netcon->net, NULL, errstr, strlen(errstr), NULL); report_disconnect(port, netcon); gensio_free(netcon->net); netcon->net = NULL; } return; } finish_setup_net(port, netcon); } void handle_new_net(port_info_t *port, struct gensio *net, net_info_t *netcon) { netcon->net = net; report_newcon(port, netcon); /* XXX log netcon->remote */ setup_port(port, netcon); } ser2net-4.6.2/dataxfer.h000066400000000000000000000103071461221161700150560ustar00rootroot00000000000000/* * ser2net - A program for allowing telnet connection to serial ports * Copyright (C) 2001-2020 Corey Minyard * * SPDX-License-Identifier: GPL-2.0-only * * In addition, as a special exception, the copyright holders of * ser2net give you permission to combine ser2net with free software * programs or libraries that are released under the GNU LGPL and * with code included in the standard release of OpenSSL under the * OpenSSL license (or modified versions of such code, with unchanged * license). You may copy and distribute such a system following the * terms of the GNU GPL for ser2net and the licenses of the other code * concerned, provided that you include the source code of that * other code when and as the GNU GPL requires distribution of source * code. * * Note that people who make modified versions of ser2net are not * obligated to grant this special exception for their modified * versions; it is their choice whether to do so. The GNU General * Public License gives permission to release a modified version * without this exception; this exception also makes it possible to * release a modified version which carries forward this exception. */ #ifndef DATAXFER #define DATAXFER #include "controller.h" #ifdef linux #include /* Check, if the toolchain provides SER_RS485_RX_DURING_TX macro * (introduced in kernel 3.2) */ #if HAVE_DECL_TIOCSRS485 #ifndef SER_RS485_RX_DURING_TX #define SER_RS485_RX_DURING_TX (1 << 4) #endif /* SER_RS485_RX_DURING_TX */ #endif /* HAVE_DECL_TIOCSRS485 */ #endif /* linux */ /* Create a port given the criteria. */ int portconfig(struct absout *eout, const char *name, const char *accstr, const char *state, unsigned int timeout, const char *devname, const char * const *devcfg); void apply_new_ports(struct absout *eout); /* Shut down all the ports, and provide a way to check when done. */ void shutdown_ports(void); int check_ports_shutdown(void); /* Initialize the data transfer code. */ void dataxfer_init(void); /* Show information about a port (or all ports if portspec is NULL). The parameters are all strings that the routine will convert to integers. Error output will be generated on invalid data. */ void showports(struct controller_info *cntlr, const char *portspec, bool yaml); /* Show information about a port (as above) but in a one-line format. */ void showshortports(struct controller_info *cntlr, const char *portspec); /* Set the port's timeout. The parameters are all strings that the routine will convert to integers. Error output will be generated on invalid data. */ void setporttimeout(struct controller_info *cntlr, const char *portspec, const char *timeout); /* Modify the DTR and RTS lines for the port. */ void setportcontrol(struct controller_info *cntlr, const char *portspec, char * const controls[]); /* Set the enable state of a port (off, raw, telnet). The parameters are all strings that the routine will convert to integers. Error output will be generated on invalid data. */ void setportenable(struct controller_info *cntlr, const char *portspec, const char *enable); /* Start data monitoring on the given port, type may be either "tcp" or "term" and only one direction may be monitored. This return NULL if the monitor fails. The monitor output will go to the controller via the controller_write() call. */ void *data_monitor_start(struct controller_info *cntlr, const char *type, const char *portspec); /* Stop monitoring the given id. */ void data_monitor_stop(struct controller_info *cntlr, void *monitor_id); /* Shut down the port, if it is connected. */ void disconnect_port(struct controller_info *cntlr, const char *portspec); struct devio; /* Initialization function for device I/O */ int devcfg_init(struct devio *io, struct absout *eout, const char *instr, int (*otherconfig)(void *data, struct absout *eout, const char *item), void *data); int add_rotator(struct absout *eout, const char *name, const char *accstr, int portc, const char **ports, const char **options, int lineno); void free_rotators(void); #endif /* DATAXFER */ ser2net-4.6.2/defaults.c000066400000000000000000000137741461221161700150750ustar00rootroot00000000000000/* * ser2net - A program for allowing telnet connection to serial ports * Copyright (C) 2001-2020 Corey Minyard * * SPDX-License-Identifier: GPL-2.0-only * * In addition, as a special exception, the copyright holders of * ser2net give you permission to combine ser2net with free software * programs or libraries that are released under the GNU LGPL and * with code included in the standard release of OpenSSL under the * OpenSSL license (or modified versions of such code, with unchanged * license). You may copy and distribute such a system following the * terms of the GNU GPL for ser2net and the licenses of the other code * concerned, provided that you include the source code of that * other code when and as the GNU GPL requires distribution of source * code. * * Note that people who make modified versions of ser2net are not * obligated to grant this special exception for their modified * versions; it is their choice whether to do so. The GNU General * Public License gives permission to release a modified version * without this exception; this exception also makes it possible to * release a modified version which carries forward this exception. */ #include #include #include #include "ser2net.h" #include "defaults.h" #define PORT_BUFSIZE 64 /* Default data transfer buffer size */ struct default_data { const char *name; enum gensio_default_type type; int min; int max; struct { int intval; const char *strval; } def; struct gensio_enum_val *enums; }; static struct default_data defaults[] = { /* All port types */ { "telnet-brk-on-sync",GENSIO_DEFAULT_BOOL,.def.intval = 0 }, { "kickolduser", GENSIO_DEFAULT_BOOL, .def.intval = 0 }, { "chardelay", GENSIO_DEFAULT_BOOL, .def.intval = 1 }, { "chardelay-scale",GENSIO_DEFAULT_INT, .min = 1, .max = 1000, .def.intval = 20 }, { "chardelay-min", GENSIO_DEFAULT_INT, .min = 1, .max = 100000, .def.intval = 1000 }, { "chardelay-max", GENSIO_DEFAULT_INT, .min = 1, .max = 1000000, .def.intval = 20000 }, { "dev-to-net-bufsize", GENSIO_DEFAULT_INT,.min = 1, .max = 65536, .def.intval = PORT_BUFSIZE }, { "net-to-dev-bufsize", GENSIO_DEFAULT_INT,.min = 1, .max = 65536, .def.intval = PORT_BUFSIZE }, { "max-connections", GENSIO_DEFAULT_INT, .min=1, .max=65536, .def.intval = 1 }, { "connector-retry-time", GENSIO_DEFAULT_INT, .min=1, .max=10000000, .def.intval = 10 }, { "accepter-retry-time", GENSIO_DEFAULT_INT, .min=1, .max=10000000, .def.intval = 10 }, { "remaddr", GENSIO_DEFAULT_STR, .def.strval = NULL }, { "connback", GENSIO_DEFAULT_STR, .def.strval = NULL }, { "authdir", GENSIO_DEFAULT_STR, .def.strval = NULL }, { "authdir-admin", GENSIO_DEFAULT_STR, .def.strval = NULL }, { "pamauth", GENSIO_DEFAULT_STR, .def.strval = NULL }, { "pamauth-admin", GENSIO_DEFAULT_STR, .def.strval = NULL }, { "allowed-users", GENSIO_DEFAULT_STR, .def.strval = NULL }, { "signature", GENSIO_DEFAULT_STR, .def.strval = "ser2net" }, { "openstr", GENSIO_DEFAULT_STR, .def.strval = NULL }, { "closestr", GENSIO_DEFAULT_STR, .def.strval = NULL }, { "closeon", GENSIO_DEFAULT_STR, .def.strval = NULL }, { "banner", GENSIO_DEFAULT_STR, .def.strval = NULL }, { "sendon", GENSIO_DEFAULT_STR, .def.strval = NULL }, { "mdns", GENSIO_DEFAULT_BOOL, .def.intval = 0 }, { "mdns-sysattrs", GENSIO_DEFAULT_BOOL, .def.intval = 0 }, { "mdns-type", GENSIO_DEFAULT_STR, .def.strval = NULL }, { "mdns-domain", GENSIO_DEFAULT_STR, .def.strval = NULL }, { "mdns-host", GENSIO_DEFAULT_STR, .def.strval = NULL }, { "mdns-interface", GENSIO_DEFAULT_INT, .min=-1, .max=100000, .def.intval = -1}, { NULL } }; static int setup_ser2net_defaults(void) { unsigned int i; int err; for (i = 0; defaults[i].name; i++) { err = gensio_set_default(so, NULL, defaults[i].name, defaults[i].def.strval, defaults[i].def.intval); if (err) return err; } err = gensio_set_default(so, NULL, "authdir", authdir, 0); if (err) return err; err = gensio_set_default(so, NULL, "authdir-admin", admin_authdir, 0); if (err) return err; err = gensio_set_default(so, "ssl", "key", keyfile, 0); if (err) return err; err = gensio_set_default(so, "ssl", "cert", certfile, 0); if (err) return err; #ifdef gensio_version_ge /* gensio_version_ge came in with 2.2.3 and 2.3.0 */ /* Print out a message on the socket if tcpd denies a connection. */ err = gensio_set_default(so, "tcp", "tcpd", "print", 0); /* If GE_NOTFOUND is returned, that means gensio doesn't have tcpd. */ if (err && err != GE_NOTFOUND) return err; #endif return 0; } int setup_defaults(void) { unsigned int i; int err; static bool defaults_added = false; if (defaults_added) { gensio_reset_defaults(so); } else { for (i = 0; defaults[i].name; i++) { err = gensio_add_default(so, defaults[i].name, defaults[i].type, defaults[i].def.strval, defaults[i].def.intval, defaults[i].min, defaults[i].max, defaults[i].enums); if (err && err != GE_EXISTS) return err; } defaults_added = true; } return setup_ser2net_defaults(); } int find_default_int(const char *name) { int err, val; err = gensio_get_default(so, "ser2net", name, false, GENSIO_DEFAULT_INT, NULL, &val); if (err) abort(); return val; } bool find_default_bool(const char *name) { int err; int val; err = gensio_get_default(so, "ser2net", name, false, GENSIO_DEFAULT_BOOL, NULL, &val); if (err) abort(); return val; } int find_default_str(const char *name, char **rstr) { int err; char *val; char *newstr = NULL; err = gensio_get_default(so, "ser2net", name, false, GENSIO_DEFAULT_STR, &val, NULL); if (err) abort(); if (val) { newstr = strdup(val); so->free(so, val); if (!newstr) return GE_NOMEM; } *rstr = newstr; return 0; } ser2net-4.6.2/defaults.h000066400000000000000000000032001461221161700150610ustar00rootroot00000000000000/* * ser2net - A program for allowing telnet connection to serial ports * Copyright (C) 2001-2020 Corey Minyard * * SPDX-License-Identifier: GPL-2.0-only * * In addition, as a special exception, the copyright holders of * ser2net give you permission to combine ser2net with free software * programs or libraries that are released under the GNU LGPL and * with code included in the standard release of OpenSSL under the * OpenSSL license (or modified versions of such code, with unchanged * license). You may copy and distribute such a system following the * terms of the GNU GPL for ser2net and the licenses of the other code * concerned, provided that you include the source code of that * other code when and as the GNU GPL requires distribution of source * code. * * Note that people who make modified versions of ser2net are not * obligated to grant this special exception for their modified * versions; it is their choice whether to do so. The GNU General * Public License gives permission to release a modified version * without this exception; this exception also makes it possible to * release a modified version which carries forward this exception. */ #ifndef DEFAULTS #define DEFAULTS #include int setup_defaults(void); /* Return the default int/bool value for the given name. */ int find_default_int(const char *name); bool find_default_bool(const char *name); /* Return the default string value for the given name. Return GE_NOMEM if out of memory. The returned value must be freed. */ int find_default_str(const char *name, char **rstr); #endif /* DEFAULTS */ ser2net-4.6.2/fileio.c000066400000000000000000000131011461221161700145150ustar00rootroot00000000000000/* * ser2net - A program for allowing telnet connection to serial ports * Copyright (C) 2023 Corey Minyard * * SPDX-License-Identifier: GPL-2.0-only * * In addition, as a special exception, the copyright holders of * ser2net give you permission to combine ser2net with free software * programs or libraries that are released under the GNU LGPL and * with code included in the standard release of OpenSSL under the * OpenSSL license (or modified versions of such code, with unchanged * license). You may copy and distribute such a system following the * terms of the GNU GPL for ser2net and the licenses of the other code * concerned, provided that you include the source code of that * other code when and as the GNU GPL requires distribution of source * code. * * Note that people who make modified versions of ser2net are not * obligated to grant this special exception for their modified * versions; it is their choice whether to do so. The GNU General * Public License gives permission to release a modified version * without this exception; this exception also makes it possible to * release a modified version which carries forward this exception. */ #include #include #include #include #include #include #include #include "ser2net.h" #include "fileio.h" #define READBUF_SIZE 1024 struct ftypes { FILE *f; char *rbuf; unsigned int rpos; unsigned int rlen; }; int f_open(const char *filename, int iop, int mode, ftype **rf) { ftype *f; char *op; int oop = 0, rv = 0, fd; if (iop & DO_READ && iop & DO_WRITE) { oop = O_RDWR; op = "r+"; } else if (iop & DO_READ) { oop = O_RDONLY; op = "r"; } else if (iop & DO_WRITE) { oop = O_WRONLY; op = "w"; } else { /* Must set read or write. */ return GE_INVAL; } if (iop & DO_CREATE) oop |= O_CREAT; f = malloc(sizeof(*f)); if (!f) return GE_NOMEM; f->f = NULL; f->rbuf = NULL; if (iop & DO_READ) { f->rbuf = malloc(READBUF_SIZE); if (!f->rbuf) { rv = GE_NOMEM; goto out_err; } f->rpos = 0; f->rlen = 0; } fd = open(filename, oop, mode); if (fd == -1) { rv = gensio_os_err_to_err(so, errno); goto out_err; } f->f = fdopen(fd, op); if (!f->f) { close(fd); rv = gensio_os_err_to_err(so, errno); goto out_err; } if (iop & DO_APPEND) { rv = fseek(f->f, 0, SEEK_END); if (rv == -1) { rv = gensio_os_err_to_err(so, errno); goto out_err; } else { rv = 0; } } out_err: if (rv) { if (f->rbuf) free(f->rbuf); free(f); } else { *rf = f; } return rv; } int f_stdio_open(FILE *stdf, int op, int mode, ftype **rf) { ftype *f = malloc(sizeof(*f)); if (!f) return GE_NOMEM; if (op & DO_READ) { f->rbuf = malloc(READBUF_SIZE); if (!f->rbuf) { free(f); return GE_NOMEM; } f->rpos = 0; f->rlen = 0; } f->f = stdf; *rf = f; return 0; } int f_close(ftype *f) { fclose(f->f); if (f->rbuf) free(f->rbuf); free(f); return 0; } int f_write(ftype *f, const void *buf, unsigned int len, unsigned int *outlen) { int rv = 0; if (len > 0) { rv = fwrite(buf, 1, len, f->f); if (rv == 0) return gensio_os_err_to_err(so, errno); /* * This is primarily used for tracing, so flush on every write. */ fflush(f->f); } if (outlen) *outlen = rv; return 0; } int f_read(ftype *f, void *ibuf, unsigned int len, unsigned int *routlen) { char *buf = ibuf; unsigned int clen; unsigned int outlen = 0; int rv = 0, rc; if (!f->rbuf) return GE_NOTSUP; while (len > 0) { if (f->rlen > 0) { clen = len; if (len > f->rlen) clen = f->rlen; memcpy(buf, f->rbuf + f->rpos, clen); f->rlen -= clen; f->rpos += clen; len -= clen; buf += clen; outlen += clen; continue; } rc = fread(f->rbuf, 1, READBUF_SIZE, f->f); if (rc == 0) { if (feof(f->f)) { if (outlen == 0) rv = GE_REMCLOSE; } else { rv = gensio_os_err_to_err(so, errno); } break; } f->rlen = rc; f->rpos = 0; } if (!rv && routlen) *routlen = outlen; return rv; } int f_gets(ftype *f, char **ibuf, unsigned int *ilen, unsigned int *ibuflen) { int rv = 0, rc; unsigned int len = *ilen, buflen = *ibuflen; char *buf = *ibuf, *newbuf = NULL; if (!f->rbuf) return GE_NOTSUP; if (len > buflen) return GE_INVAL; do { while (f->rlen > 0) { /* Do this first so we have room for a '\0'. */ if (len == buflen) { newbuf = realloc(buf, buflen + 256); if (!newbuf) { rv = GE_NOMEM; goto out_err; } buf = newbuf; *ibuf = newbuf; buflen += 256; *ibuflen = buflen; } if (f->rbuf[f->rpos] == '\n') { f->rpos++; f->rlen--; goto found; } buf[len++] = f->rbuf[f->rpos++]; f->rlen--; } rc = fread(f->rbuf, 1, READBUF_SIZE, f->f); if (rc == 0) { if (feof(f->f)) { if (len == *ilen) rv = GE_REMCLOSE; } else { rv = gensio_os_err_to_err(so, errno); goto out_err; } break; } f->rlen = rc; f->rpos = 0; } while(1); found: /* * This is safe without a check, we checked for room before * looking for the '\n'. */ buf[len] = '\0'; *ilen = len; out_err: return rv; } int f_seek(ftype *f, unsigned int pos, int op) { int rv; if (op != SEEK_ABSOLUTE) return GE_INVAL; rv = fseek(f->f, pos, SEEK_SET); if (rv == -1) { rv = gensio_os_err_to_err(so, errno); } else { f->rlen = 0; rv = 0; } return rv; } ser2net-4.6.2/fileio.h000066400000000000000000000035011461221161700145250ustar00rootroot00000000000000/* * ser2net - A program for allowing telnet connection to serial ports * Copyright (C) 2023 Corey Minyard * * SPDX-License-Identifier: GPL-2.0-only * * In addition, as a special exception, the copyright holders of * ser2net give you permission to combine ser2net with free software * programs or libraries that are released under the GNU LGPL and * with code included in the standard release of OpenSSL under the * OpenSSL license (or modified versions of such code, with unchanged * license). You may copy and distribute such a system following the * terms of the GNU GPL for ser2net and the licenses of the other code * concerned, provided that you include the source code of that * other code when and as the GNU GPL requires distribution of source * code. * * Note that people who make modified versions of ser2net are not * obligated to grant this special exception for their modified * versions; it is their choice whether to do so. The GNU General * Public License gives permission to release a modified version * without this exception; this exception also makes it possible to * release a modified version which carries forward this exception. */ #ifndef FILEIO_H #define FILEIO_H #include typedef struct ftypes ftype; #define DO_READ 1 #define DO_WRITE 2 #define DO_CREATE 4 #define DO_APPEND 8 int f_open(const char *filename, int op, int mode, ftype **f); int f_stdio_open(FILE *stdf, int op, int mode, ftype **f); int f_close(ftype *f); int f_write(ftype *f, const void *buf, unsigned int len, unsigned int *outlen); int f_read(ftype *f, void *buf, unsigned int len, unsigned int *outlen); int f_gets(ftype *f, char **buf, unsigned int *len, unsigned int *buflen); #define SEEK_ABSOLUTE 1 int f_seek(ftype *f, unsigned int pos, int op); #endif /* FILEIO_H */ ser2net-4.6.2/gbuf.c000066400000000000000000000036361461221161700142050ustar00rootroot00000000000000/* * ser2net - A program for allowing telnet connection to serial ports * Copyright (C) 2001-2020 Corey Minyard * * SPDX-License-Identifier: GPL-2.0-only * * In addition, as a special exception, the copyright holders of * ser2net give you permission to combine ser2net with free software * programs or libraries that are released under the GNU LGPL and * with code included in the standard release of OpenSSL under the * OpenSSL license (or modified versions of such code, with unchanged * license). You may copy and distribute such a system following the * terms of the GNU GPL for ser2net and the licenses of the other code * concerned, provided that you include the source code of that * other code when and as the GNU GPL requires distribution of source * code. * * Note that people who make modified versions of ser2net are not * obligated to grant this special exception for their modified * versions; it is their choice whether to do so. The GNU General * Public License gives permission to release a modified version * without this exception; this exception also makes it possible to * release a modified version which carries forward this exception. */ #include #include #include "gbuf.h" gensiods gbuf_room_left(struct gbuf *buf) { return buf->maxsize - buf->cursize; } void gbuf_append(struct gbuf *buf, unsigned char *data, gensiods len) { memcpy(buf->buf + buf->pos, data, len); buf->cursize += len; buf->pos += len; } gensiods gbuf_cursize(struct gbuf *buf) { return buf->cursize; } void gbuf_reset(struct gbuf *buf) { buf->cursize = 0; buf->pos = 0; } int gbuf_init(struct gbuf *buf, gensiods size) { buf->buf = malloc(size); if (!buf->buf) return GE_NOMEM; buf->maxsize = size; gbuf_reset(buf); return 0; } void gbuf_free(struct gbuf *buf) { if (buf->buf) free(buf->buf); free(buf); } ser2net-4.6.2/gbuf.h000066400000000000000000000032171461221161700142050ustar00rootroot00000000000000/* * ser2net - A program for allowing telnet connection to serial ports * Copyright (C) 2001-2020 Corey Minyard * * SPDX-License-Identifier: GPL-2.0-only * * In addition, as a special exception, the copyright holders of * ser2net give you permission to combine ser2net with free software * programs or libraries that are released under the GNU LGPL and * with code included in the standard release of OpenSSL under the * OpenSSL license (or modified versions of such code, with unchanged * license). You may copy and distribute such a system following the * terms of the GNU GPL for ser2net and the licenses of the other code * concerned, provided that you include the source code of that * other code when and as the GNU GPL requires distribution of source * code. * * Note that people who make modified versions of ser2net are not * obligated to grant this special exception for their modified * versions; it is their choice whether to do so. The GNU General * Public License gives permission to release a modified version * without this exception; this exception also makes it possible to * release a modified version which carries forward this exception. */ #ifndef GBUF #define GBUF #include struct gbuf { unsigned char *buf; gensiods maxsize; gensiods cursize; gensiods pos; }; gensiods gbuf_room_left(struct gbuf *buf); void gbuf_append(struct gbuf *buf, unsigned char *data, gensiods len); gensiods gbuf_cursize(struct gbuf *buf); void gbuf_reset(struct gbuf *buf); int gbuf_init(struct gbuf *buf, gensiods size); void gbuf_free(struct gbuf *buf); #endif /* GBUF */ ser2net-4.6.2/led.c000066400000000000000000000116421461221161700140220ustar00rootroot00000000000000/* * ser2net - A program for allowing telnet connection to serial ports * Copyright (C) 2016-2020 Corey Minyard * Copyright (C) 2016 Michael Heimpold * * SPDX-License-Identifier: GPL-2.0-only * * In addition, as a special exception, the copyright holders of * ser2net give you permission to combine ser2net with free software * programs or libraries that are released under the GNU LGPL and * with code included in the standard release of OpenSSL under the * OpenSSL license (or modified versions of such code, with unchanged * license). You may copy and distribute such a system following the * terms of the GNU GPL for ser2net and the licenses of the other code * concerned, provided that you include the source code of that * other code when and as the GNU GPL requires distribution of source * code. * * Note that people who make modified versions of ser2net are not * obligated to grant this special exception for their modified * versions; it is their choice whether to do so. The GNU General * Public License gives permission to release a modified version * without this exception; this exception also makes it possible to * release a modified version which carries forward this exception. */ #include #include #include #include #include #include #include "ser2net.h" #include "led.h" #include "led_sysfs.h" /* list of all registered LED drivers */ static struct led_driver_s *led_drivers = NULL; static struct gensio_lock *led_lock; /* all LEDs in the system. */ static struct led_s *leds = NULL; static void lock_leds(void) { gensio_os_funcs_lock(so, led_lock); } static void unlock_leds(void) { gensio_os_funcs_unlock(so, led_lock); } static struct led_driver_s * led_driver_by_name(const char *name) { struct led_driver_s *drv = led_drivers; while (drv) { if (strcmp(name, drv->name) == 0) return drv; drv = drv->next; } return NULL; } int led_driver_init(void) { int rv = 0; led_lock = gensio_os_funcs_alloc_lock(so); if (!led_lock) { fprintf(stderr, "Could not alloc ser2net led lock\n"); return -1; } rv |= led_sysfs_register(); return rv; } int led_driver_register(struct led_driver_s *led_driver) { led_driver->next = led_drivers; led_drivers = led_driver; return 0; } static struct led_s * i_find_led(const char *name) { struct led_s *led = leds; while (led) { if (strcmp(name, led->name) == 0) break; led = led->next; } return led; } struct led_s * find_led(const char *name) { struct led_s *led; lock_leds(); led = i_find_led(name); if (led) led->refcount++; unlock_leds(); return led; } int add_led(const char *name, const char *driverstr, const char * const *options, int lineno, struct absout *eout) { struct led_driver_s *driver; struct led_s *led = NULL; lock_leds(); led = i_find_led(name); if (led) { eout->out(eout, "LED %s already exists on line %d\n", name, lineno); goto out_err; } driver = led_driver_by_name(driverstr); if (!driver) { eout->out(eout, "Unknown LED driver '%s' for LED '%s' on %d", driverstr, name, lineno); goto out_err; } led = calloc(1, sizeof(*led)); if (!led) { eout->out(eout, "Out of memory handling LED '%s' on %d", name, lineno); goto out_err; } led->name = strdup(name); if (!led->name) { eout->out(eout, "Out of memory handling LED '%s' on %d", name, lineno); goto out_err; } led->refcount = 1; led->driver = driver; if (led->driver->init(led, options, lineno, eout) < 0) { /* errors should be reported by driver itself */ goto out_err; } if (led->driver->configure) { if (led->driver->configure(led->drv_data, lineno, eout) < 0) { /* * errors should be reported by driver itself; however, we * cleanup here */ if (led->driver->free) led->driver->free(led); goto out_err; } } led->next = leds; leds = led; unlock_leds(); return 0; out_err: unlock_leds(); if (led) { if (led->name) free(led->name); free(led); } return -1; } static void i_free_led(struct led_s *led) { assert(led->refcount > 0); led->refcount--; if (led->refcount == 0) { /* let driver deconfigure the LED */ if (led->driver->deconfigure) led->driver->deconfigure(led->drv_data); /* let driver free its own data when it registered a cleanup function */ if (led->driver->free) led->driver->free(led); free(led->name); free(led); } } void free_led(struct led_s *led) { lock_leds(); i_free_led(led); unlock_leds(); } void free_leds(void) { lock_leds(); while (leds) { struct led_s *led = leds; leds = leds->next; i_free_led(led); } unlock_leds(); } int led_flash(struct led_s *led) { return led->driver->flash(led->drv_data); } ser2net-4.6.2/led.h000066400000000000000000000055261461221161700140330ustar00rootroot00000000000000/* * ser2net - A program for allowing telnet connection to serial ports * Copyright (C) 2016-2020 Corey Minyard * Copyright (C) 2016 Michael Heimpold * * SPDX-License-Identifier: GPL-2.0-only * * In addition, as a special exception, the copyright holders of * ser2net give you permission to combine ser2net with free software * programs or libraries that are released under the GNU LGPL and * with code included in the standard release of OpenSSL under the * OpenSSL license (or modified versions of such code, with unchanged * license). You may copy and distribute such a system following the * terms of the GNU GPL for ser2net and the licenses of the other code * concerned, provided that you include the source code of that * other code when and as the GNU GPL requires distribution of source * code. * * Note that people who make modified versions of ser2net are not * obligated to grant this special exception for their modified * versions; it is their choice whether to do so. The GNU General * Public License gives permission to release a modified version * without this exception; this exception also makes it possible to * release a modified version which carries forward this exception. */ #ifndef LED_H #define LED_H #include "absout.h" struct led_driver_s; struct led_s { struct led_s *next; char *name; unsigned int refcount; struct led_driver_s *driver; void *drv_data; }; struct led_driver_s { struct led_driver_s *next; const char *name; /* required: parse the parameters from config file */ int (*init)(struct led_s *led, const char * const *options, int lineno, struct absout *eout); /* optional, but required when drv_data is malloced in init */ int (*free)(struct led_s *led); /* optional: called once during initialization, prepares the LED */ int (*configure)(void *drv_data, int lineno, struct absout *eout); /* required: called when data transfer should be signaled */ int (*flash)(void *drv_data); /* optional: called during deinitialization, could switch the LED off */ int (*deconfigure)(void *drv_data); }; /* Initializes and registers all LED drivers */ int led_driver_init(void); /* Callback to register a given LED driver in the system */ int led_driver_register(struct led_driver_s *led_driver); /* Handle an LED config line */ int add_led(const char *name, const char *driverstr, const char * const *options, int lineno, struct absout *eout); /* Search for a LED by name. This will increment the refcount. */ struct led_s *find_led(const char *name); /* Decrement the LED's refcount and free if it reaches 0. */ void free_led(struct led_s *led); /* Free all registered LEDs in the system */ void free_leds(void); /* Flash the given LED */ int led_flash(struct led_s *led); #endif /* LED_H */ ser2net-4.6.2/led_sysfs.c000066400000000000000000000223321461221161700152470ustar00rootroot00000000000000/* * ser2net - A program for allowing telnet connection to serial ports * Copyright (C) 2015-2020 Corey Minyard * Copyright (C) 2015 I2SE GmbH * Copyright (C) 2016 Michael Heimpold * * SPDX-License-Identifier: GPL-2.0-only * * In addition, as a special exception, the copyright holders of * ser2net give you permission to combine ser2net with free software * programs or libraries that are released under the GNU LGPL and * with code included in the standard release of OpenSSL under the * OpenSSL license (or modified versions of such code, with unchanged * license). You may copy and distribute such a system following the * terms of the GNU GPL for ser2net and the licenses of the other code * concerned, provided that you include the source code of that * other code when and as the GNU GPL requires distribution of source * code. * * Note that people who make modified versions of ser2net are not * obligated to grant this special exception for their modified * versions; it is their choice whether to do so. The GNU General * Public License gives permission to release a modified version * without this exception; this exception also makes it possible to * release a modified version which carries forward this exception. */ /* This file contains a LED driver for Linux's sysfs based LEDs. */ #ifdef USE_SYSFS_LED_FEATURE #include #include #include #include #include #include #include #include #include #include "ser2net.h" #include "led.h" #include "led_sysfs.h" #define SYSFS_LED_BASE "/sys/class/leds" #define BUFSIZE 4096 struct led_sysfs_s { char *device; int state; int duration; int max_brightness; }; static int led_is_trigger_missing(const char *led, struct absout *eout) { char path[PATH_MAX]; char buffer[BUFSIZE + 1], *trigger; int fd, c; snprintf(path, sizeof(path), "%s/%s/trigger", SYSFS_LED_BASE, led); if ((fd = open(path, O_RDONLY)) == -1) { eout->out(eout, "led: Unable to open %s", buffer); return -1; } if ((c = read(fd, buffer, BUFSIZE)) <= 0) { eout->out(eout, "led: Unable to read from %s", path); close(fd); return -1; } close(fd); buffer[c] = '\0'; trigger = strstr(buffer, "transient"); if (!trigger) eout->out(eout, "led: missing transient trigger in %s," " maybe you need to 'modprobe ledtrig-transient'", path); return trigger == NULL; } static int led_write(const char *led, const char *property, const char *buf, int lineno, struct absout *eout) { char filename[255]; int fd; char linestr[100] = ""; snprintf(filename, sizeof(filename), "%s/%s/%s", SYSFS_LED_BASE, led, property); if ((fd = open(filename, O_WRONLY | O_TRUNC)) == -1) { if (lineno) snprintf(linestr, sizeof(linestr), "on line %d ", lineno); eout->out(eout, "Unable to open LED %s%s: %s", linestr, led, strerror(errno)); return -1; } if (write(fd, buf, strlen(buf)) != strlen(buf)) { if (lineno) snprintf(linestr, sizeof(linestr), "on line %d ", lineno); eout->out(eout, "Unable to write to LED %s%s: %s", linestr, led, strerror(errno)); close(fd); return -1; } close(fd); return 0; } static int led_read(const char *led, const char *property, char *buf, unsigned int buflen, int *len, int lineno, struct absout *eout) { char filename[255]; int fd; char linestr[100] = ""; ssize_t rlen; snprintf(filename, sizeof(filename), "%s/%s/%s", SYSFS_LED_BASE, led, property); if ((fd = open(filename, O_RDONLY)) == -1) { if (lineno) snprintf(linestr, sizeof(linestr), "on line %d ", lineno); eout->out(eout, "Unable to open LED for read %s%s: %s", linestr, led, strerror(errno)); return -1; } rlen = read(fd, buf, buflen); if (rlen < 0) { if (lineno) snprintf(linestr, sizeof(linestr), "on line %d ", lineno); eout->out(eout, "Unable to read from LED %s%s: %s", linestr, led, strerror(errno)); close(fd); return -1; } *len = rlen; close(fd); return 0; } static int led_read_num(const char *led, const char *property, int *val, int lineno, struct absout *eout) { char buf[100], *end; char linestr[100] = ""; int len; int rv, rval; rv = led_read(led, property, buf, sizeof(buf) - 1, &len, lineno, eout); if (rv) return rv; /* Kill trailing spaces and terminate. */ while(len >= 0 && isspace(buf[--len])) ; buf[++len] = '\0'; rval = strtol(buf, &end, 0); if (!buf[0] || *end != '\0') { if (lineno) snprintf(linestr, sizeof(linestr), "on line %d ", lineno); eout->out(eout, "Invalid number from LED value %s%s: %s", linestr, led, buf); return 1; } *val = rval; return 0; } static int led_sysfs_init(struct led_s *led, const char * const *options, int lineno, struct absout *eout) { struct led_sysfs_s *drv_data = NULL; const char *key, *value; unsigned int i, len; drv_data = calloc(1, sizeof(*drv_data)); if (!drv_data) { eout->out(eout, "Out of memory handling LED %s on line %d.", led->name, lineno); return -1; } /* preset to detect default and/or wrong user input */ drv_data->state = -1; for (i = 0; options[i]; i++) { value = strchr(options[i], '='); if (!value) { eout->out(eout, "Missing '=' in option %s on line %d\n", options[i], lineno); goto out_err; } if (value == options[i]) { eout->out(eout, "Missing key in option '%s' on line %d\n", options[i], lineno); goto out_err; } len = value - options[i]; value++; key = options[i]; if (strncasecmp(key, "device", len) == 0) { /* if 'device' is given more than once, last wins */ if (drv_data->device) free(drv_data->device); drv_data->device = strdup(value); if (!drv_data->device) { eout->out(eout, "Out of memory handling LED '%s' on line %d.", led->name, lineno); goto out_err; } } if (strncasecmp(key, "duration", len) == 0) drv_data->duration = atoi(value); if (strncasecmp(key, "state", len) == 0) drv_data->state = atoi(value); } if (!drv_data->device) { eout->out(eout, "LED '%s': parameter 'device' required, but missing on line %d.", led->name, lineno); if (drv_data->device) free(drv_data->device); goto out_err; } if (drv_data->duration < 0) { eout->out(eout, "LED '%s': invalid duration, using default on line %d.", led->name, lineno); drv_data->duration = 10; } if (drv_data->duration == 0) drv_data->duration = 10; if (led_read_num(drv_data->device, "max_brightness", &drv_data->max_brightness, lineno, eout)) { eout->out(eout, "LED '%s': Unable to read max_brightness using 1 on line %d.", led->name, lineno); drv_data->max_brightness = 1; } if (drv_data->state == -1) drv_data->state = 1; if (drv_data->state < 0 || drv_data->state > drv_data->max_brightness) { eout->out(eout, "LED '%s': invalid state on line %d: %d." " min is %d, max is %d." " Defaulting to 1.", led->name, lineno, drv_data->state, 0, drv_data->max_brightness); drv_data->state = 1; } led->drv_data = (void *)drv_data; return 0; out_err: free(drv_data); return -1; } static int led_sysfs_free(struct led_s *led) { struct led_sysfs_s *ctx = (struct led_sysfs_s *)led->drv_data; free(ctx->device); free(ctx); led->drv_data = NULL; return 0; } static int led_sysfs_configure(void *led_driver_data, int lineno, struct absout *eout) { struct led_sysfs_s *ctx = (struct led_sysfs_s *)led_driver_data; char buffer[255]; int rv = 0; /* check whether we can enable the transient trigger for this led */ rv = led_is_trigger_missing(ctx->device, eout); if (rv != 0) return rv; /* * switch to transient trigger, this will kick creation of additional * property file in sysfs */ rv = led_write(ctx->device, "trigger", "transient", lineno, eout); if (rv) return rv; /* pre-configure the trigger for our needs */ snprintf(buffer, sizeof(buffer), "%d", ctx->duration); rv |= led_write(ctx->device, "duration", buffer, lineno, eout); snprintf(buffer, sizeof(buffer), "%d", ctx->state); rv |= led_write(ctx->device, "state", buffer, lineno, eout); return rv; } static int led_sysfs_flash(void *led_driver_data) { struct led_sysfs_s *ctx = (struct led_sysfs_s *)led_driver_data; return led_write(ctx->device, "activate", "1", 0, &seout); } static int led_sysfs_deconfigure(void *led_driver_data) { struct led_sysfs_s *ctx = (struct led_sysfs_s *)led_driver_data; int rv = 0; rv |= led_write(ctx->device, "trigger", "none", 0, &seout); rv |= led_write(ctx->device, "brightness", "0", 0, &seout); return rv; } static struct led_driver_s led_sysfs_driver = { .name = "sysfs", .init = led_sysfs_init, .free = led_sysfs_free, .configure = led_sysfs_configure, .flash = led_sysfs_flash, .deconfigure = led_sysfs_deconfigure, }; int led_sysfs_register(void) { return led_driver_register(&led_sysfs_driver); } #else int led_sysfs_register(void) { return 0; } #endif ser2net-4.6.2/led_sysfs.h000066400000000000000000000026251461221161700152570ustar00rootroot00000000000000/* * ser2net - A program for allowing telnet connection to serial ports * Copyright (C) 2015-2020 Corey Minyard * Copyright (C) 2015 I2SE GmbH * Copyright (C) 2016 Michael Heimpold * * SPDX-License-Identifier: GPL-2.0-only * * In addition, as a special exception, the copyright holders of * ser2net give you permission to combine ser2net with free software * programs or libraries that are released under the GNU LGPL and * with code included in the standard release of OpenSSL under the * OpenSSL license (or modified versions of such code, with unchanged * license). You may copy and distribute such a system following the * terms of the GNU GPL for ser2net and the licenses of the other code * concerned, provided that you include the source code of that * other code when and as the GNU GPL requires distribution of source * code. * * Note that people who make modified versions of ser2net are not * obligated to grant this special exception for their modified * versions; it is their choice whether to do so. The GNU General * Public License gives permission to release a modified version * without this exception; this exception also makes it possible to * release a modified version which carries forward this exception. */ #ifndef LED_SYSFS_H #define LED_SYSFS_H int led_sysfs_register(void); #endif /* LED_SYSFS_H */ ser2net-4.6.2/m4/000077500000000000000000000000001461221161700134265ustar00rootroot00000000000000ser2net-4.6.2/m4/.gitignore000066400000000000000000000000771461221161700154220ustar00rootroot00000000000000libtool.m4 ltoptions.m4 ltsugar.m4 ltversion.m4 lt~obsolete.m4 ser2net-4.6.2/m4/ax_check_openssl.m4000066400000000000000000000101321461221161700171750ustar00rootroot00000000000000# =========================================================================== # http://www.gnu.org/software/autoconf-archive/ax_check_openssl.html # =========================================================================== # # SYNOPSIS # # AX_CHECK_OPENSSL([action-if-found[, action-if-not-found]]) # # DESCRIPTION # # Look for OpenSSL in a number of default spots, or in a user-selected # spot (via --with-openssl). Sets # # OPENSSL_INCLUDES to the include directives required # OPENSSL_LIBS to the -l directives required # OPENSSL_LDFLAGS to the -L or -R flags required # # and calls ACTION-IF-FOUND or ACTION-IF-NOT-FOUND appropriately # # This macro sets OPENSSL_INCLUDES such that source files should use the # openssl/ directory in include directives: # # #include # # LICENSE # # Copyright (c) 2009,2010 Zmanda Inc. # Copyright (c) 2009,2010 Dustin J. Mitchell # # Copying and distribution of this file, with or without modification, are # permitted in any medium without royalty provided the copyright notice # and this notice are preserved. This file is offered as-is, without any # warranty. #serial 8 AU_ALIAS([CHECK_SSL], [AX_CHECK_OPENSSL]) AC_DEFUN([AX_CHECK_OPENSSL], [ found=false AC_ARG_WITH([openssl], [AS_HELP_STRING([--with-openssl=DIR], [root of the OpenSSL directory])], [ case "$withval" in "" | y | ye | yes | n | no) AC_MSG_ERROR([Invalid --with-openssl value]) ;; *) ssldirs="$withval" ;; esac ], [ # if pkg-config is installed and openssl has installed a .pc file, # then use that information and don't search ssldirs AC_PATH_PROG([PKG_CONFIG], [pkg-config]) if test x"$PKG_CONFIG" != x""; then OPENSSL_LDFLAGS=`$PKG_CONFIG openssl --libs-only-L 2>/dev/null` if test $? = 0; then OPENSSL_LIBS=`$PKG_CONFIG openssl --libs-only-l 2>/dev/null` OPENSSL_INCLUDES=`$PKG_CONFIG openssl --cflags-only-I 2>/dev/null` found=true fi fi # no such luck; use some default ssldirs if ! $found; then ssldirs="/usr/local/ssl /usr/lib/ssl /usr/ssl /usr/pkg /usr/local /usr" fi ] ) # note that we #include , so the OpenSSL headers have to be in # an 'openssl' subdirectory if ! $found; then OPENSSL_INCLUDES= for ssldir in $ssldirs; do AC_MSG_CHECKING([for openssl/ssl.h in $ssldir]) if test -f "$ssldir/include/openssl/ssl.h"; then OPENSSL_INCLUDES="-I$ssldir/include" OPENSSL_LDFLAGS="-L$ssldir/lib" OPENSSL_LIBS="-lssl -lcrypto" found=true AC_MSG_RESULT([yes]) break else AC_MSG_RESULT([no]) fi done # if the file wasn't found, well, go ahead and try the link anyway -- maybe # it will just work! fi # try the preprocessor and linker with our new flags, # being careful not to pollute the global LIBS, LDFLAGS, and CPPFLAGS AC_MSG_CHECKING([whether compiling and linking against OpenSSL works]) echo "Trying link with OPENSSL_LDFLAGS=$OPENSSL_LDFLAGS;" \ "OPENSSL_LIBS=$OPENSSL_LIBS; OPENSSL_INCLUDES=$OPENSSL_INCLUDES" >&AS_MESSAGE_LOG_FD save_LIBS="$LIBS" save_LDFLAGS="$LDFLAGS" save_CPPFLAGS="$CPPFLAGS" LDFLAGS="$LDFLAGS $OPENSSL_LDFLAGS" LIBS="$OPENSSL_LIBS $LIBS" CPPFLAGS="$OPENSSL_INCLUDES $CPPFLAGS" AC_LINK_IFELSE( [AC_LANG_PROGRAM([#include ], [SSL_new(NULL)])], [ AC_MSG_RESULT([yes]) $1 ], [ AC_MSG_RESULT([no]) $2 ]) CPPFLAGS="$save_CPPFLAGS" LDFLAGS="$save_LDFLAGS" LIBS="$save_LIBS" AC_SUBST([OPENSSL_INCLUDES]) AC_SUBST([OPENSSL_LIBS]) AC_SUBST([OPENSSL_LDFLAGS]) ]) ser2net-4.6.2/m4/ax_compare_version.m4000066400000000000000000000146531461221161700175640ustar00rootroot00000000000000# =========================================================================== # https://www.gnu.org/software/autoconf-archive/ax_compare_version.html # =========================================================================== # # SYNOPSIS # # AX_COMPARE_VERSION(VERSION_A, OP, VERSION_B, [ACTION-IF-TRUE], [ACTION-IF-FALSE]) # # DESCRIPTION # # This macro compares two version strings. Due to the various number of # minor-version numbers that can exist, and the fact that string # comparisons are not compatible with numeric comparisons, this is not # necessarily trivial to do in a autoconf script. This macro makes doing # these comparisons easy. # # The six basic comparisons are available, as well as checking equality # limited to a certain number of minor-version levels. # # The operator OP determines what type of comparison to do, and can be one # of: # # eq - equal (test A == B) # ne - not equal (test A != B) # le - less than or equal (test A <= B) # ge - greater than or equal (test A >= B) # lt - less than (test A < B) # gt - greater than (test A > B) # # Additionally, the eq and ne operator can have a number after it to limit # the test to that number of minor versions. # # eq0 - equal up to the length of the shorter version # ne0 - not equal up to the length of the shorter version # eqN - equal up to N sub-version levels # neN - not equal up to N sub-version levels # # When the condition is true, shell commands ACTION-IF-TRUE are run, # otherwise shell commands ACTION-IF-FALSE are run. The environment # variable 'ax_compare_version' is always set to either 'true' or 'false' # as well. # # Examples: # # AX_COMPARE_VERSION([3.15.7],[lt],[3.15.8]) # AX_COMPARE_VERSION([3.15],[lt],[3.15.8]) # # would both be true. # # AX_COMPARE_VERSION([3.15.7],[eq],[3.15.8]) # AX_COMPARE_VERSION([3.15],[gt],[3.15.8]) # # would both be false. # # AX_COMPARE_VERSION([3.15.7],[eq2],[3.15.8]) # # would be true because it is only comparing two minor versions. # # AX_COMPARE_VERSION([3.15.7],[eq0],[3.15]) # # would be true because it is only comparing the lesser number of minor # versions of the two values. # # Note: The characters that separate the version numbers do not matter. An # empty string is the same as version 0. OP is evaluated by autoconf, not # configure, so must be a string, not a variable. # # The author would like to acknowledge Guido Draheim whose advice about # the m4_case and m4_ifvaln functions make this macro only include the # portions necessary to perform the specific comparison specified by the # OP argument in the final configure script. # # LICENSE # # Copyright (c) 2008 Tim Toolan # # Copying and distribution of this file, with or without modification, are # permitted in any medium without royalty provided the copyright notice # and this notice are preserved. This file is offered as-is, without any # warranty. #serial 13 dnl ######################################################################### AC_DEFUN([AX_COMPARE_VERSION], [ AC_REQUIRE([AC_PROG_AWK]) # Used to indicate true or false condition ax_compare_version=false # Convert the two version strings to be compared into a format that # allows a simple string comparison. The end result is that a version # string of the form 1.12.5-r617 will be converted to the form # 0001001200050617. In other words, each number is zero padded to four # digits, and non digits are removed. AS_VAR_PUSHDEF([A],[ax_compare_version_A]) A=`echo "$1" | sed -e 's/\([[0-9]]*\)/Z\1Z/g' \ -e 's/Z\([[0-9]]\)Z/Z0\1Z/g' \ -e 's/Z\([[0-9]][[0-9]]\)Z/Z0\1Z/g' \ -e 's/Z\([[0-9]][[0-9]][[0-9]]\)Z/Z0\1Z/g' \ -e 's/[[^0-9]]//g'` AS_VAR_PUSHDEF([B],[ax_compare_version_B]) B=`echo "$3" | sed -e 's/\([[0-9]]*\)/Z\1Z/g' \ -e 's/Z\([[0-9]]\)Z/Z0\1Z/g' \ -e 's/Z\([[0-9]][[0-9]]\)Z/Z0\1Z/g' \ -e 's/Z\([[0-9]][[0-9]][[0-9]]\)Z/Z0\1Z/g' \ -e 's/[[^0-9]]//g'` dnl # In the case of le, ge, lt, and gt, the strings are sorted as necessary dnl # then the first line is used to determine if the condition is true. dnl # The sed right after the echo is to remove any indented white space. m4_case(m4_tolower($2), [lt],[ ax_compare_version=`echo "x$A x$B" | sed 's/^ *//' | sort -r | sed "s/x${A}/false/;s/x${B}/true/;1q"` ], [gt],[ ax_compare_version=`echo "x$A x$B" | sed 's/^ *//' | sort | sed "s/x${A}/false/;s/x${B}/true/;1q"` ], [le],[ ax_compare_version=`echo "x$A x$B" | sed 's/^ *//' | sort | sed "s/x${A}/true/;s/x${B}/false/;1q"` ], [ge],[ ax_compare_version=`echo "x$A x$B" | sed 's/^ *//' | sort -r | sed "s/x${A}/true/;s/x${B}/false/;1q"` ],[ dnl Split the operator from the subversion count if present. m4_bmatch(m4_substr($2,2), [0],[ # A count of zero means use the length of the shorter version. # Determine the number of characters in A and B. ax_compare_version_len_A=`echo "$A" | $AWK '{print(length)}'` ax_compare_version_len_B=`echo "$B" | $AWK '{print(length)}'` # Set A to no more than B's length and B to no more than A's length. A=`echo "$A" | sed "s/\(.\{$ax_compare_version_len_B\}\).*/\1/"` B=`echo "$B" | sed "s/\(.\{$ax_compare_version_len_A\}\).*/\1/"` ], [[0-9]+],[ # A count greater than zero means use only that many subversions A=`echo "$A" | sed "s/\(\([[0-9]]\{4\}\)\{m4_substr($2,2)\}\).*/\1/"` B=`echo "$B" | sed "s/\(\([[0-9]]\{4\}\)\{m4_substr($2,2)\}\).*/\1/"` ], [.+],[ AC_WARNING( [invalid OP numeric parameter: $2]) ],[]) # Pad zeros at end of numbers to make same length. ax_compare_version_tmp_A="$A`echo $B | sed 's/./0/g'`" B="$B`echo $A | sed 's/./0/g'`" A="$ax_compare_version_tmp_A" # Check for equality or inequality as necessary. m4_case(m4_tolower(m4_substr($2,0,2)), [eq],[ test "x$A" = "x$B" && ax_compare_version=true ], [ne],[ test "x$A" != "x$B" && ax_compare_version=true ],[ AC_WARNING([invalid OP parameter: $2]) ]) ]) AS_VAR_POPDEF([A])dnl AS_VAR_POPDEF([B])dnl dnl # Execute ACTION-IF-TRUE / ACTION-IF-FALSE. if test "$ax_compare_version" = "true" ; then m4_ifvaln([$4],[$4],[:])dnl m4_ifvaln([$5],[else $5])dnl fi ]) dnl AX_COMPARE_VERSION ser2net-4.6.2/m4/ax_config_feature.m4000066400000000000000000000123721461221161700173450ustar00rootroot00000000000000# =========================================================================== # http://www.gnu.org/software/autoconf-archive/ax_config_feature.html # =========================================================================== # # SYNOPSIS # # AX_CONFIG_FEATURE(FEATURE-NAME, FEATURE-DESCRIPTION, DEFINE, DEFINE-DESCRIPTION, [ACTION-IF-ENABLED [, ACTION-IF-NOT-ENABLED]]) # # DESCRIPTION # # AX_CONFIG_FEATURE is a simple wrapper for AC_ARG_ENABLE, it enables the # feature FEATURE-NAME and AC_DEFINEs the passed DEFINE, depending on the # user choice. DESCRIPTION will be used for AC_DEFINEs. ACTION-IF-ENABLED # and ACTION-IF-NOT-ENABLED are the actions that will be run. A feature is # enabled by default, in order to change this behaviour use the # AX_CONFIG_FEATURE_DEFAULT_ENABLED and AX_CONFIG_FEATURE_DEFAULT_DISABLED # macros. # # A simple example: # # AX_CONFIG_FEATURE_DEFAULT_ENABLED # AX_CONFIG_FEATURE(feature_xxxxx, [turns on/off XXXXX support], # HAVE_XXXXX, [Define if you want XXXXX support]) # # ... # # AX_CONFIG_FEATURE_DEFAULT_DISABLED # AX_CONFIG_FEATURE(feature_yyyyy, [turns on/off YYYYY support], # HAVE_YYYYY, [Define if you want YYYYY support], # [enable_yyyyy="yes"], [enable_yyyyy="no"]) # AM_CONDITIONAL(YYYYY, [test "$enable_yyyyy" = "yes"]) # # AX_CONFIG_FEATURE_DEFAULT_ENABLED # AX_CONFIG_FEATURE(...) # # ... # # If you have lot of features and you want a verbose dumping of each user # selection use AX_CONFIG_FEATURE_VERBOSE. Use AX_CONFIG_FEATURE_SILENT in # order to remove a previously AX_CONFIG_FEATURE_VERBOSE. By default # features are silent. # # Use AX_CONFIG_FEATURE_ENABLE or AX_CONFIG_FEATURE_DISABLE in order to # enable or disable a specific feature. # # Another simple example: # # AS_IF([some_test_here],[AX_CONFIG_FEATURE_ENABLE(feature_xxxxx)],[]) # # AX_CONFIG_FEATURE(feature_xxxxx, [turns on/off XXXXX support], # HAVE_XXXXX, [Define if you want XXXXX support]) # AX_CONFIG_FEATURE(feature_yyyyy, [turns on/off YYYYY support], # HAVE_YYYYY, [Define if you want YYYYY support], # [enable_yyyyy="yes"], [enable_yyyyy="no"]) # # ... # # NOTE: AX_CONFIG_FEATURE_ENABLE() must be placed first of the relative # AX_CONFIG_FEATURE() macro ... # # LICENSE # # Copyright (c) 2008 Francesco Salvestrini # # 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, see . # # As a special exception, the respective Autoconf Macro's copyright owner # gives unlimited permission to copy, distribute and modify the configure # scripts that are the output of Autoconf when processing the Macro. You # need not follow the terms of the GNU General Public License when using # or distributing such scripts, even though portions of the text of the # Macro appear in them. The GNU General Public License (GPL) does govern # all other use of the material that constitutes the Autoconf Macro. # # This special exception to the GPL applies to versions of the Autoconf # Macro released by the Autoconf Archive. When you make and distribute a # modified version of the Autoconf Macro, you may extend this special # exception to the GPL to apply to your modified version as well. #serial 10 AC_DEFUN([AX_CONFIG_FEATURE],[ dnl m4_pushdef([FEATURE], patsubst([$1], -, _))dnl AC_ARG_ENABLE([$1],AS_HELP_STRING([--enable-$1],[$2]),[ case "${enableval}" in yes) ax_config_feature_[]FEATURE[]="yes" ;; no) ax_config_feature_[]FEATURE[]="no" ;; *) AC_MSG_ERROR([bad value ${enableval} for feature --$1]) ;; esac ]) AS_IF([test "$ax_config_feature_[]FEATURE[]" = yes],[ dnl AC_DEFINE([$3]) $5 AS_IF([test "$ax_config_feature_verbose" = yes],[ dnl AC_MSG_NOTICE([Feature $1 is enabled]) ]) ],[ dnl $6 AS_IF([test "$ax_config_feature_verbose" = yes],[ dnl AC_MSG_NOTICE([Feature $1 is disabled]) ]) ]) AH_TEMPLATE([$3],[$4]) m4_popdef([FEATURE])dnl ]) dnl Feature global AC_DEFUN([AX_CONFIG_FEATURE_VERBOSE],[ dnl ax_config_feature_verbose=yes ]) dnl Feature global AC_DEFUN([AX_CONFIG_FEATURE_SILENT],[ dnl ax_config_feature_verbose=no ]) dnl Feature specific AC_DEFUN([AX_CONFIG_FEATURE_DEFAULT_ENABLED], [ ax_config_feature_[]FEATURE[]_default=yes ]) dnl Feature specific AC_DEFUN([AX_CONFIG_FEATURE_DEFAULT_DISABLED], [ ax_config_feature_[]FEATURE[]_default=no ]) dnl Feature specific AC_DEFUN([AX_CONFIG_FEATURE_ENABLE],[ dnl ax_config_feature_[]patsubst([$1], -, _)[]=yes ]) dnl Feature specific AC_DEFUN([AX_CONFIG_FEATURE_DISABLE],[ dnl ax_config_feature_[]patsubst([$1], -, _)[]=no ]) ser2net-4.6.2/m4/ax_have_epoll.m4000066400000000000000000000070551461221161700165050ustar00rootroot00000000000000# =========================================================================== # http://www.gnu.org/software/autoconf-archive/ax_have_epoll.html # =========================================================================== # # SYNOPSIS # # AX_HAVE_EPOLL([ACTION-IF-FOUND], [ACTION-IF-NOT-FOUND]) # AX_HAVE_EPOLL_PWAIT([ACTION-IF-FOUND], [ACTION-IF-NOT-FOUND]) # # DESCRIPTION # # This macro determines whether the system supports the epoll I/O event # interface. A neat usage example would be: # # AX_HAVE_EPOLL( # [AX_CONFIG_FEATURE_ENABLE(epoll)], # [AX_CONFIG_FEATURE_DISABLE(epoll)]) # AX_CONFIG_FEATURE( # [epoll], [This platform supports epoll(7)], # [HAVE_EPOLL], [This platform supports epoll(7).]) # # The epoll interface was added to the Linux kernel in version 2.5.45, and # the macro verifies that a kernel newer than this is installed. This # check is somewhat unreliable if doesn't match the # running kernel, but it is necessary regardless, because glibc comes with # stubs for the epoll_create(), epoll_wait(), etc. that allow programs to # compile and link even if the kernel is too old; the problem would then # be detected only at runtime. # # Linux kernel version 2.6.19 adds the epoll_pwait() call in addition to # epoll_wait(). The availability of that function can be tested with the # second macro. Generally speaking, it is safe to assume that # AX_HAVE_EPOLL would succeed if AX_HAVE_EPOLL_PWAIT has, but not the # other way round. # # LICENSE # # Copyright (c) 2008 Peter Simons # # Copying and distribution of this file, with or without modification, are # permitted in any medium without royalty provided the copyright notice # and this notice are preserved. This file is offered as-is, without any # warranty. #serial 10 AC_DEFUN([AX_HAVE_EPOLL], [dnl ax_have_epoll_cppflags="${CPPFLAGS}" AC_CHECK_HEADER([linux/version.h], [CPPFLAGS="${CPPFLAGS} -DHAVE_LINUX_VERSION_H"]) AC_MSG_CHECKING([for Linux epoll(7) interface]) AC_CACHE_VAL([ax_cv_have_epoll], [dnl AC_LINK_IFELSE([dnl AC_LANG_PROGRAM([dnl #include #ifdef HAVE_LINUX_VERSION_H # include # if LINUX_VERSION_CODE < KERNEL_VERSION(2,5,45) # error linux kernel version is too old to have epoll # endif #endif ], [dnl int fd, rc; struct epoll_event ev; fd = epoll_create(128); rc = epoll_wait(fd, &ev, 1, 0);])], [ax_cv_have_epoll=yes], [ax_cv_have_epoll=no])]) CPPFLAGS="${ax_have_epoll_cppflags}" AS_IF([test "${ax_cv_have_epoll}" = "yes"], [AC_MSG_RESULT([yes]) $1],[AC_MSG_RESULT([no]) $2]) ])dnl AC_DEFUN([AX_HAVE_EPOLL_PWAIT], [dnl ax_have_epoll_cppflags="${CPPFLAGS}" AC_CHECK_HEADER([linux/version.h], [CPPFLAGS="${CPPFLAGS} -DHAVE_LINUX_VERSION_H"]) AC_MSG_CHECKING([for Linux epoll(7) interface with signals extension]) AC_CACHE_VAL([ax_cv_have_epoll_pwait], [dnl AC_LINK_IFELSE([dnl AC_LANG_PROGRAM([dnl #ifdef HAVE_LINUX_VERSION_H # include # if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,19) # error linux kernel version is too old to have epoll_pwait # endif #endif #include #include ], [dnl int fd, rc; struct epoll_event ev; fd = epoll_create(128); rc = epoll_wait(fd, &ev, 1, 0); rc = epoll_pwait(fd, &ev, 1, 0, (sigset_t const *)(0));])], [ax_cv_have_epoll_pwait=yes], [ax_cv_have_epoll_pwait=no])]) CPPFLAGS="${ax_have_epoll_cppflags}" AS_IF([test "${ax_cv_have_epoll_pwait}" = "yes"], [AC_MSG_RESULT([yes]) $1],[AC_MSG_RESULT([no]) $2]) ])dnl ser2net-4.6.2/m4/ax_pkg_swig.m4000066400000000000000000000151601461221161700161750ustar00rootroot00000000000000# =========================================================================== # https://www.gnu.org/software/autoconf-archive/ax_pkg_swig.html # =========================================================================== # # SYNOPSIS # # AX_PKG_SWIG([major.minor.micro], [action-if-found], [action-if-not-found]) # # DESCRIPTION # # This macro searches for a SWIG installation on your system. If found, # then SWIG is AC_SUBST'd; if not found, then $SWIG is empty. If SWIG is # found, then SWIG_LIB is set to the SWIG library path, and AC_SUBST'd. # # You can use the optional first argument to check if the version of the # available SWIG is greater than or equal to the value of the argument. It # should have the format: N[.N[.N]] (N is a number between 0 and 999. Only # the first N is mandatory.) If the version argument is given (e.g. # 1.3.17), AX_PKG_SWIG checks that the swig package is this version number # or higher. # # As usual, action-if-found is executed if SWIG is found, otherwise # action-if-not-found is executed. # # In configure.in, use as: # # AX_PKG_SWIG(1.3.17, [], [ AC_MSG_ERROR([SWIG is required to build..]) ]) # AX_SWIG_ENABLE_CXX # AX_SWIG_MULTI_MODULE_SUPPORT # AX_SWIG_PYTHON # # LICENSE # # Copyright (c) 2008 Sebastian Huber # Copyright (c) 2008 Alan W. Irwin # Copyright (c) 2008 Rafael Laboissiere # Copyright (c) 2008 Andrew Collier # Copyright (c) 2011 Murray Cumming # # 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, see . # # As a special exception, the respective Autoconf Macro's copyright owner # gives unlimited permission to copy, distribute and modify the configure # scripts that are the output of Autoconf when processing the Macro. You # need not follow the terms of the GNU General Public License when using # or distributing such scripts, even though portions of the text of the # Macro appear in them. The GNU General Public License (GPL) does govern # all other use of the material that constitutes the Autoconf Macro. # # This special exception to the GPL applies to versions of the Autoconf # Macro released by the Autoconf Archive. When you make and distribute a # modified version of the Autoconf Macro, you may extend this special # exception to the GPL to apply to your modified version as well. #serial 13 AC_DEFUN([AX_PKG_SWIG],[ # Find path to the "swig" executable. AC_PATH_PROGS([SWIG],[swig swig3.0 swig2.0]) if test -z "$SWIG" ; then m4_ifval([$3],[$3],[:]) elif test -n "$1" ; then AC_MSG_CHECKING([SWIG version]) [swig_version=`$SWIG -version 2>&1 | grep 'SWIG Version' | sed 's/.*\([0-9][0-9]*\.[0-9][0-9]*\.[0-9][0-9]*\).*/\1/g'`] AC_MSG_RESULT([$swig_version]) if test -n "$swig_version" ; then # Calculate the required version number components [required=$1] [required_major=`echo $required | sed 's/[^0-9].*//'`] if test -z "$required_major" ; then [required_major=0] fi [required=`echo $required | sed 's/[0-9]*[^0-9]//'`] [required_minor=`echo $required | sed 's/[^0-9].*//'`] if test -z "$required_minor" ; then [required_minor=0] fi [required=`echo $required | sed 's/[0-9]*[^0-9]//'`] [required_patch=`echo $required | sed 's/[^0-9].*//'`] if test -z "$required_patch" ; then [required_patch=0] fi # Calculate the available version number components [available=$swig_version] [available_major=`echo $available | sed 's/[^0-9].*//'`] if test -z "$available_major" ; then [available_major=0] fi [available=`echo $available | sed 's/[0-9]*[^0-9]//'`] [available_minor=`echo $available | sed 's/[^0-9].*//'`] if test -z "$available_minor" ; then [available_minor=0] fi [available=`echo $available | sed 's/[0-9]*[^0-9]//'`] [available_patch=`echo $available | sed 's/[^0-9].*//'`] if test -z "$available_patch" ; then [available_patch=0] fi # Convert the version tuple into a single number for easier comparison. # Using base 100 should be safe since SWIG internally uses BCD values # to encode its version number. required_swig_vernum=`expr $required_major \* 10000 \ \+ $required_minor \* 100 \+ $required_patch` available_swig_vernum=`expr $available_major \* 10000 \ \+ $available_minor \* 100 \+ $available_patch` if test $available_swig_vernum -lt $required_swig_vernum; then AC_MSG_WARN([SWIG version >= $1 is required. You have $swig_version.]) SWIG='' m4_ifval([$3],[$3],[]) else AC_MSG_CHECKING([for SWIG library]) SWIG_LIB=`$SWIG -swiglib` AC_MSG_RESULT([$SWIG_LIB]) m4_ifval([$2],[$2],[]) fi else AC_MSG_WARN([cannot determine SWIG version]) SWIG='' m4_ifval([$3],[$3],[]) fi fi AC_SUBST([SWIG_LIB]) ]) ser2net-4.6.2/m4/ax_prog_python_version.m4000066400000000000000000000040661461221161700205030ustar00rootroot00000000000000# =========================================================================== # https://www.gnu.org/software/autoconf-archive/ax_prog_python_version.html # =========================================================================== # # SYNOPSIS # # AX_PROG_PYTHON_VERSION([VERSION],[ACTION-IF-TRUE],[ACTION-IF-FALSE]) # # DESCRIPTION # # Makes sure that python supports the version indicated. If true the shell # commands in ACTION-IF-TRUE are executed. If not the shell commands in # ACTION-IF-FALSE are run. Note if $PYTHON is not set (for example by # running AC_CHECK_PROG or AC_PATH_PROG) the macro will fail. # # Example: # # AC_PATH_PROG([PYTHON],[python]) # AX_PROG_PYTHON_VERSION([2.4.4],[ ... ],[ ... ]) # # This will check to make sure that the python you have supports at least # version 2.4.4. # # NOTE: This macro uses the $PYTHON variable to perform the check. # AX_WITH_PYTHON can be used to set that variable prior to running this # macro. The $PYTHON_VERSION variable will be valorized with the detected # version. # # LICENSE # # Copyright (c) 2009 Francesco Salvestrini # # Copying and distribution of this file, with or without modification, are # permitted in any medium without royalty provided the copyright notice # and this notice are preserved. This file is offered as-is, without any # warranty. #serial 12 AC_DEFUN([AX_PROG_PYTHON_VERSION],[ AC_REQUIRE([AC_PROG_SED]) AC_REQUIRE([AC_PROG_GREP]) AS_IF([test -n "$PYTHON"],[ ax_python_version="$1" AC_MSG_CHECKING([for python version]) changequote(<<,>>) python_version=`$PYTHON -V 2>&1 | $GREP "^Python " | $SED -e 's/^.* \([0-9]*\.[0-9]*\.[0-9]*\)/\1/'` changequote([,]) AC_MSG_RESULT($python_version) AC_SUBST([PYTHON_VERSION],[$python_version]) AX_COMPARE_VERSION([$ax_python_version],[le],[$python_version],[ : $2 ],[ : $3 ]) ],[ AC_MSG_WARN([could not find the python interpreter]) $3 ]) ]) ser2net-4.6.2/m4/ax_pthread.m4000066400000000000000000000540341461221161700160150ustar00rootroot00000000000000# =========================================================================== # https://www.gnu.org/software/autoconf-archive/ax_pthread.html # =========================================================================== # # SYNOPSIS # # AX_PTHREAD([ACTION-IF-FOUND[, ACTION-IF-NOT-FOUND]]) # # DESCRIPTION # # This macro figures out how to build C programs using POSIX threads. It # sets the PTHREAD_LIBS output variable to the threads library and linker # flags, and the PTHREAD_CFLAGS output variable to any special C compiler # flags that are needed. (The user can also force certain compiler # flags/libs to be tested by setting these environment variables.) # # Also sets PTHREAD_CC and PTHREAD_CXX to any special C compiler that is # needed for multi-threaded programs (defaults to the value of CC # respectively CXX otherwise). (This is necessary on e.g. AIX to use the # special cc_r/CC_r compiler alias.) # # NOTE: You are assumed to not only compile your program with these flags, # but also to link with them as well. For example, you might link with # $PTHREAD_CC $CFLAGS $PTHREAD_CFLAGS $LDFLAGS ... $PTHREAD_LIBS $LIBS # $PTHREAD_CXX $CXXFLAGS $PTHREAD_CFLAGS $LDFLAGS ... $PTHREAD_LIBS $LIBS # # If you are only building threaded programs, you may wish to use these # variables in your default LIBS, CFLAGS, and CC: # # LIBS="$PTHREAD_LIBS $LIBS" # CFLAGS="$CFLAGS $PTHREAD_CFLAGS" # CXXFLAGS="$CXXFLAGS $PTHREAD_CFLAGS" # CC="$PTHREAD_CC" # CXX="$PTHREAD_CXX" # # In addition, if the PTHREAD_CREATE_JOINABLE thread-attribute constant # has a nonstandard name, this macro defines PTHREAD_CREATE_JOINABLE to # that name (e.g. PTHREAD_CREATE_UNDETACHED on AIX). # # Also HAVE_PTHREAD_PRIO_INHERIT is defined if pthread is found and the # PTHREAD_PRIO_INHERIT symbol is defined when compiling with # PTHREAD_CFLAGS. # # ACTION-IF-FOUND is a list of shell commands to run if a threads library # is found, and ACTION-IF-NOT-FOUND is a list of commands to run it if it # is not found. If ACTION-IF-FOUND is not specified, the default action # will define HAVE_PTHREAD. # # Please let the authors know if this macro fails on any platform, or if # you have any other suggestions or comments. This macro was based on work # by SGJ on autoconf scripts for FFTW (http://www.fftw.org/) (with help # from M. Frigo), as well as ac_pthread and hb_pthread macros posted by # Alejandro Forero Cuervo to the autoconf macro repository. We are also # grateful for the helpful feedback of numerous users. # # Updated for Autoconf 2.68 by Daniel Richard G. # # LICENSE # # Copyright (c) 2008 Steven G. Johnson # Copyright (c) 2011 Daniel Richard G. # Copyright (c) 2019 Marc Stevens # # 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 3 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, see . # # As a special exception, the respective Autoconf Macro's copyright owner # gives unlimited permission to copy, distribute and modify the configure # scripts that are the output of Autoconf when processing the Macro. You # need not follow the terms of the GNU General Public License when using # or distributing such scripts, even though portions of the text of the # Macro appear in them. The GNU General Public License (GPL) does govern # all other use of the material that constitutes the Autoconf Macro. # # This special exception to the GPL applies to versions of the Autoconf # Macro released by the Autoconf Archive. When you make and distribute a # modified version of the Autoconf Macro, you may extend this special # exception to the GPL to apply to your modified version as well. #serial 31 AU_ALIAS([ACX_PTHREAD], [AX_PTHREAD]) AC_DEFUN([AX_PTHREAD], [ AC_REQUIRE([AC_CANONICAL_HOST]) AC_REQUIRE([AC_PROG_CC]) AC_REQUIRE([AC_PROG_SED]) AC_LANG_PUSH([C]) ax_pthread_ok=no # We used to check for pthread.h first, but this fails if pthread.h # requires special compiler flags (e.g. on Tru64 or Sequent). # It gets checked for in the link test anyway. # First of all, check if the user has set any of the PTHREAD_LIBS, # etcetera environment variables, and if threads linking works using # them: if test "x$PTHREAD_CFLAGS$PTHREAD_LIBS" != "x"; then ax_pthread_save_CC="$CC" ax_pthread_save_CFLAGS="$CFLAGS" ax_pthread_save_LIBS="$LIBS" AS_IF([test "x$PTHREAD_CC" != "x"], [CC="$PTHREAD_CC"]) AS_IF([test "x$PTHREAD_CXX" != "x"], [CXX="$PTHREAD_CXX"]) CFLAGS="$CFLAGS $PTHREAD_CFLAGS" LIBS="$PTHREAD_LIBS $LIBS" AC_MSG_CHECKING([for pthread_join using $CC $PTHREAD_CFLAGS $PTHREAD_LIBS]) AC_LINK_IFELSE([AC_LANG_CALL([], [pthread_join])], [ax_pthread_ok=yes]) AC_MSG_RESULT([$ax_pthread_ok]) if test "x$ax_pthread_ok" = "xno"; then PTHREAD_LIBS="" PTHREAD_CFLAGS="" fi CC="$ax_pthread_save_CC" CFLAGS="$ax_pthread_save_CFLAGS" LIBS="$ax_pthread_save_LIBS" fi # We must check for the threads library under a number of different # names; the ordering is very important because some systems # (e.g. DEC) have both -lpthread and -lpthreads, where one of the # libraries is broken (non-POSIX). # Create a list of thread flags to try. Items with a "," contain both # C compiler flags (before ",") and linker flags (after ","). Other items # starting with a "-" are C compiler flags, and remaining items are # library names, except for "none" which indicates that we try without # any flags at all, and "pthread-config" which is a program returning # the flags for the Pth emulation library. ax_pthread_flags="pthreads none -Kthread -pthread -pthreads -mthreads pthread --thread-safe -mt pthread-config" # The ordering *is* (sometimes) important. Some notes on the # individual items follow: # pthreads: AIX (must check this before -lpthread) # none: in case threads are in libc; should be tried before -Kthread and # other compiler flags to prevent continual compiler warnings # -Kthread: Sequent (threads in libc, but -Kthread needed for pthread.h) # -pthread: Linux/gcc (kernel threads), BSD/gcc (userland threads), Tru64 # (Note: HP C rejects this with "bad form for `-t' option") # -pthreads: Solaris/gcc (Note: HP C also rejects) # -mt: Sun Workshop C (may only link SunOS threads [-lthread], but it # doesn't hurt to check since this sometimes defines pthreads and # -D_REENTRANT too), HP C (must be checked before -lpthread, which # is present but should not be used directly; and before -mthreads, # because the compiler interprets this as "-mt" + "-hreads") # -mthreads: Mingw32/gcc, Lynx/gcc # pthread: Linux, etcetera # --thread-safe: KAI C++ # pthread-config: use pthread-config program (for GNU Pth library) case $host_os in freebsd*) # -kthread: FreeBSD kernel threads (preferred to -pthread since SMP-able) # lthread: LinuxThreads port on FreeBSD (also preferred to -pthread) ax_pthread_flags="-kthread lthread $ax_pthread_flags" ;; hpux*) # From the cc(1) man page: "[-mt] Sets various -D flags to enable # multi-threading and also sets -lpthread." ax_pthread_flags="-mt -pthread pthread $ax_pthread_flags" ;; openedition*) # IBM z/OS requires a feature-test macro to be defined in order to # enable POSIX threads at all, so give the user a hint if this is # not set. (We don't define these ourselves, as they can affect # other portions of the system API in unpredictable ways.) AC_EGREP_CPP([AX_PTHREAD_ZOS_MISSING], [ # if !defined(_OPEN_THREADS) && !defined(_UNIX03_THREADS) AX_PTHREAD_ZOS_MISSING # endif ], [AC_MSG_WARN([IBM z/OS requires -D_OPEN_THREADS or -D_UNIX03_THREADS to enable pthreads support.])]) ;; solaris*) # On Solaris (at least, for some versions), libc contains stubbed # (non-functional) versions of the pthreads routines, so link-based # tests will erroneously succeed. (N.B.: The stubs are missing # pthread_cleanup_push, or rather a function called by this macro, # so we could check for that, but who knows whether they'll stub # that too in a future libc.) So we'll check first for the # standard Solaris way of linking pthreads (-mt -lpthread). ax_pthread_flags="-mt,-lpthread pthread $ax_pthread_flags" ;; esac # Are we compiling with Clang? AC_CACHE_CHECK([whether $CC is Clang], [ax_cv_PTHREAD_CLANG], [ax_cv_PTHREAD_CLANG=no # Note that Autoconf sets GCC=yes for Clang as well as GCC if test "x$GCC" = "xyes"; then AC_EGREP_CPP([AX_PTHREAD_CC_IS_CLANG], [/* Note: Clang 2.7 lacks __clang_[a-z]+__ */ # if defined(__clang__) && defined(__llvm__) AX_PTHREAD_CC_IS_CLANG # endif ], [ax_cv_PTHREAD_CLANG=yes]) fi ]) ax_pthread_clang="$ax_cv_PTHREAD_CLANG" # GCC generally uses -pthread, or -pthreads on some platforms (e.g. SPARC) # Note that for GCC and Clang -pthread generally implies -lpthread, # except when -nostdlib is passed. # This is problematic using libtool to build C++ shared libraries with pthread: # [1] https://gcc.gnu.org/bugzilla/show_bug.cgi?id=25460 # [2] https://bugzilla.redhat.com/show_bug.cgi?id=661333 # [3] https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=468555 # To solve this, first try -pthread together with -lpthread for GCC AS_IF([test "x$GCC" = "xyes"], [ax_pthread_flags="-pthread,-lpthread -pthread -pthreads $ax_pthread_flags"]) # Clang takes -pthread (never supported any other flag), but we'll try with -lpthread first AS_IF([test "x$ax_pthread_clang" = "xyes"], [ax_pthread_flags="-pthread,-lpthread -pthread"]) # The presence of a feature test macro requesting re-entrant function # definitions is, on some systems, a strong hint that pthreads support is # correctly enabled case $host_os in darwin* | hpux* | linux* | osf* | solaris*) ax_pthread_check_macro="_REENTRANT" ;; aix*) ax_pthread_check_macro="_THREAD_SAFE" ;; *) ax_pthread_check_macro="--" ;; esac AS_IF([test "x$ax_pthread_check_macro" = "x--"], [ax_pthread_check_cond=0], [ax_pthread_check_cond="!defined($ax_pthread_check_macro)"]) if test "x$ax_pthread_ok" = "xno"; then for ax_pthread_try_flag in $ax_pthread_flags; do case $ax_pthread_try_flag in none) AC_MSG_CHECKING([whether pthreads work without any flags]) ;; *,*) PTHREAD_CFLAGS=`echo $ax_pthread_try_flag | sed "s/^\(.*\),\(.*\)$/\1/"` PTHREAD_LIBS=`echo $ax_pthread_try_flag | sed "s/^\(.*\),\(.*\)$/\2/"` AC_MSG_CHECKING([whether pthreads work with "$PTHREAD_CFLAGS" and "$PTHREAD_LIBS"]) ;; -*) AC_MSG_CHECKING([whether pthreads work with $ax_pthread_try_flag]) PTHREAD_CFLAGS="$ax_pthread_try_flag" ;; pthread-config) AC_CHECK_PROG([ax_pthread_config], [pthread-config], [yes], [no]) AS_IF([test "x$ax_pthread_config" = "xno"], [continue]) PTHREAD_CFLAGS="`pthread-config --cflags`" PTHREAD_LIBS="`pthread-config --ldflags` `pthread-config --libs`" ;; *) AC_MSG_CHECKING([for the pthreads library -l$ax_pthread_try_flag]) PTHREAD_LIBS="-l$ax_pthread_try_flag" ;; esac ax_pthread_save_CFLAGS="$CFLAGS" ax_pthread_save_LIBS="$LIBS" CFLAGS="$CFLAGS $PTHREAD_CFLAGS" LIBS="$PTHREAD_LIBS $LIBS" # Check for various functions. We must include pthread.h, # since some functions may be macros. (On the Sequent, we # need a special flag -Kthread to make this header compile.) # We check for pthread_join because it is in -lpthread on IRIX # while pthread_create is in libc. We check for pthread_attr_init # due to DEC craziness with -lpthreads. We check for # pthread_cleanup_push because it is one of the few pthread # functions on Solaris that doesn't have a non-functional libc stub. # We try pthread_create on general principles. AC_LINK_IFELSE([AC_LANG_PROGRAM([#include # if $ax_pthread_check_cond # error "$ax_pthread_check_macro must be defined" # endif static void *some_global = NULL; static void routine(void *a) { /* To avoid any unused-parameter or unused-but-set-parameter warning. */ some_global = a; } static void *start_routine(void *a) { return a; }], [pthread_t th; pthread_attr_t attr; pthread_create(&th, 0, start_routine, 0); pthread_join(th, 0); pthread_attr_init(&attr); pthread_cleanup_push(routine, 0); pthread_cleanup_pop(0) /* ; */])], [ax_pthread_ok=yes], []) CFLAGS="$ax_pthread_save_CFLAGS" LIBS="$ax_pthread_save_LIBS" AC_MSG_RESULT([$ax_pthread_ok]) AS_IF([test "x$ax_pthread_ok" = "xyes"], [break]) PTHREAD_LIBS="" PTHREAD_CFLAGS="" done fi # Clang needs special handling, because older versions handle the -pthread # option in a rather... idiosyncratic way if test "x$ax_pthread_clang" = "xyes"; then # Clang takes -pthread; it has never supported any other flag # (Note 1: This will need to be revisited if a system that Clang # supports has POSIX threads in a separate library. This tends not # to be the way of modern systems, but it's conceivable.) # (Note 2: On some systems, notably Darwin, -pthread is not needed # to get POSIX threads support; the API is always present and # active. We could reasonably leave PTHREAD_CFLAGS empty. But # -pthread does define _REENTRANT, and while the Darwin headers # ignore this macro, third-party headers might not.) # However, older versions of Clang make a point of warning the user # that, in an invocation where only linking and no compilation is # taking place, the -pthread option has no effect ("argument unused # during compilation"). They expect -pthread to be passed in only # when source code is being compiled. # # Problem is, this is at odds with the way Automake and most other # C build frameworks function, which is that the same flags used in # compilation (CFLAGS) are also used in linking. Many systems # supported by AX_PTHREAD require exactly this for POSIX threads # support, and in fact it is often not straightforward to specify a # flag that is used only in the compilation phase and not in # linking. Such a scenario is extremely rare in practice. # # Even though use of the -pthread flag in linking would only print # a warning, this can be a nuisance for well-run software projects # that build with -Werror. So if the active version of Clang has # this misfeature, we search for an option to squash it. AC_CACHE_CHECK([whether Clang needs flag to prevent "argument unused" warning when linking with -pthread], [ax_cv_PTHREAD_CLANG_NO_WARN_FLAG], [ax_cv_PTHREAD_CLANG_NO_WARN_FLAG=unknown # Create an alternate version of $ac_link that compiles and # links in two steps (.c -> .o, .o -> exe) instead of one # (.c -> exe), because the warning occurs only in the second # step ax_pthread_save_ac_link="$ac_link" ax_pthread_sed='s/conftest\.\$ac_ext/conftest.$ac_objext/g' ax_pthread_link_step=`AS_ECHO(["$ac_link"]) | sed "$ax_pthread_sed"` ax_pthread_2step_ac_link="($ac_compile) && (echo ==== >&5) && ($ax_pthread_link_step)" ax_pthread_save_CFLAGS="$CFLAGS" for ax_pthread_try in '' -Qunused-arguments -Wno-unused-command-line-argument unknown; do AS_IF([test "x$ax_pthread_try" = "xunknown"], [break]) CFLAGS="-Werror -Wunknown-warning-option $ax_pthread_try -pthread $ax_pthread_save_CFLAGS" ac_link="$ax_pthread_save_ac_link" AC_LINK_IFELSE([AC_LANG_SOURCE([[int main(void){return 0;}]])], [ac_link="$ax_pthread_2step_ac_link" AC_LINK_IFELSE([AC_LANG_SOURCE([[int main(void){return 0;}]])], [break]) ]) done ac_link="$ax_pthread_save_ac_link" CFLAGS="$ax_pthread_save_CFLAGS" AS_IF([test "x$ax_pthread_try" = "x"], [ax_pthread_try=no]) ax_cv_PTHREAD_CLANG_NO_WARN_FLAG="$ax_pthread_try" ]) case "$ax_cv_PTHREAD_CLANG_NO_WARN_FLAG" in no | unknown) ;; *) PTHREAD_CFLAGS="$ax_cv_PTHREAD_CLANG_NO_WARN_FLAG $PTHREAD_CFLAGS" ;; esac fi # $ax_pthread_clang = yes # Various other checks: if test "x$ax_pthread_ok" = "xyes"; then ax_pthread_save_CFLAGS="$CFLAGS" ax_pthread_save_LIBS="$LIBS" CFLAGS="$CFLAGS $PTHREAD_CFLAGS" LIBS="$PTHREAD_LIBS $LIBS" # Detect AIX lossage: JOINABLE attribute is called UNDETACHED. AC_CACHE_CHECK([for joinable pthread attribute], [ax_cv_PTHREAD_JOINABLE_ATTR], [ax_cv_PTHREAD_JOINABLE_ATTR=unknown for ax_pthread_attr in PTHREAD_CREATE_JOINABLE PTHREAD_CREATE_UNDETACHED; do AC_LINK_IFELSE([AC_LANG_PROGRAM([#include ], [int attr = $ax_pthread_attr; return attr /* ; */])], [ax_cv_PTHREAD_JOINABLE_ATTR=$ax_pthread_attr; break], []) done ]) AS_IF([test "x$ax_cv_PTHREAD_JOINABLE_ATTR" != "xunknown" && \ test "x$ax_cv_PTHREAD_JOINABLE_ATTR" != "xPTHREAD_CREATE_JOINABLE" && \ test "x$ax_pthread_joinable_attr_defined" != "xyes"], [AC_DEFINE_UNQUOTED([PTHREAD_CREATE_JOINABLE], [$ax_cv_PTHREAD_JOINABLE_ATTR], [Define to necessary symbol if this constant uses a non-standard name on your system.]) ax_pthread_joinable_attr_defined=yes ]) AC_CACHE_CHECK([whether more special flags are required for pthreads], [ax_cv_PTHREAD_SPECIAL_FLAGS], [ax_cv_PTHREAD_SPECIAL_FLAGS=no case $host_os in solaris*) ax_cv_PTHREAD_SPECIAL_FLAGS="-D_POSIX_PTHREAD_SEMANTICS" ;; esac ]) AS_IF([test "x$ax_cv_PTHREAD_SPECIAL_FLAGS" != "xno" && \ test "x$ax_pthread_special_flags_added" != "xyes"], [PTHREAD_CFLAGS="$ax_cv_PTHREAD_SPECIAL_FLAGS $PTHREAD_CFLAGS" ax_pthread_special_flags_added=yes]) AC_CACHE_CHECK([for PTHREAD_PRIO_INHERIT], [ax_cv_PTHREAD_PRIO_INHERIT], [AC_LINK_IFELSE([AC_LANG_PROGRAM([[#include ]], [[int i = PTHREAD_PRIO_INHERIT; return i;]])], [ax_cv_PTHREAD_PRIO_INHERIT=yes], [ax_cv_PTHREAD_PRIO_INHERIT=no]) ]) AS_IF([test "x$ax_cv_PTHREAD_PRIO_INHERIT" = "xyes" && \ test "x$ax_pthread_prio_inherit_defined" != "xyes"], [AC_DEFINE([HAVE_PTHREAD_PRIO_INHERIT], [1], [Have PTHREAD_PRIO_INHERIT.]) ax_pthread_prio_inherit_defined=yes ]) CFLAGS="$ax_pthread_save_CFLAGS" LIBS="$ax_pthread_save_LIBS" # More AIX lossage: compile with *_r variant if test "x$GCC" != "xyes"; then case $host_os in aix*) AS_CASE(["x/$CC"], [x*/c89|x*/c89_128|x*/c99|x*/c99_128|x*/cc|x*/cc128|x*/xlc|x*/xlc_v6|x*/xlc128|x*/xlc128_v6], [#handle absolute path differently from PATH based program lookup AS_CASE(["x$CC"], [x/*], [ AS_IF([AS_EXECUTABLE_P([${CC}_r])],[PTHREAD_CC="${CC}_r"]) AS_IF([test "x${CXX}" != "x"], [AS_IF([AS_EXECUTABLE_P([${CXX}_r])],[PTHREAD_CXX="${CXX}_r"])]) ], [ AC_CHECK_PROGS([PTHREAD_CC],[${CC}_r],[$CC]) AS_IF([test "x${CXX}" != "x"], [AC_CHECK_PROGS([PTHREAD_CXX],[${CXX}_r],[$CXX])]) ] ) ]) ;; esac fi fi test -n "$PTHREAD_CC" || PTHREAD_CC="$CC" test -n "$PTHREAD_CXX" || PTHREAD_CXX="$CXX" AC_SUBST([PTHREAD_LIBS]) AC_SUBST([PTHREAD_CFLAGS]) AC_SUBST([PTHREAD_CC]) AC_SUBST([PTHREAD_CXX]) # Finally, execute ACTION-IF-FOUND/ACTION-IF-NOT-FOUND: if test "x$ax_pthread_ok" = "xyes"; then ifelse([$1],,[AC_DEFINE([HAVE_PTHREAD],[1],[Define if you have POSIX threads libraries and header files.])],[$1]) : else ax_pthread_ok=no $2 fi AC_LANG_POP ])dnl AX_PTHREAD ser2net-4.6.2/m4/ax_python_devel.m4000066400000000000000000000257121461221161700170670ustar00rootroot00000000000000# =========================================================================== # https://www.gnu.org/software/autoconf-archive/ax_python_devel.html # =========================================================================== # # SYNOPSIS # # AX_PYTHON_DEVEL([version]) # # DESCRIPTION # # Note: Defines as a precious variable "PYTHON_VERSION". Don't override it # in your configure.ac. # # This macro checks for Python and tries to get the include path to # 'Python.h'. It provides the $(PYTHON_CPPFLAGS) and $(PYTHON_LIBS) output # variables. It also exports $(PYTHON_EXTRA_LIBS) and # $(PYTHON_EXTRA_LDFLAGS) for embedding Python in your code. # # You can search for some particular version of Python by passing a # parameter to this macro, for example ">= '2.3.1'", or "== '2.4'". Please # note that you *have* to pass also an operator along with the version to # match, and pay special attention to the single quotes surrounding the # version number. Don't use "PYTHON_VERSION" for this: that environment # variable is declared as precious and thus reserved for the end-user. # # This macro should work for all versions of Python >= 2.1.0. As an end # user, you can disable the check for the python version by setting the # PYTHON_NOVERSIONCHECK environment variable to something else than the # empty string. # # If you need to use this macro for an older Python version, please # contact the authors. We're always open for feedback. # # LICENSE # # Copyright (c) 2009 Sebastian Huber # Copyright (c) 2009 Alan W. Irwin # Copyright (c) 2009 Rafael Laboissiere # Copyright (c) 2009 Andrew Collier # Copyright (c) 2009 Matteo Settenvini # Copyright (c) 2009 Horst Knorr # Copyright (c) 2013 Daniel Mullner # # 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 3 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, see . # # As a special exception, the respective Autoconf Macro's copyright owner # gives unlimited permission to copy, distribute and modify the configure # scripts that are the output of Autoconf when processing the Macro. You # need not follow the terms of the GNU General Public License when using # or distributing such scripts, even though portions of the text of the # Macro appear in them. The GNU General Public License (GPL) does govern # all other use of the material that constitutes the Autoconf Macro. # # This special exception to the GPL applies to versions of the Autoconf # Macro released by the Autoconf Archive. When you make and distribute a # modified version of the Autoconf Macro, you may extend this special # exception to the GPL to apply to your modified version as well. #serial 21 AU_ALIAS([AC_PYTHON_DEVEL], [AX_PYTHON_DEVEL]) AC_DEFUN([AX_PYTHON_DEVEL],[ # # Allow the use of a (user set) custom python version # AC_ARG_VAR([PYTHON_VERSION],[The installed Python version to use, for example '2.3'. This string will be appended to the Python interpreter canonical name.]) AC_PATH_PROG([PYTHON],[python[$PYTHON_VERSION]]) if test -z "$PYTHON"; then AC_MSG_ERROR([Cannot find python$PYTHON_VERSION in your system path]) PYTHON_VERSION="" fi # # Check for a version of Python >= 2.1.0 # AC_MSG_CHECKING([for a version of Python >= '2.1.0']) ac_supports_python_ver=`$PYTHON -c "import sys; \ ver = sys.version.split ()[[0]]; \ print (ver >= '2.1.0')"` if test "$ac_supports_python_ver" != "True"; then if test -z "$PYTHON_NOVERSIONCHECK"; then AC_MSG_RESULT([no]) AC_MSG_FAILURE([ This version of the AC@&t@_PYTHON_DEVEL macro doesn't work properly with versions of Python before 2.1.0. You may need to re-run configure, setting the variables PYTHON_CPPFLAGS, PYTHON_LIBS, PYTHON_SITE_PKG, PYTHON_EXTRA_LIBS and PYTHON_EXTRA_LDFLAGS by hand. Moreover, to disable this check, set PYTHON_NOVERSIONCHECK to something else than an empty string. ]) else AC_MSG_RESULT([skip at user request]) fi else AC_MSG_RESULT([yes]) fi # # if the macro parameter ``version'' is set, honour it # if test -n "$1"; then AC_MSG_CHECKING([for a version of Python $1]) ac_supports_python_ver=`$PYTHON -c "import sys; \ ver = sys.version.split ()[[0]]; \ print (ver $1)"` if test "$ac_supports_python_ver" = "True"; then AC_MSG_RESULT([yes]) else AC_MSG_RESULT([no]) AC_MSG_ERROR([this package requires Python $1. If you have it installed, but it isn't the default Python interpreter in your system path, please pass the PYTHON_VERSION variable to configure. See ``configure --help'' for reference. ]) PYTHON_VERSION="" fi fi # # Check if you have distutils, else fail # AC_MSG_CHECKING([for the distutils Python package]) ac_distutils_result=`$PYTHON -c "import distutils" 2>&1` if test $? -eq 0; then AC_MSG_RESULT([yes]) else AC_MSG_RESULT([no]) AC_MSG_ERROR([cannot import Python module "distutils". Please check your Python installation. The error was: $ac_distutils_result]) PYTHON_VERSION="" fi # # Check for Python include path # AC_MSG_CHECKING([for Python include path]) if test -z "$PYTHON_CPPFLAGS"; then python_path=`$PYTHON -c "import distutils.sysconfig; \ print (distutils.sysconfig.get_python_inc ());"` plat_python_path=`$PYTHON -c "import distutils.sysconfig; \ print (distutils.sysconfig.get_python_inc (plat_specific=1));"` if test -n "${python_path}"; then if test "${plat_python_path}" != "${python_path}"; then python_path="-I$python_path -I$plat_python_path" else python_path="-I$python_path" fi fi PYTHON_CPPFLAGS=$python_path fi AC_MSG_RESULT([$PYTHON_CPPFLAGS]) AC_SUBST([PYTHON_CPPFLAGS]) # # Check for Python library path # AC_MSG_CHECKING([for Python library path]) if test -z "$PYTHON_LIBS"; then # (makes two attempts to ensure we've got a version number # from the interpreter) ac_python_version=`cat<]], [[Py_Initialize();]]) ],[pythonexists=yes],[pythonexists=no]) AC_LANG_POP([C]) # turn back to default flags CPPFLAGS="$ac_save_CPPFLAGS" LIBS="$ac_save_LIBS" LDFLAGS="$ac_save_LDFLAGS" AC_MSG_RESULT([$pythonexists]) if test ! "x$pythonexists" = "xyes"; then AC_MSG_FAILURE([ Could not link test program to Python. Maybe the main Python library has been installed in some non-standard library path. If so, pass it to configure, via the LIBS environment variable. Example: ./configure LIBS="-L/usr/non-standard-path/python/lib" ============================================================================ ERROR! You probably have to install the development version of the Python package for your distribution. The exact name of this package varies among them. ============================================================================ ]) PYTHON_VERSION="" fi # # all done! # ]) ser2net-4.6.2/mdns.c000066400000000000000000000231701461221161700142160ustar00rootroot00000000000000/* * ser2net - A program for allowing telnet connection to serial ports * Copyright (C) 2023 Corey Minyard * * SPDX-License-Identifier: GPL-2.0-only * * In addition, as a special exception, the copyright holders of * ser2net give you permission to combine ser2net with free software * programs or libraries that are released under the GNU LGPL and * with code included in the standard release of OpenSSL under the * OpenSSL license (or modified versions of such code, with unchanged * license). You may copy and distribute such a system following the * terms of the GNU GPL for ser2net and the licenses of the other code * concerned, provided that you include the source code of that * other code when and as the GNU GPL requires distribution of source * code. * * Note that people who make modified versions of ser2net are not * obligated to grant this special exception for their modified * versions; it is their choice whether to do so. The GNU General * Public License gives permission to release a modified version * without this exception; this exception also makes it possible to * release a modified version which carries forward this exception. */ #include #include #include #include "mdns.h" #include "defaults.h" #ifdef DO_MDNS #include struct gensio_mdns *mdns; struct gensio_enum_val mdns_nettypes[] = { { "unspec", GENSIO_NETTYPE_UNSPEC }, { "ipv4", GENSIO_NETTYPE_IPV4 }, { "ipv6", GENSIO_NETTYPE_IPV6 }, { NULL } }; void msnd_info_init(struct mdns_info *m) { memset(m, 0, sizeof(*m)); m->mdns_interface = -1; m->mdns_nettype = GENSIO_NETTYPE_UNSPEC; } int mdns_info_getdefaults(struct mdns_info *m, const char *str, struct absout *eout) { msnd_info_init(m); m->mdns_interface = find_default_int("mdns-interface"); if (find_default_str("mdns-type", &m->mdns_type)) { eout->out(eout, "Can't get default value for mdns-type for %s:" " out of memory", str); return -1; } if (find_default_str("mdns-domain", &m->mdns_domain)) { eout->out(eout, "Can't get default value for mdns-domain for %s:" " out of memory", str); return -1; } if (find_default_str("mdns-host", &m->mdns_host)) { eout->out(eout, "Can't get default value for mdns-host for %s:" " out of memory", str); return -1; } return 0; } int mdns_checkoption(const char *option, struct mdns_info *m, const char *name, struct absout *eout) { const char *val; char *fval; if (gensio_check_keybool(option, "mdns", &m->mdns) > 0) return 1; if (gensio_check_keyuint(option, "mdns-port", &m->mdns_port) > 0) return 1; if (gensio_check_keyint(option, "mdns-interface", &m->mdns_interface) > 0) return 1; if (gensio_check_keyenum(option, "mdns-nettype", mdns_nettypes, &m->mdns_nettype) > 0) return 1; if (gensio_check_keyvalue(option, "mdns-name", &val) > 0) { fval = strdup(val); if (!fval) { eout->out(eout, "Out of memory allocating %s mdns-name", name); return -1; } if (m->mdns_name) free(m->mdns_name); m->mdns_name = fval; return 1; } if (gensio_check_keyvalue(option, "mdns-type", &val) > 0) { fval = strdup(val); if (!fval) { eout->out(eout, "Out of memory allocating %s mdns-type", name); return -1; } if (m->mdns_type) free(m->mdns_type); m->mdns_type = fval; return 1; } if (gensio_check_keyvalue(option, "mdns-domain", &val) > 0) { fval = strdup(val); if (!fval) { eout->out(eout, "Out of memory allocating %s mdns-domain", name); return -1; } if (m->mdns_domain) free(m->mdns_domain); m->mdns_domain = fval; return 1; } if (gensio_check_keyvalue(option, "mdns-host", &val) > 0) { fval = strdup(val); if (!fval) { eout->out(eout, "Out of memory allocating %s mdns-host", name); return -1; } if (m->mdns_host) free(m->mdns_host); m->mdns_host = fval; return 1; } if (gensio_check_keyvalue(option, "mdns-txt", &val) > 0) { int err = gensio_argv_append(so, &m->mdns_txt, val, &m->mdns_txt_args, &m->mdns_txt_argc, true); if (err) { eout->out(eout, "Out of memory allocating %s mdns-txt: %s", name, gensio_err_to_str(err)); return -1; } return 1; } return 0; } static void cleanup_mdns_data(struct mdns_info *m) { m->mdns = false; m->mdns_port = 0; m->mdns_interface = -1; m->mdns_nettype = GENSIO_NETTYPE_UNSPEC; if (m->mdns_name) { free(m->mdns_name); m->mdns_name = NULL; } if (m->mdns_type) { free(m->mdns_type); m->mdns_type = NULL; } if (m->mdns_domain) { free(m->mdns_domain); m->mdns_domain = NULL; } if (m->mdns_host) { free(m->mdns_host); m->mdns_host = NULL; } if (m->mdns_txt) { gensio_argv_free(so, m->mdns_txt); m->mdns_txt = NULL; } m->mdns_txt_argc = 0; m->mdns_txt_args = 0; } static char * derive_mdns_type(struct gensio_accepter *acc) { /* Get a mdns type based on the gensio. */ unsigned int i; const char *type, *ntype = "_iostream._tcp"; type = gensio_acc_get_type(acc, 0); for (i = 1; type; i++) { if (strcmp(type, "tcp") == 0) break; if (strcmp(type, "udp") == 0) { ntype = "_iostream._udp"; break; } type = gensio_acc_get_type(acc, i); } return strdup(ntype); } static void mdns_addprovider(struct mdns_info *m, const char *name, struct absout *eout) { gensiods i; static char *provstr = "provider="; char *tmps = NULL; for (i = 0; i < m->mdns_txt_argc; i++) { if (strncmp(m->mdns_txt[i], provstr, strlen(provstr)) == 0) /* User already specified it, don't override. */ return; } tmps = gensio_alloc_sprintf(so, "%s%s", provstr, "ser2net"); if (!tmps) goto out_nomem; if (gensio_argv_append(so, &m->mdns_txt, tmps, &m->mdns_txt_args, &m->mdns_txt_argc, false)) goto out_nomem; return; out_nomem: eout->out(eout, "Error allocating %s mdns provider: out of memory", name); if (tmps) so->free(so, tmps); } static void mdns_addstack(struct mdns_info *m, const char *name, struct absout *eout) { gensiods i; static char *stackstr = "gensiostack="; const char *type; char *stack = NULL, *tmps = NULL; for (i = 0; i < m->mdns_txt_argc; i++) { if (strncmp(m->mdns_txt[i], stackstr, strlen(stackstr)) == 0) /* User already specified it, don't override. */ return; } for (i = 0; ; i++) { type = gensio_acc_get_type(m->acc, i); if (!type) break; tmps = gensio_alloc_sprintf(so, "%s%s%s", stack ? stack : "", stack ? "," : "", type); if (!tmps) goto out_nomem; if (stack) so->free(so, stack); stack = tmps; } if (!stack) return; tmps = gensio_alloc_sprintf(so, "%s%s", stackstr, stack); if (!tmps) goto out_nomem; stack = tmps; if (gensio_argv_append(so, &m->mdns_txt, stack, &m->mdns_txt_args, &m->mdns_txt_argc, false)) goto out_nomem; return; out_nomem: eout->out(eout, "Error allocating %s mdns stack: out of memory", name); if (stack) so->free(so, stack); } void mdns_setup(struct mdns_info *m, const char *name, struct gensio_accepter *acc, struct absout *eout) { int err; char portnum_str[20]; gensiods portnum_len = sizeof(portnum_str); if (!m->mdns) return; if (!m->mdns_name && !name) { eout->out(eout, "%s mdns enabled, but no name given", name); return; } if (!mdns) { eout->out(eout, "mdns requested for %s port, but mdns failed" " to start or is disabled in gensio.", name); return; } m->acc = acc; if (!m->mdns_port) { strcpy(portnum_str, "0"); err = gensio_acc_control(m->acc, GENSIO_CONTROL_DEPTH_FIRST, true, GENSIO_ACC_CONTROL_LPORT, portnum_str, &portnum_len); if (err) { eout->out(eout, "Can't get %s mdns port: %s", name, gensio_err_to_str(err)); goto out_cleanup; } m->mdns_port = strtoul(portnum_str, NULL, 0); } if (!m->mdns_type) { m->mdns_type = derive_mdns_type(m->acc); if (!m->mdns_type) { eout->out(eout, "Can't alloc %s mdns type: out of memory", name); goto out_cleanup; } } if (!m->mdns_name) { m->mdns_name = strdup(name); if (!m->mdns_name) { eout->out(eout, "Can't alloc %s mdns name: out of memory", name); goto out_cleanup; } } mdns_addprovider(m, name, eout); mdns_addstack(m, name, eout); /* * Always stick on the NULL, that doesn't update argc so it's safe, * a new txt will just write over the NULL we added. */ err = gensio_argv_append(so, &m->mdns_txt, NULL, &m->mdns_txt_args, &m->mdns_txt_argc, true); if (err) { eout->out(eout, "Error terminating %s mdns-txt: %s", name, gensio_err_to_str(err)); goto out_cleanup; } err = gensio_mdns_add_service(mdns, m->mdns_interface, m->mdns_nettype, m->mdns_name, m->mdns_type, m->mdns_domain, m->mdns_host, m->mdns_port, m->mdns_txt, &m->mdns_service); if (err) { eout->out(eout, "Can't add %s mdns service: %s", name, gensio_err_to_str(err)); goto out_cleanup; } return; out_cleanup: cleanup_mdns_data(m); return; } void mdns_shutdown(struct mdns_info *m) { if (m->mdns_service) gensio_mdns_remove_service(m->mdns_service); m->mdns_service = NULL; cleanup_mdns_data(m); } void init_mdns(void) { int err = gensio_alloc_mdns(so, &mdns); /* * If gensio doesn't support MDNS, that's not reportable unless * the user tries to use it. */ if (err && err != GE_NOTSUP) /* Not fatal */ fprintf(stderr, "Unable to start mdns: %s\n", gensio_err_to_str(err)); } #else void init_mdns(void) { } #endif ser2net-4.6.2/mdns.h000066400000000000000000000043211461221161700142200ustar00rootroot00000000000000/* * ser2net - A program for allowing telnet connection to serial ports * Copyright (C) 2023 Corey Minyard * * SPDX-License-Identifier: GPL-2.0-only * * In addition, as a special exception, the copyright holders of * ser2net give you permission to combine ser2net with free software * programs or libraries that are released under the GNU LGPL and * with code included in the standard release of OpenSSL under the * OpenSSL license (or modified versions of such code, with unchanged * license). You may copy and distribute such a system following the * terms of the GNU GPL for ser2net and the licenses of the other code * concerned, provided that you include the source code of that * other code when and as the GNU GPL requires distribution of source * code. * * Note that people who make modified versions of ser2net are not * obligated to grant this special exception for their modified * versions; it is their choice whether to do so. The GNU General * Public License gives permission to release a modified version * without this exception; this exception also makes it possible to * release a modified version which carries forward this exception. */ #ifndef SER2NET_MDNS #define SER2NET_MDNS #include "ser2net.h" #ifdef DO_MDNS #include #include #include extern struct gensio_mdns *mdns; extern struct gensio_enum_val mdns_nettypes[]; struct mdns_info { struct gensio_accepter *acc; bool mdns; unsigned int mdns_port; int mdns_interface; int mdns_nettype; char *mdns_name; char *mdns_type; char *mdns_domain; char *mdns_host; const char **mdns_txt; gensiods mdns_txt_argc; gensiods mdns_txt_args; struct gensio_mdns_service *mdns_service; }; void msnd_info_init(struct mdns_info *m); int mdns_info_getdefaults(struct mdns_info *m, const char *str, struct absout *eout); int mdns_checkoption(const char *option, struct mdns_info *m, const char *name, struct absout *eout); void mdns_setup(struct mdns_info *m, const char *name, struct gensio_accepter *acc, struct absout *eout); void mdns_shutdown(struct mdns_info *m); #endif #endif /* SER2NET_MDNS */ ser2net-4.6.2/port.c000066400000000000000000000652311461221161700142450ustar00rootroot00000000000000/* * ser2net - A program for allowing telnet connection to serial ports * Copyright (C) 2020 Corey Minyard * * SPDX-License-Identifier: GPL-2.0-only * * In addition, as a special exception, the copyright holders of * ser2net give you permission to combine ser2net with free software * programs or libraries that are released under the GNU LGPL and * with code included in the standard release of OpenSSL under the * OpenSSL license (or modified versions of such code, with unchanged * license). You may copy and distribute such a system following the * terms of the GNU GPL for ser2net and the licenses of the other code * concerned, provided that you include the source code of that * other code when and as the GNU GPL requires distribution of source * code. * * Note that people who make modified versions of ser2net are not * obligated to grant this special exception for their modified * versions; it is their choice whether to do so. The GNU General * Public License gives permission to release a modified version * without this exception; this exception also makes it possible to * release a modified version which carries forward this exception. */ #include #include #include #include #include "ser2net.h" #include "port.h" #include "gbuf.h" #include #include struct gensio_lock *ports_lock; port_info_t *ports = NULL; /* Linked list of ports. */ port_info_t *new_ports = NULL; /* New ports during config/reconfig. */ port_info_t *new_ports_end = NULL; net_info_t * first_live_net_con(port_info_t *port) { net_info_t *netcon; for_each_connection(port, netcon) { if (netcon->net) return netcon; } return NULL; } bool port_in_use(port_info_t *port) { return (port->net_to_dev_state != PORT_UNCONNECTED && port->net_to_dev_state != PORT_CLOSED); } /* Checks to see if some other port has the same device in use. Must be called with ports_lock held. */ int is_device_already_inuse(port_info_t *check_port) { port_info_t *port = ports; while (port != NULL) { if (port != check_port) { if ((strcmp(port->devname, check_port->devname) == 0) && port_in_use(port)) { return 1; } } port = port->next; } return 0; } int num_connected_net(port_info_t *port) { net_info_t *netcon; int count = 0; for_each_connection(port, netcon) { if (netcon->net) count++; } return count; } gensiods net_raddr(struct gensio *io, struct sockaddr_storage *addr, gensiods *socklen) { *socklen = sizeof(*addr); #if (defined(gensio_version_major) && (gensio_version_major > 2 || \ (gensio_version_major == 2 && gensio_version_minor > 0))) return gensio_control(io, GENSIO_CONTROL_DEPTH_FIRST, true, GENSIO_CONTROL_RADDR_BIN, (char *) addr, socklen); #else return gensio_get_raddr(io, (char *) addr, socklen); #endif } void reset_timer(net_info_t *netcon) { if (netcon->connect_back && netcon->port->connback_timeout_set) netcon->timeout_left = netcon->port->connback_timeout; else netcon->timeout_left = netcon->port->timeout; netcon->timeout_running = netcon->timeout_left != 0; } void port_start_timer(port_info_t *port) { gensio_time timeout; unsigned int timeout_sec; switch (port->dev_to_net_state) { case PORT_UNCONNECTED: timeout_sec = port->connector_retry_time; break; case PORT_CLOSED: case PORT_NOT_STARTED: timeout_sec = port->accepter_retry_time; break; default: timeout_sec = 1; break; } #ifdef gensio_version_major timeout.secs = timeout_sec; timeout.nsecs = 0; #else timeout.tv_sec = timeout_sec; timeout.tv_usec = 0; #endif so->start_timer(port->timer, &timeout); } static void kick_old_user(port_info_t *port, net_info_t *netcon, struct gensio *new_net) { char *err = "kicked off, new user is coming\r\n"; /* If another user is waiting for a kick, kick that user. */ if (netcon->new_net) { gensio_write(netcon->new_net, NULL, err, strlen(err), NULL); gensio_free(netcon->new_net); } /* Wait it to be unconnected and clean, restart the process. */ netcon->new_net = new_net; shutdown_one_netcon(netcon, err); } static void check_port_new_net(port_info_t *port, net_info_t *netcon) { struct gensio *net; if (!netcon->new_net) return; if (netcon->net) { /* Something snuck in before, kick this one out. */ char *err = "kicked off, new user is coming\r\n"; gensio_write(netcon->new_net, NULL, err, strlen(err), NULL); gensio_free(netcon->new_net); netcon->new_net = NULL; return; } net = netcon->new_net; netcon->new_net = NULL; handle_new_net(port, net, netcon); } static int port_new_con(port_info_t *port, struct gensio *net) { const char *err = NULL; unsigned int i, j; struct sockaddr_storage addr; gensiods socklen; so->lock(ports_lock); /* For is_device_already_inuse() */ so->lock(port->lock); if (port->net_to_dev_state == PORT_CLOSING) { /* Can happen on a race with the callback disable. */ if (!port->enabled) { err = "Port closing\r\n"; goto out_err; } /* We hold off new connections during a close */ goto out; } if (!port->enabled) { err = "Port disabled\r\n"; goto out_err; } if (!net_raddr(net, &addr, &socklen)) { if (!remaddr_check(port->remaddrs, (struct sockaddr *) &addr, socklen)) { err = "Accessed denied due to your net address\r\n"; goto out_err; } } for (j = port->max_connections, i = 0; i < port->max_connections; i++) { if (!port->netcons[i].net && !port->netcons[i].remote_fixed) break; if (!port->netcons[i].remote_fixed) j = i; } if (i == port->max_connections) { if (port->kickolduser_mode && j < port->max_connections) { /* Kick off the first non-fixed user. */ kick_old_user(port, &port->netcons[j], net); goto out; } err = "Port already in use\r\n"; } if (!err && is_device_already_inuse(port)) err = "Port's device already in use\r\n"; if (port->connbacks && !port->io_open) err = "Port's device failed open\r\n"; if (err) { out_err: so->unlock(port->lock); so->unlock(ports_lock); gensio_write(net, NULL, err, strlen(err), NULL); gensio_free(net); return 0; } /* We have to hold the ports_lock until after this call so the device won't get used (from is_device_already_inuse()). */ handle_new_net(port, net, &(port->netcons[i])); out: so->unlock(port->lock); so->unlock(ports_lock); return 0; } /* A connection request has come in on a port. */ static int handle_port_child_event(struct gensio_accepter *accepter, void *user_data, int event, void *data) { port_info_t *port = user_data; if (event == GENSIO_ACC_EVENT_LOG) { do_gensio_log(port->name, data); return 0; } switch (event) { case GENSIO_ACC_EVENT_NEW_CONNECTION: return port_new_con(port, data); #ifdef GENSIO_ACC_EVENT_PARMLOG case GENSIO_ACC_EVENT_PARMLOG: { struct gensio_parmlog_data *d = (struct gensio_parmlog_data *) data; seout.vout(&seout, d->log, d->args); return 0; } #endif default: return handle_acc_auth_event(port->authdir, port->pamauth, port->allowed_users, event, data); } } int startup_port(struct absout *eout, port_info_t *port) { int err; if (port->dev_to_net_state == PORT_NOT_STARTED) { port_start_timer(port); return GE_NOTREADY; } if (port->dev_to_net_state != PORT_CLOSED) return GE_INUSE; err = gensio_acc_startup(port->accepter); if (err) { eout->out(eout, "Unable to startup network port %s: %s", port->name, gensio_err_to_str(err)); /* Retry in a bit. */ port_start_timer(port); return err; } port->dev_to_net_state = PORT_UNCONNECTED; port->net_to_dev_state = PORT_UNCONNECTED; #ifdef DO_MDNS mdns_setup(&port->mdns_info, port->name, port->accepter, eout); #endif if (port->connbacks) { err = port_dev_enable(port); if (err) { eout->out(eout, "Unable to enable port device %s: %s", port->name, gensio_err_to_str(err)); shutdown_port(port, "Error enabling port connector"); err = 0; /* Don't report an error here, let the shutdown run. */ } } return err; } static void call_port_op_done(port_info_t *port) { void (*port_op_done)(struct port_info *, void *) = port->port_op_done; if (port_op_done) { port->port_op_done = NULL; port_op_done(port, port->port_op_data); } } static void finish_shutdown_port(struct gensio_runner *runner, void *cb_data) { port_info_t *port = cb_data; so->lock(ports_lock); so->lock(port->lock); if (port->enabled) { port->net_to_dev_state = PORT_UNCONNECTED; port->dev_to_net_state = PORT_UNCONNECTED; if (port->connbacks) port_start_timer(port); } else { port->net_to_dev_state = PORT_CLOSED; port->dev_to_net_state = PORT_CLOSED; } gbuf_reset(&port->net_to_dev); if (port->devstr) { gbuf_free(port->devstr); port->devstr = NULL; } gbuf_reset(&port->dev_to_net); port->dev_bytes_received = 0; port->dev_bytes_sent = 0; if (gensio_acc_exit_on_close(port->accepter)) /* This was a zero port (for stdin/stdout), this is only allowed with one port at a time, and we shut down when it closes. */ exit(0); /* If the port has been deleted, then finish the job. */ if (port->deleted) { port_info_t *curr, *prev, *new; new = port->new_config; port->new_config = NULL; prev = NULL; for (curr = ports; curr && curr != port; curr = curr->next) prev = curr; if (curr) { if (prev == NULL) ports = curr->next; else prev->next = curr->next; } so->unlock(port->lock); free_port(port); /* Start the replacement port if it was set. */ if (new) { so->lock(new->lock); if (prev) { new->next = prev->next; prev->next = new; } else { new->next = ports; ports = new; } if (new->enabled) startup_port(&seout, new); so->unlock(new->lock); } so->unlock(ports_lock); return; /* We have to return here because we no longer have a port. */ } else if (port->enabled) { net_info_t *netcon; gensio_acc_set_accept_callback_enable(port->accepter, true); for_each_connection(port, netcon) check_port_new_net(port, netcon); } else { /* Port was disabled, shut it down. */ #ifdef DO_MDNS mdns_shutdown(&port->mdns_info); #endif gensio_acc_shutdown(port->accepter, NULL, NULL); call_port_op_done(port); } so->unlock(port->lock); so->unlock(ports_lock); } static void io_shutdown_done(struct gensio *io, void *cb_data) { port_info_t *port = cb_data; port->io_open = false; so->run(port->runshutdown); } void shutdown_port_io(port_info_t *port) { int err = 1; shutdown_trace(port); if (port->io) err = gensio_close(port->io, io_shutdown_done, port); if (err) { port->io_open = false; so->run(port->runshutdown); } } static void timer_shutdown_done(struct gensio_timer *timer, void *cb_data) { port_info_t *port = cb_data; so->lock(port->lock); gensio_set_write_callback_enable(port->io, false); shutdown_port_io(port); so->unlock(port->lock); } /* Output any pending data and the devstr buffer. */ static void handle_dev_fd_close_write(port_info_t *port) { int err; if (gbuf_cursize(&port->net_to_dev) != 0) err = gbuf_write(port, &port->net_to_dev); else if (port->devstr) err = gbuf_write(port, port->devstr); else goto closeit; if (err) { seout.out(&seout, "The dev write(3) for port %s had error: %s", port->name, gensio_err_to_str(err)); goto closeit; } if (gbuf_cursize(&port->net_to_dev) || (port->devstr && gbuf_cursize(port->devstr))) return; closeit: if (port->shutdown_timeout_count) { gensio_set_write_callback_enable(port->io, false); err = so->stop_timer_with_done(port->timer, timer_shutdown_done, port); if (err == GE_TIMEDOUT) { port->shutdown_timeout_count = 0; shutdown_port_io(port); } } } static void start_shutdown_port_io(port_info_t *port) { if (!port->io_open) { so->run(port->runshutdown); return; } if (port->devstr) gbuf_free(port->devstr); port->devstr = process_str_to_buf(port, NULL, port->closestr, &seout); port->dev_write_handler = handle_dev_fd_close_write; gensio_set_write_callback_enable(port->io, true); } static void netcon_finish_shutdown(net_info_t *netcon) { port_info_t *port = netcon->port; if (netcon->net) { report_disconnect(port, netcon); gensio_free(netcon->net); netcon->net = NULL; } netcon->closing = false; netcon->bytes_received = 0; netcon->bytes_sent = 0; netcon->write_pos = 0; if (netcon->banner) { gbuf_free(netcon->banner); netcon->banner = NULL; } if (num_connected_net(port) == 0) { if (port->net_to_dev_state == PORT_CLOSING) { start_shutdown_port_io(port); } else if (port->connbacks && port->enabled) { /* Leave the device open for connect backs. */ gensio_set_write_callback_enable(port->io, true); port->dev_to_net_state = PORT_WAITING_INPUT; port->net_to_dev_state = PORT_UNCONNECTED; check_port_new_net(port, netcon); } else { shutdown_port(port, NULL); } } else { check_port_new_net(port, netcon); } } static void handle_net_fd_closed(struct gensio *net, void *cb_data) { net_info_t *netcon = cb_data; port_info_t *port = netcon->port; so->lock(port->lock); netcon_finish_shutdown(netcon); so->unlock(port->lock); } void shutdown_one_netcon(net_info_t *netcon, const char *reason) { int err; if (netcon->closing) return; netcon->write_pos = 0; footer_trace(netcon->port, "netcon", reason); netcon->close_on_output_done = false; netcon->closing = true; err = gensio_close(netcon->net, handle_net_fd_closed, netcon); if (err) netcon_finish_shutdown(netcon); } static bool shutdown_all_netcons(port_info_t *port, bool close_on_output_only) { net_info_t *netcon; bool some_to_close = false; for_each_connection(port, netcon) { if (netcon->net) { if (close_on_output_only && !netcon->close_on_output_done) continue; some_to_close = true; netcon->write_pos = port->dev_to_net.cursize; shutdown_one_netcon(netcon, "port closing"); } } return some_to_close; } static void accept_read_disabled(struct gensio_accepter *acc, void *cb_data) { port_info_t *port = cb_data; net_info_t *netcon; bool some_to_close = false; so->lock(port->lock); port->shutdown_started = false; /* * At this point we won't receive any more accepts until we re-enable it, * go into closing state unless it wasn't an error and a new net came in. */ if (port->net_to_dev_state != PORT_CLOSING && num_connected_net(port) > 0) { /* * It wasn't an error close and a new connection came in. Just * ignore the shutdown. */ gensio_acc_set_accept_callback_enable(port->accepter, true); for_each_connection(port, netcon) { if (netcon->net) gensio_set_read_callback_enable(netcon->net, true); } gensio_set_read_callback_enable(port->io, true); goto out_unlock; } /* After this point we will shut down the port completely. */ /* FIXME - this should be calculated somehow, not a raw number .*/ port->shutdown_timeout_count = 4; footer_trace(port, "port", port->shutdown_reason); for_each_connection(port, netcon) { if (netcon->net) { some_to_close = true; if (netcon->new_net) { /* Something is waiting for a kick. */ char *err = "port is shutting down\r\n"; gensio_write(netcon->new_net, NULL, err, strlen(err), NULL); gensio_free(netcon->new_net); netcon->new_net = NULL; } if (port->dev_to_net_state == PORT_WAITING_OUTPUT_CLEAR && netcon->write_pos < port->dev_to_net.cursize) /* Net has data to send, wait until it's done. */ netcon->close_on_output_done = true; else shutdown_one_netcon(netcon, "port closing"); } } port->dev_to_net_state = PORT_CLOSING; port->net_to_dev_state = PORT_CLOSING; if (!some_to_close) start_shutdown_port_io(port); out_unlock: so->unlock(port->lock); } int shutdown_port(port_info_t *port, const char *errreason) { net_info_t *netcon; int err; if (port->shutdown_started && port->net_to_dev_state != PORT_CLOSING && errreason) { /* An error occurred and we are in a non-err shutdown. Convert it. */ port->shutdown_reason = errreason; port->net_to_dev_state = PORT_CLOSING; gensio_set_read_callback_enable(port->io, false); return 0; } if (port->net_to_dev_state == PORT_CLOSING || port->net_to_dev_state == PORT_CLOSED || (port->enabled && port->dev_to_net_state == PORT_UNCONNECTED) || port->shutdown_started) return GE_INUSE; if (errreason) { /* It's an error, force a shutdown. Don't set dev_to_net_state yet. */ port->shutdown_reason = errreason; port->net_to_dev_state = PORT_CLOSING; /* Shut down write on an error. */ gensio_set_read_callback_enable(port->io, false); } else { port->shutdown_reason = "All users disconnected"; } port->shutdown_started = true; err = gensio_acc_set_accept_callback_enable_cb(port->accepter, false, accept_read_disabled, port); /* This is bad, it's an out of memory condition. Abort. */ assert(err == 0); for_each_connection(port, netcon) { if (netcon->net) gensio_set_read_callback_enable(netcon->net, false); } gensio_set_read_callback_enable(port->io, false); return 0; } static bool handle_shutdown_timeout(port_info_t *port) { /* Something wasn't able to do any writes and locked up the shutdown. */ /* Check the network connections first. */ if (shutdown_all_netcons(port, true)) return true; shutdown_port_io(port); return false; } static int port_startup(port_info_t *new_port, struct absout *eout, bool retry) { int err; err = str_to_gensio(new_port->devname, so, handle_dev_event, new_port, &new_port->io); if (err) { if (!retry || new_port->retry_startup_counter % 64 == 0) eout->out(eout, "device configuration %s invalid: %s", new_port->devname, gensio_err_to_str(err)); goto out_err; } err = str_to_gensio_accepter(new_port->accstr, so, handle_port_child_event, new_port, &new_port->accepter); if (err) { gensio_free(new_port->io); new_port->io = NULL; if (!retry || new_port->retry_startup_counter % 64 == 0) eout->out(eout, "Invalid accepter port name/number '%s': %s", new_port->accstr, gensio_err_to_str(err)); goto out_err; } if (new_port->enabled && new_port->do_telnet) { const char *str = "telnet"; struct gensio_accepter *parent; if (new_port->allow_2217) str = "telnet(rfc2217=true)"; err = str_to_gensio_accepter_child(new_port->accepter, str, so, handle_port_child_event, new_port, &parent); if (err) { gensio_free(new_port->io); new_port->io = NULL; gensio_acc_free(new_port->accepter); new_port->accepter = NULL; if (!retry || new_port->retry_startup_counter % 64 == 0) eout->out(eout, "Could not allocate telnet gensio: %s", gensio_err_to_str(err)); goto out_err; } new_port->accepter = parent; } new_port->dev_to_net_state = PORT_CLOSED; if (retry) eout->out(eout, "port accepter '%s' is now started", new_port->accstr); return 0; out_err: new_port->retry_startup_counter++; return err; } static void port_timeout(struct gensio_timer *timer, void *data) { port_info_t *port = (port_info_t *) data; net_info_t *netcon; int err; so->lock(port->lock); if (port->dev_to_net_state == PORT_NOT_STARTED) { err = port_startup(port, &seout, true); if (err) goto out; } if (port->dev_to_net_state == PORT_CLOSED) { if (port->enabled) startup_port(&seout, port); goto out_unlock; } if (port->dev_to_net_state == PORT_UNCONNECTED) { if (port->connbacks && !port->io_open) { err = port_dev_enable(port); if (err) goto out; } goto out_unlock; } if (port->dev_to_net_state == PORT_CLOSING) { if (port->shutdown_timeout_count <= 1) { bool dotimer = false; port->shutdown_timeout_count = 0; dotimer = handle_shutdown_timeout(port); so->unlock(port->lock); if (dotimer) goto out; return; } else { port->shutdown_timeout_count--; goto out; } } if (port->nocon_read_enable_time_left) { port->nocon_read_enable_time_left--; if (port->nocon_read_enable_time_left == 0) gensio_set_read_callback_enable(port->io, true); goto out; } /* * Check the timeout on all the ports. If we have a separate * timeout for connect backs, we need to check that separately. */ if (port_in_use(port)) { for_each_connection(port, netcon) { if (!netcon->net || !netcon->timeout_running) continue; netcon->timeout_left--; if (netcon->timeout_left < 0) shutdown_one_netcon(netcon, "timeout"); } } out: port_start_timer(port); out_unlock: so->unlock(port->lock); } void apply_new_ports(struct absout *eout) { port_info_t *new, *curr, *next, *prev, *new_prev; so->lock(ports_lock); /* First turn off all the accepters. */ for (curr = ports; curr; curr = curr->next) { int err; if (curr->deleted) continue; if (curr->enabled && curr->accepter) { /* * This unlock is a little strange, but we don't want to * do any waiting while holding the ports lock, otherwise * we might deadlock on a deletion in finish_shutdown_port(). * This is save as long as curr is not deleted, because curr * will not go away, though curr->next may change, that * shouldn't matter. */ so->unlock(ports_lock); err = gensio_acc_set_accept_callback_enable_s(curr->accepter, false); /* Errors only happen on out of memory. */ assert(err == 0); so->lock(ports_lock); curr->accepter_stopped = true; } } /* At this point we can't get any new accepts. */ /* * See if the port already exists, and link it to this port. We * put the old port in the new port's place for now. */ for (new_prev = NULL, new = new_ports; new; new_prev = new, new = new->next) { so->lock(new->lock); for (prev = NULL, curr = ports; curr; prev = curr, curr = curr->next) { so->lock(curr->lock); if (strcmp(curr->name, new->name) == 0) { if (port_in_use(curr)) { /* If we are disabling, kick off old users. */ if (!new->enabled && curr->enabled) shutdown_all_netcons(curr, false); } else { if (strcmp(curr->accstr, new->accstr) == 0 && curr->enabled) { /* * Accepter didn't change and was on, just * move it over. This avoid issues with a * connection coming in during a reconfig. */ struct gensio_accepter *tmp; tmp = new->accepter; new->accepter = curr->accepter; new->accepter_stopped = true; curr->accepter = tmp; curr->accepter_stopped = false; gensio_acc_set_user_data(curr->accepter, curr); gensio_acc_set_user_data(new->accepter, new); } /* Just let the old one get deleted. */ so->unlock(curr->lock); break; } /* We are reconfiguring this port. */ if (curr->new_config) free_port(curr->new_config); curr->new_config = new; /* * Put the current entry into the place of the new entry * in the new_ports array. */ if (prev) prev->next = curr->next; else ports = curr->next; curr->next = new->next; if (new_prev) new_prev->next = curr; else new_ports = curr; if (new_ports_end == new) new_ports_end = curr; gensio_acc_disable(curr->accepter); curr->deleted = true; so->unlock(new->lock); new = curr; break; } so->unlock(curr->lock); } so->unlock(new->lock); } /* * We nuke any old port without a new config. Do this first so * new ports can use the given port numbers that might be in these. */ for (curr = ports; curr; curr = next) { next = curr->next; so->lock(curr->lock); if (curr->accepter_stopped && curr->enabled) gensio_acc_disable(curr->accepter); curr->deleted = true; curr->enabled = false; if (!port_in_use(curr)) { so->unlock(curr->lock); free_port(curr); } else { /* Leave it in the new ports for shutdown when the user closes. */ if (new_ports_end) new_ports_end->next = curr; curr->next = NULL; new_ports_end = curr; so->unlock(curr->lock); } } /* Now start up the new ports. */ ports = new_ports; new_ports = NULL; new_ports_end = NULL; for (curr = ports; curr; curr = curr->next) { so->lock(curr->lock); if (!curr->deleted) { if (curr->accepter_stopped) { curr->accepter_stopped = false; if (curr->enabled) { gensio_acc_set_accept_callback_enable(curr->accepter, true); curr->dev_to_net_state = PORT_UNCONNECTED; curr->net_to_dev_state = PORT_UNCONNECTED; } else { gensio_acc_disable(curr->accepter); } } else { if (curr->enabled) startup_port(eout, curr); } } so->unlock(curr->lock); } so->unlock(ports_lock); } int dataxfer_setup_port(port_info_t *new_port, struct absout *eout) { new_port->timer = so->alloc_timer(so, port_timeout, new_port); if (!new_port->timer) { eout->out(eout, "Could not allocate timer data"); return -1; } new_port->send_timer = so->alloc_timer(so, port_send_timeout, new_port); if (!new_port->send_timer) { eout->out(eout, "Could not allocate timer data"); return -1; } new_port->runshutdown = so->alloc_runner(so, finish_shutdown_port, new_port); if (!new_port->runshutdown) { eout->out(eout, "Could not allocate shutdown runner"); return -1; } new_port->dev_to_net_state = PORT_NOT_STARTED; new_port->net_to_dev_state = PORT_CLOSED; port_startup(new_port, eout, false); return 0; } void shutdown_ports(void) { port_info_t *port, *next, *prev; so->lock(ports_lock); prev = NULL; for (port = ports; port; port = next) { next = port->next; so->lock(port->lock); if (port->enabled) { if (port->new_config) { free_port(port->new_config); port->new_config = NULL; } port->deleted = true; port->enabled = false; if (shutdown_port(port, "program shutdown") != 0) goto do_free_port; so->unlock(port->lock); prev = port; } else { do_free_port: if (prev) prev->next = port->next; else ports = port->next; so->unlock(port->lock); free_port(port); } } so->unlock(ports_lock); } int check_ports_shutdown(void) { return ports == NULL; } void shutdown_dataxfer(void) { shutdown_rotators(); if (ports_lock) so->free_lock(ports_lock); } int init_dataxfer(void) { int rv; ports_lock = so->alloc_lock(so); if (!ports_lock) { rv = GE_NOMEM; goto out; } rv = init_rotators(); out: if (rv) shutdown_dataxfer(); return rv; } ser2net-4.6.2/port.h000066400000000000000000000332351461221161700142510ustar00rootroot00000000000000/* * ser2net - A program for allowing telnet connection to serial ports * Copyright (C) 2020 Corey Minyard * * SPDX-License-Identifier: GPL-2.0-only * * In addition, as a special exception, the copyright holders of * ser2net give you permission to combine ser2net with free software * programs or libraries that are released under the GNU LGPL and * with code included in the standard release of OpenSSL under the * OpenSSL license (or modified versions of such code, with unchanged * license). You may copy and distribute such a system following the * terms of the GNU GPL for ser2net and the licenses of the other code * concerned, provided that you include the source code of that * other code when and as the GNU GPL requires distribution of source * code. * * Note that people who make modified versions of ser2net are not * obligated to grant this special exception for their modified * versions; it is their choice whether to do so. The GNU General * Public License gives permission to release a modified version * without this exception; this exception also makes it possible to * release a modified version which carries forward this exception. */ #ifndef PORT #define PORT #ifdef WIN32 #include #else #include #endif #include #include "gbuf.h" #include "absout.h" #include "mdns.h" #include "timeproc.h" #include "fileio.h" /* States for the net_to_dev_state and dev_to_net_state. */ #define PORT_NOT_STARTED 0 /* The dataxfer_start_port failed. */ #define PORT_CLOSED 1 /* The accepter is disabled. */ #define PORT_UNCONNECTED 2 /* The TCP port is not connected to anything right now. */ #define PORT_WAITING_INPUT 3 /* Waiting for input from the input side. */ #define PORT_WAITING_OUTPUT_CLEAR 4 /* Waiting for output to clear so I can send data. */ #define PORT_CLOSING 5 /* Waiting for output close string to be sent. */ typedef struct trace_info_s { bool hexdump; /* output each block as a hexdump */ bool timestamp; /* precede each line with a timestamp */ char *filename; /* open file. NULL if not used */ ftype *f; /* open file. NULL if not used */ } trace_info_t; typedef struct port_info port_info_t; typedef struct net_info net_info_t; struct net_info { port_info_t *port; /* My port. */ bool closing; /* Is the connection in the process of closing? */ struct gensio *net; /* When connected, the network connection, NULL otherwise. */ bool remote_fixed; /* Tells if the remote address was set in the configuration, and cannot be changed. */ bool connect_back; /* True if we connect to the remote address when data comes in. */ const char *remote_str; gensiods bytes_received; /* Number of bytes read from the network port. */ gensiods bytes_sent; /* Number of bytes written to the network port. */ struct gbuf *banner; /* Outgoing banner */ gensiods write_pos; /* Our current position in the output buffer where we need to start writing next. */ int timeout_left; /* The amount of time left (in seconds) before the timeout goes off. */ bool timeout_running; /* Do we use the timer? */ /* * Close the session when all the output has been written to the * network port. */ bool close_on_output_done; unsigned char linestate_mask; unsigned char modemstate_mask; bool modemstate_sent; /* Has a modemstate been sent? */ bool linestate_sent; /* Has a linestate been sent? */ char remaddr[NI_MAXHOST + NI_MAXSERV + 2]; /* * If a user gets kicked, store the information for the new user * here since we have already accepted the connection or received * the packet, we have to store it someplace. */ struct gensio *new_net; }; struct port_info { struct gensio_lock *lock; /* If false, port is not accepting, if true it is. */ bool enabled; const char *shutdown_reason; void (*port_op_done)(struct port_info *, void *); void *port_op_data; /* FIXME - remove this with old config. An old config specified telnet. */ bool do_telnet; /* Keeps a count of retried port startups. */ unsigned int retry_startup_counter; /* The port has been deleted, but still has connections in use. */ bool deleted; /* Used to count operations (timer stops) during free. */ unsigned int free_count; int timeout; /* The number of seconds to wait without any I/O before we shut the port down. */ struct gensio_timer *timer; /* Used to timeout when the no I/O has been seen for a certain period of time. */ struct gensio_timer *send_timer; /* Used to delay a bit when waiting for characters to batch up as many characters as possible. */ bool send_timer_running; /* Time to retry if the connector/accepter fails to come up. */ unsigned int connector_retry_time; unsigned int accepter_retry_time; unsigned int nocon_read_enable_time_left; /* Used if a connect back is requested an no connections could be made, to try again. */ /* * Used to count timeouts during a shutdown, to make sure close * happens in a reasonable amount of time. If this is zero, this * means that shutdown_port_io() has already been called. */ unsigned int shutdown_timeout_count; struct gensio_runner *runshutdown; /* Used to run things at the base context. This way we don't have to worry that we are running inside a handler context that needs to be waited for exit. */ unsigned int chardelay; /* The amount of time to wait after receiving a character before sending it, unless we receive another character. Based on bit rate. */ unsigned int bps; /* Bits per second rate. */ unsigned int bpc; /* Bits per character. */ unsigned int stopbits; unsigned int paritybits; bool enable_chardelay; /* Disable data flowing in the given directions. */ bool no_dev_to_net; bool no_net_to_dev; unsigned int chardelay_scale; /* The number of character periods to wait for the next character, in tenths of a character period. */ unsigned int chardelay_min; /* The minimum chardelay, in microseconds. */ unsigned int chardelay_max; /* Maximum amount of time to wait before sending the data. */ gensio_time send_time; /* When using chardelay, the time when we will send the data, no matter what, set by chardelay_max. */ /* Information about the network port. */ char *name; /* The name given for the port. */ char *accstr; /* The accepter string. */ struct gensio_accepter *accepter; /* Used to receive new connections. */ bool accepter_stopped; struct port_remaddr *remaddrs; /* Remote addresses allowed. */ struct port_remaddr *connbacks; /* Connect back addresses */ unsigned int num_waiting_connect_backs; unsigned int connback_timeout; bool connback_timeout_set; unsigned int max_connections; /* Maximum number of connections we can accept at a time for this port. */ net_info_t *netcons; gensiods dev_bytes_received; /* Number of bytes read from the device. */ gensiods dev_bytes_sent; /* Number of bytes written to the device. */ /* * Informationd use when transferring information from the network * port to the terminal device. */ int net_to_dev_state; /* State of transferring data from the network port to the device. */ struct gbuf net_to_dev; /* Buffer for network to dev transfers. */ struct controller_info *net_monitor; /* If non-null, send any input received from the network port to this controller port. */ struct gbuf *devstr; /* Outgoing string */ /* * Information used when transferring information from the * terminal device to the network port. */ int dev_to_net_state; /* State of transferring data from the device to the network port. */ struct gbuf dev_to_net; /* * We have called shutdown_port but the accepter has not yet been * read disabled. */ bool shutdown_started; struct controller_info *dev_monitor; /* If non-null, send any input received from the device to this controller port. */ struct port_info *next; /* Used to keep a linked list of these. */ /* * The port was reconfigured but had pending users. This holds the * new config until the pending users have finished. */ struct port_info *new_config; char *rs485; /* If not NULL, rs485 was specified. */ /* For RFC 2217 */ unsigned char last_modemstate; unsigned char last_linestate; /* Allow RFC 2217 mode */ bool allow_2217; /* Send a break if we get a sync command? */ bool telnet_brk_on_sync; /* kickolduser mode */ bool kickolduser_mode; /* Banner to display at startup, or NULL if none. */ char *bannerstr; /* RFC 2217 signature. */ char *signaturestr; /* String to send to device at startup, or NULL if none. */ char *openstr; /* String to send to device at close, or NULL if none. */ char *closestr; /* * Close on string to shutdown connection when received from * serial side, or NULL if none. */ char *closeon; gensiods closeon_pos; gensiods closeon_len; /* * File to read/write trace, NULL if none. If the same, then * trace information is in the same file, only one open is done. */ trace_info_t trace_read; trace_info_t trace_write; trace_info_t trace_both; /* * Pointers to the above, that way if two are the same file we can just * set up one and point both to it. */ trace_info_t *tr; trace_info_t *tw; trace_info_t *tb; char *devname; struct gensio *io; /* For handling I/O operation to the device */ bool io_open; void (*dev_write_handler)(port_info_t *); /* * devname as specified on the line, not the substituted version. Only * non-null if devname was substituted. */ char *orig_devname; /* * LED to flash for serial traffic */ struct led_s *led_tx; struct led_s *led_rx; /* * Directory that has authentication info. */ char *authdir; /* * Enable PAM authentication */ char *pamauth; /* * List of authorized users. If NULL, all users are authorized. * If no allowed users are specified, the default is taken. */ struct gensio_list *allowed_users; char *default_allowed_users; /* * Delimiter for sending. */ char *sendon; gensiods sendon_pos; gensiods sendon_len; #ifdef DO_MDNS struct mdns_info mdns_info; #endif }; /* In dataxfer.c */ void handle_new_net(port_info_t *port, struct gensio *net, net_info_t *netcon); int handle_dev_event(struct gensio *io, void *user_data, int event, int err, unsigned char *buf, gensiods *buflen, const char *const *auxdata); int port_dev_enable(port_info_t *port); int gbuf_write(port_info_t *port, struct gbuf *buf); void report_disconnect(port_info_t *port, net_info_t *netcon); void port_send_timeout(struct gensio_timer *timer, void *data); /* In port.c */ extern struct gensio_lock *ports_lock; extern port_info_t *ports; extern port_info_t *new_ports; extern port_info_t *new_ports_end; net_info_t *first_live_net_con(port_info_t *port); bool port_in_use(port_info_t *port); int is_device_already_inuse(port_info_t *check_port); int num_connected_net(port_info_t *port); gensiods net_raddr(struct gensio *io, struct sockaddr_storage *addr, gensiods *socklen); void reset_timer(net_info_t *netcon); #define for_each_connection(port, netcon) \ for (netcon = port->netcons; \ netcon < &(port->netcons[port->max_connections]); \ netcon++) void shutdown_one_netcon(net_info_t *netcon, const char *reason); int dataxfer_setup_port(port_info_t *new_port, struct absout *eout); int startup_port(struct absout *eout, port_info_t *port); int shutdown_port(port_info_t *port, const char *errreason); void port_start_timer(port_info_t *port); /* In portconfig.c */ bool remaddr_check(const struct port_remaddr *list, const struct sockaddr *addr, socklen_t len); void remaddr_list_free(struct port_remaddr *list); void free_port(port_info_t *port); /* In ser2net_str.c */ struct gbuf *process_str_to_buf(port_info_t *port, net_info_t *netcon, const char *str, struct absout *eout); char *process_str_to_str(port_info_t *port, net_info_t *netcon, const char *str, timev *ts, gensiods *lenrv, int isfilename, struct absout *eout); gensiods net_raddr_str(struct gensio *io, char *buf, gensiods buflen); /* In rotator.c */ void shutdown_rotators(void); int init_rotators(void); /* In trace.c */ void header_trace(port_info_t *port, net_info_t *netcon); void footer_trace(port_info_t *port, char *type, const char *reason); void do_trace(port_info_t *port, trace_info_t *t, const unsigned char *buf, gensiods buf_len, const char *prefix); void setup_trace(port_info_t *port, struct absout *eout); void shutdown_trace(port_info_t *port); /* In addsyattrs.c */ void add_sys_attrs(struct absout *eout, const char *portname, const char *devname, const char ***txt, gensiods *args, gensiods *argc); #endif /* PORT */ ser2net-4.6.2/portconfig.c000066400000000000000000000570571461221161700154420ustar00rootroot00000000000000/* * ser2net - A program for allowing telnet connection to serial ports * Copyright (C) 2020 Corey Minyard * * SPDX-License-Identifier: GPL-2.0-only * * In addition, as a special exception, the copyright holders of * ser2net give you permission to combine ser2net with free software * programs or libraries that are released under the GNU LGPL and * with code included in the standard release of OpenSSL under the * OpenSSL license (or modified versions of such code, with unchanged * license). You may copy and distribute such a system following the * terms of the GNU GPL for ser2net and the licenses of the other code * concerned, provided that you include the source code of that * other code when and as the GNU GPL requires distribution of source * code. * * Note that people who make modified versions of ser2net are not * obligated to grant this special exception for their modified * versions; it is their choice whether to do so. The GNU General * Public License gives permission to release a modified version * without this exception; this exception also makes it possible to * release a modified version which carries forward this exception. */ #include #include #include #include #include #include #include "ser2net.h" #include "port.h" #include "absout.h" #include "readconfig.h" #include "defaults.h" #include "led.h" #ifdef gensio_version_major /* When the version info was added, the type was changed. */ typedef struct gensio_addr gaddrinfo; #define gensio_free_addrinfo(o, a) gensio_addr_free(a) #else typedef struct addrinfo gaddrinfo; #endif /* * This infrastructure allows a list of addresses to be kept. This is * for checking remote addresses */ struct port_remaddr { char *str; gaddrinfo *ai; bool is_port_set; struct port_remaddr *next; }; /* Add a remaddr to the given list, return 0 on success or errno on fail. */ static int remaddr_append(struct port_remaddr **list, struct port_remaddr **cblist, const char *str, bool is_connect_back) { struct port_remaddr *r = NULL, *r2, *rcb = NULL; gaddrinfo *ai = NULL; bool is_port_set = false; int err = 0; if (!is_connect_back) { if (*str == '!') { str++; is_connect_back = true; } #ifdef gensio_version_major err = gensio_scan_network_port(so, str, false, &ai, NULL, &is_port_set, NULL, NULL); #else int socktype, protocol; err = gensio_scan_network_port(so, str, false, &ai, &socktype, &protocol, &is_port_set, NULL, NULL); #endif if (err) return err; /* FIXME - We currently ignore the protocol. */ r = malloc(sizeof(*r)); if (!r) { err = GE_NOMEM; goto out; } memset(r, 0, sizeof(*r)); r->str = strdup(str); if (!r->str) { free(r); r = NULL; err = GE_NOMEM; goto out; } r->ai = ai; ai = NULL; r->is_port_set = is_port_set; r->next = NULL; r2 = *list; if (!r2) { *list = r; } else { while (r2->next) r2 = r2->next; r2->next = r; } } if (is_connect_back) { rcb = malloc(sizeof(*rcb)); if (!rcb) { err = GE_NOMEM; goto out; } memset(rcb, 0, sizeof(*rcb)); rcb->str = strdup(str); if (!rcb->str) { err = GE_NOMEM; goto out; } rcb->next = NULL; r2 = *cblist; if (!r2) { *cblist = rcb; } else { while (r2->next) r2 = r2->next; r2->next = rcb; } } out: if (err) { if (r) { if (r->str) free(r->str); if (r->ai) gensio_free_addrinfo(so, r->ai); free(r); } if (rcb) /* rcb->str cannot be set, the last failure is its allocation. */ free(rcb); if (ai) gensio_free_addrinfo(so, ai); } return err; } static bool ai_check(gaddrinfo *ai, const struct sockaddr *addr, socklen_t len, bool is_port_set) { #ifdef gensio_version_major return gensio_addr_addr_present(ai, addr, len, is_port_set); #else while (ai) { if (gensio_sockaddr_equal(addr, len, ai->ai_addr, ai->ai_addrlen, is_port_set)) return true; ai = ai->ai_next; } return false; #endif } /* Check that the given address matches something in the list. */ bool remaddr_check(const struct port_remaddr *list, const struct sockaddr *addr, socklen_t len) { const struct port_remaddr *r = list; if (!r) return true; for (; r; r = r->next) { if (ai_check(r->ai, addr, len, r->is_port_set)) return true; } return false; } void remaddr_list_free(struct port_remaddr *list) { struct port_remaddr *r; while (list) { r = list; list = r->next; if (r->ai) gensio_free_addrinfo(so, r->ai); free(r->str); free(r); } } static int port_add_remaddr(struct absout *eout, port_info_t *port, const char *istr) { char *str; char *strtok_data; char *remstr; int err = 0; str = strdup(istr); if (!str) { eout->out(eout, "Out of memory handling remote address '%s'", istr); return GE_NOMEM; } remstr = strtok_r(str, ";", &strtok_data); /* Note that we ignore an empty remaddr. */ while (remstr && *remstr) { err = remaddr_append(&port->remaddrs, &port->connbacks, remstr, false); if (err) { eout->out(eout, "Error adding remote address '%s': %s\n", remstr, gensio_err_to_str(err)); break; } remstr = strtok_r(NULL, ";", &strtok_data); } free(str); return err; } static int port_add_connback(struct absout *eout, port_info_t *port, const char *istr) { char *str; char *strtok_data; char *remstr; int err = 0; str = strdup(istr); if (!str) { eout->out(eout, "Out of memory handling connect back address '%s'", istr); return GE_NOMEM; } remstr = strtok_r(str, ";", &strtok_data); /* Note that we ignore an empty remaddr. */ while (remstr && *remstr) { err = remaddr_append(NULL, &port->connbacks, remstr, true); if (err) { eout->out(eout, "Error adding connect back address '%s': %s\n", remstr, gensio_err_to_str(err)); break; } remstr = strtok_r(NULL, ";", &strtok_data); } free(str); return err; } /* Must be called with port->lock held. */ static void finish_free_port(port_info_t *port) { assert(port->free_count > 0); port->free_count--; if (port->free_count != 0) { so->unlock(port->lock); return; } so->unlock(port->lock); so->free_lock(port->lock); remaddr_list_free(port->remaddrs); remaddr_list_free(port->connbacks); if (port->accepter) gensio_acc_free(port->accepter); if (port->dev_to_net.buf) free(port->dev_to_net.buf); if (port->net_to_dev.buf) free(port->net_to_dev.buf); if (port->timer) so->free_timer(port->timer); if (port->send_timer) so->free_timer(port->send_timer); if (port->runshutdown) so->free_runner(port->runshutdown); if (port->io) gensio_free(port->io); if (port->trace_read.filename) free(port->trace_read.filename); if (port->trace_write.filename) free(port->trace_write.filename); if (port->trace_both.filename) free(port->trace_both.filename); if (port->devname) free(port->devname); if (port->name) free(port->name); if (port->accstr) free(port->accstr); if (port->new_config) free_port(port->new_config); if (port->bannerstr) free(port->bannerstr); if (port->signaturestr) free(port->signaturestr); if (port->authdir) free(port->authdir); free_user_list(port->allowed_users); if (port->default_allowed_users) free(port->default_allowed_users); if (port->openstr) free(port->openstr); if (port->closestr) free(port->closestr); if (port->closeon) free(port->closeon); if (port->netcons) free(port->netcons); if (port->orig_devname) free(port->orig_devname); if (port->sendon) free(port->sendon); if (port->led_rx) free_led(port->led_rx); if (port->led_tx) free_led(port->led_tx); #ifdef DO_MDNS mdns_shutdown(&port->mdns_info); #endif /* DO_MDNS */ free(port); } static void gen_timer_shutdown_done(struct gensio_timer *timer, void *cb_data) { port_info_t *port = cb_data; so->lock(port->lock); finish_free_port(port); /* Releases lock */ } void free_port(port_info_t *port) { net_info_t *netcon; int err; if (port->netcons) { for_each_connection(port, netcon) { char *err = "Port was deleted\n\r"; if (netcon->new_net) { gensio_write(netcon->new_net, NULL, err, strlen(err), NULL); gensio_free(netcon->new_net); } } } so->lock(port->lock); port->free_count = 1; /* Make sure all the timers are stopped. */ if (port->send_timer) { err = so->stop_timer_with_done(port->send_timer, gen_timer_shutdown_done, port); if (err != GE_TIMEDOUT) port->free_count++; } if (port->timer) { err = so->stop_timer_with_done(port->timer, gen_timer_shutdown_done, port); if (err != GE_TIMEDOUT) port->free_count++; } finish_free_port(port); /* Releases lock */ } static int strdupcat(char **str, const char *cat) { char *s = malloc(strlen(*str) + strlen(cat) + 2); if (!s) return GE_NOMEM; strcpy(s, *str); strcat(s, ","); strcat(s, cat); free(*str); *str = s; return 0; } static int check_keyvalue_default(const char *str, const char *name, const char **value, const char *def) { if (strcmp(str, name) == 0) *value = def; else return gensio_check_keyvalue(str, name, value); return 1; } static int update_str_val(const char *str, char **outstr, const char *name, struct absout *eout) { char *fval = strdup(str); if (!fval) { eout->out(eout, "Out of memory allocating %s", name); return -1; } if (*outstr) free(*outstr); *outstr = fval; return 0; } static int myconfig(port_info_t *port, struct absout *eout, const char *pos) { char *fval; const char *val; int rv; if (gensio_check_keybool(pos, "kickolduser", &port->kickolduser_mode) > 0) { } else if (gensio_check_keybool(pos, "trace-hexdump", &port->trace_read.hexdump) > 0) { port->trace_write.hexdump = port->trace_read.hexdump; port->trace_both.hexdump = port->trace_read.hexdump; } else if (gensio_check_keybool(pos, "trace-timestamp", &port->trace_read.timestamp) > 0) { port->trace_write.timestamp = port->trace_read.timestamp; port->trace_both.timestamp = port->trace_read.timestamp; } else if (gensio_check_keybool(pos, "trace-read-hexdump", &port->trace_read.hexdump) > 0) { } else if (gensio_check_keybool(pos, "trace-read-timestamp", &port->trace_read.timestamp) > 0) { } else if (gensio_check_keybool(pos, "trace-write-hexdump", &port->trace_write.hexdump) > 0) { } else if (gensio_check_keybool(pos, "trace-write-timestamp", &port->trace_write.timestamp) > 0) { } else if (gensio_check_keybool(pos, "trace-both-hexdump", &port->trace_both.hexdump) > 0) { } else if (gensio_check_keybool(pos, "trace-both-timestamp", &port->trace_both.timestamp) > 0) { } else if (gensio_check_keyvalue(pos, "trace-read", &val) > 0) { /* trace read, data from the port to the socket */ if (update_str_val(val, &port->trace_read.filename, "trace-read", eout)) return -1; } else if (gensio_check_keyvalue(pos, "trace-write", &val) > 0) { /* trace write, data from the socket to the port */ if (update_str_val(val, &port->trace_write.filename, "trace-write", eout)) return -1; } else if (gensio_check_keyvalue(pos, "trace-both", &val) > 0) { /* trace both directions. */ if (update_str_val(val, &port->trace_both.filename, "trace-both", eout)) return -1; } else if (gensio_check_keyvalue(pos, "led-rx", &val) > 0) { /* LED for UART RX traffic */ port->led_rx = find_led(val); if (!port->led_rx) { eout->out(eout, "Could not find led-rx LED: %s", val); return -1; } } else if (gensio_check_keyvalue(pos, "led-tx", &val) > 0) { /* LED for UART TX traffic */ port->led_tx = find_led(val); if (!port->led_tx) { eout->out(eout, "Could not find led-tx LED: %s", val); return -1; } } else if (gensio_check_keybool(pos, "telnet-brk-on-sync", &port->telnet_brk_on_sync) > 0) { } else if (gensio_check_keybool(pos, "chardelay", &port->enable_chardelay) > 0) { } else if (gensio_check_keybool(pos, "no-con-to-acc", &port->no_dev_to_net) > 0) { } else if (gensio_check_keybool(pos, "no-acc-to-con", &port->no_net_to_dev) > 0) { } else if (gensio_check_keyuint(pos, "chardelay-scale", &port->chardelay_scale) > 0) { } else if (gensio_check_keyuint(pos, "chardelay-min", &port->chardelay_min) > 0) { } else if (gensio_check_keyuint(pos, "chardelay-max", &port->chardelay_max) > 0) { } else if (gensio_check_keyds(pos, "dev-to-net-bufsize", &port->dev_to_net.maxsize) > 0) { if (port->dev_to_net.maxsize < 2) port->dev_to_net.maxsize = 2; } else if (gensio_check_keyds(pos, "net-to-dev-bufsize", &port->net_to_dev.maxsize) > 0) { if (port->net_to_dev.maxsize < 2) port->net_to_dev.maxsize = 2; } else if (gensio_check_keyuint(pos, "max-connections", &port->max_connections) > 0) { if (port->max_connections < 1) port->max_connections = 1; } else if (gensio_check_keyuint(pos, "accepter-retry-time", &port->accepter_retry_time) > 0) { if (port->accepter_retry_time < 1) port->accepter_retry_time = 1; } else if (gensio_check_keyuint(pos, "connector-retry-time", &port->connector_retry_time) > 0) { if (port->connector_retry_time < 1) port->connector_retry_time = 1; } else if (gensio_check_keyvalue(pos, "authdir", &val) > 0) { fval = strdup(val); if (!fval) { eout->out(eout, "Out of memory allocating authdir"); return -1; } if (port->authdir) free(port->authdir); port->authdir = fval; } else if (gensio_check_keyvalue(pos, "pamauth", &val) > 0) { fval = strdup(val); if (!fval) { eout->out(eout, "Out of memory allocating pamauth"); return -1; } if (port->pamauth) free(port->pamauth); port->pamauth = fval; } else if (gensio_check_keyvalue(pos, "allowed-users", &val) > 0) { rv = add_allowed_users(&port->allowed_users, val, eout); if (rv) return -1; } else if (gensio_check_keyvalue(pos, "remaddr", &val) > 0) { rv = port_add_remaddr(eout, port, val); if (rv) return -1; } else if (gensio_check_keyvalue(pos, "connback", &val) > 0) { rv = port_add_connback(eout, port, val); if (rv) return -1; } else if (gensio_check_keyuint(pos, "connback-timeout", &port->connback_timeout) > 0) { port->connback_timeout_set = true; } else if (check_keyvalue_default(pos, "banner", &val, "") > 0) { fval = strdup(val); if (!fval) { eout->out(eout, "Out of memory allocating banner"); return -1; } if (port->bannerstr) free(port->bannerstr); port->bannerstr = fval; } else if (check_keyvalue_default(pos, "openstr", &val, "") > 0) { fval = strdup(val); if (!fval) { eout->out(eout, "Out of memory allocating openstr"); return -1; } if (port->openstr) free(port->openstr); port->openstr = fval; } else if (check_keyvalue_default(pos, "closestr", &val, "") > 0) { fval = strdup(val); if (!fval) { eout->out(eout, "Out of memory allocating closestr"); return -1; } if (port->closestr) free(port->closestr); port->closestr = fval; } else if (gensio_check_keyvalue(pos, "closeon", &val) > 0) { struct timeval tv = { 0, 0 }; gensiods len; fval = process_str_to_str(port, NULL, val, &tv, &len, false, eout); if (!fval) { eout->out(eout, "Out of memory allocating closeon"); return -1; } if (port->closeon) free(port->closeon); port->closeon = fval; port->closeon_len = len; } else if (check_keyvalue_default(pos, "signature", &val, "") > 0) { fval = strdup(val); if (!fval) { eout->out(eout, "Out of memory banner"); return -1; } if (port->signaturestr) free(port->signaturestr); port->signaturestr = fval; } else if (gensio_check_keyvalue(pos, "sendon", &val) > 0) { struct timeval tv = { 0, 0 }; gensiods len; fval= process_str_to_str(port, NULL, val, &tv, &len, false, eout); if (!fval) { eout->out(eout, "Out of memory allocating sendon"); return -1; } if (port->sendon) free(port->sendon); port->sendon = fval; port->sendon_len = len; #ifdef DO_MDNS } else if (mdns_checkoption(pos, &port->mdns_info, port->name, eout) > 0) { #endif /* DO_MDNS */ } else { eout->out(eout, "Unknown config item: %s", pos); return -1; } return 0; } static void process_connect_back(struct absout *eout, port_info_t *port, struct port_remaddr *r) { net_info_t *netcon; for_each_connection(port, netcon) { if (netcon->remote_fixed) continue; netcon->remote_fixed = true; netcon->remote_str = r->str; netcon->connect_back = true; return; } if (eout) eout->out(eout, "Too many connect back remote addresses specified" " for the max-connections given"); } static int init_port_data(port_info_t *port, struct absout *eout) { port->enabled = false; port->net_to_dev_state = PORT_CLOSED; port->dev_to_net_state = PORT_CLOSED; port->trace_read.f = NULL; port->trace_write.f = NULL; port->trace_both.f = NULL; port->telnet_brk_on_sync = find_default_bool("telnet-brk-on-sync"); port->kickolduser_mode = find_default_bool("kickolduser"); port->enable_chardelay = find_default_int("chardelay"); port->chardelay_scale = find_default_int("chardelay-scale"); port->chardelay_min = find_default_int("chardelay-min"); port->chardelay_max = find_default_int("chardelay-max"); port->dev_to_net.maxsize = find_default_int("dev-to-net-bufsize"); port->net_to_dev.maxsize = find_default_int("net-to-dev-bufsize"); port->max_connections = find_default_int("max-connections"); port->connector_retry_time = find_default_int("connector-retry-time"); port->accepter_retry_time = find_default_int("accepter-retry-time"); if (find_default_str("authdir", &port->authdir)) return GE_NOMEM; if (find_default_str("pamauth", &port->pamauth)) return GE_NOMEM; if (find_default_str("allowed-users", &port->default_allowed_users)) return GE_NOMEM; if (find_default_str("signature", &port->signaturestr)) return GE_NOMEM; if (find_default_str("banner", &port->bannerstr)) return GE_NOMEM; if (find_default_str("openstr", &port->openstr)) return GE_NOMEM; if (find_default_str("closestr", &port->closestr)) return GE_NOMEM; if (find_default_str("closeon", &port->closeon)) return GE_NOMEM; if (find_default_str("sendon", &port->sendon)) return GE_NOMEM; port->led_tx = NULL; port->led_rx = NULL; #ifdef DO_MDNS mdns_info_getdefaults(&port->mdns_info, port->name, eout); /* The bool is not defaulted in getdefaults. */ port->mdns_info.mdns = find_default_bool("mdns"); #endif /* DO_MDNS */ return 0; } /* Create a port based on a set of parameters passed in. */ int portconfig(struct absout *eout, const char *name, const char *accstr, const char *state, unsigned int timeout, const char *devname, const char * const *devcfg) { port_info_t *new_port, *curr; net_info_t *netcon; int err; bool write_only = false; unsigned int i; struct port_remaddr *r; so->lock(ports_lock); curr = new_ports; while (curr) { if (strcmp(curr->name, name) == 0) { /* We don't allow duplicate names. */ so->unlock(ports_lock); eout->out(eout, "Duplicate connection name: %s", name); return -1; } curr = curr->next; } so->unlock(ports_lock); new_port = malloc(sizeof(port_info_t)); if (new_port == NULL) { eout->out(eout, "Could not allocate a port data structure"); return -1; } memset(new_port, 0, sizeof(*new_port)); new_port->lock = so->alloc_lock(so); if (!new_port->lock) { eout->out(eout, "Could not allocate lock"); goto errout; } new_port->devname = strdup(devname); if (!new_port->devname) { eout->out(eout, "unable to allocate device name"); goto errout; } err = init_port_data(new_port, eout); if (err) goto errout; if (!new_port->name) { new_port->name = strdup(name); if (!new_port->name) { eout->out(eout, "unable to allocate port name"); goto errout; } } if (!new_port->accstr) { new_port->accstr = strdup(accstr); if (!new_port->accstr) { eout->out(eout, "unable to allocate port accepter string"); goto errout; } } if (strcmp(state, "on") == 0) { new_port->enabled = true; } else if (strcmp(state, "raw") == 0) { new_port->enabled = true; } else if (strcmp(state, "rawlp") == 0) { /* FIXME - remove this someday. */ new_port->enabled = true; write_only = true; } else if (strcmp(state, "telnet") == 0) { /* FIXME - remove this someday. */ new_port->enabled = true; new_port->do_telnet = true; } else if (strcmp(state, "off") == 0) { new_port->enabled = false; } else { eout->out(eout, "state was invalid"); goto errout; } new_port->timeout = timeout; for (i = 0; devcfg[i]; i++) { err = myconfig(new_port, eout, devcfg[i]); if (err) goto errout; } if (write_only) { err = strdupcat(&new_port->devname, "WRONLY"); if (err) { eout->out(eout, "Out of memory appending to devname"); goto errout; } } if (new_port->rs485) { err = strdupcat(&new_port->devname, "rs485="); if (!err) err = strdupcat(&new_port->devname, new_port->rs485); if (err) { eout->out(eout, "Out of memory appending to devname"); goto errout; } } if (!new_port->allowed_users && new_port->default_allowed_users) { err = add_allowed_users(&new_port->allowed_users, new_port->default_allowed_users, eout); if (err) goto errout; } if (new_port->default_allowed_users) { free(new_port->default_allowed_users); new_port->default_allowed_users = NULL; } if (dataxfer_setup_port(new_port, eout)) goto errout; if (gbuf_init(&new_port->dev_to_net, new_port->dev_to_net.maxsize)) { eout->out(eout, "Could not allocate dev to net buffer"); goto errout; } if (gbuf_init(&new_port->net_to_dev, new_port->net_to_dev.maxsize)) { eout->out(eout, "Could not allocate net to dev buffer"); goto errout; } /* * Don't handle the remaddr/connect back defaults until here, we * don't want to mess with it if the user has set it, because the * user may set it to an empty string. */ if (!new_port->remaddrs) { char *remaddr; if (find_default_str("remaddr", &remaddr)) { eout->out(eout, "Out of memory processing default remote address"); } else if (remaddr) { err = port_add_remaddr(eout, new_port, remaddr); free(remaddr); if (err) goto errout; } } if (!new_port->connbacks) { char *remaddr; if (find_default_str("connback", &remaddr)) { eout->out(eout, "Out of memory processing default connect back " "address"); } else if (remaddr) { err = port_add_connback(eout, new_port, remaddr); free(remaddr); if (err) goto errout; } } new_port->netcons = malloc(sizeof(net_info_t) * new_port->max_connections); if (new_port->netcons == NULL) { eout->out(eout, "Could not allocate a port data structure"); goto errout; } memset(new_port->netcons, 0, sizeof(net_info_t) * new_port->max_connections); for_each_connection(new_port, netcon) netcon->port = new_port; for (r = new_port->connbacks; r; r = r->next) process_connect_back(eout, new_port, r); /* Link it on the end of new_ports for now. */ if (new_ports_end) new_ports_end->next = new_port; else new_ports = new_port; new_ports_end = new_port; return 0; errout: free_port(new_port); return -1; } ser2net-4.6.2/portinfo.c000066400000000000000000000376761461221161700151350ustar00rootroot00000000000000/* * ser2net - A program for allowing telnet connection to serial ports * Copyright (C) 2001-2020 Corey Minyard * * SPDX-License-Identifier: GPL-2.0-only * * In addition, as a special exception, the copyright holders of * ser2net give you permission to combine ser2net with free software * programs or libraries that are released under the GNU LGPL and * with code included in the standard release of OpenSSL under the * OpenSSL license (or modified versions of such code, with unchanged * license). You may copy and distribute such a system following the * terms of the GNU GPL for ser2net and the licenses of the other code * concerned, provided that you include the source code of that * other code when and as the GNU GPL requires distribution of source * code. * * Note that people who make modified versions of ser2net are not * obligated to grant this special exception for their modified * versions; it is their choice whether to do so. The GNU General * Public License gives permission to release a modified version * without this exception; this exception also makes it possible to * release a modified version which carries forward this exception. */ #include #include #include #ifndef GENSIO_ACONTROL_SER_RTS #include #endif #include "ser2net.h" #include "dataxfer.h" #include "port.h" static int cntrl_absverrout(struct absout *e, const char *str, va_list ap) { struct controller_info *cntlr = e->data; char buf[1024]; vsnprintf(buf, sizeof(buf), str, ap); return controller_outputf(cntlr, "error", "%s", buf); } static int cntrl_abserrout(struct absout *e, const char *str, ...) { struct controller_info *cntlr = e->data; va_list ap; char buf[1024]; va_start(ap, str); vsnprintf(buf, sizeof(buf), str, ap); va_end(ap); return controller_outputf(cntlr, "error", "%s", buf); } #define REMOTEADDR_COLUMN_WIDTH \ (INET6_ADDRSTRLEN - 1 /* terminating NUL */ + 1 /* comma */ + 5 /* strlen("65535") */) char *state_str[] = { "not started", "closed", "unconnected", "waiting input", "waiting output", "closing" }; char *enabled_str[] = { "off", "on" }; /* Print information about a port to the control port given in cntlr. */ static void showshortport(struct controller_info *cntlr, port_info_t *port) { char buffer[NI_MAXHOST + NI_MAXSERV + 2]; int count = 0; net_info_t *netcon = NULL; controller_outputf(cntlr, NULL, "%-22s ", port->name); if (port->deleted) controller_outputf(cntlr, NULL, "%-6s ", "DEL"); else controller_outputf(cntlr, NULL, "%-6s ", enabled_str[port->enabled]); controller_outputf(cntlr, NULL, "%7d ", port->timeout); netcon = first_live_net_con(port); if (!netcon) netcon = &(port->netcons[0]); if (port_in_use(port)) { if (net_raddr_str(netcon->net, buffer, sizeof(buffer)) != 0) count = controller_outputf(cntlr, NULL, "%s", buffer); } else { count = controller_outputf(cntlr, NULL, "unconnected"); } while (count < REMOTEADDR_COLUMN_WIDTH + 1) { controller_outs(cntlr, NULL, " "); count++; } controller_outputf(cntlr, NULL, "%-22s ", port->accstr); controller_outputf(cntlr, NULL, "%-22s ", port->devname); controller_outputf(cntlr, NULL, "%-14s ", state_str[port->net_to_dev_state]); controller_outputf(cntlr, NULL, "%-14s ", state_str[port->dev_to_net_state]); controller_outputf(cntlr, NULL, "%9lu ", (unsigned long) netcon->bytes_received); controller_outputf(cntlr, NULL, "%9lu ", (unsigned long) netcon->bytes_sent); controller_outputf(cntlr, NULL, "%9lu ", (unsigned long)port->dev_bytes_received); controller_outputf(cntlr, NULL, "%9lu ", (unsigned long) port->dev_bytes_sent); if (net_raddr_str(port->io, buffer, sizeof(buffer)) != 0) controller_outputf(cntlr, NULL, "%s", buffer); controller_outs(cntlr, NULL, "\r\n"); } /* Print information about a port to the control port given in cntlr. */ static void showport(struct controller_info *cntlr, port_info_t *port, bool yaml) { char buffer[NI_MAXHOST + NI_MAXSERV + 2], *cfg, *oth = NULL, *tstr; net_info_t *netcon; if (yaml) { controller_outs(cntlr, "port", NULL); controller_indent(cntlr, 1); controller_outputf(cntlr, "name", "%s", port->name); } else { controller_outputf(cntlr, "port", "%s", port->name); controller_indent(cntlr, 1); } controller_outputf(cntlr, "accepter", "%s", port->accstr); controller_outputf(cntlr, "enable state", "%s", enabled_str[port->enabled]); controller_outputf(cntlr, "timeout", "%d", port->timeout); for_each_connection(port, netcon) { if (netcon->net) { buffer[0] = '\0'; net_raddr_str(netcon->net, buffer, sizeof(buffer)); if (yaml) { controller_outs(cntlr, "connected", NULL); controller_indent(cntlr, 1); controller_outputf(cntlr, "name", "%s", buffer); } else { controller_outputf(cntlr, "connected to", "%s", buffer); controller_indent(cntlr, 1); } controller_outputf(cntlr, "bytes read from TCP", "%lu", (unsigned long) netcon->bytes_received); controller_outputf(cntlr, "bytes written to TCP", "%lu", (unsigned long) netcon->bytes_sent); controller_indent(cntlr, -1); } } if (port->orig_devname) controller_outputf(cntlr, "device", "%s (%s)", port->devname, port->orig_devname); else controller_outputf(cntlr, "device", "%s", port->devname); if (net_raddr_str(port->io, buffer, sizeof(buffer)) != 0) { cfg = strchr(buffer, ','); if (cfg) { cfg++; oth = strchr(cfg, ' '); } else { cfg = ""; oth = strchr(buffer, ' '); } if (oth) { *oth = '\0'; oth++; tstr = oth; while (*tstr) { if (*tstr == ' ') *tstr = ','; tstr++; } } else { oth = ""; } if (cfg[0]) controller_outputf(cntlr, "device config", "%s", cfg); if (oth[0] && strcmp(oth, "offline") != 0) { if (yaml) controller_outputf(cntlr, "device controls", "[ %s ]", oth); else controller_outputf(cntlr, "device controls", "%s", oth); } } else { controller_outputf(cntlr, "device config", "?"); controller_outputf(cntlr, "device controls", "?"); } controller_outputf(cntlr, "tcp to device state", "%s", state_str[port->net_to_dev_state]); controller_outputf(cntlr, "device to tcp state", "%s", state_str[port->dev_to_net_state]); controller_outputf(cntlr, "bytes read from device", "%lu", (unsigned long) port->dev_bytes_received); controller_outputf(cntlr, "bytes written to device", "%lu", (unsigned long) port->dev_bytes_sent); if (!yaml) { if (port->new_config != NULL) { controller_outputf(cntlr, NULL, "Port will be reconfigured when current" " session closes.\r\n"); } else if (port->deleted) { controller_outputf(cntlr, NULL, "Port will be deleted when current" " session closes.\r\n"); } } else { char *infostr = "retained"; if (port->new_config != NULL) infostr = "reconfigured"; else if (port->deleted) infostr = "deleted"; controller_outputf(cntlr, "close state", "%s", infostr); } controller_indent(cntlr, -1); } /* * Find a port data structure given a port name. Returns with port->lock * held, if it returns a non-NULL port. */ static port_info_t * find_port_by_name(const char *name, bool allow_deleted) { port_info_t *port; so->lock(ports_lock); port = ports; while (port != NULL) { if (strcmp(name, port->name) == 0) { so->lock(port->lock); so->unlock(ports_lock); if (port->deleted && !allow_deleted) { so->unlock(port->lock); return NULL; } return port; } port = port->next; } so->unlock(ports_lock); return NULL; } /* Handle a showport command from the control port. */ void showports(struct controller_info *cntlr, const char *portspec, bool yaml) { port_info_t *port; if (portspec == NULL) { so->lock(ports_lock); /* Dump everything. */ port = ports; while (port != NULL) { so->lock(port->lock); showport(cntlr, port, yaml); so->unlock(port->lock); port = port->next; } so->unlock(ports_lock); } else { port = find_port_by_name(portspec, true); if (port == NULL) { controller_outputf(cntlr, "error", "Invalid port number - %s", portspec); } else { showport(cntlr, port, yaml); so->unlock(port->lock); } } } /* Handle a showport command from the control port. */ void showshortports(struct controller_info *cntlr, const char *portspec) { port_info_t *port; controller_outputf(cntlr, NULL, "%-22s %-6s %7s %-*s %-22s %-22s %-14s %-14s %9s %9s %9s %9s %s\r\n", "Port name", "Type", "Timeout", REMOTEADDR_COLUMN_WIDTH, "Remote address", "Accepter", "Device", "TCP to device", "Device to TCP", "TCP in", "TCP out", "Dev in", "Dev out", "State"); if (portspec == NULL) { so->lock(ports_lock); /* Dump everything. */ port = ports; while (port != NULL) { so->lock(port->lock); showshortport(cntlr, port); so->unlock(port->lock); port = port->next; } so->unlock(ports_lock); } else { port = find_port_by_name(portspec, true); if (port == NULL) { controller_outputf(cntlr, "error", "Invalid port number: %s", portspec); } else { showshortport(cntlr, port); so->unlock(port->lock); } } } /* Set the timeout on a port. The port number and timeout are passed in as strings, this code will convert them, return any errors, and perform the operation. */ void setporttimeout(struct controller_info *cntlr, const char *portspec, const char *timeout) { port_info_t *port; net_info_t *netcon; port = find_port_by_name(portspec, true); if (port == NULL) { controller_outputf(cntlr, "error", "Invalid port number - %s", portspec); } else { int timeout_num = scan_int(timeout); if (timeout_num == -1) { controller_outputf(cntlr, "error", "Invalid timeout - %s", timeout); } else { port->timeout = timeout_num; for_each_connection(port, netcon) { if (netcon->net) reset_timer(netcon); } } so->unlock(port->lock); } } /* Modify the controls of a port. The port number and configuration are passed in as strings, this code will get the port and then call the code to control the device. */ void setportcontrol(struct controller_info *cntlr, const char *portspec, char * const controls[]) { port_info_t *port; unsigned int i; port = find_port_by_name(portspec, false); if (port == NULL) { controller_outputf(cntlr, "error", "Invalid port number - %s", portspec); goto out; } else if (!port_in_use(port)) { controller_outputf(cntlr, "error", "Port is not currently connected - %s", portspec); } else { struct gensio *io = port->io; if (!io) goto out_unlock; for (i = 0; controls[i]; i++) { #ifdef GENSIO_ACONTROL_SER_RTS if (strcmp(controls[i], "RTSHI") == 0) gensio_acontrol(io, GENSIO_CONTROL_DEPTH_FIRST, GENSIO_CONTROL_SET, GENSIO_ACONTROL_SER_RTS, "on", 2, NULL, NULL, NULL); else if (strcmp(controls[i], "RTSLO") == 0) gensio_acontrol(io, GENSIO_CONTROL_DEPTH_FIRST, GENSIO_CONTROL_SET, GENSIO_ACONTROL_SER_RTS, "off", 3, NULL, NULL, NULL); else if (strcmp(controls[i], "DTRHI") == 0) gensio_acontrol(io, GENSIO_CONTROL_DEPTH_FIRST, GENSIO_CONTROL_SET, GENSIO_ACONTROL_SER_DTR, "on", 2, NULL, NULL, NULL); else if (strcmp(controls[i], "DTRLO") == 0) gensio_acontrol(io, GENSIO_CONTROL_DEPTH_FIRST, GENSIO_CONTROL_SET, GENSIO_ACONTROL_SER_DTR, "off", 3, NULL, NULL, NULL); #else struct sergensio *sio = gensio_to_sergensio(io); if (!sio) goto out_unlock; if (strcmp(controls[i], "RTSHI") == 0) sergensio_rts(sio, SERGENSIO_RTS_ON, NULL, NULL); else if (strcmp(controls[i], "RTSLO") == 0) sergensio_rts(sio, SERGENSIO_RTS_OFF, NULL, NULL); else if (strcmp(controls[i], "DTRHI") == 0) sergensio_dtr(sio, SERGENSIO_DTR_ON, NULL, NULL); else if (strcmp(controls[i], "DTRLO") == 0) sergensio_dtr(sio, SERGENSIO_DTR_OFF, NULL, NULL); #endif else controller_outputf(cntlr, "error", "Invalid device control - %s", controls[i]); } } out_unlock: so->unlock(port->lock); out: return; } static void port_op_finished(struct port_info *port, void *data) { struct gensio_waiter *waiter = data; so->wake(waiter); } /* Set the enable state of a port. */ void setportenable(struct controller_info *cntlr, const char *portspec, const char *enable) { port_info_t *port; bool new_enable; struct absout eout = { .out = cntrl_abserrout, .vout = cntrl_absverrout, .data = cntlr }; int rv; struct gensio_waiter *waiter = NULL; port = find_port_by_name(portspec, false); if (port == NULL) { controller_outputf(cntlr, "error", "Invalid port - %s", portspec); return; } if (strcmp(enable, "off") == 0) { new_enable = false; } else if (strcmp(enable, "on") == 0) { new_enable = true; } else if (strcmp(enable, "raw") == 0) { new_enable = true; } else { controller_outputf(cntlr, "error", "Invalid enable - %s", enable); goto out_unlock; } if (port->enabled == new_enable) { controller_outputf(cntlr, "error", "port was already in the given state"); goto out_unlock; } port->enabled = new_enable; if (!new_enable) { waiter = so->alloc_waiter(so); if (!waiter) { controller_outputf(cntlr, "error", "Out of memory"); rv = GE_NOMEM; } else { port->port_op_done = port_op_finished; port->port_op_data = waiter; rv = shutdown_port(port, "admin disable"); if (rv) { controller_outputf(cntlr, "error", "disabling port: %s", gensio_err_to_str(rv)); so->free_waiter(waiter); waiter = NULL; } } } else { rv = startup_port(&eout, port); } if (rv) port->enabled = !new_enable; out_unlock: so->unlock(port->lock); if (waiter) { so->wait(waiter, 1, NULL); so->free_waiter(waiter); } } /* Start data monitoring on the given port, type may be either "tcp" or "term" and only one direction may be monitored. This return NULL if the monitor fails. The monitor output will go to "fd". */ void * data_monitor_start(struct controller_info *cntlr, const char *type, const char *portspec) { port_info_t *port; port = find_port_by_name(portspec, true); if (port == NULL) { controller_outputf(cntlr, "error", "Invalid port number - %s", portspec); goto out; } if ((port->net_monitor != NULL) || (port->dev_monitor != NULL)) { controller_outputf(cntlr, "error", "Port is already being monitored"); goto out_unlock; } if (strcmp(type, "tcp") == 0) { port->net_monitor = cntlr; } else if (strcmp(type, "term") == 0) { port->dev_monitor = cntlr; } else { controller_outs(cntlr, "invalid monitor type - %s", type); so->unlock(port->lock); port = NULL; goto out; } out_unlock: so->unlock(port->lock); out: return port; } /* Stop monitoring the given id. */ void data_monitor_stop(struct controller_info *cntlr, void *monitor_id) { port_info_t *port = (port_info_t *) monitor_id; port_info_t *curr; so->lock(ports_lock); curr = ports; while (curr) { if (curr == port) { so->lock(port->lock); port->net_monitor = NULL; port->dev_monitor = NULL; so->unlock(port->lock); break; } curr = curr->next; } so->unlock(ports_lock); } void disconnect_port(struct controller_info *cntlr, const char *portspec) { port_info_t *port; port = find_port_by_name(portspec, true); if (port == NULL) { controller_outputf(cntlr, "error", "Invalid port number - %s", portspec); goto out; } else if (!port_in_use(port)) { controller_outputf(cntlr, "error", "Port not connected - %s", portspec); goto out_unlock; } shutdown_port(port, "admin disconnect"); out_unlock: so->unlock(port->lock); out: return; } ser2net-4.6.2/readconfig.c000066400000000000000000000034511461221161700153560ustar00rootroot00000000000000/* * ser2net - A program for allowing telnet connection to serial ports * Copyright (C) 2001-2020 Corey Minyard * * SPDX-License-Identifier: GPL-2.0-only * * In addition, as a special exception, the copyright holders of * ser2net give you permission to combine ser2net with free software * programs or libraries that are released under the GNU LGPL and * with code included in the standard release of OpenSSL under the * OpenSSL license (or modified versions of such code, with unchanged * license). You may copy and distribute such a system following the * terms of the GNU GPL for ser2net and the licenses of the other code * concerned, provided that you include the source code of that * other code when and as the GNU GPL requires distribution of source * code. * * Note that people who make modified versions of ser2net are not * obligated to grant this special exception for their modified * versions; it is their choice whether to do so. The GNU General * Public License gives permission to release a modified version * without this exception; this exception also makes it possible to * release a modified version which carries forward this exception. */ /* This file holds the code that reads the configuration file and calls the code in dataxfer to actually create all the ports in the configuration file. */ #include #include #include #include #include #include #include #include #include #include "ser2net.h" #include "dataxfer.h" #include "readconfig.h" #include "led.h" #include "defaults.h" int readconfig_init(void) { int err = setup_defaults(); if (err) return err; free_leds(); free_rotators(); return 0; } ser2net-4.6.2/readconfig.h000066400000000000000000000031461461221161700153640ustar00rootroot00000000000000/* * ser2net - A program for allowing telnet connection to serial ports * Copyright (C) 2001-2020 Corey Minyard * * SPDX-License-Identifier: GPL-2.0-only * * In addition, as a special exception, the copyright holders of * ser2net give you permission to combine ser2net with free software * programs or libraries that are released under the GNU LGPL and * with code included in the standard release of OpenSSL under the * OpenSSL license (or modified versions of such code, with unchanged * license). You may copy and distribute such a system following the * terms of the GNU GPL for ser2net and the licenses of the other code * concerned, provided that you include the source code of that * other code when and as the GNU GPL requires distribution of source * code. * * Note that people who make modified versions of ser2net are not * obligated to grant this special exception for their modified * versions; it is their choice whether to do so. The GNU General * Public License gives permission to release a modified version * without this exception; this exception also makes it possible to * release a modified version which carries forward this exception. */ #ifndef READCONFIG #define READCONFIG #include #include "absout.h" #include "fileio.h" /* Initialize for a new config read. */ int readconfig_init(void); /* Read the specified configuration file and call the routine to create the ports. */ int yaml_readconfig(ftype *f, char *filename, char **config_lines, unsigned int num_config_lines, struct absout *errout); #endif /* READCONFIG */ ser2net-4.6.2/reconf000077500000000000000000000003441461221161700143110ustar00rootroot00000000000000#!/bin/sh # If you get this code and you don't have a configure, you can run this # script to create the build infrastructure. case `uname` in Darwin*) glibtoolize ;; *) libtoolize ;; esac aclocal autoconf automake -a ser2net-4.6.2/rotator.c000066400000000000000000000225451461221161700147540ustar00rootroot00000000000000/* * ser2net - A program for allowing telnet connection to serial ports * Copyright (C) 2001-2020 Corey Minyard * * SPDX-License-Identifier: GPL-2.0-only * * In addition, as a special exception, the copyright holders of * ser2net give you permission to combine ser2net with free software * programs or libraries that are released under the GNU LGPL and * with code included in the standard release of OpenSSL under the * OpenSSL license (or modified versions of such code, with unchanged * license). You may copy and distribute such a system following the * terms of the GNU GPL for ser2net and the licenses of the other code * concerned, provided that you include the source code of that * other code when and as the GNU GPL requires distribution of source * code. * * Note that people who make modified versions of ser2net are not * obligated to grant this special exception for their modified * versions; it is their choice whether to do so. The GNU General * Public License gives permission to release a modified version * without this exception; this exception also makes it possible to * release a modified version which carries forward this exception. */ #include #include #include #include #include "ser2net.h" #include "port.h" #include "defaults.h" typedef struct rotator { /* Rotators use the ports_lock for mutex. */ int curr_port; const char **portv; int portc; char *name; char *accstr; struct gensio_accepter *accepter; char *authdir; char *pamauth; struct gensio_list *allowed_users; char *default_allowed_users; unsigned int accepter_retry_time; /* If the rotator fails startup, start time timer to retry it. */ struct gensio_timer *restart_timer; struct rotator *next; } rotator_t; static rotator_t *rotators = NULL; /* Returns with the port locked, if non-NULL. */ static port_info_t * find_rotator_port(const char *portname, struct gensio *net, unsigned int *netconnum) { port_info_t *port = ports; while (port) { if (strcmp(port->name, portname) == 0) { unsigned int i; struct sockaddr_storage addr; gensiods socklen; int err; so->lock(port->lock); if (!port->enabled) goto next; if (port->dev_to_net_state == PORT_CLOSING || port->dev_to_net_state == PORT_CLOSED) goto next; err = net_raddr(net, &addr, &socklen); if (err) goto next; if (!remaddr_check(port->remaddrs, (struct sockaddr *) &addr, socklen)) goto next; if (port->net_to_dev_state == PORT_UNCONNECTED && is_device_already_inuse(port)) goto next; for (i = 0; i < port->max_connections; i++) { if (!port->netcons[i].net) { *netconnum = i; return port; } } next: so->unlock(port->lock); } port = port->next; } return NULL; } /* A connection request has come in on a port. */ static int rot_new_con(rotator_t *rot, struct gensio *net) { int i; const char *err; so->lock(ports_lock); i = rot->curr_port; do { unsigned int netconnum = 0; port_info_t *port = find_rotator_port(rot->portv[i], net, &netconnum); if (++i >= rot->portc) i = 0; if (port) { rot->curr_port = i; so->unlock(ports_lock); handle_new_net(port, net, &port->netcons[netconnum]); so->unlock(port->lock); return 0; } } while (i != rot->curr_port); so->unlock(ports_lock); err = "No free port found\r\n"; gensio_write(net, NULL, err, strlen(err), NULL); gensio_free(net); return 0; } static int handle_rot_child_event(struct gensio_accepter *accepter, void *user_data, int event, void *data) { rotator_t *rot = user_data; if (event == GENSIO_ACC_EVENT_LOG) { do_gensio_log(rot->accstr, data); return 0; } switch (event) { case GENSIO_ACC_EVENT_NEW_CONNECTION: return rot_new_con(rot, data); #ifdef GENSIO_ACC_EVENT_PARMLOG case GENSIO_ACC_EVENT_PARMLOG: { struct gensio_parmlog_data *d = (struct gensio_parmlog_data *) data; seout.vout(&seout, d->log, d->args); return 0; } #endif default: return handle_acc_auth_event(rot->authdir, rot->pamauth, rot->allowed_users, event, data); } } static struct gensio_waiter *rotator_shutdown_wait; static void handle_rot_shutdown_done(struct gensio_accepter *accepter, void *cb_data) { so->wake(rotator_shutdown_wait); } static void rot_timer_shutdown_done(struct gensio_timer *timer, void *cb_data) { so->wake(rotator_shutdown_wait); } static void free_rotator(rotator_t *rot) { int err; unsigned int free_count = 0; if (rot->accepter) { err = gensio_acc_shutdown(rot->accepter, handle_rot_shutdown_done, rot); if (!err) free_count++; } if (rot->restart_timer) { err = so->stop_timer_with_done(rot->restart_timer, rot_timer_shutdown_done, rot); if (err != GE_TIMEDOUT) free_count++; } if (free_count) so->wait(rotator_shutdown_wait, free_count, NULL); if (rot->accepter) gensio_acc_free(rot->accepter); if (rot->authdir) free(rot->authdir); if (rot->pamauth) free(rot->pamauth); free_user_list(rot->allowed_users); if (rot->default_allowed_users) free(rot->default_allowed_users); if (rot->name) free(rot->name); if (rot->accstr) free(rot->accstr); if (rot->portv) gensio_argv_free(so, rot->portv); if (rot->restart_timer) so->free_timer(rot->restart_timer); free(rot); } void free_rotators(void) { rotator_t *rot, *next; rot = rotators; while (rot) { next = rot->next; free_rotator(rot); rot = next; } rotators = NULL; } static void rot_timeout(struct gensio_timer *timer, void *cb_data) { rotator_t *rot = cb_data; int rv; rv = gensio_acc_startup(rot->accepter); if (rv) { gensio_time timeout = { rot->accepter_retry_time, 0 }; seout.out(&seout, "Failed to start rotator: %s", gensio_err_to_str(rv)); so->start_timer(rot->restart_timer, &timeout); } } void shutdown_rotators(void) { if (rotator_shutdown_wait) so->free_waiter(rotator_shutdown_wait); } int init_rotators(void) { rotator_shutdown_wait = so->alloc_waiter(so); if (!rotator_shutdown_wait) return GE_NOMEM; return 0; } int add_rotator(struct absout *eout, const char *name, const char *accstr, int portc, const char **ports, const char **options, int lineno) { rotator_t *rot; int rv; rot = malloc(sizeof(*rot)); if (!rot) return GE_NOMEM; memset(rot, 0, sizeof(*rot)); rot->name = strdup(name); if (!rot->name) goto out_nomem; rot->accstr = strdup(accstr); if (!rot->accstr) goto out_nomem; if (find_default_str("authdir", &rot->authdir)) goto out_nomem; if (find_default_str("pamauth", &rot->pamauth)) goto out_nomem; if (find_default_str("allowed-users", &rot->default_allowed_users)) goto out_nomem; rot->accepter_retry_time = find_default_int("accepter-retry-time"); if (options) { unsigned int i; const char *str; for (i = 0; options[i]; i++) { if (gensio_check_keyvalue(options[i], "authdir", &str) > 0) { if (rot->authdir) free(rot->authdir); rot->authdir = strdup(str); if (!rot->authdir) { eout->out(eout, "Out of memory allocating rotator" " authdir on line %d\n", lineno); goto out_nomem; } continue; } else if (gensio_check_keyvalue(options[i], "pamauth", &str) > 0) { if (rot->pamauth) free(rot->pamauth); rot->pamauth = strdup(str); if (!rot->pamauth) { eout->out(eout, "Out of memory allocating rotator" " pamauth on line %d\n", lineno); goto out_nomem; } continue; } else if (gensio_check_keyvalue(options[i], "allowed-users", &str) > 0) { rv = add_allowed_users(&rot->allowed_users, str, eout); if (rv) goto out_err; continue; } else if (gensio_check_keyuint(options[i], "accepter-retry-time", &rot->accepter_retry_time) > 0) { if (rot->accepter_retry_time < 1) rot->accepter_retry_time = 1; continue; } free_rotator(rot); eout->out(eout, "Invalid option %s for rotator on line %d\n", options[i], lineno); return GE_INVAL; } } rot->restart_timer = so->alloc_timer(so, rot_timeout, rot); if (!rot->restart_timer) { eout->out(eout, "Unable to allocate timer on line %d", lineno); goto out_nomem; } rot->portc = portc; rot->portv = ports; rv = str_to_gensio_accepter(rot->accstr, so, handle_rot_child_event, rot, &rot->accepter); if (rv) { eout->out(eout, "accepter was invalid on line %d", lineno); goto out_err; } if (!rot->allowed_users && rot->default_allowed_users) { rv = add_allowed_users(&rot->allowed_users, rot->default_allowed_users, eout); if (rv) goto out_err; } if (rot->default_allowed_users) { free(rot->default_allowed_users); rot->default_allowed_users = NULL; } rot->next = rotators; rotators = rot; rv = gensio_acc_startup(rot->accepter); if (rv) { gensio_time timeout = { rot->accepter_retry_time, 0 }; eout->out(eout, "Failed to start rotator on line %d: %s", lineno, gensio_err_to_str(rv)); so->start_timer(rot->restart_timer, &timeout); /* Don't error out, retry. */ } return 0; out_nomem: rv = GE_NOMEM; out_err: /* If we fail, the user should free these. */ rot->portc = 0; rot->portv = NULL; free_rotator(rot); return rv; } ser2net-4.6.2/ser2net.8000066400000000000000000000242011461221161700145600ustar00rootroot00000000000000.TH ser2net 8 06/02/01 "Serial to network proxy" .SH NAME ser2net \- Serial to network proxy .SH SYNOPSIS .B ser2net [\-c configfile] [\-C sysconfdir] [\-Y configline] [\-p controlport] [\-n] [\-d] [\-b] [\-v] [-P pidfile] .SH DESCRIPTION The .BR ser2net daemon allows telnet and tcp sessions to be established with a unit's serial ports or with an IPMI Serial Over LAN (SOL) interface. .PP The program comes up normally as a daemon, opens the network ports specified in the configuration file, and waits for connections. Once a connection occurs, the program attempts to set up the connection and open the serial port. If another user is already using the connection or serial port, the connection is refused with an error message. .SH OPTIONS .TP .I "\-c config\-file" Set the configuration file to one other than the default of .BR "sysconfdir/ser2net/ser2net.yaml". If the .B config-file is .B "-" then standard input is read as the config file. See \-C for info on sysconfdir .TP .I "\-C sysconfdir" Set the directory where the configuration file and ssl keys are stored. This is generally /etc/ser2net on Unix-type systems, on Windows it is ../etc/ser2net from the executable's location by default. .TP .I "\-a authdir" Set the default directory where authentication information is stored. This is generally /usr/share/ser2net on Unix-type systems, on Windows it is ../share/ser2net from the executable's location by default. This can be overridden in the config file. See the ser2net.yaml.5 for info on what this does. .TP .I "\-A admin-authdir" Set the default directory where the administrator's authentication information is stores. This is .BR "sysconfdir/auth" by default. This can be overridden in the config file. See the ser2net.yaml.5 for info on what this does. .TP .I "\-Y yaml-config-string" Add a yaml config string to the end of strings to be processed. This may be specified multiple times for multiple strings. These are appended onto the end of a yaml config file; though if this is specified, the default config file is disabled and you have to enable it with the -c option. To make things easier to handle, any # in the config string that is not inside quotes (what would normally start a comment in yaml) is converted to a new line. Plus a new line is added after each separate config string. So, for instance: -Y 'connection: &con01# accepter: tcp,2013' -Y ' connector: serialdev,/dev/ttyEcho0,9600n81,local' -Y ' options:# banner: "### A Banner ###\er\en"' can be put on the ser2net command line. .TP .I \-n Stops the daemon from forking and detaching from the controlling terminal. This is useful for running from init. .TP .I \-d Like -n, but also sends the system logs to standard output. This is most useful for debugging purposes. .TP .I \-P pidfile If specified, put the process id (pid) of ser2net in the pidfile, replacing whatever was in that file previously. A pidfile is not created by default, you must specify this to create one. Note also that this filename must be specific with the full path, as ser2net will change directory to "/" when it becomes a daemon. .TP .I \-u If UUCP locking is enabled, this will disable the use of UUCP locks. .TP .I \-b Cisco IOS uses a different mechanism for specifying the baud rates than the mechanism described in RFC2217. This option sets the IOS version of setting the baud rates. The default is RFC2217's. Note that this capability is now handled automatically and this option is ignored. .TP .I \-v Prints the version of the program and exits. .TP .I \-t Spawn the given number of threads for ser2net to use. The default is 1. Only valid if pthreads is enabled at build time. .TP .I \-p Enables the admin interface on the given accepter specification. See "ADMIN CONNECTION" in ser2net.yaml(5) for more details on how to configure this, and "ADMIN INTERFACE" below for details on how to use it. .TP .I \-s signature Specifies the default RFC2217 signature. .SH ADMIN INTERFACE The admin interface provides a simple interface for controlling the ports and viewing their status. To accomplish this, it has the following commands: .TP .B showport [] Show information about a port. If no port is given, all ports are displayed. .TP .B showshortport [] Show information about a port, each port on one line. If no port is given, all ports are displayed. This can produce very wide output. .TP .B help Display a short list and summary of commands. .TP .B exit Disconnect from the control port. .TP .B yaml Go into yaml output mode. See YAML MODES below. .TP .B version Display the version of this program. .TP .B monitor Display all the input for a given port on the calling control port. Only one direction may be monitored at a time. The type field may be .I tcp or .I term and specifies whether to monitor data from the network port or from the serial port Note that data monitoring is best effort, if the controller port cannot keep up the data will be silently dropped. A controller may only monitor one thing and a port may only be monitored by one controller. .TP .B monitor stop Stop the current monitor. .TP .B disconnect Disconnect the tcp connection on the port. .TP .B setporttimeout Set the amount of time in seconds before the port connection will be shut down if no activity has been seen on the port. .TP .B setportconfig Set the port configuration as in the device configuration in the .BR /etc/ser2net/ser2net.yaml file. If conflicting options are specified, the last option will be the one used. Note that these will not change until the port is disconnected and connected again. Options .I 300, 1200, 2400, 4800, 9600, 19200, 38400, 57600, 115200 set the various baud rates. The following speed may be available if your system has the values defined and your hardware supports it: 230400, 460800, 500000, 576000, 921600, 1000000, 1152000, 1500000, 2000000, 2500000, 3000000, 3500000, 4000000. Parity, databits, and stopbits may be specified in the classical manner after the speed, as in 9600N81. This has the following format: .B [N|E|O|M|S[5|6|7|8[1|2]]]. Setting serial options this way does not work on SOL, SOL has fixed N81 serial options. .I EVEN, ODD, NONE (MARK and SPACE if supported) set the parity. .I 1STOPBIT, 2STOPBITS set the number of stop bits. .I 7DATABITS, 8DATABITS set the number of data bits. .I [-]XONXOFF turns on (- off) XON/XOFF support. .I [-]RTSCTS turns on (- off) hardware flow control. .I [-]LOCAL ignores (- checks) the modem control lines (DCD, DTR, etc.) .TP .B setportcontrol Modify dynamic port controls. These do not stay between connections. Controls are: .I DTRHI, DTRLO Turns on and off the DTR line. .I RTSHI, RTSLO Turns on and off the RTS line. .TP .B setportenable Sets the port operation state. Valid states are: .I off to shut the network port down, .I raw to enable the network port transfer all I/O as-is, .I rawlp to enable the network port input and device output without termios setting, and .I telnet to enable the network port is up run the telnet negotiation protocol on the port. .TP .B reload Causes ser2net to reload its configuration. Any error output will come out in the output, along with going to syslog. .SS YAML MODES If the "yaml" command is issued, echo is turned off and all output is YAML compliant. The form is basically the same as the non-YAML output, with some minor adjustments to make it YAML compliant. In addition, all responses are in the form: %YAML 1.1 --- response: ... If there is an error, it is returned in the response with the "error" key. If the command succeeded, no "error" key will be present in the response mapping. The "..." will be at the end of all responses. The following commands are available in yaml output mode: exit, version, showport, disconnect, setporttimeout, setportenable, setportcontrol, reload, If "%YAML" is seen in the input, YAML input and output modes are activated, echo is disabled, and all input is expected to be in the form: --- command: name: id: parms: [ parm1 [, parm2 [...]]] ... The id is optional and will just be returned in the response. The parms are optional, too, unless the command requires them. Extra parms are ignored, along with unknown keys in the main mapping. Note that you have to deal with the "->" that is issued when the connection is made, before going into YAML mode, YAML doesn't handle that well. In YAML output mode, you will get asynchronous reports of connections and disconnections in the form: %YAML 1.1 --- new connection: name: !!str 'con1' remaddr: !!str 'ipv6,::1,59072' ... %YAML 1.1 --- disconnect: name: !!str 'con1' remaddr: !!str 'ipv6,::1,59072' ... .SH CONFIGURATION Configuration is accomplished through the file .BR /etc/ser2net/ser2net.yaml . A file with another name or path may be specified using the .I \-c option. The yaml configuration file is described in ser2net.yaml(5) .SH "SIGNALS" .TP 0.5i .B SIGHUP If ser2net receives a SIGHUP, it will reread it configuration file and make the appropriate changes. If an in use connection is changed or deleted, the actual change will not occur until the port is disconnected, except that if you disable a connection it will kick the users off. ser2net uses the name (the connection alias) of the connection to tell if it is new, changed or deleted. If the new configuration file has a connection with the same name, it is treated as a change. This has some unusual interactions with connections that allow more than one simultaneous connection. It works just like the other port, but the accepter is disabled and new connections will not be accepted until all the existing connections are closed. .SH "ERRORS" All error output after startup goes to syslog, not standard output, unless you use the -d option. .SH "FILES" /etc/ser2net/ser2net.yaml, /etc/ser2net/ser2net.key, /etc/ser2net/ser2net.crt, /usr/share/ser2net .SH "SEE ALSO" telnet(1), ser2net.yaml(5), hosts_access(5) .SH "KNOWN PROBLEMS" None. .SH AUTHOR .PP Corey Minyard ser2net-4.6.2/ser2net.c000066400000000000000000000567471461221161700146570ustar00rootroot00000000000000/* * ser2net - A program for allowing telnet connection to serial ports * Copyright (C) 2001-2020 Corey Minyard * * SPDX-License-Identifier: GPL-2.0-only * * In addition, as a special exception, the copyright holders of * ser2net give you permission to combine ser2net with free software * programs or libraries that are released under the GNU LGPL and * with code included in the standard release of OpenSSL under the * OpenSSL license (or modified versions of such code, with unchanged * license). You may copy and distribute such a system following the * terms of the GNU GPL for ser2net and the licenses of the other code * concerned, provided that you include the source code of that * other code when and as the GNU GPL requires distribution of source * code. * * Note that people who make modified versions of ser2net are not * obligated to grant this special exception for their modified * versions; it is their choice whether to do so. The GNU General * Public License gives permission to release a modified version * without this exception; this exception also makes it possible to * release a modified version which carries forward this exception. */ /* This is the entry point for the ser2net program. It reads parameters, initializes everything, then starts the select loop. */ #include #include #include #include #include "ser2net.h" #include "readconfig.h" #include "controller.h" #include "dataxfer.h" #include "led.h" #include "fileio.h" bool admin_port_from_cmdline = false; char *admin_port = NULL; /* Can be set from readconfig, too. */ static char *pid_file = NULL; static int detach = 1; int ser2net_debug = 0; int ser2net_debug_level = 0; volatile int in_shutdown = 0; static unsigned int num_threads = 1; char *confdir; char *authdir; char *admin_authdir; char *keyfile; char *certfile; static char *config_file; bool config_file_set; static char * alloc_vsprintf(const char *fmt, va_list va) { va_list va2; int len; char c[1], *str; va_copy(va2, va); len = vsnprintf(c, 0, fmt, va); str = malloc(len + 1); if (str) vsnprintf(str, len + 1, fmt, va2); va_end(va2); return str; } static char * alloc_sprintf(const char *fmt, ...) { va_list va; char *s; va_start(va, fmt); s = alloc_vsprintf(fmt, va); va_end(va); return s; } #ifdef _WIN32 #include static int drop_last_diritem(char *dir) { char *s, *s2; s = strrchr(dir, '/'); s2 = strrchr(dir, '\\'); if (!s && !s2) return 0; if (s && s2) { if (s < s2) s = s2; } else if (s2) { s = s2; } *s = '\0'; return 1; } static char * get_basedir(void) { char dir[256]; DWORD rv; rv = GetModuleFileNameA(NULL, dir, sizeof(dir)); if (rv == 0) return NULL; if (!drop_last_diritem(dir)) return NULL; if (!drop_last_diritem(dir)) return NULL; return strdup(dir); } static int setup_paths_base(void) { char *basedir = get_basedir(); if (!basedir) { fprintf(stderr, "Unable to allocate base dir\n"); return 1; } if (!confdir) { confdir = alloc_sprintf("%s%setc%sser2net", basedir, DIRSEPS, DIRSEPS); if (!confdir) { fprintf(stderr, "Unable to allocate confdir\n"); goto out_err; } } if (!authdir) { authdir = alloc_sprintf("%s%sshare%sser2net%sauth", basedir, DIRSEPS, DIRSEPS, DIRSEPS); if (!confdir) { fprintf(stderr, "Unable to allocate authdir\n"); goto out_err; } } free(basedir); return 0; out_err: free(basedir); return 1; } #else static int setup_paths_base(void) { if (!confdir) { confdir = alloc_sprintf("%s%sser2net", SYSCONFDIR, DIRSEPS); if (!confdir) { fprintf(stderr, "Unable to allocate authdir\n"); return 1; } } if (!authdir) { authdir = alloc_sprintf("%s%sser2net%sauth", DATAROOT, DIRSEPS, DIRSEPS); if (!authdir) { fprintf(stderr, "Unable to allocate authdir\n"); return 1; } } return 0; } #endif static int setup_paths(void) { if (setup_paths_base()) return 1; if (!admin_authdir) { admin_authdir = alloc_sprintf("%s%sauth", confdir, DIRSEPS); if (!admin_authdir) { fprintf(stderr, "Unable to allocate admin authdir\n"); goto out_err; } } if (!keyfile) { keyfile = alloc_sprintf("%s%sser2net.key", confdir, DIRSEPS); if (!keyfile) { fprintf(stderr, "Unable to allocate keyfile\n"); goto out_err; } } if (!certfile) { certfile = alloc_sprintf("%s%sser2net.crt", confdir, DIRSEPS); if (!certfile) { fprintf(stderr, "Unable to allocate certfile\n"); goto out_err; } } if (!config_file && !config_file_set) { config_file = alloc_sprintf("%s%sser2net.yaml", confdir, DIRSEPS); if (!config_file) { fprintf(stderr, "Unable to allocate config file name\n"); goto out_err; } } return 0; out_err: return 1; } struct s2n_threadinfo { struct gensio_thread *gthread; struct gensio_waiter *waiter; struct gensio_runner *runner; } *threads; struct gensio_os_proc_data *procdata; struct gensio_os_funcs *so; char *rfc2217_signature = "ser2net"; static char *help_string = "%s: Valid parameters are:\n" " -c - use a config file besides %s\n" " -C - Set the directory where the configuration file and\n" " ssl keys are stored. This is generally /etc/ser2net on Unix-type\n" " systems, on Windows it is ../etc/ser2net from the executable's\n" " location by default.\n" " -p - Start a controller session on the given TCP port\n" " -P - set location of pid file\n" " -n - Don't detach from the controlling terminal\n" " -d - Don't detach and send debug I/O to standard output\n" " -l - Increase the debugging level\n" " -u - Disable UUCP locking\n" " -t - Use the given number of threads, default 1\n" " -b - unused (was Do CISCO IOS baud-rate negotiation, instead of RFC2217)\n" " -v - print the program's version and exit\n" " -s - specify a default signature for RFC2217 protocol\n" " -Y - Handle a yaml configuration string. This may be specified multiple\n" " times; these strings are strung together as if they were one input\n" " string. This disables the default config file, you must specify -c\n" " after the last -Y. The config file will be processed first, if it\n" " is specified, then the -Y strings in order, as if they are one\n" " contiguous file. '#' characters outside of quotes will be converted\n" " to newlines to make things easier to handle. Each -Y will be\n" " terminated with a newline automatically.\n"; static char **config_lines; static unsigned int num_config_lines; #ifndef WIN32 #include #endif static int stderr_evout(struct absout *e, const char *str, va_list ap) { char buf[1024]; vsnprintf(buf, sizeof(buf), str, ap); #ifndef WIN32 syslog(LOG_ERR, "%s", buf); #endif fprintf(stderr, "%s\n", buf); return 0; } static int stderr_eout(struct absout *e, const char *str, ...) { va_list ap; va_start(ap, str); stderr_evout(e, str, ap); va_end(ap); return 0; } #ifndef WIN32 #include #include static int syslog_evout(struct absout *e, const char *str, va_list ap) { char buf[1024]; vsnprintf(buf, sizeof(buf), str, ap); syslog(LOG_ERR, "%s", buf); return 0; } static int syslog_eout(struct absout *e, const char *str, ...) { va_list ap; va_start(ap, str); syslog_evout(e, str, ap); va_end(ap); return 0; } struct absout seout = { .out = syslog_eout, .vout = syslog_evout }; static int gensio_log_level_to_syslog(int gloglevel) { switch (gloglevel) { case GENSIO_LOG_FATAL: return LOG_EMERG; case GENSIO_LOG_ERR: return LOG_ERR; case GENSIO_LOG_WARNING: return LOG_WARNING; case GENSIO_LOG_INFO: default: return LOG_INFO; } return LOG_ERR; } void do_gensio_log(const char *name, struct gensio_loginfo *i) { char buf[256]; vsnprintf(buf, sizeof(buf), i->str, i->args); syslog(gensio_log_level_to_syslog(i->level), "%s: %s", name, buf); } static void ser2net_gensio_logger(struct gensio_os_funcs *o, enum gensio_log_levels level, const char *log, va_list args) { int priority = gensio_log_level_to_syslog(level); vsyslog(priority, log, args); } #else #define SIGUSR1 0 struct absout seout = { .out = stderr_eout, .vout = stderr_evout }; void do_gensio_log(const char *name, struct gensio_loginfo *i) { char buf[256]; vsnprintf(buf, sizeof(buf), i->str, i->args); fprintf(stderr, "%s: %s: %s\n", gensio_log_level_to_str(i->level), name, buf); } static void ser2net_gensio_logger(struct gensio_os_funcs *o, enum gensio_log_levels level, const char *log, va_list args) { char buf[256]; vsnprintf(buf, sizeof(buf), log, args); fprintf(stderr, "%s: %s\n", gensio_log_level_to_str(level), buf); } #endif static struct absout stderr_absout = { .out = stderr_eout, .vout = stderr_evout }; static ftype * fopen_config_file(char **rfilename, struct absout *eout) { int err; ftype *instream; err = f_open(config_file, DO_READ, 0, &instream); if (err) { eout->out(eout, "Unable to open config file '%s': %s", config_file, gensio_err_to_str(err)); return NULL; } else { *rfilename = config_file; } return instream; } int reread_config_file(const char *reqtype, struct absout *eout) { int rv = GE_NOTFOUND; if (config_file) { ftype *instream = NULL; char *filename; seout.out(&seout, "Got %s, re-reading configuration", reqtype); readconfig_init(); instream = fopen_config_file(&filename, &seout); if (!instream) goto out; if (!admin_port_from_cmdline) controller_shutdown(); rv = yaml_readconfig(instream, filename, config_lines, num_config_lines, eout); f_close(instream); if (!rv) apply_new_ports(&seout); } out: return rv; } void add_usec_to_time(gensio_time *tv, int usec) { #ifdef gensio_version_major tv->nsecs += usec * 1000; while (tv->nsecs >= 1000000000) { tv->nsecs -= 1000000000; tv->secs += 1; } #else tv->tv_usec += usec; while (tv->tv_usec >= 1000000) { tv->tv_usec -= 1000000; tv->tv_sec += 1; } #endif } int sub_time(gensio_time *left, gensio_time *right) { gensio_time dest; #ifdef gensio_version_major dest.secs = left->secs - right->secs; dest.nsecs = left->nsecs - right->nsecs; while (dest.nsecs < 0) { dest.nsecs += 1000000000; dest.secs--; } return (dest.secs * 1000000) + (dest.nsecs + 500) / 1000; #else dest.tv_sec = left->tv_sec - right->tv_sec; dest.tv_usec = left->tv_usec - right->tv_usec; while (dest.tv_usec < 0) { dest.tv_usec += 1000000; dest.tv_sec--; } return (dest.tv_sec * 1000000) + dest.tv_usec; #endif } /* Scan for a positive integer, and return it. Return -1 if the integer was invalid. */ int scan_int(const char *str) { int rv = 0; if (*str == '\0') { return -1; } for (;;) { switch (*str) { case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': rv = (rv * 10) + ((*str) - '0'); break; case '\0': return rv; default: return -1; } str++; } return rv; } static void arg_error(char *name) { if (setup_paths()) return; fprintf(stderr, help_string, name, config_file); printf("\n"); printf(" config file: %s\n", config_file); printf(" config dir: %s\n", confdir); printf(" user auth dir: %s\n", authdir); printf(" admin auth dir: %s\n", admin_authdir); printf(" keyfile: %s\n", keyfile); printf(" certfile: %s\n", certfile); } static int make_pidfile(void) { #ifndef WIN32 int rv, len; ftype *fpidfile; char buf[20]; if (!pid_file) return 0; rv = f_open(pid_file, DO_WRITE | DO_CREATE, 0644, &fpidfile); if (rv) { seout.out(&seout, "Error opening pidfile '%s': %m, pidfile not created: %s", pid_file, gensio_err_to_str(rv)); pid_file = NULL; return rv; } len = snprintf(buf, sizeof(buf), "%d\n", getpid()); f_write(fpidfile, buf, len, NULL); f_close(fpidfile); #endif return 0; } static void cleanup_pidfile(void) { #ifndef WIN32 if (pid_file) unlink(pid_file); #endif } void do_detach(void) { #ifndef WIN32 if (detach) { int pid; /* Detach from the calling terminal. */ openlog("ser2net", LOG_PID | LOG_CONS, LOG_DAEMON); syslog(LOG_NOTICE, "ser2net startup"); if ((pid = fork()) > 0) { exit(0); } else if (pid < 0) { seout.out(&seout, "Error forking first fork: %s", strerror(errno)); exit(1); } else { /* setsid() is necessary if we really want to demonize */ setsid(); /* Second fork to really daemonize me. */ if ((pid = fork()) > 0) { exit(0); } else if (pid < 0) { seout.out(&seout, "Error forking second fork: %s", strerror(errno)); exit(1); } } /* Close all my standard I/O. */ if (chdir("/") < 0) { seout.out(&seout, "unable to chdir to '/': %s", strerror(errno)); exit(1); } close(0); close(1); close(2); } else if (ser2net_debug) { openlog("ser2net", LOG_PID | LOG_CONS | LOG_PERROR, LOG_DAEMON); } else { openlog("ser2net", LOG_PID | LOG_CONS, LOG_DAEMON); } #endif } static struct gensio_lock *config_lock; static struct gensio_lock *maint_lock; static int in_config_read = 0; void start_maint_op(void) { gensio_os_funcs_lock(so, maint_lock); } void end_maint_op(void) { gensio_os_funcs_unlock(so, maint_lock); } static void cleanup_s2n_threadinfo(struct s2n_threadinfo *thread) { if (thread->waiter) gensio_os_funcs_free_waiter(so, thread->waiter); if (thread->runner) gensio_os_funcs_free_runner(so, thread->runner); } static void config_join_runner(struct gensio_runner *r, void *data) { struct s2n_threadinfo *thread = data; gensio_os_wait_thread(thread->gthread); cleanup_s2n_threadinfo(thread); gensio_os_funcs_zfree(so, thread); } static void config_reread_thread(void *data) { struct s2n_threadinfo *thread = data; start_maint_op(); reread_config_file("SIGHUP", &seout); end_maint_op(); gensio_os_funcs_lock(so, config_lock); in_config_read = 0; gensio_os_funcs_unlock(so, config_lock); gensio_os_funcs_run(so, thread->runner); } static void thread_reread_config_file(void *data) { int rv; struct s2n_threadinfo *thread = NULL; gensio_os_funcs_lock(so, config_lock); if (in_config_read || in_shutdown) { so->unlock(config_lock); return; } in_config_read = 1; gensio_os_funcs_unlock(so, config_lock); thread = gensio_os_funcs_zalloc(so, sizeof(*thread)); if (!thread) goto out_nomem; thread->waiter = gensio_os_funcs_alloc_waiter(so); if (!thread->waiter) goto out_nomem; thread->runner = gensio_os_funcs_alloc_runner(so, config_join_runner, thread); if (!thread->runner) goto out_nomem; rv = gensio_os_new_thread(so, config_reread_thread, thread, &thread->gthread); if (rv) { seout.out(&seout, "Unable to start thread to reread config file: %s", gensio_err_to_str(rv)); goto out_unlock; } return; out_nomem: seout.out(&seout, "Reconfig failed: Out of memory"); out_unlock: if (thread) cleanup_s2n_threadinfo(thread); gensio_os_funcs_lock(so, config_lock); in_config_read = 0; gensio_os_funcs_unlock(so, config_lock); } static void op_loop(void *data) { struct s2n_threadinfo *thread = data; gensio_os_funcs_wait(so, thread->waiter, 1, NULL); } static int start_threads(void) { int i, rv; threads = gensio_os_funcs_zalloc(so, sizeof(*threads) * num_threads); if (!threads) { seout.out(&seout, "Unable to allocate thread info"); return 1; } for (i = 0; i < num_threads; i++) { threads[i].waiter = gensio_os_funcs_alloc_waiter(so); if (!threads[i].waiter) goto out_nomem; } for (i = 1; i < num_threads; i++) { rv = gensio_os_new_thread(so, op_loop, &threads[i], &threads[i].gthread); if (rv) { seout.out(&seout, "Unable to start thread: %s", gensio_err_to_str(rv)); goto out; } } return 0; out_nomem: seout.out(&seout, "Unable to alloc data for threads"); out: for (i = 0; i < num_threads; i++) { if (threads[i].gthread) { gensio_os_funcs_wake(so, threads[i].waiter); gensio_os_wait_thread(threads[i].gthread); } cleanup_s2n_threadinfo(&threads[i]); } gensio_os_funcs_zfree(so, threads); threads = NULL; return 1; } static void stop_threads(void) { int i; for (i = 0; i < num_threads; i++) gensio_os_funcs_wake(so, threads[i].waiter); } static void shutdown_cleanly(void *data) { struct gensio_time timeout; gensio_os_funcs_lock(so, config_lock); in_shutdown = 1; /* Make sure we aren't in a reconfig. */ while (in_config_read) { gensio_os_funcs_unlock(so, config_lock); timeout.secs = 1; timeout.nsecs = 0; gensio_os_funcs_service(so, &timeout); gensio_os_funcs_lock(so, config_lock); } gensio_os_funcs_unlock(so, config_lock); stop_threads(); } int main(int argc, char *argv[]) { unsigned int i; int err, rv; char *end; int print_when_ready = 0; ftype *instream = NULL; char *filename; gensio_set_progname("ser2net"); config_lines = malloc(sizeof(*config_lines)); if (!config_lines) { fprintf(stderr, "Out of memory\n"); return 1; } *config_lines = NULL; for (i = 1; i < argc; i++) { if ((argv[i][0] != '-') || (strlen(argv[i]) != 2)) { fprintf(stderr, "Invalid argument: '%s'\n", argv[i]); arg_error(argv[0]); return 1; } switch (argv[i][1]) { case 'r': print_when_ready = 1; break; case 'n': detach = 0; break; case 'd': detach = 0; ser2net_debug = 1; break; case 'l': ser2net_debug_level++; gensio_set_log_mask(gensio_get_log_mask() | 1 << (ser2net_debug_level + 2)); break; case 'b': break; case 'Y': /* Get a config line. */ i++; if (i == argc) { fprintf(stderr, "No config line specified with -%c\n", argv[i][1]); arg_error(argv[0]); return 1; } num_config_lines++; config_lines = realloc(config_lines, sizeof(*config_lines) * (num_config_lines + 1)); if (!config_lines) { fprintf(stderr, "Out of memory handling config line\n"); return 1; } config_lines[num_config_lines - 1] = argv[i]; if (!config_file_set) { config_file = NULL; config_file_set = true; } break; case 'c': /* Get a config file. */ i++; if (i == argc) { fprintf(stderr, "No config file specified with -c\n"); arg_error(argv[0]); return 1; } config_file = argv[i]; config_file_set = true; break; case 'C': i++; if (i == argc) { fprintf(stderr, "No config dir specified with -c\n"); arg_error(argv[0]); return 1; } confdir = argv[i]; break; case 'a': i++; if (i == argc) { fprintf(stderr, "No auth dir specified with -a\n"); arg_error(argv[0]); return 1; } authdir = argv[i]; break; case 'A': i++; if (i == argc) { fprintf(stderr, "No admin auth dir specified with -A\n"); arg_error(argv[0]); return 1; } admin_authdir = argv[i]; break; case 'p': /* Get the control port. */ i++; if (i == argc) { fprintf(stderr, "No control port specified with -p\n"); arg_error(argv[0]); return 1; } admin_port = strdup(argv[i]); if (!admin_port) { fprintf(stderr, "Could not allocate memory for -p\n"); return 1; } admin_port_from_cmdline = true; break; case 'P': i++; if (i == argc) { fprintf(stderr, "No pid file specified with -P\n"); arg_error(argv[0]); return 1; } pid_file = argv[i]; break; case 'u': gensio_uucp_locking_enabled = 0; break; case 'v': printf("%s version %s\n", argv[0], VERSION); return 0; case 's': i++; if (i == argc) { fprintf(stderr, "No signature specified\n"); return 1; } rfc2217_signature = argv[i]; break; case 't': i++; if (i == argc) { fprintf(stderr, "No thread count specified\n"); return 1; } num_threads = strtoul(argv[i], &end, 10); if (end == argv[i] || *end != '\0') { fprintf(stderr, "Invalid thread count specified: %s\n", argv[i]); return 1; } if (num_threads == 0) num_threads = 1; break; default: fprintf(stderr, "Invalid option: '%s'\n", argv[i]); arg_error(argv[0]); return 1; } } if (setup_paths()) return 1; err = gensio_default_os_hnd(SIGUSR1, &so); if (err) { fprintf(stderr, "Could not alloc ser2net gensio selector\n"); return 1; } so->vlog = ser2net_gensio_logger; err = gensio_os_proc_setup(so, &procdata); if (err) { seout.out(&seout, "Unable to setup proc: %s", gensio_err_to_str(err)); return 1; } err = gensio_os_proc_register_reload_handler(procdata, thread_reread_config_file, NULL); if (err && err != GE_NOTSUP) { seout.out(&seout, "Unable to setup reconfig handler: %s", gensio_err_to_str(err)); return 1; } err = gensio_os_proc_register_term_handler(procdata, shutdown_cleanly, NULL); if (err) { seout.out(&seout, "Unable to setup termination handler: %s", gensio_err_to_str(err)); return 1; } if (led_driver_init() < 0) { fprintf(stderr, "Error while initializing LED drivers\n"); return 1; } config_lock = gensio_os_funcs_alloc_lock(so); if (!config_lock) { fprintf(stderr, "Could not alloc ser2net config lock\n"); return 1; } maint_lock = gensio_os_funcs_alloc_lock(so); if (!maint_lock) { fprintf(stderr, "Could not alloc ser2net maint lock\n"); return 1; } init_mdns(); err = init_dataxfer(); if (err) { fprintf(stderr, "Could not initialize dataxfer: '%s'\n", strerror(err)); return 1; } err = readconfig_init(); if (err) { fprintf(stderr, "Could not initialize defaults: '%s'\n", gensio_err_to_str(err)); return 1; } if (admin_port) controller_init(admin_port, NULL, NULL, &stderr_absout); if (config_file) { if (strcmp(config_file, "-") == 0) { err = f_stdio_open(stdin, DO_READ, 0, &instream); if (err) { fprintf(stderr, "Unable to create stdin file: %s\n", gensio_err_to_str(err)); return 1; } filename = ""; } else { instream = fopen_config_file(&filename, &stderr_absout); if (!instream) return 1; } } else { filename = ""; } rv = yaml_readconfig(instream, filename, config_lines, num_config_lines, &stderr_absout); if (rv) return 1; if (instream) f_close(instream); apply_new_ports(&seout); do_detach(); /* write pid file */ if (make_pidfile()) return 1; if (start_threads()) return 1; if (print_when_ready) { printf("Ready\n"); fflush(stdout); } op_loop(&threads[0]); for (i = 1; i < num_threads; i++) gensio_os_wait_thread(threads[i].gthread); free_rotators(); free_controllers(); shutdown_ports(); while (!check_ports_shutdown()) { struct gensio_time timeout; timeout.secs = 1; timeout.nsecs = 0; gensio_os_funcs_service(so, &timeout); } shutdown_dataxfer(); cleanup_pidfile(); if (admin_port) free(admin_port); if (config_lines) free(config_lines); gensio_os_funcs_free_lock(so, maint_lock); gensio_os_funcs_free_lock(so, config_lock); gensio_os_funcs_free(so); return 0; } ser2net-4.6.2/ser2net.h000066400000000000000000000057531461221161700146530ustar00rootroot00000000000000/* * ser2net - A program for allowing telnet connection to serial ports * Copyright (C) 2001-2020 Corey Minyard * * SPDX-License-Identifier: GPL-2.0-only * * In addition, as a special exception, the copyright holders of * ser2net give you permission to combine ser2net with free software * programs or libraries that are released under the GNU LGPL and * with code included in the standard release of OpenSSL under the * OpenSSL license (or modified versions of such code, with unchanged * license). You may copy and distribute such a system following the * terms of the GNU GPL for ser2net and the licenses of the other code * concerned, provided that you include the source code of that * other code when and as the GNU GPL requires distribution of source * code. * * Note that people who make modified versions of ser2net are not * obligated to grant this special exception for their modified * versions; it is their choice whether to do so. The GNU General * Public License gives permission to release a modified version * without this exception; this exception also makes it possible to * release a modified version which carries forward this exception. */ #ifndef SER2NET_H #define SER2NET_H #include #include #include #include "absout.h" /* The default rfc2217 signature if none is provided. */ extern char *rfc2217_signature; extern struct gensio_os_funcs *so; extern int ser2net_debug; extern int ser2net_debug_level; extern int ser2net_wake_sig; #if (defined(gensio_version_major) && (gensio_version_major > 2 || \ (gensio_version_major == 2 && gensio_version_minor >= 2))) #define DO_MDNS #endif void init_mdns(void); void start_maint_op(void); void end_maint_op(void); int reread_config_file(const char *reqtype, struct absout *eout); int init_dataxfer(void); void shutdown_dataxfer(void); #ifndef gensio_version_major typedef struct timeval gensio_time; #endif void add_usec_to_time(gensio_time *tv, int usec); int sub_time(gensio_time *left, gensio_time *right); /* Scan for a positive integer, and return it. Return -1 if the integer was invalid. Spaces are not handled. */ int scan_int(const char *str); /* * Handle authorization events from accepters. */ int handle_acc_auth_event(const char *authdir, const char *pamauth, const struct gensio_list *allowed_users, int event, void *data); /* * Add to/free the list of allowed users. */ int add_allowed_users(struct gensio_list **users, const char *str, struct absout *eout); void free_user_list(struct gensio_list *users); /* System err output (syslog on *nix). */ extern struct absout seout; void do_gensio_log(const char *name, struct gensio_loginfo *i); #ifdef _WIN32 #define DIRSEP '\\' #define DIRSEPS "\\" #else #define DIRSEP '/' #define DIRSEPS "/" #endif extern char *confdir; extern char *authdir; extern char *admin_authdir; extern char *keyfile; extern char *certfile; #endif /* SER2NET_H */ ser2net-4.6.2/ser2net.init000066400000000000000000000016701461221161700153610ustar00rootroot00000000000000#!/bin/sh # Startup script for ser2net # # chkconfig: 2345 95 20 # description: Make serial ports available to network via TCP/IP. # Source function library. . /etc/rc.d/init.d/functions [ -f /usr/sbin/ser2net ] || exit 0 start() { echo -n "Starting ser2net: " daemon ser2net RETVAL=$? echo if test $RETVAL == 0; then touch /var/lock/subsys/ser2net fi return $RETVAL } stop() { echo -n "Shutting down ser2net " if test "x`pidof ser2net`" != x then killproc ser2net else success "ser2net shutdown" fi echo rm -f /var/lock/subsys/ser2net return 0 } case "$1" in start) start ;; stop) stop ;; status) status ser2net ;; restart) stop start ;; condrestart) if test "x`pidof ser2net`" != x; then stop start fi ;; *) echo "Usage: ser2net {start|stop|restart|condrestart|status}" exit 1 esac exit 0 ser2net-4.6.2/ser2net.iss000066400000000000000000000071241461221161700152140ustar00rootroot00000000000000; Script generated by the Inno Setup Script Wizard. ; SEE THE DOCUMENTATION FOR DETAILS ON CREATING INNO SETUP SCRIPT FILES! #define MyAppName "Ser2Net" #define MyAppVersion "4.6.2" #define MyAppPublisher "Ser2Net" #define MyAppURL "https://github.com/cminyard/ser2net" #define InstallDir "c:/msys64/home/cminyard/install/Ser2Net" #define mingw64Dir "c:/msys64/ucrt64" [Setup] ; NOTE: The value of AppId uniquely identifies this application. Do not use the same AppId value in installers for other applications. ; (To generate a new GUID, click Tools | Generate GUID inside the IDE.) AppId={{20B100EC-E722-47F4-923A-34ECABC50215}} AppName={#MyAppName} AppVersion={#MyAppVersion} ;AppVerName={#MyAppName} {#MyAppVersion} AppPublisher={#MyAppPublisher} AppPublisherURL={#MyAppURL} AppSupportURL={#MyAppURL} AppUpdatesURL={#MyAppURL} DefaultDirName={autopf}\{#MyAppName} DefaultGroupName={#MyAppName} AllowNoIcons=yes ; Uncomment the following line to run in non administrative install mode (install for current user only.) ;PrivilegesRequired=lowest PrivilegesRequiredOverridesAllowed=dialog OutputDir=C:\msys64\home\cminyard OutputBaseFilename=Ser2Net Compression=lzma SolidCompression=yes WizardStyle=modern ChangesEnvironment=true [Files] Source: "{#InstallDir}/bin/ser2net.exe"; DestDir: "{app}/bin" Source: "{#mingw64Dir}/bin/libyaml-0-2.dll"; DestDir: "{app}/bin" [Dirs] Name: "{app}/etc/ser2net" Name: "{app}/share/ser2net" [Languages] Name: "english"; MessagesFile: "compiler:Default.isl" [Icons] Name: "{group}\{cm:ProgramOnTheWeb,{#MyAppName}}"; Filename: "{#MyAppURL}" Name: "{group}\{cm:UninstallProgram,{#MyAppName}}"; Filename: "{uninstallexe}" [Code] const EnvironmentKey = 'SYSTEM\CurrentControlSet\Control\Session Manager\Environment'; procedure EnvAddPath(Path: string); var Paths: string; begin { Retrieve current path (use empty string if entry not exists) } if not RegQueryStringValue(HKEY_LOCAL_MACHINE, EnvironmentKey, 'Path', Paths) then Paths := ''; { Skip if string already found in path } if Pos(';' + Uppercase(Path) + ';', ';' + Uppercase(Paths) + ';') > 0 then exit; { App string to the end of the path variable } Paths := Paths + ';'+ Path +';' { Overwrite (or create if missing) path environment variable } if RegWriteStringValue(HKEY_LOCAL_MACHINE, EnvironmentKey, 'Path', Paths) then Log(Format('The [%s] added to PATH: [%s]', [Path, Paths])) else Log(Format('Error while adding the [%s] to PATH: [%s]', [Path, Paths])); end; procedure EnvRemovePath(Path: string); var Paths: string; P: Integer; begin { Skip if registry entry not exists } if not RegQueryStringValue(HKEY_LOCAL_MACHINE, EnvironmentKey, 'Path', Paths) then exit; { Skip if string not found in path } P := Pos(';' + Uppercase(Path) + ';', ';' + Uppercase(Paths) + ';'); if P = 0 then exit; { Update path variable } Delete(Paths, P - 1, Length(Path) + 1); { Overwrite path environment variable } if RegWriteStringValue(HKEY_LOCAL_MACHINE, EnvironmentKey, 'Path', Paths) then Log(Format('The [%s] removed from PATH: [%s]', [Path, Paths])) else Log(Format('Error while removing the [%s] from PATH: [%s]', [Path, Paths])); end; procedure CurStepChanged(CurStep: TSetupStep); begin if CurStep = ssPostInstall then EnvAddPath(ExpandConstant('{app}/bin')); end; procedure CurUninstallStepChanged(CurUninstallStep: TUninstallStep); begin if CurUninstallStep = usPostUninstall then EnvRemovePath(ExpandConstant('{app}/bin')); end; ser2net-4.6.2/ser2net.service.in000066400000000000000000000004421461221161700164570ustar00rootroot00000000000000[Unit] Description=Serial to network proxy Requires=network.target Documentation=man:ser2net(8) [Service] Type=simple User=ser2net Group=dialout ExecStart=@ser2netbindir@/ser2net -n ExecReload=kill -HUP $MAINPID StandardOutput=null Restart=on-failure [Install] WantedBy=multi-user.target ser2net-4.6.2/ser2net.yaml000066400000000000000000000350441461221161700153620ustar00rootroot00000000000000%YAML 1.1 --- # # This is a ser2net configuration file, showing examples of all # sorts of things. It's all commented out so it's safe to put # in /etc/ser2net/ser2net.yaml. # # This is described in ser2net.yaml(5) # # NOTE: ser2net can parse two types of yaml files, called version 1 # and version 2 files. You cannot mix things from these versions. # See the man page for details, and the end of this file (search for # "VERSION 2" for a version 2 example. Everything up to that point # is version 1 only. Version 1 is not yaml compliant and can't be # parsed by a normal yaml parser. Version 2 is yaml compliant. # YAML gives an error on empty files, just add something so the # error doesn't happen. define: &confver 1.0 # # You can include other files from here, only at the main level. # # These must be full yaml files, but are included in context at # # this point, with all the defines, connection names, etc. # # This can let you have individual connections in individual # # files to ease management. globs are accepted. # include: /etc/ser2net/ser2net/*.yaml # # Just a basic serial port The con0 is the name of the connection, # # each connection must be given a unique name. The accepter is the # # information about how to receive connections, in this case it is # # on TCP port 2002. The connector is what the connection is hooked # # to when a connection comes in, in this case serial device /dev/ttyS0 # # with the given serial parameters. local means to ignore modem # # control lines, and is generally what you want. # # Tracing is added here, note the \p, see the ser2net.yaml.5 man page # # for details on special string handling. # connection: &con00 # accepter: tcp,2012 # connector: serialdev,/dev/ttyS0,9600n81,local # trace-both: '/var/log/trace-\p' # # Like the above, but with telnet and rfc2217 enabled. With the # # gensio library that ser2net uses, protocols stack, so this stacks # # telnet on top of tcp. It also shows how long complex lines can be broken # # up, and that by-path serial ports are no problem in yaml. # connection: &con01 # accepter: telnet(rfc2217),tcp,2013 # connector: serialdev, # /dev/serial/by-path/platform-3f980000.usb-usb-0:1.2:1.0-port0, # 9600n81,local # # Set all baud rates to 115200n81 by default. # default: # name: speed # value: 115200n81 # # Enable CLOCAL by default # default: # name: local # value: true # class: serialdev # # Create a secure admin interface on tcp,2000 # # Enable mdns on it, connect with gtlssh -m admin1 # admin: &admin1 # accepter: mux,certauth,ssl,tcp,2000 # options: # mdns: true # # Define an arbitrary string substitution, we will use this later # # for a banner. # define: &banner1 This is a banner for port \N(\p)\r\n # # Create an LED to flash the ACPI standby light on serial activity. # led: &led1 # driver: sysfs # options: # device: "tpacpi::standby" # # This creates an arbitrary string we will use inside other strings. # # The quotes are necessary to keep YAML from interpreting the colons. # define: &serbase "/dev/serial/by-path/pci-0000:00:1d.0" # define: &serUSB0 "*(serbase)-usb-0:1.8.2.4:1.0" # # A connection on # # /dev/serial/by-path/pci-0000:00:1d.0-usb-0:1.8.2.4:1.0-port0 # # mapped to tcp port 2002 with telnet enabled. # # Note that it is disabled at startup. You can enable it through # # the admin interface or by changing enable to "on" and sending a SIGHUP. # # Note that even though this is disabled, you can still access it through # # the rotator. # connection: &con1 # accepter: telnet(rfc2217),tcp,2002 # timeout: 0 # enable: off # connector: serialdev,*(serUSB0)-port0 # options: # banner: *banner1 # led-tx: *led1 # led-rx: *led1 # # A connection on # # /dev/serial/by-path/pci-0000:00:1d.0-usb-0:1.8.2.4:1.0-port1 # # mapped to SCTP port 2003. The baud rate is overridden from the # # default we set before. It also demonstrates how lines can be # # continued. # # You can access SCTP ports with the gensiot command, to connect # # to this one do: # # gensiot telnet,sctp,,2003 # connection: &con2 # accepter: telnet(rfc2217),sctp,2003 # timeout: 0 # connector: serialdev,*(serUSB0)-port1,9600e71 # local=false # options: # banner: *banner1 # # # # An IPMI SOL connection. See the OpenIPMI library for details on # # how to configure this. # # Note the use of *{} to insert a password from an external file. # connection: &ipmicon1 # accepter: telnet,tcp,3022 # connector: ipmisol,lan -U admin -P *{pwfile} 192.168.27.37,115200 # # A rotator on TCP port 2020. It will alternate between the # # two previous connections. The "dummy" gensio accepter is # # useful in this case if you don't want independent access # # to each connection. This will only be bound to address # # 192.168.27.3. # rotator: &rot1 # accepter: telnet,tcp,192.168.27.3,2020 # connections: [ # *con1, # *con2 # ] # # Now lets talk about secure connections. At this stage in the state # # of computers, all your connections should be at least encrypted, and # # preferably authenticated. # # # # Note that the examples below use SCTP. TCP can be substituted. They # # also use the echo gensio, which is useful for testing. # # # # For encrypted connections to work, you need to use the ssl gensio # # over other connections. This requires a certificate and key, which # # reside in /etc/ser2net/ser2net.crt and /etc/ser2net/ser2net.key by # # default. You can, of course, change the defaults or override these # # on the accepter line. Make sure ser2net.key is not readable by # # anyone but the user running ser2net, or anyone can see your private # # key. Distribute ser2net.crt to the users so they can authenticate # # the server. You can get your certificate from a certificate # # authority for best practice. Or you can create it yourself. # # # # To create your own keys, do: # # # # gtlssh-keygen --keydir /etc/ser2net --commonname "`hostname`-ser2net" serverkey ser2net # # # # which will install them in the right place. Copy ser2net.crt to # # the accessing system. Restart ser2net. You can then connect with: # # # # gensiot telnet,ssl(CA=ser2net.crt),sctp,2004 # # # # Note that the commonname of the key is important because it # # makes the subject name of the certificate more unique. You can really # # put anything you want for what you provide to keygen. That will be the # # subject name of the certificate. # connection: &con3 # accepter: telnet,ssl,sctp,2004 # connector: echo # options: # banner: *banner1 # # Authentication with SSL is not shown, but is described in # # ser2net.yaml(5). It's fairly inconvenient. # # Now do an authenticated connection with certauth. You need a host # # key/certificate pair as in the ssl-only example above. Then you # # need to create /usr/share/ser2net/auth/. You can create a # # file named "password" in that directory and put a password there. # # Make sure it's only readable by ser2net! Or you can create a # # directory named /usr/share/ser2net/auth//allowed_certs, # # put the user's gtlssh certificate there, and run # # gtlssh-keygen rehash /usr/share/ser2net/auth//allowed_certs # # Then you can do: # # gtlssh --telnet -p 2005 @ # # and do a normal gtlssh authentication. See gtlssh(1) for details. # connection: &con4 # accepter: telnet,mux,certauth(enable-password),ssl,sctp,2005 # connector: echo # options: # banner: *banner1 # # If you just want to have encryption and no authentication but you want # # to use gtlssh for login, you set allow-authfail # # on certauth, which will run the protocol but ignore any failures, and # # then you can just log in. It's easy enough to just handle the # # certificate, though, so that's really preferred. # connection: &con5 # accepter: telnet,mux,certauth(allow-authfail),ssl,sctp,2006 # connector: echo # options: # openstr: hello # banner: *banner1 # # The following shows how to quote some strings. Notice they are all # # in single quotes. If you put them in double quotes, yaml will interpret # # the '\' sequences on it's own, and thus ser2net will not be able to # # process them. The yaml rules are complex, and the single quotes are # # not necessary in all the instance below, but it's good practice to use # # them to avoid confusion. Notice that you can do '' in a single quote # # string to add one single quote. # connection: &con6 # accepter: tcp,2007 # connector: echo # options: # openstr: 'Hello\x20there' # closestr: 'Goodbye\nnow' # banner: '''a Special Banner \d''' # closeon: 'ctrl-a\001nil\000' # # An example showing mDNS support. You must add "mdns: true" to enable # # mDNS on a port to enable it. By default it uses the name for the mDNS # # name and "_iostream._xxx" for the type, where xxx is either tcp, udp # # or sctp base on the gensio type. Everything else defaults. Don't # # set mdns-host or mdns-domain unless you know what you are doing. # # mdns-txt can be specified multiple times to add multiple strings. # # # # mdns-sysattrs on Linux adds USB attributes to the txt data from # # sysfs. If the serial port is a USB one, it will add the following # # txt fields: # # devicetype=serialusb # # bInterfaceNumber=... # # interface=... # # idProduct=... # # idVendor=... # # serial=... # # manufacturer=... # # product=... # # These fields are pulled straight from sysfs, see information on that # # for details on what these mean. Not all the fields may be present # # depending on the specific USB device. If you set this on a non-USB # # serial port, it will just add: # # devicetype=serial # # This may be extended in the future for other OSes or other device # # interfaces. # connection: &rack-3-slot-5 # accepter: tcp,2034 # connector: echo # options: # mdns: true # mdns-interface: -1 # mdns-nettype: unspec # mdns-name: rack-3-slot-5 # mdns-type: "_iostream._tcp" # mdns-domain: "local" # mdns-host: "myhost.local" # mdns-txt: "server=ser2net" # mdns-txt: "type=echo" # mdns-sysattrs: true # # An example showing all options and their defaults. Note that empty # # strings are ok for most strings, but not for things that take filenames # # or are required to be aliases. # connection: &con7 # accepter: tcp,2008 # connector: echo # options: # kickolduser: false # trace-hexdump: false # trace-timestamp: false # trace-read-hexdump: false # trace-read-timestamp: false # trace-write-hexdump: false # trace-write-timestamp: false # trace-both-hexdump: false # trace-both-timestamp: false # trace-read: # trace-write: # trace-both: # led-rx: # led-tx: # telnet-brk-on-sync: false # chardelay: false # chardelay-scale: 20 # chardelay-min: 1000 # chardelay-max: 20000 # sendon: "" # dev-to-net-bufsize: 64 # net-to-dev-bufsize: 64 # max-connections: 1 # authdir: # pamauth: "" # allowed-users: # remaddr: # connback: # connback-timeout: