From 53755faf6d18cbf3adbcff0a78cb78c1eb91addc Mon Sep 17 00:00:00 2001 From: "marcin.kosiba" Date: Mon, 12 Feb 2007 19:37:24 +0000 Subject: [PATCH] pre alpha version - initial revision --- AUDIT | 41 + BENCHMARKS | 70 + BUGS | 22 + COPYING | 346 ++++ COPYRIGHT | 2 + Changelog | 937 ++++++++++ FAQ | 245 +++ INSTALL | 166 ++ LICENSE | 8 + Makefile | 746 ++++++++ Makefile_default | 744 ++++++++ README | 39 + README.security | 2 + README.ssl | 40 + REFS | 45 + REWARD | 2 + SIZE | 8 + SPEED | 37 + TODO | 66 + TUNING | 26 + access.c | 72 + access.h | 29 + ascii.c | 78 + ascii.h | 48 + banner.c | 76 + banner.h | 33 + builddefs.h | 9 + countedPtr.h | 319 ++++ cpp_to_c_helpers.h | 16 + defs.h | 22 + event.cpp | 106 ++ event.h | 180 ++ event_auto.h | 131 ++ event_c_api.h | 280 +++ exceptions.h | 267 +++ features.c | 47 + features.h | 9 + file_logger_plugin.cpp | 179 ++ file_logger_plugin.h | 43 + filesize.h | 7 + filestr.c | 54 + filestr.h | 26 + ftpcmdio.c | 225 +++ ftpcmdio.h | 97 ++ ftpcodes.h | 76 + ftpdataio.c | 616 +++++++ ftpdataio.h | 99 ++ hash.c | 147 ++ hash.h | 15 + ipaddrparse.c | 220 +++ ipaddrparse.h | 20 + logging.c | 352 ++++ logging.h | 77 + ls.c | 436 +++++ ls.h | 45 + main.c | 341 ++++ mysql_plugin.cpp | 1420 ++++++++++++++++ mysql_plugin.h | 79 + netstr.c | 116 ++ netstr.h | 54 + oneprocess.c | 96 ++ oneprocess.h | 47 + parseconf.c | 364 ++++ parseconf.h | 21 + pgsql_plugin.cpp | 1082 ++++++++++++ pgsql_plugin.h | 57 + plugin_architecture_api.cpp | 169 ++ plugin_architecture_api.h | 103 ++ plugin_architecture_interface.cpp | 134 ++ plugin_architecture_interface.h | 35 + postlogin.c | 2176 ++++++++++++++++++++++++ postlogin.h | 15 + postlogin_events.c | 216 +++ postlogin_events.h | 24 + postlogin_requests.c | 284 ++++ postlogin_requests.h | 21 + postprivparent.c | 123 ++ postprivparent.h | 16 + prelogin.c | 252 +++ prelogin.h | 16 + privops.c | 270 +++ privops.h | 50 + privsock.c | 139 ++ privsock.h | 119 ++ readwrite.c | 98 ++ readwrite.h | 23 + request.cpp | 112 ++ request.h | 97 ++ request_auto.h | 80 + request_c_api.h | 117 ++ request_results.cpp | 13 + request_results.h | 31 + secbuf.c | 89 + secbuf.h | 27 + secutil.c | 125 ++ secutil.h | 37 + serialization_auto.h | 130 ++ session.h | 98 ++ slave_thread.cpp | 394 +++++ slave_thread.h | 10 + slave_thread_auto.h | 16 + ssl.c | 435 +++++ ssl.h | 21 + standalone.c | 303 ++++ standalone.h | 23 + str.c | 668 ++++++++ str.h | 126 ++ strlist.c | 174 ++ strlist.h | 32 + sysdeputil.c | 1193 +++++++++++++ sysdeputil.h | 59 + sysstr.c | 183 ++ sysstr.h | 40 + sysutil.c | 2648 +++++++++++++++++++++++++++++ sysutil.h | 334 ++++ tcpwrap.c | 49 + tcpwrap.h | 7 + tunables.c | 144 ++ tunables.h | 142 ++ twoprocess.c | 492 ++++++ twoprocess.h | 46 + typeid_hash.cpp | 23 + typeid_hash.h | 11 + udsrf.h | 102 ++ udsrf_auto.h | 1179 +++++++++++++ unknown_object.h | 134 ++ utility.c | 52 + utility.h | 44 + vsf_findlibs.sh | 65 + vsftpd.8 | 34 + vsftpd.conf | 117 ++ vsftpd.conf.5 | 966 +++++++++++ vsftpver.h | 7 + vsfutils.cpp | 145 ++ vsfutils.h | 19 + vsfutils_c.h | 8 + 136 files changed, 27409 insertions(+) create mode 100644 AUDIT create mode 100644 BENCHMARKS create mode 100644 BUGS create mode 100644 COPYING create mode 100644 COPYRIGHT create mode 100644 Changelog create mode 100644 FAQ create mode 100644 INSTALL create mode 100644 LICENSE create mode 100644 Makefile create mode 100644 Makefile_default create mode 100644 README create mode 100644 README.security create mode 100644 README.ssl create mode 100644 REFS create mode 100644 REWARD create mode 100644 SIZE create mode 100644 SPEED create mode 100644 TODO create mode 100644 TUNING create mode 100644 access.c create mode 100644 access.h create mode 100644 ascii.c create mode 100644 ascii.h create mode 100644 banner.c create mode 100644 banner.h create mode 100644 builddefs.h create mode 100644 countedPtr.h create mode 100644 cpp_to_c_helpers.h create mode 100644 defs.h create mode 100644 event.cpp create mode 100644 event.h create mode 100644 event_auto.h create mode 100644 event_c_api.h create mode 100644 exceptions.h create mode 100644 features.c create mode 100644 features.h create mode 100644 file_logger_plugin.cpp create mode 100644 file_logger_plugin.h create mode 100644 filesize.h create mode 100644 filestr.c create mode 100644 filestr.h create mode 100644 ftpcmdio.c create mode 100644 ftpcmdio.h create mode 100644 ftpcodes.h create mode 100644 ftpdataio.c create mode 100644 ftpdataio.h create mode 100644 hash.c create mode 100644 hash.h create mode 100644 ipaddrparse.c create mode 100644 ipaddrparse.h create mode 100644 logging.c create mode 100644 logging.h create mode 100644 ls.c create mode 100644 ls.h create mode 100644 main.c create mode 100644 mysql_plugin.cpp create mode 100644 mysql_plugin.h create mode 100644 netstr.c create mode 100644 netstr.h create mode 100644 oneprocess.c create mode 100644 oneprocess.h create mode 100644 parseconf.c create mode 100644 parseconf.h create mode 100644 pgsql_plugin.cpp create mode 100644 pgsql_plugin.h create mode 100644 plugin_architecture_api.cpp create mode 100644 plugin_architecture_api.h create mode 100644 plugin_architecture_interface.cpp create mode 100644 plugin_architecture_interface.h create mode 100644 postlogin.c create mode 100644 postlogin.h create mode 100644 postlogin_events.c create mode 100644 postlogin_events.h create mode 100644 postlogin_requests.c create mode 100644 postlogin_requests.h create mode 100644 postprivparent.c create mode 100644 postprivparent.h create mode 100644 prelogin.c create mode 100644 prelogin.h create mode 100644 privops.c create mode 100644 privops.h create mode 100644 privsock.c create mode 100644 privsock.h create mode 100644 readwrite.c create mode 100644 readwrite.h create mode 100644 request.cpp create mode 100644 request.h create mode 100644 request_auto.h create mode 100644 request_c_api.h create mode 100644 request_results.cpp create mode 100644 request_results.h create mode 100644 secbuf.c create mode 100644 secbuf.h create mode 100644 secutil.c create mode 100644 secutil.h create mode 100644 serialization_auto.h create mode 100644 session.h create mode 100644 slave_thread.cpp create mode 100644 slave_thread.h create mode 100644 slave_thread_auto.h create mode 100644 ssl.c create mode 100644 ssl.h create mode 100644 standalone.c create mode 100644 standalone.h create mode 100644 str.c create mode 100644 str.h create mode 100644 strlist.c create mode 100644 strlist.h create mode 100644 sysdeputil.c create mode 100644 sysdeputil.h create mode 100644 sysstr.c create mode 100644 sysstr.h create mode 100644 sysutil.c create mode 100644 sysutil.h create mode 100644 tcpwrap.c create mode 100644 tcpwrap.h create mode 100644 tunables.c create mode 100644 tunables.h create mode 100644 twoprocess.c create mode 100644 twoprocess.h create mode 100644 typeid_hash.cpp create mode 100644 typeid_hash.h create mode 100644 udsrf.h create mode 100644 udsrf_auto.h create mode 100644 unknown_object.h create mode 100644 utility.c create mode 100644 utility.h create mode 100755 vsf_findlibs.sh create mode 100644 vsftpd.8 create mode 100644 vsftpd.conf create mode 100644 vsftpd.conf.5 create mode 100644 vsftpver.h create mode 100644 vsfutils.cpp create mode 100644 vsfutils.h create mode 100644 vsfutils_c.h diff --git a/AUDIT b/AUDIT new file mode 100644 index 0000000..1e6d2df --- /dev/null +++ b/AUDIT @@ -0,0 +1,41 @@ +This file contains information on the audit status of the code in this program. +Each file has an audit code of between 1 and 5, ranging from 1 being unaudited +and 5 being heavily audited by multiple competent people. + +The important rule is that when a file is changed, the audit status goes back +to 1, _unless_ the change(s) are audited very carefully as they go in. + +access.c 2 +ascii.c 3 +banner.c 2 +dirchange.c 3 +filestr.c 3 +ftpcmdio.c 3 +ftpdataio.c 2 +hash.c 1 +ipaddrparse.c 1 +logging.c 3 +ls.c 2 +main.c 3 +netstr.c 3 +oneprocess.c 3 +parseconf.c 2 +postlogin.c 2 +postprivparent.c 3 +prelogin.c 3 +privops.c 2 +privparent.c 3 +privsock.c 3 +secbuf.c 3 +secutil.c 3 +ssl.c 1 +standalone.c 1 +str.c 2 +strlist.c 2 +sysdeputil.c 2 +sysstr.c 3 +sysutil.c 2 +tunables.c 3 +twoprocess.c 2 +utility.c 3 + diff --git a/BENCHMARKS b/BENCHMARKS new file mode 100644 index 0000000..51432f9 --- /dev/null +++ b/BENCHMARKS @@ -0,0 +1,70 @@ +- See also SPEED + +Update 2nd Nov 2001 +ftp.redhat.com ran vsftpd for the RedHat 7.2 release. vsftpd achieved 4,000 +concurrent users on a single machine with 1Gb RAM. Even with this insane user +count, bandwidth remained totally saturated. The user count could have been +higher, but the machine ran out of processes. + +-- +Below are some quick benchmark figures vs. wu-ftpd. This is an untuned BETA +version of vsftpd (0.0.10) + +The executive summary is that wu-ftpd got a thorough thrashing. The most +telling statistic is wu-ftpd typically failing to sustain 400 users, whereas +vsftpd copes with 1000 with room to spare. + +A 2.2.x kernel was used. A 2.4.x kernel should make vsftpd look even better +relative to wu-ftpd thanks to the sendfile() boosts in 2.4.x. A 2.4.x kernel +with zerocopy should be amazing. + +Many thanks to Andrew Anderson +-- + +Here's some benchmarks that I did on vsftpd vs. wu-ftpd. The tests were +run with "dkftpbench -hftpserver -n500 -t600 -f/pub/dkftp/". The +attached file are the summary output with time to reach the steady-state +condition. + +The interesting things I noticed are: + +- In the raw test results, vsftpd had a much higher peak on the x10k.dat +transfer run than wu-ftpd did. Wu-ftpd peaked at ~150 connections and +bled down to ~130 connections, while vsftpd peaked at ~400 connections and +bled down to ~160 connections. I tend to believe the peaks more than the +final steady-state that dkftpbench reports, though. + +- For the other tests, our wu-ftpd setup was limited to 400 connections, +but in about half of the x100k/x1000k runs could not even sustain 400 +connections, while vsftpd handled 500 easily on those runs. + +- During the peak runs at x10k, the machine load with vsftpd looked like +this (I don't have this data still for the wu-ftpd runs): + +01:01:00 AM all 4.92 0.00 21.23 73.85 +03:31:00 AM all 4.89 0.00 19.53 75.58 +05:11:00 AM all 4.19 0.00 16.89 78.92 +07:01:00 AM all 5.61 0.00 22.47 71.92 + +The steady-state loads were more in the 3-5% user, 10-15% system. For the +x100/x1000 loads with vsftpd, the system load looked like this: + +x100k.dat: +09:01:00 AM all 2.27 0.00 9.79 87.94 + +x1000k.dat: +11:01:00 AM all 0.42 0.00 5.75 93.83 + +Not bad -- 500 concurrent users for ~7% system load. + +- Just for kicks I ran the x1000k test with 1000 users. At peak load: + +X1000k.dat with 1000 users: +04:41:00 PM all 1.23 0.00 46.59 52.18 + +Based on what I'm seeing, it looks like if a server had enough bandwidth, +it could indeed sustain ~2000 users with the current 2 process model +that's implemented in vsftpd. I did notice that dkftpbench slowed down +the connection rate after 800 connections. I'm not sure if that was a +dkftpbench issue, or if I ran into something other limit. + diff --git a/BUGS b/BUGS new file mode 100644 index 0000000..d9b729c --- /dev/null +++ b/BUGS @@ -0,0 +1,22 @@ +BUGS +==== + +This file, surprisingly enough, contains a list of known outstanding bugs +in the program. Bugs that get documented here are typically not particularly +serious, and may never get fixed. Serious bugs will get fixed immediately. + +- RFC compliance: if we get no PORT or PASV, looks like we're supposed to +assume a PORT to the same IP as control connection, and port 20. +- RFC compliance: shouldn't include directories in NLST. Note that wu-ftpd +complies here, almost all other FTPd's don't +- ls should list nothing, but it lists the +directory name itself +- In ASCII mode, the SIZE command needs to take into account the size of +the file _after_ ASCII linefeed mangling? +- ASCII mode uploads: we're only supposed to remove \r if they preceed \n, +but I rip them out unconditionally. +- Security model: vsf_privop_get_ftp_port_sock() should probably do the +connect() to the remote location itself. +- If someone has one of the timeouts (command, data) setup, but not the other, +then timeout will behave whackily. + diff --git a/COPYING b/COPYING new file mode 100644 index 0000000..85030dd --- /dev/null +++ b/COPYING @@ -0,0 +1,346 @@ +vsftpd is licensed under version 2 of the GNU GPL. +As copyright holder, I give permission for vsftpd to be linked to the OpenSSL +libraries. This includes permission for vsftpd binaries to be distributed +linked against the OpenSSL libraries. All other obligations under the GPL v2 +remain intact. + + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc. + 59 Temple Place, Suite 330, Boston, MA 02111-1307 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 Library 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 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 Library General +Public License instead of this License. diff --git a/COPYRIGHT b/COPYRIGHT new file mode 100644 index 0000000..2595256 --- /dev/null +++ b/COPYRIGHT @@ -0,0 +1,2 @@ +Everything within this tar archive is Copyright (c) Chris Evans. + diff --git a/Changelog b/Changelog new file mode 100644 index 0000000..7c656d9 --- /dev/null +++ b/Changelog @@ -0,0 +1,937 @@ +0.0.1 initial versioned tarball released +---------------------------------------- + +- Added "-ldl" to LIBS to get linking to work on RedHat6.1 +- Add RedHat6.1 on list of tested platforms :) + +0.0.2 packaged +-------------- + +- Emit version in greeting string +- In PORT command, reject numbers <0 or >255. Problem noted by Solar Designer, + +- Allow an option AND a path for LIST/NLST, e.g. "LIST -al /pub". Reported by +Bill Nottingham , using ncftp. Further noted by Colin +Hogben using emacs and James Antill . +- Don't prepend directory path for LIST (but still so for NLST). Noted by +Colin Hogben and Ingo Luetkebohle +- Fix problem listing non-existant or unreadable directories - just return +a blank listing rather than an error. Problem noted by Martin Sillence +, using squid. +- Fix KDE's downloads (via KFM), it was using the "SIZE" command which I had +not implemented. Reported by Simon Dales and Jo Dillon +. Apparently implementing SIZE also fixed lftp's download +time estimator, reported by Ingo Luetkebohle +- Remove abornal_exit() from utility.c +- Fix so we don't write "500 OOPS: child died" upon QUIT. Reported by Solar +Designer, and Tim Bagot + +0.0.3 packaged +-------------- + +- Oops: fix so we don't emit a status 150 mark unless we actually got a +connection from the client (stops some clients hanging trying to list an +inaccessible directory) + +0.0.4 packaged +-------------- + +- In verbose directory listing, report symlink targets. Use the traditional +syntax of: "link_name -> target_path" +- Damn netscape! The comma in the response text to PASV confused it, so it +had to be removed. Discovered with tcpdump! +- Don't require clients to redo PORT or PASV if a RETR or STOR fails due +to inability to open/create file. Fixes Netscape symlink navigation problem. +- Fix for listing absolute paths with only one /, e.g. "ls /.message" was +failing + +0.0.5 packaged +-------------- + +- Remove README.ftpproto +- Add SECURITY/OVERVIEW +- Add SECURITY/DESIGN +- Note that as a security tweak, we should lose more privs if we're configured +for anonymous only logins (TODO) +- Add SECURITY/IMPLEMENTATION, SECURITY/TRUST, but nothing in them yet. +- Convert str.c to vsf_sysutil_*. This leaves the following to do: + checkauth.c, main.c, postprivparent.c, privparent.c, privsock.c, utility.c +- Convert privparent.c to vsf_sysutil_*. +- Create BUGS and move existing listed bugs from TODO into this new file +- Add parseconf.h, parseconf.c to handle parsing of a config file (work in +progress) +- Fix change_full_credentials() in utility.c, to always chdir() even if we +are not going to do a chroot() +- Rename get_random_byte() to vsf_sysutil_get_random_byte(), and move from +utility.c to sysutil.c +- Create new file secutil.c, move change_full_credentials() to it and rename +- Convert utility.c to vsf_sysutil_*. +- handle_local_login(): don't look up username; common_do_login() does it +- implement different tunable umask() values for local/anonymous users +- implement SITE UMASK +- implement SITE CHMOD +- whoops! allow non-anonymous users to overwrite files with STOR + +0.0.6 packaged +-------------- + +- SECURITY: when in anonymous-only mode, reject usernames that aren't the +anonymous usernames. This is hoping some FTP clients will be stopped from +sending a cleartext password. Idea from Gerald Teschl . +- Decided to put "telnet strings" on the back burner :) +- Sprinkling of static in main.c +- Complete parseconf.c config file parsing and plug it into main.c +- Convert main.c to vsf_sysutil_*. This leaves + checkauth.c, postprivparent.c and privsock.c +- Now we have runtime config, make compiled in defaults extra paranoid +- Implement "tunable_anon_world_readable_only" to only serve publicly +readable files anonymously +- Add sample "vsftpd.conf" +- Eww - missing "return" in parseconf.c +- Move ASCII mode transfers out of critical section in TODO +- parseconf.c: if an integer starts with "0", treat it as octal +- Ban "SITE CHMOD" if !tunable_write_enable +- Wrote SECURITY/TRUST +- Wrote SECURITY/IMPLEMENTATION, probably more to come +- Update INSTALL +- Add "tunable_nopriv_user" +- Update parseconf.c with the two latest new config variables +- Add sysdeputil.h, sysdeputil.c for system specific facilities, i.e. +capabilites, authentication. +- Lose checkauth.c,h - they moved into sysdeputil.c,h +- Lose config.h - it moved into sysdeputil.c +- Convert postprivparent.c to vsf_sysutil_* (leaves privsock.c) +- Convert privsock.c to vsf_sysutil_*. All done, yay!! :) +- D'oh! Missing "!" in postlogin.c refused to server publicly readable files:) +- Fix chown() of uploaded files (broken initialization order in main()) +- Add SPEED, and fill it with wild speculation +- Rename distribution directory "vsftpd-x.x.x" (note the added "d") + +0.0.7 packaged +-------------- + +- Build with -O2 +- Fix "uninitialized" warnings -O2 exposed - the one in capabilities setup +could be nasty! +- Nail warning in vsf_sysutil_sendfile(). We're now "-Wall warning free" +- Build with -Werror to signal intent to _stay_ warning free +- A few int -> long in the area of file sizes and offsets +- Remove comma's at end of enum lists (-pedantic caught it) +- Impact from fixing warnings caused by -pedantic +- Date format %e -> %d in date display, %e isn't everywhere +- Paranoia in vsf_sysutil_malloc() +- Clean up interface to substring searching in str.c +- Cleanups in str.c +- Squash most "unsigned<->signed" conversions exposed by -Wconversion +- Lose "-g" to CFLAGS; after all we're bug-free now ;-) +- Add "AUDIT" +- Fix up a bunch of potential 64-bit issues (maybe >2Gb files will work on +64-bit platforms now, no way to test) +- Implement PR_SET_KEEPCAPS support for 2.2.18+ and 2.4.0+ kernels +- In sysdeputil.c, change NULL -> 0 to help Solaris build problem +- Repair vsf_sysutil_sendfile() and the caller +- Logging: log the username +- Logging: don't log "//" as start of filenames under certain conditions +- Logging: log the date. Logging is almost useful now! +- Logging: log MKD commands too; they are used in anon ftp a fair bit +- Take the trouble to look into partial reads/writes. Looks like we are safe. +- vsf_sysutil_read and vsf_sysutil_write now hide EINTR and retry +- Replace some vsf_sysutil_{read,write} usage with +vsf_sysutil_{read,write)_loop which handles partial reads and writes +- Implement a sendfile() replacement for systems which lack it +- Implement runtime checking for system specific Linux stuff, i.e. +prctl(PR_SET_KEEPCAPS). This is inspired by RedHat7.0 headers claiming to +be a 2.4.0 kernel, but actually you are running on 2.2.x! :-( +- Strip the build executable at link time + +0.0.8 packaged +-------------- + +- A few incorrect sizeof()'s in postlogin.c, thanks to Antonomasia + for noting these. +- Decide that ASCII support isn't too important for now (waiting for users to +demand it). Also decide that ABOR is a must :( Thanks to Zach Brown + for the discussion. +- More TODO items thanks to Stephen White - 2.0.x +issues. +- Provide a definition for SHUT_RDWR in sysutil.c, not all systems have that +definition yet. Thanks Stephen White . +- Tidy privparent.c +- Decide ASCII _is_ quite important, thanks Solar ;-) +- Bit of extra paranoia in sysutil.c: don't call mem*() if size == 0 +- Tidy str.c +- Command line: if vsftpd has an argument, it is a path to a config file. +- Set TCP_NODELAY on command stream +- Don't lseek() for RETR in common case with REST set to 0 +- Correct error code for transfer after succesful connection (425 -> 426) +- ABOR support. Bah. +- APPE support (why not, it was trivial). Putting off ASCII support ;-) +- Add ASCII transfer support. Bah. +- Tidy up sysutil.c, fix breakage in read_loop and write_loop. + +0.0.9 packaged +-------------- + +- Remove ".message" from distribution. Thanks Mitchell Blank Jr + +- Note where I can get some load testing software, thanks to Dan Kegel +. I'll do that soon because I hope to waste wu-ftpd. +- Fix an Alpha build warning and check return value from final pam_end(). +Reported by Solar Designer . +- Add xinetd.d/vsftpd, from Kurt Seifried . +- Integrate comments/fixes into SECURITY documentation, thanks to Antonomasia + +- SECURITY: default tunable_chroot_local_user to 0, because it is dangerous to +give users write access to the filesystem root (think of opening trusted +files relative to the root). Thanks again Solar Designer +. +- Add "make install" target. Currently it is minimal! +- Clearer error message if vsftpd is started manually. Suggestion from +Tom . +- Report futuristic or old (>6 months) dates in a different format, showing +the year like /bin/ls does. +- Add KERNEL-2.4.0-WARNING. Whoo-hoo. Why do all my non-trivial programs seem +to trigger kernel bugs? +- SECURITY: refuse to allow anonymous logins if some bonehead has configured +the anonymous ftp user with write access to the ftp root. +- Fix ASCII downloads so that \n UNCONDITIONALLY maps to \r\n. This behaviour +is now consistent with wu-ftpd and results in simpler code. +- Fix ASCII uploads to not to fail to strip some \r characters. Noted by +Mitchell Blank Jr . +- Add TODO items: log transfer rate and anonymous password. Andrew Anderson +. + +0.0.10 packaged +--------------- + +- Remove errant #include from sysutil.c. Noted by Jan-Frode +Myklebust +- Use gettimeofday(2) not time(2), for better resolution. +- Add transfer rate to the log +- Add to sysutil.c, spotted by Kevin Vajk . +- Spell "LICENSE" correctly: Kevin Vajk . +- Use fcntl() for locking instead of flock() because it is much more standard. +flock() usage noted by Kevin Vajk . +- Use more portable IPPROTO_* instead of SOL_* (IPPROTO_IP, IPPROTO_TCP). +Thanks to Neil Blakey-Milner porting to FreeBSD. +- Start of Solaris port, thanks to Kurt Seifried +for access to a Solaris 8 box. +- Portability fix: include before . +- Port to Solaris 8: new directory port. New file porting_junk.h. New file +solaris_bogons.h +- Add vsf_findlibs.sh to cater for different platform link requirements. Now +builds on Solaris and Linux with "make". +- struct sockaddr casts to kill Solaris warnings. +- sysdeputil.c: remove unused variable warnings. +- sysutil.c: use _exit() instead of exit() to avoid libc doing stuff on exit. +Fixes segfault reported by Joshua Hill . +- Add BENCHMARKS. Many thanks to Andrew Anderson . +- Fix disconnect/crash if SIGURG received whilst blocking on command stream. +- Update INSTALL with more platforms. + +0.0.11 packaged +--------------- + +- Brag about performance in README. And why not. +- Better bail-out message if the "ftp" anonymous user isn't found +- Better bail-out message if the secure chroot directory isn't found +- Introduce tunable_one_process_model and start work on it +- Fix rare segfault on exit - race leading to infinite stack recursion +- Don't bail out if we didn't get an argv[0]. Who cares? Noted by Kurt Seifried +. +- Change logged date format to include the year. +- Add option to log in standard (wu-ftpd like) "xferlog" format. +- Cater for sendfile() returning EINTR in sysdeputil.c +- Use SO_LINGER on data sockets, to get accurate transfer rates! +- Cater for an interrupted blocking close() +- Tuning: eliminate 3 mprotect(), 1 munmap() and 1 mmap() system call per +command read. +- Prevent infinite loops calling sendfile(). Two bugs - we needed to check +the sendfile() return for 0 (doh!!) and also, we sometimes did lseek() on +a file, to beyond its end. Thanks to Daniel Veillard +for reporting. +- Tuning: cache fd's for /etc/passwd and /etc/group to avoid syscalls. +- Tuning: "assist" the get*uid(), get*nam() calls to not make lots of useless +syscalls, if /etc/group and /etc/passwd are missing. Thanks to Daniel Veillard + for reporting. +- Use SO_LINGER timeout of 5 mins; INT_MAX seemed to do nothing! +- Finally(!) fix transfer rate timing. + +0.0.12 packaged +--------------- + +- Update INSTALL. Mention the config file can be given on the command line. +- Lower VSFTP_MAX_COMMAND_LINE to 4096 (wu-ftpd uses 512 I think). +- Add RedHat/vsftpd-rh7.spec, kindly provided by Emmanuel Galanos +. +- Add more RedHat/* spec files etc, kindly provided by Andrew Anderson +. +- Cleanup: move two process model code to "twoprocess.c". +- Damn! Make the file lock _block_ if it's busy, in sysutil.c. +- Finish implementing one process model - benchmarks to follow +- Don't log success if the download is ABOR'ed during the blocking close(). +- Build on systems without PAM (obviously local logins won't work..) +- Beware of FreeBSD accept() bug: ai32@drexel.edu +- Implemented a customizable ftp banner with "ftpd_banner" config file setting +- Builds on OpenBSD 2.8 - woohoo +- FreeBSD: look for libpam.so* in /usr/lib +- FreeBSD: add #include otherwise CMSG_* break. +- Kill privparent.[ch] - merged them into twoprocess.c +- Enable SIGCHLD handler _before_ forking - should nail a race which could lead +to zombies. Inspired by zombie report from Joe Klemmer . +- Data connection timeout code. +- ftpcmdio.c: Don't cancel the alarm when we get a command. For safety, we +insist that that the only way to "cancel" the alarm is to reset it. This +prevents hangs blocking on write() to the command stream. Of course, data +transfers are long running operations and have their own timeouts. +- Data transfer timeout now kills session. +- Take care that no writes block once we've decided to abandon ship. +- FreeBSD sendfile() support. I wonder if it works! + +0.0.13 packaged +--------------- + +- Split out directory listing code into ls.c +- Change blocking accept() and connect() code to use select() not SIGALRM! +- Remove alarm() timeout junk from file locking in logging.c +- Cater for signals interrupting the blocking file lock +- Whoops: fix data timeout incorrectly going off. Noted and fixed by Joshua +Hill . +- Implement tunable_pasv_promiscuous to relax PASV IP checks. Useful if you +are playing with secure tunneling of command connection. Idea, patch from +Seth Vidal . +- Much better line-by-line file reading string buffer functions. +- Use the above better functions for directory messages and config file +reading. This eliminates a probable quadratic algorithm, i.e. it's a speedup. +- Explictly free certain buffers rather than using the static trick. For +example, the config file buffer which is only used once. +- Massive cleanup and refactoring of login code. +- Add ability to specify file containing list of banned e-mail addresses for +anonymous users. Apparently a required feature for big sites trying to avoid +DDoS attacks. +- Add ability to specify file containing list of users to chroot(), request +from helo , who also persuaded me not to use the homedir +hack in /etc/passwd. +- Add TODO: PASV port range config setting, for firewalled setups. From Rafal +Wojtczuk . +- Rudimentary support for non-PAM local user authentication, with +encouragement and helpful discussion from D Richard Felker III +. +- Use MAP_ANON instead of mmap() /dev/zero for anonymous pages. It saves +using a file descriptor. Neither are standard(?) but MAP_ANON seems to work +on a superset of systems compared with mmap() /dev/zero. +- Ability to specify a PASV local port range with pasv_min_port and +pasv_max_port. Request from Rafal Wojtczuk . +- Non-PAM authentication: check /etc/shells, and support shadow password and +account expiry. +- First cut at a vsftpd.conf man page! (vsftpd.conf.5) + +0.0.14 packaged +--------------- + +- Default to ASCII mode transfers, as per RFC. Bug noted with Macintosh client +by William Day . +- Implement "ls -a". +- Implement "ls -r". +- Implement "ls -l", i.e. "NLST -L" now works +- Implement "ls -t". Superb - now the oft-used "ls -ltr" works! +- setproctitle() support - FreeBSD only in the first cut. +- setproctitle() on Linux support - what a hack! This crap really needs kernel +support. I'm ashamed I bothered. +- Repair the contributed spec files a bit, based on reports from Oleg Drokin + and Jakob Lichtenberg . +- Show remote IP and local username in setproctitle() support. +- Add vsftpd.8 man page, thanks to Daniel Jacobowitz . +- In sysdeputil.c, check macros LINUX_VERSION_CODE and KERNEL_VERSION are +defined. From James Antill . +- Workaround a broken firewall that expects a very precise PASV response. We +now match wu-ftpd. Many many thanks to Jakob Lichtenberg for +his help. +- If tunable_anon_world_readable_only (default), don't list directories unless +they are world readable. +- Use qsort() for directory sorting - eliminates gross quadratic sorting. +Turbo charges directory listings with 1000's of entries. +- Fix big memory leak in str_list_free(). +- Simplify + reduce heap usage in strlist.c +- Optimize away lots of excessive heap usage and redundant copying in str.c +- By default, show numeric user/group id's in directory listings. Makes +generating directory listings perhaps 4 times(!) faster, and is noticeable +with e.g. 5000 entries in a directory. n.b. this performance figure is as +measured on a glibc-2.2 system, so glibc would seem to be inefficient. +- Don't use MSG_DONTWAIT - prefer the more portable fcntl()/O_NONBLOCK. Fixes +glibc-2.0 build issues. +- Work around broken Linux-2.0 unix fd passing. Now builds/runs on RH5.2. +- Build fixes for FreeBSD 3.5, with help from Jerry Walsh . +- Only restrict directory listings to world-readable for _anonymous_ users! +Thanks again Jerry Walsh for the report. +- Add TUNING +- Special case for security/performance: if we need _no_ privilege, then +force one process model. Security: root dropped totally straight away. +Performance: no messing around forking etc. +- Minor performance tweaks, don't leave big mappings lying around from +config file parsing. + +0.0.15 packaged +--------------- + +- Argh. Fix SuSE 6.0 build issue (time_t used but not defined). Reported by +Peter Stern . +- Another SuSE 6.0 issue - another damn system lacking CMSG_SPACE etc. +- Cope with any return value from blocking close(2). Previously, we missed +EAGAIN, which some systems might return (not Linux). +- New wizzy synchronous signal framework, to prevent re-entrancy issues. It +presents an interface very similar to the traditional UNIX async interface. +Technically this is a security fix; imagine a SIGURG (user controllable!) +coming in whilst we are deep inside glibc. The SIGURG handler is non-trivial +and may well re-enter and upset glibc. Specific example: the malloc subsystem. +- When handing SIGURG, account the time taken under the data tranfer timeout. +- Install the command timeout handler before we write anything to the remote. +- Cleanup capabilities handling to be taken care of in secutil.c. +- Fix bug: one_process_model mode could lose supplementary groups. +- Add "SIZE" file. +- Make one_process_model work with the anon deny e-mail list. +- Massive cleanups. Start moving static state into a session structure. +- Oops - fix Solaris 8 build by fixing include order in porting_junk.h, and +include a dirfd() replacement. Noted by William Yodlowsky + and Mike Batchelor . +- Fix return of a void function call in a void function. It upsets Sun's +compiler. (gcc is fine with it, I'm not sure if it's against the rules). +Noted by Mike Batchelor . +- Make it possible to use port ranges starting lower than 5001, from +Matthew Kirkwood . +- Use a /dev/zero mmap() fallback if we do not find MAP_ANON. This should +fix the build on Solaris 2.6, 2.7 machines. Reported by Mike Batchelor +. Also noted as one of the problems facing an IRIX build. +- Add MDTM support, so clients like ncftp can set the date on downloaded files. +- Add irix_bogons.h, trying to port to IRIX 6.5, with help from Jan-Frode +Myklebust . +- Don't reference "struct msghdr.msg_flags", not all systems have it. Clear it +with vsf_sysutil_memclr() instead. Found on IRIX 6.5.11 +- Cater for systems lacking getusershell(), e.g. IRIX 6.5.11, by not using it. +- Fix compiler error with header files claiming 2.4 headers but only having +2.2 headers. Reported by Ben Ricker . +- Kill warning on system without capabilities. +- Add -R option to ls (disabled by default), to cater for broken clients which +assume it is present (e.g. mirror). +- Add "Makefile.sun", from Mike Batchelor . +- Fix PORT transfer crashes with "one_process_model". Reported by +Andrew Anderson . +- Cater for HP-UX shared libraries which end in ".sl", from Kevin Vajk +. +- Add hpux_bogons.h, and make MAP_ANON a synonym for MAP_ANONYMOUS. +- Move send_fd and recv_fd to sysdeputil.c and provide old-style fd passing +code for IRIX and HP-UX. +- Get it going on HP-UX 11.11 and HP-UX 10.20, thanks to Kevin Vajk +. Minor changes to hpux_bogons.h +- Update vsftpd.conf with "ls_recurse_enable". +- Get it going on IRIX 6.5.11, thanks to Jan-Frode Myklebust +. +- Fix reporting of filenames in MKD operations (regression since 0.0.15). +- Wow - lots of contributed .spec files. Adopt those from Seth Vidal +. +- Fix FreeBSD build. + +0.9.0 packaged +-------------- + +- Fix .spec files to include URL, from Seth Vidal . +- Don't let unprintable characters escape into setproctitle(). Thanks to +Solar Designer for the suggestion. +- Make the PAM service name a tunable, suggestion from Solar Designer. +- Add option to log all FTP protocol (log_ftp_protocol). +- Log logins, successful or failed. +- Refuse to download a file in ASCII mode if REST position != 0. Solar +reminded me by looking in the BUGS file. +- Clearly mark an ASCII download in the FTP response string. +- Argh. Fix broken upload timeout again (goes off erroneously). +- Fix logging of FTP protocol, add logging of pid. Reported by Frank Fiamingo +. +- Fix bug where logging code bug()'s on the second logged operation, iff +logging is in fact disabled! Reported by Alexander Schreiber +. +- From Solar: be paranoid about libc implementations of isprint() in sysutil.c +- Careful not to write any unprintable characters into the log. +- fchmod() files that we fchown(), to prevent suid games, etc. +- Cleanups, added comments to some headers. +- Minor speedups to some str.c string handling functions. +- Joe Klemmer reports zombies again! Nail a couple of +races: make the SIGCHLD handler async, and cater for an interrupted wait(2) +syscall. +- If chroot_local_user=YES then chroot_list_enable becomes a list of users to +NOT chroot(). With input from Lars Hecking . + +0.9.1 packaged +-------------- + +- DAMN! Fix silly "missing newline" logging bug. + +0.9.1 repackaged +---------------- + +- Refuse to start if local_enable and anonymous_enable are NO, hit by +Lars Hecking . +- Report anonymous e-mail in the LOGIN log event, idea from Joachim Blaabjerg +. +- Fix man page install in vsftpd-rh7.spec, from Matthew Galgoci +. +- Fix chown_upload bug noted by brett . +- Add concept of guest user, idea from Andrew Anderson . +- Simple bandwidth limitation, inspired by Mads Martin J�rgensen +. +- Fix chown_upload bug in a different way. +- Correct *_umask details in vsftpd.conf.5, from brett . +- Don't show .files unless "ls -a" was specified, n.b. this differs in +behaviour from wu-ftpd, but not proftpd. +- Implement directory write(2) buffering, for a 33% reduction in CPU used to +send big dirs. Activate the bandwidth limit on directory listings. +- HPUX enhancements: setproctitle and sendfile. Thanks to Kevin Vajk +. +- We DON'T need to follow symlinks on "ls -R" - phew. +- Add README.solaris. Thanks to Mike Batchelor . +- Implement passing remote host to PAM (for pam_access etc.), thanks to +Emmanuel Galanos . +- Fix guest_enable so that this means all non-anonymous users are guest users. +- Add ability to deny selected users before they get the chance to send their +cleartext password!! +- Fix FreeBSD build - use a cast instead of floor() which needs libm. + +0.9.2 packaged +-------------- + +- Fix potential leak in PAM handling code. +- Fix build in the non-PAM case (dammit!!). Reported by Alexey E. Korchagin + and Michael Fengler . +- Include filename and size in bytes in the "here comes the data" 150 message. +- Change link flags from "-s" to "-Wl,-s" +- Add libcap support - should fix ia64, Alpha build problems with syscalls. +- Tidy up vsf_findlibs.sh +- Work with NFS mounted home dirs and root_squash, thanks to Hunter Matthews + for the report. +- Add FAQ. +- Improve "make install". +- Fix Solaris build (nanosleep is in a separate library, typical). +- Fix REST + STOR combination, investigation inspired by Mike Batchelor +. + +0.9.3 packaged +-------------- + +- Update xinetd file to reflect /usr/local location. Thanks to Fridtjof +Busse . +- Make our 150 response code match wu-ftpd - allows broken "ange-ftp" of +emacs to do a percentage complete indicator. Reported by Jonathan Kamens + via Andrew Anderson . +- Fix build on S390, ia64 platforms (poor kernel includes). Patch from +. +- Fix up vsf_findlibs.sh to cater for RedHat7.2 which has libcap. Reported +by Chris Burton . +- Boast some more in BENCHMARKS. +- Add anon_root and local_root, inspired by Ole Tange . +- Fix up vsf_findlibs.sh to cater for broken Mandrake, and also consider +the case of missing PAM headers (no pam-devel installed). Thanks to +Jeff Baldwin for access to Mandrake. + +At this point: 1.0.0 packaged and released +------------------------------------------ +Ah, the wonderful psychology of release numbers +----------------------------------------------- + +- Fix IRIX build (capabilities issue), Jan-Frode Myklebust +. +- Fix FreeBSD build, reported by Jim Breton +. +- Fix Debian build, reported by Brian Clark . + +1.0.1 packaged +-------------- + +- Fix .spec files to use /usr/local/sbin not /usr/sbin, noted by Bill Unruh +. +- Small doc tweaks and improvements(?) +- Add COPYING, the GNU GPL version 2. +- Add use_localtime config option to override the use of GMT times. +- Add tunable_check_shell (default YES) so people can disable this if they +are not using PAM. +- AIX 5.1 build support, thanks to Jan-Frode Myklebust +. +- Add "hide_ids" option to show user/group in directory listings as "ftp". +Request from Solar. +- Use the seemingly more portable setreuid() and setregid(), poxy HP. +- Use status 550 instead of 500 for known but disabled commands. +- Rename "dirchange.[ch]" to "banner.[ch]". +- Multiline connect banner support via "banner_file" config option. +- Minor error message changes. +- Add more FAQ entries. +- Add patch to specify PASV address - thanks to Mike McLean . +- Drop the 2.4.0 kernel warning file +- Rudimentary standalone listener support - to be expanded in a later release. +- If sendfile() returns EINVAL just fall back to normal routines - handles +non-pagecache backed files. +- Add "port_promiscuous" setting - should help enabling FXP. +- Modify anon_root and local_root to change directory _before_ applying the +chroot(). +- Open all files O_NONBLOCK to avoid pipes blocking on open. +- Support wu-ftpd style per-user chroot() via /./ in /etc/passwd HOMEDIR. +- Add SIGHUP support to new built in listener. +- Per-user config overrides, via "user_config_dir" - woohoo! +- Warning fixes, i.e. change "index" to "indexx" thanks to Olaf Kirch +. +- Make sure the standalone daemon doesn't leak zombies! +- Supposedly fix kernel messages about MSG_PEEK race - thanks to advice from +Alexey . +- Add global client limit for standalone mode. +- Add username that failed when we die with str_getpwnam. +- Add a bunch of documentation under EXAMPLES. + +At this point: 1.1.0 package released +------------------------------------- + +(Note - 1.1.0 also included large file (>2Gb) support). + +- Fix port_promiscuous, oops! Thanks to Bj�rn-Ove Heimsund +. +- Fix to support umasks which create executable files. Reported by +"Martin, Andreas" . +- Make the messages more.. professional :( Thanks to Steven G. Taylor +. +- Allow anon users to append to files if they can delete files! Suggestion +from Michael Leuchtenburg . +- Hopefully fix Solaris build (-lresolv) +- Replace atoll() with a homebrew - modern FreeBSD, OpenBSD lack it. +- Different solution for a umask which creates executable files: +file_open_mode. +- First attempt at Tru64 build, working with . +- A few minor FAQ additions. +- Change date format in the log from Sep 09 -> Sep 9. Avoids breaking some +broken log parsers. +- Make "INSTALL" better and clearer. +- Fix passwd_chroot_enable, reported by James Jones . +- Finish Tru64 building :-) +- Add tunable_no_anon_password as asked for by Stephen Quinney +. + +At this point: 1.1.1 package released +------------------------------------- + +- Add per-IP connection limits in standalone mode. +- Add logging of refused connect due to global or IP connection limits. +- (Many thanks for testing and suggestions from Rob van Nieuwkerk + and Adrian Reber . +- Make connection limit exceeded messages nonblocking. +- Don't exit the listener if fork fails. + +At this point: 1.1.2 package released +------------------------------------- + +- Support for tcp_wrappers. +- First stab at Solaris sendfilev() support. +- Don't bomb out the listener on SIGHUP if the config became invalid. +- End vsf_findlibs.sh with "exit 0;" - thanks Lars Hecking ! +- Integrate with tcp_wrappers - load config based on VSFTPD_LOAD_CONF +environment variables. Allows per-IP configurability in standalone mode. +- Fix build without tcp_wrappers. +- Fix Solaris sendfilev() support - interruption via a signal returns EINTR +rather than a partial byte count! +- Add to EXAMPLE/ - PER_IP_CONFIG and INTERNET_SITE_NOINETD + +At this point: 1.1.3 package released +------------------------------------- + +- Eliminate crypt() not defined warning. +- "grep -q" is not standard to redirect to /dev/null instead. +- Make banned_email_file work second time around. +- Add force_dot_files to work around broken clients. The behaviour when +enabled is very wu-ftpd like. +- Implement SITE HELP - should work around IE bug? +- Update README, vsftpd.conf with references to read the manual page! +- Log revamp: add dual_log_enable to log to xferlog AND vsftpd.log. +- Log revamp: add syslog_enable to log vsftpd.log to syslog(). +- Add "background" option to background the listener process. +- Fix warning is vsftpd.8 man page, Bill Nottingham . +- Fix tcp wrappers support to NOT emit loads of Bad file descriptor messages +to the system log. +- Add ability to make bandwidth limiter smoother by using e.g. +trans_chunk_size=8192. +- Add ability for virtual users to use local privs non anon privs, via +virtual_use_local_privs=YES. +- Fix sendfile() fallback on FreeBSD, thanks to Adam Stroud +. +- Add pam_session support, as well as utmp and wtmp logging for local logins +(when using a PAM build). Tested pam_limits maxlogins works. +- Ensure the source IP address for PORT connects is always the same as the +control connection local IP address. Previously it was not when NOT using +connect_from_port_20 in the presence of multiple local IP addresses. +- Oops - make max_per_ip and max_clients work with the two process model +when both connect_from_port_20 and chown_uploads are false. +- Initial IPv6 support (EPSV only). +- Add EPRT support to IPv6. +- Fix "ls .file" to list .file even if the ls -a flag is not present. Noted +by and thanks to Sean Millichamp . +- Better error messages for config file parse fail: include setting name. +- Fix bug in str_split_text where text is greater than 1 character long! +- Make it build on Solaris8 - switch from utmp to utmpx and handle missing +LOG_FTP. +- Always check for VSFTPD_LOAD_CONF environment variable. +- Implement HELP properly (should help broken clients). +- Fix FreeBSD build (no utmpx.h, so disable feature). +- Fix chown_uploads. +- "Guess fix" for FreeBSD reported bug. I reckon FreeBSD is returning -EINTR +from a blocking close but still closing the fd, despite the error return. So +cater for this. Reported by Drew Vogel . +- Add download_enable and dirlist_enable. Useful in conjunction with the +per-user config stuff. +- Add chmod_enable. +- Implement STRU and MODE for _old_, broken clients! +- Log connects. +- Fix 500 OOPS with chown_uploads and an APPE command. +- Improve some error messages: die -> die2 for more information. +- Repair max_per_ip (problem comparing IPv4 addresses). +- Make chown_uploads work with virtual users. +- Chmod files to 0600 before chown_uploads kicks in. +- Add STOU support. +- Add cmds_allowed config parameter. +- Add some FAQ entries. + +At this point: v1.2.0 released! +=============================== + +- Apply NetBSD patch to sysdeputil.c to activate a few features. Thanks to +Lubomir Sedlacik . +- Apply fix for broken clients that terminate commands with \r\r\n. Thanks +to Andrey Chernomyrdin . +- AIX send_file support, thanks to Tomas Ogren . +- Fix typos in vsftpd.conf.5, thanks to SEKINE Tatsuo . +- Simple -F flag support to LIST and NLST. Needed for some broken clients. +- Add simple ? wildcard in pattern matching. +- Make pasv_min_port and pasv_max_port work if they are the same value. Thanks +to Marvin Solomon . +- Paranoia: ignore user_config_dir if username has a / in it. +- Implement stub ALLO command to keep busybox/ftpput happy. +- Implement REIN, ACCT and SMNT stubs. +- Implement FEAT along with an OPTS stub. +- Implement STAT (no-args version). +- Implement STAT (file/dir). +- Add very simple access control via hide_file and deny_file. These should +NOT be used for securing content as they are very dumb! Filesystem permissions +are still the recommended way for securing important content. +- Allow unsetting of string values with option= (i.e. blank). +- Default virtual users to being chroot()'ed to the guest_user's home +directory, if virtual_use_local_privs is not set. +- Add support for "user_sub_token", where you can set the home directory of +guest_user to "/home/virtual/$USER", and "user_sub_token" to "$USER" to +have a root directory auto generated based on username logging in, e.g. +fred logs in and gets chroot()'ed in /home/virtual/fred. +- Fix bug in str_replace_text if replace token matches at end of string. +- Recognize P@SW as PASV; works around an SMC router bug. +- Accept an async ABOR sequence if it arrives via non-urgent data. Fixes issue +with Cisco routers. Thanks to Eddie Corns . +- Implement simple {,} support in pattern matcher (nested not handled). Handy +to use with hide_file and deny_file options. +(v1.2.1pre2) +- Fix port range with pasv_min_port and pasv_max_port to use the full range +(the upper limit wasn't being used very often!). +- Activate SO_REUSEADDR on passive listen sockets - makes servers with +restricted port ranges much more useable! +- Add secure_email_list_enable, to provide simple anonymous password control. +For some cases, it's better than the hassle of virtual users. Idea thanks to +Malcolm O'Callaghan, . +- Add some FAQ entries. +(v1.2.1pre3) +- Fix issue with failure to call openlog() before using tcp_wrappers. Part +of RH bugzilla #89765. (The more serious part was fixed with v1.2.0). + +At this point: v1.2.1 released! +=============================== + +- Fix FreeBSD 5.1/5.2 issue with time_t being long long on that platform. +Thanks to Matthias Andree . +- Tweak vsftpd.conf.5 to avoid automated mails from ESR ;-) +- Remove vsftpd spec files, they are old and buggy. +- Add -v flag which just outputs the version and exits. +- Fix nasty issue resulting in listener instability under extreme load (root +cause was re-entering malloc/free). Many thanks to Olivier Baudron + for an excellent report. +(v1.2.2pre1) +- Fix build with modern glibc-2.3 and no libcap on Linux. +- Fix 64-bit file support on Solaris. +(v1.2.2pre2) +- Add initial support for running as the user which launched vsftpd, i.e. no +root needed. Warning - easy to create insecurity if you use this without +knowing what you are doing. +- For above run-as-launching-user support: make CDUP re-use CWD code so that +deny_file of *..* is useful. +- Attempt fix of 64-bit file support on FreeBSD (may need another go). +- Update INSTALL to refer to more modern platforms. + +At this point: v1.2.2 released! (need to get the listener fix out) +================================================================== + +- Improve logging (log deletes, renames, chmods, etc. as requested by users). +- Add no_log_lock to work around Solaris / Veritas locking hangs. +- Add EPRT, EPSV, PASV and TVFS to FEAT response. +- Implement use of MDTM to set timestamps. +- Recognize FEAT prior to login. +- Add OpenSSL (AUTH TLS / SSL) support for encrypted control and data +connections! Hurrah. +- Increase max size of .message files to 4000 characters, thanks to Eric +Pancer for the report. +- Add easy builddefs.h ability to disable PAM builds even when PAM is installed. +- Report vsftpd version in STAT output. +- Add REFS file. +- Change parent<->child socket comms from DGRAM to STREAM for increased +reliability. The main benefit is should the parent be killed (or crash out) +then the child won't block on a read() that will never return. +- Make str_reserve reserve space for the trailing zero as well, so we don't +cause a reallocation if we exactly fill the buffer. +- Optimize the sending of strings over the parent<->child comms links. +- Improve the build system so tcp_wrappers, PAM and OpenSSL can be forcibly +compiled out. +- Fix vsftpd.conf.5 typos, thanks to Dmitry V. Levin . +- If trans_chunk_size is between 1 and 4096, use 4096 rather than ignoring +totally. Thanks to Brad . +- Lose Makefile.sun and README.solaris special cases. +- Add SSL / TLS info to SECURITY texts. +- Add README.ssl +- Add documentation for new SSL options to vsftpd.conf.5. +- Add support for CWD ~ (and in general support ~ at start of any filename). +Also support stuff like ~chris/pics, if tilde_user_enable=YES is set. Note that +all of this is for very very broken clients :-( +- Fix compile warnings. +- Update INSTALL with (recent) OS X as a working platform. + +At this point: v2.0.0 released! +=============================== + +- Add -lcrypto for the SSL build; needed for some systems! Thanks to Nelson +Chang . +- Oops; fix session bale out if an empty length password is given. +- Fix build on Fedora Core 2 (-lcap cannot seem to find /lib/libcap.so). +- Fix vsftpd.conf.5 man page error in "ssl_sslv3", thanks to Etienne Chevillard +. +- Clarify licensing: I allow linking of my GPL software with the OpenSSL +libraries. Thanks to Jonas Bofjall . +- Add COPYRIGHT. +- Fix build on OpenBSD, FreeBSD, probably NetBSD too (they aren't SuSv2 +compliant; timezone should be a variable not a function). +- Fix build where PAM build is enabled but PAM headers are missing. +- Fix build on RHEL3 (remove errant include from twoprocess.c). + +At this point: v2.0.1 released! +=============================== + +- Fix FAQ typo, thanks to Jose Santiago Oyervides Gonzalez +. +- Emit data transfer status messages (success / failure) after flushing and +waiting for the full data transfer to reach the client. This should help work +around buggy FTP clients such as FlashFXP, which is known to truncate files +incorrectly. +(v2.0.2pre1) +- Make str_empty actually allocate an empty string. +- Change the ASCII receive code to ONLY rip out \r if it is just before a \n; +someone finally complained about this. +(v2.0.2pre2) +- Enable AIX Large File Support #define from Tomas gren . +- Add a couple of FAQ entries. +- Fix time delta code areas to cope with negative deltas, which will occur +if the clock is adjusted backwards. Thanks to Andrew Anderson + for a great report. +- Fix "errno" checks to be robust in multiple places; previously, calls to +failing library calls could be made inbetween the original library call and +the "errno" reads. Thanks to Andrew Anderson for a great +report. +- Make bandwidth limiter work with SSL data connections. +(v2.0.2pre3) +- Note that the SSL / bandwidth limiter bug fixed a much more serious bug: +SSL data connection dropouts after data_connection_timeout seconds. +- Typo fixes. + +At this point: v2.0.2 released! (need to get the SSL dropout fix out) +===================================================================== + +- Document what regex expressions are supported in the man page. +- New settings rsa_private_key_file and dsa_private_key_file to allow +separate files for the certificates and private keys. +- Initial, simple fix for timed out processes not exiting when SSL is in use. +Better fix (which reports timeout to client properly) to follow. +- Add which setsockopt option failed to die("setsockopt") calls. +- Fix when running on recent OpenBSDs - OpenBSD change broke vsftpd. Lower +linger timeout from INT_MAX to 32767 (SHORT_MAX). Reported by +Ewoud van der Vliet and Ed Vazquez +. +(v2.0.3pre1) +- Fix error with IPv4 connections to IPv6 listeners and PORT type data +connections when connect_from_port_20 is set. RedHat bugzilla 134541. Reported +by Joe Orton , Radek Vokal and +Andreas Kupfer . +- Remove vsf_sysutil_sockaddr_same_family (unused). +- Support protocol 1 (IPv4) in EPRT. +- Add ssl.c to AUDIT. +- Allow config file to use "ssl_ciphers=" to use default OpenSSL cipher list. +- Allow "EPSV 1" to mean IPv4 EPSV. +- Report dummy IP but correct port with IPv6 / PASV. +- Handle SSL_WANT_READ and SSL_WANT_WRITE retries in SSL_read and SSL_write; +fixes SSL upload failures when data timeouts are in use with some clients. +Specifically, I used the test case FileZilla 2.2.12a on Windows XP. Reported +by Lee Lawrence (using CuteFTP and BackupEdge) and +Christian DELAIR (using lftp, FileZilla and +SmartFTP). Thanks to these two people for valuable help. +(v2.0.3pre2) +- Implicitly disable connect_from_port_20 and chown_uploads when a non-root +user is using run_as_launching_user. +- Add force_anon_logins_ssl and force_anon_data_ssl for a fully SSL secure +anonymous-only solution (useful when you don't have root access and a range +of acceptable anonymous passwords as credentials). +- Use SSL BIO callbacks to fix data connection timeout checks; the checks +weren't all occurring promply. + +At this point: v2.0.3 released! (need to get about three imporant fixes out) +============================================================================ + +- Add explicit "This FTP server does not allow anonymous logins" message. +- Add paranoid checks to sysutil.c for large values / lengths. +- Fix incorrect comment about ASCII and SIZE in the vsftpd.conf example. +- Load per-IP config files earlier; allows more settings to be tuned on a +per-IP level. Suggested by Reber Tobias . +- Fix MDTM on non-existant files. Reported by Ken A . +- {} regex fix so that {*} correctly matches everything. Reported by +Tom Van de Wiele . +- Add "mdtm_write" option to disable MDTM being able to set file timestamps. +- Fix HPUX build, thanks to Kevin Vajk . +- Add optional file locking support via lock_upload_files (default on). +- Apply LDFLAGS patch from Mads Martin Joergensen . +- Add pasv_addr_resolve option to allow pasv_address to get DNS resolved once +at startup. +- Apply patch to fix timezone issues (caused by chroot() interacting badly with +newer glibc versions). Thanks to Dmitry V. Levin and +Mads Martin Joergensen . + +At this point: v2.0.4 released! +=============================== + +- Apply fix for O_NONBLOCK vs. XFS DMAPI filesystem. Thanks to Sudha Srinivasan +. +- Fix build warnings exposed by my upgrade to Fedora Core 5 / GCC4.1.1. +- Be more honest in FEAT response if PORT or PASV are disabled! Reported by +Charles Honton . Allows MS Explorer to get the transfer mode +correct. +- pam_pwdb.so -> pam_unix.so in example PAM file. Thanks to +Rhodes, Colin . +- Add FAQ issue regarding "chroot fails with SSL" - in fact, sshd is being hit +here instead ;-) +- Minor man page doc tweaks. +- Tiny bit of paranoia in privops.c. +- Revert change to reject anonymous logins before asking for password. This +fixes complaints about IE not showing the FTP login dialog. +- Change SSL certificate load to cater for chaining too. +- Added delay_failed_login and delay_successful_login to help limit resources +taken by brute force attacks. +- Kick session after a few login fails. Allows IP blocking solutions to be more +immediately effective. +- Replace setenv() with more portable putenv(). First part of Solaris fix. +- Replace tm_gmtoff usage with timezone and daylight. Second part of Solaris +fix. +- Set PAM items TTY and RUSER if possible. +- OpenBSD build warning fixes. +- So, timezone and daylight are not available on BSD, so redo the whole TZ +thing again. Should use only very portable constructs now. + diff --git a/FAQ b/FAQ new file mode 100644 index 0000000..125ab80 --- /dev/null +++ b/FAQ @@ -0,0 +1,245 @@ +vsftpd frequently asked questions!! +----------------------------------- + +Q) Can I restrict users to their home directories? +A) Yes. You are probably after the setting: +chroot_local_user=YES + +Q) Why don't symlinks work with chroot_local_user=YES? +A) This is a consequence of how chroot() security works. As alternatives, +look into hard links, or if you have a modern Linux, see the powerful +"mount --bind". + +Q) Does vsftpd support a limit on the number of users connected? +A1) Yes, indirectly. vsftpd is an inetd-based service. If use the popular +"xinetd" as your inetd, this supports per-service per-IP connection limits. +There is an example of this in the "EXAMPLE" directory. +A2) If you run vsftpd in "standalone" mode with the setting listen=YES, then +you can investigate the setting (e.g.): +max_clients=10 + +Q) Help! I'm getting the error message "refusing to run with writable anonymous +root". +A) vsftpd is protecting against dangerous configurations. The cause of this +message is usually dodgy ownership of the ftp home directory. The home +directory should NOT be owned by the ftp user itself. Neither should it +be writable by the ftp user. A way to fix this is: +chown root ~ftp; chmod -w ~ftp + +Q) Help! I'm getting the error message "str_getpwnam". +A) The most likely cause of this is that the user that is configured as the +'nopriv_user' setting (often 'nobody') does not exist on your system. vsftpd +needs this user to run bits of itself with no privilege. + +Q) Help! Local users cannot log in. +A) There are various possible problems. +A1) By default, vsftpd disables any logins other than anonymous logins. Put +local_enable=YES in your /etc/vsftpd.conf to allow local users to log in. +A2) vsftpd tries to link with PAM. (Run "ldd vsftpd" and look for libpam to +find out whether this has happened or not). If vsftpd links with PAM, then +you will need to have a PAM file installed for the vsftpd service. There is +a sample one for RedHat systems included in the "RedHat" directory - put it +under /etc/pam.d +A3) If vsftpd didn't link with PAM, then there are various possible issues. Is +the user's shell in /etc/shells? If you have shadowed passwords, does your +system have a "shadow.h" file in the include path? +A4) If you are not using PAM, then vsftpd will do its own check for a valid +user shell in /etc/shells. You may need to disable this if you use an invalid +shell to disable logins other than FTP logins. Put check_shell=NO in your +/etc/vsftpd.conf. + +Q) Help! Uploads or other write commands give me "500 Unknown command.". +A) By default, write commands, including uploads and new directories, are +disabled. This is a security measure. To enable writes, put write_enable=YES +in your /etc/vsftpd.conf. + +Q) Help! What are the security implications referred to in the +"chroot_local_user" option? +A) Firstly note that other ftp daemons have the same implications. It is a +generic problem. +The problem isn't too severe, but it is this: Some people have FTP user +accounts which are not trusted to have full shell access. If these +accounts can also upload files, there is a small risk. A bad user now has +control of the filesystem root, which is their home directory. The ftp +daemon might cause some config file to be read - e.g. /etc/some_file. With +chroot(), this file is now under the control of the user. vsftpd is +careful in this area. But, the system's libc might want to open locale +config files or other settings... + +Q) Help! Uploaded files are appearing with permissions -rw-------. +A1) Depending on if this is an upload by a local user or an anonymous user, +use "local_umask" or "anon_umask" to change this. For example, use +"anon_umask=022" to give anonymously uploaded files permissions +-rw-r--r--. Note that the "0" before the "22" is important. +A2) Also see the vsftpd.conf.5 man page for the new "file_open_mode" +parameter. + +Q) Help! How do I integrate with LDAP users and logins? +A) Use vsftpd's PAM integration to do this, and have PAM authenticate against +an LDAP repository. + +Q) Help! Does vsftpd do virtual hosting setups? +A1) Yes. If you integrate vsftpd with xinetd, you can use xinetd to bind to +several different IP addresses. For each IP address, get xinetd to launch +vsftpd with a different config file. This way, you can get different behaviour +per virtual address. +A2) Alternatively, run as many copies as vsftpd as necessary, in standalone +mode. Use "listen_address=x.x.x.x" to set the virtual IP. + +Q) Help! Does vsftpd support virtual users? +A) Yes, via PAM integration. Set "guest_enable=YES" in /etc/vsftpd.conf. This +has the effect of mapping every non-anonymous successful login to the local +username specified in "guest_username". Then, use PAM and (e.g.) its pam_userdb +module to provide authentication against an external (i.e. non-/etc/passwd) +repository of users. +Note - currently there is a restriction that with guest_enable enabled, local +users also get mapped to guest_username. +There is an example of virtual users setup in the "EXAMPLE" directory. + +Q) Help! Does vsftpd support different settings for different users? +A) Yes - in a very powerful way. Look at the setting "user_config_dir" in the +manual page. + +Q) Help! Can I restrict vsftpd data connections to a specific range of ports? +A) Yes. See the config settings "pasv_min_port" and "pasv_max_port". + +Q) Help! I'm getting the message "OOPS: chdir". +A) If this is for an anonymous login, check that the home directory for the +user "ftp" is correct. If you are using the config setting "anon_root", check +that is correct too. + +Q) Help! vsftpd is reporting times as GMT times and not local times! +A) This behaviour can be changed with the setting "use_localtime=YES". + +Q) Help! Can I disable certain FTP commands? +A) Yes. There are some individual settings (e.g. dirlist_enable) or you can +specify a complete set of allowed commands with "cmds_allowed". + +Q) Help! Can I change the port that vsftpd runs on? +A1) Yes. If you are running vsftpd in standalone mode, use the "listen_port" +directive in vsftpd.conf. +A2) Yes. If you are running vsftpd from an inetd or xinetd program, this +becomes an inetd or xinetd problem. You must change the inetd or xinetd +configuration files (perhaps /etc/inetd.conf or /etc/xinetd.d/vsftpd) + +Q) Help! Will vsftpd authenticate against an LDAP server? What about a +MySQL server? +A) Yes. vsftpd uses PAM for authentication, so you need to configure PAM +to use pam_ldap or pam_mysql modules. This may involve installing the PAM +modules and then editing the PAM config file (perhaps /etc/pam.d/vsftpd). + +Q) Help! Does vsftpd support per-IP limits? +A1) Yes. If you are running vsftpd standalone, there is a "max_per_ip" +setting. +A2) Yes. If you are running vsftpd via xinetd, there is an xinetd config +variable "per_source". + +Q) Help! Does vsftpd support bandwidth limiting? +A) Yes. See vsftpd.conf.5 man page and investigate settings such as +"anon_max_rate" and "local_max_rate". + +Q) Help! Does vsftpd support IP-based access control? +A1) Yes. vsftpd can integrate with tcp_wrappers (if built with this support). +It is enabled with the setting "tcp_wrappers=YES". +A2) Yes. vsftpd can be run from xinetd, which supports tcp_wrappers +integration. + +Q) Help! Does vsftpd support IPv6? +A) Yes, as of version 1.2.0. Read the vsftpd.conf.5 man page. + +Q) Help! vsftpd doesn't build, it fails with an error about being unable to +find -lcap. +A) Install the libcap package and retry the build. Seems to affect Debian +users a lot. + +Q) Help! I've put settings in /etc/vsftpd.conf, but they are not taking +effect! +A) This is affecting some RedHat users - some RedHat versions put the config +file in /etc/vsftpd/vsftpd.conf. + +Q) Help! vsftpd doesn't build, it complains about problems with incomplete +types in sysutil.c. +A) Your system probably doesn't have IPv6 support. Either use a more modern +system, use an older vsftpd (e.g. v1.1.3), or wait for a version of vsftpd +without this problem! + +Q) Help! I'm getting messages along the lines of 500 OOPS: vsf_sysutil_bind +when trying to do downloads (particularly lots of small files). +A) vsftpd-1.2.1 should sort this out. + +Q) Help! Does vsftpd support hiding or denying certain files? +A) Yes. Look at the hide_file and deny_file options in the manual page. + +Q) Help! Does vsftpd support FXP? +A) Yes. An FTP server does not have to do anything special to support FXP. +However, you many get tripped up by vsftpd's security precautions on IP +addresses. In order to relax these precautions, have a look in the +vsftpd.conf.5 for pasv_promiscuous (and the less advisable port_promiscuous). + +Q) Help! I'm getting the error "426 Failure writing network stream." on +downloads. +A) You shouldn't see this with v1.2.1 or newer versions of vsftpd. Older +versions of vsftpd can give this error if the user tries to download +something from an unusual filesystem (e.g. FAT), which don't support +performance features used by vsftpd. With vsftpd-1.1.3 and newer there is a +config workaround, use_sendfile=NO. + +Q) Help! I'm using the pam_userdb login module and the login hangs. +A) This could be a bad interaction with glibc version 2.3 and PAM. A Debian +user reported this. The initial report is here: +http://lists.debian.org/debian-glibc/2003/debian-glibc-200309/msg00310.html + +Q) Help! Does vsftpd support large files (>2Gb?). +A) Yes, it does. + +Q) Help! Well, large file support doesn't seem to be working, then! +A1) Large file support first appeared in v1.1.0. +A2) Solaris large file support wasn't fixed until v1.2.2. +A3) FreeBSD large file support wasn't fixed until v1.2.2. +A4) The early Linux 2.6 kernels had a bug in this area - use v2.6.6 or newer. +A5) Are you sure your FTP _client_ correctly supports large files? + +Q) Help! The built-in vsftpd listener is hanging or crashing! +A) A bug in this area is fixed in vsftpd v1.2.2. The problem has always existed +but seems to frequently trigger only on certain platforms. For example, +Fedora Core 1 - the suspected trigger is a glibc-2.3 platform, possibly in +combination with a NPTL-enabled kernel. + +Q) Help! I'm using Solaris / Veritas and vsftpd is hanging! +A) Suspected bug with the Solaris / Veritas combination. With vsftpd-1.2.3 +there is a possible workaround: no_log_lock=YES in your vsftpd.conf.5. + +Q) Does vsftpd support SSL / TLS based encryption? +A) Yes, as of v2.0.0, this is supported for the control and data connections +(hurrah). You need a build of vsftpd with this support enabled, and then you +need to activate the ssl_enable setting. NOTE there are security considerations +with this support. Please make sure to read the ssl_enable section in the +vsftpd.conf.5 man page thoroughly before using. + +Q) Help! I'm using FlashFXP and getting truncated files on download. +A) FlashFXP is buggy - particularly with SSL transfers. Upgrade to v3.0RC4 +or newer, which is reported to be fixed. + +Q) Help! I'm trying to build vsftpd, and I get an error along the lines of +"krb5.h: no such file or directory". +A) Yes, seems to be a problem with some RedHat setups. See +http://bugzilla.redhat.com/bugzilla/show_bug.cgi?id=111301 for details and +suggested workarounds. + +Q) Help! I'm getting the error "OOPS: capset" when I try to connect to vsftpd. +A) This is an issue with SELinux enabled distributions. The solution is to +make sure the capability kernel module is loaded. + +Q) Help! I'm getting the error "ftp: netin: Interrupted system call". +A) Seems to be a bug in ftp-tls, particularly with SSL transfers with +bandwidth limiting in effect. + +Q) Help! When trying SSL transfers, users log in and are no longer restricted +to their home directory! They can browse the entire filesystem! +A) Most likely, your FTP client is in fact using the SSH protocol rather than +the FTP protocol - so sshd is in control and not vsftpd! + +Q) Blah.. blah.. +A) For a good idea of what vsftpd can do, read the vsftpd.conf.5 man page +and the EXAMPLES. + diff --git a/INSTALL b/INSTALL new file mode 100644 index 0000000..4f811aa --- /dev/null +++ b/INSTALL @@ -0,0 +1,166 @@ +INSTALL +======= + +This file details how to build and install / run vsftpd from the vsftpd +distribution .tar.gz file. + +Step 1) Build vsftpd. + +Switch to the directory created when you unpacked the vsftpd .tar.gz file. +e.g.: + +cd vsftpd-1.1.2 + +edit "builddefs.h" to handle compile-time settings (tcp_wrappers build, +etc). + +Just type "make" (and mail me to fix it if it doesn't build ;-). +This should produce you a vsftpd binary. You can test for this, e.g.: + +[chris@localhost vsftpd]$ ls -l vsftpd +-rwxrwxr-x 1 chris chris 61748 Sep 27 00:26 vsftpd + +Step 2) Satisfy vsftpd pre-requisites +2a) vsftpd needs the user "nobody" in the default configuration. Add this +user in case it does not already exist. e.g.: + +[root@localhost root]# useradd nobody +useradd: user nobody exists + +2b) vsftpd needs the (empty) directory /usr/share/empty in the default +configuration. Add this directory in case it does not already exist. e.g.: + +[root@localhost root]# mkdir /usr/share/empty/ +mkdir: cannot create directory `/usr/share/empty': File exists + +2c) For anonymous FTP, you will need the user "ftp" to exist, and have a +valid home directory (which is NOT owned or writable by the user "ftp"). +The following commands could be used to set up the user "ftp" if you do not +have one: + +[root@localhost root]# mkdir /var/ftp/ +[root@localhost root]# useradd -d /var/ftp ftp + +(the next two are useful to run even if the user "ftp" already exists). +[root@localhost root]# chown root.root /var/ftp +[root@localhost root]# chmod og-w /var/ftp + +Step 3) Install vsftpd config file, executable, man page, etc. + +Running "make install" will try to copy the binary, man pages, etc. to +somewhere sensible. +Or you might want to copy these things by hand, e.g.: +cp vsftpd /usr/local/sbin/vsftpd +cp vsftpd.conf.5 /usr/local/man/man5 +cp vsftpd.8 /usr/local/man/man8 + +"make install" doesn't copy the sample config file. It is recommended you +do this: +cp vsftpd.conf /etc + +Step 4) Smoke test (without an inetd). + +vsftpd can run standalone or via an inetd (such as inetd or xinetd). You will +typically get more control running vsftpd from an inetd. But first we will run +it without, so we can check things are going well so far. +Edit /etc/vsftpd.conf, and add this line at the bottom: + +listen=YES + +This tells vsftpd it will NOT be running from inetd. +Right, now let's try and run it! +Log in as root. +Make sure you are not running other FTP servers (or vsftpd will not be able +to use the FTP port, 21). +Run the binary from wherever you put it, e.g.: + +[root@localhost root]# /usr/local/sbin/vsftpd & +[1] 2104 + +If all is well, you can now connect! e.g.: + +[chris@localhost chris]$ ftp localhost +Connected to localhost (127.0.0.1). +220 (vsFTPd 1.1.1) +Name (localhost:chris): ftp +331 Please specify the password. +Password: +230 Login successful. Have fun. +Remote system type is UNIX. +Using binary mode to transfer files. +ftp> ls +227 Entering Passive Mode (127,0,0,1,229,133) +150 Here comes the directory listing. +d--x--x--x 2 0 0 4096 Jan 14 2002 bin +d--x--x--x 2 0 0 4096 Apr 21 20:52 etc +drwxr-xr-x 2 0 0 4096 Apr 21 20:52 lib +drwxr-sr-x 2 0 50 4096 Jul 26 22:58 pub +226 Directory send OK. +ftp> + +Step 5) Run from an inetd of some kind (optional - standalone mode is now +recommended) + +You may want to run the binary from an inetd of some kind, because this can +give you extra features - e.g. xinetd has a lot of settings. (Note that +vsftpd's inbuilt listener covers most of the more useful xinetd settings). + +5a) If using standard "inetd", you will need to edit /etc/inetd.conf, and add +a line such as: + +ftp stream tcp nowait root /usr/sbin/tcpd /usr/local/sbin/vsftpd + +(Make sure to remove or comment out any existing ftp service lines. If you +don't have tcp_wrappers installed, or don't want to use them, take out the +/usr/sbin/tcpd part). + +inetd will need to be told to reload its config file: +kill -SIGHUP `pidof inetd` + +5b) If using "xinetd", you can follow a provided example, by looking at the +file EXAMPLE/INTERNET_SITE/README. Various other examples show how to leverage +the more powerful xinetd features. + + +Step 6) Set up PAM for local logins (optional) + +If you are running vsftpd on a PAM enabled machine, you will need to have a +/etc/pam.d/ftp file present, otherwise non-anonymous logins will fail. [NOTE - +if you have an older version of PAM, that file might be /etc/pam.conf] + +For a standard setup, you can just copy a provided example file: +cp RedHat/vsftpd.pam /etc/pam.d/ftp + + +Step 7) Customize your configuration + +As well as the above three pre-requisites, you are recommended to install a +config file. The default location for the config file is /etc/vsftpd.conf. +There is a sample vsftpd.conf in the distribution tarball. You probably want +to copy that to /etc/vsftpd.conf as a basis for modification, i.e.: + +cp vsftpd.conf /etc + +The default configuration allows neither local user logins nor anonymous +uploads. You may wish to change these defaults. + +Other notes +=========== + +Tested platforms (well, it builds) +- Any modern, well featured platform should work fine! Recent versions of +the platforms listed below, and often older ones, should work fine. +- Fedora Core +- RedHat Linux +- RedHat Enterprise Linux +- Solaris / GNU tools (Solaris 8 or newer) +- SuSE Linux +- Debian Linux +- OpenBSD +- FreeBSD +- NetBSD +- HP-UX / GNU tools +- IRIX / GNU tools +- AIX / GNU tools +- Mac OS X (note; older versions have setgroups() problem. 10.3.4 reported OK) + diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..27761e7 --- /dev/null +++ b/LICENSE @@ -0,0 +1,8 @@ +See "COPYING". + +vsftpd is licensed under version 2 of the GNU GPL. +As copyright holder, I give permission for vsftpd to be linked to the OpenSSL +libraries. This includes permission for vsftpd binaries to be distributed +linked against the OpenSSL libraries. All other obligations under the GPL v2 +remain intact. + diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..4afff8a --- /dev/null +++ b/Makefile @@ -0,0 +1,746 @@ +# Makefile for systems with GNU tools +CC = gcc4 +CXX = g++4 +INSTALL = install + +ILINUX = -I/usr/src/linux-2.6.17-rc6/include + +#Debug flags +CFLAGS = -g -Wall -W -Wshadow +CXXFLAGS = -g -Wall `mysql_config --cflags` + +IFLAGS = $(ILINUX) -idirafter dummyinc +IXXFLAGS = -I`pg_config --includedir` + +#Release flags +CFLAGS = -O2 -Wall -W -Wshadow +CXXFLAGS = -O2 -Wall -W -Wshadow `mysql_config --cflags` + +LIBS = `./vsf_findlibs.sh` -lpthread `mysql_config --libs` -L`pg_config --libdir` -lpq +LINK = -g + +#All *.cpp files in the current directory +OBJSCPP := $(patsubst %.cpp,%.o,$(wildcard *.cpp)) +#All *.c files in the current directory +OBJS := $(patsubst %.c,%.o,$(wildcard *.c)) + +#Build rule for *.c files +.c.o: + $(CC) -c $*.c $(CFLAGS) $(IFLAGS) + +#Build rule for *.cpp files +.cpp.o: + $(CXX) -c $*.cpp $(CXXFLAGS) $(IXXFLAGS) + +#link the executable +vsftpd: $(OBJS) $(OBJSCPP) + $(CXX) -o vsftpd $(OBJS) $(OBJSCPP) $(LINK) $(LIBS) $(LDFLAGS) + +#default vsftp install +install: + if [ -x /usr/local/sbin ]; then \ + $(INSTALL) -m 755 vsftpd /usr/local/sbin/vsftpd; \ + else \ + $(INSTALL) -m 755 vsftpd /usr/sbin/vsftpd; fi + if [ -x /usr/local/man ]; then \ + $(INSTALL) -m 644 vsftpd.8 /usr/local/man/man8/vsftpd.8; \ + $(INSTALL) -m 644 vsftpd.conf.5 /usr/local/man/man5/vsftpd.conf.5; \ + elif [ -x /usr/share/man ]; then \ + $(INSTALL) -m 644 vsftpd.8 /usr/share/man/man8/vsftpd.8; \ + $(INSTALL) -m 644 vsftpd.conf.5 /usr/share/man/man5/vsftpd.conf.5; \ + else \ + $(INSTALL) -m 644 vsftpd.8 /usr/man/man8/vsftpd.8; \ + $(INSTALL) -m 644 vsftpd.conf.5 /usr/man/man5/vsftpd.conf.5; fi + if [ -x /etc/xinetd.d ]; then \ + $(INSTALL) -m 644 xinetd.d/vsftpd /etc/xinetd.d/vsftpd; fi + +#clean objects, executable and auto-generated files +clean: autoclean + rm -f *.o *.swp vsftpd + +#remake dependancies +depend: + mkdep -o Makefile *.c *.cpp + +#build rules for code-generators +code_generation/struct_filter: code_generation/struct_filter.lex + (cd code_generation; ./build.sh struct_filter;); + +code_generation/serialization: code_generation/serialization.lex code_generation/serialization.yacc + (cd code_generation; ./build.sh serialization;); + +code_generation/sender_receiver: code_generation/sender_receiver.lex code_generation/sender_receiver.yacc + (cd code_generation; ./build.sh sender_receiver;); + +code_generation/free_memory: code_generation/free_memory.lex code_generation/free_memory.yacc + (cd code_generation; ./build.sh free_memory;); + +#fake targets +.PHONY: all autoclean depend + +all : depend autoclean vsftpd + +#removes autobuilt files +autoclean: + rm udsrf_auto.h &>/dev/null; echo -n + rm event_auto.h &>/dev/null; echo -n + rm request_auto.h &>/dev/null; echo -n + rm serialization_auto.h &>/dev/null; echo -n + rm slave_thread_auto.h &>/dev/null; echo -n + rm code_generation/struct_filter &>/dev/null; echo -n + rm code_generation/serialization &>/dev/null; echo -n + rm code_generation/sender_receiver &>/dev/null; echo -n + rm code_generation/free_memory &>/dev/null; echo -n + +#hardcoded dependancies +udsrf.h: udsrf_auto.h +event.cpp : event_auto.h +request.cpp : request_auto.h +unknown_object.h : serialization_auto.h +slave_thread.cpp : slave_thread_auto.h + +udsrf_auto.h: code_generation/sender_receiver code_generation/struct_filter event_c_api.h request_c_api.h + cat event_c_api.h | code_generation/struct_filter | code_generation/sender_receiver s > udsrf_auto.h + cat event_c_api.h | code_generation/struct_filter | code_generation/sender_receiver r >> udsrf_auto.h + cat request_c_api.h | code_generation/struct_filter | code_generation/sender_receiver s >> udsrf_auto.h + cat request_c_api.h | code_generation/struct_filter | code_generation/sender_receiver r >> udsrf_auto.h + +event_auto.h: event_c_api.h code_generation/events_receiver.py code_generation/free_memory code_generation/struct_filter + cat event_c_api.h | code_generation/struct_filter | code_generation/events_receiver.py Event > event_auto.h + cat event_c_api.h | code_generation/struct_filter | code_generation/free_memory >> event_auto.h + +request_auto.h: request_c_api.h code_generation/events_receiver.py code_generation/free_memory code_generation/struct_filter + cat request_c_api.h | code_generation/struct_filter | code_generation/events_receiver.py Request > request_auto.h + cat request_c_api.h | code_generation/struct_filter | code_generation/free_memory >> request_auto.h + +serialization_auto.h : request_c_api.h code_generation/serialization code_generation/struct_filter + cat request_results.h | code_generation/struct_filter | code_generation/serialization s > serialization_auto.h + cat request_results.h | code_generation/struct_filter | code_generation/serialization d >> serialization_auto.h + +slave_thread_auto.h : mysql_plugin.h pgsql_plugin.h file_logger_plugin.h code_generation/plugin_creator.py + cat *.h | code_generation/plugin_creator.py > slave_thread_auto.h + +#--- DO NOT EDIT BELOW THIS LINE +#--- These lines were automatically generated by `mkdep' + +access.o: access.c +access.o: access.h +access.o: cpp_to_c_helpers.h +access.o: filesize.h +access.o: ls.h +access.o: str.h +access.o: tunables.h + +ascii.o: ascii.c +ascii.o: ascii.h + +banner.o: banner.c +banner.o: banner.h +banner.o: cpp_to_c_helpers.h +banner.o: filesize.h +banner.o: filestr.h +banner.o: ftpcmdio.h +banner.o: session.h +banner.o: str.h +banner.o: strlist.h +banner.o: sysstr.h +banner.o: sysutil.h +banner.o: tunables.h + +features.o: features.c +features.o: features.h +features.o: ftpcmdio.h +features.o: ftpcodes.h +features.o: tunables.h + +filestr.o: cpp_to_c_helpers.h +filestr.o: filesize.h +filestr.o: filestr.c +filestr.o: filestr.h +filestr.o: secbuf.h +filestr.o: str.h +filestr.o: sysutil.h + +ftpcmdio.o: cpp_to_c_helpers.h +ftpcmdio.o: defs.h +ftpcmdio.o: filesize.h +ftpcmdio.o: ftpcmdio.c +ftpcmdio.o: ftpcmdio.h +ftpcmdio.o: ftpcodes.h +ftpcmdio.o: logging.h +ftpcmdio.o: netstr.h +ftpcmdio.o: readwrite.h +ftpcmdio.o: secbuf.h +ftpcmdio.o: session.h +ftpcmdio.o: str.h +ftpcmdio.o: sysutil.h +ftpcmdio.o: tunables.h +ftpcmdio.o: utility.h + +ftpdataio.o: ascii.h +ftpdataio.o: cpp_to_c_helpers.h +ftpdataio.o: defs.h +ftpdataio.o: filesize.h +ftpdataio.o: ftpcmdio.h +ftpdataio.o: ftpcodes.h +ftpdataio.o: ftpdataio.c +ftpdataio.o: ftpdataio.h +ftpdataio.o: logging.h +ftpdataio.o: ls.h +ftpdataio.o: oneprocess.h +ftpdataio.o: readwrite.h +ftpdataio.o: secbuf.h +ftpdataio.o: session.h +ftpdataio.o: ssl.h +ftpdataio.o: str.h +ftpdataio.o: strlist.h +ftpdataio.o: sysdeputil.h +ftpdataio.o: sysstr.h +ftpdataio.o: sysutil.h +ftpdataio.o: tunables.h +ftpdataio.o: twoprocess.h +ftpdataio.o: utility.h + +hash.o: filesize.h +hash.o: hash.c +hash.o: hash.h +hash.o: sysutil.h +hash.o: utility.h + +ipaddrparse.o: cpp_to_c_helpers.h +ipaddrparse.o: filesize.h +ipaddrparse.o: ipaddrparse.c +ipaddrparse.o: ipaddrparse.h +ipaddrparse.o: str.h +ipaddrparse.o: sysutil.h + +logging.o: cpp_to_c_helpers.h +logging.o: filesize.h +logging.o: logging.c +logging.o: logging.h +logging.o: session.h +logging.o: str.h +logging.o: sysstr.h +logging.o: sysutil.h +logging.o: tunables.h +logging.o: utility.h + +ls.o: access.h +ls.o: cpp_to_c_helpers.h +ls.o: filesize.h +ls.o: ls.c +ls.o: ls.h +ls.o: str.h +ls.o: strlist.h +ls.o: sysstr.h +ls.o: sysutil.h +ls.o: tunables.h + +main.o: cpp_to_c_helpers.h +main.o: defs.h +main.o: filesize.h +main.o: filestr.h +main.o: ftpcmdio.h +main.o: logging.h +main.o: main.c +main.o: oneprocess.h +main.o: parseconf.h +main.o: plugin_architecture_interface.h +main.o: session.h +main.o: ssl.h +main.o: standalone.h +main.o: str.h +main.o: sysdeputil.h +main.o: sysutil.h +main.o: tcpwrap.h +main.o: tunables.h +main.o: twoprocess.h +main.o: utility.h +main.o: vsftpver.h + +netstr.o: cpp_to_c_helpers.h +netstr.o: filesize.h +netstr.o: netstr.c +netstr.o: netstr.h +netstr.o: str.h +netstr.o: sysstr.h +netstr.o: sysutil.h +netstr.o: utility.h + +oneprocess.o: cpp_to_c_helpers.h +oneprocess.o: filesize.h +oneprocess.o: oneprocess.c +oneprocess.o: postlogin.h +oneprocess.o: prelogin.h +oneprocess.o: privops.h +oneprocess.o: secutil.h +oneprocess.o: session.h +oneprocess.o: str.h +oneprocess.o: sysdeputil.h +oneprocess.o: sysstr.h +oneprocess.o: tunables.h +oneprocess.o: utility.h + +parseconf.o: cpp_to_c_helpers.h +parseconf.o: defs.h +parseconf.o: filesize.h +parseconf.o: filestr.h +parseconf.o: parseconf.c +parseconf.o: parseconf.h +parseconf.o: str.h +parseconf.o: sysutil.h +parseconf.o: tunables.h +parseconf.o: utility.h + +postlogin.o: access.h +postlogin.o: banner.h +postlogin.o: cpp_to_c_helpers.h +postlogin.o: defs.h +postlogin.o: event_c_api.h +postlogin.o: features.h +postlogin.o: filesize.h +postlogin.o: ftpcmdio.h +postlogin.o: ftpcodes.h +postlogin.o: ftpdataio.h +postlogin.o: ipaddrparse.h +postlogin.o: logging.h +postlogin.o: oneprocess.h +postlogin.o: postlogin.c +postlogin.o: postlogin.h +postlogin.o: postlogin_events.h +postlogin.o: postlogin_requests.h +postlogin.o: request_c_api.h +postlogin.o: request_results.h +postlogin.o: session.h +postlogin.o: ssl.h +postlogin.o: str.h +postlogin.o: sysdeputil.h +postlogin.o: sysstr.h +postlogin.o: sysutil.h +postlogin.o: tunables.h +postlogin.o: twoprocess.h +postlogin.o: utility.h +postlogin.o: vsftpver.h +postlogin.o: vsfutils_c.h + +postlogin_events.o: cpp_to_c_helpers.h +postlogin_events.o: event_c_api.h +postlogin_events.o: filesize.h +postlogin_events.o: ftpdataio.h +postlogin_events.o: postlogin_events.c +postlogin_events.o: postlogin_events.h +postlogin_events.o: session.h +postlogin_events.o: str.h +postlogin_events.o: sysstr.h +postlogin_events.o: sysutil.h +postlogin_events.o: vsfutils_c.h + +postlogin_requests.o: cpp_to_c_helpers.h +postlogin_requests.o: filesize.h +postlogin_requests.o: ftpdataio.h +postlogin_requests.o: postlogin_requests.c +postlogin_requests.o: postlogin_requests.h +postlogin_requests.o: request_c_api.h +postlogin_requests.o: request_results.h +postlogin_requests.o: session.h +postlogin_requests.o: str.h +postlogin_requests.o: sysstr.h +postlogin_requests.o: sysutil.h +postlogin_requests.o: vsfutils_c.h + +postprivparent.o: cpp_to_c_helpers.h +postprivparent.o: defs.h +postprivparent.o: filesize.h +postprivparent.o: postprivparent.c +postprivparent.o: postprivparent.h +postprivparent.o: privops.h +postprivparent.o: privsock.h +postprivparent.o: secutil.h +postprivparent.o: session.h +postprivparent.o: str.h +postprivparent.o: sysdeputil.h +postprivparent.o: sysstr.h +postprivparent.o: sysutil.h +postprivparent.o: tunables.h +postprivparent.o: utility.h + +prelogin.o: banner.h +prelogin.o: cpp_to_c_helpers.h +prelogin.o: defs.h +prelogin.o: event_c_api.h +prelogin.o: features.h +prelogin.o: filesize.h +prelogin.o: ftpcmdio.h +prelogin.o: ftpcodes.h +prelogin.o: logging.h +prelogin.o: oneprocess.h +prelogin.o: prelogin.c +prelogin.o: prelogin.h +prelogin.o: session.h +prelogin.o: ssl.h +prelogin.o: str.h +prelogin.o: sysdeputil.h +prelogin.o: sysutil.h +prelogin.o: tunables.h +prelogin.o: twoprocess.h +prelogin.o: vsftpver.h + +privops.o: cpp_to_c_helpers.h +privops.o: defs.h +privops.o: event_c_api.h +privops.o: filesize.h +privops.o: ftpcmdio.h +privops.o: ftpcodes.h +privops.o: logging.h +privops.o: privops.c +privops.o: privops.h +privops.o: request_c_api.h +privops.o: request_results.h +privops.o: session.h +privops.o: str.h +privops.o: sysdeputil.h +privops.o: sysutil.h +privops.o: tunables.h +privops.o: utility.h + +privsock.o: cpp_to_c_helpers.h +privsock.o: defs.h +privsock.o: filesize.h +privsock.o: netstr.h +privsock.o: privsock.c +privsock.o: privsock.h +privsock.o: session.h +privsock.o: str.h +privsock.o: sysdeputil.h +privsock.o: sysutil.h +privsock.o: utility.h + +readwrite.o: cpp_to_c_helpers.h +readwrite.o: defs.h +readwrite.o: filesize.h +readwrite.o: netstr.h +readwrite.o: privsock.h +readwrite.o: readwrite.c +readwrite.o: readwrite.h +readwrite.o: session.h +readwrite.o: ssl.h +readwrite.o: str.h +readwrite.o: sysutil.h + +secbuf.o: filesize.h +secbuf.o: secbuf.c +secbuf.o: secbuf.h +secbuf.o: sysdeputil.h +secbuf.o: sysutil.h +secbuf.o: utility.h + +secutil.o: cpp_to_c_helpers.h +secutil.o: filesize.h +secutil.o: secutil.c +secutil.o: secutil.h +secutil.o: str.h +secutil.o: sysdeputil.h +secutil.o: sysstr.h +secutil.o: sysutil.h +secutil.o: utility.h + +ssl.o: builddefs.h +ssl.o: cpp_to_c_helpers.h +ssl.o: defs.h +ssl.o: filesize.h +ssl.o: ftpcmdio.h +ssl.o: ftpcodes.h +ssl.o: session.h +ssl.o: ssl.c +ssl.o: ssl.h +ssl.o: str.h +ssl.o: sysutil.h +ssl.o: tunables.h +ssl.o: utility.h + +standalone.o: cpp_to_c_helpers.h +standalone.o: defs.h +standalone.o: filesize.h +standalone.o: hash.h +standalone.o: ipaddrparse.h +standalone.o: parseconf.h +standalone.o: plugin_architecture_interface.h +standalone.o: session.h +standalone.o: standalone.c +standalone.o: standalone.h +standalone.o: str.h +standalone.o: sysdeputil.h +standalone.o: sysutil.h +standalone.o: tunables.h +standalone.o: utility.h + +str.o: cpp_to_c_helpers.h +str.o: filesize.h +str.o: str.c +str.o: str.h +str.o: sysutil.h +str.o: utility.h + +strlist.o: cpp_to_c_helpers.h +strlist.o: filesize.h +strlist.o: str.h +strlist.o: strlist.c +strlist.o: strlist.h +strlist.o: sysutil.h +strlist.o: utility.h + +sysdeputil.o: builddefs.h +sysdeputil.o: cpp_to_c_helpers.h +sysdeputil.o: defs.h +sysdeputil.o: filesize.h +sysdeputil.o: port/cmsg_extras.h +sysdeputil.o: port/porting_junk.h +sysdeputil.o: secbuf.h +sysdeputil.o: str.h +sysdeputil.o: sysdeputil.c +sysdeputil.o: sysdeputil.h +sysdeputil.o: sysutil.h +sysdeputil.o: tunables.h +sysdeputil.o: utility.h + +sysstr.o: cpp_to_c_helpers.h +sysstr.o: defs.h +sysstr.o: filesize.h +sysstr.o: secbuf.h +sysstr.o: str.h +sysstr.o: sysstr.c +sysstr.o: sysstr.h +sysstr.o: sysutil.h +sysstr.o: tunables.h +sysstr.o: utility.h + +sysutil.o: cpp_to_c_helpers.h +sysutil.o: event_c_api.h +sysutil.o: filesize.h +sysutil.o: port/cmsg_extras.h +sysutil.o: port/porting_junk.h +sysutil.o: request_c_api.h +sysutil.o: request_results.h +sysutil.o: sysutil.c +sysutil.o: sysutil.h +sysutil.o: tunables.h +sysutil.o: utility.h + +tcpwrap.o: builddefs.h +tcpwrap.o: tcpwrap.c +tcpwrap.o: tcpwrap.h +tcpwrap.o: utility.h + +tunables.o: tunables.c +tunables.o: tunables.h + +twoprocess.o: cpp_to_c_helpers.h +twoprocess.o: defs.h +twoprocess.o: filesize.h +twoprocess.o: filestr.h +twoprocess.o: parseconf.h +twoprocess.o: postlogin.h +twoprocess.o: postprivparent.h +twoprocess.o: prelogin.h +twoprocess.o: privops.h +twoprocess.o: privsock.h +twoprocess.o: readwrite.h +twoprocess.o: secutil.h +twoprocess.o: session.h +twoprocess.o: ssl.h +twoprocess.o: str.h +twoprocess.o: sysdeputil.h +twoprocess.o: sysstr.h +twoprocess.o: sysutil.h +twoprocess.o: tunables.h +twoprocess.o: twoprocess.c +twoprocess.o: twoprocess.h +twoprocess.o: utility.h + +utility.o: cpp_to_c_helpers.h +utility.o: defs.h +utility.o: filesize.h +utility.o: str.h +utility.o: sysutil.h +utility.o: utility.c +utility.o: utility.h + +event.o: countedPtr.h +event.o: cpp_to_c_helpers.h +event.o: event.cpp +event.o: event.h +event.o: event_auto.h +event.o: event_c_api.h +event.o: exceptions.h +event.o: filesize.h +event.o: plugin_architecture_api.h +event.o: plugin_architecture_interface.h +event.o: request.h +event.o: request_c_api.h +event.o: request_results.h +event.o: serialization_auto.h +event.o: session.h +event.o: str.h +event.o: typeid_hash.h +event.o: udsrf.h +event.o: udsrf_auto.h +event.o: unknown_object.h + +file_logger_plugin.o: countedPtr.h +file_logger_plugin.o: cpp_to_c_helpers.h +file_logger_plugin.o: event.h +file_logger_plugin.o: event_c_api.h +file_logger_plugin.o: exceptions.h +file_logger_plugin.o: file_logger_plugin.cpp +file_logger_plugin.o: file_logger_plugin.h +file_logger_plugin.o: plugin_architecture_api.h +file_logger_plugin.o: request.h +file_logger_plugin.o: request_c_api.h +file_logger_plugin.o: request_results.h +file_logger_plugin.o: serialization_auto.h +file_logger_plugin.o: typeid_hash.h +file_logger_plugin.o: unknown_object.h + +mysql_plugin.o: countedPtr.h +mysql_plugin.o: cpp_to_c_helpers.h +mysql_plugin.o: event.h +mysql_plugin.o: event_c_api.h +mysql_plugin.o: exceptions.h +mysql_plugin.o: mysql_plugin.cpp +mysql_plugin.o: mysql_plugin.h +mysql_plugin.o: plugin_architecture_api.h +mysql_plugin.o: request.h +mysql_plugin.o: request_c_api.h +mysql_plugin.o: request_results.h +mysql_plugin.o: serialization_auto.h +mysql_plugin.o: tunables.h +mysql_plugin.o: typeid_hash.h +mysql_plugin.o: unknown_object.h +mysql_plugin.o: vsfutils.h +mysql_plugin.o: vsfutils_c.h + +pgsql_plugin.o: countedPtr.h +pgsql_plugin.o: cpp_to_c_helpers.h +pgsql_plugin.o: event.h +pgsql_plugin.o: event_c_api.h +pgsql_plugin.o: exceptions.h +pgsql_plugin.o: pgsql_plugin.cpp +pgsql_plugin.o: pgsql_plugin.h +pgsql_plugin.o: plugin_architecture_api.h +pgsql_plugin.o: request.h +pgsql_plugin.o: request_c_api.h +pgsql_plugin.o: request_results.h +pgsql_plugin.o: serialization_auto.h +pgsql_plugin.o: tunables.h +pgsql_plugin.o: typeid_hash.h +pgsql_plugin.o: unknown_object.h +pgsql_plugin.o: vsfutils.h +pgsql_plugin.o: vsfutils_c.h + +plugin_architecture_api.o: countedPtr.h +plugin_architecture_api.o: cpp_to_c_helpers.h +plugin_architecture_api.o: event.h +plugin_architecture_api.o: event_c_api.h +plugin_architecture_api.o: exceptions.h +plugin_architecture_api.o: plugin_architecture_api.cpp +plugin_architecture_api.o: plugin_architecture_api.h +plugin_architecture_api.o: request.h +plugin_architecture_api.o: request_c_api.h +plugin_architecture_api.o: request_results.h +plugin_architecture_api.o: serialization_auto.h +plugin_architecture_api.o: typeid_hash.h +plugin_architecture_api.o: unknown_object.h + +plugin_architecture_interface.o: countedPtr.h +plugin_architecture_interface.o: cpp_to_c_helpers.h +plugin_architecture_interface.o: event_c_api.h +plugin_architecture_interface.o: exceptions.h +plugin_architecture_interface.o: filesize.h +plugin_architecture_interface.o: plugin_architecture_interface.cpp +plugin_architecture_interface.o: plugin_architecture_interface.h +plugin_architecture_interface.o: request.h +plugin_architecture_interface.o: request_c_api.h +plugin_architecture_interface.o: request_results.h +plugin_architecture_interface.o: serialization_auto.h +plugin_architecture_interface.o: session.h +plugin_architecture_interface.o: slave_thread.h +plugin_architecture_interface.o: str.h +plugin_architecture_interface.o: typeid_hash.h +plugin_architecture_interface.o: udsrf.h +plugin_architecture_interface.o: udsrf_auto.h +plugin_architecture_interface.o: unknown_object.h + +request.o: countedPtr.h +request.o: cpp_to_c_helpers.h +request.o: event.h +request.o: event_c_api.h +request.o: exceptions.h +request.o: filesize.h +request.o: plugin_architecture_api.h +request.o: plugin_architecture_interface.h +request.o: request.cpp +request.o: request.h +request.o: request_auto.h +request.o: request_c_api.h +request.o: request_results.h +request.o: serialization_auto.h +request.o: session.h +request.o: str.h +request.o: typeid_hash.h +request.o: udsrf.h +request.o: udsrf_auto.h +request.o: unknown_object.h + +request_results.o: cpp_to_c_helpers.h +request_results.o: request_results.cpp +request_results.o: request_results.h + +slave_thread.o: countedPtr.h +slave_thread.o: cpp_to_c_helpers.h +slave_thread.o: event.h +slave_thread.o: event_c_api.h +slave_thread.o: exceptions.h +slave_thread.o: file_logger_plugin.h +slave_thread.o: filesize.h +slave_thread.o: mysql_plugin.h +slave_thread.o: pgsql_plugin.h +slave_thread.o: plugin_architecture_api.h +slave_thread.o: plugin_architecture_interface.h +slave_thread.o: request.h +slave_thread.o: request_c_api.h +slave_thread.o: request_results.h +slave_thread.o: serialization_auto.h +slave_thread.o: session.h +slave_thread.o: slave_thread.cpp +slave_thread.o: slave_thread.h +slave_thread.o: slave_thread_auto.h +slave_thread.o: str.h +slave_thread.o: tunables.h +slave_thread.o: typeid_hash.h +slave_thread.o: udsrf.h +slave_thread.o: udsrf_auto.h +slave_thread.o: unknown_object.h +slave_thread.o: vsfutils.h +slave_thread.o: vsfutils_c.h + +typeid_hash.o: typeid_hash.cpp +typeid_hash.o: typeid_hash.h + +vsfutils.o: cpp_to_c_helpers.h +vsfutils.o: vsfutils.cpp +vsfutils.o: vsfutils.h +vsfutils.o: vsfutils_c.h + +udsrf.o: exceptions.h +udsrf.o: udsrf.cpp +udsrf.o: udsrf.h + +hi_prio_thread.o: exceptions.h +hi_prio_thread.o: hi_prio_thread.cpp +hi_prio_thread.o: hi_prio_thread.h +hi_prio_thread.o: udsrf.h + +lo_prio_thread.o: hi_prio_thread.h +lo_prio_thread.o: lo_prio_thread.cpp +lo_prio_thread.o: lo_prio_thread.h + diff --git a/Makefile_default b/Makefile_default new file mode 100644 index 0000000..8b1065e --- /dev/null +++ b/Makefile_default @@ -0,0 +1,744 @@ +# Makefile for systems with GNU tools +CC = gcc +CXX = g++ +INSTALL = install + +#Debug flags +CFLAGS = -g -Wall -W -Wshadow +CXXFLAGS = -g -Wall `mysql_config --cflags` + +IFLAGS = $(ILINUX) -idirafter dummyinc +IXXFLAGS = -I`pg_config --includedir` + +#Release flags +#CFLAGS = -O2 -Wall -W -Wshadow -pedantic -Werror -Wconversion +#CXXFLAGS = -O2 -Wall -W -Wshadow -pedantic -Werror -Wconversion `mysql_config --cflags` + +LIBS = `./vsf_findlibs.sh` -lpthread `mysql_config --libs` -L`pg_config --libdir` -lpq +LINK = -g + +#All *.cpp files in the current directory +OBJSCPP := $(patsubst %.cpp,%.o,$(wildcard *.cpp)) +#All *.c files in the current directory +OBJS := $(patsubst %.c,%.o,$(wildcard *.c)) + +#Build rule for *.c files +.c.o: + $(CC) -c $*.c $(CFLAGS) $(IFLAGS) + +#Build rule for *.cpp files +.cpp.o: + $(CXX) -c $*.cpp $(CXXFLAGS) $(IXXFLAGS) + +#link the executable +vsftpd: $(OBJS) $(OBJSCPP) + $(CXX) -o vsftpd $(OBJS) $(OBJSCPP) $(LINK) $(LIBS) $(LDFLAGS) + +#default vsftp install +install: + if [ -x /usr/local/sbin ]; then \ + $(INSTALL) -m 755 vsftpd /usr/local/sbin/vsftpd; \ + else \ + $(INSTALL) -m 755 vsftpd /usr/sbin/vsftpd; fi + if [ -x /usr/local/man ]; then \ + $(INSTALL) -m 644 vsftpd.8 /usr/local/man/man8/vsftpd.8; \ + $(INSTALL) -m 644 vsftpd.conf.5 /usr/local/man/man5/vsftpd.conf.5; \ + elif [ -x /usr/share/man ]; then \ + $(INSTALL) -m 644 vsftpd.8 /usr/share/man/man8/vsftpd.8; \ + $(INSTALL) -m 644 vsftpd.conf.5 /usr/share/man/man5/vsftpd.conf.5; \ + else \ + $(INSTALL) -m 644 vsftpd.8 /usr/man/man8/vsftpd.8; \ + $(INSTALL) -m 644 vsftpd.conf.5 /usr/man/man5/vsftpd.conf.5; fi + if [ -x /etc/xinetd.d ]; then \ + $(INSTALL) -m 644 xinetd.d/vsftpd /etc/xinetd.d/vsftpd; fi + +#clean objects, executable and auto-generated files +clean: autoclean + rm -f *.o *.swp vsftpd + +#remake dependancies +depend: + mkdep -o Makefile *.c *.cpp + +#build rules for code-generators +code_generation/struct_filter: code_generation/struct_filter.lex + (cd code_generation; ./build.sh struct_filter;); + +code_generation/serialization: code_generation/serialization.lex code_generation/serialization.yacc + (cd code_generation; ./build.sh serialization;); + +code_generation/sender_receiver: code_generation/sender_receiver.lex code_generation/sender_receiver.yacc + (cd code_generation; ./build.sh sender_receiver;); + +code_generation/free_memory: code_generation/free_memory.lex code_generation/free_memory.yacc + (cd code_generation; ./build.sh free_memory;); + +#fake targets +.PHONY: all autoclean depend + +all : depend autoclean vsftpd + +#removes autobuilt files +autoclean: + rm udsrf_auto.h &>/dev/null; echo -n + rm event_auto.h &>/dev/null; echo -n + rm request_auto.h &>/dev/null; echo -n + rm serialization_auto.h &>/dev/null; echo -n + rm slave_thread_auto.h &>/dev/null; echo -n + rm code_generation/struct_filter &>/dev/null; echo -n + rm code_generation/serialization &>/dev/null; echo -n + rm code_generation/sender_receiver &>/dev/null; echo -n + rm code_generation/free_memory &>/dev/null; echo -n + +#hardcoded dependancies +udsrf.h: udsrf_auto.h +event.cpp : event_auto.h +request.cpp : request_auto.h +unknown_object.h : serialization_auto.h +slave_thread.cpp : slave_thread_auto.h + +udsrf_auto.h: code_generation/sender_receiver code_generation/struct_filter event_c_api.h request_c_api.h + cat event_c_api.h | code_generation/struct_filter | code_generation/sender_receiver s > udsrf_auto.h + cat event_c_api.h | code_generation/struct_filter | code_generation/sender_receiver r >> udsrf_auto.h + cat request_c_api.h | code_generation/struct_filter | code_generation/sender_receiver s >> udsrf_auto.h + cat request_c_api.h | code_generation/struct_filter | code_generation/sender_receiver r >> udsrf_auto.h + +event_auto.h: event_c_api.h code_generation/events_receiver.py code_generation/free_memory code_generation/struct_filter + cat event_c_api.h | code_generation/struct_filter | code_generation/events_receiver.py Event > event_auto.h + cat event_c_api.h | code_generation/struct_filter | code_generation/free_memory >> event_auto.h + +request_auto.h: request_c_api.h code_generation/events_receiver.py code_generation/free_memory code_generation/struct_filter + cat request_c_api.h | code_generation/struct_filter | code_generation/events_receiver.py Request > request_auto.h + cat request_c_api.h | code_generation/struct_filter | code_generation/free_memory >> request_auto.h + +serialization_auto.h : request_c_api.h code_generation/serialization code_generation/struct_filter + cat request_results.h | code_generation/struct_filter | code_generation/serialization s > serialization_auto.h + cat request_results.h | code_generation/struct_filter | code_generation/serialization d >> serialization_auto.h + +slave_thread_auto.h : mysql_plugin.h pgsql_plugin.h file_logger_plugin.h code_generation/plugin_creator.py + cat *.h | code_generation/plugin_creator.py > slave_thread_auto.h + +#--- DO NOT EDIT BELOW THIS LINE +#--- These lines were automatically generated by `mkdep' + +access.o: access.c +access.o: access.h +access.o: cpp_to_c_helpers.h +access.o: filesize.h +access.o: ls.h +access.o: str.h +access.o: tunables.h + +ascii.o: ascii.c +ascii.o: ascii.h + +banner.o: banner.c +banner.o: banner.h +banner.o: cpp_to_c_helpers.h +banner.o: filesize.h +banner.o: filestr.h +banner.o: ftpcmdio.h +banner.o: session.h +banner.o: str.h +banner.o: strlist.h +banner.o: sysstr.h +banner.o: sysutil.h +banner.o: tunables.h + +features.o: features.c +features.o: features.h +features.o: ftpcmdio.h +features.o: ftpcodes.h +features.o: tunables.h + +filestr.o: cpp_to_c_helpers.h +filestr.o: filesize.h +filestr.o: filestr.c +filestr.o: filestr.h +filestr.o: secbuf.h +filestr.o: str.h +filestr.o: sysutil.h + +ftpcmdio.o: cpp_to_c_helpers.h +ftpcmdio.o: defs.h +ftpcmdio.o: filesize.h +ftpcmdio.o: ftpcmdio.c +ftpcmdio.o: ftpcmdio.h +ftpcmdio.o: ftpcodes.h +ftpcmdio.o: logging.h +ftpcmdio.o: netstr.h +ftpcmdio.o: readwrite.h +ftpcmdio.o: secbuf.h +ftpcmdio.o: session.h +ftpcmdio.o: str.h +ftpcmdio.o: sysutil.h +ftpcmdio.o: tunables.h +ftpcmdio.o: utility.h + +ftpdataio.o: ascii.h +ftpdataio.o: cpp_to_c_helpers.h +ftpdataio.o: defs.h +ftpdataio.o: filesize.h +ftpdataio.o: ftpcmdio.h +ftpdataio.o: ftpcodes.h +ftpdataio.o: ftpdataio.c +ftpdataio.o: ftpdataio.h +ftpdataio.o: logging.h +ftpdataio.o: ls.h +ftpdataio.o: oneprocess.h +ftpdataio.o: readwrite.h +ftpdataio.o: secbuf.h +ftpdataio.o: session.h +ftpdataio.o: ssl.h +ftpdataio.o: str.h +ftpdataio.o: strlist.h +ftpdataio.o: sysdeputil.h +ftpdataio.o: sysstr.h +ftpdataio.o: sysutil.h +ftpdataio.o: tunables.h +ftpdataio.o: twoprocess.h +ftpdataio.o: utility.h + +hash.o: filesize.h +hash.o: hash.c +hash.o: hash.h +hash.o: sysutil.h +hash.o: utility.h + +ipaddrparse.o: cpp_to_c_helpers.h +ipaddrparse.o: filesize.h +ipaddrparse.o: ipaddrparse.c +ipaddrparse.o: ipaddrparse.h +ipaddrparse.o: str.h +ipaddrparse.o: sysutil.h + +logging.o: cpp_to_c_helpers.h +logging.o: filesize.h +logging.o: logging.c +logging.o: logging.h +logging.o: session.h +logging.o: str.h +logging.o: sysstr.h +logging.o: sysutil.h +logging.o: tunables.h +logging.o: utility.h + +ls.o: access.h +ls.o: cpp_to_c_helpers.h +ls.o: filesize.h +ls.o: ls.c +ls.o: ls.h +ls.o: str.h +ls.o: strlist.h +ls.o: sysstr.h +ls.o: sysutil.h +ls.o: tunables.h + +main.o: cpp_to_c_helpers.h +main.o: defs.h +main.o: filesize.h +main.o: filestr.h +main.o: ftpcmdio.h +main.o: logging.h +main.o: main.c +main.o: oneprocess.h +main.o: parseconf.h +main.o: plugin_architecture_interface.h +main.o: session.h +main.o: ssl.h +main.o: standalone.h +main.o: str.h +main.o: sysdeputil.h +main.o: sysutil.h +main.o: tcpwrap.h +main.o: tunables.h +main.o: twoprocess.h +main.o: utility.h +main.o: vsftpver.h + +netstr.o: cpp_to_c_helpers.h +netstr.o: filesize.h +netstr.o: netstr.c +netstr.o: netstr.h +netstr.o: str.h +netstr.o: sysstr.h +netstr.o: sysutil.h +netstr.o: utility.h + +oneprocess.o: cpp_to_c_helpers.h +oneprocess.o: filesize.h +oneprocess.o: oneprocess.c +oneprocess.o: postlogin.h +oneprocess.o: prelogin.h +oneprocess.o: privops.h +oneprocess.o: secutil.h +oneprocess.o: session.h +oneprocess.o: str.h +oneprocess.o: sysdeputil.h +oneprocess.o: sysstr.h +oneprocess.o: tunables.h +oneprocess.o: utility.h + +parseconf.o: cpp_to_c_helpers.h +parseconf.o: defs.h +parseconf.o: filesize.h +parseconf.o: filestr.h +parseconf.o: parseconf.c +parseconf.o: parseconf.h +parseconf.o: str.h +parseconf.o: sysutil.h +parseconf.o: tunables.h +parseconf.o: utility.h + +postlogin.o: access.h +postlogin.o: banner.h +postlogin.o: cpp_to_c_helpers.h +postlogin.o: defs.h +postlogin.o: event_c_api.h +postlogin.o: features.h +postlogin.o: filesize.h +postlogin.o: ftpcmdio.h +postlogin.o: ftpcodes.h +postlogin.o: ftpdataio.h +postlogin.o: ipaddrparse.h +postlogin.o: logging.h +postlogin.o: oneprocess.h +postlogin.o: postlogin.c +postlogin.o: postlogin.h +postlogin.o: postlogin_events.h +postlogin.o: postlogin_requests.h +postlogin.o: request_c_api.h +postlogin.o: request_results.h +postlogin.o: session.h +postlogin.o: ssl.h +postlogin.o: str.h +postlogin.o: sysdeputil.h +postlogin.o: sysstr.h +postlogin.o: sysutil.h +postlogin.o: tunables.h +postlogin.o: twoprocess.h +postlogin.o: utility.h +postlogin.o: vsftpver.h +postlogin.o: vsfutils_c.h + +postlogin_events.o: cpp_to_c_helpers.h +postlogin_events.o: event_c_api.h +postlogin_events.o: filesize.h +postlogin_events.o: ftpdataio.h +postlogin_events.o: postlogin_events.c +postlogin_events.o: postlogin_events.h +postlogin_events.o: session.h +postlogin_events.o: str.h +postlogin_events.o: sysstr.h +postlogin_events.o: sysutil.h +postlogin_events.o: vsfutils_c.h + +postlogin_requests.o: cpp_to_c_helpers.h +postlogin_requests.o: filesize.h +postlogin_requests.o: ftpdataio.h +postlogin_requests.o: postlogin_requests.c +postlogin_requests.o: postlogin_requests.h +postlogin_requests.o: request_c_api.h +postlogin_requests.o: request_results.h +postlogin_requests.o: session.h +postlogin_requests.o: str.h +postlogin_requests.o: sysstr.h +postlogin_requests.o: sysutil.h +postlogin_requests.o: vsfutils_c.h + +postprivparent.o: cpp_to_c_helpers.h +postprivparent.o: defs.h +postprivparent.o: filesize.h +postprivparent.o: postprivparent.c +postprivparent.o: postprivparent.h +postprivparent.o: privops.h +postprivparent.o: privsock.h +postprivparent.o: secutil.h +postprivparent.o: session.h +postprivparent.o: str.h +postprivparent.o: sysdeputil.h +postprivparent.o: sysstr.h +postprivparent.o: sysutil.h +postprivparent.o: tunables.h +postprivparent.o: utility.h + +prelogin.o: banner.h +prelogin.o: cpp_to_c_helpers.h +prelogin.o: defs.h +prelogin.o: event_c_api.h +prelogin.o: features.h +prelogin.o: filesize.h +prelogin.o: ftpcmdio.h +prelogin.o: ftpcodes.h +prelogin.o: logging.h +prelogin.o: oneprocess.h +prelogin.o: prelogin.c +prelogin.o: prelogin.h +prelogin.o: session.h +prelogin.o: ssl.h +prelogin.o: str.h +prelogin.o: sysdeputil.h +prelogin.o: sysutil.h +prelogin.o: tunables.h +prelogin.o: twoprocess.h +prelogin.o: vsftpver.h + +privops.o: cpp_to_c_helpers.h +privops.o: defs.h +privops.o: event_c_api.h +privops.o: filesize.h +privops.o: ftpcmdio.h +privops.o: ftpcodes.h +privops.o: logging.h +privops.o: privops.c +privops.o: privops.h +privops.o: request_c_api.h +privops.o: request_results.h +privops.o: session.h +privops.o: str.h +privops.o: sysdeputil.h +privops.o: sysutil.h +privops.o: tunables.h +privops.o: utility.h + +privsock.o: cpp_to_c_helpers.h +privsock.o: defs.h +privsock.o: filesize.h +privsock.o: netstr.h +privsock.o: privsock.c +privsock.o: privsock.h +privsock.o: session.h +privsock.o: str.h +privsock.o: sysdeputil.h +privsock.o: sysutil.h +privsock.o: utility.h + +readwrite.o: cpp_to_c_helpers.h +readwrite.o: defs.h +readwrite.o: filesize.h +readwrite.o: netstr.h +readwrite.o: privsock.h +readwrite.o: readwrite.c +readwrite.o: readwrite.h +readwrite.o: session.h +readwrite.o: ssl.h +readwrite.o: str.h +readwrite.o: sysutil.h + +secbuf.o: filesize.h +secbuf.o: secbuf.c +secbuf.o: secbuf.h +secbuf.o: sysdeputil.h +secbuf.o: sysutil.h +secbuf.o: utility.h + +secutil.o: cpp_to_c_helpers.h +secutil.o: filesize.h +secutil.o: secutil.c +secutil.o: secutil.h +secutil.o: str.h +secutil.o: sysdeputil.h +secutil.o: sysstr.h +secutil.o: sysutil.h +secutil.o: utility.h + +ssl.o: builddefs.h +ssl.o: cpp_to_c_helpers.h +ssl.o: defs.h +ssl.o: filesize.h +ssl.o: ftpcmdio.h +ssl.o: ftpcodes.h +ssl.o: session.h +ssl.o: ssl.c +ssl.o: ssl.h +ssl.o: str.h +ssl.o: sysutil.h +ssl.o: tunables.h +ssl.o: utility.h + +standalone.o: cpp_to_c_helpers.h +standalone.o: defs.h +standalone.o: filesize.h +standalone.o: hash.h +standalone.o: ipaddrparse.h +standalone.o: parseconf.h +standalone.o: plugin_architecture_interface.h +standalone.o: session.h +standalone.o: standalone.c +standalone.o: standalone.h +standalone.o: str.h +standalone.o: sysdeputil.h +standalone.o: sysutil.h +standalone.o: tunables.h +standalone.o: utility.h + +str.o: cpp_to_c_helpers.h +str.o: filesize.h +str.o: str.c +str.o: str.h +str.o: sysutil.h +str.o: utility.h + +strlist.o: cpp_to_c_helpers.h +strlist.o: filesize.h +strlist.o: str.h +strlist.o: strlist.c +strlist.o: strlist.h +strlist.o: sysutil.h +strlist.o: utility.h + +sysdeputil.o: builddefs.h +sysdeputil.o: cpp_to_c_helpers.h +sysdeputil.o: defs.h +sysdeputil.o: filesize.h +sysdeputil.o: port/cmsg_extras.h +sysdeputil.o: port/porting_junk.h +sysdeputil.o: secbuf.h +sysdeputil.o: str.h +sysdeputil.o: sysdeputil.c +sysdeputil.o: sysdeputil.h +sysdeputil.o: sysutil.h +sysdeputil.o: tunables.h +sysdeputil.o: utility.h + +sysstr.o: cpp_to_c_helpers.h +sysstr.o: defs.h +sysstr.o: filesize.h +sysstr.o: secbuf.h +sysstr.o: str.h +sysstr.o: sysstr.c +sysstr.o: sysstr.h +sysstr.o: sysutil.h +sysstr.o: tunables.h +sysstr.o: utility.h + +sysutil.o: cpp_to_c_helpers.h +sysutil.o: event_c_api.h +sysutil.o: filesize.h +sysutil.o: port/cmsg_extras.h +sysutil.o: port/porting_junk.h +sysutil.o: request_c_api.h +sysutil.o: request_results.h +sysutil.o: sysutil.c +sysutil.o: sysutil.h +sysutil.o: tunables.h +sysutil.o: utility.h + +tcpwrap.o: builddefs.h +tcpwrap.o: tcpwrap.c +tcpwrap.o: tcpwrap.h +tcpwrap.o: utility.h + +tunables.o: tunables.c +tunables.o: tunables.h + +twoprocess.o: cpp_to_c_helpers.h +twoprocess.o: defs.h +twoprocess.o: filesize.h +twoprocess.o: filestr.h +twoprocess.o: parseconf.h +twoprocess.o: postlogin.h +twoprocess.o: postprivparent.h +twoprocess.o: prelogin.h +twoprocess.o: privops.h +twoprocess.o: privsock.h +twoprocess.o: readwrite.h +twoprocess.o: secutil.h +twoprocess.o: session.h +twoprocess.o: ssl.h +twoprocess.o: str.h +twoprocess.o: sysdeputil.h +twoprocess.o: sysstr.h +twoprocess.o: sysutil.h +twoprocess.o: tunables.h +twoprocess.o: twoprocess.c +twoprocess.o: twoprocess.h +twoprocess.o: utility.h + +utility.o: cpp_to_c_helpers.h +utility.o: defs.h +utility.o: filesize.h +utility.o: str.h +utility.o: sysutil.h +utility.o: utility.c +utility.o: utility.h + +event.o: countedPtr.h +event.o: cpp_to_c_helpers.h +event.o: event.cpp +event.o: event.h +event.o: event_auto.h +event.o: event_c_api.h +event.o: exceptions.h +event.o: filesize.h +event.o: plugin_architecture_api.h +event.o: plugin_architecture_interface.h +event.o: request.h +event.o: request_c_api.h +event.o: request_results.h +event.o: serialization_auto.h +event.o: session.h +event.o: str.h +event.o: typeid_hash.h +event.o: udsrf.h +event.o: udsrf_auto.h +event.o: unknown_object.h + +file_logger_plugin.o: countedPtr.h +file_logger_plugin.o: cpp_to_c_helpers.h +file_logger_plugin.o: event.h +file_logger_plugin.o: event_c_api.h +file_logger_plugin.o: exceptions.h +file_logger_plugin.o: file_logger_plugin.cpp +file_logger_plugin.o: file_logger_plugin.h +file_logger_plugin.o: plugin_architecture_api.h +file_logger_plugin.o: request.h +file_logger_plugin.o: request_c_api.h +file_logger_plugin.o: request_results.h +file_logger_plugin.o: serialization_auto.h +file_logger_plugin.o: typeid_hash.h +file_logger_plugin.o: unknown_object.h + +mysql_plugin.o: countedPtr.h +mysql_plugin.o: cpp_to_c_helpers.h +mysql_plugin.o: event.h +mysql_plugin.o: event_c_api.h +mysql_plugin.o: exceptions.h +mysql_plugin.o: mysql_plugin.cpp +mysql_plugin.o: mysql_plugin.h +mysql_plugin.o: plugin_architecture_api.h +mysql_plugin.o: request.h +mysql_plugin.o: request_c_api.h +mysql_plugin.o: request_results.h +mysql_plugin.o: serialization_auto.h +mysql_plugin.o: tunables.h +mysql_plugin.o: typeid_hash.h +mysql_plugin.o: unknown_object.h +mysql_plugin.o: vsfutils.h +mysql_plugin.o: vsfutils_c.h + +pgsql_plugin.o: countedPtr.h +pgsql_plugin.o: cpp_to_c_helpers.h +pgsql_plugin.o: event.h +pgsql_plugin.o: event_c_api.h +pgsql_plugin.o: exceptions.h +pgsql_plugin.o: pgsql_plugin.cpp +pgsql_plugin.o: pgsql_plugin.h +pgsql_plugin.o: plugin_architecture_api.h +pgsql_plugin.o: request.h +pgsql_plugin.o: request_c_api.h +pgsql_plugin.o: request_results.h +pgsql_plugin.o: serialization_auto.h +pgsql_plugin.o: tunables.h +pgsql_plugin.o: typeid_hash.h +pgsql_plugin.o: unknown_object.h +pgsql_plugin.o: vsfutils.h +pgsql_plugin.o: vsfutils_c.h + +plugin_architecture_api.o: countedPtr.h +plugin_architecture_api.o: cpp_to_c_helpers.h +plugin_architecture_api.o: event.h +plugin_architecture_api.o: event_c_api.h +plugin_architecture_api.o: exceptions.h +plugin_architecture_api.o: plugin_architecture_api.cpp +plugin_architecture_api.o: plugin_architecture_api.h +plugin_architecture_api.o: request.h +plugin_architecture_api.o: request_c_api.h +plugin_architecture_api.o: request_results.h +plugin_architecture_api.o: serialization_auto.h +plugin_architecture_api.o: typeid_hash.h +plugin_architecture_api.o: unknown_object.h + +plugin_architecture_interface.o: countedPtr.h +plugin_architecture_interface.o: cpp_to_c_helpers.h +plugin_architecture_interface.o: event_c_api.h +plugin_architecture_interface.o: exceptions.h +plugin_architecture_interface.o: filesize.h +plugin_architecture_interface.o: plugin_architecture_interface.cpp +plugin_architecture_interface.o: plugin_architecture_interface.h +plugin_architecture_interface.o: request.h +plugin_architecture_interface.o: request_c_api.h +plugin_architecture_interface.o: request_results.h +plugin_architecture_interface.o: serialization_auto.h +plugin_architecture_interface.o: session.h +plugin_architecture_interface.o: slave_thread.h +plugin_architecture_interface.o: str.h +plugin_architecture_interface.o: typeid_hash.h +plugin_architecture_interface.o: udsrf.h +plugin_architecture_interface.o: udsrf_auto.h +plugin_architecture_interface.o: unknown_object.h + +request.o: countedPtr.h +request.o: cpp_to_c_helpers.h +request.o: event.h +request.o: event_c_api.h +request.o: exceptions.h +request.o: filesize.h +request.o: plugin_architecture_api.h +request.o: plugin_architecture_interface.h +request.o: request.cpp +request.o: request.h +request.o: request_auto.h +request.o: request_c_api.h +request.o: request_results.h +request.o: serialization_auto.h +request.o: session.h +request.o: str.h +request.o: typeid_hash.h +request.o: udsrf.h +request.o: udsrf_auto.h +request.o: unknown_object.h + +request_results.o: cpp_to_c_helpers.h +request_results.o: request_results.cpp +request_results.o: request_results.h + +slave_thread.o: countedPtr.h +slave_thread.o: cpp_to_c_helpers.h +slave_thread.o: event.h +slave_thread.o: event_c_api.h +slave_thread.o: exceptions.h +slave_thread.o: file_logger_plugin.h +slave_thread.o: filesize.h +slave_thread.o: mysql_plugin.h +slave_thread.o: pgsql_plugin.h +slave_thread.o: plugin_architecture_api.h +slave_thread.o: plugin_architecture_interface.h +slave_thread.o: request.h +slave_thread.o: request_c_api.h +slave_thread.o: request_results.h +slave_thread.o: serialization_auto.h +slave_thread.o: session.h +slave_thread.o: slave_thread.cpp +slave_thread.o: slave_thread.h +slave_thread.o: slave_thread_auto.h +slave_thread.o: str.h +slave_thread.o: tunables.h +slave_thread.o: typeid_hash.h +slave_thread.o: udsrf.h +slave_thread.o: udsrf_auto.h +slave_thread.o: unknown_object.h +slave_thread.o: vsfutils.h +slave_thread.o: vsfutils_c.h + +typeid_hash.o: typeid_hash.cpp +typeid_hash.o: typeid_hash.h + +vsfutils.o: cpp_to_c_helpers.h +vsfutils.o: vsfutils.cpp +vsfutils.o: vsfutils.h +vsfutils.o: vsfutils_c.h + +udsrf.o: exceptions.h +udsrf.o: udsrf.cpp +udsrf.o: udsrf.h + +hi_prio_thread.o: exceptions.h +hi_prio_thread.o: hi_prio_thread.cpp +hi_prio_thread.o: hi_prio_thread.h +hi_prio_thread.o: udsrf.h + +lo_prio_thread.o: hi_prio_thread.h +lo_prio_thread.o: lo_prio_thread.cpp +lo_prio_thread.o: lo_prio_thread.h + diff --git a/README b/README new file mode 100644 index 0000000..bc9ec53 --- /dev/null +++ b/README @@ -0,0 +1,39 @@ +This is vsftpd, version 2.0.5 +Author: Chris Evans +Contact: chris@scary.beasts.org +Website: http://vsftpd.beasts.org/ +- All options are documented in the vsftpd.conf.5 manual page. +- See the FAQ file for solutions to frequently asked questions. +- Visit http://vsftpd.beasts.org/ for vsftpd news and releases. + +What is this? +============= + +vsftpd is an FTP server, or daemon. The "vs" stands for Very Secure. Obviously +this is not a guarantee, but a reflection that I have written the entire +codebase with security in mind, and carefully designed the program to be +resilient to attack. + +Recent evidence shows that vsftpd is also extremely fast and scalable. vsftpd +has achieved ~4000 concurrent users on a single machine, in a production +environment. + +vsftpd is now a proven stable solution. Of particular note, RedHat used vsftpd +to enable ftp.redhat.com to support 15,000 concurrent users across their +server pool. This extreme load was generated by the release of RedHat 7.2 to +the world. +vsftpd now powers some of the largest and most prestigious sites on the +internet. + +Installation +============ + +Please see the INSTALL file. + +Configuration +============= + +All configuration options are documented in the manual page vsftpd.conf.5. +Various example configurations are discussed in the EXAMPLE directory. +Frequently asked questions are tackled in the FAQ file. + diff --git a/README.security b/README.security new file mode 100644 index 0000000..bb5f44a --- /dev/null +++ b/README.security @@ -0,0 +1,2 @@ +For documentation about the security of vsftpd, please consult the files +located within the SECURITY directory. diff --git a/README.ssl b/README.ssl new file mode 100644 index 0000000..2ec70cb --- /dev/null +++ b/README.ssl @@ -0,0 +1,40 @@ +As of vsftpd version 2.0.0, SSL / TLS support is provided. + +The SSL / TLS support provides the ability to encrypt FTP logins and subsequent +commands, as well as the data transfers themselves. The encyption will, for +example, stop the stealing of sensitive passwords via network snooping. + +By default, SSL support is disabled both at compile time and at runtime. +Before considering enabling / using SSL support, there are some security +considerations: + +- Only enable SSL if absolutely necessary. Enabling SSL will allow attackers +to make use of any security problems in the OpenSSL libraries. Note that +the OpenSSL libraries are a large quantity of code and have had the occasional +security problem in the past. +For example, your server might use virtual users to control access to +non-sensitive download content. In this case, the passwords might not be +worth securing with SSL. + +- After enabling SSL, consider restricting access to an SSL enabled server +where feasible. For example, only the internal network might need access. + + +In order to enable and use SSL support, you need the following: + +- vsftpd built with OpenSSL support. This is a decision your vsftpd packager +made, or if you are building vsftpd yourself, edit "builddefs.h" and change the +"#undef VSF_BUILD_SSL" to "#define VSF_BUILD_SSL". +- "ssl_enable=YES" in your vsftpd.conf. +- A SSL certificate. By default, an RSA certificate is looked for at the +location /usr/share/ssl/certs/vsftpd.pem. To get an RSA certificate, either +buy one from a certificate authority, or you can create your own self-signed +certificate. If you have OpenSSL installed, you may find a "Makefile" in +your shared certificates directory, e.g. /usr/share/ssl/certs. In that case, +go to that directory and type e.g. "make vsftpd.pem". Then answer the +questions you are asked. Alternatively, read the man page for "openssl". +- Also be aware of the following SSL related parameters. Read the vsftpd.conf.5 +manual page to learn about them: allow_anon_ssl, force_local_logins_ssl, +force_local_data_ssl, ssl_sslv2, ssl_sslv3, ssl_tlsv1, rsa_cert_file, +dsa_cert_file, ssl_ciphers. + diff --git a/REFS b/REFS new file mode 100644 index 0000000..66a70aa --- /dev/null +++ b/REFS @@ -0,0 +1,45 @@ +The following FTP related references are useful: +(Implemented or partially implemented in vsftpd): +================================================= + +RFC-959, original FTP spec. +http://www.rfc-editor.org/rfc/rfc959.txt + +RFC-1123, the small FTP related section extends / clarifies RFC-959. +http://www.rfc-editor.org/rfc/rfc1123.txt + +RFC-2228, FTP security extensions. vsftpd implements the small subset needed +to support TLS / SSL connections. +http://www.rfc-editor.org/rfc/rfc2228.txt + +RFC-2389. Proposes FEAT and OPTS commands. +http://www.rfc-editor.org/rfc/rfc2389.txt + +RFC-2428. Essentially IPv6 support. +http://www.rfc-editor.org/rfc/rfc2428.txt + +"Securing FTP with TLS" (draft-murray-auth-ftp-ssl-09.txt). Document that +dives into the standardized behaviour of SSL / TLS connections in conjunction +with RFC-2228. +http://www.isaserver.org/articles/Securing_FTP_with_TLS.html + +"Extensions to FTP" (draft-ietf-ftpext-mlst-16.txt). Standardizes SIZE, MDTM, +MLST and MLSD. Note that vsftpd has not implemented MLST and MLSD due to lack +of demand from users. Perhaps the client support just isn't there. +http://www.ietf.org/internet-drafts/draft-ietf-ftpext-mlst-16.txt + +(Not implemented in vsftpd): +============================ + +RFC-1579. Proposes an APSV command. No users have requested this in vsftpd; +perhaps the client support just isn't there. +http://www.rfc-editor.org/rfc/rfc1579.txt + +RFC-1639. Proposes commands LPRT and LPSV. Seems to be deprecated in favour of +EPRT and EPSV in RFC-2428. +http://www.rfc-editor.org/rfc/rfc1639.txt + +RFC-2640. Deals with internationalization and the LANG command. I'm not seeing +any vsftpd users with requirements in this area. +http://www.rfc-editor.org/rfc/rfc2640.txt + diff --git a/REWARD b/REWARD new file mode 100644 index 0000000..0be4ca7 --- /dev/null +++ b/REWARD @@ -0,0 +1,2 @@ +At some stage, a reward may be offered to anyone finding a serious security +hole in vsftpd. Mail me if you have any ideas :) diff --git a/SIZE b/SIZE new file mode 100644 index 0000000..d918b1f --- /dev/null +++ b/SIZE @@ -0,0 +1,8 @@ +I'm not sure what you expected to find in this file :-) + +Anyway, this is to explain that vsftpd is not as much code as you might +expect from running a command like "wc -l *.c". Why? Simply because I use +a very verbose style of coding in vsftpd, which consumes a lot of lines. +Verbose code is very important in a secure program. How can you verify a +program's security if it is not readable? + diff --git a/SPEED b/SPEED new file mode 100644 index 0000000..a744b8f --- /dev/null +++ b/SPEED @@ -0,0 +1,37 @@ +- See also BENCHMARKS + +This FTPd should be very performant. The reasons for this are below, followed +by specific benchmarks as and when I get them. + +1) Generally, it is a fairly minimal FTPd. There should not be much code and/or +syscall bloat. + +2) For binary downloads, Linux sendfile() is used. This is a lot lighter on +CPU/syscall usage than your regular read()/write() loop. + +3) The "ls" command is fully internal. That is to say, an external "ls" command +does not need to be launch. Launching an external process is costly because +of the fork(), exec(), ELF loader startup, etc. + + +It is not all good news, of course. Potential sources of poor performance +include + +1) Overhead of two processes per session (in some common configurations). + +2) Excessive heap usage hidden behind the string API. + + +BENCHMARKS +========== + +1) vsftpd downloads ASCII data at at least twice the rate of wu-ftpd. + +2) vsftpd has achieved 86Mbyte/sec download over Gigabit ethernet between +Linux-2.4.x boxes (thanks to sendfile()) + +3) vsftpd has smaller virtual memory usage (and RSS, it seems) + +4) Various reports have trickled in and indicate that vsftpd thumps wu-ftpd +in performance tests. + diff --git a/TODO b/TODO new file mode 100644 index 0000000..c19e9f9 --- /dev/null +++ b/TODO @@ -0,0 +1,66 @@ +CRITICAL +======== + +- Improve FAQ, docs (ongoing..) +- Integrated test suite (I'm so lazy..) +- Sweedish, Russian etc. characters showing as ? in the log - many complaints. +- Check re-entrancy of usage of vsf_sysutil_exit_func. +- Fix SSL session timeout more gracefully. +- Use SSL_peek (surely pipelining won't work with current SSL code?) + +NOT SO CRITICAL +=============== + +- "add_group" support. +- Implict SSL support (port 990?) +- Do something with client certs. +- Add CHLD handler to priv parent process to take care of session crashes? +- Still reports FlashFXP broken when trying to do FXP. +- Add negation, other support to regex handler. +- Fix SIGHUP config file reload to correctly reset _removed_ settings to +their default values. +- Upload file size limits. +- Allow groups to be listed in user lists. +- Allow space in username. +- Minor: background should happen after listen has completed so that failure +can result in a non-zero exit code. +- Better reporting of failed uploads due to out of device space or quota all +used. +- option to chroot to home dir and THEN apply init_dir +- separate upload/download max rates +- select() is assuming Linux behaviour (not threatening stability) +- add example global bandwidth limiting. +- switch to new signal model +- have a chown_uploads for non-anon users too; also more control over +permissions of uploaded file + +ON THE BACK BURNER +================== + +- MLST, MLSD +- LPRT, LPSV +- Small race: signal might come in just before we start a blocking call +- log logout (pam session support provides this for locals) +- Limits on GIDs allowed to authenticate? +- Dynamic login info e.g. you are user XXX of YYY. +- Handle SIGINT. +- Session byte transfer counts in STAT output. +- Mac OS X setgroups() issue (report of success with 10.3.4, maybe OS X bug +has been fixed?) +- Fix for systems with no IPv6 (e.g. Solaris 7) (I'm only getting limited +numbers of these reports) +- Look into using GnuTLS in place of OpenSSL (more compatible license). +- put anon FTP users in wtmp too? +- Test vsftpd with pam_opie (issues with challenge/response vs. FTP protocol?) +- SITE GROUP support. +- SITE UTIME? +- Allow listener to listen on multiple IPs; bonus points if the different +IPs can have different configs. + +NOT PLANNED +=========== + +- telnet strings (no demand) +- "Minimal" build support +- transparent tar / compression support (no demand) + diff --git a/TUNING b/TUNING new file mode 100644 index 0000000..228b82c --- /dev/null +++ b/TUNING @@ -0,0 +1,26 @@ +So, you want vsftpd to go quickly? + +Here are some random assorted performance tips. + +1) vsftpd thrives because of its lightweight RSS and vm usage. If you run +a glibc based system (e.g. RedHat 5+), look in /etc/nsswitch.conf, and +if possible, disable the "nis" and "nisplus" options for "passwd", "shadow" +and "group". This prevents unneeded runtime libraries being added into +the vsftpd virtual memory space. + +2) vsftpd will attempt to save CPU power by using sendfile() on capable +operating systems. Currently, Linux 2.2+ and FreeBSD 3.0+ use sendfile(). +Consider running on these excellent operating systems. + +3) Irritated by vsftpd using _two_ processes per connection? Don't be, it's +a very secure architecture. However, if you run Linux 2.4+, or Linux 2.2.19+, a +"one process" security model is possible thanks to nifty security features. +See the vsftpd.conf man page. + +4) Avoid large directories (e.g. thousands of entries) if possible. Many +filesystems do not handle such cases efficiently at all. Preparing large +directory listings will require vsftpd to use moderate amounts of memory +and CPU. If you _must_ have large directories, consider either making them +unreadable, or use a filesystem which copes well with large directories such +as reiserfs. + diff --git a/access.c b/access.c new file mode 100644 index 0000000..3b32767 --- /dev/null +++ b/access.c @@ -0,0 +1,72 @@ +/* + * Part of Very Secure FTPd + * Licence: GPL v2 + * Author: Chris Evans + * access.c + * + * Routines to do very very simple access control based on filenames. + */ + +#include "access.h" +#include "ls.h" +#include "tunables.h" +#include "str.h" + +int +vsf_access_check_file(const struct mystr* p_filename_str) +{ + static struct mystr s_access_str; + + if (!tunable_deny_file) + { + return 1; + } + if (str_isempty(&s_access_str)) + { + str_alloc_text(&s_access_str, tunable_deny_file); + } + if (vsf_filename_passes_filter(p_filename_str, &s_access_str)) + { + return 0; + } + else + { + struct str_locate_result loc_res = + str_locate_str(p_filename_str, &s_access_str); + if (loc_res.found) + { + return 0; + } + } + return 1; +} + +int +vsf_access_check_file_visible(const struct mystr* p_filename_str) +{ + static struct mystr s_access_str; + + if (!tunable_hide_file) + { + return 1; + } + if (str_isempty(&s_access_str)) + { + str_alloc_text(&s_access_str, tunable_hide_file); + } + if (vsf_filename_passes_filter(p_filename_str, &s_access_str)) + { + return 0; + } + else + { + struct str_locate_result loc_res = + str_locate_str(p_filename_str, &s_access_str); + if (loc_res.found) + { + return 0; + } + } + return 1; +} + diff --git a/access.h b/access.h new file mode 100644 index 0000000..788c071 --- /dev/null +++ b/access.h @@ -0,0 +1,29 @@ +#ifndef VSF_ACCESS_H +#define VSF_ACCESS_H + +struct mystr; + +/* vsf_access_check_file() + * PURPOSE + * Check whether the current session has permission to access the given + * filename. + * PARAMETERS + * p_filename_str - the filename to check access for + * RETURNS + * Returns 1 if access is granted, otherwise 0. + */ +int vsf_access_check_file(const struct mystr* p_filename_str); + +/* vsf_access_check_file_visible() + * PURPOSE + * Check whether the current session has permission to view the given + * filename in directory listings. + * PARAMETERS + * p_filename_str - the filename to check visibility for + * RETURNS + * Returns 1 if the file should be visible, otherwise 0. + */ +int vsf_access_check_file_visible(const struct mystr* p_filename_str); + +#endif /* VSF_ACCESS_H */ + diff --git a/ascii.c b/ascii.c new file mode 100644 index 0000000..9f3e0e5 --- /dev/null +++ b/ascii.c @@ -0,0 +1,78 @@ +/* + * Part of Very Secure FTPd + * Licence: GPL v2 + * Author: Chris Evans + * ascii.c + * + * Routines to handle ASCII mode tranfers. Yuk. + */ + +#include "ascii.h" + +struct ascii_to_bin_ret +vsf_ascii_ascii_to_bin(char* p_buf, unsigned int in_len, int prev_cr) +{ + /* Task: translate all \r\n into plain \n. A plain \r not followed by \n must + * not be removed. + */ + struct ascii_to_bin_ret ret; + unsigned int indexx = 0; + unsigned int written = 0; + char* p_out = p_buf + 1; + ret.last_was_cr = 0; + if (prev_cr && (!in_len || p_out[0] != '\n')) + { + p_buf[0] = '\r'; + ret.p_buf = p_buf; + written++; + } + else + { + ret.p_buf = p_out; + } + while (indexx < in_len) + { + char the_char = p_buf[indexx + 1]; + if (the_char != '\r') + { + *p_out++ = the_char; + written++; + } + else if (indexx == in_len - 1) + { + ret.last_was_cr = 1; + } + else if (p_buf[indexx + 2] != '\n') + { + *p_out++ = the_char; + written++; + } + indexx++; + } + ret.stored = written; + return ret; +} + +unsigned int +vsf_ascii_bin_to_ascii(const char* p_in, char* p_out, unsigned int in_len) +{ + /* Task: translate all \n into \r\n. Note that \r\n becomes \r\r\n. That's + * what wu-ftpd does, and it's easier :-) + */ + unsigned int indexx = 0; + unsigned int written = 0; + while (indexx < in_len) + { + char the_char = p_in[indexx]; + if (the_char == '\n') + { + *p_out++ = '\r'; + written++; + } + *p_out++ = the_char; + written++; + indexx++; + } + return written; +} + diff --git a/ascii.h b/ascii.h new file mode 100644 index 0000000..0a30a2b --- /dev/null +++ b/ascii.h @@ -0,0 +1,48 @@ +#ifndef VSFTP_ASCII_H +#define VSFTP_ASCII_H + +struct mystr; + +/* vsf_ascii_ascii_to_bin() + * PURPOSE + * This function converts an input buffer from ascii format to binary format. + * This entails ripping out all occurences of '\r' that are followed by '\n'. + * The result is stored in "p_out". + * PARAMETERS + * p_in - the input and output buffer, which MUST BE at least as big as + * "in_len" PLUS ONE. This is to cater for a leading '\r' in the + * buffer if certain conditions are met. + * in_len - the length in bytes of the buffer. + * prev_cr - set to non-zero if this buffer fragment was immediately + * preceeded by a '\r'. + * RETURNS + * The number of characters stored in the buffer, the buffer address, and + * if we ended on a '\r'. + */ +struct ascii_to_bin_ret +{ + unsigned int stored; + int last_was_cr; + char* p_buf; +}; +struct ascii_to_bin_ret vsf_ascii_ascii_to_bin( + char* p_in, unsigned int in_len, int prev_cr); + +/* vsf_ascii_bin_to_ascii() + * PURPOSE + * This function converts an input buffer from binary format to ascii format. + * This entails replacing all occurences of '\n' with '\r\n'. The result is + * stored in "p_out". + * PARAMETERS + * p_in - the input buffer, which is not modified + * p_out - the output buffer, which MUST BE at least TWICE as big as + * "in_len" + * in_len - the length in bytes of the input buffer + * RETURNS + * The number of characters stored in the output buffer + */ +unsigned int vsf_ascii_bin_to_ascii(const char* p_in, char* p_out, + unsigned int in_len); + +#endif /* VSFTP_ASCII_H */ + diff --git a/banner.c b/banner.c new file mode 100644 index 0000000..bc662d0 --- /dev/null +++ b/banner.c @@ -0,0 +1,76 @@ +/* + * Part of Very Secure FTPd + * Licence: GPL v2 + * Author: Chris Evans + * banner.c + * + * Calls exposed to handle the junk a typical FTP server has to do upon + * entering a new directory (messages, etc), as well as general banner + * writing support. + */ + +#include "banner.h" +#include "strlist.h" +#include "str.h" +#include "sysstr.h" +#include "tunables.h" +#include "ftpcmdio.h" +#include "filestr.h" +#include "session.h" +#include "sysutil.h" + +/* Definitions */ +#define VSFTP_MAX_VISIT_REMEMBER 100 +#define VSFTP_MAX_MSGFILE_SIZE 4000 + +void +vsf_banner_dir_changed(struct vsf_session* p_sess, int ftpcode) +{ + struct mystr dir_str = INIT_MYSTR; + /* Do nothing if .message support is off */ + if (!tunable_dirmessage_enable) + { + return; + } + if (p_sess->p_visited_dir_list == 0) + { + struct mystr_list the_list = INIT_STRLIST; + p_sess->p_visited_dir_list = vsf_sysutil_malloc(sizeof(struct mystr_list)); + *p_sess->p_visited_dir_list = the_list; + } + str_getcwd(&dir_str); + /* Do nothing if we already visited this directory */ + if (!str_list_contains_str(p_sess->p_visited_dir_list, &dir_str)) + { + /* Just in case, cap the max. no of visited directories we'll remember */ + if (str_list_get_length(p_sess->p_visited_dir_list) < + VSFTP_MAX_VISIT_REMEMBER) + { + str_list_add(p_sess->p_visited_dir_list, &dir_str, 0); + } + /* If we have a .message file, squirt it out prepended by the ftpcode and + * the continuation mark '-' + */ + { + struct mystr msg_file_str = INIT_MYSTR; + (void) str_fileread(&msg_file_str, tunable_message_file, + VSFTP_MAX_MSGFILE_SIZE); + vsf_banner_write(p_sess, &msg_file_str, ftpcode); + str_free(&msg_file_str); + } + } + str_free(&dir_str); +} + +void +vsf_banner_write(struct vsf_session* p_sess, struct mystr* p_str, int ftpcode) +{ + struct mystr msg_line_str = INIT_MYSTR; + unsigned int str_pos = 0; + while (str_getline(p_str, &msg_line_str, &str_pos)) + { + vsf_cmdio_write_str_hyphen(p_sess, ftpcode, &msg_line_str); + } + str_free(&msg_line_str); +} + diff --git a/banner.h b/banner.h new file mode 100644 index 0000000..0f3fc42 --- /dev/null +++ b/banner.h @@ -0,0 +1,33 @@ +#ifndef VSF_BANNER_H +#define VSF_BANNER_H + +struct vsf_session; +struct mystr; + +/* vsf_banner_dir_changed() + * PURPOSE + * This function, when called, will check if the current directory has just + * been entered for the first time in this session. If so, and message file + * support is on, a message file is looked for (default .message), and output + * to the FTP control connection with the FTP code prefix specified by + * "ftpcode". + * PARAMETERS + * p_sess - the current FTP session object + * ftpcode - the FTP code to show with the message + */ +void vsf_banner_dir_changed(struct vsf_session* p_sess, int ftpcode); + +/* vsf_banner_write() + * PURPOSE + * This function, when called, will write the specified string as a multiline + * FTP banner, using the specified FTP response code. + * PARAMETERS + * p_sess - the current FTP session object + * p_str - the string to write + * ftpcode - the FTP code to show with the message + */ +void vsf_banner_write(struct vsf_session* p_sess, struct mystr* p_str, + int ftpcode); + +#endif /* VSF_BANNER_H */ + diff --git a/builddefs.h b/builddefs.h new file mode 100644 index 0000000..e908352 --- /dev/null +++ b/builddefs.h @@ -0,0 +1,9 @@ +#ifndef VSF_BUILDDEFS_H +#define VSF_BUILDDEFS_H + +#undef VSF_BUILD_TCPWRAPPERS +#define VSF_BUILD_PAM +#undef VSF_BUILD_SSL + +#endif /* VSF_BUILDDEFS_H */ + diff --git a/countedPtr.h b/countedPtr.h new file mode 100644 index 0000000..d9918da --- /dev/null +++ b/countedPtr.h @@ -0,0 +1,319 @@ +#ifndef _H_INCLUDE_COUNTED_PTR +#define _H_INCLUDE_COUNTED_PTR + +#include + +/** + * this is a reference counting smartpointer + * do not use this for arrays, use CountedArrayPtr + */ +template +class CountedPtr { +private: + T * ptr; + long * count; + +public: + explicit CountedPtr(T * p = 0) + : ptr(p), count(new long(1)) { + } + + CountedPtr(const CountedPtr & p) throw() + : ptr(p.ptr), count(p.count) { + ++*count; + } + + ~CountedPtr() throw() { + dispose(); + } + + CountedPtr& operator= (const CountedPtr & p) throw() { + if (this != &p) { + dispose(); + ptr = p.ptr; + count = p.count; + ++*count; + } + return *this; + } + + bool isNull() const throw() { + return ptr == 0; + } + + T& operator*() const throw() { + return *ptr; + } + + T* operator->() const throw() { + return ptr; + } + + T* get() const throw() { + return ptr; + } + +private: + void dispose() { + if (--*count == 0) { + delete count; + delete ptr; + ptr = 0; + count = 0; + } + } +}; + + +/** + * this is a reference counting smartpointer for arrays + */ +template +class CountedArrayPtr { +private: + T * ptr; + long * count; + +public: + explicit CountedArrayPtr(T * p = 0) + : ptr(p), count(new long(1)) { + } + + CountedArrayPtr(const CountedArrayPtr & p) throw() + : ptr(p.ptr), count(p.count) { + ++*count; + } + + ~CountedArrayPtr() throw() { + dispose(); + } + + CountedArrayPtr& operator= (const CountedArrayPtr& p) throw() { + if (this != &p) { + dispose(); + ptr = p.ptr; + count = p.count; + ++*count; + } + return *this; + } + + T& operator[](int index) const throw() { + return ptr[index]; + } + + T* get() const throw() { + return ptr; + } + +private: + void dispose() { + if (--*count == 0) { + delete count; + delete [] ptr; + ptr = 0; + } + } +}; + +template + class auto_arr + { + private: + _Tp* _M_ptr; + + public: + /// The pointed-to type. + typedef _Tp element_type; + + /** + * @brief An %auto_ptr is usually constructed from a raw pointer. + * @param p A pointer (defaults to NULL). + * + * This object now @e owns the object pointed to by @a p. + */ + explicit + auto_arr(element_type* __p = 0) throw() : _M_ptr(__p) { } + + /** + * @brief An %auto_ptr can be constructed from another %auto_ptr. + * @param a Another %auto_ptr of the same type. + * + * This object now @e owns the object previously owned by @a a, + * which has given up ownsership. + */ + auto_arr(auto_arr& __a) throw() : _M_ptr(__a.release()) { } + + /** + * @brief An %auto_ptr can be constructed from another %auto_ptr. + * @param a Another %auto_ptr of a different but related type. + * + * A pointer-to-Tp1 must be convertible to a + * pointer-to-Tp/element_type. + * + * This object now @e owns the object previously owned by @a a, + * which has given up ownsership. + */ + template + auto_arr(auto_arr<_Tp1>& __a) throw() : _M_ptr(__a.release()) { } + + /** + * @brief %auto_ptr assignment operator. + * @param a Another %auto_ptr of the same type. + * + * This object now @e owns the object previously owned by @a a, + * which has given up ownsership. The object that this one @e + * used to own and track has been deleted. + */ + auto_arr& + operator=(auto_arr& __a) throw() + { + reset(__a.release()); + return *this; + } + + /** + * @brief %auto_ptr assignment operator. + * @param a Another %auto_ptr of a different but related type. + * + * A pointer-to-Tp1 must be convertible to a pointer-to-Tp/element_type. + * + * This object now @e owns the object previously owned by @a a, + * which has given up ownsership. The object that this one @e + * used to own and track has been deleted. + */ + template + auto_arr& + operator=(auto_arr<_Tp1>& __a) throw() + { + reset(__a.release()); + return *this; + } + + /** + * When the %auto_ptr goes out of scope, the object it owns is + * deleted. If it no longer owns anything (i.e., @c get() is + * @c NULL), then this has no effect. + * + * @if maint + * The C++ standard says there is supposed to be an empty throw + * specification here, but omitting it is standard conforming. Its + * presence can be detected only if _Tp::~_Tp() throws, but this is + * prohibited. [17.4.3.6]/2 + * @end maint + */ + ~auto_arr() { delete [] _M_ptr; } + + /** + * @brief Smart pointer dereferencing. + * + * If this %auto_ptr no longer owns anything, then this + * operation will crash. (For a smart pointer, "no longer owns + * anything" is the same as being a null pointer, and you know + * what happens when you dereference one of those...) + */ + element_type& + operator*() const throw() + { + _GLIBCXX_DEBUG_ASSERT(_M_ptr != 0); + return *_M_ptr; + } + + /** + * @brief Smart pointer dereferencing. + * + * This returns the pointer itself, which the language then will + * automatically cause to be dereferenced. + */ + element_type* + operator->() const throw() + { + _GLIBCXX_DEBUG_ASSERT(_M_ptr != 0); + return _M_ptr; + } + + /** + * @brief Bypassing the smart pointer. + * @return The raw pointer being managed. + * + * You can get a copy of the pointer that this object owns, for + * situations such as passing to a function which only accepts + * a raw pointer. + * + * @note This %auto_ptr still owns the memory. + */ + element_type* + get() const throw() { return _M_ptr; } + + /** + * @brief Bypassing the smart pointer. + * @return The raw pointer being managed. + * + * You can get a copy of the pointer that this object owns, for + * situations such as passing to a function which only accepts + * a raw pointer. + * + * @note This %auto_ptr no longer owns the memory. When this object + * goes out of scope, nothing will happen. + */ + element_type* + release() throw() + { + element_type* __tmp = _M_ptr; + _M_ptr = 0; + return __tmp; + } + + /** + * @brief Forcibly deletes the managed object. + * @param p A pointer (defaults to NULL). + * + * This object now @e owns the object pointed to by @a p. The + * previous object has been deleted. + */ + void + reset(element_type* __p = 0) throw() + { + if (__p != _M_ptr) + { + delete [] _M_ptr; + _M_ptr = __p; + } + } + + /** @{ + * @brief Automatic conversions + * + * These operations convert an %auto_ptr into and from an auto_ptr_ref + * automatically as needed. This allows constructs such as + * @code + * auto_ptr func_returning_auto_ptr(.....); + * ... + * auto_ptr ptr = func_returning_auto_ptr(.....); + * @endcode + */ + auto_arr(std::auto_ptr_ref __ref) throw() + : _M_ptr(__ref._M_ptr) { } + + auto_arr& + operator=(std::auto_ptr_ref __ref) throw() + { + if (__ref._M_ptr != this->get()) + { + delete [] _M_ptr; + _M_ptr = __ref._M_ptr; + } + return *this; + } + + template + operator std::auto_ptr_ref<_Tp1>() throw() + { return std::auto_ptr_ref<_Tp1>(this->release()); } + + template + operator auto_arr<_Tp1>() throw() + { return auto_arr<_Tp1>(this->release()); } + /** @} */ + }; + +#endif diff --git a/cpp_to_c_helpers.h b/cpp_to_c_helpers.h new file mode 100644 index 0000000..de81f4f --- /dev/null +++ b/cpp_to_c_helpers.h @@ -0,0 +1,16 @@ +#ifndef VSF_C_T_CP_HELPERS +#define VSF_C_T_CP_HELPERS + +#ifdef __cplusplus + #define C_COMPATIBLE_FUNCTION extern "C" +#else + #define C_COMPATIBLE_FUNCTION +#endif + +#ifdef __cplusplus + #define HIDE_CPP_FROM_C(strings) strings +#else + #define HIDE_CPP_FROM_C(strings) +#endif + +#endif diff --git a/defs.h b/defs.h new file mode 100644 index 0000000..d2bcc1b --- /dev/null +++ b/defs.h @@ -0,0 +1,22 @@ +#ifndef VSF_DEFS_H +#define VSF_DEFS_H + +#define VSFTP_DEFAULT_CONFIG "/etc/vsftpd.conf" + +#define VSFTP_COMMAND_FD 0 + +#define VSFTP_PASSWORD_MAX 128 +#define VSFTP_USERNAME_MAX 32 +#define VSFTP_MAX_COMMAND_LINE 4096 +#define VSFTP_DATA_BUFSIZE 65536 +#define VSFTP_DIR_BUFSIZE 16384 +#define VSFTP_PATH_MAX 4096 +#define VSFTP_CONF_FILE_MAX 100000 +#define VSFTP_LISTEN_BACKLOG 32 +#define VSFTP_SECURE_UMASK 077 +#define VSFTP_ROOT_UID 0 +/* Must be greater than both VSFTP_MAX_COMMAND_LINE and VSFTP_DIR_BUFSIZE */ +#define VSFTP_PRIVSOCK_MAXSTR VSFTP_DIR_BUFSIZE + +#endif /* VSF_DEFS_H */ + diff --git a/event.cpp b/event.cpp new file mode 100644 index 0000000..d21c2e3 --- /dev/null +++ b/event.cpp @@ -0,0 +1,106 @@ +#include "event.h" +#include "udsrf.h" +#include "plugin_architecture_interface.h" +#include "plugin_architecture_api.h" +#include + +#define send_event_function(fun_name, struct_name) C_COMPATIBLE_FUNCTION void send_event_##fun_name (struct Event_##struct_name * event) { \ + sendData(plugin_architecture_get_slave_socket_slow(), (int)Event_Number_##struct_name); \ + sendData(plugin_architecture_get_slave_socket_slow(), Event_Number_##struct_name); \ + sendData(plugin_architecture_get_slave_socket_slow(), *event); \ +} + +#define send_event_double_function(fun_name, struct_name) C_COMPATIBLE_FUNCTION void send_event_##fun_name (struct Event_##struct_name * event) { \ + sendData(plugin_architecture_get_slave_socket_fast(), (int)Event_Number_##struct_name); \ + sendData(plugin_architecture_get_slave_socket_fast(), Event_Number_##struct_name); \ + sendData(plugin_architecture_get_slave_socket_fast(), *event); \ + sendData(plugin_architecture_get_slave_socket_slow(), (int)Event_Number_##struct_name); \ + sendData(plugin_architecture_get_slave_socket_slow(), Event_Number_##struct_name); \ + sendData(plugin_architecture_get_slave_socket_slow(), *event); \ +} + +#define getHandled(eventname) void Event##eventname ::getHandled(VSFPlugin & plugin) { \ + plugin.handleEvent(*this); \ + } + +#define create_read_member(membername) void Event##membername ::readFromSock(int sock) { recvData(sock, i_struct); } + +#define gen_funcs(fname, sname) send_event_function(fname, sname) \ + getHandled(sname) \ + create_read_member(sname) + +#define gen_funcs_double(fname, sname) send_event_double_function(fname, sname) \ + getHandled(sname) \ + create_read_member(sname) + +template void deleteStruct(T & to_delete) {} + +#include "event_auto.h" + +struct vsf_sysutil_sockaddr +{ + union + { + struct sockaddr u_sockaddr; + struct sockaddr_in u_sockaddr_in; + struct sockaddr_in6 u_sockaddr_in6; + } u; +}; + +C_COMPATIBLE_FUNCTION void populate_shared_event(struct Event_Shared * event, struct vsf_session* p_sess) { + if (event == 0 || p_sess == 0) + { + throw NullPointerException(); + } + memcpy(&event->remote_ip, &p_sess->p_remote_addr->u.u_sockaddr_in, sizeof(event->remote_ip)); + event->user_name = const_cast(str_strdup(&p_sess->plugin_arch_user_str)); + gettimeofday(&event->timestamp,0); +} + +C_COMPATIBLE_FUNCTION void populate_shared_event_for_connection(struct Event_Shared * event, struct vsf_session* p_sess) { + if (event == 0 || p_sess == 0) + { + throw NullPointerException(); + } + memcpy(&event->remote_ip, &p_sess->p_remote_addr->u.u_sockaddr_in, sizeof(event->remote_ip)); + event->user_name = ""; + gettimeofday(&event->timestamp,0); +} + +EventLogin::~EventLogin() { + deleteStruct(i_struct); +} +EventFailedLogin::~EventFailedLogin() { + deleteStruct(i_struct); +} +EventDownload::~EventDownload() { + deleteStruct(i_struct); +} +EventUpload::~EventUpload() { + deleteStruct(i_struct); +} +EventMkdir::~EventMkdir() { + deleteStruct(i_struct); +} +EventConnection::~EventConnection() { + deleteStruct(i_struct); +} +EventDelete::~EventDelete() { + deleteStruct(i_struct); +} +EventRename::~EventRename() { + deleteStruct(i_struct); +} +EventRmdir::~EventRmdir() { + deleteStruct(i_struct); +} +EventChmod::~EventChmod() { + deleteStruct(i_struct); +} +EventCliEnd::~EventCliEnd() { + deleteStruct(i_struct); +} +EventServQuit::~EventServQuit() { + deleteStruct(i_struct); +} + diff --git a/event.h b/event.h new file mode 100644 index 0000000..65b683b --- /dev/null +++ b/event.h @@ -0,0 +1,180 @@ +#ifndef VSF_EVENT +#define VSF_EVENT + +#include "event_c_api.h" +#include + +class VSFPlugin; + +/** + * Just a set of wrappers for the struct's. Mainly to provide polymorphism + * for the plugin architecture. + */ +class Event { +public: + virtual int getNumber() = 0; + virtual void getHandled(VSFPlugin &) = 0; + static std::auto_ptr readFromSocket(int sock); + virtual ~Event() {} +private: + virtual void readFromSock(int sock) = 0; +}; + +class EventLogin : public Event { +public: + virtual int getNumber() { return Event_Number_Login; } + virtual void getHandled(VSFPlugin &); + const struct Event_Login * getData() const {return &i_struct;} + virtual ~EventLogin(); +private: + EventLogin() {} + virtual void readFromSock(int sock); + struct Event_Login i_struct; + friend class Event; +}; + +class EventFailedLogin : public Event { +public: + virtual int getNumber() { return Event_Number_FailedLogin; } + virtual void getHandled(VSFPlugin &); + const struct Event_FailedLogin * getData() const {return &i_struct;} + virtual ~EventFailedLogin(); +private: + EventFailedLogin(){} + virtual void readFromSock(int sock); + struct Event_FailedLogin i_struct; + friend class Event; +}; + +class EventDownload : public Event { +public: + virtual int getNumber() { return Event_Number_Download; } + virtual void getHandled(VSFPlugin &); + const struct Event_Download * getData() const {return &i_struct;} + virtual ~EventDownload(); +private: + EventDownload() {} + virtual void readFromSock(int sock); + struct Event_Download i_struct; + friend class Event; +}; + +class EventUpload : public Event { +public: + virtual int getNumber() { return Event_Number_Upload; } + virtual void getHandled(VSFPlugin &); + const struct Event_Upload * getData() const {return &i_struct;} + virtual ~EventUpload(); +private: + EventUpload() {} + virtual void readFromSock(int sock); + struct Event_Upload i_struct; + friend class Event; +}; + +class EventMkdir : public Event { +public: + virtual int getNumber() { return Event_Number_Mkdir; } + virtual void getHandled(VSFPlugin &); + const struct Event_Mkdir * getData() const {return &i_struct;} + virtual ~EventMkdir(); +private: + EventMkdir() {} + virtual void readFromSock(int sock); + struct Event_Mkdir i_struct; + friend class Event; +}; + +class EventConnection : public Event { +public: + virtual int getNumber() { return Event_Number_Connection; } + virtual void getHandled(VSFPlugin &); + const struct Event_Connection * getData() const {return &i_struct;} + virtual ~EventConnection(); +private: + EventConnection() {} + virtual void readFromSock(int sock); + struct Event_Connection i_struct; + friend class Event; +}; + +class EventDelete : public Event { +public: + virtual int getNumber() { return Event_Number_Delete; } + virtual void getHandled(VSFPlugin &); + const struct Event_Delete * getData() const {return &i_struct;} + virtual ~EventDelete(); +private: + EventDelete() {} + virtual void readFromSock(int sock); + struct Event_Delete i_struct; + friend class Event; +}; + +class EventRename : public Event { +public: + virtual int getNumber() { return Event_Number_Rename; } + virtual void getHandled(VSFPlugin &); + const struct Event_Rename * getData() const {return &i_struct;} + virtual ~EventRename(); +private: + EventRename() {} + virtual void readFromSock(int sock); + struct Event_Rename i_struct; + friend class Event; +}; + +class EventRmdir : public Event { +public: + virtual int getNumber() { return Event_Number_Rmdir; } + virtual void getHandled(VSFPlugin &); + const struct Event_Rmdir * getData() const {return &i_struct;} + virtual ~EventRmdir(); +private: + EventRmdir() {} + virtual void readFromSock(int sock); + struct Event_Rmdir i_struct; + friend class Event; +}; + +class EventChmod : public Event { +public: + virtual int getNumber() { return Event_Number_Chmod; } + virtual void getHandled(VSFPlugin &); + const struct Event_Chmod * getData() const {return &i_struct;} + virtual ~EventChmod(); +private: + EventChmod() {} + virtual void readFromSock(int sock); + struct Event_Chmod i_struct; + friend class Event; +}; + +class EventCliEnd : public Event { +public: + virtual int getNumber() { return Event_Number_CliEnd; } + virtual void getHandled(VSFPlugin &); + const struct Event_CliEnd * getData() const {return &i_struct;} + virtual ~EventCliEnd(); +private: + EventCliEnd() {} + virtual void readFromSock(int sock); + struct Event_CliEnd i_struct; + friend class Event; +}; + +class EventServQuit : public Event { +public: + virtual int getNumber() { return Event_Number_ServQuit; } + virtual void getHandled(VSFPlugin &); + const struct Event_ServQuit * getData() const {return &i_struct;} + virtual ~EventServQuit(); +private: + EventServQuit() {} + virtual void readFromSock(int sock); + struct Event_ServQuit i_struct; + friend class Event; +}; + + +#endif //VSF_EVENT diff --git a/event_auto.h b/event_auto.h new file mode 100644 index 0000000..7444a19 --- /dev/null +++ b/event_auto.h @@ -0,0 +1,131 @@ +gen_funcs( login , Login ) +gen_funcs( failedLogin , FailedLogin ) +gen_funcs( download , Download ) +gen_funcs( upload , Upload ) +gen_funcs( mkdir , Mkdir ) +gen_funcs( connection , Connection ) +gen_funcs( delete , Delete ) +gen_funcs( rename , Rename ) +gen_funcs( rmdir , Rmdir ) +gen_funcs( chmod , Chmod ) +gen_funcs_double( cliEnd , CliEnd ) +gen_funcs_double( servQuit , ServQuit ) +std::auto_ptr Event::readFromSocket(int sock) { + Event_Number_Enum eventNumber; + recvData(sock, eventNumber); + if (!(eventNumber & Event_Identifier)) + { + throw InconsistentTypeException(); + } + std::auto_ptr toRet; + switch (eventNumber) + { + case Event_Number_Login: + toRet = std::auto_ptr(new EventLogin); + toRet->readFromSock(sock); + break; + case Event_Number_FailedLogin: + toRet = std::auto_ptr(new EventFailedLogin); + toRet->readFromSock(sock); + break; + case Event_Number_Download: + toRet = std::auto_ptr(new EventDownload); + toRet->readFromSock(sock); + break; + case Event_Number_Upload: + toRet = std::auto_ptr(new EventUpload); + toRet->readFromSock(sock); + break; + case Event_Number_Mkdir: + toRet = std::auto_ptr(new EventMkdir); + toRet->readFromSock(sock); + break; + case Event_Number_Connection: + toRet = std::auto_ptr(new EventConnection); + toRet->readFromSock(sock); + break; + case Event_Number_Delete: + toRet = std::auto_ptr(new EventDelete); + toRet->readFromSock(sock); + break; + case Event_Number_Rename: + toRet = std::auto_ptr(new EventRename); + toRet->readFromSock(sock); + break; + case Event_Number_Rmdir: + toRet = std::auto_ptr(new EventRmdir); + toRet->readFromSock(sock); + break; + case Event_Number_Chmod: + toRet = std::auto_ptr(new EventChmod); + toRet->readFromSock(sock); + break; + case Event_Number_CliEnd: + toRet = std::auto_ptr(new EventCliEnd); + toRet->readFromSock(sock); + break; + case Event_Number_ServQuit: + toRet = std::auto_ptr(new EventServQuit); + toRet->readFromSock(sock); + break; + default: + throw UnknownEventNumber(); + } +return toRet; +} +template<> void deleteStruct(Event_Shared & to_delete) { + deleteStruct(to_delete.timestamp); + deleteStruct(to_delete.remote_ip); + delete [] to_delete.user_name; +} +template<> void deleteStruct(Event_FileData_Shared & to_delete) { + delete [] to_delete.path; + delete [] to_delete.name; +} +template<> void deleteStruct(Event_Login & to_delete) { + deleteStruct(to_delete.sharedInfo); +} +template<> void deleteStruct(Event_FailedLogin & to_delete) { + deleteStruct(to_delete.sharedInfo); + delete [] to_delete.failed_password; +} +template<> void deleteStruct(Event_Download & to_delete) { + deleteStruct(to_delete.sharedInfo); + deleteStruct(to_delete.fileInfo); +} +template<> void deleteStruct(Event_Upload & to_delete) { + deleteStruct(to_delete.sharedInfo); + deleteStruct(to_delete.fileInfo); +} +template<> void deleteStruct(Event_Mkdir & to_delete) { + deleteStruct(to_delete.sharedInfo); + delete [] to_delete.path; +} +template<> void deleteStruct(Event_Connection & to_delete) { + deleteStruct(to_delete.sharedInfo); + delete [] to_delete.reason; +} +template<> void deleteStruct(Event_Delete & to_delete) { + deleteStruct(to_delete.sharedInfo); + delete [] to_delete.path; +} +template<> void deleteStruct(Event_Rename & to_delete) { + deleteStruct(to_delete.sharedInfo); + delete [] to_delete.old_name; + delete [] to_delete.new_name; +} +template<> void deleteStruct(Event_Rmdir & to_delete) { + deleteStruct(to_delete.sharedInfo); + delete [] to_delete.path; +} +template<> void deleteStruct(Event_Chmod & to_delete) { + deleteStruct(to_delete.sharedInfo); + delete [] to_delete.path; + delete [] to_delete.name; + delete [] to_delete.new_attrib; +} +template<> void deleteStruct(Event_CliEnd & to_delete) { + deleteStruct(to_delete.timestamp); +} +template<> void deleteStruct(Event_ServQuit & to_delete) { +} diff --git a/event_c_api.h b/event_c_api.h new file mode 100644 index 0000000..6678c41 --- /dev/null +++ b/event_c_api.h @@ -0,0 +1,280 @@ +#ifndef H_EVENT_C_API +#define H_EVENT_C_API + +#include "cpp_to_c_helpers.h" +#include +#include +#include +#include "session.h" + +static const int Event_Identifier = 0x01000000; + +enum Event_Number_Enum { + Event_Number_Identifier = 0x01000000, + Event_Number_Login, + Event_Number_FailedLogin, + Event_Number_Download, + Event_Number_Upload, + Event_Number_Mkdir, + Event_Number_Connection, + Event_Number_Delete, + Event_Number_Rename, + Event_Number_Rmdir, + Event_Number_Chmod, + Event_Number_CliEnd, + Event_Number_ServQuit, + Event_Number_Shared, + Event_Number_MaxNumber //do not remove, this **must** be the last enum +}; + +/** + * shared part of all events + */ +struct Event_Shared { + struct timeval timestamp; // time of the event + struct sockaddr_in remote_ip; // the ip of the remote host + const char * user_name; // the user name the remote host has logged in as. If the user is not logged in this is set to "" +}; + +/** + * shared part of all file-related events + */ +struct Event_FileData_Shared { + const char * path; // the directory the file resides in + const char * name; // file name + unsigned int size; // file size in bytes +}; + +/** + * sent when a successful login occurs + */ +struct Event_Login { + struct Event_Shared sharedInfo; + int is_anonymous; // is the login anonymous +}; + +/** + * sent when a failed login occurs + */ +struct Event_FailedLogin { + struct Event_Shared sharedInfo; + const char * failed_password; // password the user used and failed +}; + +/** + * sent when a download occurs, complete or not + */ +struct Event_Download { + struct Event_Shared sharedInfo; + struct Event_FileData_Shared fileInfo; + int is_complete; // was the download complete, -1 when permission was denied, 0 for success non-zero for failure +}; + +/** + * sent when an upload occurs + */ +struct Event_Upload { + struct Event_Shared sharedInfo; + struct Event_FileData_Shared fileInfo; + int is_complete; // was the upload complete, -1 when permission was denied + int is_append; // was the data appended to an existing file + int is_overwrite; // did the upload cause a file to be overwritten +}; + +/** + * sent when the user tries to create a directory + */ +struct Event_Mkdir { + struct Event_Shared sharedInfo; + const char * path; //path of the directory the user wanted to create + int succeeded; //did the operation succeed (-1 if because of request failure), 0 for success non-zero for failure +}; + +/** + * sent when a connection request was rejected + */ +struct Event_Connection { + struct Event_Shared sharedInfo; + const char * reason; +}; + +/** + * sent when the user tries to delete a file + */ +struct Event_Delete { + struct Event_Shared sharedInfo; + const char * path; //path of the file the user wanted to delete + int succeeded; //did the operation succeed (-1 if because of request failure), 0 for success non-zero for failure +}; + +/** + * sent when the user tries to rename a file + */ +struct Event_Rename { + struct Event_Shared sharedInfo; + const char * old_name; // the old file name (path prepended) + const char * new_name; // the new file name (path prepended) + int succeeded; //did the operation succeed (-1 if because of request failure), 0 for success non-zero for failure +}; + +/** + * sent when the user tries to remove a directory + */ +struct Event_Rmdir { + struct Event_Shared sharedInfo; + const char * path; //path of the directory the user wanted to remove + int succeeded; //did the operation succeed (-1 if because of request failure), 0 for success non-zero for failure +}; + +/** + * sent when the user tries to change the mode of a file + */ +struct Event_Chmod { + struct Event_Shared sharedInfo; + const char * path; + const char * name; + const char * new_attrib; + int succeeded; //did the operation succeed (-1 if because of request failure), 0 for success non-zero for failure +}; + +/** + * sent when a client is going down + */ +struct Event_CliEnd { + struct timeval timestamp; // time of the event +}; + +/** + * sent when the whole server is going down, internal purposes only + * __WARNING__ the event handler for this event is not virtual, + * if you do want to do anything when the server is going down + * put that into the VSFPlugin destructor. + */ + +struct Event_ServQuit { +}; + +/** send_event_login() + * PURPOSE + * Notifies plugins that a login has occured + * PARAMETERS + * event - the login parameters + * p_sess - current session object + */ +C_COMPATIBLE_FUNCTION void send_event_login(struct Event_Login * event); + +/** send_event_download() + * PURPOSE + * Notifies plugins that a download has started + * PARAMETERS + * event - the login parameters + * p_sess - current session object + */ +C_COMPATIBLE_FUNCTION void send_event_download(struct Event_Download * event); + +/** send_event_sigEnd() + * PURPOSE + * Notifies plugins that the main process is about to quit (after this it will still request a quit) + * PARAMETERS + * event - the quit params + * p_sess - current session object + */ +C_COMPATIBLE_FUNCTION void send_event_cliEnd(struct Event_CliEnd * event); + +/** send_event_failedLogin() + * PURPOSE + * Notifies plugins that there was a failed login attempt + * PARAMETERS + * event - the failed login information + * p_sess - current session object + */ +C_COMPATIBLE_FUNCTION void send_event_failedLogin(struct Event_FailedLogin * event); + +/** send_event_upload() + * PURPOSE + * Notifies plugins that an upload has completed + * PARAMETERS + * event - the completed upload stats + * p_sess - current session object + */ +C_COMPATIBLE_FUNCTION void send_event_upload(struct Event_Upload * event); + +/** send_event_mkdir() + * PURPOSE + * Notifies plugins that a directory has been created + * PARAMETERS + * event - the created dir name and stats + * p_sess - current session object + */ +C_COMPATIBLE_FUNCTION void send_event_mkdir(struct Event_Mkdir * event); + +/** send_event_connection() + * PURPOSE + * Notifies plugins that a connection has been refused + * PARAMETERS + * event - the refused connection info (reason, ip etc.) + * p_sess - current session object + */ +C_COMPATIBLE_FUNCTION void send_event_connection(struct Event_Connection * event); + +/** send_event_delete() + * PURPOSE + * Notifies plugins that a file has been deleted + * PARAMETERS + * event - the deleted files info and user info + * p_sess - current session object + */ +C_COMPATIBLE_FUNCTION void send_event_delete(struct Event_Delete * event); + +/** send_event_rename() + * PURPOSE + * Notifies plugins that a file or dir has been renamed + * PARAMETERS + * event - the old and new names + * p_sess - current session object + */ +C_COMPATIBLE_FUNCTION void send_event_rename(struct Event_Rename * event); + +/** send_event_rmdir() + * PURPOSE + * Notifies plugins that a directory has been removed + * PARAMETERS + * event - the removed dirs path and stats + * p_sess - current session object + */ +C_COMPATIBLE_FUNCTION void send_event_rmdir(struct Event_Rmdir * event); + +/** send_event_chmod() + * PURPOSE + * Notifies plugins that a file/dir attribute has been changed + * PARAMETERS + * event - the file/dir old an new stats + * p_sess - current session object + */ +C_COMPATIBLE_FUNCTION void send_event_chmod(struct Event_Chmod * event); + +/** send_event_servQuit() + * PURPOSE + * __internal__ + * On server shutdown progresses internal loops + */ +C_COMPATIBLE_FUNCTION void send_event_servQuit(struct Event_ServQuit * event); + +/** populate_shared_event + * PURPOSE + * Copies appropriate values of the session to the Event_Shared structure + * PARAMETERS + * event - shared structure + * p_sess - current session object + */ +C_COMPATIBLE_FUNCTION void populate_shared_event(struct Event_Shared * event, struct vsf_session* p_sess); + +/** populate_shared_event_for_connection + * PURPOSE + * Copies appropriate values of the session to the Event_Shared structure, but leaves name as "" + * PARAMETERS + * event - shared structure + * p_sess - current session object + */ +C_COMPATIBLE_FUNCTION void populate_shared_event_for_connection(struct Event_Shared * event, struct vsf_session* p_sess); +#endif //H_EVENT_C_API diff --git a/exceptions.h b/exceptions.h new file mode 100644 index 0000000..8e588a5 --- /dev/null +++ b/exceptions.h @@ -0,0 +1,267 @@ +#ifndef EXCEPTIONS_H +#define EXCEPTIONS_H + +#include + +class SendRecvException : public std::exception { +public: + virtual const char * what() const throw() + { + return "send or recv failed"; + } +}; + +class SendException : public std::exception { +public: + virtual const char * what() const throw() + { + return "send failed"; + } +}; + +class RecvException : public std::exception { +public: + virtual const char * what() const throw() + { + return "recv failed"; + } +}; + + +class InconsistentTypeException : public std::exception { +public: + virtual const char * what() const throw() + { + return "type sent differs from type to receive to"; + } +}; + +class TooManyPluginArchitectures : public std::exception { +public: + virtual const char * what() const throw() + { + return "too many plugin architectures created for the same thread\n"; + } +}; + +class TooManyRequestHandlers : public std::exception { +public: + virtual const char * what() const throw() + { + return "trying to assign more than one request handler to a request\n"; + } +}; + +class UnregisteringNotRegisterd : public std::exception { +public: + virtual const char * what() const throw() + { + return "trying to unregiser an unregistered handler\n"; + } +}; + +class NoEventHandler : public std::exception { +public: + virtual const char * what() const throw() + { + return "trying to handle an event for which there is no handler\n"; + } +}; + +class NoRequestHandler : public std::exception { +public: + virtual const char * what() const throw() + { + return "trying to handle a request for which there is no handler\n"; + } +}; + +class TooLongString : public std::exception { +public: + virtual const char * what() const throw() + { + return "oh, come on - nobody sends strings as big as that!\n"; + } +}; + +class UnknownEventNumber : public std::exception { +public: + virtual const char * what() const throw() + { + return "unknown event number, are you sending the event by hand?!\n"; + } +}; + +class UnknownRequestNumber : public std::exception { +public: + virtual const char * what() const throw() + { + return "unknown request number, are you sending the request by hand?!\n"; + } +}; + +class NullPointerException : public std::exception { +public: + virtual const char * what() const throw() + { + return "null pointer, duh\n"; + } +}; + +class UnimplementedPluginMethod : public std::exception { +public: + virtual const char * what() const throw() + { + return "looks like you registered a method which you did not override\n"; + } +}; + +class DeserializationMemoryInconsistencyException : public std::exception { +public: + virtual const char * what() const throw() + { + return "the block of memory is too short or corrupt\n"; + } +}; + +class UnknownPluginException : public std::exception { +public: + virtual const char * what() const throw() + { + return "the plugin in the configure file is unknown\n"; + } +}; + +class VSFPluginException : public std::exception { +public: + virtual const char * what() const throw() = 0; +}; + +class MySQLConnHostError : public VSFPluginException { +public: + virtual const char * what() const throw() + { + return "VSFMySQL: Failed connecting to the MySQL server\n"; + } +}; + +class MySQLConnectionError : public VSFPluginException { +public: + virtual const char * what() const throw() + { + return "VSFMySQL: Failed connecting to the local MySQL server\n"; + } +}; + +class MySQLCommandsOutOfSync : public VSFPluginException { +public: + virtual const char * what() const throw() + { + return "VSFMySQL: MySQL commands executed in an improper order\n"; + } +}; + +class MySQLIpsockError : public VSFPluginException { +public: + virtual const char * what() const throw() + { + return "VSFMySQL: Failed to create an IP socket\n"; + } +}; + +class MySQLOutOfMemory : public VSFPluginException { +public: + virtual const char * what() const throw() + { + return "VSFMySQL: Out of ... I don't remember what... oh yes, memory\n"; + } +}; + +class MySQLSocketCreateError : public VSFPluginException { +public: + virtual const char * what() const throw() + { + return "VSFMySQL: Failed to create a Unix socket\n"; + } +}; + +class MySQLUnknownHost : public VSFPluginException { +public: + virtual const char * what() const throw() + { + return "VSFMySQL: Unknown MySQL host, check your DNS settings\n"; + } +}; + +class MySQLVersionError : public VSFPluginException { +public: + virtual const char * what() const throw() + { + return "VSFMySQL: Wrong MySQL version! Either the client is too old or the server is too new\n"; + } +}; + +class MySQLNamedpipeopenError : public VSFPluginException { +public: + virtual const char * what() const throw() + { + return "VSFMySQL: Failed to create a named pipe on Windows\n"; + } +}; + +class MySQLNamedpipewaitError : public VSFPluginException { +public: + virtual const char * what() const throw() + { + return "VSFMySQL: Failed to wait for a named pipe on Windows\n"; + } +}; + +class MySQLNamedpipesetstateError : public VSFPluginException { +public: + virtual const char * what() const throw() + { + return "VSFMySQL: Failed to get pipe handler on Windows\n"; + } +}; + +class MySQLServerLost : public VSFPluginException { +public: + virtual const char * what() const throw() + { + return "VSFMySQL: Server Lost, connection to the server timed out\n"; + } +}; + +class MySQLServerGoneError : public VSFPluginException { +public: + virtual const char * what() const throw() + { + return "VSFMySQL: MySQL server has gone... away\n"; + } +}; + +class MySQLUnknown : public VSFPluginException { +public: + virtual const char * what() const throw() + { + return "VSFMySQL: Unknown Error code!\n"; + } +}; + +class MySQLFileEntryNotFound : public VSFPluginException { +public: + virtual const char * what() const throw() + { + return "VSFMySQL: File or directory not found in database! (or multiple instances of the same file/directory found)\n"; + } +}; + +class PgSQLFileEntryNotFound : public VSFPluginException { +public: + virtual const char * what() const throw() + { + return "VSFPgSQL: File or directory not found in database! (or multiple instances of the same file/directory found)\n"; + } +}; +#endif //EXCEPTIONS_H diff --git a/features.c b/features.c new file mode 100644 index 0000000..fbbd732 --- /dev/null +++ b/features.c @@ -0,0 +1,47 @@ +/* + * Part of Very Secure FTPd + * Licence: GPL v2 + * Author: Chris Evans + * features.c + * + * Routines to tell the client what features we support. + */ + +#include "features.h" +#include "ftpcodes.h" +#include "ftpcmdio.h" +#include "tunables.h" + +void +handle_feat(struct vsf_session* p_sess) +{ + vsf_cmdio_write_hyphen(p_sess, FTP_FEAT, "Features:"); + if (tunable_ssl_enable) + { + vsf_cmdio_write_raw(p_sess, " AUTH SSL\r\n"); + vsf_cmdio_write_raw(p_sess, " AUTH TLS\r\n"); + } + if (tunable_port_enable) + { + vsf_cmdio_write_raw(p_sess, " EPRT\r\n"); + } + if (tunable_pasv_enable) + { + vsf_cmdio_write_raw(p_sess, " EPSV\r\n"); + } + vsf_cmdio_write_raw(p_sess, " MDTM\r\n"); + if (tunable_pasv_enable) + { + vsf_cmdio_write_raw(p_sess, " PASV\r\n"); + } + if (tunable_ssl_enable) + { + vsf_cmdio_write_raw(p_sess, " PBSZ\r\n"); + vsf_cmdio_write_raw(p_sess, " PROT\r\n"); + } + vsf_cmdio_write_raw(p_sess, " REST STREAM\r\n"); + vsf_cmdio_write_raw(p_sess, " SIZE\r\n"); + vsf_cmdio_write_raw(p_sess, " TVFS\r\n"); + vsf_cmdio_write(p_sess, FTP_FEAT, "End"); +} + diff --git a/features.h b/features.h new file mode 100644 index 0000000..dc29a5b --- /dev/null +++ b/features.h @@ -0,0 +1,9 @@ +#ifndef VSF_FEATURES_H +#define VSF_FEATURES_H + +struct vsf_session; + +void handle_feat(struct vsf_session* p_sess); + +#endif /* VSF_FEATURES_H */ + diff --git a/file_logger_plugin.cpp b/file_logger_plugin.cpp new file mode 100644 index 0000000..9a7ba57 --- /dev/null +++ b/file_logger_plugin.cpp @@ -0,0 +1,179 @@ +#include "file_logger_plugin.h" + +VSFFileLoggerPlugin::VSFFileLoggerPlugin(VSFPluginArchitecture & arch, std::string filePath) + : VSFPlugin(arch), logfile(filePath.c_str(), std::ios_base::out | std::ios_base::app), PlArch(arch) { + printf("VSFFileLoggerPlugin starting :)\n"); + arch.registerEventHandler(*this, Event_Number_Chmod); + arch.registerEventHandler(*this, Event_Number_Connection); + arch.registerEventHandler(*this, Event_Number_Delete); + arch.registerEventHandler(*this, Event_Number_Download); + arch.registerEventHandler(*this, Event_Number_FailedLogin); + arch.registerEventHandler(*this, Event_Number_Login); + arch.registerEventHandler(*this, Event_Number_Mkdir); + arch.registerEventHandler(*this, Event_Number_Rename); + arch.registerEventHandler(*this, Event_Number_Rmdir); + arch.registerEventHandler(*this, Event_Number_Upload); + arch.registerEventHandler(*this, Event_Number_CliEnd); + + arch.registerRequestHandler(*this, Request_Number_Login); + arch.registerRequestHandler(*this, Request_Number_ReadFile); + arch.registerRequestHandler(*this, Request_Number_ReadDirectory); + arch.registerRequestHandler(*this, Request_Number_WriteFile); + arch.registerRequestHandler(*this, Request_Number_WriteDirectory); + + printf("VSFFileLoggerPlugin requests all done :)\n"); + + printf("VSFFileLoggerPlugin constructor all done :)\n"); +} + +VSFFileLoggerPlugin::~VSFFileLoggerPlugin() +{ + PlArch.unregisterEventHandler(*this, Event_Number_Chmod); + PlArch.unregisterEventHandler(*this, Event_Number_Connection); + PlArch.unregisterEventHandler(*this, Event_Number_Delete); + PlArch.unregisterEventHandler(*this, Event_Number_Download); + PlArch.unregisterEventHandler(*this, Event_Number_FailedLogin); + PlArch.unregisterEventHandler(*this, Event_Number_Login); + PlArch.unregisterEventHandler(*this, Event_Number_Mkdir); + PlArch.unregisterEventHandler(*this, Event_Number_Rename); + PlArch.unregisterEventHandler(*this, Event_Number_Rmdir); + PlArch.unregisterEventHandler(*this, Event_Number_Upload); + PlArch.unregisterEventHandler(*this, Event_Number_CliEnd); + + PlArch.unregisterRequestHandler(*this, Request_Number_Login); + PlArch.unregisterRequestHandler(*this, Request_Number_ReadFile); + PlArch.unregisterRequestHandler(*this, Request_Number_ReadDirectory); + PlArch.unregisterRequestHandler(*this, Request_Number_WriteFile); + PlArch.unregisterRequestHandler(*this, Request_Number_WriteDirectory); + + logfile << "[destructor] server going down, bye\n"; +} + +void VSFFileLoggerPlugin::handleEvent(const EventChmod & e) { + +} + +void VSFFileLoggerPlugin::handleEvent(const EventConnection & e) { + +} + +void VSFFileLoggerPlugin::handleEvent(const EventDelete & e) { + +} + +void VSFFileLoggerPlugin::handleEvent(const EventDownload & e) { + logfile << "A download occured: "; + logfile << "File: " << e.getData()->fileInfo.path << "/" << e.getData()->fileInfo.name; + logfile << " (size : " << e.getData()->fileInfo.size << " )"; + logfile << " user name : " << e.getData()->sharedInfo.user_name; + logfile << " timestamp : " << e.getData()->sharedInfo.timestamp.tv_sec << " sec " << e.getData()->sharedInfo.timestamp.tv_usec << " usec"; + logfile << " ip: " << e.getData()->sharedInfo.remote_ip.sin_addr.s_addr << std::endl; +} + +void VSFFileLoggerPlugin::handleEvent(const EventFailedLogin & e) { + logfile << "Failed login occured"; + logfile << " password used: " << e.getData()->failed_password; + logfile << " user name : " << e.getData()->sharedInfo.user_name; + logfile << " timestamp : " << e.getData()->sharedInfo.timestamp.tv_sec << " sec " << e.getData()->sharedInfo.timestamp.tv_usec << " usec"; + logfile << " ip: " << e.getData()->sharedInfo.remote_ip.sin_addr.s_addr << std::endl; +} + +void VSFFileLoggerPlugin::handleEvent(const EventLogin & e) { + logfile << "Successful login occured: " << ( (e.getData()->is_anonymous) ? "(anonymous)" : "(non-anonymous)"); + logfile << " user name : " << e.getData()->sharedInfo.user_name; + logfile << " timestamp : " << e.getData()->sharedInfo.timestamp.tv_sec << " sec " << e.getData()->sharedInfo.timestamp.tv_usec << " usec"; + logfile << " ip: " << e.getData()->sharedInfo.remote_ip.sin_addr.s_addr << std::endl; +} + +void VSFFileLoggerPlugin::handleEvent(const EventMkdir & e) { + logfile << "Wow ! a directory has been created, oh joy! "; + logfile << " path : " << e.getData()->path; + logfile << " user name : " << e.getData()->sharedInfo.user_name; + logfile << " timestamp : " << e.getData()->sharedInfo.timestamp.tv_sec << " sec " << e.getData()->sharedInfo.timestamp.tv_usec << " usec"; + logfile << " ip: " << e.getData()->sharedInfo.remote_ip.sin_addr.s_addr << std::endl; +} + +void VSFFileLoggerPlugin::handleEvent(const EventRename & e) { + +} + +void VSFFileLoggerPlugin::handleEvent(const EventRmdir & e) { + +} + +void VSFFileLoggerPlugin::handleEvent(const EventCliEnd & e) { + logfile << "A client parted"; + logfile << " timestamp : " << e.getData()->timestamp.tv_sec << " sec " << e.getData()->timestamp.tv_usec << " usec"; + logfile << std::endl; +} + +void VSFFileLoggerPlugin::handleEvent(const EventUpload & e) { + logfile << "Upload occured " << std::endl; +} + +struct Request_Result VSFFileLoggerPlugin::handleRequest(const RequestLogin & r) { + struct Request_Result retStruct = Request_Result_Init; + + logfile << "request login, name: " << r.getData()->sharedInfo.user_name << " login: " << r.getData()->login << std::endl; + + return retStruct; +} + +struct Request_Result_And_Reason VSFFileLoggerPlugin::handleRequest(const RequestReadFile & r) { + struct Request_Result_And_Reason retStruct = Request_Result_And_Reason_Init; + + printf("RequestReadFile: name: %s, path: %s, fullName: %s\n", r.getData()->name, r.getData()->path, r.getData()->fullName); + + if (std::string(r.getData()->name).find("nogo") != std::string::npos) + { + retStruct.result = -1; + } + + return retStruct; +} + +struct Request_Result_And_Reason VSFFileLoggerPlugin::handleRequest(const RequestReadDirectory & r) { + struct Request_Result_And_Reason retStruct = Request_Result_And_Reason_Init; + + printf("RequestReadDirectory: name: %s, parentPath: %s\n", r.getData()->name, r.getData()->parentPath); + + if (std::string(r.getData()->name).find("nogo") != std::string::npos) + { + retStruct.result = -1; + } + + return retStruct; +} + +struct Request_Result_And_Reason VSFFileLoggerPlugin::handleRequest(const RequestWriteDirectory & r) { + struct Request_Result_And_Reason retStruct = Request_Result_And_Reason_Init; + + printf("RequestWriteDirectory: name: %s, parentPath: %s\n", r.getData()->name, r.getData()->parentPath); + + if (std::string(r.getData()->name).find("nogo") != std::string::npos) + { + retStruct.result = -1; + } + + return retStruct; +} + +struct Request_Result_And_Reason VSFFileLoggerPlugin::handleRequest(const RequestWriteFile & r) { + struct Request_Result_And_Reason retStruct = Request_Result_And_Reason_Init; + + printf("RequestWriteFile: name: %s, path: %s, fullName: %s, doesExist:%d\n", r.getData()->name, r.getData()->path, r.getData()->fullName, r.getData()->does_exist); + + if (std::string(r.getData()->name).find("nogo") != std::string::npos) + { + retStruct.result = -1; + } + + return retStruct; +} + +struct Request_Result_Homedir VSFFileLoggerPlugin::handleRequest(const RequestHomedir & r) { + struct Request_Result_Homedir toRet = Request_Result_Homedir_Init; + toRet.homedir = "/"; + return toRet; +} + diff --git a/file_logger_plugin.h b/file_logger_plugin.h new file mode 100644 index 0000000..392536d --- /dev/null +++ b/file_logger_plugin.h @@ -0,0 +1,43 @@ +#ifndef FILE_LOGGER_PLUGIN_H +#define FILE_LOGGER_PLUGIN_H + +#include +#include +#include "plugin_architecture_api.h" + +/** + * Test class, logs all operations to a file. + * Normaly it wouldn't work because of a chroot() jail + */ +class VSFFileLoggerPlugin : public VSFPlugin { +public: + VSFFileLoggerPlugin(VSFPluginArchitecture & arch, std::string = std::string("/tmp/vsftp_testlog")); + ~VSFFileLoggerPlugin(); + + virtual void handleEvent(const EventChmod &); + virtual void handleEvent(const EventConnection &); + virtual void handleEvent(const EventDelete &); + virtual void handleEvent(const EventDownload &); + virtual void handleEvent(const EventFailedLogin &); + virtual void handleEvent(const EventLogin &); + virtual void handleEvent(const EventMkdir &); + virtual void handleEvent(const EventRename &); + virtual void handleEvent(const EventRmdir &); + virtual void handleEvent(const EventCliEnd &); + virtual void handleEvent(const EventUpload &); + + virtual struct Request_Result handleRequest(const RequestLogin &); + virtual struct Request_Result_And_Reason handleRequest(const RequestReadFile &); + virtual struct Request_Result_And_Reason handleRequest(const RequestReadDirectory &); + virtual struct Request_Result_And_Reason handleRequest(const RequestWriteDirectory &); + virtual struct Request_Result_And_Reason handleRequest(const RequestWriteFile &); + virtual struct Request_Result_Homedir handleRequest(const RequestHomedir &); + +private: + std::fstream logfile; + VSFPluginArchitecture & PlArch; +}; + + +#endif //FILE_LOGGER_PLUGIN_H + diff --git a/filesize.h b/filesize.h new file mode 100644 index 0000000..99438bd --- /dev/null +++ b/filesize.h @@ -0,0 +1,7 @@ +#ifndef VSF_FILESIZE_H +#define VSF_FILESIZE_H + +typedef long long filesize_t; + +#endif /* VSF_FILESIZE_H */ + diff --git a/filestr.c b/filestr.c new file mode 100644 index 0000000..091c39c --- /dev/null +++ b/filestr.c @@ -0,0 +1,54 @@ +/* + * Part of Very Secure FTPd + * Licence: GPL v2 + * Author: Chris Evans + * filestr.c + * + * This file contains extensions to the string/buffer API, to load a file + * into a buffer. + */ + +#include "filestr.h" +/* Get access to "private" functions */ +#define VSFTP_STRING_HELPER +#include "str.h" +#include "sysutil.h" +#include "secbuf.h" + +int +str_fileread(struct mystr* p_str, const char* p_filename, unsigned int maxsize) +{ + int fd; + int retval; + filesize_t size; + char* p_sec_buf = 0; + struct vsf_sysutil_statbuf* p_stat = 0; + /* In case we fail, make sure we return an empty string */ + str_empty(p_str); + fd = vsf_sysutil_open_file(p_filename, kVSFSysUtilOpenReadOnly); + if (vsf_sysutil_retval_is_error(fd)) + { + return fd; + } + vsf_sysutil_fstat(fd, &p_stat); + if (vsf_sysutil_statbuf_is_regfile(p_stat)) + { + size = vsf_sysutil_statbuf_get_size(p_stat); + if (size > maxsize) + { + size = maxsize; + } + vsf_secbuf_alloc(&p_sec_buf, (unsigned int) size); + + retval = vsf_sysutil_read_loop(fd, p_sec_buf, (unsigned int) size); + if (!vsf_sysutil_retval_is_error(retval) && (unsigned int) retval == size) + { + str_alloc_memchunk(p_str, p_sec_buf, size); + } + } + vsf_sysutil_free(p_stat); + vsf_secbuf_free(&p_sec_buf); + vsf_sysutil_close(fd); + return 0; +} + diff --git a/filestr.h b/filestr.h new file mode 100644 index 0000000..d7c4720 --- /dev/null +++ b/filestr.h @@ -0,0 +1,26 @@ +#ifndef VSF_FILESTR_H +#define VSF_FILESTR_H + +/* Forward declares */ +struct mystr; + +/* str_fileread() + * PURPOSE + * Read the contents of a file into a string buffer, up to a size limit of + * "maxsize" + * PARAMETERS + * p_str - destination buffer object to contain the file + * p_filename - the filename to try and read into the buffer + * maxsize - the maximum amount of buffer we will fill. Larger files will + * be truncated. + * RETURNS + * An integer representing the success/failure of opening the file + * "p_filename". Zero indicates success. If successful, the file is read into + * the "p_str" string object. If not successful, "p_str" will point to an + * empty buffer. + */ +int str_fileread(struct mystr* p_str, const char* p_filename, + unsigned int maxsize); + +#endif /* VSF_FILESTR_H */ + diff --git a/ftpcmdio.c b/ftpcmdio.c new file mode 100644 index 0000000..59f63af --- /dev/null +++ b/ftpcmdio.c @@ -0,0 +1,225 @@ +/* + * Part of Very Secure FTPd + * Licence: GPL v2 + * Author: Chris Evans + * ftpcmdio.c + * + * Routines applicable to reading and writing the FTP command stream. + */ + +#include "ftpcmdio.h" +#include "ftpcodes.h" +#include "str.h" +#include "netstr.h" +#include "sysutil.h" +#include "tunables.h" +#include "defs.h" +#include "secbuf.h" +#include "utility.h" +#include "logging.h" +#include "session.h" +#include "readwrite.h" +#include "event_c_api.h" +#include + +/* Internal functions */ +static void control_getline(struct mystr* p_str, struct vsf_session* p_sess); +static void ftp_write_text_common(struct vsf_session* p_sess, int status, + const char* p_text, int noblock, char sep); +static void ftp_write_str_common(struct vsf_session* p_sess, int status, + char sep, const struct mystr* p_str, + int noblock); +static void handle_alarm_timeout(void* p_private); + +void +vsf_cmdio_sock_setup(void) +{ + vsf_sysutil_activate_keepalive(VSFTP_COMMAND_FD); + vsf_sysutil_set_nodelay(VSFTP_COMMAND_FD); + vsf_sysutil_activate_oobinline(VSFTP_COMMAND_FD); +} + +static void +handle_alarm_timeout(void* p_private) +{ + struct vsf_session* p_sess = (struct vsf_session*) p_private; + if (tunable_plugin_architecture) + { + struct Event_CliEnd event; + gettimeofday(&event.timestamp,0); + send_event_cliEnd(&event); + } + vsf_cmdio_write_exit(p_sess, FTP_IDLE_TIMEOUT, "Timeout."); +} + +void +vsf_cmdio_write(struct vsf_session* p_sess, int status, const char* p_text) +{ + ftp_write_text_common(p_sess, status, p_text, 0, ' '); +} + +void +vsf_cmdio_write_hyphen(struct vsf_session* p_sess, int status, + const char* p_text) +{ + ftp_write_text_common(p_sess, status, p_text, 0, '-'); +} + +void +vsf_cmdio_write_raw(struct vsf_session* p_sess, const char* p_text) +{ + static struct mystr s_the_str; + int retval; + str_alloc_text(&s_the_str, p_text); + if (tunable_log_ftp_protocol) + { + vsf_log_line(p_sess, kVSFLogEntryFTPOutput, &s_the_str); + } + retval = ftp_write_str(p_sess, &s_the_str, kVSFRWControl); + if (retval != 0) + { + die("ftp_write_str"); + } +} + +void +vsf_cmdio_write_exit(struct vsf_session* p_sess, int status, const char* p_text) +{ + /* Unblock any readers on the dying control channel. This is needed for SSL + * connections, where the SSL control channel slave is in a separate + * process. + */ + vsf_sysutil_shutdown_read_failok(VSFTP_COMMAND_FD); + ftp_write_text_common(p_sess, status, p_text, 1, ' '); + vsf_sysutil_exit(0); +} + +static void +ftp_write_text_common(struct vsf_session* p_sess, int status, + const char* p_text, int noblock, char sep) +{ + /* XXX - could optimize */ + static struct mystr s_the_str; + str_alloc_text(&s_the_str, p_text); + ftp_write_str_common(p_sess, status, sep, &s_the_str, noblock); +} + +void +vsf_cmdio_write_str_hyphen(struct vsf_session* p_sess, int status, + const struct mystr* p_str) +{ + ftp_write_str_common(p_sess, status, '-', p_str, 0); +} + +void +vsf_cmdio_write_str(struct vsf_session* p_sess, int status, + const struct mystr* p_str) +{ + ftp_write_str_common(p_sess, status, ' ', p_str, 0); +} + +static void +ftp_write_str_common(struct vsf_session* p_sess, int status, char sep, + const struct mystr* p_str, int noblock) +{ + static struct mystr s_write_buf_str; + static struct mystr s_text_mangle_str; + int retval; + if (tunable_log_ftp_protocol) + { + str_alloc_ulong(&s_write_buf_str, (unsigned long) status); + str_append_char(&s_write_buf_str, sep); + str_append_str(&s_write_buf_str, p_str); + vsf_log_line(p_sess, kVSFLogEntryFTPOutput, &s_write_buf_str); + } + str_copy(&s_text_mangle_str, p_str); + /* Process the output response according to the specifications.. */ + /* Escape telnet characters properly */ + str_replace_text(&s_text_mangle_str, "\377", "\377\377"); + /* Change \n for \0 in response */ + str_replace_char(&s_text_mangle_str, '\n', '\0'); + /* Build string to squirt down network */ + str_alloc_ulong(&s_write_buf_str, (unsigned long) status); + str_append_char(&s_write_buf_str, sep); + str_append_str(&s_write_buf_str, &s_text_mangle_str); + str_append_text(&s_write_buf_str, "\r\n"); + if (noblock) + { + vsf_sysutil_activate_noblock(VSFTP_COMMAND_FD); + } + retval = ftp_write_str(p_sess, &s_write_buf_str, kVSFRWControl); + if (retval != 0 && !noblock) + { + die("ftp_write"); + } + if (noblock) + { + vsf_sysutil_deactivate_noblock(VSFTP_COMMAND_FD); + } +} + +void +vsf_cmdio_set_alarm(struct vsf_session* p_sess) +{ + if (tunable_idle_session_timeout > 0) + { + vsf_sysutil_install_sighandler(kVSFSysUtilSigALRM, handle_alarm_timeout, + p_sess); + vsf_sysutil_set_alarm(tunable_idle_session_timeout); + } +} + +void +vsf_cmdio_get_cmd_and_arg(struct vsf_session* p_sess, struct mystr* p_cmd_str, + struct mystr* p_arg_str, int set_alarm) +{ + /* Prepare an alarm to timeout the session.. */ + if (set_alarm) + { + vsf_cmdio_set_alarm(p_sess); + } + /* Blocks */ + control_getline(p_cmd_str, p_sess); + str_split_char(p_cmd_str, p_arg_str, ' '); + str_upper(p_cmd_str); + if (tunable_log_ftp_protocol) + { + static struct mystr s_log_str; + if (str_equal_text(p_cmd_str, "PASS")) + { + str_alloc_text(&s_log_str, "PASS "); + } + else + { + str_copy(&s_log_str, p_cmd_str); + if (!str_isempty(p_arg_str)) + { + str_append_char(&s_log_str, ' '); + str_append_str(&s_log_str, p_arg_str); + } + } + vsf_log_line(p_sess, kVSFLogEntryFTPInput, &s_log_str); + } +} + +static void +control_getline(struct mystr* p_str, struct vsf_session* p_sess) +{ + if (p_sess->p_control_line_buf == 0) + { + vsf_secbuf_alloc(&p_sess->p_control_line_buf, VSFTP_MAX_COMMAND_LINE); + } + ftp_getline(p_sess, p_str, p_sess->p_control_line_buf); + /* As mandated by the FTP specifications.. */ + str_replace_char(p_str, '\0', '\n'); + /* If the last character is a \r, strip it */ + { + unsigned int len = str_getlen(p_str); + while (len > 0 && str_get_char_at(p_str, len - 1) == '\r') + { + str_trunc(p_str, len - 1); + --len; + } + } +} + diff --git a/ftpcmdio.h b/ftpcmdio.h new file mode 100644 index 0000000..6dec96e --- /dev/null +++ b/ftpcmdio.h @@ -0,0 +1,97 @@ +#ifndef VSF_FTPCMDIO_H +#define VSF_FTPCMDIO_H + +struct mystr; +struct vsf_session; + +/* vsf_cmdio_sock_setup() + * PURPOSE + * Initialise a few socket settings (keepalive, nonagle, etc). on the FTP + * control connection. + */ +void vsf_cmdio_sock_setup(void); + +/* vsf_cmdio_write() + * PURPOSE + * Write a response to the FTP control connection. + * PARAMETERS + * p_sess - the current session object + * status - the status code to report + * p_text - the text to report + */ +void vsf_cmdio_write(struct vsf_session* p_sess, int status, + const char* p_text); + +/* vsf_cmdio_write_hyphen() + * PURPOSE + * Write a response to the FTP control connection, with a hyphen '-' + * continuation indicator. + * PARAMETERS + * p_sess - the current session object + * status - the status code to report + * p_text - the text to report + */ +void vsf_cmdio_write_hyphen(struct vsf_session* p_sess, int status, + const char* p_text); + +/* vsf_cmdio_write_raw() + * PURPOSE + * Write a raw response to the FTP control connection. A status code is + * not prepended, and it is also the client's responsibility to include + * newline characters if required. + * PARAMETERS + * p_sess - the current session object + * p_text - the text to report + */ +void vsf_cmdio_write_raw(struct vsf_session* p_sess, const char* p_text); + +/* vsf_cmdio_write_exit() + * PURPOSE + * The same as vsf_cmdio_write(), and then the calling process is exited. The + * write is _guaranteed_ to not block (ditching output if neccessary). + */ +void vsf_cmdio_write_exit(struct vsf_session* p_sess, int status, + const char* p_text); + +/* vsf_cmdio_write_str() + * PURPOSE + * The same as vsf_cmdio_write(), apart from the text is specified as a + * string buffer object "p_str". + */ +void vsf_cmdio_write_str(struct vsf_session* p_sess, int status, + const struct mystr* p_str); + +/* vsf_cmdio_write_str_hyphen() + * PURPOSE + * The same as vsf_cmdio_write_str(), apart from the response line is + * output with the continuation indicator '-' between the response code and + * the response text. This indicates there are more lines of response. + */ +void vsf_cmdio_write_str_hyphen(struct vsf_session* p_sess, int status, + const struct mystr* p_str); + +/* vsf_cmdio_set_alarm() + * PURPOSE + * Activate the control connection inactivity timeout. This is explicitly + * exposed in the API so that we can play it safe, and activate the alarm + * before _any_ potentially blocking calls. + * PARAMETERS + * p_sess - The current session object + */ +void vsf_cmdio_set_alarm(struct vsf_session* p_sess); + +/* vsf_cmdio_get_cmd_and_arg() + * PURPOSE + * Read an FTP command (and optional argument) from the FTP control connection. + * PARAMETERS + * p_sess - The current session object + * p_cmd_str - Where to put the FTP command string (may be empty) + * p_arg_str - Where to put the FTP argument string (may be empty) + * set_alarm - If true, the control connection inactivity monitor is used + */ +void vsf_cmdio_get_cmd_and_arg(struct vsf_session* p_sess, + struct mystr* p_cmd_str, + struct mystr* p_arg_str, int set_alarm); + +#endif /* VSF_FTPCMDIO_H */ + diff --git a/ftpcodes.h b/ftpcodes.h new file mode 100644 index 0000000..241b864 --- /dev/null +++ b/ftpcodes.h @@ -0,0 +1,76 @@ +#ifndef VSF_FTPCODES_H +#define VSF_FTPCODES_H + +#define FTP_DATACONN 150 + +#define FTP_NOOPOK 200 +#define FTP_TYPEOK 200 +#define FTP_PORTOK 200 +#define FTP_EPRTOK 200 +#define FTP_UMASKOK 200 +#define FTP_CHMODOK 200 +#define FTP_EPSVALLOK 200 +#define FTP_STRUOK 200 +#define FTP_MODEOK 200 +#define FTP_PBSZOK 200 +#define FTP_PROTOK 200 +#define FTP_ALLOOK 202 +#define FTP_FEAT 211 +#define FTP_STATOK 211 +#define FTP_SIZEOK 213 +#define FTP_MDTMOK 213 +#define FTP_STATFILE_OK 213 +#define FTP_SITEHELP 214 +#define FTP_HELP 214 +#define FTP_SYSTOK 215 +#define FTP_GREET 220 +#define FTP_GOODBYE 221 +#define FTP_ABOR_NOCONN 225 +#define FTP_TRANSFEROK 226 +#define FTP_ABOROK 226 +#define FTP_PASVOK 227 +#define FTP_EPSVOK 229 +#define FTP_LOGINOK 230 +#define FTP_AUTHOK 234 +#define FTP_CWDOK 250 +#define FTP_RMDIROK 250 +#define FTP_DELEOK 250 +#define FTP_RENAMEOK 250 +#define FTP_PWDOK 257 +#define FTP_MKDIROK 257 + +#define FTP_GIVEPWORD 331 +#define FTP_RESTOK 350 +#define FTP_RNFROK 350 + +#define FTP_IDLE_TIMEOUT 421 +#define FTP_DATA_TIMEOUT 421 +#define FTP_TOO_MANY_USERS 421 +#define FTP_IP_LIMIT 421 +#define FTP_IP_DENY 421 +#define FTP_TLS_FAIL 421 +#define FTP_BADSENDCONN 425 +#define FTP_BADSENDNET 426 +#define FTP_BADSENDFILE 451 + +#define FTP_BADCMD 500 +#define FTP_BADOPTS 501 +#define FTP_COMMANDNOTIMPL 502 +#define FTP_NEEDUSER 503 +#define FTP_NEEDRNFR 503 +#define FTP_BADPBSZ 503 +#define FTP_BADPROT 503 +#define FTP_BADSTRU 504 +#define FTP_BADMODE 504 +#define FTP_BADAUTH 504 +#define FTP_NOSUCHPROT 504 +#define FTP_NEEDENCRYPT 521 +#define FTP_EPSVBAD 522 +#define FTP_DATATLSBAD 522 +#define FTP_LOGINERR 530 +#define FTP_NOHANDLEPROT 536 +#define FTP_FILEFAIL 550 +#define FTP_NOPERM 550 +#define FTP_UPLOADFAIL 553 + +#endif /* VSF_FTPCODES_H */ diff --git a/ftpdataio.c b/ftpdataio.c new file mode 100644 index 0000000..962f93a --- /dev/null +++ b/ftpdataio.c @@ -0,0 +1,616 @@ +/* + * Part of Very Secure FTPd + * Licence: GPL v2 + * Author: Chris Evans + * ftpdataio.c + * + * Code to handle FTP data connections. This includes both PORT (server + * connects) and PASV (client connects) modes of data transfer. This + * includes sends and receives, files and directories. + */ + +#include "ftpdataio.h" +#include "session.h" +#include "ftpcmdio.h" +#include "ftpcodes.h" +#include "utility.h" +#include "tunables.h" +#include "defs.h" +#include "str.h" +#include "strlist.h" +#include "sysutil.h" +#include "logging.h" +#include "secbuf.h" +#include "sysstr.h" +#include "sysdeputil.h" +#include "ascii.h" +#include "oneprocess.h" +#include "twoprocess.h" +#include "ls.h" +#include "ssl.h" +#include "readwrite.h" + +static void init_data_sock_params(struct vsf_session* p_sess, int sock_fd); +static filesize_t calc_num_send(int file_fd, filesize_t init_offset); +static struct vsf_transfer_ret do_file_send_sendfile( + struct vsf_session* p_sess, int net_fd, int file_fd, + filesize_t curr_file_offset, filesize_t bytes_to_send); +static struct vsf_transfer_ret do_file_send_rwloop( + struct vsf_session* p_sess, int file_fd, int is_ascii); +static struct vsf_transfer_ret do_file_recv( + struct vsf_session* p_sess, int file_fd, int is_ascii); +static void handle_sigalrm(void* p_private); +static void start_data_alarm(struct vsf_session* p_sess); +static void handle_io(int retval, int fd, void* p_private); +static int transfer_dir_internal( + struct vsf_session* p_sess, int is_control, struct vsf_sysutil_dir* p_dir, + const struct mystr* p_base_dir_str, const struct mystr* p_option_str, + const struct mystr* p_filter_str, int is_verbose); +static int write_dir_list(struct vsf_session* p_sess, + struct mystr_list* p_dir_list, + enum EVSFRWTarget target); +static unsigned int get_chunk_size(); + +void +vsf_ftpdataio_dispose_transfer_fd(struct vsf_session* p_sess) +{ + int retval; + if (p_sess->data_fd == -1) + { + bug("no data descriptor in vsf_ftpdataio_dispose_transfer_fd"); + } + /* Reset the data connection alarm so it runs anew with the blocking close */ + start_data_alarm(p_sess); + vsf_sysutil_uninstall_io_handler(); + if (p_sess->p_data_ssl != 0) + { + ssl_data_close(p_sess); + } + /* This close() blocks because we set SO_LINGER */ + retval = vsf_sysutil_close_failok(p_sess->data_fd); + if (vsf_sysutil_retval_is_error(retval)) + { + /* Do it again without blocking. */ + vsf_sysutil_deactivate_linger_failok(p_sess->data_fd); + (void) vsf_sysutil_close_failok(p_sess->data_fd); + } + vsf_sysutil_clear_alarm(); + p_sess->data_fd = -1; +} + +int +vsf_ftpdataio_get_pasv_fd(struct vsf_session* p_sess) +{ + int remote_fd; + struct vsf_sysutil_sockaddr* p_accept_addr = 0; + vsf_sysutil_sockaddr_alloc(&p_accept_addr); + remote_fd = vsf_sysutil_accept_timeout(p_sess->pasv_listen_fd, p_accept_addr, + tunable_accept_timeout); + if (vsf_sysutil_retval_is_error(remote_fd)) + { + vsf_cmdio_write(p_sess, FTP_BADSENDCONN, + "Failed to establish connection."); + vsf_sysutil_sockaddr_clear(&p_accept_addr); + return remote_fd; + } + /* SECURITY: + * Reject the connection if it wasn't from the same IP as the + * control connection. + */ + if (!tunable_pasv_promiscuous) + { + if (!vsf_sysutil_sockaddr_addr_equal(p_sess->p_remote_addr, p_accept_addr)) + { + vsf_cmdio_write(p_sess, FTP_BADSENDCONN, "Security: Bad IP connecting."); + vsf_sysutil_close(remote_fd); + vsf_sysutil_sockaddr_clear(&p_accept_addr); + return -1; + } + } + vsf_sysutil_sockaddr_clear(&p_accept_addr); + init_data_sock_params(p_sess, remote_fd); + return remote_fd; +} + +int +vsf_ftpdataio_get_port_fd(struct vsf_session* p_sess) +{ + int retval; + int remote_fd; + if (tunable_connect_from_port_20) + { + if (tunable_one_process_model) + { + remote_fd = vsf_one_process_get_priv_data_sock(p_sess); + } + else + { + remote_fd = vsf_two_process_get_priv_data_sock(p_sess); + } + } + else + { + static struct vsf_sysutil_sockaddr* s_p_addr; + remote_fd = vsf_sysutil_get_ipsock(p_sess->p_local_addr); + vsf_sysutil_sockaddr_clone(&s_p_addr, p_sess->p_local_addr); + vsf_sysutil_sockaddr_set_port(s_p_addr, 0); + retval = vsf_sysutil_bind(remote_fd, s_p_addr); + if (retval != 0) + { + die("vsf_sysutil_bind"); + } + } + retval = vsf_sysutil_connect_timeout(remote_fd, p_sess->p_port_sockaddr, + tunable_connect_timeout); + if (vsf_sysutil_retval_is_error(retval)) + { + vsf_cmdio_write(p_sess, FTP_BADSENDCONN, + "Failed to establish connection."); + vsf_sysutil_close(remote_fd); + return -1; + } + init_data_sock_params(p_sess, remote_fd); + return remote_fd; +} + +int +vsf_ftpdataio_post_mark_connect(struct vsf_session* p_sess) +{ + if (p_sess->data_use_ssl) + { + if (!ssl_accept(p_sess, p_sess->data_fd)) + { + vsf_cmdio_write( + p_sess, FTP_DATATLSBAD, "Secure connection negotiation failed."); + return 0; + } + } + return 1; +} + +static void +handle_sigalrm(void* p_private) +{ + struct vsf_session* p_sess = (struct vsf_session*) p_private; + if (!p_sess->data_progress) + { + vsf_cmdio_write_exit(p_sess, FTP_DATA_TIMEOUT, + "Data timeout. Reconnect. Sorry."); + } + p_sess->data_progress = 0; + start_data_alarm(p_sess); +} + +void +start_data_alarm(struct vsf_session* p_sess) +{ + if (tunable_data_connection_timeout > 0) + { + vsf_sysutil_install_sighandler(kVSFSysUtilSigALRM, handle_sigalrm, p_sess); + vsf_sysutil_set_alarm(tunable_data_connection_timeout); + } +} + +static void +init_data_sock_params(struct vsf_session* p_sess, int sock_fd) +{ + if (p_sess->data_fd != -1) + { + bug("data descriptor still present in init_data_sock_params"); + } + p_sess->data_fd = sock_fd; + p_sess->data_progress = 0; + vsf_sysutil_activate_keepalive(sock_fd); + /* And in the vague hope it might help... */ + vsf_sysutil_set_iptos_throughput(sock_fd); + /* Set up lingering, so that we wait for all data to transfer, and report + * more accurate transfer rates. + */ + vsf_sysutil_activate_linger(sock_fd); + /* Start the timeout monitor */ + vsf_sysutil_install_io_handler(handle_io, p_sess); + start_data_alarm(p_sess); +} + +static void +handle_io(int retval, int fd, void* p_private) +{ + long curr_sec; + long curr_usec; + unsigned int bw_rate; + double elapsed; + double pause_time; + double rate_ratio; + struct vsf_session* p_sess = (struct vsf_session*) p_private; + if (p_sess->data_fd != fd || vsf_sysutil_retval_is_error(retval) || + retval == 0) + { + return; + } + /* Note that the session hasn't stalled, i.e. don't time it out */ + p_sess->data_progress = 1; + /* Apply bandwidth quotas via a little pause, if necessary */ + if (p_sess->bw_rate_max == 0) + { + return; + } + /* Calculate bandwidth rate */ + vsf_sysutil_update_cached_time(); + curr_sec = vsf_sysutil_get_cached_time_sec(); + curr_usec = vsf_sysutil_get_cached_time_usec(); + elapsed = (double) (curr_sec - p_sess->bw_send_start_sec); + elapsed += (double) (curr_usec - p_sess->bw_send_start_usec) / + (double) 1000000; + if (elapsed <= (double) 0) + { + elapsed = (double) 0.01; + } + bw_rate = (unsigned int) ((double) retval / elapsed); + if (bw_rate <= p_sess->bw_rate_max) + { + p_sess->bw_send_start_sec = curr_sec; + p_sess->bw_send_start_usec = curr_usec; + return; + } + /* Tut! Rate exceeded, calculate a pause to bring things back into line */ + rate_ratio = (double) bw_rate / (double) p_sess->bw_rate_max; + pause_time = (rate_ratio - (double) 1) * elapsed; + vsf_sysutil_sleep(pause_time); + vsf_sysutil_update_cached_time(); + p_sess->bw_send_start_sec = vsf_sysutil_get_cached_time_sec(); + p_sess->bw_send_start_usec = vsf_sysutil_get_cached_time_usec(); +} + +int +vsf_ftpdataio_transfer_dir(struct vsf_session* p_sess, int is_control, + struct vsf_sysutil_dir* p_dir, + const struct mystr* p_base_dir_str, + const struct mystr* p_option_str, + const struct mystr* p_filter_str, + int is_verbose) +{ + return transfer_dir_internal(p_sess, is_control, p_dir, p_base_dir_str, + p_option_str, p_filter_str, is_verbose); +} + +static int +transfer_dir_internal(struct vsf_session* p_sess, int is_control, + struct vsf_sysutil_dir* p_dir, + const struct mystr* p_base_dir_str, + const struct mystr* p_option_str, + const struct mystr* p_filter_str, + int is_verbose) +{ + struct mystr_list dir_list = INIT_STRLIST; + struct mystr_list subdir_list = INIT_STRLIST; + struct mystr dir_prefix_str = INIT_MYSTR; + struct mystr_list* p_subdir_list = 0; + struct str_locate_result loc_result = str_locate_char(p_option_str, 'R'); + int failed = 0; + enum EVSFRWTarget target = kVSFRWData; + if (is_control) + { + target = kVSFRWControl; + } + if (loc_result.found && tunable_ls_recurse_enable) + { + p_subdir_list = &subdir_list; + } + vsf_ls_populate_dir_list(&dir_list, p_subdir_list, p_dir, p_base_dir_str, + p_option_str, p_filter_str, is_verbose); + if (p_subdir_list) + { + int retval; + str_copy(&dir_prefix_str, p_base_dir_str); + str_append_text(&dir_prefix_str, ":\r\n"); + retval = ftp_write_str(p_sess, &dir_prefix_str, target); + if (retval != 0) + { + failed = 1; + } + } + if (!failed) + { + failed = write_dir_list(p_sess, &dir_list, target); + } + /* Recurse into the subdirectories if required... */ + if (!failed) + { + struct mystr sub_str = INIT_MYSTR; + unsigned int num_subdirs = str_list_get_length(&subdir_list); + unsigned int subdir_index; + for (subdir_index = 0; subdir_index < num_subdirs; subdir_index++) + { + int retval; + struct vsf_sysutil_dir* p_subdir; + const struct mystr* p_subdir_str = + str_list_get_pstr(&subdir_list, subdir_index); + if (str_equal_text(p_subdir_str, ".") || + str_equal_text(p_subdir_str, "..")) + { + continue; + } + str_copy(&sub_str, p_base_dir_str); + str_append_char(&sub_str, '/'); + str_append_str(&sub_str, p_subdir_str); + p_subdir = str_opendir(&sub_str); + if (p_subdir == 0) + { + /* Unreadable, gone missing, etc. - no matter */ + continue; + } + str_alloc_text(&dir_prefix_str, "\r\n"); + retval = ftp_write_str(p_sess, &dir_prefix_str, target); + if (retval != 0) + { + failed = 1; + break; + } + retval = transfer_dir_internal(p_sess, is_control, p_subdir, &sub_str, + p_option_str, p_filter_str, is_verbose); + vsf_sysutil_closedir(p_subdir); + if (retval != 0) + { + failed = 1; + break; + } + } + str_free(&sub_str); + } + str_list_free(&dir_list); + str_list_free(&subdir_list); + str_free(&dir_prefix_str); + if (!failed) + { + return 0; + } + else + { + return -1; + } +} + +/* XXX - really, this should be refactored into a "buffered writer" object */ +static int +write_dir_list(struct vsf_session* p_sess, struct mystr_list* p_dir_list, + enum EVSFRWTarget target) +{ + /* This function writes out a list of strings to the client, over the + * data socket. We now coalesce the strings into fewer write() syscalls, + * which saved 33% CPU time writing a large directory. + */ + int retval = 0; + unsigned int dir_index_max = str_list_get_length(p_dir_list); + unsigned int dir_index; + struct mystr buf_str = INIT_MYSTR; + str_reserve(&buf_str, VSFTP_DIR_BUFSIZE); + for (dir_index = 0; dir_index < dir_index_max; dir_index++) + { + str_append_str(&buf_str, str_list_get_pstr(p_dir_list, dir_index)); + if (dir_index == dir_index_max - 1 || + str_getlen(&buf_str) + + str_getlen(str_list_get_pstr(p_dir_list, dir_index + 1)) > + VSFTP_DIR_BUFSIZE) + { + /* Writeout needed - we're either at the end, or we filled the buffer */ + int writeret = ftp_write_str(p_sess, &buf_str, target); + if (writeret != 0) + { + retval = 1; + break; + } + str_empty(&buf_str); + } + } + str_free(&buf_str); + return retval; +} + +struct vsf_transfer_ret +vsf_ftpdataio_transfer_file(struct vsf_session* p_sess, int remote_fd, + int file_fd, int is_recv, int is_ascii) +{ + if (!is_recv) + { + if (is_ascii || p_sess->data_use_ssl) + { + return do_file_send_rwloop(p_sess, file_fd, is_ascii); + } + else + { + filesize_t curr_offset = vsf_sysutil_get_file_offset(file_fd); + filesize_t num_send = calc_num_send(file_fd, curr_offset); + return do_file_send_sendfile( + p_sess, remote_fd, file_fd, curr_offset, num_send); + } + } + else + { + return do_file_recv(p_sess, file_fd, is_ascii); + } +} + +static struct vsf_transfer_ret +do_file_send_rwloop(struct vsf_session* p_sess, int file_fd, int is_ascii) +{ + static char* p_readbuf; + static char* p_asciibuf; + struct vsf_transfer_ret ret_struct = { 0, 0 }; + unsigned int chunk_size = get_chunk_size(); + char* p_writefrom_buf; + if (p_readbuf == 0) + { + /* NOTE!! * 2 factor because we can double the data by doing our ASCII + * linefeed mangling + */ + vsf_secbuf_alloc(&p_asciibuf, VSFTP_DATA_BUFSIZE * 2); + vsf_secbuf_alloc(&p_readbuf, VSFTP_DATA_BUFSIZE); + } + if (is_ascii) + { + p_writefrom_buf = p_asciibuf; + } + else + { + p_writefrom_buf = p_readbuf; + } + while (1) + { + unsigned int num_to_write; + int retval = vsf_sysutil_read(file_fd, p_readbuf, chunk_size); + if (vsf_sysutil_retval_is_error(retval)) + { + ret_struct.retval = -1; + return ret_struct; + } + else if (retval == 0) + { + /* Success - cool */ + return ret_struct; + } + if (is_ascii) + { + num_to_write = vsf_ascii_bin_to_ascii(p_readbuf, p_asciibuf, + (unsigned int) retval); + } + else + { + num_to_write = (unsigned int) retval; + } + retval = ftp_write_data(p_sess, p_writefrom_buf, num_to_write); + if (vsf_sysutil_retval_is_error(retval) || + (unsigned int) retval != num_to_write) + { + ret_struct.retval = -2; + return ret_struct; + } + ret_struct.transferred += (unsigned int) retval; + } +} + +static struct vsf_transfer_ret +do_file_send_sendfile(struct vsf_session* p_sess, int net_fd, int file_fd, + filesize_t curr_file_offset, filesize_t bytes_to_send) +{ + int retval; + unsigned int chunk_size = 0; + struct vsf_transfer_ret ret_struct = { 0, 0 }; + filesize_t init_file_offset = curr_file_offset; + filesize_t bytes_sent; + if (p_sess->bw_rate_max) + { + chunk_size = get_chunk_size(); + } + /* Just because I can ;-) */ + retval = vsf_sysutil_sendfile(net_fd, file_fd, &curr_file_offset, + bytes_to_send, chunk_size); + bytes_sent = curr_file_offset - init_file_offset; + ret_struct.transferred = bytes_sent; + if (vsf_sysutil_retval_is_error(retval)) + { + ret_struct.retval = -2; + return ret_struct; + } + else if (bytes_sent != bytes_to_send) + { + ret_struct.retval = -2; + return ret_struct; + } + return ret_struct; +} + +static filesize_t +calc_num_send(int file_fd, filesize_t init_offset) +{ + static struct vsf_sysutil_statbuf* s_p_statbuf; + filesize_t bytes_to_send; + /* Work out how many bytes to send based on file size minus current offset */ + vsf_sysutil_fstat(file_fd, &s_p_statbuf); + bytes_to_send = vsf_sysutil_statbuf_get_size(s_p_statbuf); + if (init_offset < 0 || bytes_to_send < 0) + { + die("calc_num_send: negative file offset or send count"); + } + /* Don't underflow if some bonehead sets a REST greater than the file size */ + if (init_offset > bytes_to_send) + { + bytes_to_send = 0; + } + else + { + bytes_to_send -= init_offset; + } + return bytes_to_send; +} + +static struct vsf_transfer_ret +do_file_recv(struct vsf_session* p_sess, int file_fd, int is_ascii) +{ + static char* p_recvbuf; + unsigned int num_to_write; + struct vsf_transfer_ret ret_struct = { 0, 0 }; + unsigned int chunk_size = get_chunk_size(); + int prev_cr = 0; + if (p_recvbuf == 0) + { + /* Now that we do ASCII conversion properly, the plus one is to cater for + * the fact we may need to stick a '\r' at the front of the buffer if the + * last buffer fragment eneded in a '\r' and the current buffer fragment + * does not start with a '\n'. + */ + vsf_secbuf_alloc(&p_recvbuf, VSFTP_DATA_BUFSIZE + 1); + } + while (1) + { + const char* p_writebuf = p_recvbuf + 1; + int retval = ftp_read_data(p_sess, p_recvbuf + 1, chunk_size); + if (vsf_sysutil_retval_is_error(retval)) + { + ret_struct.retval = -2; + return ret_struct; + } + else if (retval == 0 && !prev_cr) + { + /* Transfer done, nifty */ + return ret_struct; + } + num_to_write = (unsigned int) retval; + ret_struct.transferred += num_to_write; + if (is_ascii) + { + /* Handle ASCII conversion if we have to. Note that using the same + * buffer for source and destination is safe, because the ASCII -> + * binary transform only ever results in a smaller file. + */ + struct ascii_to_bin_ret ret = + vsf_ascii_ascii_to_bin(p_recvbuf, num_to_write, prev_cr); + num_to_write = ret.stored; + prev_cr = ret.last_was_cr; + p_writebuf = ret.p_buf; + } + retval = vsf_sysutil_write_loop(file_fd, p_writebuf, num_to_write); + if (vsf_sysutil_retval_is_error(retval) || + (unsigned int) retval != num_to_write) + { + ret_struct.retval = -1; + return ret_struct; + } + } +} + +static unsigned int +get_chunk_size() +{ + unsigned int ret = VSFTP_DATA_BUFSIZE; + if (tunable_trans_chunk_size < VSFTP_DATA_BUFSIZE && + tunable_trans_chunk_size > 0) + { + ret = tunable_trans_chunk_size; + if (ret < 4096) + { + ret = 4096; + } + } + return ret; +} + diff --git a/ftpdataio.h b/ftpdataio.h new file mode 100644 index 0000000..472023a --- /dev/null +++ b/ftpdataio.h @@ -0,0 +1,99 @@ +#ifndef VSF_FTPDATAIO_H +#define VSF_FTPDATAIO_H + +#include "filesize.h" + +struct mystr; +struct vsf_sysutil_sockaddr; +struct vsf_sysutil_dir; +struct vsf_session; + +/* vsf_ftpdataio_dispose_transfer_fd() + * PURPOSE + * Close down the remote data transfer file descriptor. If unsent data reamins + * on the connection, this method blocks until it is transferred (or the data + * timeout goes off, or the connection is severed). + * PARAMETERS + * p_sess - the current FTP session object + */ +void vsf_ftpdataio_dispose_transfer_fd(struct vsf_session* p_sess); + +/* vsf_ftpdataio_get_pasv_fd() + * PURPOSE + * Return a connection data file descriptor obtained by the PASV connection + * method. This includes accept()'ing a connection from the remote. + * PARAMETERS + * p_sess - the current FTP session object + * RETURNS + * The file descriptor upon success, or -1 upon error. + */ +int vsf_ftpdataio_get_pasv_fd(struct vsf_session* p_sess); + +/* vsf_ftpdataio_get_pasv_fd() + * PURPOSE + * Return a connection data file descriptor obtained by the PORT connection + * method. This includes connect()'ing to the remote. + * PARAMETERS + * p_sess - the current FTP session object + * RETURNS + * The file descriptor upon success, or -1 upon error. + */ +int vsf_ftpdataio_get_port_fd(struct vsf_session* p_sess); + +/* vsf_ftpdataio_post_mark_connect() + * PURPOSE + * Perform any post-150-status-mark setup on the data connection. For example, + * the negotiation of SSL. + * PARAMETERS + * p_sess - the current FTP session object + * RETURNS + * 1 on success, 0 otherwise. + */ +int vsf_ftpdataio_post_mark_connect(struct vsf_session* p_sess); + +/* vsf_ftpdataio_transfer_file() + * PURPOSE + * Send data between the network and a local file. Send and receive are + * supported, as well as ASCII mangling. + * PARAMETERS + * remote_fd - the file descriptor of the remote data connection + * file_fd - the file descriptor of the local file + * is_recv - 0 for sending to the remote, otherwise receive + * is_ascii - non zero for ASCII mangling + * RETURNS + * A structure, containing + * retval - 0 for success, failure otherwise + * (-1 = local problem -2 = remote problem) + * transferred - number of bytes transferred + */ +struct vsf_transfer_ret +{ + int retval; + filesize_t transferred; +}; +struct vsf_transfer_ret vsf_ftpdataio_transfer_file( + struct vsf_session* p_sess, + int remote_fd, int file_fd, int is_recv, int is_ascii); + +/* vsf_ftpdataio_transfer_dir() + * PURPOSE + * Send an ASCII directory lising of the requested directory to the remote + * client. + * PARAMETERS + * p_sess - the current session object + * is_control - whether to send on the control connection or data connection + * p_dir - the local directory object + * p_base_dir_str - the directory we opened relative to the current one + * p_option_str - the options list provided to "ls" + * p_filter_str - the filter string provided to "ls" + * is_verbose - set to 0 if NLST used, 1 if LIST used + */ +int vsf_ftpdataio_transfer_dir(struct vsf_session* p_sess, int is_control, + struct vsf_sysutil_dir* p_dir, + const struct mystr* p_base_dir_str, + const struct mystr* p_option_str, + const struct mystr* p_filter_str, + int is_verbose); + +#endif /* VSF_FTPDATAIO_H */ + diff --git a/hash.c b/hash.c new file mode 100644 index 0000000..a1ea29d --- /dev/null +++ b/hash.c @@ -0,0 +1,147 @@ +/* + * Part of Very Secure FTPd + * Licence: GPL v2 + * Author: Chris Evans + * hash.c + * + * Routines to handle simple hash table lookups and modifications. + */ + +#include "hash.h" +#include "sysutil.h" +#include "utility.h" + +struct hash_node +{ + void* p_key; + void* p_value; + struct hash_node* p_prev; + struct hash_node* p_next; +}; + +struct hash +{ + unsigned int buckets; + unsigned int key_size; + unsigned int value_size; + hashfunc_t hash_func; + struct hash_node** p_nodes; +}; + +/* Internal functions */ +struct hash_node** hash_get_bucket(struct hash* p_hash, void* p_key); +struct hash_node* hash_get_node_by_key(struct hash* p_hash, void* p_key); + +struct hash* +hash_alloc(unsigned int buckets, unsigned int key_size, + unsigned int value_size, hashfunc_t hash_func) +{ + unsigned int size; + struct hash* p_hash = vsf_sysutil_malloc(sizeof(*p_hash)); + p_hash->buckets = buckets; + p_hash->key_size = key_size; + p_hash->value_size = value_size; + p_hash->hash_func = hash_func; + size = sizeof(struct hash_node*) * buckets; + p_hash->p_nodes = vsf_sysutil_malloc(size); + vsf_sysutil_memclr(p_hash->p_nodes, size); + return p_hash; +} + +void* +hash_lookup_entry(struct hash* p_hash, void* p_key) +{ + struct hash_node* p_node = hash_get_node_by_key(p_hash, p_key); + if (!p_node) + { + return p_node; + } + return p_node->p_value; +} + +void +hash_add_entry(struct hash* p_hash, void* p_key, void* p_value) +{ + struct hash_node** p_bucket; + struct hash_node* p_new_node; + if (hash_lookup_entry(p_hash, p_key)) + { + bug("duplicate hash key"); + } + p_bucket = hash_get_bucket(p_hash, p_key); + p_new_node = vsf_sysutil_malloc(sizeof(*p_new_node)); + p_new_node->p_prev = 0; + p_new_node->p_next = 0; + p_new_node->p_key = vsf_sysutil_malloc(p_hash->key_size); + vsf_sysutil_memcpy(p_new_node->p_key, p_key, p_hash->key_size); + p_new_node->p_value = vsf_sysutil_malloc(p_hash->value_size); + vsf_sysutil_memcpy(p_new_node->p_value, p_value, p_hash->value_size); + + if (!*p_bucket) + { + *p_bucket = p_new_node; + } + else + { + p_new_node->p_next = *p_bucket; + (*p_bucket)->p_prev = p_new_node; + *p_bucket = p_new_node; + } +} + +void +hash_free_entry(struct hash* p_hash, void* p_key) +{ + struct hash_node* p_node = hash_get_node_by_key(p_hash, p_key); + if (!p_node) + { + bug("hash node not found"); + } + vsf_sysutil_free(p_node->p_key); + vsf_sysutil_free(p_node->p_value); + + if (p_node->p_prev) + { + p_node->p_prev->p_next = p_node->p_next; + } + else + { + struct hash_node** p_bucket = hash_get_bucket(p_hash, p_key); + *p_bucket = p_node->p_next; + } + if (p_node->p_next) + { + p_node->p_next->p_prev = p_node->p_prev; + } + + vsf_sysutil_free(p_node); +} + +struct hash_node** +hash_get_bucket(struct hash* p_hash, void* p_key) +{ + unsigned int bucket = (*p_hash->hash_func)(p_hash->buckets, p_key); + if (bucket >= p_hash->buckets) + { + bug("bad bucket lookup"); + } + return &(p_hash->p_nodes[bucket]); +} + +struct hash_node* +hash_get_node_by_key(struct hash* p_hash, void* p_key) +{ + struct hash_node** p_bucket = hash_get_bucket(p_hash, p_key); + struct hash_node* p_node = *p_bucket; + if (!p_node) + { + return p_node; + } + while (p_node != 0 && + vsf_sysutil_memcmp(p_key, p_node->p_key, p_hash->key_size) != 0) + { + p_node = p_node->p_next; + } + return p_node; +} + diff --git a/hash.h b/hash.h new file mode 100644 index 0000000..1007d7c --- /dev/null +++ b/hash.h @@ -0,0 +1,15 @@ +#ifndef VSFTP_HASH_H +#define VSFTP_HASH_H + +struct hash; + +typedef unsigned int (*hashfunc_t)(unsigned int, void*); + +struct hash* hash_alloc(unsigned int buckets, unsigned int key_size, + unsigned int value_size, hashfunc_t hash_func); +void* hash_lookup_entry(struct hash* p_hash, void* p_key); +void hash_add_entry(struct hash* p_hash, void* p_key, void* p_value); +void hash_free_entry(struct hash* p_hash, void* p_key); + +#endif /* VSFTP_HASH_H */ + diff --git a/ipaddrparse.c b/ipaddrparse.c new file mode 100644 index 0000000..a4a7215 --- /dev/null +++ b/ipaddrparse.c @@ -0,0 +1,220 @@ +/* + * Part of Very Secure FTPd + * Licence: GPL v2 + * Author: Chris Evans + * ipaddrparse.c + * + * A routine to parse ip addresses. I'm paranoid and don't want to use + * inet_pton. + */ + +#include "ipaddrparse.h" +#include "sysutil.h" +#include "str.h" + +static int ipv6_parse_main(struct mystr* p_out_str, + const struct mystr* p_in_str); +static int ipv6_parse_hex(struct mystr* p_out_str, + const struct mystr* p_in_str); +static int ipv4_parse_dotquad(struct mystr* p_out_str, + const struct mystr* p_in_str); + +const unsigned char* +vsf_sysutil_parse_ipv6(const struct mystr* p_str) +{ + static struct mystr s_ret; + static struct mystr s_rhs_ret; + static struct mystr s_lhs_str; + static struct mystr s_rhs_str; + unsigned int lhs_len; + unsigned int rhs_len; + str_empty(&s_ret); + str_empty(&s_rhs_ret); + str_copy(&s_lhs_str, p_str); + str_split_text(&s_lhs_str, &s_rhs_str, "::"); + if (!ipv6_parse_main(&s_ret, &s_lhs_str)) + { + return 0; + } + if (!ipv6_parse_main(&s_rhs_ret, &s_rhs_str)) + { + return 0; + } + lhs_len = str_getlen(&s_ret); + rhs_len = str_getlen(&s_rhs_ret); + if (lhs_len + rhs_len > 16) + { + return 0; + } + if (rhs_len > 0) + { + unsigned int add_nulls = 16 - (lhs_len + rhs_len); + while (add_nulls--) + { + str_append_char(&s_ret, '\0'); + } + str_append_str(&s_ret, &s_rhs_ret); + } + return (const unsigned char*) str_getbuf(&s_ret); +} + +const unsigned char* +vsf_sysutil_parse_ipv4(const struct mystr* p_str) +{ + static unsigned char items[4]; + return vsf_sysutil_parse_uchar_string_sep(p_str, '.', items, sizeof(items)); +} + +const unsigned char* +vsf_sysutil_parse_uchar_string_sep( + const struct mystr* p_str, char sep, unsigned char* p_items, + unsigned int items) +{ + static struct mystr s_tmp_str; + unsigned int i; + str_copy(&s_tmp_str, p_str); + for (i=0; i 255) + { + return 0; + } + /* If this truncates from int to uchar, we don't care */ + p_items[i] = (unsigned char) this_number; + /* The right hand side of the comma now becomes the new string to + * breakdown + */ + str_copy(&s_tmp_str, &s_rhs_sep_str); + } + return p_items; +} + +static int +ipv6_parse_main(struct mystr* p_out_str, const struct mystr* p_in_str) +{ + static struct mystr s_lhs_str; + static struct mystr s_rhs_str; + struct str_locate_result loc_ret; + str_copy(&s_lhs_str, p_in_str); + while (!str_isempty(&s_lhs_str)) + { + str_split_char(&s_lhs_str, &s_rhs_str, ':'); + if (str_isempty(&s_lhs_str)) + { + return 0; + } + loc_ret = str_locate_char(&s_lhs_str, '.'); + if (loc_ret.found) + { + if (!ipv4_parse_dotquad(p_out_str, &s_lhs_str)) + { + return 0; + } + } + else if (!ipv6_parse_hex(p_out_str, &s_lhs_str)) + { + return 0; + } + str_copy(&s_lhs_str, &s_rhs_str); + } + return 1; +} + +static int +ipv6_parse_hex(struct mystr* p_out_str, const struct mystr* p_in_str) +{ + unsigned int len = str_getlen(p_in_str); + unsigned int i; + unsigned int val = 0; + for (i=0; i= '0' && ch <= '9') + { + ch -= '0'; + } + else if (ch >= 'A' && ch <= 'F') + { + ch -= 'A'; + ch += 10; + } + else + { + return 0; + } + val <<= 4; + val |= ch; + if (val > 0xFFFF) + { + return 0; + } + } + str_append_char(p_out_str, (val >> 8)); + str_append_char(p_out_str, (val & 0xFF)); + return 1; +} + +static int +ipv4_parse_dotquad(struct mystr* p_out_str, const struct mystr* p_in_str) +{ + unsigned int len = str_getlen(p_in_str); + unsigned int i; + unsigned int val = 0; + unsigned int final_val = 0; + int seen_char = 0; + int dots = 0; + for (i=0; i= '0' && ch <= '9') + { + ch -= '0'; + val *= 10; + val += ch; + if (val > 255) + { + return 0; + } + seen_char = 1; + } + else + { + return 0; + } + } + if (dots != 3 || !seen_char) + { + return 0; + } + final_val <<= 8; + final_val |= val; + str_append_char(p_out_str, (final_val >> 24)); + str_append_char(p_out_str, ((final_val >> 16) & 0xFF)); + str_append_char(p_out_str, ((final_val >> 8) & 0xFF)); + str_append_char(p_out_str, (final_val & 0xFF)); + return 1; +} + diff --git a/ipaddrparse.h b/ipaddrparse.h new file mode 100644 index 0000000..cae025f --- /dev/null +++ b/ipaddrparse.h @@ -0,0 +1,20 @@ +#ifndef VSF_IPADDRPARSE_H +#define VSF_IPADDRPARSE_H + +struct mystr; + +/* Effectively doing the same sort of job as inet_pton. Since inet_pton does + * a non-trivial amount of parsing, we'll do it ourselves for maximum security + * and safety. + */ + +const unsigned char* vsf_sysutil_parse_ipv6(const struct mystr* p_str); + +const unsigned char* vsf_sysutil_parse_ipv4(const struct mystr* p_str); + +const unsigned char* vsf_sysutil_parse_uchar_string_sep( + const struct mystr* p_str, char sep, unsigned char* p_items, + unsigned int items); + +#endif /* VSF_IPADDRPARSE_H */ + diff --git a/logging.c b/logging.c new file mode 100644 index 0000000..98ce316 --- /dev/null +++ b/logging.c @@ -0,0 +1,352 @@ +/* + * Part of Very Secure FTPd + * Licence: GPL v2 + * Author: Chris Evans + * + * logging.c + */ + +#include "logging.h" +#include "tunables.h" +#include "utility.h" +#include "str.h" +#include "sysutil.h" +#include "sysstr.h" +#include "session.h" + +/* File local functions */ +static int vsf_log_type_is_transfer(enum EVSFLogEntryType type); +static void vsf_log_common(struct vsf_session* p_sess, int succeeded, + enum EVSFLogEntryType what, + const struct mystr* p_str); +static void vsf_log_do_log_vsftpd_format(struct vsf_session* p_sess, + struct mystr* p_str, int succeeded, + enum EVSFLogEntryType what, + const struct mystr* p_log_str); +static void vsf_log_do_log_wuftpd_format(struct vsf_session* p_sess, + struct mystr* p_str, int succeeded); +static void vsf_log_do_log_to_file(int fd, struct mystr* p_str); + +void +vsf_log_init(struct vsf_session* p_sess) +{ + int retval; + if (tunable_syslog_enable || tunable_tcp_wrappers) + { + vsf_sysutil_openlog(); + } + if (!tunable_xferlog_enable && !tunable_dual_log_enable) + { + return; + } + if (tunable_dual_log_enable || tunable_xferlog_std_format) + { + retval = vsf_sysutil_create_or_open_file(tunable_xferlog_file, 0600); + if (vsf_sysutil_retval_is_error(retval)) + { + die2("failed to open xferlog log file:", tunable_xferlog_file); + } + p_sess->xferlog_fd = retval; + } + if (tunable_dual_log_enable || !tunable_xferlog_std_format) + { + if (!tunable_syslog_enable) + { + retval = vsf_sysutil_create_or_open_file(tunable_vsftpd_log_file, 0600); + if (vsf_sysutil_retval_is_error(retval)) + { + die2("failed to open vsftpd log file:", tunable_vsftpd_log_file); + } + p_sess->vsftpd_log_fd = retval; + } + } +} + +static int +vsf_log_type_is_transfer(enum EVSFLogEntryType type) +{ + return (type == kVSFLogEntryDownload || type == kVSFLogEntryUpload); +} + +void +vsf_log_start_entry(struct vsf_session* p_sess, enum EVSFLogEntryType what) +{ + if (p_sess->log_type != 0) + { + bug("non null log_type in vsf_log_start_entry"); + } + p_sess->log_type = (unsigned long) what; + p_sess->log_start_sec = 0; + p_sess->log_start_usec = 0; + p_sess->transfer_size = 0; + str_empty(&p_sess->log_str); + if (vsf_log_type_is_transfer(what)) + { + vsf_sysutil_update_cached_time(); + p_sess->log_start_sec = vsf_sysutil_get_cached_time_sec(); + p_sess->log_start_usec = vsf_sysutil_get_cached_time_usec(); + } +} + +void +vsf_log_line(struct vsf_session* p_sess, enum EVSFLogEntryType what, + struct mystr* p_str) +{ + vsf_log_common(p_sess, 1, what, p_str); +} + +int +vsf_log_entry_pending(struct vsf_session* p_sess) +{ + if (p_sess->log_type == 0) + { + return 0; + } + return 1; +} + +void +vsf_log_do_log(struct vsf_session* p_sess, int succeeded) +{ + vsf_log_common(p_sess, succeeded, (enum EVSFLogEntryType) p_sess->log_type, + &p_sess->log_str); + p_sess->log_type = 0; +} + +static void +vsf_log_common(struct vsf_session* p_sess, int succeeded, + enum EVSFLogEntryType what, const struct mystr* p_str) +{ + static struct mystr s_log_str; + /* Handle xferlog line if appropriate */ + if (p_sess->xferlog_fd != -1 && vsf_log_type_is_transfer(what)) + { + vsf_log_do_log_wuftpd_format(p_sess, &s_log_str, succeeded); + vsf_log_do_log_to_file(p_sess->xferlog_fd, &s_log_str); + } + /* Handle vsftpd.log line if appropriate */ + if (p_sess->vsftpd_log_fd != -1) + { + vsf_log_do_log_vsftpd_format(p_sess, &s_log_str, succeeded, what, p_str); + vsf_log_do_log_to_file(p_sess->vsftpd_log_fd, &s_log_str); + } + /* Handle syslog() line if appropriate */ + if (tunable_syslog_enable) + { + int severe = 0; + vsf_log_do_log_vsftpd_format(p_sess, &s_log_str, succeeded, what, p_str); + if (what == kVSFLogEntryLogin && !succeeded) + { + severe = 1; + } + str_syslog(&s_log_str, severe); + } +} + +static void +vsf_log_do_log_to_file(int fd, struct mystr* p_str) +{ + if (!tunable_no_log_lock) + { + int retval = vsf_sysutil_lock_file_write(fd); + if (vsf_sysutil_retval_is_error(retval)) + { + return; + } + } + str_replace_unprintable(p_str, '?'); + str_append_char(p_str, '\n'); + /* Ignore write failure; maybe the disk filled etc. */ + (void) str_write_loop(p_str, fd); + if (!tunable_no_log_lock) + { + vsf_sysutil_unlock_file(fd); + } +} + +static void +vsf_log_do_log_wuftpd_format(struct vsf_session* p_sess, struct mystr* p_str, + int succeeded) +{ + long delta_sec; + enum EVSFLogEntryType what = (enum EVSFLogEntryType) p_sess->log_type; + /* Date - vsf_sysutil_get_current_date updates cached time */ + str_alloc_text(p_str, vsf_sysutil_get_current_date()); + str_append_char(p_str, ' '); + /* Transfer time (in seconds) */ + delta_sec = vsf_sysutil_get_cached_time_sec() - p_sess->log_start_sec; + if (delta_sec <= 0) + { + delta_sec = 1; + } + str_append_ulong(p_str, (unsigned long) delta_sec); + str_append_char(p_str, ' '); + /* Remote host name */ + str_append_str(p_str, &p_sess->remote_ip_str); + str_append_char(p_str, ' '); + /* Bytes transferred */ + str_append_filesize_t(p_str, p_sess->transfer_size); + str_append_char(p_str, ' '); + /* Filename */ + str_append_str(p_str, &p_sess->log_str); + str_append_char(p_str, ' '); + /* Transfer type (ascii/binary) */ + if (p_sess->is_ascii) + { + str_append_text(p_str, "a "); + } + else + { + str_append_text(p_str, "b "); + } + /* Special action flag - tar, gzip etc. */ + str_append_text(p_str, "_ "); + /* Direction of transfer */ + if (what == kVSFLogEntryUpload) + { + str_append_text(p_str, "i "); + } + else + { + str_append_text(p_str, "o "); + } + /* Access mode: anonymous/real user, and identity */ + if (p_sess->is_anonymous) + { + str_append_text(p_str, "a "); + str_append_str(p_str, &p_sess->anon_pass_str); + } + else + { + str_append_text(p_str, "r "); + str_append_str(p_str, &p_sess->user_str); + } + str_append_char(p_str, ' '); + /* Service name, authentication method, authentication user id */ + str_append_text(p_str, "ftp 0 * "); + /* Completion status */ + if (succeeded) + { + str_append_char(p_str, 'c'); + } + else + { + str_append_char(p_str, 'i'); + } +} + +static void +vsf_log_do_log_vsftpd_format(struct vsf_session* p_sess, struct mystr* p_str, + int succeeded, enum EVSFLogEntryType what, + const struct mystr* p_log_str) +{ + /* Date - vsf_sysutil_get_current_date updates cached time */ + str_alloc_text(p_str, vsf_sysutil_get_current_date()); + /* Pid */ + str_append_text(p_str, " [pid "); + str_append_ulong(p_str, vsf_sysutil_getpid()); + str_append_text(p_str, "] "); + /* User */ + if (!str_isempty(&p_sess->user_str)) + { + str_append_char(p_str, '['); + str_append_str(p_str, &p_sess->user_str); + str_append_text(p_str, "] "); + } + /* And the action */ + if (what != kVSFLogEntryFTPInput && what != kVSFLogEntryFTPOutput && + what != kVSFLogEntryConnection) + { + if (succeeded) + { + str_append_text(p_str, "OK "); + } + else + { + str_append_text(p_str, "FAIL "); + } + } + switch (what) + { + case kVSFLogEntryDownload: + str_append_text(p_str, "DOWNLOAD"); + break; + case kVSFLogEntryUpload: + str_append_text(p_str, "UPLOAD"); + break; + case kVSFLogEntryMkdir: + str_append_text(p_str, "MKDIR"); + break; + case kVSFLogEntryLogin: + str_append_text(p_str, "LOGIN"); + break; + case kVSFLogEntryFTPInput: + str_append_text(p_str, "FTP command"); + break; + case kVSFLogEntryFTPOutput: + str_append_text(p_str, "FTP response"); + break; + case kVSFLogEntryConnection: + str_append_text(p_str, "CONNECT"); + break; + case kVSFLogEntryDelete: + str_append_text(p_str, "DELETE"); + break; + case kVSFLogEntryRename: + str_append_text(p_str, "RENAME"); + break; + case kVSFLogEntryRmdir: + str_append_text(p_str, "RMDIR"); + break; + case kVSFLogEntryChmod: + str_append_text(p_str, "CHMOD"); + break; + default: + bug("bad entry_type in vsf_log_do_log"); + break; + } + str_append_text(p_str, ": Client \""); + str_append_str(p_str, &p_sess->remote_ip_str); + str_append_char(p_str, '"'); + if (what == kVSFLogEntryLogin && !str_isempty(&p_sess->anon_pass_str)) + { + str_append_text(p_str, ", anon password \""); + str_append_str(p_str, &p_sess->anon_pass_str); + str_append_char(p_str, '"'); + } + if (!str_isempty(p_log_str)) + { + str_append_text(p_str, ", \""); + str_append_str(p_str, p_log_str); + str_append_char(p_str, '"'); + } + if (what != kVSFLogEntryFTPInput && what != kVSFLogEntryFTPOutput) + { + if (p_sess->transfer_size) + { + str_append_text(p_str, ", "); + str_append_filesize_t(p_str, p_sess->transfer_size); + str_append_text(p_str, " bytes"); + } + if (vsf_log_type_is_transfer(what)) + { + long delta_sec = vsf_sysutil_get_cached_time_sec() - + p_sess->log_start_sec; + long delta_usec = vsf_sysutil_get_cached_time_usec() - + p_sess->log_start_usec; + double time_delta = (double) delta_sec + ((double) delta_usec / + (double) 1000000); + double kbyte_rate; + if (time_delta <= 0) + { + time_delta = 0.1; + } + kbyte_rate = + ((double) p_sess->transfer_size / time_delta) / (double) 1024; + str_append_text(p_str, ", "); + str_append_double(p_str, kbyte_rate); + str_append_text(p_str, "Kbyte/sec"); + } + } +} + diff --git a/logging.h b/logging.h new file mode 100644 index 0000000..575b673 --- /dev/null +++ b/logging.h @@ -0,0 +1,77 @@ +#ifndef VSF_LOGGING_H +#define VSF_LOGGING_H + +/* Forward delcarations */ +struct mystr; +struct vsf_session; + +enum EVSFLogEntryType +{ + kVSFLogEntryNull = 1, + kVSFLogEntryDownload, + kVSFLogEntryUpload, + kVSFLogEntryMkdir, + kVSFLogEntryLogin, + kVSFLogEntryFTPInput, + kVSFLogEntryFTPOutput, + kVSFLogEntryConnection, + kVSFLogEntryDelete, + kVSFLogEntryRename, + kVSFLogEntryRmdir, + kVSFLogEntryChmod +}; + +/* vsf_log_init() + * PURPOSE + * Initialize the logging services, by opening a writable file descriptor to + * the log file (should logging be enabled). + * PARAMETERS + * p_sess - the current session object + */ +void vsf_log_init(struct vsf_session* p_sess); + +/* vsf_log_start_entry() + * PURPOSE + * Denote the start of a logged operation. Importantly, timing information + * (if applicable) will be taken starting from this call. + * PARAMETERS + * p_sess - the current session object + * what - the type of operation which just started + */ +void vsf_log_start_entry(struct vsf_session* p_sess, + enum EVSFLogEntryType what); + +/* vsf_log_entry_pending() + * PURPOSE + * Determine whether a log entry has been started and not yet closed. + * RETURNS + * 0 if no log entry is pending; 1 if one is. + */ +int vsf_log_entry_pending(struct vsf_session* p_sess); + +/* vsf_log_do_log() + * PURPOSE + * Denote the end of a logged operation, specifying whether the operation + * was successful or not. + * PARAMETERS + * p_sess - the current session object + * succeeded - 0 for a failed operation, 1 for a successful operation + */ +void vsf_log_do_log(struct vsf_session* p_sess, int succeeded); + +/* vsf_log_line() + * PURPOSE + * Logs a single line of information, without disturbing any pending log + * operations (e.g. a download log spans a period of time). + * This call must be used for any logging calls nested within a call to + * the vsf_log_start_entry() function. + * PARAMETERS + * p_sess - the current session object + * what - the type of operation to log + * p_str - the string to log + */ +void vsf_log_line(struct vsf_session* p_sess, enum EVSFLogEntryType what, + struct mystr* p_str); + +#endif /* VSF_LOGGING_H */ + diff --git a/ls.c b/ls.c new file mode 100644 index 0000000..93018f0 --- /dev/null +++ b/ls.c @@ -0,0 +1,436 @@ +/* + * Part of Very Secure FTPd + * Licence: GPL v2 + * Author: Chris Evans + * ls.c + * + * Would you believe, code to handle directory listing. + */ + +#include "ls.h" +#include "access.h" +#include "str.h" +#include "strlist.h" +#include "sysstr.h" +#include "sysutil.h" +#include "tunables.h" + +static void build_dir_line(struct mystr* p_str, + const struct mystr* p_filename_str, + const struct vsf_sysutil_statbuf* p_stat); + +void +vsf_ls_populate_dir_list(struct mystr_list* p_list, + struct mystr_list* p_subdir_list, + struct vsf_sysutil_dir* p_dir, + const struct mystr* p_base_dir_str, + const struct mystr* p_option_str, + const struct mystr* p_filter_str, + int is_verbose) +{ + struct mystr dirline_str = INIT_MYSTR; + struct mystr normalised_base_dir_str = INIT_MYSTR; + struct str_locate_result loc_result; + int a_option; + int r_option; + int t_option; + int F_option; + int do_stat = 0; + loc_result = str_locate_char(p_option_str, 'a'); + a_option = loc_result.found; + loc_result = str_locate_char(p_option_str, 'r'); + r_option = loc_result.found; + loc_result = str_locate_char(p_option_str, 't'); + t_option = loc_result.found; + loc_result = str_locate_char(p_option_str, 'F'); + F_option = loc_result.found; + loc_result = str_locate_char(p_option_str, 'l'); + if (loc_result.found) + { + is_verbose = 1; + } + /* Invert "reverse" arg for "-t", the time sorting */ + if (t_option) + { + r_option = !r_option; + } + if (is_verbose || t_option || F_option || p_subdir_list != 0) + { + do_stat = 1; + } + /* If the filter starts with a . then implicitly enable -a */ + if (!str_isempty(p_filter_str) && str_get_char_at(p_filter_str, 0) == '.') + { + a_option = 1; + } + /* "Normalise" the incoming base directory string by making sure it + * ends in a '/' if it is nonempty + */ + if (!str_equal_text(p_base_dir_str, ".")) + { + str_copy(&normalised_base_dir_str, p_base_dir_str); + } + if (!str_isempty(&normalised_base_dir_str)) + { + unsigned int len = str_getlen(&normalised_base_dir_str); + if (str_get_char_at(&normalised_base_dir_str, len - 1) != '/') + { + str_append_char(&normalised_base_dir_str, '/'); + } + } + /* If we're going to need to do time comparisions, cache the local time */ + if (is_verbose) + { + vsf_sysutil_update_cached_time(); + } + while (1) + { + int len; + static struct mystr s_next_filename_str; + static struct mystr s_next_path_and_filename_str; + static struct vsf_sysutil_statbuf* s_p_statbuf; + str_next_dirent(&s_next_filename_str, p_dir); + if (str_isempty(&s_next_filename_str)) + { + break; + } + len = str_getlen(&s_next_filename_str); + if (len > 0 && str_get_char_at(&s_next_filename_str, 0) == '.') + { + if (!a_option && !tunable_force_dot_files) + { + continue; + } + if (!a_option && + ((len == 2 && str_get_char_at(&s_next_filename_str, 1) == '.') || + len == 1)) + { + continue; + } + } + /* Don't show hidden directory entries */ + if (!vsf_access_check_file_visible(&s_next_filename_str)) + { + continue; + } + /* If we have an ls option which is a filter, apply it */ + if (!str_isempty(p_filter_str)) + { + if (!vsf_filename_passes_filter(&s_next_filename_str, p_filter_str)) + { + continue; + } + } + /* Calculate the full path (relative to CWD) for lstat() and + * output purposes + */ + str_copy(&s_next_path_and_filename_str, &normalised_base_dir_str); + str_append_str(&s_next_path_and_filename_str, &s_next_filename_str); + if (do_stat) + { + /* lstat() the file. Of course there's a race condition - the + * directory entry may have gone away whilst we read it, so + * ignore failure to stat + */ + int retval = str_lstat(&s_next_path_and_filename_str, &s_p_statbuf); + if (vsf_sysutil_retval_is_error(retval)) + { + continue; + } + } + if (is_verbose) + { + static struct mystr s_final_file_str; + /* If it's a damn symlink, we need to append the target */ + str_copy(&s_final_file_str, &s_next_filename_str); + if (vsf_sysutil_statbuf_is_symlink(s_p_statbuf)) + { + static struct mystr s_temp_str; + int retval = str_readlink(&s_temp_str, &s_next_path_and_filename_str); + if (retval == 0 && !str_isempty(&s_temp_str)) + { + str_append_text(&s_final_file_str, " -> "); + str_append_str(&s_final_file_str, &s_temp_str); + } + } + if (F_option && vsf_sysutil_statbuf_is_dir(s_p_statbuf)) + { + str_append_char(&s_final_file_str, '/'); + } + build_dir_line(&dirline_str, &s_final_file_str, s_p_statbuf); + } + else + { + /* Just emit the filenames - note, we prepend the directory for NLST + * but not for LIST + */ + str_copy(&dirline_str, &s_next_path_and_filename_str); + if (F_option) + { + if (vsf_sysutil_statbuf_is_dir(s_p_statbuf)) + { + str_append_char(&dirline_str, '/'); + } + else if (vsf_sysutil_statbuf_is_symlink(s_p_statbuf)) + { + str_append_char(&dirline_str, '@'); + } + } + str_append_text(&dirline_str, "\r\n"); + } + /* Add filename into our sorted list - sorting by filename or time. Also, + * if we are required to, maintain a distinct list of direct + * subdirectories. + */ + { + static struct mystr s_temp_str; + const struct mystr* p_sort_str = 0; + const struct mystr* p_sort_subdir_str = 0; + if (!t_option) + { + p_sort_str = &s_next_filename_str; + } + else + { + str_alloc_text(&s_temp_str, + vsf_sysutil_statbuf_get_sortkey_mtime(s_p_statbuf)); + p_sort_str = &s_temp_str; + p_sort_subdir_str = &s_temp_str; + } + str_list_add(p_list, &dirline_str, p_sort_str); + if (p_subdir_list != 0 && vsf_sysutil_statbuf_is_dir(s_p_statbuf)) + { + str_list_add(p_subdir_list, &s_next_filename_str, p_sort_subdir_str); + } + } + } /* END: while(1) */ + str_list_sort(p_list, r_option); + if (p_subdir_list != 0) + { + str_list_sort(p_subdir_list, r_option); + } + str_free(&dirline_str); + str_free(&normalised_base_dir_str); +} + +int +vsf_filename_passes_filter(const struct mystr* p_filename_str, + const struct mystr* p_filter_str) +{ + /* A simple routine to match a filename against a pattern. + * This routine is used instead of e.g. fnmatch(3), because we should be + * reluctant to trust the latter. fnmatch(3) involves _lots_ of string + * parsing and handling. There is broad potential for any given fnmatch(3) + * implementation to be buggy. + * + * Currently supported pattern(s): + * - any number of wildcards, "*" or "?" + * - {,} syntax (not nested) + * + * Note that pattern matching is only supported within the last path + * component. For example, searching for /a/b/? will work, but searching + * for /a/?/c will not. + */ + struct mystr filter_remain_str = INIT_MYSTR; + struct mystr name_remain_str = INIT_MYSTR; + struct mystr temp_str = INIT_MYSTR; + struct mystr brace_list_str = INIT_MYSTR; + struct mystr new_filter_str = INIT_MYSTR; + int ret = 0; + char last_token = 0; + int must_match_at_current_pos = 1; + str_copy(&filter_remain_str, p_filter_str); + str_copy(&name_remain_str, p_filename_str); + + while (!str_isempty(&filter_remain_str)) + { + static struct mystr s_match_needed_str; + /* Locate next special token */ + struct str_locate_result locate_result = + str_locate_chars(&filter_remain_str, "*?{"); + /* Isolate text leading up to token (if any) - needs to be matched */ + if (locate_result.found) + { + unsigned int indexx = locate_result.index; + str_left(&filter_remain_str, &s_match_needed_str, indexx); + str_mid_to_end(&filter_remain_str, &temp_str, indexx + 1); + str_copy(&filter_remain_str, &temp_str); + last_token = locate_result.char_found; + } + else + { + /* No more tokens. Must match remaining filter string exactly. */ + str_copy(&s_match_needed_str, &filter_remain_str); + str_empty(&filter_remain_str); + last_token = 0; + } + if (!str_isempty(&s_match_needed_str)) + { + /* Need to match something.. could be a match which has to start at + * current position, or we could allow it to start anywhere + */ + unsigned int indexx; + locate_result = str_locate_str(&name_remain_str, &s_match_needed_str); + if (!locate_result.found) + { + /* Fail */ + goto out; + } + indexx = locate_result.index; + if (must_match_at_current_pos && indexx > 0) + { + goto out; + } + /* Chop matched string out of remainder */ + str_mid_to_end(&name_remain_str, &temp_str, + indexx + str_getlen(&s_match_needed_str)); + str_copy(&name_remain_str, &temp_str); + } + if (last_token == '?') + { + if (str_isempty(&name_remain_str)) + { + goto out; + } + str_right(&name_remain_str, &temp_str, str_getlen(&name_remain_str) - 1); + str_copy(&name_remain_str, &temp_str); + must_match_at_current_pos = 1; + } + else if (last_token == '{') + { + struct str_locate_result end_brace = + str_locate_char(&filter_remain_str, '}'); + must_match_at_current_pos = 1; + if (end_brace.found) + { + str_split_char(&filter_remain_str, &temp_str, '}'); + str_copy(&brace_list_str, &filter_remain_str); + str_copy(&filter_remain_str, &temp_str); + str_split_char(&brace_list_str, &temp_str, ','); + while (!str_isempty(&brace_list_str)) + { + str_copy(&new_filter_str, &brace_list_str); + str_append_str(&new_filter_str, &filter_remain_str); + if (vsf_filename_passes_filter(&name_remain_str, &new_filter_str)) + { + ret = 1; + goto out; + } + str_copy(&brace_list_str, &temp_str); + str_split_char(&brace_list_str, &temp_str, ','); + } + goto out; + } + else if (str_isempty(&name_remain_str) || + str_get_char_at(&name_remain_str, 0) != '{') + { + goto out; + } + else + { + str_right(&name_remain_str, &temp_str, + str_getlen(&name_remain_str) - 1); + str_copy(&name_remain_str, &temp_str); + } + } + else + { + must_match_at_current_pos = 0; + } + } + /* Any incoming string left means no match unless we ended on the correct + * type of wildcard. + */ + if (str_getlen(&name_remain_str) > 0 && last_token != '*') + { + goto out; + } + /* OK, a match */ + ret = 1; +out: + str_free(&filter_remain_str); + str_free(&name_remain_str); + str_free(&temp_str); + str_free(&brace_list_str); + str_free(&new_filter_str); + return ret; +} + +static void +build_dir_line(struct mystr* p_str, const struct mystr* p_filename_str, + const struct vsf_sysutil_statbuf* p_stat) +{ + static struct mystr s_tmp_str; + filesize_t size = vsf_sysutil_statbuf_get_size(p_stat); + /* Permissions */ + str_alloc_text(p_str, vsf_sysutil_statbuf_get_perms(p_stat)); + str_append_char(p_str, ' '); + /* Hard link count */ + str_alloc_ulong(&s_tmp_str, vsf_sysutil_statbuf_get_links(p_stat)); + str_lpad(&s_tmp_str, 4); + str_append_str(p_str, &s_tmp_str); + str_append_char(p_str, ' '); + /* User */ + if (tunable_hide_ids) + { + str_alloc_text(&s_tmp_str, "ftp"); + } + else + { + int uid = vsf_sysutil_statbuf_get_uid(p_stat); + struct vsf_sysutil_user* p_user = 0; + if (tunable_text_userdb_names) + { + p_user = vsf_sysutil_getpwuid(uid); + } + if (p_user == 0) + { + str_alloc_ulong(&s_tmp_str, (unsigned long) uid); + } + else + { + str_alloc_text(&s_tmp_str, vsf_sysutil_user_getname(p_user)); + } + } + str_rpad(&s_tmp_str, 8); + str_append_str(p_str, &s_tmp_str); + str_append_char(p_str, ' '); + /* Group */ + if (tunable_hide_ids) + { + str_alloc_text(&s_tmp_str, "ftp"); + } + else + { + int gid = vsf_sysutil_statbuf_get_gid(p_stat); + struct vsf_sysutil_group* p_group = 0; + if (tunable_text_userdb_names) + { + p_group = vsf_sysutil_getgrgid(gid); + } + if (p_group == 0) + { + str_alloc_ulong(&s_tmp_str, (unsigned long) gid); + } + else + { + str_alloc_text(&s_tmp_str, vsf_sysutil_group_getname(p_group)); + } + } + str_rpad(&s_tmp_str, 8); + str_append_str(p_str, &s_tmp_str); + str_append_char(p_str, ' '); + /* Size in bytes */ + str_alloc_filesize_t(&s_tmp_str, size); + str_lpad(&s_tmp_str, 8); + str_append_str(p_str, &s_tmp_str); + str_append_char(p_str, ' '); + /* Date stamp */ + str_append_text(p_str, vsf_sysutil_statbuf_get_date(p_stat, + tunable_use_localtime)); + str_append_char(p_str, ' '); + /* Filename */ + str_append_str(p_str, p_filename_str); + str_append_text(p_str, "\r\n"); +} + diff --git a/ls.h b/ls.h new file mode 100644 index 0000000..99ad981 --- /dev/null +++ b/ls.h @@ -0,0 +1,45 @@ +#ifndef VSF_LS_H +#define VSF_LS_H + +struct mystr; +struct mystr_list; +struct vsf_sysutil_dir; + +/* vsf_ls_populate_dir_list() + * PURPOSE + * Given a directory handle, populate a formatted directory entry list (/bin/ls + * format). Also optionally populate a list of subdirectories. + * PARAMETERS + * p_list - the string list object for the result list of entries + * p_subdir_list - the string list object for the result list of + * subdirectories. May be 0 if client is not interested. + * p_dir - the directory object to be listed + * p_base_dir_str - the directory name we are listing, relative to current + * p_option_str - the string of options given to the LIST/NLST command + * p_filter_str - the filter string given to LIST/NLST - e.g. "*.mp3" + * is_verbose - set to 1 for LIST, 0 for NLST + */ +void vsf_ls_populate_dir_list(struct mystr_list* p_list, + struct mystr_list* p_subdir_list, + struct vsf_sysutil_dir* p_dir, + const struct mystr* p_base_dir_str, + const struct mystr* p_option_str, + const struct mystr* p_filter_str, + int is_verbose); + +/* vsf_filename_passes_filter() + * PURPOSE + * Determine whether the given filename is matched by the given filter string. + * The format of the filter string is a small subset of a regular expression. + * Currently, just * and ? are supported. + * PARAMETERS + * p_filename_str - the filename to match + * p_filter_str - the filter to match against + * RETURNS + * Returns 1 if there is a match, 0 otherwise. + */ +int vsf_filename_passes_filter(const struct mystr* p_filename_str, + const struct mystr* p_filter_str); + +#endif /* VSF_LS_H */ + diff --git a/main.c b/main.c new file mode 100644 index 0000000..c8e6f5e --- /dev/null +++ b/main.c @@ -0,0 +1,341 @@ +/* + * Part of Very Secure FTPd + * Licence: GPL v2 + * Author: Chris Evans + * main.c + */ + +#include "session.h" +#include "utility.h" +#include "tunables.h" +#include "logging.h" +#include "str.h" +#include "filestr.h" +#include "ftpcmdio.h" +#include "sysutil.h" +#include "sysdeputil.h" +#include "defs.h" +#include "parseconf.h" +#include "oneprocess.h" +#include "twoprocess.h" +#include "standalone.h" +#include "tcpwrap.h" +#include "vsftpver.h" +#include "ssl.h" +#include "plugin_architecture_interface.h" + +/* + * Forward decls of helper functions + */ +static void die_unless_privileged(void); +static void do_sanity_checks(void); +static void session_init(struct vsf_session* p_sess); +static void env_init(void); + +int +main(int argc, const char* argv[]) +{ + struct vsf_session the_session = + { + /* Control connection */ + 0, 0, 0, + /* Data connection */ + -1, 0, -1, 0, 0, 0, 0, + /* Login */ + 1, INIT_MYSTR, INIT_MYSTR, INIT_MYSTR, + /* Protocol state */ + 0, 1, INIT_MYSTR, 0, 0, + /* Session state */ + 0, + /* Userids */ + -1, -1, -1, + /* Pre-chroot() cache */ + INIT_MYSTR, INIT_MYSTR, INIT_MYSTR, INIT_MYSTR, 1, + /* Logging */ + -1, -1, INIT_MYSTR, 0, 0, 0, INIT_MYSTR, 0, + /* Buffers */ + INIT_MYSTR, INIT_MYSTR, + /* Parent <-> child comms */ + -1, -1, + /* Number of clients */ + 0, 0, + /* Home directory */ + INIT_MYSTR, + /* Secure connection state */ + 0, 0, 0, 0, 0, 0, -1, -1, + /* Login fails */ + 0 + }; + int config_specified = 0; + const char* p_config_name = VSFTP_DEFAULT_CONFIG; + /* Zero or one argument supported. If one argument is passed, it is the + * path to the config file + */ + if (argc > 2) + { + die("vsftpd: too many arguments (I take an optional config file only)"); + } + else if (argc == 0) + { + die("vsftpd: missing argv[0]"); + } + if (argc == 2) + { + if (!vsf_sysutil_strcmp(argv[1], "-v")) + { + vsf_exit("vsftpd: version " VSF_VERSION "\n"); + } + p_config_name = argv[1]; + config_specified = 1; + } + + /* This might need to open /dev/zero on systems lacking MAP_ANON. Needs + * to be done early (i.e. before config file parse, which may use + * anonymous pages + */ + vsf_sysutil_map_anon_pages_init(); + /* Parse config file if it's there */ + { + struct vsf_sysutil_statbuf* p_statbuf = 0; + int retval = vsf_sysutil_stat(p_config_name, &p_statbuf); + if (!vsf_sysutil_retval_is_error(retval)) + { + vsf_parseconf_load_file(p_config_name, 1); + } + else if (config_specified) + { + die2("vsftpd: cannot open config file:", p_config_name); + } + vsf_sysutil_free(p_statbuf); + } + /* Resolve pasv_address if required */ + if (tunable_pasv_address && tunable_pasv_addr_resolve) + { + struct vsf_sysutil_sockaddr* p_addr = 0; + const char* p_numeric_addr; + vsf_sysutil_dns_resolve(&p_addr, tunable_pasv_address); + vsf_sysutil_free((char*) tunable_pasv_address); + p_numeric_addr = vsf_sysutil_inet_ntop(p_addr); + tunable_pasv_address = vsf_sysutil_strdup(p_numeric_addr); + vsf_sysutil_free(p_addr); + } + if (!tunable_run_as_launching_user) + { + /* Just get out unless we start with requisite privilege */ + die_unless_privileged(); + } + + if (tunable_setproctitle_enable) + { + /* Warning -- warning -- may nuke argv, environ */ + vsf_sysutil_setproctitle_init(argc, argv); + } + /* Initialize the SSL system here if needed - saves the overhead of each + * child doing this itself. + */ + if (tunable_ssl_enable) + { + ssl_init(&the_session); + } + if (tunable_plugin_architecture) + { + vsf_plugin_architecture_start(&the_session); + } + if (tunable_listen || tunable_listen_ipv6) + { + /* Standalone mode */ + struct vsf_client_launch ret = vsf_standalone_main(); + the_session.num_clients = ret.num_children; + the_session.num_this_ip = ret.num_this_ip; + } + if (tunable_tcp_wrappers) + { + the_session.tcp_wrapper_ok = vsf_tcp_wrapper_ok(VSFTP_COMMAND_FD); + } + { + const char* p_load_conf = vsf_sysutil_getenv("VSFTPD_LOAD_CONF"); + if (p_load_conf) + { + vsf_parseconf_load_file(p_load_conf, 1); + } + } + /* Sanity checks - exit with a graceful error message if our STDIN is not + * a socket. Also check various config options don't collide. + */ + do_sanity_checks(); + /* Initializes session globals - e.g. IP addr's etc. */ + session_init(&the_session); + /* Set up "environment", e.g. process group etc. */ + env_init(); + /* Set up logging - must come after global init because we need the remote + * address to convert into text + */ + vsf_log_init(&the_session); + str_alloc_text(&the_session.remote_ip_str, + vsf_sysutil_inet_ntop(the_session.p_remote_addr)); + /* Set up options on the command socket */ + vsf_cmdio_sock_setup(); + if (tunable_setproctitle_enable) + { + vsf_sysutil_set_proctitle_prefix(&the_session.remote_ip_str); + vsf_sysutil_setproctitle("connected"); + } + /* We might chroot() very soon (one process model), so we need to open + * any required config files here. + */ + /* SSL may have been enabled by a per-IP configuration.. */ + if (tunable_ssl_enable) + { + ssl_init(&the_session); + } + if (tunable_deny_email_enable) + { + int retval = str_fileread(&the_session.banned_email_str, + tunable_banned_email_file, VSFTP_CONF_FILE_MAX); + if (vsf_sysutil_retval_is_error(retval)) + { + die2("cannot open anon e-mail list file:", tunable_banned_email_file); + } + } + if (tunable_banner_file) + { + int retval = str_fileread(&the_session.banner_str, tunable_banner_file, + VSFTP_CONF_FILE_MAX); + if (vsf_sysutil_retval_is_error(retval)) + { + die2("cannot open banner file:", tunable_banner_file); + } + } + if (tunable_secure_email_list_enable) + { + int retval = str_fileread(&the_session.email_passwords_str, + tunable_email_password_file, + VSFTP_CONF_FILE_MAX); + if (vsf_sysutil_retval_is_error(retval)) + { + die2("cannot open email passwords file:", tunable_email_password_file); + } + } + /* Special case - can force one process model if we've got a setup + * needing _no_ privs + */ + if (!tunable_local_enable && !tunable_connect_from_port_20 && + !tunable_chown_uploads) + { + tunable_one_process_model = 1; + } + if (tunable_run_as_launching_user) + { + tunable_one_process_model = 1; + if (!vsf_sysutil_running_as_root()) + { + tunable_connect_from_port_20 = 0; + tunable_chown_uploads = 0; + } + } + + if (tunable_one_process_model) + { + vsf_one_process_start(&the_session); + } + else + { + vsf_two_process_start(&the_session); + } + /* NOTREACHED */ + bug("should not get here: main"); + return 1; +} + +static void +die_unless_privileged(void) +{ + if (!vsf_sysutil_running_as_root()) + { + die("vsftpd: must be started as root (see run_as_launching_user option)"); + } +} + +static void +do_sanity_checks(void) +{ + { + struct vsf_sysutil_statbuf* p_statbuf = 0; + vsf_sysutil_fstat(VSFTP_COMMAND_FD, &p_statbuf); + if (!vsf_sysutil_statbuf_is_socket(p_statbuf)) + { + die("vsftpd: not configured for standalone, must be started from inetd"); + } + vsf_sysutil_free(p_statbuf); + } + if (tunable_one_process_model) + { + if (tunable_local_enable) + { + die("vsftpd: security: 'one_process_model' is anonymous only"); + } + if (!vsf_sysdep_has_capabilities_as_non_root()) + { + die("vsftpd: security: 'one_process_model' needs a better OS"); + } + } + if (!tunable_local_enable && !tunable_anonymous_enable) + { + die("vsftpd: both local and anonymous access disabled!"); + } +} + +static void +env_init(void) +{ + vsf_sysutil_make_session_leader(); + /* Set up a secure umask - we'll set the proper one after login */ + vsf_sysutil_set_umask(VSFTP_SECURE_UMASK); + /* Fire up libc's timezone initialisation, before we chroot()! */ + vsf_sysutil_tzset(); + /* Signals. We'll always take -EPIPE rather than a rude signal, thanks */ + vsf_sysutil_install_null_sighandler(kVSFSysUtilSigPIPE); +} + +static void +session_init(struct vsf_session* p_sess) +{ + /* Get the addresses of the control connection */ + vsf_sysutil_getpeername(VSFTP_COMMAND_FD, &p_sess->p_remote_addr); + vsf_sysutil_getsockname(VSFTP_COMMAND_FD, &p_sess->p_local_addr); + /* If anonymous mode is active, fetch the uid of the anonymous user */ + if (tunable_anonymous_enable) + { + const struct vsf_sysutil_user* p_user = + vsf_sysutil_getpwnam(tunable_ftp_username); + if (p_user == 0) + { + die2("vsftpd: cannot locate user specified in 'ftp_username':", + tunable_ftp_username); + } + p_sess->anon_ftp_uid = vsf_sysutil_user_getuid(p_user); + } + if (tunable_guest_enable) + { + const struct vsf_sysutil_user* p_user = + vsf_sysutil_getpwnam(tunable_guest_username); + if (p_user == 0) + { + die2("vsftpd: cannot locate user specified in 'guest_username':", + tunable_guest_username); + } + p_sess->guest_user_uid = vsf_sysutil_user_getuid(p_user); + } + if (tunable_chown_uploads) + { + const struct vsf_sysutil_user* p_user = + vsf_sysutil_getpwnam(tunable_chown_username); + if (p_user == 0) + { + die2("vsftpd: cannot locate user specified in 'chown_username':", + tunable_chown_username); + } + p_sess->anon_upload_chown_uid = vsf_sysutil_user_getuid(p_user); + } +} + diff --git a/mysql_plugin.cpp b/mysql_plugin.cpp new file mode 100644 index 0000000..3ba51cf --- /dev/null +++ b/mysql_plugin.cpp @@ -0,0 +1,1420 @@ +#include "mysql_plugin.h" +#warning the following code is readable only by compilers, trying to understand it may cause permanent brain damage +using namespace std; + +MyDatabaseConnection::MyDatabaseConnection() +{ +_result=0; +int error_num; +_dbconn=mysql_init(NULL); +mysql_options(_dbconn,MYSQL_READ_DEFAULT_GROUP,"vsftp"); +printf("MYSQL: connektink \n"); + if(!mysql_real_connect(_dbconn,tunable_mysql_database_host, tunable_mysql_database_username, + tunable_mysql_database_password, tunable_mysql_database_dbname, + tunable_mysql_database_port, tunable_mysql_database_socketname, + tunable_mysql_database_connectionflags)) + { + error_num=mysql_errno(_dbconn); + switch(error_num) + { + case CR_CONN_HOST_ERROR: + throw MySQLConnHostError(); + break; + case CR_CONNECTION_ERROR: + throw MySQLConnectionError(); + break; + case CR_IPSOCK_ERROR: + throw MySQLIpsockError(); + break; + case CR_OUT_OF_MEMORY: + throw MySQLOutOfMemory(); + break; + case CR_SOCKET_CREATE_ERROR: + throw MySQLSocketCreateError(); + break; + case CR_UNKNOWN_HOST: + throw MySQLUnknownHost(); + break; + case CR_VERSION_ERROR: + throw MySQLVersionError(); + break; + case CR_NAMEDPIPEOPEN_ERROR: + throw MySQLNamedpipeopenError(); + break; + case CR_NAMEDPIPEWAIT_ERROR: + throw MySQLNamedpipewaitError(); + break; + case CR_NAMEDPIPESETSTATE_ERROR: + throw MySQLNamedpipesetstateError(); + break; + case CR_SERVER_LOST: + throw MySQLServerLost(); + break; + default: + throw MySQLUnknown(); + break; + } + } +} + +MyDatabaseConnection::~MyDatabaseConnection() +{ + mysql_close(_dbconn); +} + +MYSQL_RES * MyDatabaseConnection::query(const string query) +{ + int error_num; + printf("MYSQL query: %s\n",query.c_str()); + if(mysql_real_query(_dbconn,query.c_str(),query.length())!=0) + { + error_num=mysql_errno(_dbconn); + switch(error_num) + { + case CR_COMMANDS_OUT_OF_SYNC: + throw MySQLCommandsOutOfSync(); + break; + case CR_SERVER_GONE_ERROR: + throw MySQLServerGoneError(); + break; + case CR_SERVER_LOST: + throw MySQLServerLost(); + break; + case CR_UNKNOWN_ERROR: + throw MySQLUnknown(); + break; + default: + printf("MYSQL Unhandled Error %d : %s\n", mysql_errno(_dbconn), mysql_error(_dbconn)); + } + printf("MYSQL Error %d : %s\n", mysql_errno(_dbconn), mysql_error(_dbconn)); + return 0; + } + _result = mysql_store_result(_dbconn); + return _result; +} + +string VSFMySQLPlugin::sanitize(const string & original) +{ + stringstream retval; + char * escaped; + escaped = new char[2*original.length() + 1]; + mysql_real_escape_string(dbcon->getConnection(), escaped,original.c_str(),original.length()); + retval << escaped; + delete [] escaped; + return retval.str(); +} +/* +void VSFMySQLPlugin::connectDB(MYSQL * & connection) +{ +int error_num; +connection=mysql_init(NULL); +mysql_options(connection,MYSQL_READ_DEFAULT_GROUP,"vsftp"); +printf("VSMYSQL: connektink %s\n", getTimeStamp().c_str()); + if(!mysql_real_connect(connection,tunable_mysql_database_host, tunable_mysql_database_username, + tunable_mysql_database_password, tunable_mysql_database_dbname, + tunable_mysql_database_port, tunable_mysql_database_socketname, + tunable_mysql_database_connectionflags)) + { + error_num=mysql_errno(connection); + switch(error_num) + { + case CR_CONN_HOST_ERROR: + throw MySQLConnHostError(); + break; + case CR_CONNECTION_ERROR: + throw MySQLConnectionError(); + break; + case CR_IPSOCK_ERROR: + throw MySQLIpsockError(); + break; + case CR_OUT_OF_MEMORY: + throw MySQLOutOfMemory(); + break; + case CR_SOCKET_CREATE_ERROR: + throw MySQLSocketCreateError(); + break; + case CR_UNKNOWN_HOST: + throw MySQLUnknownHost(); + break; + case CR_VERSION_ERROR: + throw MySQLVersionError(); + break; + case CR_NAMEDPIPEOPEN_ERROR: + throw MySQLNamedpipeopenError(); + break; + case CR_NAMEDPIPEWAIT_ERROR: + throw MySQLNamedpipewaitError(); + break; + case CR_NAMEDPIPESETSTATE_ERROR: + throw MySQLNamedpipesetstateError(); + break; + case CR_SERVER_LOST: + throw MySQLServerLost(); + break; + default: + throw MySQLUnknown(); + break; + } + } +} +//*/ +string VSFMySQLPlugin::sanitize(const char * original) +{ + stringstream retval; + char * escaped; + escaped = new char[2*strlen(original) + 1]; + mysql_real_escape_string(dbcon->getConnection(), escaped,original,strlen(original)); + retval << escaped; + delete [] escaped; + return retval.str(); +} +string VSFMySQLPlugin::unslash(const string & original) +{ + return unslash(original.c_str()); +} +string VSFMySQLPlugin::unslash(const char * original) +{ + char * escaped; + stringstream retval; + if(strlen(original) <= 1) + { + retval << original; + return retval.str(); + } + escaped = strdup(original); + if(strlen(escaped)>1 && escaped[strlen(escaped)-1]=='/') + { + escaped[strlen(escaped)-1]=0; + } + retval << escaped; + if(escaped) + { + free(escaped); + } + return retval.str(); +} + + +long long int VSFMySQLPlugin::getEid(const struct Event_Shared sharedInfo) +{ + MyDatabaseConnection mycon; + long long int eid; + stringstream query; + MYSQL_RES * result; + long long int uid; + uid =getUid(sharedInfo.user_name); + if(uid==-1) { + query << "SELECT `eid` FROM `event` WHERE `uid` IS NULL AND `timestamp` = '" << getTimeStamp(sharedInfo.timestamp) << "' AND `ip_address` = '" << getIP(sharedInfo.remote_ip) << "'"; + } else { + query << "SELECT `eid` FROM `event` WHERE `uid` = " << uid << " AND `timestamp` = '" << getTimeStamp(sharedInfo.timestamp) << "' AND `ip_address` = '" << getIP(sharedInfo.remote_ip) << "'"; + } + + result = mycon.query(query.str()); + if((result==0)||(mysql_num_rows(result)!=1)) + { + mysql_free_result(result); + printf("MYSQL:TOO MANY SIMILAR EVENTS!\n"); + return -1; + } + else + { + MYSQL_ROW row; + row = mysql_fetch_row(result); + sscanf(row[0],"%lld",&eid); + mysql_free_result(result); + return eid; + } +} + +string VSFMySQLPlugin::getTimeStamp() +{ + struct tm tstamp; + time_t rawtime; + stringstream tsstream; + + time ( &rawtime ); + gmtime_r(&rawtime,&tstamp); + + + tsstream << setw(4) << right << setfill('0') << 1900+tstamp.tm_year << "-" << + setw(2) << right << setfill('0') << tstamp.tm_mon+1 << "-" + << setw(2) << right << setfill('0') << tstamp.tm_mday << " " << + setw(2) << right << setfill('0') << tstamp.tm_hour << ":" << + setw(2) << right << setfill('0') << tstamp.tm_min << ":" << + setw(2) << right << setfill('0') << tstamp.tm_sec; + + return tsstream.str(); +} + +long long int VSFMySQLPlugin::getUid(string username) +{ + MyDatabaseConnection mycon; + long long int uid; + MYSQL_RES * result; + stringstream querystream; + + querystream << "SELECT `uid` FROM `users` WHERE `name` = '" << sanitize(username) << "'"; + + + result = mycon.query(querystream.str()); + if((result==0)||(mysql_num_rows(result)!=1)) + { + mysql_free_result(result); + printf("VSMYSQL:NO SUCH USER!\n"); + return -1; + } + else + { + MYSQL_ROW row; + row = mysql_fetch_row(result); + sscanf(row[0],"%lld",&uid); + mysql_free_result(result); + return uid; + } +} + +string VSFMySQLPlugin::getIP(const struct sockaddr_in& sa_in ) +{ + char * ipaddr; + ipaddr=new char[16]; + if(sa_in.sin_family==AF_INET) + { + inet_ntop(sa_in.sin_family,&(sa_in.sin_addr),ipaddr,16); + } + else + { + printf("VSFMYSQL: vsftp-database does not support IPv6, but it's an easy feature to add yourself :)\n"); + } + string ipstring; + ipstring.assign(ipaddr); + return ipstring; +} + +string VSFMySQLPlugin::getTimeStamp(struct timeval tval) +{ + struct tm tstamp; + stringstream tsstream; + + gmtime_r(&(tval.tv_sec),&tstamp); + + tsstream << setw(4) << right << setfill('0') << 1900+tstamp.tm_year << "-" << + setw(2) << right << setfill('0') << tstamp.tm_mon+1 << "-" + << setw(2) << right << setfill('0') << tstamp.tm_mday << " " << + setw(2) << right << setfill('0') << tstamp.tm_hour << ":" << + setw(2) << right << setfill('0') << tstamp.tm_min << ":" << + setw(2) << right << setfill('0') << tstamp.tm_sec; + return tsstream.str(); +} +/* +MYSQL_RES * VSFMySQLPlugin::queryDB(const string query) +{ + return queryDB(query,_dbconn); +} + +MYSQL_RES * VSFMySQLPlugin::queryDB(const string query, MYSQL * connection) +{ + int error_num; + printf("%s\n",query.c_str()); + if(mysql_real_query(connection,query.c_str(),query.length())!=0) + { + error_num=mysql_errno(connection); + switch(error_num) + { + case CR_COMMANDS_OUT_OF_SYNC: + throw MySQLCommandsOutOfSync(); + break; + case CR_SERVER_GONE_ERROR: + throw MySQLServerGoneError(); + break; + case CR_SERVER_LOST: + throw MySQLServerLost(); + break; + case CR_UNKNOWN_ERROR: + throw MySQLUnknown(); + break; + default: + cout << "QS:" << query << ":QE" << endl; + printf("EVEN WORSE %d\n!",error_num); + } + printf("Error %d : %s\n", mysql_errno(connection), mysql_error(connection)); + return 0; + } + return mysql_store_result(connection); +} +//*/ +VSFMySQLPlugin::VSFMySQLPlugin(VSFPluginArchitecture & arch) : VSFPlugin(arch), PlArch(arch) +{ + stringstream querystream; + //init variables; + commit.assign("COMMIT"); +//init connection to the database and check if the tables exist + + arch.registerRequestHandler(*this, Request_Number_Login); + arch.registerRequestHandler(*this, Request_Number_ReadFile); + arch.registerRequestHandler(*this, Request_Number_ReadDirectory); + arch.registerRequestHandler(*this, Request_Number_WriteFile); + arch.registerRequestHandler(*this, Request_Number_WriteDirectory); + arch.registerRequestHandler(*this, Request_Number_Homedir); + + arch.registerEventHandler(*this, Event_Number_Chmod); + arch.registerEventHandler(*this, Event_Number_Connection); + arch.registerEventHandler(*this, Event_Number_Delete); + arch.registerEventHandler(*this, Event_Number_Download); + arch.registerEventHandler(*this, Event_Number_FailedLogin); + arch.registerEventHandler(*this, Event_Number_Login); + arch.registerEventHandler(*this, Event_Number_Mkdir); + arch.registerEventHandler(*this, Event_Number_Rename); + arch.registerEventHandler(*this, Event_Number_Rmdir); + arch.registerEventHandler(*this, Event_Number_Upload); + arch.registerEventHandler(*this, Event_Number_CliEnd); + //init connection handler +/* + //VS _dbconn=mysql_init(NULL); + + //connect to the DB + //VS mysql_options(_dbconn,MYSQL_READ_DEFAULT_GROUP,"vsftp"); +printf("VSMYSQL: connektink %s\n", getTimeStamp().c_str()); + if(!mysql_real_connect(_dbconn,tunable_mysql_database_host, tunable_mysql_database_username, + tunable_mysql_database_password, tunable_mysql_database_dbname, + tunable_mysql_database_port, tunable_mysql_database_socketname, + tunable_mysql_database_connectionflags)) + { + error_num=mysql_errno(_dbconn); + switch(error_num) + { + case CR_CONN_HOST_ERROR: + throw MySQLConnHostError(); + break; + case CR_CONNECTION_ERROR: + throw MySQLConnectionError(); + break; + case CR_IPSOCK_ERROR: + throw MySQLIpsockError(); + break; + case CR_OUT_OF_MEMORY: + throw MySQLOutOfMemory(); + break; + case CR_SOCKET_CREATE_ERROR: + throw MySQLSocketCreateError(); + break; + case CR_UNKNOWN_HOST: + throw MySQLUnknownHost(); + break; + case CR_VERSION_ERROR: + throw MySQLVersionError(); + break; + case CR_NAMEDPIPEOPEN_ERROR: + throw MySQLNamedpipeopenError(); + break; + case CR_NAMEDPIPEWAIT_ERROR: + throw MySQLNamedpipewaitError(); + break; + case CR_NAMEDPIPESETSTATE_ERROR: + throw MySQLNamedpipesetstateError(); + break; + case CR_SERVER_LOST: + throw MySQLServerLost(); + break; + default: + throw MySQLUnknown(); + break; + } + } +//*/ + dbcon=new MyDatabaseConnection; +//connected to the database or exceptioned out + +//add server startup to logs + querystream << "INSERT INTO `event` (`eid`, `uid`, `timestamp`, `ip_address`, `event_type`) VALUES (DEFAULT, NULL, '" << getTimeStamp() << "', '', 'Server Startup')"; + dbcon->query(querystream.str()); + dbcon->query(commit); +//check if folders exist on HDD +} + +VSFMySQLPlugin::~VSFMySQLPlugin() +{ +stringstream querystream; + +querystream << "INSERT INTO `event` (`eid`, `uid`, `timestamp`, `ip_address`, `event_type`) VALUES (DEFAULT, NULL, '" << getTimeStamp() << "','','Server Shutdown')"; + + dbcon->query(querystream.str()); + dbcon->query(commit); + //disconnect from database + delete dbcon; + //free all allocated space + + PlArch.unregisterEventHandler(*this, Event_Number_Chmod); + PlArch.unregisterEventHandler(*this, Event_Number_Connection); + PlArch.unregisterEventHandler(*this, Event_Number_Delete); + PlArch.unregisterEventHandler(*this, Event_Number_Download); + PlArch.unregisterEventHandler(*this, Event_Number_FailedLogin); + PlArch.unregisterEventHandler(*this, Event_Number_Login); + PlArch.unregisterEventHandler(*this, Event_Number_Mkdir); + PlArch.unregisterEventHandler(*this, Event_Number_Rename); + PlArch.unregisterEventHandler(*this, Event_Number_Rmdir); + PlArch.unregisterEventHandler(*this, Event_Number_Upload); + PlArch.unregisterEventHandler(*this, Event_Number_CliEnd); + + PlArch.unregisterRequestHandler(*this, Request_Number_Login); + PlArch.unregisterRequestHandler(*this, Request_Number_ReadFile); + PlArch.unregisterRequestHandler(*this, Request_Number_ReadDirectory); + PlArch.unregisterRequestHandler(*this, Request_Number_WriteFile); + PlArch.unregisterRequestHandler(*this, Request_Number_WriteDirectory); + PlArch.unregisterRequestHandler(*this, Request_Number_Homedir); +} + +void VSFMySQLPlugin::handleEvent(const EventChmod & event) +{ + int file_true; + int improvise; + MyDatabaseConnection mycon; +MYSQL_RES * result; +MYSQL_ROW row; +long long int ourfid; + + stringstream querystream; + int old_owner_write,old_owner_read,new_owner_write,new_owner_read; + int old_group_write,old_group_read,new_group_write,new_group_read; + int old_others_write,old_others_read,new_others_write,new_others_read; + long long int uid; + + file_true=0; + improvise=0; + querystream.str(""); + querystream << "SELECT `owner_read`, `owner_write`, `group_read`, `group_write`, `others_read`, `others_write` FROM `directories` WHERE `path` = '" << sanitize(event.getData()->path) << sanitize(event.getData()->name) << "'"; + result = mycon.query(querystream.str()); + + if((result==0)||(mysql_num_rows(result)!=1)) + { + querystream.str(""); + querystream << "SELECT f.`owner_read`, f.`owner_write`, f.`group_read`, f.`group_write`, f.`others_read`, f.`others_write` FROM `files` f JOIN `directories` d ON d.`did` = f.`directory` WHERE f.`name` = '" << sanitize(event.getData()->name) << "' AND d.`path` = '" << unslash(sanitize(event.getData()->path)) << "'"; + result = mycon.query(querystream.str()); + + file_true=1; + if((result==0)||(mysql_num_rows(result)!=1)) + { + improvise=1; + } + } + + + if(improvise==0) + { + row = mysql_fetch_row(result); + } + //changing from the octal representation to the separate bit's representation + new_owner_write=0; + new_owner_read=0; + new_group_write=0; + new_group_read=0; + new_others_write=0; + new_others_read=0; + + if(((event.getData()->new_attrib[0]) & 0x2)>0 ) + { + new_owner_write=1; + } + if(((event.getData()->new_attrib[0]) & 0x4)>0 ) + { + new_owner_read=1; + } + if(((event.getData()->new_attrib[1]) & 0x2)>0 ) + { + new_group_write=1; + } + if(((event.getData()->new_attrib[1]) & 0x4)>0 ) + { + new_group_read=1; + } + if(((event.getData()->new_attrib[2]) & 0x2)>0 ) + { + new_others_write=1; + } + if(((event.getData()->new_attrib[2]) & 0x4)>0 ) + { + new_others_read=1; + } + //interpreting selected data + if(improvise==0) + { + old_owner_read=(strcmp(row[0],"0")==0)?0:1; + old_owner_write=(strcmp(row[1],"0")==0)?0:1; + old_group_read=(strcmp(row[2],"0")==0)?0:1; + old_group_write=(strcmp(row[3],"0")==0)?0:1; + old_others_read=(strcmp(row[4],"0")==0)?0:1; + old_others_write=(strcmp(row[5],"0")==0)?0:1; + } + else + { + old_owner_read=0; + old_owner_write=0; + old_group_read=0; + old_group_write=0; + old_others_read=0; + old_others_write=0; + } + //basic inserting + querystream.str(""); + uid=getUid(event.getData()->sharedInfo.user_name); + if(uid==-1) { + querystream << "INSERT INTO `event` (`eid`,`uid`,`timestamp`,`ip_address`,`event_type`) VALUES (DEFAULT, NULL,'" << getTimeStamp(event.getData()->sharedInfo.timestamp) << "','" << getIP(event.getData()->sharedInfo.remote_ip) << "', 'Chmod')"; + } else { + querystream << "INSERT INTO `event` (`eid`,`uid`,`timestamp`,`ip_address`,`event_type`) VALUES (DEFAULT, " << uid << ",'" << getTimeStamp(event.getData()->sharedInfo.timestamp) << "','" << getIP(event.getData()->sharedInfo.remote_ip) << "', 'Chmod')"; + } + mycon.query(querystream.str()); + mycon.query(commit); + + querystream.str(""); + querystream << "INSERT INTO `event_chmod` (`eid`,`path`,`old_owner_write`,`old_owner_read`,`old_group_write`,`old_group_read`,`old_others_write`,`old_others_read`,`new_owner_write`,`new_owner_read`,`new_group_write`,`new_group_read`,`new_others_write`,`new_others_read`,`succeeded`) VALUES (" << getEid(event.getData()->sharedInfo) << ", '" << sanitize(event.getData()->path) << sanitize(event.getData()->name) << "', " << old_owner_write << ", " << old_owner_read << ", " << old_group_write << ", " << old_group_read << ", " << old_others_write << ", " << old_others_read << ", " << new_owner_write << ", " << new_owner_read << ", " << new_group_write << ", " << new_group_read << ", " << new_others_write << ", " << new_others_read << ", " << event.getData()->succeeded << ")"; + + mycon.query(querystream.str()); + mycon.query(commit); + + //insert to database + if(improvise==1) + { + return; + } + querystream.str(""); + querystream << "SELECT `fid` FROM `files` f JOIN `directories` d ON f.`directory` = d.`did` WHERE f.`name` = '" + << sanitize(event.getData()->name) << "' AND d.`path` = '" << unslash(sanitize(event.getData()->path)) << + "' "; + result=mycon.query(querystream.str()); + if((result==0)||(mysql_num_rows(result)!=1)) + { + //throw MySQLFileEntryNotFound; + querystream.str(""); + querystream << "SELECT `did` FROM `directories` WHERE `path` = '" << sanitize(event.getData()->path) << sanitize(event.getData()->name) << "' "; + result=mycon.query(querystream.str()); + if((result==0)||(mysql_num_rows(result)!=1)) + { + printf("MYSQL: file or dir for chmod not found in database\n"); + mysql_free_result(result); + return; + } + row = mysql_fetch_row(result); + sscanf(row[0],"%lld",&ourfid); + querystream.str(""); + //modifying dir + querystream << "UPDATE `directories` SET `owner_read` = "<< new_owner_read << " , `owner_write` = " << + new_owner_write << " , `group_read` = "<< new_group_read << " , `group_write` = "<< new_group_write << + " , `others_read` = " << new_others_read << " , `others_write` = "<< new_others_write << + " WHERE `did` = " << ourfid << " "; + mycon.query(querystream.str()); + mycon.query(commit); + return; + } + row = mysql_fetch_row(result); + sscanf(row[0],"%lld",&ourfid); + querystream.str(""); + //modifying file + querystream << "UPDATE `files` SET `owner_read` = "<< new_owner_read << " , `owner_write` = " << + new_owner_write << " , `group_read` = " << new_group_read << " , `group_write` = " << new_group_write << + " , `others_read` = " << new_others_read << " , `others_write` = " << new_others_write << + " WHERE `fid` = " << ourfid << " "; + mycon.query(querystream.str()); + mycon.query(commit); +} + +void VSFMySQLPlugin::handleEvent(const EventConnection & event) +{ +printf("MYSQL**** : Event Connection!\n"); +MyDatabaseConnection mycon; +stringstream querystream; +long long int uid; + uid = getUid(event.getData()->sharedInfo.user_name); + if(uid==-1) { + querystream << "INSERT INTO `event` (`eid`,`uid`,`timestamp`,`ip_address`,`event_type`) VALUES (DEFAULT, NULL,'" << getTimeStamp(event.getData()->sharedInfo.timestamp) << "','" << getIP(event.getData()->sharedInfo.remote_ip) << "', 'Connection')"; + } else { + querystream << "INSERT INTO `event` (`eid`,`uid`,`timestamp`,`ip_address`,`event_type`) VALUES (DEFAULT, " << uid << ",'" << getTimeStamp(event.getData()->sharedInfo.timestamp) << "','" << getIP(event.getData()->sharedInfo.remote_ip) << "', 'Connection')"; + } + mycon.query(querystream.str()); + mycon.query(commit); + + querystream.str(""); + querystream << "INSERT INTO `event_connection` (`eid`,`reason`) VALUES (" << getEid(event.getData()->sharedInfo) << ", '" << sanitize(event.getData()->reason) << "')"; + + mycon.query(querystream.str()); + mycon.query(commit); +} + +void VSFMySQLPlugin::handleEvent(const EventDelete & event) +{ + MyDatabaseConnection mycon; +stringstream querystream; +long long int uid; + uid = getUid(event.getData()->sharedInfo.user_name); + if(uid==-1) { + querystream << "INSERT INTO `event` (`eid`,`uid`,`timestamp`,`ip_address`,`event_type`) VALUES (DEFAULT, NULL,'" << getTimeStamp(event.getData()->sharedInfo.timestamp) << "','" << getIP(event.getData()->sharedInfo.remote_ip) << "', 'Delete')"; + } else { + querystream << "INSERT INTO `event` (`eid`,`uid`,`timestamp`,`ip_address`,`event_type`) VALUES (DEFAULT, " << uid << ",'" << getTimeStamp(event.getData()->sharedInfo.timestamp) << "','" << getIP(event.getData()->sharedInfo.remote_ip) << "', 'Delete')"; + } + mycon.query(querystream.str()); + mycon.query(commit); + //deleting the entry from the database (if succeded) + if(event.getData()->succeeded == 0) + { + std::pair filnam; + filnam = VSFUtils::sanitizePath(event.getData()->path,"" ); + querystream.str(""); + querystream << "DELETE FROM `files` WHERE `fid`=(SELECT `fid` FROM `files` f JOIN `directories` d ON f.`directory` = d.`did` WHERE f.`name` = '" << sanitize(filnam.second) << "' AND d.`path` = '" + << unslash(sanitize(filnam.first)) << "' LIMIT 1 )"; + mycon.query(querystream.str()); + mycon.query(commit); + } + querystream.str(""); + querystream << "INSERT INTO `event_delete` (`eid`,`path`,`succeeded`) VALUES (" << getEid(event.getData()->sharedInfo) << ", '" << sanitize(event.getData()->path) << "', '"<< event.getData()->succeeded << "')"; + + mycon.query(querystream.str()); + mycon.query(commit); + +} + +void VSFMySQLPlugin::handleEvent(const EventDownload & event) +{ +MyDatabaseConnection mycon; +stringstream querystream; +long long int uid; + uid = getUid(event.getData()->sharedInfo.user_name); + if(uid==-1) { + querystream << "INSERT INTO `event` (`eid`,`uid`,`timestamp`,`ip_address`,`event_type`) VALUES (DEFAULT, NULL,'" << getTimeStamp(event.getData()->sharedInfo.timestamp) << "','" << getIP(event.getData()->sharedInfo.remote_ip) << "', 'Download')"; + } else { + querystream << "INSERT INTO `event` (`eid`,`uid`,`timestamp`,`ip_address`,`event_type`) VALUES (DEFAULT, " << uid << ",'" << getTimeStamp(event.getData()->sharedInfo.timestamp) << "','" << getIP(event.getData()->sharedInfo.remote_ip) << "', 'Download')"; + } + mycon.query(querystream.str()); + mycon.query(commit); + querystream.str(""); + querystream << "INSERT INTO `event_download` (`eid`,`path`,`filename`,`size`,`is_complete`) VALUES (" << getEid(event.getData()->sharedInfo) << ", '" << unslash(sanitize(event.getData()->fileInfo.path)) << "', '" << sanitize(event.getData()->fileInfo.name) << "', " << event.getData()->fileInfo.size << ", " << event.getData()->is_complete << ")"; + + mycon.query(querystream.str()); + mycon.query(commit); +} + +void VSFMySQLPlugin::handleEvent(const EventFailedLogin & event) +{ +MyDatabaseConnection mycon; +stringstream querystream; +long long int uid; + uid =getUid(event.getData()->sharedInfo.user_name); + if(uid==-1) { + querystream << "INSERT INTO `event` (`eid`,`uid`,`timestamp`,`ip_address`,`event_type`) VALUES (DEFAULT, NULL,'" << getTimeStamp(event.getData()->sharedInfo.timestamp) << "','" << getIP(event.getData()->sharedInfo.remote_ip) << "', 'FailedLogin')"; + }else { + querystream << "INSERT INTO `event` (`eid`,`uid`,`timestamp`,`ip_address`,`event_type`) VALUES (DEFAULT, " << uid << ",'" << getTimeStamp(event.getData()->sharedInfo.timestamp) << "','" << getIP(event.getData()->sharedInfo.remote_ip) << "', 'FailedLogin')"; + } + mycon.query(querystream.str()); + mycon.query(commit); + querystream.str(""); + querystream << "INSERT INTO `event_failedlogin` (`eid`,`failed_password`) VALUES (" << getEid(event.getData()->sharedInfo) << ", '" << sanitize(event.getData()->failed_password) << "')"; + mycon.query(querystream.str()); + mycon.query(commit); +} + +void VSFMySQLPlugin::handleEvent(const EventLogin & event) +{ +MyDatabaseConnection mycon; +stringstream querystream; +long long int uid; + uid = getUid(event.getData()->sharedInfo.user_name); + if(uid==-1) { + querystream << "INSERT INTO `event` (`eid`,`uid`,`timestamp`,`ip_address`,`event_type`) VALUES (DEFAULT, NULL,'" << getTimeStamp(event.getData()->sharedInfo.timestamp) << "','" << getIP(event.getData()->sharedInfo.remote_ip) << "', 'Login')"; + } else { + querystream << "INSERT INTO `event` (`eid`,`uid`,`timestamp`,`ip_address`,`event_type`) VALUES (DEFAULT, " << uid << ",'" << getTimeStamp(event.getData()->sharedInfo.timestamp) << "','" << getIP(event.getData()->sharedInfo.remote_ip) << "', 'Login')"; + } + mycon.query(querystream.str()); + mycon.query(commit); + querystream.str(""); + querystream << "INSERT INTO `event_login` (`eid`,`is_anonymous`) VALUES (" << getEid(event.getData()->sharedInfo) << ", " << event.getData()->is_anonymous << ")"; + mycon.query(querystream.str()); + mycon.query(commit); +} + +void VSFMySQLPlugin::handleEvent(const EventMkdir & event) +{ +MyDatabaseConnection mycon; +stringstream querystream; +long long int uid; + uid = getUid(event.getData()->sharedInfo.user_name); + if(uid==-1) { + querystream << "INSERT INTO `event` (`eid`,`uid`,`timestamp`,`ip_address`,`event_type`) VALUES (DEFAULT, NULL,'" << getTimeStamp(event.getData()->sharedInfo.timestamp) << "','" << getIP(event.getData()->sharedInfo.remote_ip) << "', 'Mkdir')"; + } else { + querystream << "INSERT INTO `event` (`eid`,`uid`,`timestamp`,`ip_address`,`event_type`) VALUES (DEFAULT," << uid <<",'" << getTimeStamp(event.getData()->sharedInfo.timestamp) << "','" << getIP(event.getData()->sharedInfo.remote_ip) << "', 'Mkdir')"; + } + mycon.query(querystream.str()); + mycon.query(commit); + + //adding dir to the database (if succeded) + if(event.getData()->succeeded == 0) + { + querystream.str(""); + querystream << "INSERT INTO `directories` (`did`, `path`, `owner`, `dir_group`, `owner_read`, `owner_write`, `group_read`, `group_write`, `others_read` , `others_write`) VALUES (DEFAULT, '"<path) << "', "<< ((uid!=-1)?(uid):0) <<" , 0 , " << ((tunable_local_umask & 0400)?0:1) << " , " << ((tunable_local_umask & 0200)?0:1) << " , " << ((tunable_local_umask & 040)?0:1) << " , " << ((tunable_local_umask & 020)?0:1) << " , " << ((tunable_local_umask & 04)?0:1) << " , " << ((tunable_local_umask & 02)?0:1) << " ) "; + mycon.query(querystream.str()); + mycon.query(commit); + } + + querystream.str(""); + querystream << "INSERT INTO `event_mkdir` (`eid`,`path`,`succeeded`) VALUES (" << getEid(event.getData()->sharedInfo) << ", '" << sanitize(event.getData()->path) << "', " << event.getData()->succeeded << ")"; + + mycon.query(querystream.str()); + mycon.query(commit); +} + +void VSFMySQLPlugin::handleEvent(const EventRename & event) +{ +MyDatabaseConnection mycon; +MYSQL_RES * result; +MYSQL_ROW row; +long long int ourfid; +stringstream querystream; +long long int uid; + uid = getUid(event.getData()->sharedInfo.user_name); + if(uid==-1) { + querystream << "INSERT INTO `event` (`eid`,`uid`,`timestamp`,`ip_address`,`event_type`) VALUES (DEFAULT, NULL,'" << getTimeStamp(event.getData()->sharedInfo.timestamp) << "','" << getIP(event.getData()->sharedInfo.remote_ip) << "', 'Rename')"; + } else { + querystream << "INSERT INTO `event` (`eid`,`uid`,`timestamp`,`ip_address`,`event_type`) VALUES (DEFAULT, "<< uid <<",'" << getTimeStamp(event.getData()->sharedInfo.timestamp) << "','" << getIP(event.getData()->sharedInfo.remote_ip) << "', 'Rename')"; + } + mycon.query(querystream.str()); + mycon.query(commit); + //changing the file name in the database (if succeded) + + + if(event.getData()->succeeded==0) + { + std::pair oldnam; + std::pair newnam; + oldnam=VSFUtils::sanitizePath(event.getData()->old_name,""); + newnam=VSFUtils::sanitizePath(event.getData()->new_name,""); + querystream.str(""); + querystream << "SELECT `fid` FROM `files` f JOIN `directories` d ON f.`directory` = d.`did` WHERE f.`name` = '" + << sanitize(oldnam.second) << "' AND d.`path` = '" << unslash(sanitize(oldnam.first )) << + "' "; + result=mycon.query(querystream.str()); + if((result==0)||(mysql_num_rows(result)!=1)) + { + //throw MySQLFileEntryNotFound; + //maybe dir? + querystream.str(""); + querystream << "SELECT `did` FROM `directories` WHERE `path` = '" << sanitize(oldnam.first) << sanitize(oldnam.second) << "' "; + result=mycon.query(querystream.str()); + if((result==0)||(mysql_num_rows(result)!=1)) + { + printf("MYSQL: file or dir for renaming not found in database\n"); + mysql_free_result(result); + return; + } + row = mysql_fetch_row(result); + sscanf(row[0],"%lld",&ourfid); + querystream.str(""); + //modifying dir + querystream << "UPDATE `directories` SET `path`= '" << sanitize(oldnam.first) << sanitize(newnam.second) << "' WHERE `did` = " << ourfid << " "; + mycon.query(querystream.str()); + mycon.query(commit); + return; + } + row = mysql_fetch_row(result); + sscanf(row[0],"%lld",&ourfid); + querystream.str(""); + //modifying file + querystream << "UPDATE `files` SET `name`= '" << sanitize(newnam.second) << "' , `directory` = (SELECT `did` FROM `directories` WHERE `path`='" << unslash(sanitize(newnam.first)) << "' LIMIT 1) WHERE `fid` = " << ourfid << " "; + printf("OLDNAME: %s\n",event.getData()->old_name); + mycon.query(querystream.str()); + mycon.query(commit); + } + + + querystream.str(""); + querystream << "INSERT INTO `event_rename` (`eid`,`oldname`,`newname`,`succeeded`) VALUES (" << getEid(event.getData()->sharedInfo) << ", '" << sanitize(event.getData()->old_name) << "', '"<< sanitize(event.getData()->new_name) << "', " << event.getData()->succeeded << ")"; + + mycon.query(querystream.str()); + mycon.query(commit); +} + +void VSFMySQLPlugin::handleEvent(const EventRmdir & event) +{ +MyDatabaseConnection mycon; +MYSQL_RES * result; +MYSQL_ROW row; +long long int ourdid; + stringstream querystream; +long long int uid; + uid = getUid(event.getData()->sharedInfo.user_name); + if(uid==-1) { + querystream << "INSERT INTO `event` (`eid`,`uid`,`timestamp`,`ip_address`,`event_type`) VALUES (DEFAULT, NULL,'" << getTimeStamp(event.getData()->sharedInfo.timestamp) << "','" << getIP(event.getData()->sharedInfo.remote_ip) << "', 'Rmdir')"; + }else { + querystream << "INSERT INTO `event` (`eid`,`uid`,`timestamp`,`ip_address`,`event_type`) VALUES (DEFAULT, " << uid << ",'" << getTimeStamp(event.getData()->sharedInfo.timestamp) << "','" << getIP(event.getData()->sharedInfo.remote_ip) << "', 'Rmdir')"; + } + mycon.query(querystream.str()); + mycon.query(commit); + + //removing dir from the database (if succeded) + + if(event.getData()->succeeded == 0) + { + querystream.str(""); + querystream << "SELECT `did` FROM `directories` WHERE `path` = '" << sanitize(event.getData()->path) << "' "; + result=mycon.query(querystream.str()); + if((result==0)||(mysql_num_rows(result)!=1)) + { + //throw MySQLFileEntryNotFound; + printf("MYSQL: dir for deleting not found - database integrity breached\n"); + mysql_free_result(result); + return; + } + row = mysql_fetch_row(result); + sscanf(row[0],"%lld",&ourdid); + + querystream.str(""); + querystream << "DELETE FROM `directories` WHERE `did`=" << ourdid << " "; + mycon.query(querystream.str()); + mycon.query(commit); + } + + + + + querystream.str(""); + querystream << "INSERT INTO `event_rmdir` (`eid`,`path`,`succeeded`) VALUES (" << getEid(event.getData()->sharedInfo) << ", '" << sanitize(event.getData()->path) << "', " << event.getData()->succeeded << ")"; + + mycon.query(querystream.str()); + mycon.query(commit); +} + +void VSFMySQLPlugin::handleEvent(const EventCliEnd & event) +{ +MyDatabaseConnection mycon; +stringstream querystream; + + querystream << "INSERT INTO `event` (`eid`,`uid`,`timestamp`,`ip_address`,`event_type`) VALUES (DEFAULT, NULL, '" << getTimeStamp(event.getData()->timestamp) << "','', 'CliEnd')"; + mycon.query(querystream.str()); + mycon.query(commit); +} + +void VSFMySQLPlugin::handleEvent(const EventUpload & event) +{ +MyDatabaseConnection mycon; +stringstream querystream; +long long int uid; + uid = getUid(event.getData()->sharedInfo.user_name); + if(uid==-1) { + querystream << "INSERT INTO `event` (`eid`,`uid`,`timestamp`,`ip_address`,`event_type`) VALUES (DEFAULT, NULL,'" << getTimeStamp(event.getData()->sharedInfo.timestamp) << "','" << getIP(event.getData()->sharedInfo.remote_ip) << "', 'Upload')"; + } else { + querystream << "INSERT INTO `event` (`eid`,`uid`,`timestamp`,`ip_address`,`event_type`) VALUES (DEFAULT, " << uid << ",'" << getTimeStamp(event.getData()->sharedInfo.timestamp) << "','" << getIP(event.getData()->sharedInfo.remote_ip) << "', 'Upload')"; + } + mycon.query(querystream.str()); + mycon.query(commit); + + //creating database entry (if succeded) + + if((event.getData()->is_complete==1)&&(event.getData()->is_append==0)&&(event.getData()->is_overwrite==0)) //complete and not append or overwrite -> insert a file + { + querystream.str(""); + querystream << "INSERT INTO `files` (`fid`, `name`, `directory`, `owner`, `file_group`, `owner_read`, `owner_write`, `group_read`, `group_write`, `others_read` , `others_write`) VALUES (DEFAULT, '"<< sanitize(event.getData()->fileInfo.name) << "' , (SELECT `did` FROM `directories` WHERE `path` = '" << unslash(sanitize(event.getData()->fileInfo.path)) << "' LIMIT 1) , "<< ((uid!=-1)?(uid):0) <<" , 0 , " << ((tunable_local_umask & 0400)?0:1) << " , " << ((tunable_local_umask & 0200)?0:1) << " , " << ((tunable_local_umask & 040)?0:1) << " , " << ((tunable_local_umask & 020)?0:1) << " , " << ((tunable_local_umask & 04)?0:1) << " , " << ((tunable_local_umask & 02)?0:1) << " ) "; + mycon.query(querystream.str()); + mycon.query(commit); + } + + querystream.str(""); + querystream << "INSERT INTO `event_upload` (`eid`,`path`,`filename`,`size`,`is_complete`,`is_append`,`is_overwrite`) VALUES (" << getEid(event.getData()->sharedInfo) << ", '" << unslash(sanitize(event.getData()->fileInfo.path)) << "', '" << sanitize(event.getData()->fileInfo.name) << "', " << event.getData()->fileInfo.size << ", " << event.getData()->is_complete << ", " << event.getData()->is_append << ", " << event.getData()->is_overwrite << ")"; + + mycon.query(querystream.str()); + mycon.query(commit); +} + +Request_Result VSFMySQLPlugin::handleRequest(const RequestLogin & r) +{ + MyDatabaseConnection mycon; + struct Request_Result retStruct = Request_Result_Init; + stringstream query; + MYSQL_RES * result; + retStruct.result=-1; + query << "SELECT * FROM `users` WHERE name='" << sanitize(r.getData()->login) << "' AND passwd=SHA1('" << sanitize(r.getData()->password) << "')"; + + + result=mycon.query(query.str()); + if((result!=0)&&(mysql_num_rows(result)==1)) + { + retStruct.result=0; + query.str(""); + query << "UPDATE `users` SET `last_login`= '" << sanitize(getTimeStamp()) << "' WHERE name='" << sanitize(r.getData()->login) << "' "; + mycon.query(query.str()); + mycon.query(commit); + } + return retStruct; +} + + + +Request_Result_And_Reason VSFMySQLPlugin::handleRequest(const RequestWriteFile & r) { + struct Request_Result_And_Reason retStruct = Request_Result_And_Reason_Init; + MyDatabaseConnection mycon; + MYSQL_RES * result1; + MYSQL_RES * result2; + MYSQL_ROW permissions_row; + MYSQL_ROW row; + stringstream query; + long long int uid; + long long int ownerid; + + //get owner and permissions + query << "SELECT f.`owner`, f.`file_group`, f.`owner_write`, f.`group_write`, f.`others_write` FROM `files` f JOIN `directories` d ON d.`did` = f.`directory` WHERE d.`path`='" << unslash(sanitize(r.getData()->path)) << "' AND f.`name`='" << sanitize(r.getData()->name) << "'"; + + result1=mycon.query(query.str()); + if((result1==0)||(mysql_num_rows(result1)!=1)) + { + retStruct.result=0; + retStruct.reason=strdup("File could not be found in the database\n"); + if(result1!=0) + { + mysql_free_result(result1); + } + return retStruct; + } + permissions_row = mysql_fetch_row(result1); + + //get uid an compare + query.str(""); + query << "SELECT `uid` FROM `users` WHERE `name` = '" << sanitize(r.getData()->sharedInfo.user_name) << "'"; + + result2 = mycon.query(query.str()); + if((result2==0)||(mysql_num_rows(result2)!=1)) + { + retStruct.result=-1; + retStruct.reason=strdup("The user could not be found in the database\n"); + if(result2!=0) + { + mysql_free_result(result2); + } + return retStruct; + } + + row = mysql_fetch_row(result2); + sscanf(row[0],"%lld",&uid); + mysql_free_result(result2); + + sscanf(permissions_row[0],"%lld",&ownerid); + if(uid==ownerid) + { + //login is owner + if(strcmp(permissions_row[2],"0")==0) + { + retStruct.result=-1; + retStruct.reason=strdup("You don't have permissions to write this file\n"); + mysql_free_result(result1); + return retStruct; + } + else + { + retStruct.result=0; + retStruct.reason=strdup("OK\n"); + mysql_free_result(result1); + return retStruct; + } + } + mysql_free_result(result2); + //checking if uid is in the group + query.str(""); + query << "SELECT `uid` FROM `users_groups` WHERE `gid` = " << sanitize(permissions_row[1]) << " AND `uid` = " << uid << " "; + + result2 = mycon.query(query.str()); + if((result2!=0)&&(mysql_num_rows(result2)==1)) + { + //login is in the same group as the file + if(strcmp(permissions_row[3],"0")==0) + { + retStruct.result=-1; + retStruct.reason=strdup("You don't have permissions to write this file\n"); + mysql_free_result(result2); + mysql_free_result(result1); + return retStruct; + } + else + { + retStruct.result=0; + retStruct.reason=strdup("OK\n"); + mysql_free_result(result2); + mysql_free_result(result1); + return retStruct; + } + } + mysql_free_result(result2); + + //else login is somebody else + if(strcmp(permissions_row[4],"0")==0) + { + retStruct.result=-1; + retStruct.reason=strdup("You don't have permissions to write this file\n"); + mysql_free_result(result1); + return retStruct; + } + else + { + retStruct.result=0; + retStruct.reason=strdup("OK\n"); + mysql_free_result(result1); + return retStruct; + } + //unreachable point in code +} + +Request_Result_And_Reason VSFMySQLPlugin::handleRequest(const RequestWriteDirectory & r) { + struct Request_Result_And_Reason retStruct = Request_Result_And_Reason_Init; + MyDatabaseConnection mycon; + MYSQL_RES * result1; + MYSQL_RES * result2; + MYSQL_ROW permissions_row; + MYSQL_ROW row; + stringstream query; + long long int uid=0; + long long int ownerid=0; + + //get owner and permissions + query << "SELECT `owner`, `dir_group`, `owner_write`, `group_write`, `others_write` FROM `directories` WHERE `path`='" << sanitize(r.getData()->parentPath) << sanitize(r.getData()->name) << "'"; + + result1=mycon.query(query.str()); + if((result1==0)||(mysql_num_rows(result1)!=1)) + { + retStruct.result=-1; + retStruct.reason=strdup("Directory could not be found in the database\n"); + if(result1!=0) + { + mysql_free_result(result1); + } + return retStruct; + } + + permissions_row = mysql_fetch_row(result1); + sscanf(permissions_row[0],"%lld",&ownerid); + + + + //get uid an compare + query.str(""); + query << "SELECT `uid` FROM `users` WHERE `name` = '" << sanitize(r.getData()->sharedInfo.user_name) << "' "; + + result2 = mycon.query(query.str()); + if((result2==0)||(mysql_num_rows(result2)!=1)) + { + retStruct.result=-1; + retStruct.reason=strdup("The user could not be found in the database\n"); + mysql_free_result(result2); + mysql_free_result(result1); + return retStruct; + } + row = mysql_fetch_row(result2); + sscanf(row[0],"%lld",&uid); + + mysql_free_result(result2); + + if(uid==ownerid) + { + //login is owner + if(strcmp(permissions_row[2],"0")==0) + { + retStruct.result=-1; + retStruct.reason=strdup("You don't have permissions to write this directory\n"); + mysql_free_result(result1); + return retStruct; + } + else + { + retStruct.result=0; + retStruct.reason=strdup("OK\n"); + mysql_free_result(result1); + return retStruct; + } + } + //checking if uid is in the group + query.str(""); + query << "SELECT `uid` FROM `users_groups` WHERE `gid` = " << sanitize(permissions_row[1]) << " AND `uid` = " << uid << " "; + + result2 = mycon.query(query.str()); + if((result2!=0)&&(mysql_num_rows(result2)==1)) + { + //login is in the same group as the file + if(strcmp(permissions_row[3],"0")==0) + { + retStruct.result=-1; + retStruct.reason=strdup("You don't have permissions to write this directory\n"); + mysql_free_result(result2); + mysql_free_result(result1); + return retStruct; + } + else + { + retStruct.result=0; + retStruct.reason=strdup("OK\n"); + mysql_free_result(result2); + mysql_free_result(result1); + return retStruct; + } + } + if(result2!=0) + { + mysql_free_result(result2); + } + + //else login is somebody else + if(strcmp(permissions_row[4],"0")==0) + { + retStruct.result=-1; + retStruct.reason=strdup("You don't have permissions to write this directory\n"); + mysql_free_result(result1); + return retStruct; + } + else + { + retStruct.result=0; + retStruct.reason=strdup("OK\n"); + mysql_free_result(result1); + return retStruct; + } + //unreachable point in code +} + + +Request_Result_And_Reason VSFMySQLPlugin::handleRequest(const RequestReadFile & r) { + struct Request_Result_And_Reason retStruct = Request_Result_And_Reason_Init; + MyDatabaseConnection mycon; + MYSQL_RES * result1; + MYSQL_RES * result2; + MYSQL_ROW permissions_row; + MYSQL_ROW row; + stringstream query; + long long int uid; + long long int ownerid; + + //get owner and permissions + query << "SELECT f.`owner`, f.`file_group`, f.`owner_read`, f.`group_read`, f.`others_read` FROM `files` f JOIN `directories` d ON d.`did` = f.`directory` WHERE d.`path`='" << unslash(sanitize(r.getData()->path)) << "' AND f.`name`='" << sanitize(r.getData()->name) << "'"; + + result1=mycon.query(query.str()); + if((result1==0)||(mysql_num_rows(result1)!=1)) + { + retStruct.result=0; + retStruct.reason=strdup("File could not be found in the database\n"); + if(result1!=0) + { + mysql_free_result(result1); + } + return retStruct; + } + permissions_row = mysql_fetch_row(result1); + + //get uid an compare + query.str(""); + query << "SELECT `uid` FROM `users` WHERE `name` = '" << sanitize(r.getData()->sharedInfo.user_name) << "'"; + + result2 = mycon.query(query.str()); + if((result2==0)||(mysql_num_rows(result2)!=1)) + { + retStruct.result=-1; + retStruct.reason=strdup("The user could not be found in the database\n"); + if(result2!=0) + { + mysql_free_result(result2); + } + return retStruct; + } + + row = mysql_fetch_row(result2); + sscanf(row[0],"%lld",&uid); + mysql_free_result(result2); + + sscanf(permissions_row[0],"%lld",&ownerid); + if(uid==ownerid) + { + //login is owner + if(strcmp(permissions_row[2],"0")==0) + { + retStruct.result=-1; + retStruct.reason=strdup("You don't have permissions to read this file\n"); + mysql_free_result(result1); + return retStruct; + } + else + { + retStruct.result=0; + retStruct.reason=strdup("OK\n"); + mysql_free_result(result1); + return retStruct; + } + } + mysql_free_result(result2); + //checking if uid is in the group + query.str(""); + query << "SELECT `uid` FROM `users_groups` WHERE `gid` = " << sanitize(permissions_row[1]) << " AND `uid` = " << uid << " "; + + result2 = mycon.query(query.str()); + if((result2!=0)&&(mysql_num_rows(result2)==1)) + { + //login is in the same group as the file + if(strcmp(permissions_row[3],"0")==0) + { + retStruct.result=-1; + retStruct.reason=strdup("You don't have permissions to read this file\n"); + mysql_free_result(result2); + mysql_free_result(result1); + return retStruct; + } + else + { + retStruct.result=0; + retStruct.reason=strdup("OK\n"); + mysql_free_result(result2); + mysql_free_result(result1); + return retStruct; + } + } + mysql_free_result(result2); + + //else login is somebody else + if(strcmp(permissions_row[4],"0")==0) + { + retStruct.result=-1; + retStruct.reason=strdup("You don't have permissions to read this file\n"); + mysql_free_result(result1); + return retStruct; + } + else + { + retStruct.result=0; + retStruct.reason=strdup("OK\n"); + mysql_free_result(result1); + return retStruct; + } + //unreachable point in code +} + +Request_Result_And_Reason VSFMySQLPlugin::handleRequest(const RequestReadDirectory & r) { + struct Request_Result_And_Reason retStruct = Request_Result_And_Reason_Init; + MyDatabaseConnection mycon; + MYSQL_RES * result1; + MYSQL_RES * result2; + MYSQL_ROW permissions_row; + MYSQL_ROW row; + stringstream query; + long long int uid=0; + long long int ownerid=0; + + //get owner and permissions + query << "SELECT `owner`, `dir_group`, `owner_read`, `group_read`, `others_read` FROM `directories` WHERE `path`='" << sanitize(r.getData()->parentPath) << sanitize(r.getData()->name) << "'"; + + result1=mycon.query(query.str()); + if((result1==0)||(mysql_num_rows(result1)!=1)) + { + retStruct.result=-1; + retStruct.reason=strdup("Directory could not be found in the database\n"); + if(result1!=0) + { + mysql_free_result(result1); + } + return retStruct; + } + + permissions_row = mysql_fetch_row(result1); + sscanf(permissions_row[0],"%lld",&ownerid); + + + + //get uid an compare + query.str(""); + query << "SELECT `uid` FROM `users` WHERE `name` = '" << sanitize(r.getData()->sharedInfo.user_name) << "' "; + + result2 = mycon.query(query.str()); + if((result2==0)||(mysql_num_rows(result2)!=1)) + { + retStruct.result=-1; + retStruct.reason=strdup("The user could not be found in the database\n"); + mysql_free_result(result2); + mysql_free_result(result1); + return retStruct; + } + row = mysql_fetch_row(result2); + sscanf(row[0],"%lld",&uid); + + mysql_free_result(result2); + + if(uid==ownerid) + { + //login is owner + if(strcmp(permissions_row[2],"0")==0) + { + retStruct.result=-1; + retStruct.reason=strdup("You don't have permissions to read this directory\n"); + mysql_free_result(result1); + return retStruct; + } + else + { + retStruct.result=0; + retStruct.reason=strdup("OK\n"); + mysql_free_result(result1); + return retStruct; + } + } + //checking if uid is in the group + query.str(""); + query << "SELECT `uid` FROM `users_groups` WHERE `gid` = " << sanitize(permissions_row[1]) << " AND `uid` = " << uid << " "; + + result2 = mycon.query(query.str()); + if((result2!=0)&&(mysql_num_rows(result2)==1)) + { + //login is in the same group as the file + if(strcmp(permissions_row[3],"0")==0) + { + retStruct.result=-1; + retStruct.reason=strdup("You don't have permissions to read this directory\n"); + mysql_free_result(result2); + mysql_free_result(result1); + return retStruct; + } + else + { + retStruct.result=0; + retStruct.reason=strdup("OK\n"); + mysql_free_result(result2); + mysql_free_result(result1); + return retStruct; + } + } + if(result2!=0) + { + mysql_free_result(result2); + } + + //else login is somebody else + if(strcmp(permissions_row[4],"0")==0) + { + retStruct.result=-1; + retStruct.reason=strdup("You don't have permissions to read this directory\n"); + mysql_free_result(result1); + return retStruct; + } + else + { + retStruct.result=0; + retStruct.reason=strdup("OK\n"); + mysql_free_result(result1); + return retStruct; + } + //unreachable point in code +} + + + +struct Request_Result_Homedir VSFMySQLPlugin::handleRequest(const RequestHomedir & r) +{ + MyDatabaseConnection mycon; + struct Request_Result_Homedir retStruct = Request_Result_Homedir_Init; + stringstream query; + MYSQL_RES * result; + MYSQL_ROW row; + + query.str(""); + query << "SELECT `homedir` FROM `users` WHERE `name` = '" << sanitize(r.getData()->sharedInfo.user_name) << "' "; + result = mycon.query(query.str()); + if((result!=0)&&(mysql_num_rows(result)==1)) + { + row = mysql_fetch_row(result); + retStruct.homedir=strdup(row[0]); + } + return retStruct; +} diff --git a/mysql_plugin.h b/mysql_plugin.h new file mode 100644 index 0000000..0c21f6a --- /dev/null +++ b/mysql_plugin.h @@ -0,0 +1,79 @@ +#ifndef MYSQL_PLUGIN_H +#define MYSQL_PLUGIN_H +#include "plugin_architecture_api.h" +//#include +//#include +//#include +#include +#include +#include +#include +#include "exceptions.h" +#include "tunables.h" +#include +#include +#include +#include +#include +#include "vsfutils.h" + +using namespace std; + +class MyDatabaseConnection +{ +private: + MYSQL* _dbconn; + MYSQL_RES * _result; +public: + MyDatabaseConnection(); + ~MyDatabaseConnection(); + MYSQL_RES * query(const string); + MYSQL_RES * getResult() { return _result; } + MYSQL* getConnection() { return _dbconn; } +}; + +class VSFMySQLPlugin : public VSFPlugin +{ +private: +//protected stuff +// MYSQL* _dbconn; +MyDatabaseConnection * dbcon; + string commit; + +// MYSQL_RES * queryDB(const string); +// MYSQL_RES * queryDB(const string, MYSQL *); + string getTimeStamp(); + string getTimeStamp(struct timeval); + long long int getUid(string username); + long long int getEid(const struct Event_Shared sharedInfo); + string getIP(const struct sockaddr_in &); + string sanitize(const string &); + string sanitize(const char * original); +// void connectDB(MYSQL * & connection); + VSFPluginArchitecture & PlArch; + string unslash(const char * original); + string unslash(const string & original); +public: +//public stuff + VSFMySQLPlugin(VSFPluginArchitecture &); + ~VSFMySQLPlugin(); + void handleEvent(const EventChmod &); + void handleEvent(const EventConnection &); + void handleEvent(const EventDelete &); + void handleEvent(const EventDownload &); + void handleEvent(const EventFailedLogin &); + void handleEvent(const EventLogin &); + void handleEvent(const EventMkdir &); + void handleEvent(const EventRename &); + void handleEvent(const EventRmdir &); + void handleEvent(const EventCliEnd &); + void handleEvent(const EventUpload &); + + Request_Result handleRequest(const RequestLogin &); + Request_Result_And_Reason handleRequest(const RequestReadFile &); + Request_Result_And_Reason handleRequest(const RequestReadDirectory &); + Request_Result_And_Reason handleRequest(const RequestWriteDirectory &); + Request_Result_And_Reason handleRequest(const RequestWriteFile &); + Request_Result_Homedir handleRequest(const RequestHomedir &); +}; +#endif diff --git a/netstr.c b/netstr.c new file mode 100644 index 0000000..d92c936 --- /dev/null +++ b/netstr.c @@ -0,0 +1,116 @@ +/* + * Part of Very Secure FTPd + * Licence: GPL v2 + * Author: Chris Evans + * netstr.c + * + * The netstr interface extends the standard string interface, adding + * functions which can cope safely with building strings from the network, + * and send them out too. + */ + +#include "netstr.h" +#include "str.h" +#include "sysstr.h" +#include "utility.h" +#include "sysutil.h" + +void +str_netfd_alloc(struct mystr* p_str, int fd, char term, char* p_readbuf, + unsigned int maxlen) +{ + int retval; + unsigned int bytes_read; + unsigned int i; + char* p_readpos = p_readbuf; + unsigned int left = maxlen; + while (1) + { + if (p_readpos + left != p_readbuf + maxlen) + { + bug("poor buffer accounting in str_netfd_alloc"); + } + /* Did we hit the max? */ + if (left == 0) + { + str_empty(p_str); + return; + } + retval = vsf_sysutil_recv_peek(fd, p_readpos, left); + if (vsf_sysutil_retval_is_error(retval)) + { + die("vsf_sysutil_recv_peek"); + } + else if (retval == 0) + { + die("vsf_sysutil_recv_peek: no data"); + } + bytes_read = (unsigned int) retval; + /* Search for the terminator */ + for (i=0; i < bytes_read; i++) + { + if (p_readpos[i] == term) + { + /* Got it! */ + retval = vsf_sysutil_read_loop(fd, p_readpos, i + 1); + if (vsf_sysutil_retval_is_error(retval) || + (unsigned int) retval != i + 1) + { + die("vsf_sysutil_read_loop"); + } + if (p_readpos[i] != term) + { + die("missing terminator in str_netfd_alloc"); + } + str_alloc_alt_term(p_str, p_readbuf, term); + return; + } + } + /* Not found in this read chunk, so consume the data and re-loop */ + if (bytes_read > left) + { + bug("bytes_read > left in str_netfd_alloc"); + } + left -= bytes_read; + retval = vsf_sysutil_read_loop(fd, p_readpos, bytes_read); + if (vsf_sysutil_retval_is_error(retval) || + (unsigned int) retval != bytes_read) + { + die("vsf_sysutil_read_loop"); + } + p_readpos += bytes_read; + } /* END: while(1) */ +} + +int +str_netfd_write(const struct mystr* p_str, int fd) +{ + int ret = 0; + int retval; + unsigned int str_len = str_getlen(p_str); + if (str_len == 0) + { + bug("zero str_len in str_netfd_write"); + } + retval = str_write_loop(p_str, fd); + if (vsf_sysutil_retval_is_error(retval) || (unsigned int) retval != str_len) + { + ret = -1; + } + return ret; +} + +int +str_netfd_read(struct mystr* p_str, int fd, unsigned int len) +{ + int retval; + str_reserve(p_str, len); + str_trunc(p_str, len); + retval = str_read_loop(p_str, fd); + if (vsf_sysutil_retval_is_error(retval) || (unsigned int) retval != len) + { + return -1; + } + return retval; +} + diff --git a/netstr.h b/netstr.h new file mode 100644 index 0000000..723c348 --- /dev/null +++ b/netstr.h @@ -0,0 +1,54 @@ +#ifndef VSFTP_NETSTR_H +#define VSFTP_NETSTR_H + +struct mystr; + +/* str_netfd_alloc() + * PURPOSE + * Read a string from a network socket into a string buffer object. The string + * is delimited by a specified string terminator character. + * If any network related errors occur trying to read the string, this call + * will exit the program. + * This method avoids reading one character at a time from the network. + * PARAMETERS + * p_str - the destination string object + * fd - the file descriptor of the remote network socket + * term - the character which will terminate the string. This character + * is included in the returned string. + * p_readbuf - pointer to a scratch buffer into which to read from the + * network. This buffer must be at least "maxlen" characters! + * maxlen - maximum length of string to return. If this limit is passed, + * an empty string will be returned. + */ +void str_netfd_alloc(struct mystr* p_str, int fd, char term, + char* p_readbuf, unsigned int maxlen); + +/* str_netfd_read() + * PURPOSE + * Fills contents of a string buffer object from a (typically network) file + * descriptor. + * PARAMETERS + * p_str - the string object to be filled + * fd - the file descriptor to read from + * len - the number of bytes to read + * RETURNS + * Number read on success, -1 on failure. The read is considered a failure + * unless the full requested byte count is read. + */ +int str_netfd_read(struct mystr* p_str, int fd, unsigned int len); + +/* str_netfd_write() + * PURPOSE + * Write the contents of a string buffer object out to a (typically network) + * file descriptor. + * PARAMETERS + * p_str - the string object to send + * fd - the file descriptor to write to + * RETURNS + * Number written on success, -1 on failure. The write is considered a failure + * unless the full string buffer object is written. + */ +int str_netfd_write(const struct mystr* p_str, int fd); + +#endif /* VSFTP_NETSTR_H */ + diff --git a/oneprocess.c b/oneprocess.c new file mode 100644 index 0000000..385de96 --- /dev/null +++ b/oneprocess.c @@ -0,0 +1,96 @@ +/* + * Part of Very Secure FTPd + * Licence: GPL v2 + * Author: Chris Evans + * oneprocess.c + * + * Code for the "one process" security model. The one process security model + * is born for the purposes of raw speed at the expense of compromising the + * purity of the security model. + * The one process model will typically be disabled, for security reasons. + * Only sites with huge numbers of concurrent users are likely to feel the + * pain of two processes per session. + */ + +#include "prelogin.h" +#include "postlogin.h" +#include "privops.h" +#include "session.h" +#include "secutil.h" +#include "str.h" +#include "tunables.h" +#include "utility.h" +#include "sysstr.h" +#include "sysdeputil.h" + +void +vsf_one_process_start(struct vsf_session* p_sess) +{ + unsigned int caps = 0; + if (tunable_chown_uploads) + { + caps |= kCapabilityCAP_CHOWN; + } + if (tunable_connect_from_port_20) + { + caps |= kCapabilityCAP_NET_BIND_SERVICE; + } + { + struct mystr user_name = INIT_MYSTR; + struct mystr chdir_str = INIT_MYSTR; + str_alloc_text(&user_name, tunable_ftp_username); + if (tunable_anon_root) + { + str_alloc_text(&chdir_str, tunable_anon_root); + } + if (tunable_run_as_launching_user) + { + if (!str_isempty(&chdir_str)) + { + str_chdir(&chdir_str); + } + } + else + { + vsf_secutil_change_credentials(&user_name, 0, &chdir_str, caps, + VSF_SECUTIL_OPTION_CHROOT | VSF_SECUTIL_OPTION_USE_GROUPS); + } + str_free(&user_name); + str_free(&chdir_str); + } + init_connection(p_sess); +} + +void +vsf_one_process_login(struct vsf_session* p_sess, + const struct mystr* p_pass_str) +{ + enum EVSFPrivopLoginResult login_result = + vsf_privop_do_login(p_sess, p_pass_str); + switch (login_result) + { + case kVSFLoginFail: + return; + break; + case kVSFLoginAnon: + p_sess->is_anonymous = 1; + process_post_login(p_sess); + break; + default: + bug("bad state in vsf_one_process_login"); + break; + } +} + +int +vsf_one_process_get_priv_data_sock(struct vsf_session* p_sess) +{ + return vsf_privop_get_ftp_port_sock(p_sess); +} + +void +vsf_one_process_chown_upload(struct vsf_session* p_sess, int fd) +{ + vsf_privop_do_file_chown(p_sess, fd); +} + diff --git a/oneprocess.h b/oneprocess.h new file mode 100644 index 0000000..c6ef380 --- /dev/null +++ b/oneprocess.h @@ -0,0 +1,47 @@ +#ifndef VSF_ONEPROCESS_H +#define VSF_ONEPROCESS_H + +struct mystr; +struct vsf_session; + +/* vsf_one_process_start() + * PURPOSE + * Called to start FTP login processing using the one process model. Before + * processing starts, all possible privileges are dropped. + * PARAMETERS + * p_sess - the current session object + */ +void vsf_one_process_start(struct vsf_session* p_sess); + +/* vsf_one_process_login() + * PURPOSE + * Called to propose a login using the one process model. Only anonymous + * logins supported! + * PARAMETERS + * p_sess - the current session object + * p_pass_str - the proposed password + */ +void vsf_one_process_login(struct vsf_session* p_sess, + const struct mystr* p_pass_str); + +/* vsf_one_process_get_priv_data_sock() + * PURPOSE + * Get a privileged port 20 bound data socket using the one process model. + * PARAMETERS + * p_sess - the current session object + * RETURNS + * The file descriptor of the privileged socket + */ +int vsf_one_process_get_priv_data_sock(struct vsf_session* p_sess); + +/* vsf_one_process_chown_upload() + * PURPOSE + * Change ownership of an uploaded file using the one process model. + * PARAMETERS + * p_sess - the current session object + * fd - the file descriptor to change ownership on + */ +void vsf_one_process_chown_upload(struct vsf_session* p_sess, int fd); + +#endif /* VSF_ONEPROCESS_H */ + diff --git a/parseconf.c b/parseconf.c new file mode 100644 index 0000000..5b6a200 --- /dev/null +++ b/parseconf.c @@ -0,0 +1,364 @@ +/* + * Part of Very Secure FTPd + * Licence: GPL v2 + * Author: Chris Evans + * parseconf.c + * + * Routines and support to load in a file full of tunable variables and + * settings, populating corresponding runtime variables. + */ + +#include "parseconf.h" +#include "tunables.h" +#include "str.h" +#include "filestr.h" +#include "defs.h" +#include "sysutil.h" +#include "utility.h" + +static const char* s_p_saved_filename; +static int s_strings_copied; + +/* File local functions */ +static void handle_config_setting(struct mystr* p_setting_str, + struct mystr* p_value_str, + int errs_fatal); + +static void copy_string_settings(void); + +/* Tables mapping setting names to runtime variables */ +/* Boolean settings */ +static struct parseconf_bool_setting +{ + const char* p_setting_name; + int* p_variable; +} +parseconf_bool_array[] = +{ + { "anonymous_enable", &tunable_anonymous_enable }, + { "local_enable", &tunable_local_enable }, + { "pasv_enable", &tunable_pasv_enable }, + { "port_enable", &tunable_port_enable }, + { "chroot_local_user", &tunable_chroot_local_user }, + { "write_enable", &tunable_write_enable }, + { "anon_upload_enable", &tunable_anon_upload_enable }, + { "anon_mkdir_write_enable", &tunable_anon_mkdir_write_enable }, + { "anon_other_write_enable", &tunable_anon_other_write_enable }, + { "chown_uploads", &tunable_chown_uploads }, + { "connect_from_port_20", &tunable_connect_from_port_20 }, + { "xferlog_enable", &tunable_xferlog_enable }, + { "dirmessage_enable", &tunable_dirmessage_enable }, + { "anon_world_readable_only", &tunable_anon_world_readable_only }, + { "async_abor_enable", &tunable_async_abor_enable }, + { "ascii_upload_enable", &tunable_ascii_upload_enable }, + { "ascii_download_enable", &tunable_ascii_download_enable }, + { "one_process_model", &tunable_one_process_model }, + { "xferlog_std_format", &tunable_xferlog_std_format }, + { "pasv_promiscuous", &tunable_pasv_promiscuous }, + { "deny_email_enable", &tunable_deny_email_enable }, + { "chroot_list_enable", &tunable_chroot_list_enable }, + { "setproctitle_enable", &tunable_setproctitle_enable }, + { "text_userdb_names", &tunable_text_userdb_names }, + { "ls_recurse_enable", &tunable_ls_recurse_enable }, + { "log_ftp_protocol", &tunable_log_ftp_protocol }, + { "guest_enable", &tunable_guest_enable }, + { "userlist_enable", &tunable_userlist_enable }, + { "userlist_deny", &tunable_userlist_deny }, + { "use_localtime", &tunable_use_localtime }, + { "check_shell", &tunable_check_shell }, + { "hide_ids", &tunable_hide_ids }, + { "listen", &tunable_listen }, + { "port_promiscuous", &tunable_port_promiscuous }, + { "passwd_chroot_enable", &tunable_passwd_chroot_enable }, + { "no_anon_password", &tunable_no_anon_password }, + { "tcp_wrappers", &tunable_tcp_wrappers }, + { "use_sendfile", &tunable_use_sendfile }, + { "force_dot_files", &tunable_force_dot_files }, + { "listen_ipv6", &tunable_listen_ipv6 }, + { "dual_log_enable", &tunable_dual_log_enable }, + { "syslog_enable", &tunable_syslog_enable }, + { "background", &tunable_background }, + { "virtual_use_local_privs", &tunable_virtual_use_local_privs }, + { "session_support", &tunable_session_support }, + { "download_enable", &tunable_download_enable }, + { "dirlist_enable", &tunable_dirlist_enable }, + { "chmod_enable", &tunable_chmod_enable }, + { "secure_email_list_enable", &tunable_secure_email_list_enable }, + { "run_as_launching_user", &tunable_run_as_launching_user }, + { "no_log_lock", &tunable_no_log_lock }, + { "ssl_enable", &tunable_ssl_enable }, + { "allow_anon_ssl", &tunable_allow_anon_ssl }, + { "force_local_logins_ssl", &tunable_force_local_logins_ssl }, + { "force_local_data_ssl", &tunable_force_local_data_ssl }, + { "ssl_sslv2", &tunable_sslv2 }, + { "ssl_sslv3", &tunable_sslv3 }, + { "ssl_tlsv1", &tunable_tlsv1 }, + { "tilde_user_enable", &tunable_tilde_user_enable }, + { "force_anon_logins_ssl", &tunable_force_anon_logins_ssl }, + { "force_anon_data_ssl", &tunable_force_anon_data_ssl }, + { "mdtm_write", &tunable_mdtm_write }, + { "lock_upload_files", &tunable_lock_upload_files }, + { "pasv_addr_resolve", &tunable_pasv_addr_resolve }, + { "plugin_architecture", &tunable_plugin_architecture}, + { 0, 0 } +}; + +static struct parseconf_uint_setting +{ + const char* p_setting_name; + unsigned int* p_variable; +} +parseconf_uint_array[] = +{ + { "accept_timeout", &tunable_accept_timeout }, + { "connect_timeout", &tunable_connect_timeout }, + { "local_umask", &tunable_local_umask }, + { "anon_umask", &tunable_anon_umask }, + { "ftp_data_port", &tunable_ftp_data_port }, + { "idle_session_timeout", &tunable_idle_session_timeout }, + { "data_connection_timeout", &tunable_data_connection_timeout }, + { "pasv_min_port", &tunable_pasv_min_port }, + { "pasv_max_port", &tunable_pasv_max_port }, + { "anon_max_rate", &tunable_anon_max_rate }, + { "local_max_rate", &tunable_local_max_rate }, + { "listen_port", &tunable_listen_port }, + { "max_clients", &tunable_max_clients }, + { "file_open_mode", &tunable_file_open_mode }, + { "max_per_ip", &tunable_max_per_ip }, + { "trans_chunk_size", &tunable_trans_chunk_size }, + { "delay_failed_login", &tunable_delay_failed_login }, + { "delay_successful_login", &tunable_delay_successful_login }, + { "max_login_fails", &tunable_max_login_fails }, + { "mysql_database_port", &tunable_mysql_database_port}, + { "mysql_database_connectionflags", &tunable_mysql_database_connectionflags}, + { "pgsql_database_port", &tunable_pgsql_database_port}, + { 0, 0 } +}; + +static struct parseconf_str_setting +{ + const char* p_setting_name; + const char** p_variable; +} +parseconf_str_array[] = +{ + { "secure_chroot_dir", &tunable_secure_chroot_dir }, + { "ftp_username", &tunable_ftp_username }, + { "chown_username", &tunable_chown_username }, + { "xferlog_file", &tunable_xferlog_file }, + { "vsftpd_log_file", &tunable_vsftpd_log_file }, + { "message_file", &tunable_message_file }, + { "nopriv_user", &tunable_nopriv_user }, + { "ftpd_banner", &tunable_ftpd_banner }, + { "banned_email_file", &tunable_banned_email_file }, + { "chroot_list_file", &tunable_chroot_list_file }, + { "pam_service_name", &tunable_pam_service_name }, + { "guest_username", &tunable_guest_username }, + { "userlist_file", &tunable_userlist_file }, + { "anon_root", &tunable_anon_root }, + { "local_root", &tunable_local_root }, + { "banner_file", &tunable_banner_file }, + { "pasv_address", &tunable_pasv_address }, + { "listen_address", &tunable_listen_address }, + { "user_config_dir", &tunable_user_config_dir }, + { "listen_address6", &tunable_listen_address6 }, + { "cmds_allowed", &tunable_cmds_allowed }, + { "hide_file", &tunable_hide_file }, + { "deny_file", &tunable_deny_file }, + { "user_sub_token", &tunable_user_sub_token }, + { "email_password_file", &tunable_email_password_file }, + { "rsa_cert_file", &tunable_rsa_cert_file }, + { "dsa_cert_file", &tunable_dsa_cert_file }, + { "ssl_ciphers", &tunable_ssl_ciphers }, + { "rsa_private_key_file", &tunable_rsa_private_key_file }, + { "dsa_private_key_file", &tunable_dsa_private_key_file }, + { "sql_plugin", &tunable_sql_plugin}, + { "mysql_database_username", &tunable_mysql_database_username}, + { "mysql_database_password", &tunable_mysql_database_password}, + { "mysql_database_host", &tunable_mysql_database_host}, + { "mysql_database_dbname", &tunable_mysql_database_dbname}, + { "mysql_database_socketname", &tunable_mysql_database_socketname}, + { "pgsql_database_username", &tunable_pgsql_database_username}, + { "pgsql_database_password", &tunable_pgsql_database_password}, + { "pgsql_database_host", &tunable_pgsql_database_host}, + { "pgsql_database_dbname", &tunable_pgsql_database_dbname}, + { 0, 0 } +}; + +void +vsf_parseconf_load_file(const char* p_filename, int errs_fatal) +{ + struct mystr config_file_str = INIT_MYSTR; + struct mystr config_setting_str = INIT_MYSTR; + struct mystr config_value_str = INIT_MYSTR; + unsigned int str_pos = 0; + int retval; + if (!p_filename) + { + p_filename = s_p_saved_filename; + } + else + { + if (s_p_saved_filename) + { + vsf_sysutil_free((char*)s_p_saved_filename); + } + s_p_saved_filename = vsf_sysutil_strdup(p_filename); + } + if (!p_filename) + { + bug("null filename in vsf_parseconf_load_file"); + } + if (!s_strings_copied) + { + s_strings_copied = 1; + /* A minor hack to make sure all strings are malloc()'ed so we can free + * them at some later date. Specifically handles strings embedded in the + * binary. + */ + copy_string_settings(); + } + retval = str_fileread(&config_file_str, p_filename, VSFTP_CONF_FILE_MAX); + if (vsf_sysutil_retval_is_error(retval)) + { + if (errs_fatal) + { + die2("cannot open config file:", p_filename); + } + else + { + return; + } + } + while (str_getline(&config_file_str, &config_setting_str, &str_pos)) + { + if (str_isempty(&config_setting_str) || + str_get_char_at(&config_setting_str, 0) == '#') + { + continue; + } + /* Split into name=value pair */ + str_split_char(&config_setting_str, &config_value_str, '='); + handle_config_setting(&config_setting_str, &config_value_str, errs_fatal); + } + str_free(&config_file_str); + str_free(&config_setting_str); + str_free(&config_value_str); +} + +static void +handle_config_setting(struct mystr* p_setting_str, struct mystr* p_value_str, + int errs_fatal) +{ + /* Is it a string setting? */ + { + const struct parseconf_str_setting* p_str_setting = parseconf_str_array; + while (p_str_setting->p_setting_name != 0) + { + if (str_equal_text(p_setting_str, p_str_setting->p_setting_name)) + { + /* Got it */ + const char** p_curr_setting = p_str_setting->p_variable; + if (*p_curr_setting) + { + vsf_sysutil_free((char*)*p_curr_setting); + } + if (str_isempty(p_value_str)) + { + *p_curr_setting = 0; + } + else + { + *p_curr_setting = str_strdup(p_value_str); + } + return; + } + p_str_setting++; + } + } + if (str_isempty(p_value_str)) + { + if (errs_fatal) + { + die2("missing value in config file for: ", str_getbuf(p_setting_str)); + } + else + { + return; + } + } + /* Is it a boolean value? */ + { + const struct parseconf_bool_setting* p_bool_setting = parseconf_bool_array; + while (p_bool_setting->p_setting_name != 0) + { + if (str_equal_text(p_setting_str, p_bool_setting->p_setting_name)) + { + /* Got it */ + str_upper(p_value_str); + if (str_equal_text(p_value_str, "YES") || + str_equal_text(p_value_str, "TRUE") || + str_equal_text(p_value_str, "1")) + { + *(p_bool_setting->p_variable) = 1; + } + else if (str_equal_text(p_value_str, "NO") || + str_equal_text(p_value_str, "FALSE") || + str_equal_text(p_value_str, "0")) + { + *(p_bool_setting->p_variable) = 0; + } + else if (errs_fatal) + { + die2("bad bool value in config file for: ", + str_getbuf(p_setting_str)); + } + return; + } + p_bool_setting++; + } + } + /* Is it an unsigned integer setting? */ + { + const struct parseconf_uint_setting* p_uint_setting = parseconf_uint_array; + while (p_uint_setting->p_setting_name != 0) + { + if (str_equal_text(p_setting_str, p_uint_setting->p_setting_name)) + { + /* Got it */ + /* If the value starts with 0, assume it's an octal value */ + if (!str_isempty(p_value_str) && + str_get_char_at(p_value_str, 0) == '0') + { + *(p_uint_setting->p_variable) = str_octal_to_uint(p_value_str); + } + else + { + *(p_uint_setting->p_variable) = str_atoi(p_value_str); + } + return; + } + p_uint_setting++; + } + } + if (errs_fatal) + { + die2("unrecognised variable in config file: ", str_getbuf(p_setting_str)); + } +} + +static void +copy_string_settings(void) +{ + const struct parseconf_str_setting* p_str_setting = parseconf_str_array; + while (p_str_setting->p_setting_name != 0) + { + if (*p_str_setting->p_variable != 0) + { + *p_str_setting->p_variable = + vsf_sysutil_strdup(*p_str_setting->p_variable); + } + p_str_setting++; + } +} + diff --git a/parseconf.h b/parseconf.h new file mode 100644 index 0000000..9257ff7 --- /dev/null +++ b/parseconf.h @@ -0,0 +1,21 @@ +#ifndef VSF_PARSECONF_H +#define VSF_PARSECONF_H + +/* vsf_parseconf_load_file() + * PURPOSE + * Parse the given file as a vsftpd config file. If the file cannot be + * opened for whatever reason, a fatal error is raised. If the file contains + * any syntax errors, a fatal error is raised. + * If the call returns (no fatal error raised), then the config file was + * parsed and the global config settings will have been updated. + * PARAMETERS + * p_filename - the name of the config file to parse + * errs_fatal - errors will cause the calling process to exit if not 0 + * NOTES + * If p_filename is NULL, then the last filename passed to this function is + * used to reload the configuration details. + */ +void vsf_parseconf_load_file(const char* p_filename, int errs_fatal); + +#endif /* VSF_PARSECONF_H */ + diff --git a/pgsql_plugin.cpp b/pgsql_plugin.cpp new file mode 100644 index 0000000..a857d4e --- /dev/null +++ b/pgsql_plugin.cpp @@ -0,0 +1,1082 @@ +#include "pgsql_plugin.h" + +using namespace std; + +void VSFPgSQLPlugin::connectDB(PGconn * & connection) +{ + char *conninfo; + conninfo = new char[120]; + sprintf(conninfo,"dbname = %s host = %s port = %d user = %s password = %s", + tunable_pgsql_database_dbname,tunable_pgsql_database_host, + tunable_pgsql_database_port,tunable_pgsql_database_username, + tunable_pgsql_database_password); + printf("VSPostgreSQL: connectDB: connecting to database...\n"); + connection = PQconnectdb(conninfo); + if (PQstatus(connection) != CONNECTION_OK) + { + fprintf(stderr, "VSPostgreSQL: connection to database failed: %s",PQerrorMessage(connection)); + PQfinish(connection); + } + else + { + fprintf(stderr,"VSPostgreSQL: successfully connected...\n"); + } +} + +PGresult * VSFPgSQLPlugin::queryDB(const string query) +{ + return queryDB(query,_dbconn); +} + +PGresult * VSFPgSQLPlugin::queryDB(const string query,PGconn * connection) +{ + printf("%s\n",query.c_str()); + PGresult *result; + result = PQexec(connection,query.c_str()); + if( (PQresultStatus(result) != PGRES_TUPLES_OK) && (PQresultStatus(result) != PGRES_COMMAND_OK)) + { + printf("Database error: %s", PQerrorMessage(connection)); + PQclear(result); + return 0; + } + return result; +} + +string VSFPgSQLPlugin::getTimeStamp() +{ + stringstream timestamp; + struct tm tstamp; + time_t rawtime; + time ( &rawtime ); + gmtime_r(&rawtime,&tstamp); + timestamp << setw(4) << right << setfill('0') << 1900+tstamp.tm_year << "-" + << setw(2) << right << setfill('0') << tstamp.tm_mon + 1 << "-" + << setw(2) << right << setfill('0') << tstamp.tm_mday << " " + << setw(2) << right << setfill('0') << tstamp.tm_hour << ":" + << setw(2) << right << setfill('0') << tstamp.tm_min << ":" + << setw(2) << right << setfill('0') << tstamp.tm_sec; + return timestamp.str(); +} + +string VSFPgSQLPlugin::getTimeStamp(struct timeval tval) +{ + stringstream timestamp; + struct tm tstamp; + gmtime_r(&(tval.tv_sec),&tstamp); + timestamp << setw(4) << right << setfill('0') << 1900+tstamp.tm_year << "-" + << setw(2) << right << setfill('0') << tstamp.tm_mon + 1<< "-" + << setw(2) << right << setfill('0') << tstamp.tm_mday << " " + << setw(2) << right << setfill('0') << tstamp.tm_hour << ":" + << setw(2) << right << setfill('0') << tstamp.tm_min << ":" + << setw(2) << right << setfill('0') << tstamp.tm_sec; + return timestamp.str(); +} + +string VSFPgSQLPlugin::getIP(const struct sockaddr_in & sa_in ) +{ + char * ipaddr; + ipaddr=new char[16]; + if(sa_in.sin_family==AF_INET) + { + inet_ntop(sa_in.sin_family,&(sa_in.sin_addr),ipaddr,16); + } + else + { + printf("VSFPostgreSQL: vsftp-database does not support IPv6\n"); + } + string ipstring; + ipstring.assign(ipaddr); + return ipstring; +} + +long long int VSFPgSQLPlugin::getUid(string username) +{ + stringstream query; + long long int uid; + PGresult * result; + query << "SELECT uid FROM users WHERE name = '" << sanitize(username) << "';"; + result = queryDB(query.str()); + if((result==NULL)||(PQntuples(result)!=1)) + { + PQclear(result); + printf("VSPostgreSQL:NO SUCH USER!\n"); + return -1; + } + else + { + sscanf(PQgetvalue(result,0,0),"%lld",&uid); + PQclear(result); + return uid; + } +} + +long long int VSFPgSQLPlugin::getEid(const struct Event_Shared sharedInfo) +{ + long long int eid; + stringstream query; + PGresult * result; + + long long int uid; + uid =getUid(sharedInfo.user_name); + if(uid==-1) { + query << "SELECT eid FROM event WHERE uid IS NULL AND timestamp = '" << getTimeStamp(sharedInfo.timestamp) << "' AND ip_address = '" << getIP(sharedInfo.remote_ip) << "';"; + } else { + query << "SELECT eid FROM event WHERE uid = " << uid << " AND timestamp = '" << getTimeStamp(sharedInfo.timestamp) << "' AND ip_address = '" << getIP(sharedInfo.remote_ip) << "';"; + } + result = queryDB(query.str()); + if((result==NULL)||(PQntuples(result)!=1)) + { + PQclear(result); + printf("VSPgSQL: TOO MANY SIMILAR EVENTS!\n"); + return -1; + } + else + { + sscanf(PQgetvalue(result,0,0),"%lld",&eid); + PQclear(result); + return eid; + } +} + +string VSFPgSQLPlugin::sanitize(const string & original) +{ + stringstream sanitized; + char * escaped; + escaped = new char[2*original.length() + 1]; + PQescapeStringConn(_dbconn,escaped,original.c_str(),original.length(),NULL); + sanitized << escaped; + delete [] escaped; + return sanitized.str(); +} + +VSFPgSQLPlugin::VSFPgSQLPlugin(VSFPluginArchitecture & arch) : VSFPlugin(arch), PlugArch(arch) +{ + fprintf(stderr,"Welcome to PgSQLPlugin...\n"); + stringstream query; + char *conninfo; + + arch.registerRequestHandler(*this, Request_Number_Login); + arch.registerRequestHandler(*this, Request_Number_ReadFile); + arch.registerRequestHandler(*this, Request_Number_ReadDirectory); + arch.registerRequestHandler(*this, Request_Number_WriteFile); + arch.registerRequestHandler(*this, Request_Number_WriteDirectory); + arch.registerRequestHandler(*this, Request_Number_Homedir); + + arch.registerEventHandler(*this, Event_Number_Chmod); + arch.registerEventHandler(*this, Event_Number_Connection); + arch.registerEventHandler(*this, Event_Number_Delete); + arch.registerEventHandler(*this, Event_Number_Download); + arch.registerEventHandler(*this, Event_Number_FailedLogin); + arch.registerEventHandler(*this, Event_Number_Login); + arch.registerEventHandler(*this, Event_Number_Mkdir); + arch.registerEventHandler(*this, Event_Number_Rename); + arch.registerEventHandler(*this, Event_Number_Rmdir); + arch.registerEventHandler(*this, Event_Number_Upload); + arch.registerEventHandler(*this, Event_Number_CliEnd); + fprintf(stderr,"VSPostgreSQL: all handlers registered...\n"); + //init connection to the database and check if the tables exist + //connect to the DB + conninfo = new char[120]; + sprintf(conninfo,"dbname = %s host = %s port = %d user = %s password = %s", + tunable_pgsql_database_dbname,tunable_pgsql_database_host,tunable_pgsql_database_port, + tunable_pgsql_database_username,tunable_pgsql_database_password); + printf("VSPostgreSQL: connecting to database...\n"); + _dbconn = PQconnectdb(conninfo); + if (PQstatus(_dbconn) != CONNECTION_OK) + { + fprintf(stderr, "VSPostgreSQL: connection to database failed: %s",PQerrorMessage(_dbconn)); + PQfinish(_dbconn); + exit(-1); + } + else + { + fprintf(stderr,"VSPostgreSQL: connection established...\n"); + } + + //connected to the database + //checking if tables exist + + //TODO + //#warning TODO:check if tables exist + //add server startup to logs + query << "INSERT INTO event (eid, uid, timestamp, ip_address, event_type) VALUES (DEFAULT, NULL, '" << getTimeStamp() << "', '', 'Server Startup');"; + queryDB(query.str()); + //queryDB("COMMIT;"); +} + +VSFPgSQLPlugin::~VSFPgSQLPlugin() +{ + stringstream query; + fprintf(stderr,"Shutting down PgSQLPlugin...\n"); + //update event log + query << "INSERT INTO event (eid, uid, timestamp, ip_address, event_type) VALUES (DEFAULT, NULL, '" << getTimeStamp() << "','','Server Shutdown');"; + queryDB(query.str()); + //queryDB("COMMIT;"); + + //disconnect from database + PQfinish(_dbconn); + + //unregister from handling all events and request + PlugArch.unregisterEventHandler(*this, Event_Number_Chmod); + PlugArch.unregisterEventHandler(*this, Event_Number_Connection); + PlugArch.unregisterEventHandler(*this, Event_Number_Delete); + PlugArch.unregisterEventHandler(*this, Event_Number_Download); + PlugArch.unregisterEventHandler(*this, Event_Number_FailedLogin); + PlugArch.unregisterEventHandler(*this, Event_Number_Login); + PlugArch.unregisterEventHandler(*this, Event_Number_Mkdir); + PlugArch.unregisterEventHandler(*this, Event_Number_Rename); + PlugArch.unregisterEventHandler(*this, Event_Number_Rmdir); + PlugArch.unregisterEventHandler(*this, Event_Number_Upload); + PlugArch.unregisterEventHandler(*this, Event_Number_CliEnd); + + PlugArch.unregisterRequestHandler(*this, Request_Number_Login); + PlugArch.unregisterRequestHandler(*this, Request_Number_ReadFile); + PlugArch.unregisterRequestHandler(*this, Request_Number_ReadDirectory); + PlugArch.unregisterRequestHandler(*this, Request_Number_WriteFile); + PlugArch.unregisterRequestHandler(*this, Request_Number_WriteDirectory); + PlugArch.unregisterRequestHandler(*this, Request_Number_Homedir); + fprintf(stderr,"VSPostgreSQL: all handlers unregistered...\n"); +} + +void VSFPgSQLPlugin::handleEvent(const EventChmod & event) +{ + int file_true; + + PGresult * result; + stringstream query; + long long int uid; + int old_owner_write,old_owner_read,new_owner_write,new_owner_read; + int old_group_write,old_group_read,new_group_write,new_group_read; + int old_others_write,old_others_read,new_others_write,new_others_read; + + file_true=0; + query.str(""); + query << "SELECT owner_read, owner_write, group_read, group_write, others_read, others_write FROM directories WHERE path = '" << sanitize(event.getData()->path) << sanitize(event.getData()->name) << "';"; + result = queryDB(query.str()); + if((result==NULL)||(PQntuples(result)!=1)) + { + query.str(""); + query << "SELECT f.owner_read, f.owner_write, f.group_read, f.group_write, f.others_read, f.others_write FROM files f JOIN directories d ON d.did = f.directory WHERE f.name = '" << sanitize(event.getData()->name) << "' AND d.path = '" << unslash(sanitize(event.getData()->path)) << "';"; + result = queryDB(query.str()); + file_true=1; + if((result==NULL)||(PQntuples(result)!=1)) + { + PQclear(result); + throw PgSQLFileEntryNotFound(); + } + } + //changing from the octal representation to the separate bit's representation + new_owner_write=0; + new_owner_read=0; + new_group_write=0; + new_group_read=0; + new_others_write=0; + new_others_read=0; + + if(((event.getData()->new_attrib[0]) & 0x2)>0 ) + { + new_owner_write=1; + } + if(((event.getData()->new_attrib[0]) & 0x4)>0 ) + { + new_owner_read=1; + } + if(((event.getData()->new_attrib[1]) & 0x2)>0 ) + { + new_group_write=1; + } + if(((event.getData()->new_attrib[1]) & 0x4)>0 ) + { + new_group_read=1; + } + if(((event.getData()->new_attrib[2]) & 0x2)>0 ) + { + new_others_write=1; + } + if(((event.getData()->new_attrib[2]) & 0x4)>0 ) + { + new_others_read=1; + } + //interpreting selected data + old_owner_read=(strcmp(PQgetvalue(result,0,0),"0")==0)?0:1; + old_owner_write=(strcmp(PQgetvalue(result,0,1),"0")==0)?0:1; + old_group_read=(strcmp(PQgetvalue(result,0,2),"0")==0)?0:1; + old_group_write=(strcmp(PQgetvalue(result,0,3),"0")==0)?0:1; + old_others_read=(strcmp(PQgetvalue(result,0,4),"0")==0)?0:1; + old_others_write=(strcmp(PQgetvalue(result,0,5),"0")==0)?0:1; + + // doing the CHMOD + if (event.getData()->succeeded==0) { + query.str(""); + if(file_true!=0) { + //modifying file + query << "UPDATE files SET owner_read = "<< new_owner_read << " , owner_write = " << + new_owner_write << " , group_read = " << new_group_read << " , group_write = " << + new_group_write << " , others_read = " << new_others_read << " , others_write = " << + new_others_write << " WHERE fid = (SELECT fid FROM files f JOIN directories d ON f.directory = d.did" << + " WHERE f.name = '" << sanitize(event.getData()->name) << "' AND d.path = '" << + unslash(sanitize(event.getData()->path)) << "';"; + } + else { + //modifying directories + query << "UPDATE directories SET owner_read = "<< new_owner_read << " , owner_write = " << + new_owner_write << " , group_read = " << new_group_read << " , group_write = " << + new_group_write << " , others_read = " << new_others_read << " , others_write = " << + new_others_write << " WHERE did = (SELECT did FROM directories" << + " WHERE path = '" << sanitize(event.getData()->path) << sanitize(event.getData()->path) + << "';"; + } + queryDB(query.str()); + //queryDB("COMMIT;"); + } + + PQclear(result); + + query.str(""); + uid=getUid(event.getData()->sharedInfo.user_name); + if(uid==-1) { + query << "INSERT INTO event (eid,uid,timestamp,ip_address,event_type) VALUES (DEFAULT, NULL,'" << getTimeStamp(event.getData()->sharedInfo.timestamp) << "','" << getIP(event.getData()->sharedInfo.remote_ip) << "', 'Chmod');"; + } else { + query << "INSERT INTO event (eid,uid,timestamp,ip_address,event_type) VALUES (DEFAULT, " << uid << ",'" << getTimeStamp(event.getData()->sharedInfo.timestamp) << "','" << getIP(event.getData()->sharedInfo.remote_ip) << "', 'Chmod');"; + } + queryDB(query.str()); + //queryDB("COMMIT;"); + + query.str(""); + query << "INSERT INTO event_chmod (eid,path,old_owner_write,old_owner_read,old_group_write,old_group_read,old_others_write,old_others_read,new_owner_write,new_owner_read,new_group_write,new_group_read,new_others_write,new_others_read,succeeded) VALUES (" << getEid(event.getData()->sharedInfo) << ", '" << sanitize(event.getData()->path) << sanitize(event.getData()->name) << "', " << old_owner_write << ", " << old_owner_read << ", " << old_group_write << ", " << old_group_read << ", " << old_others_write << ", " << old_others_read << ", " << new_owner_write << ", " << new_owner_read << ", " << new_group_write << ", " << new_group_read << ", " << new_others_write << ", " << new_others_read << ", " << event.getData()->succeeded << ");"; + queryDB(query.str()); + //queryDB("COMMIT;"); +} + +void VSFPgSQLPlugin::handleEvent(const EventConnection & event) +{ + stringstream query; +printf("shit happens\n"); + long long int uid; + uid = getUid(event.getData()->sharedInfo.user_name); + if(uid==-1) { + query << "INSERT INTO event (eid,uid,timestamp,ip_address,event_type) VALUES (DEFAULT, NULL,'" << getTimeStamp(event.getData()->sharedInfo.timestamp) << "','" << getIP(event.getData()->sharedInfo.remote_ip) << "', 'Connection');"; + } else { + query << "INSERT INTO event (eid,uid,timestamp,ip_address,event_type) VALUES (DEFAULT, " << uid << ",'" << getTimeStamp(event.getData()->sharedInfo.timestamp) << "','" << getIP(event.getData()->sharedInfo.remote_ip) << "', 'Connection');"; + } + queryDB(query.str()); + //queryDB("COMMIT;"); + + query.str(""); + query << "INSERT INTO event_connection (eid,reason) VALUES (" << getEid(event.getData()->sharedInfo) << ", '" << sanitize(event.getData()->reason) << "');"; + + queryDB(query.str()); + //queryDB("COMMIT;"); +} + +void VSFPgSQLPlugin::handleEvent(const EventDelete & event) +{ + stringstream query; + + long long int uid; + uid = getUid(event.getData()->sharedInfo.user_name); + if(uid==-1) { + query << "INSERT INTO event (eid,uid,timestamp,ip_address,event_type) VALUES (DEFAULT, NULL,'" << getTimeStamp(event.getData()->sharedInfo.timestamp) << "','" << getIP(event.getData()->sharedInfo.remote_ip) << "', 'Delete');"; + } else { + query << "INSERT INTO event (eid,uid,timestamp,ip_address,event_type) VALUES (DEFAULT, " << uid << ",'" << getTimeStamp(event.getData()->sharedInfo.timestamp) << "','" << getIP(event.getData()->sharedInfo.remote_ip) << "', 'Delete');"; + } + queryDB(query.str()); + //queryDB("COMMIT;"); + + if(event.getData()->succeeded == 0) + { + std::pair filnam; + filnam = VSFUtils::sanitizePath(event.getData()->path,"" ); + query.str(""); + query << "DELETE FROM files WHERE fid=(SELECT fid FROM files f JOIN directories d ON f.directory = d.did WHERE f.name = '" + << sanitize(filnam.second) << "' AND d.path = '" << unslash(sanitize(filnam.first)) << "' );"; + queryDB(query.str()); +// queryDB("COMMIT;"); + } + + + query.str(""); + query << "INSERT INTO event_delete (eid,path,succeeded) VALUES (" << getEid(event.getData()->sharedInfo) << ", '" << sanitize(event.getData()->path) << "', '"<< event.getData()->succeeded << "');"; + + queryDB(query.str()); + //queryDB("COMMIT;"); + +} + +void VSFPgSQLPlugin::handleEvent(const EventDownload & event) +{ + stringstream query; + + long long int uid; + uid = getUid(event.getData()->sharedInfo.user_name); + if(uid==-1) { + query << "INSERT INTO event (eid,uid,timestamp,ip_address,event_type) VALUES (DEFAULT, NULL,'" << getTimeStamp(event.getData()->sharedInfo.timestamp) << "','" << getIP(event.getData()->sharedInfo.remote_ip) << "', 'Download');"; + } else { + query << "INSERT INTO event (eid,uid,timestamp,ip_address,event_type) VALUES (DEFAULT, " << uid << ",'" << getTimeStamp(event.getData()->sharedInfo.timestamp) << "','" << getIP(event.getData()->sharedInfo.remote_ip) << "', 'Download');"; + } + queryDB(query.str()); + //queryDB("COMMIT;"); + query.str(""); + query << "INSERT INTO event_download (eid,path,filename,size,is_complete,) VALUES (" << getEid(event.getData()->sharedInfo) << ", '" << unslash(sanitize(event.getData()->fileInfo.path)) << "', '" << sanitize(event.getData()->fileInfo.name) << "', " << event.getData()->fileInfo.size << ", " << event.getData()->is_complete << ");"; + + queryDB(query.str()); + //queryDB("COMMIT;"); +} + +void VSFPgSQLPlugin::handleEvent(const EventFailedLogin & event) +{ + stringstream query; + + long long int uid; + uid = getUid(event.getData()->sharedInfo.user_name); + if(uid==-1) { + query << "INSERT INTO event (eid,uid,timestamp,ip_address,event_type) VALUES (DEFAULT, NULL,'" << getTimeStamp(event.getData()->sharedInfo.timestamp) << "','" << getIP(event.getData()->sharedInfo.remote_ip) << "', 'FailedLogin');"; + } else { + query << "INSERT INTO event (eid,uid,timestamp,ip_address,event_type) VALUES (DEFAULT, " << uid << ",'" << getTimeStamp(event.getData()->sharedInfo.timestamp) << "','" << getIP(event.getData()->sharedInfo.remote_ip) << "', 'FailedLogin');"; + } + queryDB(query.str()); + //queryDB("COMMIT;"); + query.str(""); + query << "INSERT INTO event_failedlogin (eid,failed_password) VALUES (" << getEid(event.getData()->sharedInfo) << ", '" << sanitize(event.getData()->failed_password) << "');"; + queryDB(query.str()); + //queryDB("COMMIT;"); +} + +void VSFPgSQLPlugin::handleEvent(const EventLogin & event) +{ + fprintf(stderr,"Handling event: EventLogin...\n"); + stringstream query; + + long long int uid; + uid = getUid(event.getData()->sharedInfo.user_name); + if(uid==-1) { + query << "INSERT INTO event (eid,uid,timestamp,ip_address,event_type) VALUES (DEFAULT, NULL,'" << getTimeStamp(event.getData()->sharedInfo.timestamp) << "','" << getIP(event.getData()->sharedInfo.remote_ip) << "', 'Login');"; + } else { + query << "INSERT INTO event (eid,uid,timestamp,ip_address,event_type) VALUES (DEFAULT, " << uid << ",'" << getTimeStamp(event.getData()->sharedInfo.timestamp) << "','" << getIP(event.getData()->sharedInfo.remote_ip) << "', 'Login');"; + } + queryDB(query.str()); + //queryDB("COMMIT;"); + query.str(""); + query << "INSERT INTO event_login (eid,is_anonymous) VALUES (" << getEid(event.getData()->sharedInfo) << ", " << event.getData()->is_anonymous << ");"; + queryDB(query.str()); + //queryDB("COMMIT;"); +} + +void VSFPgSQLPlugin::handleEvent(const EventMkdir & event) +{ + stringstream query; + + long long int uid; + uid = getUid(event.getData()->sharedInfo.user_name); + if(uid==-1) { + query << "INSERT INTO event (eid,uid,timestamp,ip_address,event_type) VALUES (DEFAULT, NULL,'" << getTimeStamp(event.getData()->sharedInfo.timestamp) << "','" << getIP(event.getData()->sharedInfo.remote_ip) << "', 'MkDir');"; + } else { + query << "INSERT INTO event (eid,uid,timestamp,ip_address,event_type) VALUES (DEFAULT, " << uid << ",'" << getTimeStamp(event.getData()->sharedInfo.timestamp) << "','" << getIP(event.getData()->sharedInfo.remote_ip) << "', 'MkDir');"; + } + + queryDB(query.str()); + //queryDB("COMMIT;"); + query.str(""); + query << "INSERT INTO event_mkdir (eid,path,succeeded) VALUES (" << getEid(event.getData()->sharedInfo) << ", '" << unslash(sanitize(event.getData()->path)) << "', " << event.getData()->succeeded << ");"; + + queryDB(query.str()); +// queryDB("COMMIT;"); + +//adding dir to the database (if succeded) + + if(event.getData()->succeeded == 0) + { + query.str(""); + query << "INSERT INTO directories (did, path,owner,dir_group, owner_read, owner_write, group_read, group_write, others_read , others_write) VALUES (DEFAULT, '"<path)) << "', "<< ((uid!=-1)?(uid):0) <<" , 0 , " << ((tunable_local_umask & 0400)?0:1) << " , " << ((tunable_local_umask & 0200)?0:1) << " , " << ((tunable_local_umask & 040)?0:1) << " , " << ((tunable_local_umask & 020)?0:1) << " , " << ((tunable_local_umask & 04)?0:1) << " , " << ((tunable_local_umask & 02)?0:1) << " ); "; + queryDB(query.str()); + // queryDB("COMMIT;"); + } + +} + +void VSFPgSQLPlugin::handleEvent(const EventRename & event) +{ + stringstream query; + + long long int uid; + uid = getUid(event.getData()->sharedInfo.user_name); + if(uid==-1) { + query << "INSERT INTO event (eid,uid,timestamp,ip_address,event_type) VALUES (DEFAULT, NULL,'" << getTimeStamp(event.getData()->sharedInfo.timestamp) << "','" << getIP(event.getData()->sharedInfo.remote_ip) << "', 'Rename');"; + } else { + query << "INSERT INTO event (eid,uid,timestamp,ip_address,event_type) VALUES (DEFAULT, " << uid << ",'" << getTimeStamp(event.getData()->sharedInfo.timestamp) << "','" << getIP(event.getData()->sharedInfo.remote_ip) << "', 'Rename');"; + } + + queryDB(query.str()); + //queryDB("COMMIT;"); + query.str(""); + query << "INSERT INTO event_rename (eid,oldname,newname,succeeded) VALUES (" << getEid(event.getData()->sharedInfo) << ", '" << sanitize(event.getData()->old_name) << "', '"<< sanitize(event.getData()->new_name) << "', " << event.getData()->succeeded << "');"; + + queryDB(query.str()); +// queryDB("COMMIT;"); + +//changing the file name in the database (if succeded) +//TODO add separate dbconn + + if(event.getData()->succeeded==0) + { + std::pair oldnam; + std::pair newnam; + oldnam=VSFUtils::sanitizePath(event.getData()->old_name,""); + newnam=VSFUtils::sanitizePath(event.getData()->new_name,""); + query.str(""); + //modifying file + query << "UPDATE files SET name= '" << sanitize(newnam.second) << "' , directory = (SELECT did FROM directories WHERE path='" << sanitize(newnam.first) << "') WHERE fid = (SELECT fid FROM files f JOIN directories d ON f.directory = d.did WHERE f.name = '" + << sanitize(oldnam.second) << "' AND d.path = '" << sanitize(oldnam.first ) << + "');"; + queryDB(query.str()); +// queryDB("COMMIT;"); + } + +} + +void VSFPgSQLPlugin::handleEvent(const EventRmdir & event) +{ + stringstream query; + + long long int uid; + uid = getUid(event.getData()->sharedInfo.user_name); + if(uid==-1) { + query << "INSERT INTO event (eid,uid,timestamp,ip_address,event_type) VALUES (DEFAULT, NULL,'" << getTimeStamp(event.getData()->sharedInfo.timestamp) << "','" << getIP(event.getData()->sharedInfo.remote_ip) << "', 'RmDir');"; + } else { + query << "INSERT INTO event (eid,uid,timestamp,ip_address,event_type) VALUES (DEFAULT, " << uid << ",'" << getTimeStamp(event.getData()->sharedInfo.timestamp) << "','" << getIP(event.getData()->sharedInfo.remote_ip) << "', 'RmDir');"; + } + + queryDB(query.str()); + //queryDB("COMMIT;"); + + query.str(""); + query << "INSERT INTO event_rmdir (eid,path,succeeded) VALUES (" << getEid(event.getData()->sharedInfo) << ", '" << unslash(sanitize(event.getData()->path)) << "', " << event.getData()->succeeded << ");"; + + queryDB(query.str()); +// queryDB("COMMIT;"); + + //removing dir from the database (if succeded) + if(event.getData()->succeeded == 0) + { + query.str(""); + query << "DELETE FROM directories WHERE did=(SELECT did FROM directories WHERE path = '" + << unslash(sanitize(event.getData()->path)) << "' );"; + queryDB(query.str()); +// queryDB("COMMIT;"); + + } + +} + +void VSFPgSQLPlugin::handleEvent(const EventCliEnd & event) +{ + stringstream query; + + query << "INSERT INTO event (eid,uid,timestamp,ip_address,event_type) VALUES (DEFAULT, NULL, '" << getTimeStamp(event.getData()->timestamp) << "','', 'CliEnd');"; + queryDB(query.str()); +// queryDB("COMMIT;"); +} + +void VSFPgSQLPlugin::handleEvent(const EventUpload & event) +{ + stringstream query; + + long long int uid; + uid = getUid(event.getData()->sharedInfo.user_name); + if(uid==-1) { + query << "INSERT INTO event (eid,uid,timestamp,ip_address,event_type) VALUES (DEFAULT, NULL,'" << getTimeStamp(event.getData()->sharedInfo.timestamp) << "','" << getIP(event.getData()->sharedInfo.remote_ip) << "', 'Upload');"; + } else { + query << "INSERT INTO event (eid,uid,timestamp,ip_address,event_type) VALUES (DEFAULT, " << uid << ",'" << getTimeStamp(event.getData()->sharedInfo.timestamp) << "','" << getIP(event.getData()->sharedInfo.remote_ip) << "', 'Upload');"; + } + queryDB(query.str()); + //queryDB("COMMIT;"); + query.str(""); + query << "INSERT INTO event_upload (eid,path,filename,size,is_complete,is_append,is_overwrite) VALUES (" << getEid(event.getData()->sharedInfo) << ", '" << unslash(sanitize(event.getData()->fileInfo.path)) << "', '" << sanitize(event.getData()->fileInfo.name) << "', " << event.getData()->fileInfo.size << ", " << event.getData()->is_complete << ", " << event.getData()->is_append << ", " << event.getData()->is_overwrite << ");"; + + queryDB(query.str()); +// queryDB("COMMIT;"); + +//creating database entry (if succeded) + + if((event.getData()->is_complete==1)&&(event.getData()->is_append==0)&&(event.getData()->is_overwrite==0)) //complete and not append or overwrite -> insert a file + { + query.str(""); + query << "INSERT INTO files (fid,name,directory,owner,file_group,owner_read,owner_write,group_read,group_write,others_read,others_write) VALUES (DEFAULT, '"<< sanitize(event.getData()->fileInfo.name) << "' , (SELECT did FROM directories WHERE path = '" << unslash(sanitize(event.getData()->fileInfo.path)) << "') , "<< ((uid!=-1)?(uid):0) <<" , 0 , " << ((tunable_local_umask & 0400)?0:1) << " , " << ((tunable_local_umask & 0200)?0:1) << " , " << ((tunable_local_umask & 040)?0:1) << " , " << ((tunable_local_umask & 020)?0:1) << " , " << ((tunable_local_umask & 04)?0:1) << " , " << ((tunable_local_umask & 02)?0:1) << " ) ;"; + queryDB(query.str()); +// queryDB("COMMIT;"); + } + +} + +string VSFPgSQLPlugin::unslash(const string & original) +{ + return unslash(original.c_str()); +} +string VSFPgSQLPlugin::unslash(const char * original) +{ + char * escaped; + stringstream retval; + if(strlen(original) <= 1) + { + retval << original; + return retval.str(); + } + escaped = strdup(original); + if(strlen(escaped)>1 && escaped[strlen(escaped)-1]=='/') + { + escaped[strlen(escaped)-1]=0; + } + retval << escaped; + if(escaped) + { + free(escaped); + } + return retval.str(); +} + + +Request_Result VSFPgSQLPlugin::handleRequest(const RequestLogin & r) +{ + fprintf(stderr,"Handling request: RequestLogin"); + struct Request_Result retStruct = Request_Result_Init; + stringstream query; + PGresult * result; + + retStruct.result=-1; + query << "SELECT * FROM users WHERE name='" << sanitize(r.getData()->login) << "' AND passwd=MD5('" << sanitize(r.getData()->password) << "');"; + +// checkFolders() TODO +//#warning TODO: check if folders exists + + //connect to database + PGconn * _reqconn; + connectDB(_reqconn); + //execute query - check if user and password match + result=queryDB(query.str(), _reqconn); + if((result==NULL)||(PQntuples(result)==1)) + { + retStruct.result=0; + query.str(""); + //update last login info + query << "UPDATE users SET last_login=timestamp '" << sanitize(getTimeStamp()) << "' WHERE name='" << sanitize(r.getData()->login) << "'; "; + queryDB(query.str(), _reqconn); + //queryDB("COMMIT;", _reqconn); + } + //disconnect from DB + PQfinish(_reqconn); + return retStruct; +} + + +Request_Result_And_Reason VSFPgSQLPlugin::handleRequest(const RequestReadFile & r) { + struct Request_Result_And_Reason retStruct = Request_Result_And_Reason_Init; + PGconn* _reqconn; + PGresult * result; + PGresult * perm_result; + stringstream query; + long long int uid; + long long int ownerid; + + connectDB(_reqconn); + //get uid + query << "SELECT uid FROM users WHERE name = '" << sanitize(r.getData()->sharedInfo.user_name) << "';"; + result = queryDB(query.str(),_reqconn); + if((result==NULL)||(PQntuples(result)!=1)) + { + retStruct.result=-1; + retStruct.reason=strdup("The user could not be found in the database\n"); + PQclear(result); + PQfinish(_reqconn); + return retStruct; + } + sscanf(PQgetvalue(result,0,0),"%lld",&uid); + PQclear(result); + query.str(""); + + //get owner and permissions + query << "SELECT f.owner, f.file_group, f.owner_read, f.group_read, f.others_read FROM files f JOIN directories d ON d.did = f.directory WHERE d.path='" << unslash(sanitize(r.getData()->path)) << "' AND f.name='" << sanitize(r.getData()->name) << "';"; + perm_result=queryDB(query.str(), _reqconn); + if((perm_result==NULL)||(PQntuples(perm_result)!=1)) + { + retStruct.result=-1; + retStruct.reason=strdup("File could not be found in the database\n"); + PQclear(perm_result); + PQfinish(_reqconn); + return retStruct; + } + + sscanf(PQgetvalue(perm_result,0,0),"%lld",&ownerid); + if(uid==ownerid) + { + //login is owner + if(strcmp(PQgetvalue(perm_result,0,2),"0")==0) + { + retStruct.result=-1; + retStruct.reason=strdup("You don't have permissions to read this file\n"); + } + else + { + retStruct.result=0; + retStruct.reason=strdup("OK\n"); + } + PQclear(perm_result); + PQfinish(_reqconn); + return retStruct; + } + //checking if uid is in the group + query.str(""); + query << "SELECT uid FROM users_groups WHERE gid = " << sanitize(PQgetvalue(perm_result,0,1)) << " AND uid = " << uid << " ;"; + result = queryDB(query.str(),_reqconn); + if((result==NULL)||(PQntuples(result)==1)) + { + //login is in the same group as the file + if(strcmp(PQgetvalue(perm_result,0,3),"0")==0) + { + retStruct.result=-1; + retStruct.reason=strdup("You don't have permissions to read this file\n"); + } + else + { + retStruct.result=0; + retStruct.reason=strdup("OK\n"); + } + PQclear(result); + PQclear(perm_result); + PQfinish(_reqconn); + return retStruct; + } + PQclear(result); + + //else login is somebody else + if(strcmp(PQgetvalue(perm_result,0,4),"0")==0) + { + retStruct.result=-1; + retStruct.reason=strdup("You don't have permissions to read this file\n"); + } + else + { + retStruct.result=0; + retStruct.reason=strdup("OK\n"); + } + PQclear(perm_result); + PQfinish(_reqconn); + return retStruct; +} + + +Request_Result_And_Reason VSFPgSQLPlugin::handleRequest(const RequestReadDirectory & r) { + struct Request_Result_And_Reason retStruct = Request_Result_And_Reason_Init; + PGconn* _reqconn; + PGresult * result; + PGresult * perm_result; + stringstream query; + long long int uid; + long long int ownerid; + + //get owner and permissions + query << "SELECT owner, dir_group, owner_read, group_read, others_read FROM directories WHERE path='" << sanitize(r.getData()->parentPath) << sanitize(r.getData()->name) << "';"; + + connectDB(_reqconn); + perm_result=queryDB(query.str(), _reqconn); + if((perm_result==NULL)||(PQntuples(perm_result)!=1)) + { + retStruct.result=-1; + retStruct.reason=strdup("Directory could not be found in the database\n"); + PQclear(perm_result); + PQfinish(_reqconn); + return retStruct; + } + + //get uid an compare + query.str(""); + query << "SELECT uid FROM users WHERE name = '" << sanitize(r.getData()->sharedInfo.user_name) << "';"; + result = queryDB(query.str(),_reqconn); + if((result==NULL)||(PQntuples(result)!=1)) + { + retStruct.result=-1; + retStruct.reason=strdup("The user could not be found in the database\n"); + PQclear(perm_result); + PQclear(result); + PQfinish(_reqconn); + return retStruct; + } + sscanf(PQgetvalue(result,0,0),"%lld",&uid); + PQclear(result); + + sscanf(PQgetvalue(perm_result,0,0),"%lld",&ownerid); + if(uid==ownerid) + { + //login is owner + if(strcmp(PQgetvalue(perm_result,0,2),"0")==0) + { + retStruct.result=-1; + retStruct.reason=strdup("You don't have permissions to read this directory\n"); + } + else + { + retStruct.result=0; + retStruct.reason=strdup("OK\n"); + } + PQclear(perm_result); + PQfinish(_reqconn); + return retStruct; + } + + //checking if uid is in the group + query.str(""); + query << "SELECT uid FROM users_groups WHERE gid = " << sanitize(PQgetvalue(perm_result,0,1)) << " AND uid = " << uid << " ;"; + result = queryDB(query.str(),_reqconn); + if((result==NULL)||(PQntuples(result)==1)) + { + //login is in the same group as the file + if(strcmp(PQgetvalue(perm_result,0,3),"0")==0) + { + retStruct.result=-1; + retStruct.reason=strdup("You don't have permissions to read this directory\n"); + } + else + { + retStruct.result=0; + retStruct.reason=strdup("OK\n"); + } + PQclear(result); + PQclear(perm_result); + PQfinish(_reqconn); + return retStruct; + + } + PQclear(result); + + //else login is somebody else + if(strcmp(PQgetvalue(perm_result,0,4),"0")==0) + { + retStruct.result=-1; + retStruct.reason=strdup("You don't have permissions to read this directory\n"); + } + else + { + retStruct.result=0; + retStruct.reason=strdup("OK\n"); + } + PQclear(perm_result); + PQfinish(_reqconn); + return retStruct; +} + + +Request_Result_And_Reason VSFPgSQLPlugin::handleRequest(const RequestWriteFile & r) { + struct Request_Result_And_Reason retStruct = Request_Result_And_Reason_Init; + PGconn* _reqconn; + PGresult * result; + PGresult * perm_result; + stringstream query; + long long int uid; + long long int ownerid; + + //get owner and permissions + query << "SELECT f.owner, f.file_group, f.owner_write, f.group_write, f.others_write FROM files f JOIN directories d ON d.did = f.directory WHERE d.path='" << unslash(sanitize(r.getData()->path)) << "' AND f.name='" << sanitize(r.getData()->name) << "';"; + + connectDB(_reqconn); + perm_result=queryDB(query.str(), _reqconn); + if((perm_result==NULL)||(PQntuples(perm_result)!=1)) + { + retStruct.result=-1; + retStruct.reason=strdup("File could not be found in the database\n"); + PQclear(perm_result); + PQfinish(_reqconn); + return retStruct; + } + + //get uid an compare + query.str(""); + query << "SELECT uid FROM users WHERE name = '" << sanitize(r.getData()->sharedInfo.user_name) << "';"; + result = queryDB(query.str(),_reqconn); + if((result==NULL)||(PQntuples(result)!=1)) + { + retStruct.result=-1; + retStruct.reason=strdup("The user could not be found in the database\n"); + PQclear(result); + PQclear(perm_result); + PQfinish(_reqconn); + return retStruct; + } + sscanf(PQgetvalue(result,0,0),"%lld",&uid); + PQclear(result); + + sscanf(PQgetvalue(perm_result,0,0),"%lld",&ownerid); + if(uid==ownerid) + { + //login is owner + if(strcmp(PQgetvalue(perm_result,0,2),"0")==0) + { + retStruct.result=-1; + retStruct.reason=strdup("You don't have permissions to write this file\n"); + } + else + { + retStruct.result=0; + retStruct.reason=strdup("OK\n"); + } + PQclear(perm_result); + PQfinish(_reqconn); + return retStruct; + } + + //checking if uid is in the group + query.str(""); + query << "SELECT uid FROM users_groups WHERE gid = " << sanitize(PQgetvalue(perm_result,0,1)) << " AND uid = " << uid << " ;"; + result = queryDB(query.str(),_reqconn); + if((result==NULL)||(PQntuples(result)==1)) + { + //login is in the same group as the file + if(strcmp(PQgetvalue(perm_result,0,3),"0")==0) + { + retStruct.result=-1; + retStruct.reason=strdup("You don't have permissions to write this file\n"); + } + else + { + retStruct.result=0; + retStruct.reason=strdup("OK\n"); + } + PQclear(result); + PQclear(perm_result); + PQfinish(_reqconn); + return retStruct; + } + PQclear(result); + + //else login is somebody else + if(strcmp(PQgetvalue(perm_result,0,4),"0")==0) + { + retStruct.result=-1; + retStruct.reason=strdup("You don't have permissions to write this file\n"); + } + else + { + retStruct.result=0; + retStruct.reason=strdup("OK\n"); + } + PQclear(perm_result); + PQfinish(_reqconn); + return retStruct; +} + + +Request_Result_And_Reason VSFPgSQLPlugin::handleRequest(const RequestWriteDirectory & r) { + struct Request_Result_And_Reason retStruct = Request_Result_And_Reason_Init; + PGconn* _reqconn; + PGresult * result; + PGresult * perm_result; + stringstream query; + long long int uid; + long long int ownerid; + + //get owner and permissions + query << "SELECT owner, dir_group, owner_write, group_write, others_write FROM directories WHERE path='" << sanitize(r.getData()->parentPath) << sanitize(r.getData()->name) << "';"; + + connectDB(_reqconn); + perm_result=queryDB(query.str(), _reqconn); + if((perm_result==NULL)||(PQntuples(perm_result)!=1)) + { + retStruct.result=-1; + retStruct.reason=strdup("Directory could not be found in the database\n"); + PQclear(perm_result); + PQfinish(_reqconn); + return retStruct; + } + + //get uid an compare + query.str(""); + query << "SELECT uid FROM users WHERE name = '" << sanitize(r.getData()->sharedInfo.user_name) << "';"; + result = queryDB(query.str(),_reqconn); + if((result==NULL)||(PQntuples(result)!=1)) + { + retStruct.result=-1; + retStruct.reason=strdup("The user could not be found in the database\n"); + PQclear(result); + PQclear(perm_result); + PQfinish(_reqconn); + return retStruct; + } + + sscanf(PQgetvalue(result,0,0),"%lld",&uid); + PQclear(result); + + sscanf(PQgetvalue(perm_result,0,0),"%lld",&ownerid); + if(uid==ownerid) + { + //login is owner + if(strcmp(PQgetvalue(perm_result,0,2),"0")==0) + { + retStruct.result=-1; + retStruct.reason=strdup("You don't have permissions to write this directory\n"); + } + else + { + retStruct.result=0; + retStruct.reason=strdup("OK\n"); + } + PQclear(perm_result); + PQfinish(_reqconn); + return retStruct; + } + + //checking if uid is in the group + query.str(""); + query << "SELECT uid FROM users_groups WHERE gid = " << sanitize(PQgetvalue(perm_result,0,1)) << " AND uid = " << uid << " ;"; + result = queryDB(query.str(),_reqconn); + if((result!=0)||(PQntuples(result)==1)) + { + //login is in the same group as the file + if(strcmp(PQgetvalue(perm_result,0,3),"0")==0) + { + retStruct.result=-1; + retStruct.reason=strdup("You don't have permissions to write this directory\n"); + } + else + { + retStruct.result=0; + retStruct.reason=strdup("OK\n"); + } + PQclear(result); + PQclear(perm_result); + PQfinish(_reqconn); + return retStruct; + } + PQclear(result); + + //else login is somebody else + if(strcmp(PQgetvalue(perm_result,0,4),"0")==0) + { + retStruct.result=-1; + retStruct.reason=strdup("You don't have permissions to write this directory\n"); + } + else + { + retStruct.result=0; + retStruct.reason=strdup("OK\n"); + } + PQclear(perm_result); + PQfinish(_reqconn); + return retStruct; +} + +Request_Result_Homedir VSFPgSQLPlugin::handleRequest(const RequestHomedir & r) +{ + struct Request_Result_Homedir retStruct = Request_Result_Homedir_Init; + stringstream query; + PGconn* _reqconn; + PGresult * result; + query.str(""); + query << "SELECT homedir FROM users WHERE name = '" << sanitize(r.getData()->sharedInfo.user_name) << "'; "; + connectDB(_reqconn); + result = queryDB(query.str(),_reqconn); + if((result!=0)||(PQntuples(result)==1)) + { + retStruct.homedir=PQgetvalue(result,0,0); + } + PQclear(result); + PQfinish(_reqconn); + return retStruct; +} diff --git a/pgsql_plugin.h b/pgsql_plugin.h new file mode 100644 index 0000000..69037f8 --- /dev/null +++ b/pgsql_plugin.h @@ -0,0 +1,57 @@ +#ifndef PGSQL_PLUGIN_H +#define PGSQL_PLUGIN_H +#include +#include +#include +#include +#include +#include +#include "plugin_architecture_api.h" +#include "libpq-fe.h" +#include "exceptions.h" +#include "tunables.h" +#include "vsfutils.h" + +using namespace std; + +class VSFPgSQLPlugin : public VSFPlugin +{ +private: + PGconn* _dbconn; + void connectDB(PGconn* &); + PGresult * queryDB(const string); + PGresult * queryDB(const string, PGconn *); + string getTimeStamp(); + string getTimeStamp(struct timeval); + string getIP(const struct sockaddr_in &); + long long int getUid(string username); + long long int getEid(const struct Event_Shared sharedInfo); + string sanitize(const string &); + VSFPluginArchitecture & PlugArch; + string unslash(const char * original); + string unslash(const string & original); + +public: + VSFPgSQLPlugin(VSFPluginArchitecture &); + ~VSFPgSQLPlugin(); + + void handleEvent(const EventChmod &); + void handleEvent(const EventConnection &); + void handleEvent(const EventDelete &); + void handleEvent(const EventDownload &); + void handleEvent(const EventFailedLogin &); + void handleEvent(const EventLogin &); + void handleEvent(const EventMkdir &); + void handleEvent(const EventRename &); + void handleEvent(const EventRmdir &); + void handleEvent(const EventCliEnd &); + void handleEvent(const EventUpload &); + + Request_Result handleRequest(const RequestLogin &); + Request_Result_And_Reason handleRequest(const RequestReadFile &); + Request_Result_And_Reason handleRequest(const RequestReadDirectory &); + Request_Result_And_Reason handleRequest(const RequestWriteDirectory &); + Request_Result_And_Reason handleRequest(const RequestWriteFile &); + Request_Result_Homedir handleRequest(const RequestHomedir &); +}; +#endif diff --git a/plugin_architecture_api.cpp b/plugin_architecture_api.cpp new file mode 100644 index 0000000..df85a9e --- /dev/null +++ b/plugin_architecture_api.cpp @@ -0,0 +1,169 @@ +#include "plugin_architecture_api.h" +#include "exceptions.h" +#include + +bool VSFPluginArchitecture::architectureExists = false; + +VSFPluginArchitecture::VSFPluginArchitecture() { + if (!architectureExists) + { + architectureExists = true; + } + else + { + throw TooManyPluginArchitectures(); + } + memset(pluginRequestHandlers,0, sizeof(VSFPlugin*)*(Request_Number_MaxNumber - Request_Identifier)); +} + +void VSFPluginArchitecture::registerEventHandler(VSFPlugin & handler, Event_Number_Enum eventNumber) +{ + if (eventNumber >= Event_Number_MaxNumber || eventNumber <= Event_Identifier) + { + throw std::out_of_range("eventNumber not in enum bounds"); + } + else + { + eventNumber = (Event_Number_Enum)(eventNumber - Event_Number_Identifier); + } + + pluginEventHandlers[eventNumber].push_back(&handler); +} + +void VSFPluginArchitecture::registerRequestHandler(VSFPlugin & handler, Request_Number_Enum requestNumber) +{ + if (requestNumber >= Request_Number_MaxNumber || requestNumber < Request_Identifier) + { + throw std::out_of_range("eventNumber not in enum bounds"); + } + else + { + requestNumber = (Request_Number_Enum)(requestNumber - Request_Number_Identifier); + } + + if (pluginRequestHandlers[requestNumber]) + { + throw TooManyRequestHandlers(); + } + else + { + pluginRequestHandlers[requestNumber] = &handler; + } +} + +void VSFPluginArchitecture::unregisterEventHandler(VSFPlugin & handler, Event_Number_Enum eventNumber) +{ + if (eventNumber >= Event_Number_MaxNumber || eventNumber <= Event_Identifier) + { + throw std::out_of_range("eventNumber not in enum bounds"); + } + else + { + eventNumber = (Event_Number_Enum)(eventNumber - Event_Number_Identifier); + } + + bool found = false; + + for (std::vector::iterator i = pluginEventHandlers[eventNumber].begin(); + i != pluginEventHandlers[eventNumber].end(); ++i) + { + if (*i == &handler) + { + pluginEventHandlers[eventNumber].erase(i); + found = true; + break; + } + } + if (!found) + { + throw UnregisteringNotRegisterd(); + } +} + +void VSFPluginArchitecture::unregisterRequestHandler(VSFPlugin & handler, Request_Number_Enum requestNumber) +{ + if (requestNumber >= Request_Number_MaxNumber || requestNumber < Request_Identifier) + { + throw std::out_of_range("requestNumber not in enum bounds"); + } + else + { + requestNumber = (Request_Number_Enum)(requestNumber - Request_Number_Identifier); + } + if (pluginRequestHandlers[requestNumber] == &handler) + { + pluginRequestHandlers[requestNumber] = 0; + } + else + { + throw UnregisteringNotRegisterd(); + } +} + +void VSFPluginArchitecture::unregisterRequestHandler(Request_Number_Enum requestNumber) +{ + if (requestNumber >= Request_Number_MaxNumber || requestNumber < Request_Identifier) + { + throw std::out_of_range("requestNumber not in enum bounds"); + } + else + { + requestNumber = (Request_Number_Enum)(requestNumber - Request_Number_Identifier); + } + + pluginRequestHandlers[requestNumber] = 0; +} + +void VSFPluginArchitecture::handleEvent(Event & e) { + int eventNumber = e.getNumber(); + + if (eventNumber >= Event_Number_MaxNumber || eventNumber <= Event_Identifier) + { + throw std::out_of_range("eventNumber not in enum bounds"); + } + else + { + eventNumber = (Event_Number_Enum)(eventNumber - Event_Number_Identifier); + } + + if (pluginEventHandlers[eventNumber].empty()) + { + throw NoEventHandler(); + } + + for (std::vector::iterator i = pluginEventHandlers[eventNumber].begin(); + i != pluginEventHandlers[eventNumber].end(); ++i) + { + e.getHandled(*(*i)); + } +} + +UnknownObject VSFPluginArchitecture::handleRequest(Request & r) { + int requestNumber = r.getNumber(); + + if (requestNumber >= Request_Number_MaxNumber || requestNumber < Request_Identifier) + { + throw std::out_of_range("requestNumber not in enum bounds"); + } + else + { + requestNumber = (Request_Number_Enum)(requestNumber - Request_Number_Identifier); + } + + if (pluginRequestHandlers[requestNumber] == 0) + { + throw NoRequestHandler(); + } + + return r.getHandled(*pluginRequestHandlers[requestNumber]); +} + +VSFPluginArchitecture * VSFPluginArchitectureSingleton::architecture = 0; + +VSFPluginArchitecture & VSFPluginArchitectureSingleton::getVSFPluginArchitecture() { + if (architecture == 0) + { + architecture = new VSFPluginArchitecture(); + } + return *architecture; +} diff --git a/plugin_architecture_api.h b/plugin_architecture_api.h new file mode 100644 index 0000000..1eee603 --- /dev/null +++ b/plugin_architecture_api.h @@ -0,0 +1,103 @@ +#ifndef PLUGIN_ARCHITECTURE_API_H +#define PLUGIN_ARCHITECTURE_API_H + +#include "event.h" +#include "request.h" +#include +#include "unknown_object.h" + +class VSFPlugin; +class VSFPluginArchitectureSingleton; + +/** + * A handler for all events and requests. + * provides support for multiple event listeners + * and just one request listener + */ +class VSFPluginArchitecture { +public: + /** + * assigns the plugin to the given event + */ + void registerEventHandler(VSFPlugin & handler, Event_Number_Enum eventNumber); + + /** + * assigns the plugin to the given requsest, if there is already a plugin registered, + * throws an exception + */ + void registerRequestHandler(VSFPlugin & handler, Request_Number_Enum requestNumber); + + /** + * removes the plugin from the listeners list for the given event + */ + void unregisterEventHandler(VSFPlugin & handler, Event_Number_Enum eventNumber); + + /** + * removes the plugin from the request handler position + */ + void unregisterRequestHandler(VSFPlugin & handler, Request_Number_Enum requestNumber); + /** + * makes the given request slot free for registration + */ + void unregisterRequestHandler(Request_Number_Enum requestNumber); + + /** + * handles an event, if there is no handler assigned - throws an exception + */ + void handleEvent(Event &); + + /** + * handles a request, if there is no handler assigned - throws an exception + */ + UnknownObject handleRequest(Request &); + friend class VSFPluginArchitectureSingleton; +protected: + VSFPluginArchitecture(); + static bool architectureExists; +private: + std::vector pluginEventHandlers[Event_Number_MaxNumber - Event_Identifier]; + VSFPlugin* pluginRequestHandlers[Request_Number_MaxNumber - Request_Identifier]; +}; + +/** + * ensures there is only one plugin architecure :) + */ +class VSFPluginArchitectureSingleton { +public: + VSFPluginArchitecture & getVSFPluginArchitecture(); +private: + static VSFPluginArchitecture * architecture; +}; + +/** + * a plugin, essentialy a set of event and request handlers + * Do note, that the constructor must notify the architecture + * which events and request the plugin wants to handle + */ +class VSFPlugin { +public: + VSFPlugin(VSFPluginArchitecture & arch) {arch.registerEventHandler(*this, Event_Number_ServQuit);} + virtual ~VSFPlugin() {} + + virtual void handleEvent(const EventChmod &) {throw UnimplementedPluginMethod();} + virtual void handleEvent(const EventConnection &) {throw UnimplementedPluginMethod();} + virtual void handleEvent(const EventDelete &) {throw UnimplementedPluginMethod();} + virtual void handleEvent(const EventDownload &) {throw UnimplementedPluginMethod();} + virtual void handleEvent(const EventFailedLogin &) {throw UnimplementedPluginMethod();} + virtual void handleEvent(const EventLogin &) {throw UnimplementedPluginMethod();} + virtual void handleEvent(const EventMkdir &) {throw UnimplementedPluginMethod();} + virtual void handleEvent(const EventRename &) {throw UnimplementedPluginMethod();} + virtual void handleEvent(const EventRmdir &) {throw UnimplementedPluginMethod();} + virtual void handleEvent(const EventCliEnd &) {throw UnimplementedPluginMethod();} + virtual void handleEvent(const EventUpload &) {throw UnimplementedPluginMethod();} + void handleEvent(const EventServQuit &) {} + + virtual struct Request_Result handleRequest(const RequestLogin &) {throw UnimplementedPluginMethod();} + virtual struct Request_Result_Homedir handleRequest(const RequestHomedir &) {throw UnimplementedPluginMethod();} + virtual struct Request_Result_And_Reason handleRequest(const RequestReadFile &) {throw UnimplementedPluginMethod();} + virtual struct Request_Result_And_Reason handleRequest(const RequestReadDirectory &) {throw UnimplementedPluginMethod();} + virtual struct Request_Result_And_Reason handleRequest(const RequestWriteDirectory &) {throw UnimplementedPluginMethod();} + virtual struct Request_Result_And_Reason handleRequest(const RequestWriteFile &) {throw UnimplementedPluginMethod();} +}; + +#endif diff --git a/plugin_architecture_interface.cpp b/plugin_architecture_interface.cpp new file mode 100644 index 0000000..99337a7 --- /dev/null +++ b/plugin_architecture_interface.cpp @@ -0,0 +1,134 @@ +#include "plugin_architecture_interface.h" +#include "event_c_api.h" +#include +#include +#include +#include +#include +#include +#include "slave_thread.h" +#include "udsrf.h" +#include +#include +#include +#include "request.h" +#include +#include + +extern "C" void die(const char* p_text); +extern "C" int vsf_sysutil_fork(); +extern "C" void handle_slave_signal(); + +static int slave_socket_fast = 0; +static int slave_socket_slow = 0; +int slave_pid = -1; + +struct sockaddr_un fast_un; +struct sockaddr_un slow_un; + +void handle_unexpected_exception() { + handle_slave_signal(); + + die("Unexpected exception\n"); +} + +void handleSignal(int a) { + handle_slave_signal(); + + char erc[100]; + sprintf(erc,"got a signal (%s) :(\n", strsignal(a)); + die(erc); +} + +C_COMPATIBLE_FUNCTION void vsf_plugin_architecture_start(struct vsf_session* p_sess) { + static bool started = false; + if (started) + { + return; + } + std::set_unexpected(handle_unexpected_exception); + + fast_un.sun_family = PF_UNIX; + fast_un.sun_path[0] = 0; + slow_un.sun_family = PF_UNIX; + slow_un.sun_path[0] = 0; + + srandom(time(0)); + sprintf(fast_un.sun_path + 1, "vsftp_comm_fast%ld", random()); + sprintf(slow_un.sun_path + 1, "vsftp_comm_slow%ld", random()); + + int slaveno; + // create slave + + switch(slaveno = vsf_sysutil_fork()) { + case -1: + die("fork did not succeed"); + break; + case 0: + { + pid_t parent_pid = getppid(); + + slave_thread_start(slow_un, fast_un); //should never return + printf("slave thread died\n"); + if (parent_pid != 1) + { + kill(parent_pid, SIGUSR2); + } + printf("killing parent\n"); + exit(0); + } + break; + default: + slave_pid = slaveno; + + sleep(1); + + for(int i = 0; i < 3; ++i) { + if(reconnect_after_fork()) { + printf("trying to connect to plugin arch thread\n"); + sleep(1); + } + else { + printf("connect ok\n"); + break; + } + } + + if (slave_socket_fast == 0 || slave_socket_slow == 0) + { + die("connection to plugin architecture thread failed"); + } + + signal(SIGINT, handleSignal); + signal(SIGUSR2, handleSignal); + + break; + } +} + +C_COMPATIBLE_FUNCTION int plugin_architecture_get_slave_socket_fast() { + return slave_socket_fast; +} + +C_COMPATIBLE_FUNCTION int plugin_architecture_get_slave_socket_slow() { + return slave_socket_slow; +} + +C_COMPATIBLE_FUNCTION int reconnect_after_fork() { + slave_socket_fast = socket(PF_UNIX, SOCK_SEQPACKET, 0); + slave_socket_slow = socket(PF_UNIX, SOCK_SEQPACKET, 0); + + if(connect(slave_socket_fast, (struct sockaddr*)&fast_un, sizeof(fast_un))){ + goto failed; + } + if(connect(slave_socket_slow, (struct sockaddr*)&slow_un, sizeof(slow_un))){ + goto failed; + } + return 0; +failed: + close(slave_socket_fast); + close(slave_socket_slow); + slave_socket_fast = 0; + slave_socket_slow = 0; + return -1; +} diff --git a/plugin_architecture_interface.h b/plugin_architecture_interface.h new file mode 100644 index 0000000..df1972f --- /dev/null +++ b/plugin_architecture_interface.h @@ -0,0 +1,35 @@ +#ifndef VSF_PLUGIN_ARCHITECTURE_INTERFACE +#define VSF_PLUGIN_ARCHITECTURE_INTERFACE + +#include "session.h" +#include "cpp_to_c_helpers.h" + +/* vsf_plugin_architecture_start() + * PURPOSE + * Creates and initializes the threads (high and low prio) used for processing events + * and sets up the ipc + * PARAMETERS + * p_sess - the current session object + */ +C_COMPATIBLE_FUNCTION void vsf_plugin_architecture_start(struct vsf_session* p_sess); + +/** plugin_architecture_get_slave_socket_slow() + * PURPOSE + * Returns a communication socket to the slave proces (for events mostly) + */ +C_COMPATIBLE_FUNCTION int plugin_architecture_get_slave_socket_slow(); + +/** plugin_architecture_get_slave_socket_fast() + * PURPOSE + * Returns a communication socket to the slave proces (for requests mostly) + */ +C_COMPATIBLE_FUNCTION int plugin_architecture_get_slave_socket_fast(); + +/** reconnect_after_fork() + * PURPOSE + * Reconnects to the plugin architecture process after a fork so that the child can communicate + * normally. + */ +C_COMPATIBLE_FUNCTION int reconnect_after_fork(); + +#endif //PLUGIN_ARCHITECTURE_INTERFACE diff --git a/postlogin.c b/postlogin.c new file mode 100644 index 0000000..4f957b3 --- /dev/null +++ b/postlogin.c @@ -0,0 +1,2176 @@ +/* + * Part of Very Secure FTPd + * Licence: GPL v2 + * Author: Chris Evans + * postlogin.c + */ + +#include "postlogin.h" +#include "session.h" +#include "oneprocess.h" +#include "twoprocess.h" +#include "ftpcodes.h" +#include "ftpcmdio.h" +#include "ftpdataio.h" +#include "utility.h" +#include "tunables.h" +#include "defs.h" +#include "str.h" +#include "sysstr.h" +#include "banner.h" +#include "sysutil.h" +#include "logging.h" +#include "sysdeputil.h" +#include "ipaddrparse.h" +#include "access.h" +#include "features.h" +#include "ssl.h" +#include "vsftpver.h" +#include "event_c_api.h" +#include "request_c_api.h" +#include "postlogin_events.h" +#include "postlogin_requests.h" +#include "vsfutils_c.h" +#include + +/* Private local functions */ +static void handle_pwd(struct vsf_session* p_sess); +static void handle_cwd(struct vsf_session* p_sess); +static void handle_pasv(struct vsf_session* p_sess, int is_epsv); +static void handle_retr(struct vsf_session* p_sess); +static void handle_cdup(struct vsf_session* p_sess); +static void handle_list(struct vsf_session* p_sess); +static void handle_type(struct vsf_session* p_sess); +static void handle_port(struct vsf_session* p_sess); +static void handle_stor(struct vsf_session* p_sess); +static void handle_mkd(struct vsf_session* p_sess); +static void handle_rmd(struct vsf_session* p_sess); +static void handle_dele(struct vsf_session* p_sess); +static void handle_rest(struct vsf_session* p_sess); +static void handle_rnfr(struct vsf_session* p_sess); +static void handle_rnto(struct vsf_session* p_sess); +static void handle_nlst(struct vsf_session* p_sess); +static void handle_size(struct vsf_session* p_sess); +static void handle_site(struct vsf_session* p_sess); +static void handle_appe(struct vsf_session* p_sess); +static void handle_mdtm(struct vsf_session* p_sess); +static void handle_site_chmod(struct vsf_session* p_sess, + struct mystr* p_arg_str); +static void handle_site_umask(struct vsf_session* p_sess, + struct mystr* p_arg_str); +static void handle_eprt(struct vsf_session* p_sess); +static void handle_help(struct vsf_session* p_sess); +static void handle_stou(struct vsf_session* p_sess); +static void handle_stat(struct vsf_session* p_sess); +static void handle_stat_file(struct vsf_session* p_sess); + +static int pasv_active(struct vsf_session* p_sess); +static int port_active(struct vsf_session* p_sess); +static void pasv_cleanup(struct vsf_session* p_sess); +static void port_cleanup(struct vsf_session* p_sess); +static void handle_dir_common(struct vsf_session* p_sess, int full_details, + int stat_cmd); +static void prepend_path_to_filename(struct mystr* p_str); +static int get_remote_transfer_fd(struct vsf_session* p_sess, + const char* p_status_msg); +static void check_abor(struct vsf_session* p_sess); +static void handle_sigurg(void* p_private); +static void handle_upload_common(struct vsf_session* p_sess, int is_append, + int is_unique); +static void get_unique_filename(struct mystr* p_outstr, + const struct mystr* p_base); +static int data_transfer_checks_ok(struct vsf_session* p_sess); +static void resolve_tilde(struct mystr* p_str, struct vsf_session* p_sess); + +static void try_change_to_homedir(struct vsf_session* p_sess); + +void +process_post_login(struct vsf_session* p_sess) +{ + str_getcwd(&p_sess->home_str); + try_change_to_homedir(p_sess); + if (p_sess->is_anonymous) + { + vsf_sysutil_set_umask(tunable_anon_umask); + p_sess->bw_rate_max = tunable_anon_max_rate; + } + else + { + vsf_sysutil_set_umask(tunable_local_umask); + p_sess->bw_rate_max = tunable_local_max_rate; + } + if (tunable_async_abor_enable) + { + vsf_sysutil_install_sighandler(kVSFSysUtilSigURG, handle_sigurg, p_sess); + vsf_sysutil_activate_sigurg(VSFTP_COMMAND_FD); + } + /* Handle any login message */ + vsf_banner_dir_changed(p_sess, FTP_LOGINOK); + vsf_cmdio_write(p_sess, FTP_LOGINOK, "Login successful."); + while(1) + { + int cmd_ok = 1; + if (tunable_setproctitle_enable) + { + vsf_sysutil_setproctitle("IDLE"); + } + /* Blocks */ + vsf_cmdio_get_cmd_and_arg(p_sess, &p_sess->ftp_cmd_str, + &p_sess->ftp_arg_str, 1); + if (tunable_setproctitle_enable) + { + struct mystr proctitle_str = INIT_MYSTR; + str_copy(&proctitle_str, &p_sess->ftp_cmd_str); + if (!str_isempty(&p_sess->ftp_arg_str)) + { + str_append_char(&proctitle_str, ' '); + str_append_str(&proctitle_str, &p_sess->ftp_arg_str); + } + /* Suggestion from Solar */ + str_replace_unprintable(&proctitle_str, '?'); + vsf_sysutil_setproctitle_str(&proctitle_str); + str_free(&proctitle_str); + } + /* Test command against the allowed list.. */ + if (tunable_cmds_allowed) + { + static struct mystr s_src_str; + static struct mystr s_rhs_str; + str_alloc_text(&s_src_str, tunable_cmds_allowed); + while (1) + { + str_split_char(&s_src_str, &s_rhs_str, ','); + if (str_isempty(&s_src_str)) + { + cmd_ok = 0; + break; + } + else if (str_equal(&s_src_str, &p_sess->ftp_cmd_str)) + { + break; + } + str_copy(&s_src_str, &s_rhs_str); + } + } + if (!cmd_ok) + { + vsf_cmdio_write(p_sess, FTP_NOPERM, "Permission denied."); + } + else if (str_equal_text(&p_sess->ftp_cmd_str, "QUIT")) + { + vsf_cmdio_write(p_sess, FTP_GOODBYE, "Goodbye."); + if (tunable_plugin_architecture) + { + struct Event_CliEnd event; + gettimeofday(&event.timestamp,0); + send_event_cliEnd(&event); + } + vsf_sysutil_exit(0); + } + else if (str_equal_text(&p_sess->ftp_cmd_str, "PWD") || + str_equal_text(&p_sess->ftp_cmd_str, "XPWD")) + { + handle_pwd(p_sess); + } + else if (str_equal_text(&p_sess->ftp_cmd_str, "CWD") || + str_equal_text(&p_sess->ftp_cmd_str, "XCWD")) + { + handle_cwd(p_sess); + } + else if (str_equal_text(&p_sess->ftp_cmd_str, "CDUP") || + str_equal_text(&p_sess->ftp_cmd_str, "XCUP")) + { + handle_cdup(p_sess); + } + else if (tunable_pasv_enable && + !p_sess->epsv_all && + (str_equal_text(&p_sess->ftp_cmd_str, "PASV") || + str_equal_text(&p_sess->ftp_cmd_str, "P@SW"))) + { + handle_pasv(p_sess, 0); + } + else if (tunable_pasv_enable && + str_equal_text(&p_sess->ftp_cmd_str, "EPSV")) + { + handle_pasv(p_sess, 1); + } + else if (tunable_download_enable && + str_equal_text(&p_sess->ftp_cmd_str, "RETR")) + { + handle_retr(p_sess); + } + else if (str_equal_text(&p_sess->ftp_cmd_str, "NOOP")) + { + vsf_cmdio_write(p_sess, FTP_NOOPOK, "NOOP ok."); + } + else if (str_equal_text(&p_sess->ftp_cmd_str, "SYST")) + { + vsf_cmdio_write(p_sess, FTP_SYSTOK, "UNIX Type: L8"); + } + else if (str_equal_text(&p_sess->ftp_cmd_str, "HELP")) + { + handle_help(p_sess); + } + else if (tunable_dirlist_enable && + str_equal_text(&p_sess->ftp_cmd_str, "LIST")) + { + handle_list(p_sess); + } + else if (str_equal_text(&p_sess->ftp_cmd_str, "TYPE")) + { + handle_type(p_sess); + } + else if (tunable_port_enable && + !p_sess->epsv_all && + str_equal_text(&p_sess->ftp_cmd_str, "PORT")) + { + handle_port(p_sess); + } + else if (tunable_write_enable && + (tunable_anon_upload_enable || !p_sess->is_anonymous) && + str_equal_text(&p_sess->ftp_cmd_str, "STOR")) + { + handle_stor(p_sess); + } + else if (tunable_write_enable && + (tunable_anon_mkdir_write_enable || !p_sess->is_anonymous) && + (str_equal_text(&p_sess->ftp_cmd_str, "MKD") || + str_equal_text(&p_sess->ftp_cmd_str, "XMKD"))) + { + handle_mkd(p_sess); + } + else if (tunable_write_enable && + (tunable_anon_other_write_enable || !p_sess->is_anonymous) && + (str_equal_text(&p_sess->ftp_cmd_str, "RMD") || + str_equal_text(&p_sess->ftp_cmd_str, "XRMD"))) + { + handle_rmd(p_sess); + } + else if (tunable_write_enable && + (tunable_anon_other_write_enable || !p_sess->is_anonymous) && + str_equal_text(&p_sess->ftp_cmd_str, "DELE")) + { + handle_dele(p_sess); + } + else if (str_equal_text(&p_sess->ftp_cmd_str, "REST")) + { + handle_rest(p_sess); + } + else if (tunable_write_enable && + (tunable_anon_other_write_enable || !p_sess->is_anonymous) && + str_equal_text(&p_sess->ftp_cmd_str, "RNFR")) + { + handle_rnfr(p_sess); + } + else if (tunable_write_enable && + (tunable_anon_other_write_enable || !p_sess->is_anonymous) && + str_equal_text(&p_sess->ftp_cmd_str, "RNTO")) + { + handle_rnto(p_sess); + } + else if (tunable_dirlist_enable && + str_equal_text(&p_sess->ftp_cmd_str, "NLST")) + { + handle_nlst(p_sess); + } + else if (str_equal_text(&p_sess->ftp_cmd_str, "SIZE")) + { + handle_size(p_sess); + } + else if ((!p_sess->is_anonymous || tunable_plugin_architecture) && + str_equal_text(&p_sess->ftp_cmd_str, "SITE")) + { + handle_site(p_sess); + } + /* Note - the weird ABOR string is checking for an async ABOR arriving + * without a SIGURG condition. + */ + else if (str_equal_text(&p_sess->ftp_cmd_str, "ABOR") || + str_equal_text(&p_sess->ftp_cmd_str, "\377\364\377\362ABOR")) + { + vsf_cmdio_write(p_sess, FTP_ABOR_NOCONN, "No transfer to ABOR."); + } + else if (tunable_write_enable && + (tunable_anon_other_write_enable || !p_sess->is_anonymous) && + str_equal_text(&p_sess->ftp_cmd_str, "APPE")) + { + handle_appe(p_sess); + } + else if (str_equal_text(&p_sess->ftp_cmd_str, "MDTM")) + { + handle_mdtm(p_sess); + } + else if (tunable_port_enable && + str_equal_text(&p_sess->ftp_cmd_str, "EPRT")) + { + handle_eprt(p_sess); + } + else if (str_equal_text(&p_sess->ftp_cmd_str, "STRU")) + { + str_upper(&p_sess->ftp_arg_str); + if (str_equal_text(&p_sess->ftp_arg_str, "F")) + { + vsf_cmdio_write(p_sess, FTP_STRUOK, "Structure set to F."); + } + else + { + vsf_cmdio_write(p_sess, FTP_BADSTRU, "Bad STRU command."); + } + } + else if (str_equal_text(&p_sess->ftp_cmd_str, "MODE")) + { + str_upper(&p_sess->ftp_arg_str); + if (str_equal_text(&p_sess->ftp_arg_str, "S")) + { + vsf_cmdio_write(p_sess, FTP_MODEOK, "Mode set to S."); + } + else + { + vsf_cmdio_write(p_sess, FTP_BADMODE, "Bad MODE command."); + } + } + else if (str_equal_text(&p_sess->ftp_cmd_str, "STOU")) + { + handle_stou(p_sess); + } + else if (str_equal_text(&p_sess->ftp_cmd_str, "ALLO")) + { + vsf_cmdio_write(p_sess, FTP_ALLOOK, "ALLO command ignored."); + } + else if (str_equal_text(&p_sess->ftp_cmd_str, "REIN")) + { + vsf_cmdio_write(p_sess, FTP_COMMANDNOTIMPL, "REIN not implemented."); + } + else if (str_equal_text(&p_sess->ftp_cmd_str, "ACCT")) + { + vsf_cmdio_write(p_sess, FTP_COMMANDNOTIMPL, "ACCT not implemented."); + } + else if (str_equal_text(&p_sess->ftp_cmd_str, "SMNT")) + { + vsf_cmdio_write(p_sess, FTP_COMMANDNOTIMPL, "SMNT not implemented."); + } + else if (str_equal_text(&p_sess->ftp_cmd_str, "FEAT")) + { + handle_feat(p_sess); + } + else if (str_equal_text(&p_sess->ftp_cmd_str, "OPTS")) + { + vsf_cmdio_write(p_sess, FTP_BADOPTS, "Option not understood."); + } + else if (str_equal_text(&p_sess->ftp_cmd_str, "STAT") && + str_isempty(&p_sess->ftp_arg_str)) + { + handle_stat(p_sess); + } + else if (tunable_dirlist_enable && + str_equal_text(&p_sess->ftp_cmd_str, "STAT")) + { + handle_stat_file(p_sess); + } + else if (tunable_ssl_enable && str_equal_text(&p_sess->ftp_cmd_str, "PBSZ")) + { + handle_pbsz(p_sess); + } + else if (tunable_ssl_enable && str_equal_text(&p_sess->ftp_cmd_str, "PROT")) + { + handle_prot(p_sess); + } + else if (str_equal_text(&p_sess->ftp_cmd_str, "PASV") || + str_equal_text(&p_sess->ftp_cmd_str, "PORT") || + str_equal_text(&p_sess->ftp_cmd_str, "STOR") || + str_equal_text(&p_sess->ftp_cmd_str, "MKD") || + str_equal_text(&p_sess->ftp_cmd_str, "XMKD") || + str_equal_text(&p_sess->ftp_cmd_str, "RMD") || + str_equal_text(&p_sess->ftp_cmd_str, "XRMD") || + str_equal_text(&p_sess->ftp_cmd_str, "DELE") || + str_equal_text(&p_sess->ftp_cmd_str, "RNFR") || + str_equal_text(&p_sess->ftp_cmd_str, "RNTO") || + str_equal_text(&p_sess->ftp_cmd_str, "SITE") || + str_equal_text(&p_sess->ftp_cmd_str, "APPE") || + str_equal_text(&p_sess->ftp_cmd_str, "EPSV") || + str_equal_text(&p_sess->ftp_cmd_str, "EPRT") || + str_equal_text(&p_sess->ftp_cmd_str, "RETR") || + str_equal_text(&p_sess->ftp_cmd_str, "LIST") || + str_equal_text(&p_sess->ftp_cmd_str, "NLST") || + str_equal_text(&p_sess->ftp_cmd_str, "STOU") || + str_equal_text(&p_sess->ftp_cmd_str, "ALLO") || + str_equal_text(&p_sess->ftp_cmd_str, "REIN") || + str_equal_text(&p_sess->ftp_cmd_str, "ACCT") || + str_equal_text(&p_sess->ftp_cmd_str, "SMNT") || + str_equal_text(&p_sess->ftp_cmd_str, "FEAT") || + str_equal_text(&p_sess->ftp_cmd_str, "OPTS") || + str_equal_text(&p_sess->ftp_cmd_str, "STAT") || + str_equal_text(&p_sess->ftp_cmd_str, "PBSZ") || + str_equal_text(&p_sess->ftp_cmd_str, "PROT")) + { + vsf_cmdio_write(p_sess, FTP_NOPERM, "Permission denied."); + } + else + { + vsf_cmdio_write(p_sess, FTP_BADCMD, "Unknown command."); + } + if (vsf_log_entry_pending(p_sess)) + { + vsf_log_do_log(p_sess, 0); + } + } +} + +static void +handle_pwd(struct vsf_session* p_sess) +{ + static struct mystr s_cwd_buf_mangle_str; + static struct mystr s_pwd_res_str; + str_getcwd(&s_cwd_buf_mangle_str); + /* Double up any double-quotes in the pathname! */ + str_replace_text(&s_cwd_buf_mangle_str, "\"", "\"\""); + /* Enclose pathname in quotes */ + str_alloc_text(&s_pwd_res_str, "\""); + str_append_str(&s_pwd_res_str, &s_cwd_buf_mangle_str); + str_append_text(&s_pwd_res_str, "\""); + vsf_cmdio_write_str(p_sess, FTP_PWDOK, &s_pwd_res_str); +} + +static void +handle_cwd(struct vsf_session* p_sess) +{ + int retval; + struct mystr cwd_pre_chdir = INIT_MYSTR; + resolve_tilde(&p_sess->ftp_arg_str, p_sess); + if (!vsf_access_check_file(&p_sess->ftp_arg_str)) + { + vsf_cmdio_write(p_sess, FTP_NOPERM, "Permission denied."); + return; + } + + str_getcwd(&cwd_pre_chdir); + + retval = str_chdir(&p_sess->ftp_arg_str); + + if (retval == 0) + { + if (tunable_plugin_architecture) + { + struct Request_Result_And_Reason request_result; + request_result = vsf_request_read_directory(p_sess, &cwd_pre_chdir, &p_sess->ftp_arg_str); + if (request_result.result) + { + str_chdir(&cwd_pre_chdir); + vsf_cmdio_write(p_sess, FTP_FILEFAIL, request_result.reason); + doneWith_Result_And_Reason(&request_result); + goto function_end; + } + doneWith_Result_And_Reason(&request_result); + } + /* Handle any messages */ + vsf_banner_dir_changed(p_sess, FTP_CWDOK); + vsf_cmdio_write(p_sess, FTP_CWDOK, "Directory successfully changed."); + } + else + { + vsf_cmdio_write(p_sess, FTP_FILEFAIL, "Failed to change directory."); + } + +function_end: + str_free(&cwd_pre_chdir); +} + +static void +handle_cdup(struct vsf_session* p_sess) +{ + str_alloc_text(&p_sess->ftp_arg_str, ".."); + handle_cwd(p_sess); +} + +static int +port_active(struct vsf_session* p_sess) +{ + int ret = 0; + if (p_sess->p_port_sockaddr != 0) + { + ret = 1; + if (pasv_active(p_sess)) + { + bug("port and pasv both active"); + } + } + return ret; +} + +static int +pasv_active(struct vsf_session* p_sess) +{ + int ret = 0; + if (p_sess->pasv_listen_fd != -1) + { + ret = 1; + if (port_active(p_sess)) + { + bug("pasv and port both active"); + } + } + return ret; +} + +static void +port_cleanup(struct vsf_session* p_sess) +{ + vsf_sysutil_sockaddr_clear(&p_sess->p_port_sockaddr); +} + +static void +pasv_cleanup(struct vsf_session* p_sess) +{ + if (p_sess->pasv_listen_fd != -1) + { + vsf_sysutil_close(p_sess->pasv_listen_fd); + p_sess->pasv_listen_fd = -1; + } +} + +static void +handle_pasv(struct vsf_session* p_sess, int is_epsv) +{ + static struct mystr s_pasv_res_str; + static struct vsf_sysutil_sockaddr* s_p_sockaddr; + int bind_retries = 10; + unsigned short the_port = 0; + int is_ipv6 = vsf_sysutil_sockaddr_is_ipv6(p_sess->p_local_addr); + if (is_epsv && !str_isempty(&p_sess->ftp_arg_str)) + { + int argval; + str_upper(&p_sess->ftp_arg_str); + if (str_equal_text(&p_sess->ftp_arg_str, "ALL")) + { + p_sess->epsv_all = 1; + vsf_cmdio_write(p_sess, FTP_EPSVALLOK, "EPSV ALL ok."); + return; + } + argval = vsf_sysutil_atoi(str_getbuf(&p_sess->ftp_arg_str)); + if (argval < 1 || argval > 2 || (!is_ipv6 && argval == 2)) + { + vsf_cmdio_write(p_sess, FTP_EPSVBAD, "Bad network protocol."); + return; + } + } + pasv_cleanup(p_sess); + port_cleanup(p_sess); + if (is_ipv6) + { + p_sess->pasv_listen_fd = vsf_sysutil_get_ipv6_sock(); + } + else + { + p_sess->pasv_listen_fd = vsf_sysutil_get_ipv4_sock(); + } + vsf_sysutil_activate_reuseaddr(p_sess->pasv_listen_fd); + while (--bind_retries) + { + int retval; + double scaled_port; + /* IPPORT_RESERVED */ + unsigned short min_port = 1024; + unsigned short max_port = 65535; + if (tunable_pasv_min_port > min_port && tunable_pasv_min_port <= max_port) + { + min_port = tunable_pasv_min_port; + } + if (tunable_pasv_max_port >= min_port && tunable_pasv_max_port < max_port) + { + max_port = tunable_pasv_max_port; + } + the_port = vsf_sysutil_get_random_byte(); + the_port <<= 8; + the_port |= vsf_sysutil_get_random_byte(); + scaled_port = (double) min_port; + scaled_port += ((double) the_port / (double) 65536) * + ((double) max_port - min_port + 1); + the_port = (unsigned short) scaled_port; + vsf_sysutil_sockaddr_clone(&s_p_sockaddr, p_sess->p_local_addr); + vsf_sysutil_sockaddr_set_port(s_p_sockaddr, the_port); + retval = vsf_sysutil_bind(p_sess->pasv_listen_fd, s_p_sockaddr); + if (!vsf_sysutil_retval_is_error(retval)) + { + break; + } + if (vsf_sysutil_get_error() == kVSFSysUtilErrADDRINUSE) + { + continue; + } + die("vsf_sysutil_bind"); + } + if (!bind_retries) + { + die("vsf_sysutil_bind"); + } + vsf_sysutil_listen(p_sess->pasv_listen_fd, 1); + if (is_epsv) + { + str_alloc_text(&s_pasv_res_str, "Entering Extended Passive Mode (|||"); + str_append_ulong(&s_pasv_res_str, (unsigned long) the_port); + str_append_text(&s_pasv_res_str, "|)"); + vsf_cmdio_write_str(p_sess, FTP_EPSVOK, &s_pasv_res_str); + return; + } + if (tunable_pasv_address != 0) + { + /* Report passive address as specified in configuration */ + if (vsf_sysutil_inet_aton(tunable_pasv_address, s_p_sockaddr) == 0) + { + die("invalid pasv_address"); + } + } + str_alloc_text(&s_pasv_res_str, "Entering Passive Mode ("); + if (!is_ipv6) + { + str_append_text(&s_pasv_res_str, vsf_sysutil_inet_ntop(s_p_sockaddr)); + } + else + { + const void* p_v4addr = vsf_sysutil_sockaddr_ipv6_v4(s_p_sockaddr); + if (p_v4addr) + { + str_append_text(&s_pasv_res_str, vsf_sysutil_inet_ntoa(p_v4addr)); + } + else + { + str_append_text(&s_pasv_res_str, "0,0,0,0"); + } + } + str_replace_char(&s_pasv_res_str, '.', ','); + str_append_text(&s_pasv_res_str, ","); + str_append_ulong(&s_pasv_res_str, the_port >> 8); + str_append_text(&s_pasv_res_str, ","); + str_append_ulong(&s_pasv_res_str, the_port & 255); + str_append_text(&s_pasv_res_str, ")"); + vsf_cmdio_write_str(p_sess, FTP_PASVOK, &s_pasv_res_str); +} + +#include +static void +handle_retr(struct vsf_session* p_sess) +{ + static struct mystr s_mark_str; + static struct vsf_sysutil_statbuf* s_p_statbuf; + struct vsf_transfer_ret trans_ret; + int remote_fd; + int opened_file; + int is_ascii = 0; + filesize_t offset = p_sess->restart_pos; + p_sess->restart_pos = 0; + if (!data_transfer_checks_ok(p_sess)) + { + return; + } + if (p_sess->is_ascii && offset != 0) + { + vsf_cmdio_write(p_sess, FTP_FILEFAIL, + "No support for resume of ASCII transfer."); + return; + } + resolve_tilde(&p_sess->ftp_arg_str, p_sess); + vsf_log_start_entry(p_sess, kVSFLogEntryDownload); + str_copy(&p_sess->log_str, &p_sess->ftp_arg_str); + prepend_path_to_filename(&p_sess->log_str); + if (!vsf_access_check_file(&p_sess->ftp_arg_str)) + { + vsf_cmdio_write(p_sess, FTP_NOPERM, "Permission denied."); + return; + } + opened_file = str_open(&p_sess->ftp_arg_str, kVSFSysStrOpenReadOnly); + if (vsf_sysutil_retval_is_error(opened_file)) + { + vsf_cmdio_write(p_sess, FTP_FILEFAIL, "Failed to open file."); + return; + } + if (tunable_plugin_architecture) + { + struct Request_Result_And_Reason request_result; + struct mystr dirPath = INIT_MYSTR; + str_getcwd(&dirPath); + request_result= vsf_request_read_file(p_sess, &dirPath, &p_sess->ftp_arg_str); + str_free(&dirPath); + if (request_result.result) + { + vsf_cmdio_write(p_sess, FTP_FILEFAIL, request_result.reason); + send_log_event_download_failed(p_sess); + doneWith_Result_And_Reason(&request_result); + return; + } + doneWith_Result_And_Reason(&request_result); + } + /* Lock file if required */ + if (tunable_lock_upload_files) + { + vsf_sysutil_lock_file_read(opened_file); + } + vsf_sysutil_fstat(opened_file, &s_p_statbuf); + /* No games please */ + if (!vsf_sysutil_statbuf_is_regfile(s_p_statbuf)) + { + /* Note - pretend open failed */ + vsf_cmdio_write(p_sess, FTP_FILEFAIL, "Failed to open file."); + goto file_close_out; + } + /* Now deactive O_NONBLOCK, otherwise we have a problem on DMAPI filesystems + * such as XFS DMAPI. + */ + vsf_sysutil_deactivate_noblock(opened_file); + /* Optionally, we'll be paranoid and only serve publicly readable stuff */ + if (p_sess->is_anonymous && tunable_anon_world_readable_only && + !vsf_sysutil_statbuf_is_readable_other(s_p_statbuf)) + { + vsf_cmdio_write(p_sess, FTP_FILEFAIL, "Failed to open file."); + goto file_close_out; + } + /* Set the download offset (from REST) if any */ + if (offset != 0) + { + vsf_sysutil_lseek_to(opened_file, offset); + } + str_alloc_text(&s_mark_str, "Opening "); + if (tunable_ascii_download_enable && p_sess->is_ascii) + { + str_append_text(&s_mark_str, "ASCII"); + is_ascii = 1; + } + else + { + str_append_text(&s_mark_str, "BINARY"); + } + str_append_text(&s_mark_str, " mode data connection for "); + str_append_str(&s_mark_str, &p_sess->ftp_arg_str); + str_append_text(&s_mark_str, " ("); + str_append_filesize_t(&s_mark_str, + vsf_sysutil_statbuf_get_size(s_p_statbuf)); + str_append_text(&s_mark_str, " bytes)."); + remote_fd = get_remote_transfer_fd(p_sess, str_getbuf(&s_mark_str)); + if (vsf_sysutil_retval_is_error(remote_fd)) + { + goto port_pasv_cleanup_out; + } + trans_ret = vsf_ftpdataio_transfer_file(p_sess, remote_fd, + opened_file, 0, is_ascii); + vsf_ftpdataio_dispose_transfer_fd(p_sess); + p_sess->transfer_size = trans_ret.transferred; + /* Log _after_ the blocking dispose call, so we get transfer times right */ + if (tunable_plugin_architecture) + { + send_log_event_download(p_sess, &trans_ret); + } + if (trans_ret.retval == 0) + { + vsf_log_do_log(p_sess, 1); + } + /* Emit status message _after_ blocking dispose call to avoid buggy FTP + * clients truncating the transfer. + */ + if (trans_ret.retval == -1) + { + vsf_cmdio_write(p_sess, FTP_BADSENDFILE, "Failure reading local file."); + } + else if (trans_ret.retval == -2) + { + vsf_cmdio_write(p_sess, FTP_BADSENDNET, "Failure writing network stream."); + } + else + { + vsf_cmdio_write(p_sess, FTP_TRANSFEROK, "File send OK."); + } + check_abor(p_sess); +port_pasv_cleanup_out: + port_cleanup(p_sess); + pasv_cleanup(p_sess); +file_close_out: + vsf_sysutil_close(opened_file); +} + +static void +handle_list(struct vsf_session* p_sess) +{ + handle_dir_common(p_sess, 1, 0); +} + +static void +handle_dir_common(struct vsf_session* p_sess, int full_details, int stat_cmd) +{ + static struct mystr s_option_str; + static struct mystr s_filter_str; + static struct mystr s_dir_name_str; + static struct vsf_sysutil_statbuf* s_p_dirstat; + int dir_allow_read = 1; + struct vsf_sysutil_dir* p_dir = 0; + int retval = 0; + int use_control = 0; + str_empty(&s_option_str); + str_empty(&s_filter_str); + /* By default open the current directory */ + str_alloc_text(&s_dir_name_str, "."); + if (!stat_cmd && !data_transfer_checks_ok(p_sess)) + { + return; + } + /* Do we have an option? Going to be strict here - the option must come + * first. e.g. "ls -a .." fine, "ls .. -a" not fine + */ + if (!str_isempty(&p_sess->ftp_arg_str) && + str_get_char_at(&p_sess->ftp_arg_str, 0) == '-') + { + /* Chop off the '-' */ + str_mid_to_end(&p_sess->ftp_arg_str, &s_option_str, 1); + /* A space will separate options from filter (if any) */ + str_split_char(&s_option_str, &s_filter_str, ' '); + } + else + { + /* The argument, if any, is just a filter */ + str_copy(&s_filter_str, &p_sess->ftp_arg_str); + } + if (!str_isempty(&s_filter_str)) + { + resolve_tilde(&s_filter_str, p_sess); + if (!vsf_access_check_file(&s_filter_str)) + { + vsf_cmdio_write(p_sess, FTP_NOPERM, "Permission denied."); + return; + } + /* First check - is it an outright directory, as in "ls /pub" */ + p_dir = str_opendir(&s_filter_str); + + if (p_dir != 0) + { + /* Listing a directory! */ + str_copy(&s_dir_name_str, &s_filter_str); + str_free(&s_filter_str); + } + else + { + struct str_locate_result locate_result = + str_locate_char(&s_filter_str, '/'); + if (locate_result.found) + { + /* Includes a path! Reverse scan for / in the arg, to get the + * base directory and filter (if any) + */ + str_copy(&s_dir_name_str, &s_filter_str); + str_split_char_reverse(&s_dir_name_str, &s_filter_str, '/'); + /* If we have e.g. "ls /.message", we just ripped off the leading + * slash because it is the only one! + */ + if (str_isempty(&s_dir_name_str)) + { + str_alloc_text(&s_dir_name_str, "/"); + } + } + } + } + if (p_dir == 0) + { + /* NOTE - failure check done below, it's not forgotten */ + p_dir = str_opendir(&s_dir_name_str); + } + + if (p_dir) + { + if (tunable_plugin_architecture) + { + struct Request_Result_And_Reason request_result; + struct mystr parentDir = INIT_MYSTR; + str_getcwd(&parentDir); + request_result = vsf_request_read_directory(p_sess, &parentDir, &s_dir_name_str); + str_free(&parentDir); + if (request_result.result) + { + vsf_cmdio_write(p_sess, FTP_NOPERM, request_result.reason); + doneWith_Result_And_Reason(&request_result); + goto dir_close_out; + } + doneWith_Result_And_Reason(&request_result); + } + } + + /* Fine, do it */ + if (stat_cmd) + { + use_control = 1; + str_append_char(&s_option_str, 'a'); + vsf_cmdio_write_hyphen(p_sess, FTP_STATFILE_OK, "Status follows:"); + } + else + { + int remote_fd = get_remote_transfer_fd( + p_sess, "Here comes the directory listing."); + if (vsf_sysutil_retval_is_error(remote_fd)) + { + goto dir_close_out; + } + } + if (p_sess->is_anonymous && p_dir && tunable_anon_world_readable_only) + { + vsf_sysutil_dir_stat(p_dir, &s_p_dirstat); + if (!vsf_sysutil_statbuf_is_readable_other(s_p_dirstat)) + { + dir_allow_read = 0; + } + } + if (p_dir != 0 && dir_allow_read) + { + retval = vsf_ftpdataio_transfer_dir(p_sess, use_control, p_dir, + &s_dir_name_str, &s_option_str, + &s_filter_str, full_details); + } + if (!stat_cmd) + { + vsf_ftpdataio_dispose_transfer_fd(p_sess); + } + if (stat_cmd) + { + vsf_cmdio_write(p_sess, FTP_STATFILE_OK, "End of status"); + } + else if (p_dir == 0 || !dir_allow_read) + { + vsf_cmdio_write(p_sess, FTP_TRANSFEROK, + "Transfer done (but failed to open directory)."); + } + else if (retval == 0) + { + vsf_cmdio_write(p_sess, FTP_TRANSFEROK, "Directory send OK."); + } + else + { + vsf_cmdio_write(p_sess, FTP_BADSENDNET, "Failure writing network stream."); + } + check_abor(p_sess); +dir_close_out: + if (p_dir) + { + vsf_sysutil_closedir(p_dir); + } + if (!stat_cmd) + { + port_cleanup(p_sess); + pasv_cleanup(p_sess); + } +} + +static void +handle_type(struct vsf_session* p_sess) +{ + str_upper(&p_sess->ftp_arg_str); + if (str_equal_text(&p_sess->ftp_arg_str, "I") || + str_equal_text(&p_sess->ftp_arg_str, "L8") || + str_equal_text(&p_sess->ftp_arg_str, "L 8")) + { + p_sess->is_ascii = 0; + vsf_cmdio_write(p_sess, FTP_TYPEOK, "Switching to Binary mode."); + } + else if (str_equal_text(&p_sess->ftp_arg_str, "A") || + str_equal_text(&p_sess->ftp_arg_str, "A N")) + { + p_sess->is_ascii = 1; + vsf_cmdio_write(p_sess, FTP_TYPEOK, "Switching to ASCII mode."); + } + else + { + vsf_cmdio_write(p_sess, FTP_BADCMD, "Unrecognised TYPE command."); + } +} + +static void +handle_port(struct vsf_session* p_sess) +{ + unsigned short the_port; + unsigned char vals[6]; + const unsigned char* p_raw; + pasv_cleanup(p_sess); + port_cleanup(p_sess); + p_raw = vsf_sysutil_parse_uchar_string_sep(&p_sess->ftp_arg_str, ',', vals, + sizeof(vals)); + if (p_raw == 0) + { + vsf_cmdio_write(p_sess, FTP_BADCMD, "Illegal PORT command."); + return; + } + the_port = vals[4] << 8; + the_port |= vals[5]; + vsf_sysutil_sockaddr_clone(&p_sess->p_port_sockaddr, p_sess->p_local_addr); + vsf_sysutil_sockaddr_set_ipv4addr(p_sess->p_port_sockaddr, vals); + vsf_sysutil_sockaddr_set_port(p_sess->p_port_sockaddr, the_port); + /* SECURITY: + * 1) Reject requests not connecting to the control socket IP + * 2) Reject connects to privileged ports + */ + if (!tunable_port_promiscuous) + { + if (!vsf_sysutil_sockaddr_addr_equal(p_sess->p_remote_addr, + p_sess->p_port_sockaddr) || + vsf_sysutil_is_port_reserved(the_port)) + { + vsf_cmdio_write(p_sess, FTP_BADCMD, "Illegal PORT command."); + port_cleanup(p_sess); + return; + } + } + vsf_cmdio_write(p_sess, FTP_PORTOK, + "PORT command successful. Consider using PASV."); +} + +static void +handle_stor(struct vsf_session* p_sess) +{ + handle_upload_common(p_sess, 0, 0); +} + +static void +handle_upload_common(struct vsf_session* p_sess, int is_append, int is_unique) +{ + static struct vsf_sysutil_statbuf* s_p_statbuf; + static struct mystr s_filename; + struct mystr* p_filename; + struct vsf_transfer_ret trans_ret; + int new_file_fd; + int remote_fd; + int doesExist; + filesize_t offset = p_sess->restart_pos; + p_sess->restart_pos = 0; + if (!data_transfer_checks_ok(p_sess)) + { + return; + } + resolve_tilde(&p_sess->ftp_arg_str, p_sess); + p_filename = &p_sess->ftp_arg_str; + if (is_unique) + { + get_unique_filename(&s_filename, p_filename); + p_filename = &s_filename; + } + vsf_log_start_entry(p_sess, kVSFLogEntryUpload); + str_copy(&p_sess->log_str, &p_sess->ftp_arg_str); + prepend_path_to_filename(&p_sess->log_str); + if (!vsf_access_check_file(p_filename)) + { + vsf_cmdio_write(p_sess, FTP_NOPERM, "Permission denied."); + return; + } + { + int tmpFD = str_open(p_filename,kVSFSysUtilOpenReadOnly); + + if (tmpFD == -1) + { + doesExist = 0; + } + else + { + doesExist = 1; + vsf_sysutil_close(tmpFD); + } + } + /* NOTE - actual file permissions will be governed by the tunable umask */ + /* XXX - do we care about race between create and chown() of anonymous + * upload? + */ + if (is_unique || (p_sess->is_anonymous && !tunable_anon_other_write_enable)) + { + if (tunable_plugin_architecture) + { + { + struct Request_Result_And_Reason request_result; + struct mystr dirPath = INIT_MYSTR; + struct mystr dirName = INIT_MYSTR; + str_getcwd(&dirPath); + str_split_char_reverse(&dirPath, &dirName, '/'); + if (str_isempty(&dirPath)) + { + str_alloc_text(&dirPath, "/"); + } + if (str_isempty(&dirName)) + { + str_alloc_text(&dirName, "/"); + } + request_result = vsf_request_write_directory(p_sess, &dirPath, &dirName); + str_free(&dirPath); + str_free(&dirName); + if (request_result.result) + { + vsf_cmdio_write(p_sess, FTP_NOPERM, request_result.reason); + send_log_event_upload_failed(p_sess, p_filename, is_append, doesExist); + doneWith_Result_And_Reason(&request_result); + return; + } + doneWith_Result_And_Reason(&request_result); + } + } + new_file_fd = str_create(p_filename); + } + else + { + if (tunable_plugin_architecture) + { + struct vsf_sysutil_statbuf * statbuf; + if (doesExist) { + struct Request_Result_And_Reason request_result; + struct mystr dirPath = INIT_MYSTR; + str_getcwd(&dirPath); + request_result = vsf_request_write_file(p_sess, &dirPath, p_filename); + str_free(&dirPath); + if (request_result.result) + { + vsf_cmdio_write(p_sess, FTP_NOPERM, request_result.reason); + send_log_event_upload_failed(p_sess, p_filename, is_append, doesExist); + doneWith_Result_And_Reason(&request_result); + return; + } + doneWith_Result_And_Reason(&request_result); + } else { + struct Request_Result_And_Reason request_result; + struct mystr dirPath = INIT_MYSTR; + struct mystr dirName = INIT_MYSTR; + str_getcwd(&dirPath); + str_split_char_reverse(&dirPath, &dirName, '/'); + if (str_isempty(&dirPath)) + { + str_alloc_text(&dirPath, "/"); + } + if (str_isempty(&dirName)) + { + str_alloc_text(&dirName, "/"); + } + request_result = vsf_request_write_directory(p_sess, &dirPath, &dirName); + str_free(&dirPath); + str_free(&dirName); + if (request_result.result) + { + vsf_cmdio_write(p_sess, FTP_NOPERM, request_result.reason); + send_log_event_upload_failed(p_sess, p_filename, is_append, doesExist); + doneWith_Result_And_Reason(&request_result); + return; + } + doneWith_Result_And_Reason(&request_result); + } + } + /* For non-anonymous, allow open() to overwrite or append existing files */ + if (!is_append && offset == 0) + { + new_file_fd = str_create_overwrite(p_filename); + } + else + { + new_file_fd = str_create_append(p_filename); + } + } + if (vsf_sysutil_retval_is_error(new_file_fd)) + { + vsf_cmdio_write(p_sess, FTP_UPLOADFAIL, "Could not create file."); + return; + } + vsf_sysutil_fstat(new_file_fd, &s_p_statbuf); + if (vsf_sysutil_statbuf_is_regfile(s_p_statbuf)) + { + /* Now deactive O_NONBLOCK, otherwise we have a problem on DMAPI filesystems + * such as XFS DMAPI. + */ + vsf_sysutil_deactivate_noblock(new_file_fd); + } + /* Are we required to chown() this file for security? */ + if (p_sess->is_anonymous && tunable_chown_uploads) + { + vsf_sysutil_fchmod(new_file_fd, 0600); + if (tunable_one_process_model) + { + vsf_one_process_chown_upload(p_sess, new_file_fd); + } + else + { + vsf_two_process_chown_upload(p_sess, new_file_fd); + } + } + /* Are we required to lock this file? */ + if (tunable_lock_upload_files) + { + vsf_sysutil_lock_file_write(new_file_fd); + } + if (!is_append && offset != 0) + { + /* XXX - warning, allows seek past end of file! Check for seek > size? */ + vsf_sysutil_lseek_to(new_file_fd, offset); + } + if (is_unique) + { + struct mystr resp_str = INIT_MYSTR; + str_alloc_text(&resp_str, "FILE: "); + str_append_str(&resp_str, p_filename); + vsf_cmdio_write_str(p_sess, FTP_DATACONN, &resp_str); + remote_fd = get_remote_transfer_fd(p_sess, str_getbuf(&resp_str)); + str_free(&resp_str); + } + else + { + remote_fd = get_remote_transfer_fd(p_sess, "Ok to send data."); + } + if (vsf_sysutil_retval_is_error(remote_fd)) + { + goto port_pasv_cleanup_out; + } + if (tunable_ascii_upload_enable && p_sess->is_ascii) + { + trans_ret = vsf_ftpdataio_transfer_file(p_sess, remote_fd, + new_file_fd, 1, 1); + } + else + { + trans_ret = vsf_ftpdataio_transfer_file(p_sess, remote_fd, + new_file_fd, 1, 0); + } + vsf_ftpdataio_dispose_transfer_fd(p_sess); + p_sess->transfer_size = trans_ret.transferred; + /* XXX - handle failure, delete file? */ + if (tunable_plugin_architecture) + { + send_log_event_upload(p_sess, &trans_ret, p_filename, is_append, doesExist); + } + if (trans_ret.retval == 0) + { + vsf_log_do_log(p_sess, 1); + } + if (trans_ret.retval == -1) + { + vsf_cmdio_write(p_sess, FTP_BADSENDFILE, "Failure writing to local file."); + } + else if (trans_ret.retval == -2) + { + vsf_cmdio_write(p_sess, FTP_BADSENDNET, "Failure reading network stream."); + } + else + { + vsf_cmdio_write(p_sess, FTP_TRANSFEROK, "File receive OK."); + } + check_abor(p_sess); +port_pasv_cleanup_out: + port_cleanup(p_sess); + pasv_cleanup(p_sess); + vsf_sysutil_close(new_file_fd); +} + +static void +handle_mkd(struct vsf_session* p_sess) +{ + int retval; + resolve_tilde(&p_sess->ftp_arg_str, p_sess); + vsf_log_start_entry(p_sess, kVSFLogEntryMkdir); + str_copy(&p_sess->log_str, &p_sess->ftp_arg_str); + prepend_path_to_filename(&p_sess->log_str); + if (!vsf_access_check_file(&p_sess->ftp_arg_str)) + { + vsf_cmdio_write(p_sess, FTP_NOPERM, "Permission denied."); + return; + } + /* NOTE! Actual permissions will be governed by the tunable umask */ + if (tunable_plugin_architecture) + { + struct Request_Result_And_Reason request_result; + struct mystr dirPath = INIT_MYSTR; + struct mystr dirPName = INIT_MYSTR; + struct mystr dirPPath = INIT_MYSTR; + const char * toSanitize; + str_copy(&dirPath, &p_sess->ftp_arg_str); + prepend_path_to_filename(&dirPath); + toSanitize = str_strdup(&dirPath); + sanitize_path(&toSanitize); + str_alloc_text(&dirPath, toSanitize); + dirPName = getParentDirectoryNameStr(&dirPath); + dirPPath = getParentDirectoryPathStr(&dirPath); + request_result = vsf_request_write_directory(p_sess, &dirPPath, &dirPName); + str_free(&dirPath); + str_free(&dirPName); + str_free(&dirPPath); + vsf_sysutil_free((char*)toSanitize); + if (request_result.result) + { + vsf_cmdio_write(p_sess, FTP_FILEFAIL, request_result.reason); + send_log_event_mkdir_failed(p_sess); + doneWith_Result_And_Reason(&request_result); + return; + } + doneWith_Result_And_Reason(&request_result); + } + + retval = str_mkdir(&p_sess->ftp_arg_str, 0777); + + if (tunable_plugin_architecture) + { + send_log_event_mkdir(p_sess, retval); + } + if (retval != 0) + { + vsf_cmdio_write(p_sess, FTP_FILEFAIL, + "Create directory operation failed."); + return; + } + vsf_log_do_log(p_sess, 1); + { + static struct mystr s_mkd_res; + static struct mystr s_tmp_str; + str_copy(&s_tmp_str, &p_sess->ftp_arg_str); + prepend_path_to_filename(&s_tmp_str); + /* Double up double quotes */ + str_replace_text(&s_tmp_str, "\"", "\"\""); + /* Build result string */ + str_alloc_text(&s_mkd_res, "\""); + str_append_str(&s_mkd_res, &s_tmp_str); + str_append_text(&s_mkd_res, "\" created"); + vsf_cmdio_write_str(p_sess, FTP_MKDIROK, &s_mkd_res); + } +} + +static void +handle_rmd(struct vsf_session* p_sess) +{ + int retval; + resolve_tilde(&p_sess->ftp_arg_str, p_sess); + vsf_log_start_entry(p_sess, kVSFLogEntryRmdir); + str_copy(&p_sess->log_str, &p_sess->ftp_arg_str); + prepend_path_to_filename(&p_sess->log_str); + if (!vsf_access_check_file(&p_sess->ftp_arg_str)) + { + vsf_cmdio_write(p_sess, FTP_NOPERM, "Permission denied."); + return; + } + if (tunable_plugin_architecture) + { + struct Request_Result_And_Reason request_result; + struct mystr dirPath = INIT_MYSTR; + str_getcwd(&dirPath); + request_result = vsf_request_write_directory(p_sess, &dirPath ,&p_sess->ftp_arg_str); + str_free(&dirPath); + if (request_result.result) + { + vsf_cmdio_write(p_sess, FTP_FILEFAIL, request_result.reason); + send_log_event_rmdir_failed(p_sess); + doneWith_Result_And_Reason(&request_result); + return; + } + doneWith_Result_And_Reason(&request_result); + } + if (tunable_plugin_architecture) + { + struct Request_Result_And_Reason request_result; + struct mystr dirPath = INIT_MYSTR; + struct mystr dirPName = INIT_MYSTR; + struct mystr dirPPath = INIT_MYSTR; + const char * toSanitize; + str_copy(&dirPath, &p_sess->ftp_arg_str); + prepend_path_to_filename(&dirPath); + toSanitize = str_strdup(&dirPath); + sanitize_path(&toSanitize); + str_alloc_text(&dirPath, toSanitize); + dirPName = getParentDirectoryNameStr(&dirPath); + dirPPath = getParentDirectoryPathStr(&dirPath); + request_result = vsf_request_write_directory(p_sess, &dirPPath, &dirPName); + str_free(&dirPath); + str_free(&dirPName); + str_free(&dirPPath); + vsf_sysutil_free((char*)toSanitize); + if (request_result.result) + { + vsf_cmdio_write(p_sess, FTP_FILEFAIL, request_result.reason); + send_log_event_mkdir_failed(p_sess); + doneWith_Result_And_Reason(&request_result); + return; + } + doneWith_Result_And_Reason(&request_result); + } + + retval = str_rmdir(&p_sess->ftp_arg_str); + + if (tunable_plugin_architecture) + { + send_log_event_rmdir(p_sess, retval); + } + if (retval != 0) + { + vsf_cmdio_write(p_sess, FTP_FILEFAIL, + "Remove directory operation failed."); + } + else + { + vsf_log_do_log(p_sess, 1); + vsf_cmdio_write(p_sess, FTP_RMDIROK, + "Remove directory operation successful."); + } +} + +static void +handle_dele(struct vsf_session* p_sess) +{ + int retval; + resolve_tilde(&p_sess->ftp_arg_str, p_sess); + vsf_log_start_entry(p_sess, kVSFLogEntryDelete); + str_copy(&p_sess->log_str, &p_sess->ftp_arg_str); + prepend_path_to_filename(&p_sess->log_str); + if (!vsf_access_check_file(&p_sess->ftp_arg_str)) + { + vsf_cmdio_write(p_sess, FTP_NOPERM, "Permission denied."); + return; + } + + if (tunable_plugin_architecture) + { + struct Request_Result_And_Reason result; + result = checkFileWriteAccess(p_sess, &p_sess->ftp_arg_str); + if (result.result) + { + vsf_cmdio_write(p_sess, FTP_FILEFAIL, result.reason); + send_log_event_delete_failed(p_sess); + doneWith_Result_And_Reason(&result); + return; + } + doneWith_Result_And_Reason(&result); + } + + retval = str_unlink(&p_sess->ftp_arg_str); + if (tunable_plugin_architecture) + { + send_log_event_delete(p_sess, retval); + } + if (retval != 0) + { + vsf_cmdio_write(p_sess, FTP_FILEFAIL, "Delete operation failed."); + } + else + { + vsf_log_do_log(p_sess, 1); + vsf_cmdio_write(p_sess, FTP_DELEOK, "Delete operation successful."); + } +} + +static void +handle_rest(struct vsf_session* p_sess) +{ + static struct mystr s_rest_str; + filesize_t val = str_a_to_filesize_t(&p_sess->ftp_arg_str); + if (val < 0) + { + val = 0; + } + p_sess->restart_pos = val; + str_alloc_text(&s_rest_str, "Restart position accepted ("); + str_append_filesize_t(&s_rest_str, val); + str_append_text(&s_rest_str, ")."); + vsf_cmdio_write_str(p_sess, FTP_RESTOK, &s_rest_str); +} + +static void +handle_rnfr(struct vsf_session* p_sess) +{ + static struct vsf_sysutil_statbuf* p_statbuf; + int retval; + /* Clear old value */ + str_free(&p_sess->rnfr_filename_str); + resolve_tilde(&p_sess->ftp_arg_str, p_sess); + if (!vsf_access_check_file(&p_sess->ftp_arg_str)) + { + vsf_log_start_entry(p_sess, kVSFLogEntryRename); + str_copy(&p_sess->log_str, &p_sess->ftp_arg_str); + prepend_path_to_filename(&p_sess->log_str); + vsf_cmdio_write(p_sess, FTP_NOPERM, "Permission denied."); + return; + } + /* Does it exist? */ + retval = str_stat(&p_sess->ftp_arg_str, &p_statbuf); + if (retval == 0) + { + /* Yes */ + str_copy(&p_sess->rnfr_filename_str, &p_sess->ftp_arg_str); + vsf_cmdio_write(p_sess, FTP_RNFROK, "Ready for RNTO."); + } + else + { + vsf_log_start_entry(p_sess, kVSFLogEntryRename); + str_copy(&p_sess->log_str, &p_sess->ftp_arg_str); + prepend_path_to_filename(&p_sess->log_str); + vsf_cmdio_write(p_sess, FTP_FILEFAIL, "RNFR command failed."); + } +} + +static void +handle_rnto(struct vsf_session* p_sess) +{ + static struct mystr s_tmp_str; + int retval; + /* If we didn't get a RNFR, throw a wobbly */ + if (str_isempty(&p_sess->rnfr_filename_str)) + { + vsf_cmdio_write(p_sess, FTP_NEEDRNFR, + "RNFR required first."); + return; + } + resolve_tilde(&p_sess->ftp_arg_str, p_sess); + vsf_log_start_entry(p_sess, kVSFLogEntryRename); + str_copy(&p_sess->log_str, &p_sess->rnfr_filename_str); + prepend_path_to_filename(&p_sess->log_str); + str_append_char(&p_sess->log_str, ' '); + str_copy(&s_tmp_str, &p_sess->ftp_arg_str); + prepend_path_to_filename(&s_tmp_str); + str_append_str(&p_sess->log_str, &s_tmp_str); + if (!vsf_access_check_file(&p_sess->ftp_arg_str)) + { + vsf_cmdio_write(p_sess, FTP_NOPERM, "Permission denied."); + return; + } + /* NOTE - might overwrite destination file. Not a concern because the same + * could be accomplished with DELE. + */ + if (tunable_plugin_architecture) + { + struct Request_Result_And_Reason request_result; + struct mystr dirPath = INIT_MYSTR; + struct mystr dirPName = INIT_MYSTR; + struct mystr dirPPath = INIT_MYSTR; + const char * toSanitize; + str_copy(&dirPath, &p_sess->ftp_arg_str); + prepend_path_to_filename(&dirPath); + toSanitize = str_strdup(&dirPath); + sanitize_path(&toSanitize); + str_alloc_text(&dirPath, toSanitize); + dirPName = getParentDirectoryNameStr(&dirPath); + dirPPath = getParentDirectoryPathStr(&dirPath); + request_result = vsf_request_write_directory(p_sess, &dirPPath, &dirPName); + str_free(&dirPath); + str_free(&dirPName); + str_free(&dirPPath); + vsf_sysutil_free((char*)toSanitize); + if (request_result.result) + { + vsf_cmdio_write(p_sess, FTP_FILEFAIL, request_result.reason); + send_log_event_rename_failed(p_sess); + doneWith_Result_And_Reason(&request_result); + return; + } + doneWith_Result_And_Reason(&request_result); + } + retval = str_rename(&p_sess->rnfr_filename_str, &p_sess->ftp_arg_str); + if (tunable_plugin_architecture) + { + send_log_event_rename(p_sess, retval); + } + /* Clear the RNFR filename; start the two stage process again! */ + str_free(&p_sess->rnfr_filename_str); + if (retval == 0) + { + vsf_log_do_log(p_sess, 1); + vsf_cmdio_write(p_sess, FTP_RENAMEOK, "Rename successful."); + } + else + { + vsf_cmdio_write(p_sess, FTP_FILEFAIL, "Rename failed."); + } +} + +static void +handle_nlst(struct vsf_session* p_sess) +{ + handle_dir_common(p_sess, 0, 0); +} + +static void +prepend_path_to_filename(struct mystr* p_str) +{ + static struct mystr s_tmp_str; + /* Only prepend current working directory if the incoming filename is + * relative + */ + str_empty(&s_tmp_str); + if (str_isempty(p_str) || str_get_char_at(p_str, 0) != '/') + { + str_getcwd(&s_tmp_str); + /* Careful to not emit // if we are in directory / (common with chroot) */ + if (str_isempty(&s_tmp_str) || + str_get_char_at(&s_tmp_str, str_getlen(&s_tmp_str) - 1) != '/') + { + str_append_char(&s_tmp_str, '/'); + } + } + str_append_str(&s_tmp_str, p_str); + str_copy(p_str, &s_tmp_str); +} + + +static void +handle_sigurg(void* p_private) +{ + struct mystr async_cmd_str = INIT_MYSTR; + struct mystr async_arg_str = INIT_MYSTR; + struct mystr real_cmd_str = INIT_MYSTR; + unsigned int len; + struct vsf_session* p_sess = (struct vsf_session*) p_private; + /* Did stupid client sent something OOB without a data connection? */ + if (p_sess->data_fd == -1) + { + return; + } + /* Get the async command - blocks (use data timeout alarm) */ + vsf_cmdio_get_cmd_and_arg(p_sess, &async_cmd_str, &async_arg_str, 0); + /* Chop off first four characters; they are telnet characters. The client + * should have sent the first two normally and the second two as urgent + * data. + */ + len = str_getlen(&async_cmd_str); + if (len >= 4) + { + str_right(&async_cmd_str, &real_cmd_str, len - 4); + } + if (str_equal_text(&real_cmd_str, "ABOR")) + { + p_sess->abor_received = 1; + /* This is failok because of a small race condition; the SIGURG might + * be raised after the data socket is closed, but before data_fd is + * set to -1. + */ + vsf_sysutil_shutdown_failok(p_sess->data_fd); + } + else + { + /* Sorry! */ + vsf_cmdio_write(p_sess, FTP_BADCMD, "Unknown command."); + } + str_free(&async_cmd_str); + str_free(&async_arg_str); + str_free(&real_cmd_str); +} + +static int +get_remote_transfer_fd(struct vsf_session* p_sess, const char* p_status_msg) +{ + int remote_fd; + if (!pasv_active(p_sess) && !port_active(p_sess)) + { + bug("neither PORT nor PASV active in get_remote_transfer_fd"); + } + p_sess->abor_received = 0; + if (pasv_active(p_sess)) + { + remote_fd = vsf_ftpdataio_get_pasv_fd(p_sess); + } + else + { + remote_fd = vsf_ftpdataio_get_port_fd(p_sess); + } + if (vsf_sysutil_retval_is_error(remote_fd)) + { + return remote_fd; + } + vsf_cmdio_write(p_sess, FTP_DATACONN, p_status_msg); + if (vsf_ftpdataio_post_mark_connect(p_sess) != 1) + { + vsf_ftpdataio_dispose_transfer_fd(p_sess); + return -1; + } + return remote_fd; +} + +static void +check_abor(struct vsf_session* p_sess) +{ + /* If the client sent ABOR, respond to it here */ + if (p_sess->abor_received) + { + p_sess->abor_received = 0; + vsf_cmdio_write(p_sess, FTP_ABOROK, "ABOR successful."); + } +} + +static void +handle_size(struct vsf_session* p_sess) +{ + /* Note - in ASCII mode, are supposed to return the size after taking into + * account ASCII linefeed conversions. At least this is what wu-ftpd does in + * version 2.6.1. Proftpd-1.2.0pre fails to do this. + * I will not do it because it is a potential I/O DoS. + */ + static struct vsf_sysutil_statbuf* s_p_statbuf; + int retval; + resolve_tilde(&p_sess->ftp_arg_str, p_sess); + if (!vsf_access_check_file(&p_sess->ftp_arg_str)) + { + vsf_cmdio_write(p_sess, FTP_NOPERM, "Permission denied."); + return; + } + retval = str_stat(&p_sess->ftp_arg_str, &s_p_statbuf); + if (retval != 0 || !vsf_sysutil_statbuf_is_regfile(s_p_statbuf)) + { + vsf_cmdio_write(p_sess, FTP_FILEFAIL, "Could not get file size."); + } + else + { + static struct mystr s_size_res_str; + str_alloc_filesize_t(&s_size_res_str, + vsf_sysutil_statbuf_get_size(s_p_statbuf)); + vsf_cmdio_write_str(p_sess, FTP_SIZEOK, &s_size_res_str); + } +} + +static void +handle_site(struct vsf_session* p_sess) +{ + static struct mystr s_site_args_str; + /* What SITE sub-command is it? */ + str_split_char(&p_sess->ftp_arg_str, &s_site_args_str, ' '); + str_upper(&p_sess->ftp_arg_str); + if (tunable_write_enable && + tunable_chmod_enable && + str_equal_text(&p_sess->ftp_arg_str, "CHMOD")) + { + handle_site_chmod(p_sess, &s_site_args_str); + } + else if (str_equal_text(&p_sess->ftp_arg_str, "UMASK")) + { + handle_site_umask(p_sess, &s_site_args_str); + } + else if (str_equal_text(&p_sess->ftp_arg_str, "HELP")) + { + vsf_cmdio_write(p_sess, FTP_SITEHELP, "CHMOD UMASK HELP"); + } + else + { + vsf_cmdio_write(p_sess, FTP_BADCMD, "Unknown SITE command."); + } +} + +static void +handle_site_chmod(struct vsf_session* p_sess, struct mystr* p_arg_str) +{ + static struct mystr s_chmod_file_str; + unsigned int perms; + int retval; + if (str_isempty(p_arg_str)) + { + vsf_cmdio_write(p_sess, FTP_BADCMD, "SITE CHMOD needs 2 arguments."); + return; + } + str_split_char(p_arg_str, &s_chmod_file_str, ' '); + if (str_isempty(&s_chmod_file_str)) + { + vsf_cmdio_write(p_sess, FTP_BADCMD, "SITE CHMOD needs 2 arguments."); + return; + } + resolve_tilde(&s_chmod_file_str, p_sess); + vsf_log_start_entry(p_sess, kVSFLogEntryChmod); + str_copy(&p_sess->log_str, &s_chmod_file_str); + prepend_path_to_filename(&p_sess->log_str); + str_append_char(&p_sess->log_str, ' '); + str_append_str(&p_sess->log_str, p_arg_str); + if (!vsf_access_check_file(&s_chmod_file_str)) + { + vsf_cmdio_write(p_sess, FTP_NOPERM, "Permission denied."); + return; + } + /* Don't worry - our chmod() implementation only allows 0 - 0777 */ + + if (tunable_plugin_architecture) + { + struct Request_Result_And_Reason request_result; + struct mystr dirPath = INIT_MYSTR; + struct mystr dirPName = INIT_MYSTR; + struct mystr dirPPath = INIT_MYSTR; + const char * toSanitize; + str_copy(&dirPath, &s_chmod_file_str); + prepend_path_to_filename(&dirPath); + toSanitize = str_strdup(&dirPath); + sanitize_path(&toSanitize); + str_alloc_text(&dirPath, toSanitize); + dirPName = getParentDirectoryNameStr(&dirPath); + dirPPath = getParentDirectoryPathStr(&dirPath); + request_result = vsf_request_write_directory(p_sess, &dirPPath, &dirPName); + str_free(&dirPath); + str_free(&dirPName); + str_free(&dirPPath); + vsf_sysutil_free((char*)toSanitize); + if (request_result.result) + { + vsf_cmdio_write(p_sess, FTP_FILEFAIL, request_result.reason); + send_log_event_rename_failed(p_sess); + doneWith_Result_And_Reason(&request_result); + return; + } + doneWith_Result_And_Reason(&request_result); + } + + perms = str_octal_to_uint(p_arg_str); + retval = str_chmod(&s_chmod_file_str, perms); + + if (tunable_plugin_architecture) + { + send_log_event_chmod(p_sess, p_arg_str, &s_chmod_file_str, retval); + } + + if (vsf_sysutil_retval_is_error(retval)) + { + vsf_cmdio_write(p_sess, FTP_FILEFAIL, "SITE CHMOD command failed."); + } + else + { + vsf_log_do_log(p_sess, 1); + vsf_cmdio_write(p_sess, FTP_CHMODOK, "SITE CHMOD command ok."); + } +} + +static void +handle_site_umask(struct vsf_session* p_sess, struct mystr* p_arg_str) +{ + static struct mystr s_umask_resp_str; + if (str_isempty(p_arg_str)) + { + /* Empty arg => report current umask */ + str_alloc_text(&s_umask_resp_str, "Your current UMASK is "); + str_append_text(&s_umask_resp_str, + vsf_sysutil_uint_to_octal(vsf_sysutil_get_umask())); + } + else + { + /* Set current umask */ + unsigned int new_umask = str_octal_to_uint(p_arg_str); + vsf_sysutil_set_umask(new_umask); + str_alloc_text(&s_umask_resp_str, "UMASK set to "); + str_append_text(&s_umask_resp_str, + vsf_sysutil_uint_to_octal(vsf_sysutil_get_umask())); + } + vsf_cmdio_write_str(p_sess, FTP_UMASKOK, &s_umask_resp_str); +} + +static void +handle_appe(struct vsf_session* p_sess) +{ + handle_upload_common(p_sess, 1, 0); +} + +static void +handle_mdtm(struct vsf_session* p_sess) +{ + static struct mystr s_filename_str; + static struct vsf_sysutil_statbuf* s_p_statbuf; + int do_write = 0; + long modtime = 0; + struct str_locate_result loc = str_locate_char(&p_sess->ftp_arg_str, ' '); + int retval = str_stat(&p_sess->ftp_arg_str, &s_p_statbuf); + if (tunable_mdtm_write && retval != 0 && loc.found && + vsf_sysutil_isdigit(str_get_char_at(&p_sess->ftp_arg_str, 0))) + { + if (loc.index == 8 || loc.index == 14 || + (loc.index > 15 && str_get_char_at(&p_sess->ftp_arg_str, 14) == '.')) + { + do_write = 1; + } + } + if (do_write != 0) + { + str_split_char(&p_sess->ftp_arg_str, &s_filename_str, ' '); + modtime = vsf_sysutil_parse_time(str_getbuf(&p_sess->ftp_arg_str)); + str_copy(&p_sess->ftp_arg_str, &s_filename_str); + } + resolve_tilde(&p_sess->ftp_arg_str, p_sess); + if (!vsf_access_check_file(&p_sess->ftp_arg_str)) + { + vsf_cmdio_write(p_sess, FTP_NOPERM, "Permission denied."); + return; + } + if (do_write && tunable_write_enable && + (tunable_anon_other_write_enable || !p_sess->is_anonymous)) + { + retval = str_stat(&p_sess->ftp_arg_str, &s_p_statbuf); + if (retval != 0 || !vsf_sysutil_statbuf_is_regfile(s_p_statbuf)) + { + vsf_cmdio_write(p_sess, FTP_FILEFAIL, + "Could not set file modification time."); + } + else + { + retval = vsf_sysutil_setmodtime( + str_getbuf(&p_sess->ftp_arg_str), modtime, tunable_use_localtime); + if (retval != 0) + { + vsf_cmdio_write(p_sess, FTP_FILEFAIL, + "Could not set file modification time."); + } + else + { + vsf_cmdio_write(p_sess, FTP_MDTMOK, + "File modification time set."); + } + } + } + else + { + if (retval != 0 || !vsf_sysutil_statbuf_is_regfile(s_p_statbuf)) + { + vsf_cmdio_write(p_sess, FTP_FILEFAIL, + "Could not get file modification time."); + } + else + { + static struct mystr s_mdtm_res_str; + str_alloc_text(&s_mdtm_res_str, + vsf_sysutil_statbuf_get_numeric_date( + s_p_statbuf, tunable_use_localtime)); + vsf_cmdio_write_str(p_sess, FTP_MDTMOK, &s_mdtm_res_str); + } + } +} + +static void +handle_eprt(struct vsf_session* p_sess) +{ + static struct mystr s_part1_str; + static struct mystr s_part2_str; + int proto; + int port; + const unsigned char* p_raw_addr; + int is_ipv6 = vsf_sysutil_sockaddr_is_ipv6(p_sess->p_local_addr); + port_cleanup(p_sess); + pasv_cleanup(p_sess); + str_copy(&s_part1_str, &p_sess->ftp_arg_str); + str_split_char(&s_part1_str, &s_part2_str, '|'); + if (!str_isempty(&s_part1_str)) + { + goto bad_eprt; + } + /* Split out the protocol and check it */ + str_split_char(&s_part2_str, &s_part1_str, '|'); + proto = str_atoi(&s_part2_str); + if (proto < 1 || proto > 2 || (!is_ipv6 && proto == 2)) + { + vsf_cmdio_write(p_sess, FTP_BADCMD, "Bad EPRT protocol."); + return; + } + /* Split out address and parse it */ + str_split_char(&s_part1_str, &s_part2_str, '|'); + if (proto == 2) + { + p_raw_addr = vsf_sysutil_parse_ipv6(&s_part1_str); + } + else + { + p_raw_addr = vsf_sysutil_parse_ipv4(&s_part1_str); + } + if (!p_raw_addr) + { + goto bad_eprt; + } + /* Split out port and parse it */ + str_split_char(&s_part2_str, &s_part1_str, '|'); + if (!str_isempty(&s_part1_str) || str_isempty(&s_part2_str)) + { + goto bad_eprt; + } + port = str_atoi(&s_part2_str); + if (port < 0 || port > 65535) + { + goto bad_eprt; + } + vsf_sysutil_sockaddr_clone(&p_sess->p_port_sockaddr, p_sess->p_local_addr); + if (proto == 2) + { + vsf_sysutil_sockaddr_set_ipv6addr(p_sess->p_port_sockaddr, p_raw_addr); + } + else + { + vsf_sysutil_sockaddr_set_ipv4addr(p_sess->p_port_sockaddr, p_raw_addr); + } + vsf_sysutil_sockaddr_set_port(p_sess->p_port_sockaddr, (unsigned short) port); + /* SECURITY: + * 1) Reject requests not connecting to the control socket IP + * 2) Reject connects to privileged ports + */ + if (!tunable_port_promiscuous) + { + if (!vsf_sysutil_sockaddr_addr_equal(p_sess->p_remote_addr, + p_sess->p_port_sockaddr) || + vsf_sysutil_is_port_reserved(port)) + { + vsf_cmdio_write(p_sess, FTP_BADCMD, "Illegal EPRT command."); + port_cleanup(p_sess); + return; + } + } + vsf_cmdio_write(p_sess, FTP_EPRTOK, + "EPRT command successful. Consider using EPSV."); + return; +bad_eprt: + vsf_cmdio_write(p_sess, FTP_BADCMD, "Bad EPRT command."); +} + +/* XXX - add AUTH etc. */ +static void +handle_help(struct vsf_session* p_sess) +{ + vsf_cmdio_write_hyphen(p_sess, FTP_HELP, + "The following commands are recognized."); + vsf_cmdio_write_raw(p_sess, +" ABOR ACCT ALLO APPE CDUP CWD DELE EPRT EPSV FEAT HELP LIST MDTM MKD\r\n"); + vsf_cmdio_write_raw(p_sess, +" MODE NLST NOOP OPTS PASS PASV PORT PWD QUIT REIN REST RETR RMD RNFR\r\n"); + vsf_cmdio_write_raw(p_sess, +" RNTO SITE SIZE SMNT STAT STOR STOU STRU SYST TYPE USER XCUP XCWD XMKD\r\n"); + vsf_cmdio_write_raw(p_sess, +" XPWD XRMD\r\n"); + vsf_cmdio_write(p_sess, FTP_HELP, "Help OK."); +} + +static void +handle_stou(struct vsf_session* p_sess) +{ + handle_upload_common(p_sess, 0, 1); +} + +static void +get_unique_filename(struct mystr* p_outstr, const struct mystr* p_base_str) +{ + /* Use silly wu-ftpd algorithm for compatibility */ + static struct vsf_sysutil_statbuf* s_p_statbuf; + unsigned int suffix = 1; + int retval; + while (1) + { + str_copy(p_outstr, p_base_str); + str_append_char(p_outstr, '.'); + str_append_ulong(p_outstr, suffix); + retval = str_stat(p_outstr, &s_p_statbuf); + if (vsf_sysutil_retval_is_error(retval)) + { + return; + } + ++suffix; + } +} + +static void +handle_stat(struct vsf_session* p_sess) +{ + vsf_cmdio_write_hyphen(p_sess, FTP_STATOK, "FTP server status:"); + vsf_cmdio_write_raw(p_sess, " Connected to "); + vsf_cmdio_write_raw(p_sess, str_getbuf(&p_sess->remote_ip_str)); + vsf_cmdio_write_raw(p_sess, "\r\n"); + vsf_cmdio_write_raw(p_sess, " Logged in as "); + vsf_cmdio_write_raw(p_sess, str_getbuf(&p_sess->user_str)); + vsf_cmdio_write_raw(p_sess, "\r\n"); + vsf_cmdio_write_raw(p_sess, " TYPE: "); + if (p_sess->is_ascii) + { + vsf_cmdio_write_raw(p_sess, "ASCII\r\n"); + } + else + { + vsf_cmdio_write_raw(p_sess, "BINARY\r\n"); + } + if (p_sess->bw_rate_max == 0) + { + vsf_cmdio_write_raw(p_sess, " No session bandwidth limit\r\n"); + } + else + { + vsf_cmdio_write_raw(p_sess, " Session bandwidth limit in byte/s is "); + vsf_cmdio_write_raw(p_sess, vsf_sysutil_ulong_to_str(p_sess->bw_rate_max)); + vsf_cmdio_write_raw(p_sess, "\r\n"); + } + if (tunable_idle_session_timeout == 0) + { + vsf_cmdio_write_raw(p_sess, " No session timeout\r\n"); + } + else + { + vsf_cmdio_write_raw(p_sess, " Session timeout in seconds is "); + vsf_cmdio_write_raw(p_sess, + vsf_sysutil_ulong_to_str(tunable_idle_session_timeout)); + vsf_cmdio_write_raw(p_sess, "\r\n"); + } + if (p_sess->control_use_ssl) + { + vsf_cmdio_write_raw(p_sess, " Control connection is encrypted\r\n"); + } + else + { + vsf_cmdio_write_raw(p_sess, " Control connection is plain text\r\n"); + } + if (p_sess->data_use_ssl) + { + vsf_cmdio_write_raw(p_sess, " Data connections will be encrypted\r\n"); + } + else + { + vsf_cmdio_write_raw(p_sess, " Data connections will be plain text\r\n"); + } + if (p_sess->num_clients > 0) + { + vsf_cmdio_write_raw(p_sess, " At session startup, client count was "); + vsf_cmdio_write_raw(p_sess, vsf_sysutil_ulong_to_str(p_sess->num_clients)); + vsf_cmdio_write_raw(p_sess, "\r\n"); + } + vsf_cmdio_write_raw(p_sess, + " vsFTPd " VSF_VERSION " - secure, fast, stable\r\n"); + vsf_cmdio_write(p_sess, FTP_STATOK, "End of status"); +} + +static void +handle_stat_file(struct vsf_session* p_sess) +{ + handle_dir_common(p_sess, 1, 1); +} + +static int +data_transfer_checks_ok(struct vsf_session* p_sess) +{ + if (!pasv_active(p_sess) && !port_active(p_sess)) + { + vsf_cmdio_write(p_sess, FTP_BADSENDCONN, "Use PORT or PASV first."); + return 0; + } + if (tunable_ssl_enable && !p_sess->data_use_ssl && + ((tunable_force_local_data_ssl && !p_sess->is_anonymous) || + (tunable_force_anon_data_ssl && p_sess->is_anonymous))) + { + vsf_cmdio_write( + p_sess, FTP_NEEDENCRYPT, "Data connections must be encrypted."); + return 0; + } + return 1; +} + +static void +resolve_tilde(struct mystr* p_str, struct vsf_session* p_sess) +{ + unsigned int len = str_getlen(p_str); + if (len > 0 && str_get_char_at(p_str, 0) == '~') + { + static struct mystr s_rhs_str; + if (len == 1 || str_get_char_at(p_str, 1) == '/') + { + str_split_char(p_str, &s_rhs_str, '~'); + str_copy(p_str, &p_sess->home_str); + str_append_str(p_str, &s_rhs_str); + } + else if (tunable_tilde_user_enable && len > 1) + { + static struct mystr s_user_str; + struct vsf_sysutil_user* p_user; + str_copy(&s_rhs_str, p_str); + str_split_char(&s_rhs_str, &s_user_str, '~'); + str_split_char(&s_user_str, &s_rhs_str, '/'); + p_user = str_getpwnam(&s_user_str); + if (p_user != 0) + { + str_alloc_text(p_str, vsf_sysutil_user_get_homedir(p_user)); + if (!str_isempty(&s_rhs_str)) + { + str_append_char(p_str, '/'); + str_append_str(p_str, &s_rhs_str); + } + } + } + } +} + +void try_change_to_homedir(struct vsf_session* p_sess) { + struct Request_Result_Homedir homedir = vsf_request_user_homedir(p_sess); + struct mystr home = INIT_MYSTR; + str_alloc_text(&home, homedir.homedir); + str_chdir(&home); +} diff --git a/postlogin.h b/postlogin.h new file mode 100644 index 0000000..35138de --- /dev/null +++ b/postlogin.h @@ -0,0 +1,15 @@ +#ifndef VSF_POSTLOGIN_H +#define VSF_POSTLOGIN_H + +struct vsf_session; + +/* process_post_login() + * PURPOSE + * Called to begin FTP protocol parsing for a logged in session. + * PARAMETERS + * p_sess - the current session object + */ +void process_post_login(struct vsf_session* p_sess); + +#endif /* VSF_POSTLOGIN_H */ + diff --git a/postlogin_events.c b/postlogin_events.c new file mode 100644 index 0000000..32014bc --- /dev/null +++ b/postlogin_events.c @@ -0,0 +1,216 @@ +#include "postlogin_events.h" +#include "event_c_api.h" +#include "sysstr.h" +#include "sysutil.h" +#include "vsfutils_c.h" + +static void +prepend_path_to_filename(struct mystr* p_str) +{ + static struct mystr s_tmp_str; + /* Only prepend current working directory if the incoming filename is + * relative + */ + str_empty(&s_tmp_str); + if (str_isempty(p_str) || str_get_char_at(p_str, 0) != '/') + { + str_getcwd(&s_tmp_str); + /* Careful to not emit // if we are in directory / (common with chroot) */ + if (str_isempty(&s_tmp_str) || + str_get_char_at(&s_tmp_str, str_getlen(&s_tmp_str) - 1) != '/') + { + str_append_char(&s_tmp_str, '/'); + } + } + str_append_str(&s_tmp_str, p_str); + str_copy(p_str, &s_tmp_str); +} + +void send_log_event_download(struct vsf_session * p_sess, struct vsf_transfer_ret * trans_ret) { + struct Event_Download event; + struct mystr dirPath = INIT_MYSTR; + populate_shared_event(&event.sharedInfo, p_sess); + event.fileInfo.name = str_strdup(&p_sess->ftp_arg_str); + str_getcwd(&dirPath); + event.fileInfo.path = str_strdup(&dirPath); + event.fileInfo.size = p_sess->transfer_size; + event.is_complete = ((trans_ret->retval == 0) ? 1 : 0); + sanitize_path_and_name(&event.fileInfo.path, &event.fileInfo.name); + send_event_download(&event); + vsf_sysutil_free((char*)event.fileInfo.name); + vsf_sysutil_free((char*)event.fileInfo.path); + vsf_sysutil_free((char*)event.sharedInfo.user_name); +} + +void send_log_event_download_failed(struct vsf_session * p_sess) { + struct Event_Download event; + struct mystr dirPath = INIT_MYSTR; + populate_shared_event(&event.sharedInfo, p_sess); + event.fileInfo.name = str_strdup(&p_sess->ftp_arg_str); + str_getcwd(&dirPath); + event.fileInfo.path = str_strdup(&dirPath); + event.fileInfo.size = 0; + event.is_complete = -1; + sanitize_path_and_name(&event.fileInfo.path, &event.fileInfo.name); + send_event_download(&event); + vsf_sysutil_free((char*)event.fileInfo.name); + vsf_sysutil_free((char*)event.fileInfo.path); + vsf_sysutil_free((char*)event.sharedInfo.user_name); +} + +void send_log_event_upload(struct vsf_session * p_sess, struct vsf_transfer_ret * trans_ret, struct mystr * p_filename, int is_append, int doesExist) { + struct Event_Upload event; + struct mystr dirPath = INIT_MYSTR; + populate_shared_event(&event.sharedInfo, p_sess); + event.fileInfo.name = str_strdup(p_filename); + str_getcwd(&dirPath); + event.fileInfo.path = str_strdup(&dirPath); + event.fileInfo.size = p_sess->transfer_size; + event.is_complete = ((trans_ret->retval == 0) ? 1 : 0); + event.is_append = is_append; + event.is_overwrite = doesExist; + sanitize_path_and_name(&event.fileInfo.path, &event.fileInfo.name); + send_event_upload(&event); + vsf_sysutil_free((char*)event.fileInfo.name); + vsf_sysutil_free((char*)event.fileInfo.path); + vsf_sysutil_free((char*)event.sharedInfo.user_name); + str_free(&dirPath); +} + +void send_log_event_upload_failed(struct vsf_session * p_sess, struct mystr * p_filename, int is_append, int doesExist) { + struct Event_Upload event; + struct mystr dirPath = INIT_MYSTR; + populate_shared_event(&event.sharedInfo, p_sess); + event.fileInfo.name = str_strdup(p_filename); + str_getcwd(&dirPath); + event.fileInfo.path = str_strdup(&dirPath); + event.fileInfo.size = p_sess->transfer_size; + event.is_complete = -1; + event.is_append = is_append; + event.is_overwrite = doesExist; + sanitize_path_and_name(&event.fileInfo.path, &event.fileInfo.name); + send_event_upload(&event); + vsf_sysutil_free((char*)event.fileInfo.name); + vsf_sysutil_free((char*)event.fileInfo.path); + vsf_sysutil_free((char*)event.sharedInfo.user_name); + str_free(&dirPath); +} + +void send_log_event_mkdir(struct vsf_session * p_sess, int succeeded) { + struct Event_Mkdir event; + struct mystr dirPath = INIT_MYSTR; + populate_shared_event(&event.sharedInfo, p_sess); + str_copy(&dirPath, &p_sess->ftp_arg_str); + prepend_path_to_filename(&dirPath); + event.path = str_strdup(&dirPath); + event.succeeded = succeeded; + sanitize_path(&event.path); + send_event_mkdir(&event); + vsf_sysutil_free((char*)event.path); + vsf_sysutil_free((char*)event.sharedInfo.user_name); + str_free(&dirPath); +} + +void send_log_event_mkdir_failed(struct vsf_session * p_sess) { + send_log_event_mkdir(p_sess, -1); +} + +void send_log_event_rmdir(struct vsf_session * p_sess, int succeeded) { + struct Event_Rmdir event; + struct mystr dirPath = INIT_MYSTR; + populate_shared_event(&event.sharedInfo, p_sess); + str_copy(&dirPath, &p_sess->ftp_arg_str); + prepend_path_to_filename(&dirPath); + event.path = str_strdup(&dirPath); + event.succeeded = succeeded; + sanitize_path(&event.path); + send_event_rmdir(&event); + vsf_sysutil_free((char*)event.path); + vsf_sysutil_free((char*)event.sharedInfo.user_name); + str_free(&dirPath); +} + +void send_log_event_rmdir_failed(struct vsf_session * p_sess) { + send_log_event_rmdir(p_sess, -1); +} + +void send_log_event_delete(struct vsf_session * p_sess, int succeeded) { + struct Event_Delete event; + struct mystr dirPath = INIT_MYSTR; + populate_shared_event(&event.sharedInfo, p_sess); + str_copy(&dirPath, &p_sess->ftp_arg_str); + prepend_path_to_filename(&dirPath); + event.path = str_strdup(&dirPath); + event.succeeded = succeeded; + sanitize_path(&event.path); + send_event_delete(&event); + vsf_sysutil_free((char*)event.path); + vsf_sysutil_free((char*)event.sharedInfo.user_name); + str_free(&dirPath); +} + +void send_log_event_delete_failed(struct vsf_session * p_sess) { + send_log_event_delete(p_sess, 0); +} + +void send_log_event_rename(struct vsf_session * p_sess, int succeeded) { + struct Event_Rename event; + struct mystr newname = INIT_MYSTR; + struct mystr oldname = INIT_MYSTR; + populate_shared_event(&event.sharedInfo, p_sess); + str_copy(&oldname, &p_sess->rnfr_filename_str); + prepend_path_to_filename(&oldname); + event.old_name = str_strdup(&oldname); + str_copy(&newname, &p_sess->ftp_arg_str); + prepend_path_to_filename(&newname); + event.new_name = str_strdup(&newname); + event.succeeded = succeeded; + sanitize_path(&event.old_name); + sanitize_path(&event.new_name); + send_event_rename(&event); + vsf_sysutil_free((char*)event.old_name); + vsf_sysutil_free((char*)event.new_name); + vsf_sysutil_free((char*)event.sharedInfo.user_name); + str_free(&newname); + str_free(&oldname); +} + +void send_log_event_rename_failed(struct vsf_session * p_sess) { + send_log_event_rename(p_sess, -1); +} + +void send_log_event_chmod(struct vsf_session * p_sess, struct mystr * p_arg_str, struct mystr * s_chmod_file_str, int retval) { + struct Event_Chmod event; + struct mystr path_str = INIT_MYSTR; + populate_shared_event(&event.sharedInfo, p_sess); + event.new_attrib = str_strdup(p_arg_str); + str_getcwd(&path_str); + event.name = str_strdup(s_chmod_file_str); + event.path = str_strdup(&path_str); + event.succeeded = ((vsf_sysutil_retval_is_error(retval)) ? 0 : 1); + sanitize_path_and_name(&event.path, &event.name); + send_event_chmod(&event); + vsf_sysutil_free((char*)event.new_attrib); + vsf_sysutil_free((char*)event.name); + vsf_sysutil_free((char*)event.path); + vsf_sysutil_free((char*)event.sharedInfo.user_name); + str_free(&path_str); +} + +void send_log_event_chmod_failed(struct vsf_session * p_sess, struct mystr * p_arg_str, struct mystr * s_chmod_file_str) { + struct Event_Chmod event; + struct mystr path_str = INIT_MYSTR; + populate_shared_event(&event.sharedInfo, p_sess); + event.new_attrib = str_strdup(p_arg_str); + str_getcwd(&path_str); + event.name = str_strdup(s_chmod_file_str); + event.path = str_strdup(&path_str); + event.succeeded = -1; + sanitize_path_and_name(&event.path, &event.name); + send_event_chmod(&event); + vsf_sysutil_free((char*)event.new_attrib); + vsf_sysutil_free((char*)event.name); + vsf_sysutil_free((char*)event.path); + vsf_sysutil_free((char*)event.sharedInfo.user_name); + str_free(&path_str); +} diff --git a/postlogin_events.h b/postlogin_events.h new file mode 100644 index 0000000..3027d2e --- /dev/null +++ b/postlogin_events.h @@ -0,0 +1,24 @@ +#ifndef POSTLOGIN_EVENTS_H +#define POSTLOGIN_EVENTS_H + +#include "session.h" +#include "ftpdataio.h" +#include "str.h" + +void send_log_event_download(struct vsf_session * p_sess, struct vsf_transfer_ret * trans_ret); +void send_log_event_download_failed(struct vsf_session * p_sess); +void send_log_event_upload(struct vsf_session * p_sess, struct vsf_transfer_ret * trans_ret, struct mystr* p_filename, int is_append, int doesExist); +void send_log_event_upload_failed(struct vsf_session * p_sess, struct mystr* p_filename, int is_append, int doesExist); +void send_log_event_mkdir(struct vsf_session * p_sess, int succeeded); +void send_log_event_mkdir_failed(struct vsf_session * p_sess); +void send_log_event_rmdir(struct vsf_session * p_sess, int succeeded); +void send_log_event_rmdir_failed(struct vsf_session * p_sess); +void send_log_event_delete(struct vsf_session * p_sess, int succeeded); +void send_log_event_delete_failed(struct vsf_session * p_sess); +void send_log_event_chmod(struct vsf_session * p_sess, struct mystr * p_arg_str, struct mystr * s_chmod_file_str, int retval); +void send_log_event_chmod_failed(struct vsf_session * p_sess, struct mystr * p_arg_str, struct mystr * s_chmod_file_str); +void send_log_event_rename(struct vsf_session * p_sess, int succeeded); +void send_log_event_rename_failed(struct vsf_session * p_sess); + +#endif //POSTLOGIN_EVENTS_H + diff --git a/postlogin_requests.c b/postlogin_requests.c new file mode 100644 index 0000000..33ad185 --- /dev/null +++ b/postlogin_requests.c @@ -0,0 +1,284 @@ +#include "postlogin_requests.h" +#include "sysstr.h" +#include "sysutil.h" +#include "vsfutils_c.h" +#include "request_c_api.h" + +static void +prepend_path_to_filename_2(struct mystr* p_str, const struct mystr* path_str) +{ + static struct mystr s_tmp_str; + /* Only prepend current working directory if the incoming filename is + * relative + */ + str_empty(&s_tmp_str); + if (str_isempty(p_str) || str_get_char_at(p_str, 0) != '/') + { + str_copy(&s_tmp_str, path_str); + /* Careful to not emit // if we are in directory / (common with chroot) */ + if (str_isempty(&s_tmp_str) || + str_get_char_at(&s_tmp_str, str_getlen(&s_tmp_str) - 1) != '/') + { + str_append_char(&s_tmp_str, '/'); + } + } + str_append_str(&s_tmp_str, p_str); + str_copy(p_str, &s_tmp_str); +} + +static void +prepend_path_to_filename(struct mystr* p_str) +{ + static struct mystr s_tmp_str; + /* Only prepend current working directory if the incoming filename is + * relative + */ + str_empty(&s_tmp_str); + if (str_isempty(p_str) || str_get_char_at(p_str, 0) != '/') + { + str_getcwd(&s_tmp_str); + /* Careful to not emit // if we are in directory / (common with chroot) */ + if (str_isempty(&s_tmp_str) || + str_get_char_at(&s_tmp_str, str_getlen(&s_tmp_str) - 1) != '/') + { + str_append_char(&s_tmp_str, '/'); + } + } + str_append_str(&s_tmp_str, p_str); + str_copy(p_str, &s_tmp_str); +} + +const char * +getParentDirectoryName(const struct mystr * fullFilePath) { + struct mystr tmp = getParentDirectoryNameStr(fullFilePath); + const char * ret = str_strdup(&tmp); + str_free(&tmp); + return ret; +} + +const char * +getParentDirectoryPath(const struct mystr * fullFilePath) { + struct mystr tmp = getParentDirectoryPathStr(fullFilePath); + const char * ret = str_strdup(&tmp); + str_free(&tmp); + return ret; +} + +struct mystr getParentDirectoryNameStr(const struct mystr * fullFilePath) { + struct mystr tmp = INIT_MYSTR; + struct mystr name = INIT_MYSTR; + str_copy(&tmp, fullFilePath); + str_split_char_reverse(&tmp, &name, '/'); + if (str_isempty(&tmp)) + { + str_alloc_text(&name, "/"); + } + else + { + str_split_char_reverse(&tmp, &name, '/'); + } + str_free(&tmp); + return name; +} + +struct mystr getParentDirectoryPathStr(const struct mystr * fullFilePath) { + struct mystr tmp = INIT_MYSTR; + struct mystr name = INIT_MYSTR; + str_copy(&tmp, fullFilePath); + str_split_char_reverse(&tmp, &name, '/'); + if (str_isempty(&tmp)) + { + str_alloc_text(&name, "/"); + } + else + { + str_split_char_reverse(&tmp, &name, '/'); + } + if (str_isempty(&tmp)) + { + str_alloc_text(&tmp, "/"); + } + str_free(&name); + return tmp; +} + +struct Request_Result_And_Reason +checkFileWriteAccess(struct vsf_session * p_sess, struct mystr * filename) { + int tmpFD = str_open(filename, kVSFSysUtilOpenReadOnly); + if (tmpFD != -1) + { + vsf_sysutil_close(tmpFD); + + {//requestWriteFile + struct Request_Write_File request; + struct Request_Result_And_Reason request_result; + struct mystr dirName = INIT_MYSTR; + populate_shared_request(&request.sharedInfo, p_sess); + str_getcwd(&dirName); + request.does_exist = 1; + request.path = str_strdup(&dirName); + request.name = str_strdup(filename); + str_copy(&dirName, filename); + prepend_path_to_filename(&dirName); + request.fullName = str_strdup(&dirName); + sanitize_path_and_name(&request.path, &request.name); + sanitize_path(&request.fullName); + request_result = send_request_write_File(&request); + vsf_sysutil_free((char*)request.sharedInfo.user_name); + vsf_sysutil_free((char*)request.fullName); + vsf_sysutil_free((char*)request.name); + vsf_sysutil_free((char*)request.path); + str_free(&dirName); + if (request_result.result) + { + return request_result; + } + else + { + doneWith_Result_And_Reason(&request_result); + } + }//requestWriteFile + } + {//requestWriteDir + struct Request_Write_Directory request; + struct Request_Result_And_Reason request_result; + struct mystr fullFileName = INIT_MYSTR; + const char * toSanitize; + populate_shared_request(&request.sharedInfo, p_sess); + str_copy(&fullFileName, filename); + prepend_path_to_filename(&fullFileName); + toSanitize = str_strdup(&fullFileName); + sanitize_path(&toSanitize); + str_alloc_text(&fullFileName, toSanitize); + request.name = getParentDirectoryName(&fullFileName); + request.parentPath = getParentDirectoryPath(&fullFileName); + request.does_exist = 1; + sanitize_path_and_name(&request.parentPath, &request.name); + request_result = send_request_write_Directory(&request); + vsf_sysutil_free((char*)request.sharedInfo.user_name); + vsf_sysutil_free((char*)request.name); + vsf_sysutil_free((char*)request.parentPath); + str_free(&fullFileName); + vsf_sysutil_free((char*)toSanitize); + return request_result; + }//requestWriteDir + +} + +struct Request_Result_And_Reason +vsf_request_read_directory(struct vsf_session * p_sess, const struct mystr* parentPath, const struct mystr* name) { + struct Request_Read_Directory request; + struct Request_Result_And_Reason request_result; + populate_shared_request(&request.sharedInfo, p_sess); + request.name = str_strdup(name); + request.parentPath = str_strdup(parentPath); + sanitize_path_and_name(&request.parentPath, &request.name); + request_result = send_request_read_Directory(&request); + vsf_sysutil_free((char*)request.parentPath); + vsf_sysutil_free((char*)request.name); + vsf_sysutil_free((char*)request.sharedInfo.user_name); + return request_result; +} + +struct Request_Result_And_Reason +vsf_request_write_directory(struct vsf_session * p_sess, const struct mystr* parentPath, const struct mystr* name) { + struct Request_Write_Directory request; + struct Request_Result_And_Reason request_result; + populate_shared_request(&request.sharedInfo, p_sess); + { + struct mystr dirFull = INIT_MYSTR; + str_copy(&dirFull, name); + prepend_path_to_filename_2(&dirFull, parentPath); + struct vsf_sysutil_dir * pDir = str_opendir(&dirFull); + if (pDir) + { + request.does_exist = 1; + vsf_sysutil_free(pDir); + } + else + { + request.does_exist = 0; + } + str_free(&dirFull); + } + request.name = str_strdup(name); + request.parentPath = str_strdup(parentPath); + sanitize_path_and_name(&request.parentPath, &request.name); + request_result = send_request_write_Directory(&request); + vsf_sysutil_free((char*)request.name); + vsf_sysutil_free((char*)request.parentPath); + vsf_sysutil_free((char*)request.sharedInfo.user_name); + return request_result; +} + +struct Request_Result_And_Reason +vsf_request_read_file(struct vsf_session * p_sess, const struct mystr* path, const struct mystr* name) { + struct Request_Read_File request; + struct vsf_sysutil_statbuf * statbuf = 0; + struct mystr fullFilePath = INIT_MYSTR; + struct Request_Result_And_Reason request_result; + int opened_file = str_open(name, kVSFSysStrOpenReadOnly); + populate_shared_request(&request.sharedInfo, p_sess); + vsf_sysutil_fstat(opened_file, &statbuf); + request.size = vsf_sysutil_statbuf_get_size(statbuf); + request.name = str_strdup(name); + request.path = str_strdup(path); + str_copy(&fullFilePath, name); + prepend_path_to_filename_2(&fullFilePath, path); + request.fullName = str_strdup(&fullFilePath); + sanitize_path_and_name(&request.path, &request.name); + sanitize_path(&request.fullName); + request_result = send_request_read_File(&request); + vsf_sysutil_free(statbuf); + vsf_sysutil_free((char*)request.fullName); + vsf_sysutil_free((char*)request.name); + vsf_sysutil_free((char*)request.path); + vsf_sysutil_free((char*)request.sharedInfo.user_name); + str_free(&fullFilePath); + vsf_sysutil_close(opened_file); + return request_result; +} + +struct Request_Result_And_Reason +vsf_request_write_file(struct vsf_session * p_sess, const struct mystr* path, const struct mystr* name) { + struct Request_Write_File request; + struct Request_Result_And_Reason request_result; + struct mystr fullFilePath = INIT_MYSTR; + str_copy(&fullFilePath, name); + prepend_path_to_filename_2(&fullFilePath, path); + populate_shared_request(&request.sharedInfo, p_sess); + { + int tmpFD = str_open(&fullFilePath,kVSFSysUtilOpenReadOnly); + if (tmpFD == -1) + { + request.does_exist = 0; + } + else + { + request.does_exist = 1; + vsf_sysutil_close(tmpFD); + } + } + request.name = str_strdup(name); + request.path = str_strdup(path); + request.fullName = str_strdup(&fullFilePath); + sanitize_path_and_name(&request.path, &request.name); + sanitize_path(&request.fullName); + request_result = send_request_write_File(&request); + vsf_sysutil_free((char*)request.fullName); + vsf_sysutil_free((char*)request.name); + vsf_sysutil_free((char*)request.path); + vsf_sysutil_free((char*)request.sharedInfo.user_name); + str_free(&fullFilePath); + return request_result; +} + +struct Request_Result_Homedir vsf_request_user_homedir(struct vsf_session * p_sess) { + struct Request_Homedir request; + struct Request_Result_Homedir request_result = Request_Result_Homedir_Init; + populate_shared_request(&request.sharedInfo, p_sess); + request_result = send_request_homedir(&request); + vsf_sysutil_free((char*)request.sharedInfo.user_name); + return request_result; +} + diff --git a/postlogin_requests.h b/postlogin_requests.h new file mode 100644 index 0000000..3b6c5e0 --- /dev/null +++ b/postlogin_requests.h @@ -0,0 +1,21 @@ +#ifndef POSTLOGIN_REQUESTS_H +#define POSTLOGIN_REQUESTS_H + +#include "session.h" +#include "ftpdataio.h" +#include "str.h" +#include "request_results.h" + +const char * getParentDirectoryName(const struct mystr * fullFilePath); +const char * getParentDirectoryPath(const struct mystr * fullFilePath); +struct mystr getParentDirectoryNameStr(const struct mystr * fullFilePath); +struct mystr getParentDirectoryPathStr(const struct mystr * fullFilePath); +struct Request_Result_And_Reason checkFileWriteAccess(struct vsf_session * p_sess, struct mystr * filename); +struct Request_Result_And_Reason vsf_request_read_directory(struct vsf_session * p_sess, const struct mystr* parentPath, const struct mystr* name); +struct Request_Result_And_Reason vsf_request_read_file(struct vsf_session * p_sess, const struct mystr* path, const struct mystr* name); +struct Request_Result_And_Reason vsf_request_write_directory(struct vsf_session * p_sess, const struct mystr* parentPath, const struct mystr* name); +struct Request_Result_And_Reason vsf_request_write_file(struct vsf_session * p_sess, const struct mystr* path, const struct mystr* name); +struct Request_Result_Homedir vsf_request_user_homedir(struct vsf_session * p_sess); + +#endif //POSTLOGIN_REQUESTS_H + diff --git a/postprivparent.c b/postprivparent.c new file mode 100644 index 0000000..d47f99e --- /dev/null +++ b/postprivparent.c @@ -0,0 +1,123 @@ +/* + * Part of Very Secure FTPd + * Licence: GPL v2 + * Author: Chris Evans + * postprivparent.c + * + * This file contains all privileged parent services offered after logging + * in. This includes e.g. chown() of uploaded files, issuing of port 20 + * sockets. + */ + +#include "postprivparent.h" +#include "session.h" +#include "privops.h" +#include "privsock.h" +#include "utility.h" +#include "tunables.h" +#include "defs.h" +#include "sysutil.h" +#include "str.h" +#include "secutil.h" +#include "sysstr.h" +#include "sysdeputil.h" + +static void minimize_privilege(struct vsf_session* p_sess); +static void process_post_login_req(struct vsf_session* p_sess); +static void cmd_process_chown(struct vsf_session* p_sess); +static void cmd_process_get_data_sock(struct vsf_session* p_sess); + +void +vsf_priv_parent_postlogin(struct vsf_session* p_sess) +{ + minimize_privilege(p_sess); + /* We're still here... */ + while (1) + { + process_post_login_req(p_sess); + } +} + +static void +process_post_login_req(struct vsf_session* p_sess) +{ + char cmd; + vsf_sysutil_unblock_sig(kVSFSysUtilSigCHLD); + /* Blocks */ + cmd = priv_sock_get_cmd(p_sess->parent_fd); + vsf_sysutil_block_sig(kVSFSysUtilSigCHLD); + if (tunable_chown_uploads && cmd == PRIV_SOCK_CHOWN) + { + cmd_process_chown(p_sess); + } + else if (tunable_connect_from_port_20 && cmd == PRIV_SOCK_GET_DATA_SOCK) + { + cmd_process_get_data_sock(p_sess); + } + else + { + die("bad request in process_post_login_req"); + } +} + +static void +minimize_privilege(struct vsf_session* p_sess) +{ + /* So, we logged in and forked a totally unprivileged child. Our job + * now is to minimize the privilege we need in order to act as a helper + * to the child. + * + * In some happy circumstances, we can exit and be done with root + * altogether. + */ + if (!p_sess->is_anonymous && tunable_session_support) + { + /* Need to hang around to update logs, utmp, wtmp etc. on logout. + * Need to keep privs to do this. */ + return; + } + if (!tunable_chown_uploads && !tunable_connect_from_port_20 && + !tunable_max_per_ip && !tunable_max_clients) + { + /* Cool. We're outta here. */ + vsf_sysutil_exit(0); + } + { + unsigned int caps = 0; + struct mystr user_str = INIT_MYSTR; + struct mystr dir_str = INIT_MYSTR; + str_alloc_text(&user_str, tunable_nopriv_user); + str_alloc_text(&dir_str, tunable_secure_chroot_dir); + if (tunable_chown_uploads) + { + caps |= kCapabilityCAP_CHOWN; + } + if (tunable_connect_from_port_20) + { + caps |= kCapabilityCAP_NET_BIND_SERVICE; + } + vsf_secutil_change_credentials(&user_str, &dir_str, 0, caps, + VSF_SECUTIL_OPTION_CHROOT); + str_free(&user_str); + str_free(&dir_str); + } +} + +static void +cmd_process_chown(struct vsf_session* p_sess) +{ + int the_fd = priv_sock_recv_fd(p_sess->parent_fd); + vsf_privop_do_file_chown(p_sess, the_fd); + vsf_sysutil_close(the_fd); + priv_sock_send_result(p_sess->parent_fd, PRIV_SOCK_RESULT_OK); +} + +static void +cmd_process_get_data_sock(struct vsf_session* p_sess) +{ + int sock_fd = vsf_privop_get_ftp_port_sock(p_sess); + priv_sock_send_result(p_sess->parent_fd, PRIV_SOCK_RESULT_OK); + priv_sock_send_fd(p_sess->parent_fd, sock_fd); + vsf_sysutil_close(sock_fd); +} + diff --git a/postprivparent.h b/postprivparent.h new file mode 100644 index 0000000..ec029d1 --- /dev/null +++ b/postprivparent.h @@ -0,0 +1,16 @@ +#ifndef VSF_LOGINPRIVPARENT_H +#define VSF_LOGINPRIVPARENT_H + +struct vsf_session; + +/* vsf_priv_parent_postlogin() + * PURPOSE + * Called in the two process security model to commence "listening" for + * requests from the unprivileged child. + * PARAMETERS + * p_sess - the current session object + */ +void vsf_priv_parent_postlogin(struct vsf_session* p_sess); + +#endif /* VSF_LOGINPRIVPARENT_H */ + diff --git a/prelogin.c b/prelogin.c new file mode 100644 index 0000000..470265e --- /dev/null +++ b/prelogin.c @@ -0,0 +1,252 @@ +/* + * Part of Very Secure FTPd + * Licence: GPL v2 + * Author: Chris Evans + * prelogin.c + * + * Code to parse the FTP protocol prior to a successful login. + */ + +#include "prelogin.h" +#include "ftpcmdio.h" +#include "ftpcodes.h" +#include "str.h" +#include "vsftpver.h" +#include "tunables.h" +#include "oneprocess.h" +#include "twoprocess.h" +#include "sysdeputil.h" +#include "sysutil.h" +#include "session.h" +#include "banner.h" +#include "logging.h" +#include "ssl.h" +#include "features.h" +#include "defs.h" +#include "event_c_api.h" + +/* Functions used */ +static void emit_greeting(struct vsf_session* p_sess); +static void parse_username_password(struct vsf_session* p_sess); +static void handle_user_command(struct vsf_session* p_sess); +static void handle_pass_command(struct vsf_session* p_sess); + +void +init_connection(struct vsf_session* p_sess) +{ + if (tunable_setproctitle_enable) + { + vsf_sysutil_setproctitle("not logged in"); + } + /* Before we talk to the remote, make sure an alarm is set up in case + * writing the initial greetings should block. + */ + vsf_cmdio_set_alarm(p_sess); + emit_greeting(p_sess); + parse_username_password(p_sess); +} + +static void +emit_greeting(struct vsf_session* p_sess) +{ + struct mystr str_log_line = INIT_MYSTR; + /* Check for client limits (standalone mode only) */ + if (tunable_max_clients > 0 && + p_sess->num_clients > tunable_max_clients) + { + str_alloc_text(&str_log_line, "Connection refused: too many sessions."); + { + struct Event_Connection event; + populate_shared_event_for_connection(&event.sharedInfo, p_sess); + event.reason = "too many sessions"; + send_event_connection(&event); + } + vsf_log_line(p_sess, kVSFLogEntryConnection, &str_log_line); + vsf_cmdio_write_exit(p_sess, FTP_TOO_MANY_USERS, + "There are too many connected users, please try later."); + } + if (tunable_max_per_ip > 0 && + p_sess->num_this_ip > tunable_max_per_ip) + { + str_alloc_text(&str_log_line, + "Connection refused: too many sessions for this address."); + { + struct Event_Connection event; + populate_shared_event_for_connection(&event.sharedInfo, p_sess); + event.reason = "too many sessions for this address"; + send_event_connection(&event); + } + vsf_log_line(p_sess, kVSFLogEntryConnection, &str_log_line); + vsf_cmdio_write_exit(p_sess, FTP_IP_LIMIT, + "There are too many connections from your internet address."); + } + if (!p_sess->tcp_wrapper_ok) + { + str_alloc_text(&str_log_line, + "Connection refused: tcp_wrappers denial."); + { + struct Event_Connection event; + populate_shared_event_for_connection(&event.sharedInfo, p_sess); + event.reason = "tcp_wrappers denial"; + send_event_connection(&event); + } + vsf_log_line(p_sess, kVSFLogEntryConnection, &str_log_line); + vsf_cmdio_write_exit(p_sess, FTP_IP_DENY, "Service not available."); + } + vsf_log_line(p_sess, kVSFLogEntryConnection, &str_log_line); + if (!str_isempty(&p_sess->banner_str)) + { + vsf_banner_write(p_sess, &p_sess->banner_str, FTP_GREET); + str_free(&p_sess->banner_str); + vsf_cmdio_write(p_sess, FTP_GREET, ""); + } + else if (tunable_ftpd_banner == 0) + { + vsf_cmdio_write(p_sess, FTP_GREET, "(vsFTPd " VSF_VERSION + ")"); + } + else + { + vsf_cmdio_write(p_sess, FTP_GREET, tunable_ftpd_banner); + } +} + +static void +parse_username_password(struct vsf_session* p_sess) +{ + while (1) + { + vsf_cmdio_get_cmd_and_arg(p_sess, &p_sess->ftp_cmd_str, + &p_sess->ftp_arg_str, 1); + if (str_equal_text(&p_sess->ftp_cmd_str, "USER")) + { + handle_user_command(p_sess); + } + else if (str_equal_text(&p_sess->ftp_cmd_str, "PASS")) + { + handle_pass_command(p_sess); + } + else if (str_equal_text(&p_sess->ftp_cmd_str, "QUIT")) + { + vsf_cmdio_write(p_sess, FTP_GOODBYE, "Goodbye."); + vsf_sysutil_exit(0); + } + else if (str_equal_text(&p_sess->ftp_cmd_str, "FEAT")) + { + handle_feat(p_sess); + } + else if (tunable_ssl_enable && str_equal_text(&p_sess->ftp_cmd_str, "AUTH")) + { + handle_auth(p_sess); + } + else if (tunable_ssl_enable && str_equal_text(&p_sess->ftp_cmd_str, "PBSZ")) + { + handle_pbsz(p_sess); + } + else if (tunable_ssl_enable && str_equal_text(&p_sess->ftp_cmd_str, "PROT")) + { + handle_prot(p_sess); + } + else + { + vsf_cmdio_write(p_sess, FTP_LOGINERR, + "Please login with USER and PASS."); + } + } +} + +static void +handle_user_command(struct vsf_session* p_sess) +{ + /* SECURITY: If we're in anonymous only-mode, immediately reject + * non-anonymous usernames in the hope we save passwords going plaintext + * over the network + */ + int is_anon = 1; + str_copy(&p_sess->user_str, &p_sess->ftp_arg_str); + str_upper(&p_sess->ftp_arg_str); + if (!str_equal_text(&p_sess->ftp_arg_str, "FTP") && + !str_equal_text(&p_sess->ftp_arg_str, "ANONYMOUS")) + { + is_anon = 0; + } + if (!tunable_local_enable && !is_anon) + { + vsf_cmdio_write( + p_sess, FTP_LOGINERR, "This FTP server is anonymous only."); + str_empty(&p_sess->user_str); + return; + } + if (is_anon && p_sess->control_use_ssl && !tunable_allow_anon_ssl && + !tunable_force_anon_logins_ssl) + { + vsf_cmdio_write( + p_sess, FTP_LOGINERR, "Anonymous sessions may not use encryption."); + str_empty(&p_sess->user_str); + return; + } + if (tunable_ssl_enable && !is_anon && !p_sess->control_use_ssl && + tunable_force_local_logins_ssl) + { + vsf_cmdio_write( + p_sess, FTP_LOGINERR, "Non-anonymous sessions must use encryption."); + str_empty(&p_sess->user_str); + return; + } + if (tunable_ssl_enable && is_anon && !p_sess->control_use_ssl && + tunable_force_anon_logins_ssl) + { + vsf_cmdio_write( + p_sess, FTP_LOGINERR, "Anonymous sessions must use encryption."); + str_empty(&p_sess->user_str); + return; + } + if (!str_isempty(&p_sess->userlist_str)) + { + int located = str_contains_line(&p_sess->userlist_str, &p_sess->user_str); + if ((located && tunable_userlist_deny) || + (!located && !tunable_userlist_deny)) + { + vsf_cmdio_write(p_sess, FTP_LOGINERR, "Permission denied."); + str_empty(&p_sess->user_str); + return; + } + } + if (is_anon && tunable_no_anon_password) + { + /* Fake a password */ + str_alloc_text(&p_sess->ftp_arg_str, ""); + handle_pass_command(p_sess); + } + else + { + vsf_cmdio_write(p_sess, FTP_GIVEPWORD, "Please specify the password."); + } +} + +static void +handle_pass_command(struct vsf_session* p_sess) +{ + if (str_isempty(&p_sess->user_str)) + { + vsf_cmdio_write(p_sess, FTP_NEEDUSER, "Login with USER first."); + return; + } + /* These login calls never return if successful */ + if (tunable_one_process_model) + { + vsf_one_process_login(p_sess, &p_sess->ftp_arg_str); + } + else + { + vsf_two_process_login(p_sess, &p_sess->ftp_arg_str); + } + vsf_cmdio_write(p_sess, FTP_LOGINERR, "Login incorrect."); + if (++p_sess->login_fails >= tunable_max_login_fails) + { + vsf_sysutil_exit(0); + } + str_empty(&p_sess->user_str); + /* FALLTHRU if login fails */ +} + diff --git a/prelogin.h b/prelogin.h new file mode 100644 index 0000000..73c942e --- /dev/null +++ b/prelogin.h @@ -0,0 +1,16 @@ +#ifndef VSF_PRELOGIN_H +#define VSF_PRELOGIN_H + +struct vsf_session; + +/* init_connection() + * PURPOSE + * Called as an entry point to FTP protocol processing, when a client connects. + * This function will emit the FTP greeting, then start talking FTP protocol + * to the client. + * PARAMETERS + * p_sess - the current session object + */ +void init_connection(struct vsf_session* p_sess); + +#endif /* VSF_PRELOGIN_H */ diff --git a/privops.c b/privops.c new file mode 100644 index 0000000..fb215a5 --- /dev/null +++ b/privops.c @@ -0,0 +1,270 @@ +/* + * Part of Very Secure FTPd + * License: GPL v2 + * Author: Chris Evans + * privops.c + * + * Code implementing the privileged operations that the unprivileged client + * might request. + * Look for suitable paranoia in this file. + */ + +#include "privops.h" +#include "session.h" +#include "sysdeputil.h" +#include "sysutil.h" +#include "utility.h" +#include "str.h" +#include "tunables.h" +#include "defs.h" +#include "logging.h" + +#include "ftpcmdio.h" +#include "ftpcodes.h" + +#include "event_c_api.h" +#include "request_c_api.h" + +/* File private functions */ +static enum EVSFPrivopLoginResult handle_anonymous_login( + struct vsf_session* p_sess, const struct mystr* p_pass_str); +static enum EVSFPrivopLoginResult handle_local_login( + struct vsf_session* p_sess, const struct mystr* p_user_str, + const struct mystr* p_pass_str); +static void setup_username_globals(struct vsf_session* p_sess, + const struct mystr* p_str); +static enum EVSFPrivopLoginResult handle_login( + struct vsf_session* p_sess, const struct mystr* p_user_str, + const struct mystr* p_pass_str); + +int +vsf_privop_get_ftp_port_sock(struct vsf_session* p_sess) +{ + static struct vsf_sysutil_sockaddr* p_sockaddr; + int retval; + int s = vsf_sysutil_get_ipsock(p_sess->p_local_addr); + vsf_sysutil_activate_reuseaddr(s); + vsf_sysutil_sockaddr_clone(&p_sockaddr, p_sess->p_local_addr); + vsf_sysutil_sockaddr_set_port(p_sockaddr, tunable_ftp_data_port); + retval = vsf_sysutil_bind(s, p_sockaddr); + if (retval != 0) + { + die("vsf_sysutil_bind"); + } + return s; +} + +void +vsf_privop_do_file_chown(struct vsf_session* p_sess, int fd) +{ + static struct vsf_sysutil_statbuf* s_p_statbuf; + vsf_sysutil_fstat(fd, &s_p_statbuf); + /* Do nothing if it is already owned by the desired user. */ + if (vsf_sysutil_statbuf_get_uid(s_p_statbuf) == + p_sess->anon_upload_chown_uid) + { + return; + } + /* Drop it like a hot potato unless it's a regular file owned by + * the the anonymous ftp user + */ + if (p_sess->anon_upload_chown_uid == -1 || + !vsf_sysutil_statbuf_is_regfile(s_p_statbuf) || + (vsf_sysutil_statbuf_get_uid(s_p_statbuf) != p_sess->anon_ftp_uid && + vsf_sysutil_statbuf_get_uid(s_p_statbuf) != p_sess->guest_user_uid)) + { + die("invalid fd in cmd_process_chown"); + } + /* SECURITY! You need an OS which strips SUID/SGID bits on chown(), + * otherwise a compromise of the FTP user will lead to compromise of + * the "anon_upload_chown_uid" user (think chmod +s). + */ + vsf_sysutil_fchown(fd, p_sess->anon_upload_chown_uid, -1); +} + +enum EVSFPrivopLoginResult +vsf_privop_do_login(struct vsf_session* p_sess, + const struct mystr* p_pass_str) +{ + enum EVSFPrivopLoginResult result + = handle_login(p_sess, &p_sess->user_str, p_pass_str); + + vsf_log_start_entry(p_sess, kVSFLogEntryLogin); + if (result == kVSFLoginFail) + { + vsf_log_do_log(p_sess, 0); + { + struct Event_FailedLogin event; + populate_shared_event_for_connection(&event.sharedInfo, p_sess); + event.failed_password = str_strdup(p_pass_str); + event.sharedInfo.user_name = str_strdup(&p_sess->user_str); + send_event_failedLogin(&event); + vsf_sysutil_free((char*)event.failed_password); + vsf_sysutil_free((char*)event.sharedInfo.user_name); + } + + if (tunable_delay_failed_login) + { + vsf_sysutil_sleep((double) tunable_delay_failed_login); + } + } + else + { + vsf_log_do_log(p_sess, 1); + { + struct Event_Login event; + populate_shared_event(&event.sharedInfo, p_sess); + event.is_anonymous = p_sess->is_anonymous; + send_event_login(&event); + vsf_sysutil_free((char*)event.sharedInfo.user_name); + } + if (tunable_delay_successful_login) + { + vsf_sysutil_sleep((double) tunable_delay_successful_login); + } + } + return result; +} + +static enum EVSFPrivopLoginResult +handle_login(struct vsf_session* p_sess, const struct mystr* p_user_str, + const struct mystr* p_pass_str) +{ + /* Do not assume PAM can cope with dodgy input, even though it + * almost certainly can. + */ + int anonymous_login = 0; + unsigned int len = str_getlen(p_user_str); + enum EVSFPrivopLoginResult result = kVSFLoginFail; + if (len == 0 || len > VSFTP_USERNAME_MAX) + { + return kVSFLoginFail; + } + /* Throw out dodgy start characters */ + if (!vsf_sysutil_isalnum(str_get_char_at(p_user_str, 0))) + { + return kVSFLoginFail; + } + /* Throw out non-printable characters and space in username */ + if (str_contains_space(p_user_str) || + str_contains_unprintable(p_user_str)) + { + return kVSFLoginFail; + } + /* Throw out excessive length passwords */ + len = str_getlen(p_pass_str); + if (len > VSFTP_PASSWORD_MAX) + { + return kVSFLoginFail; + } + /* Check for an anonymous login or "real" login */ + if (tunable_anonymous_enable) + { + struct mystr upper_str = INIT_MYSTR; + str_copy(&upper_str, p_user_str); + str_upper(&upper_str); + if (str_equal_text(&upper_str, "FTP") || + str_equal_text(&upper_str, "ANONYMOUS")) + { + anonymous_login = 1; + } + str_free(&upper_str); + } + + if (tunable_plugin_architecture) + { + struct Request_Login request; + struct Request_Result request_result; + + populate_shared_request_for_connection(&request.sharedInfo, p_sess); + request.login = str_strdup(p_user_str); + request.password = str_strdup(p_pass_str); + request_result = send_request_login(&request); + vsf_sysutil_free((char*)request.login); + vsf_sysutil_free((char*)request.password); + if (request_result.result) + { + return kVSFLoginFail; + } else { + str_copy(&p_sess->plugin_arch_user_str, p_user_str); + result = handle_anonymous_login(p_sess, p_pass_str); + } + } else { + if (anonymous_login) + { + result = handle_anonymous_login(p_sess, p_pass_str); + } else + { + if (!tunable_local_enable) + { + die("unexpected local login in handle_login"); + } + result = handle_local_login(p_sess, p_user_str, p_pass_str); + } + } + return result; +} + +static enum EVSFPrivopLoginResult +handle_anonymous_login(struct vsf_session* p_sess, + const struct mystr* p_pass_str) +{ + if (!str_isempty(&p_sess->banned_email_str) && + str_contains_line(&p_sess->banned_email_str, p_pass_str)) + { + return kVSFLoginFail; + } + if (!str_isempty(&p_sess->email_passwords_str) && + (!str_contains_line(&p_sess->email_passwords_str, p_pass_str) || + str_isempty(p_pass_str))) + { + return kVSFLoginFail; + } + /* Store the anonymous identity string */ + str_copy(&p_sess->anon_pass_str, p_pass_str); + if (str_isempty(&p_sess->anon_pass_str)) + { + str_alloc_text(&p_sess->anon_pass_str, "?"); + } + /* "Fix" any characters which might upset the log processing */ + str_replace_char(&p_sess->anon_pass_str, ' ', '_'); + str_replace_char(&p_sess->anon_pass_str, '\n', '?'); + { + struct mystr ftp_username_str = INIT_MYSTR; + str_alloc_text(&ftp_username_str, tunable_ftp_username); + setup_username_globals(p_sess, &ftp_username_str); + str_free(&ftp_username_str); + } + str_free(&p_sess->banned_email_str); + str_free(&p_sess->email_passwords_str); + return kVSFLoginAnon; +} + +static enum EVSFPrivopLoginResult +handle_local_login(struct vsf_session* p_sess, + const struct mystr* p_user_str, + const struct mystr* p_pass_str) +{ + if (!vsf_sysdep_check_auth(p_user_str, p_pass_str, &p_sess->remote_ip_str)) + { + return kVSFLoginFail; + } + setup_username_globals(p_sess, p_user_str); + return kVSFLoginReal; +} + +static void +setup_username_globals(struct vsf_session* p_sess, const struct mystr* p_str) +{ + str_copy(&p_sess->user_str, p_str); + if (tunable_setproctitle_enable) + { + struct mystr prefix_str = INIT_MYSTR; + str_copy(&prefix_str, &p_sess->remote_ip_str); + str_append_char(&prefix_str, '/'); + str_append_str(&prefix_str, p_str); + vsf_sysutil_set_proctitle_prefix(&prefix_str); + str_free(&prefix_str); + } +} + diff --git a/privops.h b/privops.h new file mode 100644 index 0000000..f2de473 --- /dev/null +++ b/privops.h @@ -0,0 +1,50 @@ +#ifndef VSF_PRIVOPS_H +#define VSF_PRIVOPS_H + +struct mystr; +struct vsf_session; + +/* vsf_privop_get_ftp_port_sock() + * PURPOSE + * Return a network socket bound to a privileged port (less than 1024). + * PARAMETERS + * p_sess - the current session object + * RETURNS + * A file descriptor which is a socket bound to the privileged port. + */ +int vsf_privop_get_ftp_port_sock(struct vsf_session* p_sess); + +/* vsf_privop_do_file_chown() + * PURPOSE + * Takes a file owned by the unprivileged FTP user, and change the ownership + * to the value defined in the config file. + * PARAMETERS + * p_sess - the current session object + * fd - the file descriptor of the regular file + */ +void vsf_privop_do_file_chown(struct vsf_session* p_sess, int fd); + +enum EVSFPrivopLoginResult +{ + kVSFLoginNull = 0, + kVSFLoginFail, + kVSFLoginAnon, + kVSFLoginReal +}; +/* vsf_privop_do_login() + * PURPOSE + * Check if the supplied username/password combination is valid. This + * interface caters for checking both anonymous and real logins. + * PARAMETERS + * p_sess - the current session object + * p_pass_str - the proposed password + * RETURNS + * kVSFLoginFail - access denied + * kVSFLoginAnon - anonymous login credentials OK + * kVSFLoginReal - real login credentials OK + */ +enum EVSFPrivopLoginResult vsf_privop_do_login( + struct vsf_session* p_sess, const struct mystr* p_pass_str); + +#endif /* VSF_PRIVOPS_H */ + diff --git a/privsock.c b/privsock.c new file mode 100644 index 0000000..aecd427 --- /dev/null +++ b/privsock.c @@ -0,0 +1,139 @@ +/* + * Part of Very Secure FTPd + * Licence: GPL v2 + * Author: Chris Evans + * privsock.c + * + * This file contains code for a simple message and file descriptor passing + * API, over a pair of UNIX sockets. + * The messages are typically travelling across a privilege boundary, with + * heavy distrust of messages on the side of more privilege. + */ + +#include "privsock.h" + +#include "utility.h" +#include "defs.h" +#include "str.h" +#include "netstr.h" +#include "sysutil.h" +#include "sysdeputil.h" +#include "session.h" + +void +priv_sock_init(struct vsf_session* p_sess) +{ + const struct vsf_sysutil_socketpair_retval retval = + vsf_sysutil_unix_stream_socketpair(); + p_sess->parent_fd = retval.socket_one; + p_sess->child_fd = retval.socket_two; +} + +void +priv_sock_send_cmd(int fd, char cmd) +{ + int retval = vsf_sysutil_write_loop(fd, &cmd, sizeof(cmd)); + if (retval != sizeof(cmd)) + { + die("priv_sock_send_cmd"); + } +} + +void +priv_sock_send_str(int fd, const struct mystr* p_str) +{ + unsigned int len = str_getlen(p_str); + priv_sock_send_int(fd, (int) len); + if (len > 0) + { + str_netfd_write(p_str, fd); + } +} + +char +priv_sock_get_result(int fd) +{ + char res; + int retval = vsf_sysutil_read_loop(fd, &res, sizeof(res)); + if (retval != sizeof(res)) + { + die("priv_sock_get_result"); + } + return res; +} + +char +priv_sock_get_cmd(int fd) +{ + char res; + int retval = vsf_sysutil_read_loop(fd, &res, sizeof(res)); + if (retval != sizeof(res)) + { + die("priv_sock_get_cmd"); + } + return res; +} + +void +priv_sock_get_str(int fd, struct mystr* p_dest) +{ + unsigned int len = (unsigned int) priv_sock_get_int(fd); + if (len > VSFTP_PRIVSOCK_MAXSTR) + { + die("priv_sock_get_str: too big"); + } + str_empty(p_dest); + if (len > 0) + { + int retval = str_netfd_read(p_dest, fd, len); + if ((unsigned int) retval != len) + { + die("priv_sock_get_str: read error"); + } + } +} + +void +priv_sock_send_result(int fd, char res) +{ + int retval = vsf_sysutil_write_loop(fd, &res, sizeof(res)); + if (retval != sizeof(res)) + { + die("priv_sock_send_result"); + } +} + +void +priv_sock_send_fd(int fd, int send_fd) +{ + vsf_sysutil_send_fd(fd, send_fd); +} + +int +priv_sock_recv_fd(int fd) +{ + return vsf_sysutil_recv_fd(fd); +} + +void +priv_sock_send_int(int fd, int the_int) +{ + int retval = vsf_sysutil_write_loop(fd, &the_int, sizeof(the_int)); + if (retval != sizeof(the_int)) + { + die("priv_sock_send_int"); + } +} + +int +priv_sock_get_int(int fd) +{ + int the_int; + int retval = vsf_sysutil_read_loop(fd, &the_int, sizeof(the_int)); + if (retval != sizeof(the_int)) + { + die("priv_sock_get_int"); + } + return the_int; +} + diff --git a/privsock.h b/privsock.h new file mode 100644 index 0000000..cec88c7 --- /dev/null +++ b/privsock.h @@ -0,0 +1,119 @@ +#ifndef VSF_PRIVSOCK_H +#define VSF_PRIVSOCK_H + +struct mystr; +struct vsf_session; + +/* priv_sock_init() + * PURPOSE + * Initialize the priv_sock system, by opening the communications sockets. + * PARAMETERS + * p_sess - the current session object + */ +void priv_sock_init(struct vsf_session* p_sess); + +/* priv_sock_send_cmd() + * PURPOSE + * Sends a command, typically to the privileged side of the channel. + * PARAMETERS + * fd - the fd on which to send the command + * cmd - the command to send + */ +void priv_sock_send_cmd(int fd, char cmd); + +/* priv_sock_send_str() + * PURPOSE + * Sends a string to the other side of the channel. + * PARAMETERS + * fd - the fd on which to send the string + * p_str - the string to send + */ +void priv_sock_send_str(int fd, const struct mystr* p_str); + +/* priv_sock_get_result() + * PURPOSE + * Receives a response, typically from the privileged side of the channel. + * PARAMETERS + * fd - the fd on which to receive the response + * RETURNS + * The response code. + */ +char priv_sock_get_result(int fd); + +/* priv_sock_get_cmd() + * PURPOSE + * Receives a command, typically on the privileged side of the channel. + * PARAMETERS + * fd - the fd on which to receive the command. + * RETURNS + * The command that was sent. + */ +char priv_sock_get_cmd(int fd); + +/* priv_sock_get_str() + * PURPOSE + * Receives a string from the other side of the channel. + * PARAMETERS + * fd - the fd on which to receive the string + * p_dest - where to copy the received string + */ +void priv_sock_get_str(int fd, struct mystr* p_dest); + +/* priv_sock_send_result() + * PURPOSE + * Sends a command result, typically to the unprivileged side of the channel. + * PARAMETERS + * fd - the fd on which to send the result + * res - the result to send + */ +void priv_sock_send_result(int fd, char res); + +/* priv_sock_send_fd() + * PURPOSE + * Sends a file descriptor to the other side of the channel. + * PARAMETERS + * fd - the fd on which to send the descriptor + * send_fd - the descriptor to send + */ +void priv_sock_send_fd(int fd, int send_fd); + +/* priv_sock_recv_fd() + * PURPOSE + * Receives a file descriptor from the other side of the channel. + * PARAMETERS + * fd - the fd on which to receive the descriptor + * RETURNS + * The received file descriptor + */ +int priv_sock_recv_fd(int fd); + +/* priv_sock_send_int() + * PURPOSE + * Sends an integer to the other side of the channel. + * PARAMETERS + * fd - the fd on which to send the integer + * the_int - the integer to send + */ +void priv_sock_send_int(int fd, int the_int); + +/* priv_sock_get_int() + * PURPOSE + * Receives an integer from the other side of the channel. + * PARAMETERS + * fd - the fd on which to receive the integer + * RETURNS + * The integer that was sent. + */ +int priv_sock_get_int(int fd); + +#define PRIV_SOCK_LOGIN 1 +#define PRIV_SOCK_CHOWN 2 +#define PRIV_SOCK_GET_DATA_SOCK 3 +#define PRIV_SOCK_GET_USER_CMD 4 +#define PRIV_SOCK_WRITE_USER_RESP 5 + +#define PRIV_SOCK_RESULT_OK 1 +#define PRIV_SOCK_RESULT_BAD 2 + +#endif /* VSF_PRIVSOCK_H */ + diff --git a/readwrite.c b/readwrite.c new file mode 100644 index 0000000..fc25801 --- /dev/null +++ b/readwrite.c @@ -0,0 +1,98 @@ +/* + * Part of Very Secure FTPd + * Licence: GPL v2 + * Author: Chris Evans + * readwrite.c + * + * Routines to encapsulate the underlying read / write mechanism (OpenSSL vs. + * plain read()/write()). + */ + +#include "readwrite.h" +#include "session.h" +#include "netstr.h" +#include "ssl.h" +#include "privsock.h" +#include "defs.h" +#include "sysutil.h" + +int +ftp_write_str(const struct vsf_session* p_sess, const struct mystr* p_str, + enum EVSFRWTarget target) +{ + if (target == kVSFRWData) + { + if (p_sess->data_use_ssl) + { + return ssl_write_str(p_sess->p_data_ssl, p_str); + } + else + { + return str_netfd_write(p_str, p_sess->data_fd); + } + } + else + { + if (p_sess->control_use_ssl && p_sess->ssl_slave_active) + { + priv_sock_send_cmd(p_sess->ssl_consumer_fd, PRIV_SOCK_WRITE_USER_RESP); + priv_sock_send_str(p_sess->ssl_consumer_fd, p_str); + return priv_sock_get_int(p_sess->ssl_consumer_fd); + } + else if (p_sess->control_use_ssl) + { + return ssl_write_str(p_sess->p_control_ssl, p_str); + } + else + { + return str_netfd_write(p_str, VSFTP_COMMAND_FD); + } + } +} + +int +ftp_read_data(const struct vsf_session* p_sess, char* p_buf, unsigned int len) +{ + if (p_sess->data_use_ssl) + { + return ssl_read(p_sess->p_data_ssl, p_buf, len); + } + else + { + return vsf_sysutil_read(p_sess->data_fd, p_buf, len); + } +} + +int +ftp_write_data(const struct vsf_session* p_sess, const char* p_buf, + unsigned int len) +{ + if (p_sess->data_use_ssl) + { + return ssl_write(p_sess->p_data_ssl, p_buf, len); + } + else + { + return vsf_sysutil_write_loop(p_sess->data_fd, p_buf, len); + } +} + +void +ftp_getline(const struct vsf_session* p_sess, struct mystr* p_str, char* p_buf) +{ + if (p_sess->control_use_ssl && p_sess->ssl_slave_active) + { + priv_sock_send_cmd(p_sess->ssl_consumer_fd, PRIV_SOCK_GET_USER_CMD); + priv_sock_get_str(p_sess->ssl_consumer_fd, p_str); + } + else if (p_sess->control_use_ssl) + { + ssl_getline(p_sess, p_str, '\n', p_buf, VSFTP_MAX_COMMAND_LINE); + } + else + { + str_netfd_alloc( + p_str, VSFTP_COMMAND_FD, '\n', p_buf, VSFTP_MAX_COMMAND_LINE); + } +} + diff --git a/readwrite.h b/readwrite.h new file mode 100644 index 0000000..d2eb1d8 --- /dev/null +++ b/readwrite.h @@ -0,0 +1,23 @@ +#ifndef VSF_READWRITE_H +#define VSF_READWRITE_H + +struct vsf_session; +struct mystr; + +enum EVSFRWTarget +{ + kVSFRWControl = 1, + kVSFRWData +}; + +int ftp_write_str(const struct vsf_session* p_sess, const struct mystr* p_str, + enum EVSFRWTarget target); +int ftp_read_data(const struct vsf_session* p_sess, char* p_buf, + unsigned int len); +int ftp_write_data(const struct vsf_session* p_sess, const char* p_buf, + unsigned int len); +void ftp_getline(const struct vsf_session* p_sess, struct mystr* p_str, + char* p_buf); + +#endif /* VSF_READWRITE_H */ + diff --git a/request.cpp b/request.cpp new file mode 100644 index 0000000..30d73bc --- /dev/null +++ b/request.cpp @@ -0,0 +1,112 @@ +#include "request.h" +#include "udsrf.h" +#include "exceptions.h" +#include "plugin_architecture_api.h" +#include "plugin_architecture_interface.h" +#include +#include + +#define request_handler_header(retype, fun_name, struct_name) C_COMPATIBLE_FUNCTION retype send_request_##fun_name (struct Request_##struct_name * request) + +#define gen_read_member(membername) void Request##membername ::readFromSock(int sock) { recvData(sock, i_struct); } + +#define getHandled(requestname) UnknownObject Request##requestname ::getHandled(VSFPlugin & plugin) { \ + return UnknownObject(plugin.handleRequest(*this)); \ + } + +#define gen_funcs(fname, sname) getHandled(sname) \ + gen_read_member(sname) + +#define send_request_struct(sname) sendData(plugin_architecture_get_slave_socket_fast(), (int)Request_Number_##sname); \ + sendData(plugin_architecture_get_slave_socket_fast(), Request_Number_##sname); \ + sendData(plugin_architecture_get_slave_socket_fast(), *request); + + +#define return_result(stype) stype retval; \ + std::auto_ptr uData = UnknownObject::readFromSocket(plugin_architecture_get_slave_socket_fast()); \ + deserialize(std::pair((char*)uData->getData(),uData->getDataSize()), retval); \ + return retval; + +template void deleteStruct(T & to_delete) {} + +#include "request_auto.h" + +request_handler_header(struct Request_Result, login, Login) { + send_request_struct(Login) + return_result(struct Request_Result) +} + +request_handler_header(struct Request_Result_Homedir, homedir, Homedir) { + send_request_struct(Homedir) + return_result(struct Request_Result_Homedir) +} + +request_handler_header(struct Request_Result_And_Reason, read_File, Read_File) { + send_request_struct(ReadFile) + return_result(struct Request_Result_And_Reason) +} + +request_handler_header(struct Request_Result_And_Reason, read_Directory, Read_Directory) { + send_request_struct(ReadDirectory) + return_result(struct Request_Result_And_Reason) +} + +request_handler_header(struct Request_Result_And_Reason, write_File, Write_File) { + send_request_struct(WriteFile) + return_result(struct Request_Result_And_Reason) +} + +request_handler_header(struct Request_Result_And_Reason, write_Directory, Write_Directory) { + send_request_struct(WriteDirectory) + return_result(struct Request_Result_And_Reason) +} + +struct vsf_sysutil_sockaddr +{ + union + { + struct sockaddr u_sockaddr; + struct sockaddr_in u_sockaddr_in; + struct sockaddr_in6 u_sockaddr_in6; + } u; +}; + +C_COMPATIBLE_FUNCTION void populate_shared_request(struct Request_Shared * request, struct vsf_session* p_sess) { + if (request == 0 || p_sess == 0) + { + throw NullPointerException(); + } + memcpy(&request->remote_ip, &p_sess->p_remote_addr->u.u_sockaddr_in, sizeof(request->remote_ip)); + request->user_name = const_cast(str_strdup(&p_sess->plugin_arch_user_str)); + gettimeofday(&request->timestamp,0); +} + +C_COMPATIBLE_FUNCTION void populate_shared_request_for_connection(struct Request_Shared * request, struct vsf_session* p_sess) { + if (request == 0 || p_sess == 0) + { + throw NullPointerException(); + } + memcpy(&request->remote_ip, &p_sess->p_remote_addr->u.u_sockaddr_in, sizeof(request->remote_ip)); + request->user_name = ""; + gettimeofday(&request->timestamp,0); +} + +RequestLogin::~RequestLogin() { + deleteStruct(i_struct); +} +RequestHomedir::~RequestHomedir() { + deleteStruct(i_struct); +} +RequestReadFile::~RequestReadFile() { + deleteStruct(i_struct); +} +RequestReadDirectory::~RequestReadDirectory() { + deleteStruct(i_struct); +} +RequestWriteFile::~RequestWriteFile() { + deleteStruct(i_struct); +} +RequestWriteDirectory::~RequestWriteDirectory() { + deleteStruct(i_struct); +} + diff --git a/request.h b/request.h new file mode 100644 index 0000000..27f4834 --- /dev/null +++ b/request.h @@ -0,0 +1,97 @@ +#ifndef VSF_REQUEST_H +#define VSF_REQUEST_H + +#include "request_c_api.h" +#include "unknown_object.h" + +class VSFPlugin; + +class Request { +public: + virtual int getNumber() const = 0; + virtual UnknownObject getHandled(VSFPlugin &) = 0; + static std::auto_ptr readFromSocket(int sock); + virtual ~Request() {} +private: + virtual void readFromSock(int sock) = 0; +}; + +class RequestLogin : public Request { +public: + int getNumber() const { return Request_Number_Login ; } + virtual UnknownObject getHandled(VSFPlugin &); + const struct Request_Login * getData() const {return &i_struct;} + virtual ~RequestLogin(); +private: + RequestLogin() {} + friend class Request; + virtual void readFromSock(int sock); + struct Request_Login i_struct; +}; + +class RequestHomedir : public Request { +public: + int getNumber() const { return Request_Number_Login ; } + virtual UnknownObject getHandled(VSFPlugin &); + const struct Request_Homedir * getData() const {return &i_struct;} + virtual ~RequestHomedir(); +private: + RequestHomedir() {} + friend class Request; + virtual void readFromSock(int sock); + struct Request_Homedir i_struct; +}; + +class RequestReadFile : public Request { +public: + int getNumber() const { return Request_Number_ReadFile ; } + virtual UnknownObject getHandled(VSFPlugin &); + const struct Request_Read_File * getData() const {return &i_struct;} + virtual ~RequestReadFile(); +private: + RequestReadFile() {} + friend class Request; + virtual void readFromSock(int sock); + struct Request_Read_File i_struct; +}; + +class RequestReadDirectory : public Request { +public: + int getNumber() const { return Request_Number_ReadDirectory ; } + virtual UnknownObject getHandled(VSFPlugin &); + const struct Request_Read_Directory * getData() const {return &i_struct;} + virtual ~RequestReadDirectory(); +private: + RequestReadDirectory() {} + friend class Request; + virtual void readFromSock(int sock); + struct Request_Read_Directory i_struct; +}; + +class RequestWriteFile : public Request { +public: + int getNumber() const { return Request_Number_WriteFile ; } + virtual UnknownObject getHandled(VSFPlugin &); + const struct Request_Write_File * getData() const {return &i_struct;} + virtual ~RequestWriteFile(); +private: + RequestWriteFile() {} + friend class Request; + virtual void readFromSock(int sock); + struct Request_Write_File i_struct; +}; + +class RequestWriteDirectory : public Request { +public: + int getNumber() const { return Request_Number_WriteDirectory ; } + virtual UnknownObject getHandled(VSFPlugin &); + const struct Request_Write_Directory * getData() const {return &i_struct;} + virtual ~RequestWriteDirectory(); +private: + RequestWriteDirectory() {} + friend class Request; + virtual void readFromSock(int sock); + struct Request_Write_Directory i_struct; +}; + +#endif //VSF_REQUEST_H diff --git a/request_auto.h b/request_auto.h new file mode 100644 index 0000000..9f7d53a --- /dev/null +++ b/request_auto.h @@ -0,0 +1,80 @@ +gen_funcs( login , Login ) +gen_funcs( homedir , Homedir ) +gen_funcs( readFile , ReadFile ) +gen_funcs( readDirectory , ReadDirectory ) +gen_funcs( writeDirectory , WriteDirectory ) +gen_funcs( writeFile , WriteFile ) +std::auto_ptr Request::readFromSocket(int sock) { + Request_Number_Enum eventNumber; + recvData(sock, eventNumber); + if (!(eventNumber & Request_Identifier)) + { + throw InconsistentTypeException(); + } + std::auto_ptr toRet; + switch (eventNumber) + { + case Request_Number_Login: + toRet = std::auto_ptr(new RequestLogin); + toRet->readFromSock(sock); + break; + case Request_Number_Homedir: + toRet = std::auto_ptr(new RequestHomedir); + toRet->readFromSock(sock); + break; + case Request_Number_ReadFile: + toRet = std::auto_ptr(new RequestReadFile); + toRet->readFromSock(sock); + break; + case Request_Number_ReadDirectory: + toRet = std::auto_ptr(new RequestReadDirectory); + toRet->readFromSock(sock); + break; + case Request_Number_WriteDirectory: + toRet = std::auto_ptr(new RequestWriteDirectory); + toRet->readFromSock(sock); + break; + case Request_Number_WriteFile: + toRet = std::auto_ptr(new RequestWriteFile); + toRet->readFromSock(sock); + break; + default: + throw UnknownRequestNumber(); + } +return toRet; +} +template<> void deleteStruct(Request_Shared & to_delete) { + deleteStruct(to_delete.timestamp); + deleteStruct(to_delete.remote_ip); + delete [] to_delete.user_name; +} +template<> void deleteStruct(Request_Login & to_delete) { + deleteStruct(to_delete.sharedInfo); + delete [] to_delete.login; + delete [] to_delete.password; +} +template<> void deleteStruct(Request_Homedir & to_delete) { + deleteStruct(to_delete.sharedInfo); +} +template<> void deleteStruct(Request_Read_File & to_delete) { + deleteStruct(to_delete.sharedInfo); + delete [] to_delete.path; + delete [] to_delete.name; + delete [] to_delete.fullName; +} +template<> void deleteStruct(Request_Read_Directory & to_delete) { + deleteStruct(to_delete.sharedInfo); + delete [] to_delete.parentPath; + delete [] to_delete.name; +} +template<> void deleteStruct(Request_Write_Directory & to_delete) { + deleteStruct(to_delete.sharedInfo); + delete [] to_delete.parentPath; + delete [] to_delete.name; +} +template<> void deleteStruct(Request_Write_File & to_delete) { + deleteStruct(to_delete.sharedInfo); + delete [] to_delete.path; + delete [] to_delete.name; + delete [] to_delete.fullName; +} diff --git a/request_c_api.h b/request_c_api.h new file mode 100644 index 0000000..32edad4 --- /dev/null +++ b/request_c_api.h @@ -0,0 +1,117 @@ +#ifndef H_REQUEST_C_API +#define H_REQUEST_C_API + +#include +#include +#include +#include "cpp_to_c_helpers.h" +#include "request_results.h" + +static const int Request_Identifier = 0x02000000; + +enum Request_Number_Enum { + Request_Number_Identifier = 0x02000000, + Request_Number_Login, + Request_Number_Homedir, + Request_Number_ReadFile, + Request_Number_ReadDirectory, + Request_Number_WriteFile, + Request_Number_WriteDirectory, + Request_Number_MaxNumber //do not remove, this **must** be the last enum +}; + +struct Request_Shared { + struct timeval timestamp; // time of the request + struct sockaddr_in remote_ip; // the ip of the remote host + const char * user_name; // the user name the remote host has logged in as. If the user is not logged in this is set to "" +}; + +/** + * sent when a foreign host wants to log in as a user + * EXPECTED RETURNVALUE + * Request_Result_And_Reason + * 0 or -1 in type int + */ +struct Request_Login { + struct Request_Shared sharedInfo; + const char * login; + const char * password; +}; + +/** + * sent when we have succesfully logged in and want to chdir + * there + * EXPECTED RETURNVALUE + * Request_Result_Homedir + */ +struct Request_Homedir { + struct Request_Shared sharedInfo; +}; + +/** + * sent when a user wants to read data from a file + * + * EXPECTED RETURNVALUE + * 0 or -1 in type int + * reason of failure in type const char * + */ +struct Request_Read_File { + struct Request_Shared sharedInfo; + const char * path; // directory in which the file resides + const char * name; // the file name + const char * fullName; // the full path - dirname prepended + unsigned int size; // file size +}; + +/** + * sent when a user wants to read data from a directory + * + * EXPECTED RETURNVALUE + * 0 or -1 in type int + * reason of failure in type const char * + */ +struct Request_Read_Directory { + struct Request_Shared sharedInfo; + const char * parentPath; //path to the parent directory + const char * name; //name of the directory +}; + +/** + * sent when a user wants to create a directory + * + * EXPECTED RETURNVALUE + * 0 or -1 in type int + * reason of failure in type const char * + */ +struct Request_Write_Directory { + struct Request_Shared sharedInfo; + const char * parentPath; //path to the parent directory + const char * name; //name of the directory + int does_exist; +}; + +/** + * sent when a user wants to write data to a file + * + * EXPECTED RETURNVALUE + * 0 or -1 in type int + * reason of failure in type const char * + */ +struct Request_Write_File { + struct Request_Shared sharedInfo; + const char * path; // directory in which the file resides + const char * name; // the file name + const char * fullName; // the full path - dirname prepended + int does_exist; +}; + +C_COMPATIBLE_FUNCTION struct Request_Result send_request_login(struct Request_Login * request); +C_COMPATIBLE_FUNCTION struct Request_Result_Homedir send_request_homedir(struct Request_Homedir * request); +C_COMPATIBLE_FUNCTION struct Request_Result_And_Reason send_request_read_File(struct Request_Read_File * request); +C_COMPATIBLE_FUNCTION struct Request_Result_And_Reason send_request_read_Directory(struct Request_Read_Directory * request); +C_COMPATIBLE_FUNCTION struct Request_Result_And_Reason send_request_write_Directory(struct Request_Write_Directory * request); +C_COMPATIBLE_FUNCTION struct Request_Result_And_Reason send_request_write_File(struct Request_Write_File * request); +C_COMPATIBLE_FUNCTION void populate_shared_request(struct Request_Shared * request, struct vsf_session* p_sess); +C_COMPATIBLE_FUNCTION void populate_shared_request_for_connection(struct Request_Shared * request, struct vsf_session* p_sess); + +#endif //H_REQUEST_C_API diff --git a/request_results.cpp b/request_results.cpp new file mode 100644 index 0000000..e0615d3 --- /dev/null +++ b/request_results.cpp @@ -0,0 +1,13 @@ +#include "request_results.h" + +C_COMPATIBLE_FUNCTION void doneWith_Result_And_Reason(struct Request_Result_And_Reason * r) { + delete [] r->reason; +} + +C_COMPATIBLE_FUNCTION void doneWith_Result(struct Request_Result *) { + ; +} + +C_COMPATIBLE_FUNCTION void doneWith_Result_Homedir(struct Request_Result_Homedir * r) { + delete [] r->homedir; +} diff --git a/request_results.h b/request_results.h new file mode 100644 index 0000000..6fb44b3 --- /dev/null +++ b/request_results.h @@ -0,0 +1,31 @@ +#ifndef REQUEST_RESULTS_H +#define REQUEST_RESULTS_H + +#include "cpp_to_c_helpers.h" + +struct Request_Result_And_Reason { + int result; //0 for success, -1 for failure, any other value - failure + const char * reason; //on success set to "No failure", on failure set to the reason of the failure +}; + +#define Request_Result_And_Reason_Init {0, "No failure"} + +C_COMPATIBLE_FUNCTION void doneWith_Result_And_Reason(struct Request_Result_And_Reason *); + +struct Request_Result { + int result; //0 for success, -1 for failure, any other value - failure +}; + +#define Request_Result_Init {0} + +C_COMPATIBLE_FUNCTION void doneWith_Result(struct Request_Result *); + +struct Request_Result_Homedir { + const char * homedir; +}; + +#define Request_Result_Homedir_Init {"/"} + +C_COMPATIBLE_FUNCTION void doneWith_Result_Homedir(struct Request_Result_Homedir *); + +#endif //REQUEST_RESULTS_H diff --git a/secbuf.c b/secbuf.c new file mode 100644 index 0000000..3e6cc49 --- /dev/null +++ b/secbuf.c @@ -0,0 +1,89 @@ +/* + * Part of Very Secure FTPd + * Licence: GPL v2 + * Author: Chris Evans + * secbuf.c + * + * Here are some routines providing the (possibly silly) concept of a secure + * buffer. A secure buffer may not be overflowed. A single byte overflow + * will cause the program to safely terminate. + */ + +#include "secbuf.h" +#include "utility.h" +#include "sysutil.h" +#include "sysdeputil.h" + +void +vsf_secbuf_alloc(char** p_ptr, unsigned int size) +{ + unsigned int page_offset; + unsigned int round_up; + char* p_mmap; + char* p_no_access_page; + unsigned int page_size = vsf_sysutil_getpagesize(); + + /* Free any previous buffer */ + vsf_secbuf_free(p_ptr); + /* Round up to next page size */ + page_offset = size % page_size; + if (page_offset) + { + unsigned int num_pages = size / page_size; + num_pages++; + round_up = num_pages * page_size; + } + else + { + /* Allocation is on a page-size boundary */ + round_up = size; + } + /* Add on another two pages to make inaccessible */ + round_up += page_size * 2; + + p_mmap = vsf_sysutil_map_anon_pages(round_up); + /* Map the first and last page inaccessible */ + p_no_access_page = p_mmap + round_up - page_size; + vsf_sysutil_memprotect(p_no_access_page, page_size, kVSFSysUtilMapProtNone); + /* Before we make the "before" page inaccessible, store the size in it. + * A little hack so that we don't need to explicitly be passed the size + * when freeing an existing secure buffer + */ + *((unsigned int*)p_mmap) = round_up; + p_no_access_page = p_mmap; + vsf_sysutil_memprotect(p_no_access_page, page_size, kVSFSysUtilMapProtNone); + + p_mmap += page_size; + if (page_offset) + { + p_mmap += (page_size - page_offset); + } + *p_ptr = p_mmap; +} + +void +vsf_secbuf_free(char** p_ptr) +{ + unsigned int map_size; + unsigned long page_offset; + char* p_mmap = *p_ptr; + unsigned int page_size = vsf_sysutil_getpagesize(); + if (p_mmap == 0) + { + return; + } + /* Calculate the actual start of the mmap region */ + page_offset = (unsigned long) p_mmap % page_size; + if (page_offset) + { + p_mmap -= page_offset; + } + p_mmap -= page_size; + /* First make the first page readable so we can get the size */ + vsf_sysutil_memprotect(p_mmap, page_size, kVSFSysUtilMapProtReadOnly); + /* Extract the mapping size */ + map_size = *((unsigned int*)p_mmap); + /* Lose the mapping */ + vsf_sysutil_memunmap(p_mmap, map_size); +} + diff --git a/secbuf.h b/secbuf.h new file mode 100644 index 0000000..b59ebf9 --- /dev/null +++ b/secbuf.h @@ -0,0 +1,27 @@ +#ifndef VSF_SECBUF_H +#define VSF_SECBUF_H + +/* vsf_secbuf_alloc() + * PURPOSE + * Allocate a "secure buffer". A secure buffer is one which will attempt to + * catch out of bounds accesses by crashing the program (rather than + * corrupting memory). It works by using UNIX memory protection. It isn't + * foolproof. + * PARAMETERS + * p_ptr - pointer to a pointer which is to contain the secure buffer. + * Any previous buffer pointed to is freed. + * size - size in bytes required for the secure buffer. + */ +void vsf_secbuf_alloc(char** p_ptr, unsigned int size); + +/* vsf_secbuf_free() + * PURPOSE + * Frees a "secure buffer". + * PARAMETERS + * p_ptr - pointer to a pointer containing the buffer to be freed. The + * buffer pointer is nullified by this call. + */ +void vsf_secbuf_free(char** p_ptr); + +#endif /* VSF_SECBUF_H */ + diff --git a/secutil.c b/secutil.c new file mode 100644 index 0000000..3506bb8 --- /dev/null +++ b/secutil.c @@ -0,0 +1,125 @@ +/* + * Part of Very Secure FTPd + * Licence: GPL v2 + * Author: Chris Evans + * secutil.c + */ + +#include "secutil.h" +#include "str.h" +#include "sysutil.h" +#include "sysstr.h" +#include "utility.h" +#include "sysdeputil.h" + +void +vsf_secutil_change_credentials(const struct mystr* p_user_str, + const struct mystr* p_dir_str, + const struct mystr* p_ext_dir_str, + unsigned int caps, unsigned int options) +{ + struct vsf_sysutil_user* p_user; + if (!vsf_sysutil_running_as_root()) + { + bug("vsf_secutil_change_credentials: not running as root"); + } + p_user = str_getpwnam(p_user_str); + if (p_user == 0) + { + die2("cannot locate user entry:", str_getbuf(p_user_str)); + } + { + struct mystr dir_str = INIT_MYSTR; + /* Work out where the chroot() jail is */ + if (p_dir_str == 0 || str_isempty(p_dir_str)) + { + str_alloc_text(&dir_str, vsf_sysutil_user_get_homedir(p_user)); + } + else + { + str_copy(&dir_str, p_dir_str); + } + /* Sort out supplementary groups before the chroot(). We need to access + * /etc/groups + */ + if (options & VSF_SECUTIL_OPTION_USE_GROUPS) + { + vsf_sysutil_initgroups(p_user); + } + else + { + vsf_sysutil_clear_supp_groups(); + } + /* Always do the chdir() regardless of whether we are chroot()'ing */ + { + /* Do chdir() with the target effective IDs to cater for NFS mounted + * home directories. + */ + int saved_euid = 0; + int saved_egid = 0; + int retval; + if (options & VSF_SECUTIL_OPTION_CHANGE_EUID) + { + saved_euid = vsf_sysutil_geteuid(); + saved_egid = vsf_sysutil_getegid(); + vsf_sysutil_setegid(p_user); + vsf_sysutil_seteuid(p_user); + } + retval = str_chdir(&dir_str); + if (retval != 0) + { + die2("cannot change directory:", str_getbuf(&dir_str)); + } + if (p_ext_dir_str && !str_isempty(p_ext_dir_str)) + { + retval = str_chdir(p_ext_dir_str); + /* Failure on the extra directory is OK as long as we're not in + * chroot() mode + */ + if (retval != 0 && !(options & VSF_SECUTIL_OPTION_CHROOT)) + { + retval = 0; + } + } + if (retval != 0) + { + die2("cannot change directory:", str_getbuf(p_ext_dir_str)); + } + if (options & VSF_SECUTIL_OPTION_CHANGE_EUID) + { + vsf_sysutil_seteuid_numeric(saved_euid); + vsf_sysutil_setegid_numeric(saved_egid); + } + /* Do the chroot() if required */ + if (options & VSF_SECUTIL_OPTION_CHROOT) + { + vsf_sysutil_chroot("."); + } + } + str_free(&dir_str); + } + /* Handle capabilities */ + if (caps) + { + if (!vsf_sysdep_has_capabilities()) + { + /* Need privilege but OS has no capabilities - have to keep root */ + return; + } + if (!vsf_sysdep_has_capabilities_as_non_root()) + { + vsf_sysdep_adopt_capabilities(caps); + return; + } + vsf_sysdep_keep_capabilities(); + } + /* Set group id */ + vsf_sysutil_setgid(p_user); + /* Finally set user id */ + vsf_sysutil_setuid(p_user); + if (caps) + { + vsf_sysdep_adopt_capabilities(caps); + } +} + diff --git a/secutil.h b/secutil.h new file mode 100644 index 0000000..1805268 --- /dev/null +++ b/secutil.h @@ -0,0 +1,37 @@ +#ifndef VSF_SECUTIL_H +#define VSF_SECUTIL_H + +struct mystr; + +/* vsf_secutil_change_credentials() + * PURPOSE + * This function securely switches process credentials to the user specified. + * There are options to enter a chroot() jail, and supplementary groups may + * or may not be activated. + * PARAMETERS + * p_user_str - the name of the user to become + * p_dir_str - the directory to chdir() and possibly chroot() to. + * (if NULL, the user's home directory is used) + * p_ext_dir_str - the directory to chdir() and possibly chroot() to, + * applied in addition to the directory calculated by + * p_user_str and p_dir_str. + * caps - bitmap of capabilities to adopt. NOTE, if the underlying + * OS does not support capabilities as a non-root user, and + * the capability bitset is non-empty, then root privileges + * will have to be retained. + * options - see bitmask definitions below + */ + +/* chroot() the user into the new directory */ +#define VSF_SECUTIL_OPTION_CHROOT 1 +/* Activate any supplementary groups the user may have */ +#define VSF_SECUTIL_OPTION_USE_GROUPS 2 +/* Do the chdir() as the effective userid of the target user */ +#define VSF_SECUTIL_OPTION_CHANGE_EUID 4 + +void vsf_secutil_change_credentials(const struct mystr* p_user_str, + const struct mystr* p_dir_str, + const struct mystr* p_ext_dir_str, + unsigned int caps, unsigned int options); +#endif /* VSF_SECUTIL_H */ + diff --git a/serialization_auto.h b/serialization_auto.h new file mode 100644 index 0000000..be64a63 --- /dev/null +++ b/serialization_auto.h @@ -0,0 +1,130 @@ +template<> inline std::pair serialize(const Request_Result_And_Reason & object) { + int struct_size = sizeof(int); + std::pair toRet; + char * data; + char * cdata; + struct_size += sizeof(object.result); + struct_size += sizeof(int); + struct_size += strlen(object.reason) + 1; + data = new char[struct_size]; + cdata = data; + *((int*)data) = TypeIDHashtable::getHash(typeid(object)); + data += sizeof(int); + memcpy(data, &object.result, sizeof(int)); + data += sizeof(int); + *((int*)data) = strlen(object.reason); + data += sizeof(int); + memcpy(data,object.reason, strlen(object.reason) + 1); + data += strlen(object.reason) + 1; + toRet.first = cdata; + toRet.second = struct_size; + return toRet; +} +template<> inline std::pair serialize(const Request_Result & object) { + int struct_size = sizeof(int); + std::pair toRet; + char * data; + char * cdata; + struct_size += sizeof(object.result); + data = new char[struct_size]; + cdata = data; + *((int*)data) = TypeIDHashtable::getHash(typeid(object)); + data += sizeof(int); + memcpy(data, &object.result, sizeof(int)); + data += sizeof(int); + toRet.first = cdata; + toRet.second = struct_size; + return toRet; +} +template<> inline std::pair serialize(const Request_Result_Homedir & object) { + int struct_size = sizeof(int); + std::pair toRet; + char * data; + char * cdata; + struct_size += sizeof(int); + struct_size += strlen(object.homedir) + 1; + data = new char[struct_size]; + cdata = data; + *((int*)data) = TypeIDHashtable::getHash(typeid(object)); + data += sizeof(int); + *((int*)data) = strlen(object.homedir); + data += sizeof(int); + memcpy(data,object.homedir, strlen(object.homedir) + 1); + data += strlen(object.homedir) + 1; + toRet.first = cdata; + toRet.second = struct_size; + return toRet; +} +template<> inline void deserialize(const std::pair & data, Request_Result_And_Reason & object) { + int * xorsum = (int*)data.first; + char * ddata = data.first; + ddata += sizeof(int); + int struct_size = sizeof(int); + if (*xorsum != TypeIDHashtable::getHash(typeid(object))) { + throw InconsistentTypeException(); + } + struct_size += sizeof(object.result); + ddata += sizeof(object.result); + { + int strlen = *(int*)ddata; + struct_size += strlen + 1 + sizeof(int); + ddata += strlen + 1 + sizeof(int); + } + if(data.second != struct_size) { + throw DeserializationMemoryInconsistencyException(); + } + ddata = data.first + sizeof(int); + memcpy(&object.result, ddata, sizeof(int)); + ddata += sizeof(int); + { + int strlen = *(int*)ddata; + ddata += sizeof(int); + char * str = new char[strlen + 1]; + memcpy(str, ddata, strlen + 1); + ddata += strlen + 1; + object.reason = str; + } +} +template<> inline void deserialize(const std::pair & data, Request_Result & object) { + int * xorsum = (int*)data.first; + char * ddata = data.first; + ddata += sizeof(int); + int struct_size = sizeof(int); + if (*xorsum != TypeIDHashtable::getHash(typeid(object))) { + throw InconsistentTypeException(); + } + struct_size += sizeof(object.result); + ddata += sizeof(object.result); + if(data.second != struct_size) { + throw DeserializationMemoryInconsistencyException(); + } + ddata = data.first + sizeof(int); + memcpy(&object.result, ddata, sizeof(int)); + ddata += sizeof(int); +} +template<> inline void deserialize(const std::pair & data, Request_Result_Homedir & object) { + int * xorsum = (int*)data.first; + char * ddata = data.first; + ddata += sizeof(int); + int struct_size = sizeof(int); + if (*xorsum != TypeIDHashtable::getHash(typeid(object))) { + throw InconsistentTypeException(); + } + { + int strlen = *(int*)ddata; + struct_size += strlen + 1 + sizeof(int); + ddata += strlen + 1 + sizeof(int); + } + if(data.second != struct_size) { + throw DeserializationMemoryInconsistencyException(); + } + ddata = data.first + sizeof(int); + { + int strlen = *(int*)ddata; + ddata += sizeof(int); + char * str = new char[strlen + 1]; + memcpy(str, ddata, strlen + 1); + ddata += strlen + 1; + object.homedir = str; + } +} diff --git a/session.h b/session.h new file mode 100644 index 0000000..176abdc --- /dev/null +++ b/session.h @@ -0,0 +1,98 @@ +#ifndef VSF_SESSION_H +#define VSF_SESSION_H + +#ifndef VSFTP_STR_H +#include "str.h" +#endif + +#ifndef VSF_FILESIZE_H +#include "filesize.h" +#endif + +struct vsf_sysutil_sockaddr; +struct mystr_list; + +/* This struct contains variables specific to the state of the current FTP + * session + */ +struct vsf_session +{ + /* Details of the control connection */ + struct vsf_sysutil_sockaddr* p_local_addr; + struct vsf_sysutil_sockaddr* p_remote_addr; + char* p_control_line_buf; + + /* Details of the data connection */ + int pasv_listen_fd; + struct vsf_sysutil_sockaddr* p_port_sockaddr; + int data_fd; + int data_progress; + unsigned int bw_rate_max; + long bw_send_start_sec; + long bw_send_start_usec; + + /* Details of the login */ + int is_anonymous; + struct mystr user_str; + struct mystr plugin_arch_user_str; + struct mystr anon_pass_str; + + /* Details of the FTP protocol state */ + filesize_t restart_pos; + int is_ascii; + struct mystr rnfr_filename_str; + int abor_received; + int epsv_all; + + /* Details of FTP session state */ + struct mystr_list* p_visited_dir_list; + + /* Details of userids which are interesting to us */ + int anon_ftp_uid; + int guest_user_uid; + int anon_upload_chown_uid; + + /* Things we need to cache before we chroot() */ + struct mystr banned_email_str; + struct mystr email_passwords_str; + struct mystr userlist_str; + struct mystr banner_str; + int tcp_wrapper_ok; + + /* Logging related details */ + int xferlog_fd; + int vsftpd_log_fd; + struct mystr remote_ip_str; + unsigned long log_type; + long log_start_sec; + long log_start_usec; + struct mystr log_str; + filesize_t transfer_size; + + /* Buffers */ + struct mystr ftp_cmd_str; + struct mystr ftp_arg_str; + + /* Parent<->child comms channel */ + int parent_fd; + int child_fd; + + /* Other details */ + unsigned int num_clients; + unsigned int num_this_ip; + struct mystr home_str; + + /* Secure connections state */ + int control_use_ssl; + int data_use_ssl; + void* p_ssl_ctx; + void* p_control_ssl; + void* p_data_ssl; + int ssl_slave_active; + int ssl_slave_fd; + int ssl_consumer_fd; + unsigned int login_fails; +}; + +#endif /* VSF_SESSION_H */ + diff --git a/slave_thread.cpp b/slave_thread.cpp new file mode 100644 index 0000000..633e58f --- /dev/null +++ b/slave_thread.cpp @@ -0,0 +1,394 @@ +#include "slave_thread.h" +#include "udsrf.h" +#include +#include "plugin_architecture_api.h" +#include "plugin_architecture_interface.h" +#include +#include +#include "file_logger_plugin.h" +#include +#include +#include "mysql_plugin.h" +#include "pgsql_plugin.h" +#include "tunables.h" +#include "slave_thread_auto.h" + +pthread_t thread_slow; +pthread_t thread_fast; + +pthread_cond_t wait_for_plugins = PTHREAD_COND_INITIALIZER; +pthread_mutex_t wait_for_plugins_mutex = PTHREAD_MUTEX_INITIALIZER; +bool plugin_created = false; + +static volatile bool slow_runs = true; +static volatile bool fast_runs = true; + +pthread_mutex_t count_mutex = PTHREAD_MUTEX_INITIALIZER; +static volatile int slow_threads = 0; +static volatile int fast_threads = 0; + +static std::unexpected_handler old_unexpected_handler; +static std::terminate_handler old_terminate_handler; + +VSFPluginArchitectureSingleton pluginArchitecture; + +static void * slow_event_loop(void *); +static void * fast_event_loop(void *); +static void *slow_listener(void * v_un); +static void *fast_listener(void * v_un); +extern "C" void handle_slave_signal(); +void handle_signal(int); +static void handle_unexpected_exception(); +static void handle_terminate(); +void slave_thread_start(struct sockaddr_un server_address_slow, struct sockaddr_un server_address_fast); + +extern "C" void handle_slave_signal() { + slow_runs = false; + fast_runs = false; + + struct Event_ServQuit sigend; + try { + send_event_servQuit(&sigend); + } catch (...) {} //can happen you know.. +} + +void handle_signal(int) { + handle_slave_signal(); +} + +static void handle_unexpected_exception() { + try { + throw; + } catch (std::exception & e) { + cerr << "Caught an exception (" << typeid(e).name() << ")\n"; + } + catch (...) { + cerr << "Caught an unknown exception\n"; + } + printf("unexpected exception!!\n"); + handle_slave_signal(); + old_unexpected_handler(); +} + +static void handle_terminate() { + try { + throw; + } catch (std::exception & e) { + cerr << "Caught an exception (" << typeid(e).name() << ")\n"; + } + catch (...) { + cerr << "Caught an unknown exception\n"; + } + printf("unexpected terminate!!\n"); + handle_slave_signal(); + old_terminate_handler(); +} + +void slave_thread_start(struct sockaddr_un server_address_slow, struct sockaddr_un server_address_fast) { + std::auto_ptr currentPlugin; + + signal(SIGHUP, handle_signal); + signal(SIGINT, handle_signal); + signal(SIGQUIT, handle_signal); + + old_unexpected_handler = std::set_unexpected(handle_unexpected_exception); + old_terminate_handler = std::set_terminate(handle_terminate); + + pthread_attr_t attr; + pthread_attr_init(&attr); + pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); + + pthread_mutex_init(&count_mutex, 0); + + if (pthread_create(&thread_slow,&attr,slow_listener,&server_address_slow)) + { + printf("pthread_create(&thread_slow,&attr,slow_listener,&server_address_slow) failed!!\n"); + exit(0); + } + if (pthread_create(&thread_fast,&attr,fast_listener,&server_address_fast)) + { + printf("pthread_create(&thread_fast,&attr,fast_listener,&server_address_fast) failed!!\n"); + exit(0); + } + + currentPlugin = createCurrentSQLPlugin(pluginArchitecture); + + pthread_mutex_lock(&wait_for_plugins_mutex); + plugin_created = true; + pthread_cond_broadcast(&wait_for_plugins); + pthread_mutex_unlock(&wait_for_plugins_mutex); + + while(slow_runs || fast_runs || slow_threads > 0 || fast_threads > 0) { + sleep(1); + } + + pthread_mutex_destroy(&count_mutex); +} + +static void *slow_listener(void * v_un) { + struct sockaddr_un * slow_un = ((struct sockaddr_un*)v_un); + int socket_to_main_slow; + + printf("trying to create a PF_UNIX, SOCK_SEQPACKET socket\n"); + + socket_to_main_slow = socket(PF_UNIX, SOCK_SEQPACKET, 0); + + printf("slow listener at work\n"); + + if (bind(socket_to_main_slow, (struct sockaddr*)slow_un, sizeof(struct sockaddr_un))) + { + printf("bind error\n"); + perror(0); + pthread_exit(0); + } else { + printf("bind ok\n"); + } + if (listen(socket_to_main_slow,20)) + { + printf("listen error\n"); + perror(0); + pthread_exit(0); + } else { + printf("listen ok\n"); + } + + pthread_mutex_lock(&wait_for_plugins_mutex); + if (!plugin_created) + pthread_cond_wait(&wait_for_plugins, &wait_for_plugins_mutex); + pthread_mutex_unlock(&wait_for_plugins_mutex); + + printf(" entering accept loop\n"); + + while(slow_runs) + { + int * newSock = new int; + socklen_t sockaddr_len = 0; + struct sockaddr_un addr; + *newSock = accept(socket_to_main_slow, (struct sockaddr*)&addr, &sockaddr_len); + pthread_attr_t attr; + pthread_attr_init(&attr); + struct sched_param sparam; + sparam.sched_priority = 0; + pthread_attr_setschedparam(&attr, &sparam); + pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); + + if (*newSock == -1) + { + printf("accept error\n"); + perror(0); + pthread_exit(0); + } + + pthread_t thread; + if(pthread_create(&thread, &attr, slow_event_loop, newSock) == 0) + { + pthread_mutex_lock(&count_mutex); + slow_threads++; + pthread_mutex_unlock(&count_mutex); + } + } + pthread_exit(0); +} + +static void *fast_listener(void * v_un) { + struct sockaddr_un * fast_un = ((struct sockaddr_un*)v_un); + int socket_to_main_fast; + + printf("trying to create a PF_UNIX, SOCK_SEQPACKET socket\n"); + + socket_to_main_fast = socket(PF_UNIX, SOCK_SEQPACKET, 0); + + printf("fast listener at work\n"); + + if (bind(socket_to_main_fast, (struct sockaddr*)fast_un, sizeof(struct sockaddr_un))) + { + printf("bind error\n"); + perror(0); + pthread_exit(0); + } + if (listen(socket_to_main_fast,20)) + { + printf("listen error\n"); + perror(0); + pthread_exit(0); + } + + pthread_mutex_lock(&wait_for_plugins_mutex); + if (!plugin_created) + pthread_cond_wait(&wait_for_plugins, &wait_for_plugins_mutex); + pthread_mutex_unlock(&wait_for_plugins_mutex); + + printf(" entering accept loop\n"); + + while (fast_runs) + { + int * newSock = new int; + socklen_t sockaddr_len = 0; + struct sockaddr_un addr; + *newSock = accept(socket_to_main_fast, (struct sockaddr*)&addr, &sockaddr_len); + pthread_attr_t attr; + pthread_attr_init(&attr); + struct sched_param sparam; + sparam.sched_priority = -15; + pthread_attr_setschedparam(&attr, &sparam); + pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); + + if (*newSock == -1) + { + printf("accept error\n"); + perror(0); + pthread_exit(0); + } + + pthread_t thread; + if (pthread_create(&thread, &attr, fast_event_loop, newSock) == 0) + { + pthread_mutex_lock(&count_mutex); + fast_threads++; + pthread_mutex_unlock(&count_mutex); + } + } + pthread_exit(0); +} + +static void *slow_event_loop(void * v_sock) { + int socket = *((int*)v_sock); + delete (int*)v_sock; + bool running = true; + //proces iteratively + + while (running) + { + int number; + + try { + recvData(socket, number); + } + catch(SendRecvException & e) { + printf(" error in recv, bye\n"); + goto end_thread; + } + + switch (number & 0x7F000000) + { + case Event_Identifier: //event + { + std::auto_ptr event; + + event = Event::readFromSocket(socket); + + if (event->getNumber() == Event_Number_CliEnd) + { + running = false; + } + + if (event->getNumber() == Event_Number_ServQuit) + { + running = false; + slow_runs = false; + fast_runs = false; + } + + //printf("event number: %xd\n", event->getNumber()); + //printf("doing: pluginArchitecture.getVSFPluginArchitecture().handleEvent(*event); \n"); + pluginArchitecture.getVSFPluginArchitecture().handleEvent(*event); + //printf("phew, done :)\n"); + } + break; + case Request_Identifier: //request + { + std::auto_ptr request; + + request = Request::readFromSocket(socket); + + printf("slow: request number: %xd\n", request->getNumber()); + printf("doing: pluginArchitecture.getVSFPluginArchitecture().handleRequest(*request); \n"); + pluginArchitecture.getVSFPluginArchitecture().handleRequest(*request).writeToSocket(socket); + printf("phew, done :)\n"); + } + break; + default: + printf("unknown magic number in event/request\n"); + handle_slave_signal(); + goto end_thread; + break; + }//switch + }//while + + end_thread: + pthread_mutex_lock(&count_mutex); + slow_threads--; + pthread_mutex_unlock(&count_mutex); + pthread_exit(0); +} + +static void *fast_event_loop(void * v_sock) { + int socket = *((int*)v_sock); + delete (int*)v_sock; + bool running = true; + + while (running) + { + int number; + try { + recvData(socket, number); + } catch(SendRecvException & e) { + printf(" error in recv, bye\n"); + goto end_thread; + } + + switch (number & 0x7F000000) + { + case Event_Identifier: //event + { + std::auto_ptr event; + + event = Event::readFromSocket(socket); + + if (event->getNumber() == Event_Number_CliEnd) + { + running = false; + break; + } + + if (event->getNumber() == Event_Number_ServQuit) + { + running = false; + slow_runs = false; + fast_runs = false; + break; + } + + //printf("event number: %xd\n", event->getNumber()); + //printf("doing: pluginArchitecture.getVSFPluginArchitecture().handleEvent(*event); \n"); + pluginArchitecture.getVSFPluginArchitecture().handleEvent(*event); + //printf("phew, done :)\n"); + } + break; + case Request_Identifier: //request + { + std::auto_ptr request; + + request = Request::readFromSocket(socket); + + printf("fast: request number: %xd\n", request->getNumber()); + printf("doing: pluginArchitecture.getVSFPluginArchitecture().handleRequest(*request); \n"); + pluginArchitecture.getVSFPluginArchitecture().handleRequest(*request).writeToSocket(socket); + printf("phew, done :)\n"); + } + break; + default: + printf("unknown magic number in event/request\n"); + handle_slave_signal(); + goto end_thread; + break; + }//switch + + }//while + + end_thread: + pthread_mutex_lock(&count_mutex); + fast_threads--; + pthread_mutex_unlock(&count_mutex); + pthread_exit(0); +} diff --git a/slave_thread.h b/slave_thread.h new file mode 100644 index 0000000..2f6e0db --- /dev/null +++ b/slave_thread.h @@ -0,0 +1,10 @@ +#ifndef HI_PRIO_THREAD_H +#define HI_PRIO_THREAD_H + +#include +#include +#include + +void slave_thread_start(struct sockaddr_un server_address_slow, struct sockaddr_un server_address_fast); + +#endif //HI_PRIO_THREAD_H diff --git a/slave_thread_auto.h b/slave_thread_auto.h new file mode 100644 index 0000000..4c540ca --- /dev/null +++ b/slave_thread_auto.h @@ -0,0 +1,16 @@ +#include "tunables.h" +#include "plugin_architecture_api.h" +#include +#include +std::auto_ptr createCurrentSQLPlugin(VSFPluginArchitectureSingleton & pluginArchitecture) { + if (strcasecmp(tunable_sql_plugin, "VSFFileLoggerPlugin") == 0) { + return std::auto_ptr(new VSFFileLoggerPlugin(pluginArchitecture.getVSFPluginArchitecture())); + } else + if (strcasecmp(tunable_sql_plugin, "VSFMySQLPlugin") == 0) { + return std::auto_ptr(new VSFMySQLPlugin(pluginArchitecture.getVSFPluginArchitecture())); + } else + if (strcasecmp(tunable_sql_plugin, "VSFPgSQLPlugin") == 0) { + return std::auto_ptr(new VSFPgSQLPlugin(pluginArchitecture.getVSFPluginArchitecture())); + } else + { throw UnknownPluginException(); } +} diff --git a/ssl.c b/ssl.c new file mode 100644 index 0000000..70432c1 --- /dev/null +++ b/ssl.c @@ -0,0 +1,435 @@ +/* + * Part of Very Secure FTPd + * Licence: GPL v2. Note that this code interfaces with with the OpenSSL + * libraries, so please read LICENSE where I give explicit permission to link + * against the OpenSSL libraries. + * Author: Chris Evans + * ssl.c + * + * Routines to handle a SSL/TLS-based implementation of RFC 2228, i.e. + * encryption. + */ + +#include "ssl.h" +#include "session.h" +#include "ftpcodes.h" +#include "ftpcmdio.h" +#include "defs.h" +#include "str.h" +#include "sysutil.h" +#include "tunables.h" +#include "utility.h" +#include "builddefs.h" + +#ifdef VSF_BUILD_SSL + +#include +#include +#include +#include + +static char* get_ssl_error(); +static SSL* get_ssl(struct vsf_session* p_sess, int fd); +static int ssl_session_init(struct vsf_session* p_sess); +static void setup_bio_callbacks(); +static long bio_callback( + BIO* p_bio, int oper, const char* p_arg, int argi, long argl, long retval); + +static int ssl_inited; + +void +ssl_init(struct vsf_session* p_sess) +{ + if (!ssl_inited) + { + SSL_CTX* p_ctx; + long options; + SSL_library_init(); + p_ctx = SSL_CTX_new(SSLv23_server_method()); + if (p_ctx == NULL) + { + die("SSL: could not allocate SSL context"); + } + options = SSL_OP_ALL; + if (!tunable_sslv2) + { + options |= SSL_OP_NO_SSLv2; + } + if (!tunable_sslv3) + { + options |= SSL_OP_NO_SSLv3; + } + if (!tunable_tlsv1) + { + options |= SSL_OP_NO_TLSv1; + } + SSL_CTX_set_options(p_ctx, options); + if (tunable_rsa_cert_file) + { + const char* p_key = tunable_rsa_private_key_file; + if (!p_key) + { + p_key = tunable_rsa_cert_file; + } + if (SSL_CTX_use_certificate_chain_file(p_ctx, tunable_rsa_cert_file) != 1) + { + die("SSL: cannot load RSA certificate"); + } + if (SSL_CTX_use_PrivateKey_file(p_ctx, p_key, X509_FILETYPE_PEM) != 1) + { + die("SSL: cannot load RSA private key"); + } + } + if (tunable_dsa_cert_file) + { + const char* p_key = tunable_dsa_private_key_file; + if (!p_key) + { + p_key = tunable_dsa_cert_file; + } + if (SSL_CTX_use_certificate_chain_file(p_ctx, tunable_dsa_cert_file) != 1) + { + die("SSL: cannot load DSA certificate"); + } + if (SSL_CTX_use_PrivateKey_file(p_ctx, p_key, X509_FILETYPE_PEM) != 1) + { + die("SSL: cannot load DSA private key"); + } + } + if (tunable_ssl_ciphers && + SSL_CTX_set_cipher_list(p_ctx, tunable_ssl_ciphers) != 1) + { + die("SSL: could not set cipher list"); + } + if (RAND_status() != 1) + { + die("SSL: RNG is not seeded"); + } + p_sess->p_ssl_ctx = p_ctx; + ssl_inited = 1; + } +} + +void +handle_auth(struct vsf_session* p_sess) +{ + str_upper(&p_sess->ftp_arg_str); + if (str_equal_text(&p_sess->ftp_arg_str, "TLS") || + str_equal_text(&p_sess->ftp_arg_str, "TLS-C") || + str_equal_text(&p_sess->ftp_arg_str, "SSL") || + str_equal_text(&p_sess->ftp_arg_str, "TLS-P")) + { + vsf_cmdio_write(p_sess, FTP_AUTHOK, "Proceed with negotiation."); + if (!ssl_session_init(p_sess)) + { + struct mystr err_str = INIT_MYSTR; + str_alloc_text(&err_str, "Negotiation failed: "); + str_append_text(&err_str, get_ssl_error()); + vsf_cmdio_write_str(p_sess, FTP_TLS_FAIL, &err_str); + vsf_sysutil_exit(0); + } + p_sess->control_use_ssl = 1; + if (str_equal_text(&p_sess->ftp_arg_str, "SSL") || + str_equal_text(&p_sess->ftp_arg_str, "TLS-P")) + { + p_sess->data_use_ssl = 1; + } + } + else + { + vsf_cmdio_write(p_sess, FTP_BADAUTH, "Unknown AUTH type."); + } +} + +void +handle_pbsz(struct vsf_session* p_sess) +{ + if (!p_sess->control_use_ssl) + { + vsf_cmdio_write(p_sess, FTP_BADPBSZ, "PBSZ needs a secure connection."); + } + else + { + vsf_cmdio_write(p_sess, FTP_PBSZOK, "PBSZ set to 0."); + } +} + +void +handle_prot(struct vsf_session* p_sess) +{ + str_upper(&p_sess->ftp_arg_str); + if (!p_sess->control_use_ssl) + { + vsf_cmdio_write(p_sess, FTP_BADPROT, "PROT needs a secure connection."); + } + else if (str_equal_text(&p_sess->ftp_arg_str, "C")) + { + p_sess->data_use_ssl = 0; + vsf_cmdio_write(p_sess, FTP_PROTOK, "PROT now Clear."); + } + else if (str_equal_text(&p_sess->ftp_arg_str, "P")) + { + p_sess->data_use_ssl = 1; + vsf_cmdio_write(p_sess, FTP_PROTOK, "PROT now Private."); + } + else if (str_equal_text(&p_sess->ftp_arg_str, "S") || + str_equal_text(&p_sess->ftp_arg_str, "E")) + { + vsf_cmdio_write(p_sess, FTP_NOHANDLEPROT, "PROT not supported."); + } + else + { + vsf_cmdio_write(p_sess, FTP_NOSUCHPROT, "PROT not recognized."); + } +} + +void +ssl_getline(const struct vsf_session* p_sess, struct mystr* p_str, + char end_char, char* p_buf, unsigned int buflen) +{ + char* p_buf_start = p_buf; + p_buf[buflen - 1] = '\0'; + buflen--; + while (1) + { + int retval = SSL_read(p_sess->p_control_ssl, p_buf, buflen); + if (retval <= 0) + { + die("SSL_read"); + } + p_buf[retval] = '\0'; + buflen -= retval; + if (p_buf[retval - 1] == end_char || buflen == 0) + { + break; + } + p_buf += retval; + } + str_alloc_alt_term(p_str, p_buf_start, end_char); +} + +int +ssl_read(void* p_ssl, char* p_buf, unsigned int len) +{ + int retval; + int err; + do + { + retval = SSL_read((SSL*) p_ssl, p_buf, len); + err = SSL_get_error((SSL*) p_ssl, retval); + } + while (retval < 0 && (err == SSL_ERROR_WANT_READ || + err == SSL_ERROR_WANT_WRITE)); + return retval; +} + +int +ssl_write(void* p_ssl, const char* p_buf, unsigned int len) +{ + int retval; + int err; + do + { + retval = SSL_write((SSL*) p_ssl, p_buf, len); + err = SSL_get_error((SSL*) p_ssl, retval); + } + while (retval < 0 && (err == SSL_ERROR_WANT_READ || + err == SSL_ERROR_WANT_WRITE)); + return retval; +} + +int +ssl_write_str(void* p_ssl, const struct mystr* p_str) +{ + unsigned int len = str_getlen(p_str); + int ret = SSL_write((SSL*) p_ssl, str_getbuf(p_str), len); + if ((unsigned int) ret != len) + { + return -1; + } + return 0; +} + +int +ssl_accept(struct vsf_session* p_sess, int fd) +{ + SSL* p_ssl = get_ssl(p_sess, fd); + if (p_ssl == NULL) + { + return 0; + } + p_sess->p_data_ssl = p_ssl; + setup_bio_callbacks(p_ssl); + return 1; +} + +void +ssl_data_close(struct vsf_session* p_sess) +{ + SSL_free(p_sess->p_data_ssl); +} + +void +ssl_comm_channel_init(struct vsf_session* p_sess) +{ + const struct vsf_sysutil_socketpair_retval retval = + vsf_sysutil_unix_stream_socketpair(); + p_sess->ssl_consumer_fd = retval.socket_one; + p_sess->ssl_slave_fd = retval.socket_two; +} + +static SSL* +get_ssl(struct vsf_session* p_sess, int fd) +{ + SSL* p_ssl = SSL_new(p_sess->p_ssl_ctx); + if (p_ssl == NULL) + { + return NULL; + } + if (!SSL_set_fd(p_ssl, fd)) + { + SSL_free(p_ssl); + return NULL; + } + if (SSL_accept(p_ssl) != 1) + { + die(get_ssl_error()); + SSL_free(p_ssl); + return NULL; + } + return p_ssl; +} + +static int +ssl_session_init(struct vsf_session* p_sess) +{ + SSL* p_ssl = get_ssl(p_sess, VSFTP_COMMAND_FD); + if (p_ssl == NULL) + { + return 0; + } + p_sess->p_control_ssl = p_ssl; + setup_bio_callbacks(p_ssl); + return 1; +} + +static char* +get_ssl_error() +{ + SSL_load_error_strings(); + return ERR_error_string(ERR_get_error(), NULL); +} + +static void setup_bio_callbacks(SSL* p_ssl) +{ + BIO* p_bio = SSL_get_rbio(p_ssl); + BIO_set_callback(p_bio, bio_callback); + p_bio = SSL_get_wbio(p_ssl); + BIO_set_callback(p_bio, bio_callback); +} + +static long +bio_callback( + BIO* p_bio, int oper, const char* p_arg, int argi, long argl, long ret) +{ + int retval = 0; + int fd = 0; + (void) p_arg; + (void) argi; + (void) argl; + if (oper == (BIO_CB_READ | BIO_CB_RETURN) || + oper == (BIO_CB_WRITE | BIO_CB_RETURN)) + { + retval = (int) ret; + fd = BIO_get_fd(p_bio, NULL); + } + vsf_sysutil_check_pending_actions(kVSFSysUtilIO, retval, fd); + return ret; +} + +#else /* VSF_BUILD_SSL */ + +void +ssl_init(struct vsf_session* p_sess) +{ + (void) p_sess; + die("SSL: ssl_enable is set but SSL support not compiled in"); +} + +void +handle_auth(struct vsf_session* p_sess) +{ + (void) p_sess; +} + +void +handle_pbsz(struct vsf_session* p_sess) +{ + (void) p_sess; +} + +void +handle_prot(struct vsf_session* p_sess) +{ + (void) p_sess; +} + +void +ssl_getline(const struct vsf_session* p_sess, struct mystr* p_str, + char end_char, char* p_buf, unsigned int buflen) +{ + (void) p_sess; + (void) p_str; + (void) end_char; + (void) p_buf; + (void) buflen; +} + +int +ssl_read(void* p_ssl, char* p_buf, unsigned int len) +{ + (void) p_ssl; + (void) p_buf; + (void) len; + return -1; +} + +int +ssl_write(void* p_ssl, const char* p_buf, unsigned int len) +{ + (void) p_ssl; + (void) p_buf; + (void) len; + return -1; +} + +int +ssl_write_str(void* p_ssl, const struct mystr* p_str) +{ + (void) p_ssl; + (void) p_str; + return -1; +} + +int +ssl_accept(struct vsf_session* p_sess, int fd) +{ + (void) p_sess; + (void) fd; + return -1; +} + +void +ssl_data_close(struct vsf_session* p_sess) +{ + (void) p_sess; +} + +void +ssl_comm_channel_init(struct vsf_session* p_sess) +{ + (void) p_sess; +} + +#endif /* VSF_BUILD_SSL */ + diff --git a/ssl.h b/ssl.h new file mode 100644 index 0000000..4efceea --- /dev/null +++ b/ssl.h @@ -0,0 +1,21 @@ +#ifndef VSF_SSL_H +#define VSF_SSL_H + +struct vsf_session; +struct mystr; + +void ssl_getline(const struct vsf_session* p_sess, struct mystr* p_str, + char end_char, char* p_buf, unsigned int buflen); +int ssl_read(void* p_ssl, char* p_buf, unsigned int len); +int ssl_write(void* p_ssl, const char* p_buf, unsigned int len); +int ssl_write_str(void* p_ssl, const struct mystr* p_str); +void ssl_init(struct vsf_session* p_sess); +int ssl_accept(struct vsf_session* p_sess, int fd); +void ssl_data_close(struct vsf_session* p_sess); +void ssl_comm_channel_init(struct vsf_session* p_sess); +void handle_auth(struct vsf_session* p_sess); +void handle_pbsz(struct vsf_session* p_sess); +void handle_prot(struct vsf_session* p_sess); + +#endif /* VSF_SSL_H */ + diff --git a/standalone.c b/standalone.c new file mode 100644 index 0000000..00aeadb --- /dev/null +++ b/standalone.c @@ -0,0 +1,303 @@ +/* + * Part of Very Secure FTPd + * Licence: GPL v2 + * Author: Chris Evans + * standalone.c + * + * Code to listen on the network and launch children servants. + */ + +#include "standalone.h" + +#include "parseconf.h" +#include "tunables.h" +#include "sysutil.h" +#include "sysdeputil.h" +#include "utility.h" +#include "defs.h" +#include "hash.h" +#include "str.h" +#include "ipaddrparse.h" +#include "plugin_architecture_interface.h" + +static unsigned int s_children; +static struct hash* s_p_ip_count_hash; +static struct hash* s_p_pid_ip_hash; +static unsigned int s_ipaddr_size; + +static void handle_sigchld(int duff); +static void handle_sighup(int duff); +static void prepare_child(int sockfd); +static unsigned int handle_ip_count(void* p_raw_addr); +static void drop_ip_count(void* p_raw_addr); + +static unsigned int hash_ip(unsigned int buckets, void* p_key); +static unsigned int hash_pid(unsigned int buckets, void* p_key); + +struct vsf_client_launch +vsf_standalone_main(void) +{ + struct vsf_sysutil_sockaddr* p_accept_addr = 0; + int listen_sock = -1; + int retval; + s_ipaddr_size = vsf_sysutil_get_ipaddr_size(); + if (tunable_listen && tunable_listen_ipv6) + { + die("run two copies of vsftpd for IPv4 and IPv6"); + } + if (tunable_background) + { + int forkret = vsf_sysutil_fork(); + if (forkret > 0) + { + /* Parent, just exit */ + vsf_sysutil_exit(0); + } + /* Son, close standard FDs to avoid SSH hang-on-exit */ + vsf_sysutil_close_failok(0); + vsf_sysutil_close_failok(1); + vsf_sysutil_close_failok(2); + vsf_sysutil_make_session_leader(); + } + if (tunable_listen) + { + listen_sock = vsf_sysutil_get_ipv4_sock(); + } + else + { + listen_sock = vsf_sysutil_get_ipv6_sock(); + } + vsf_sysutil_activate_reuseaddr(listen_sock); + + s_p_ip_count_hash = hash_alloc(256, s_ipaddr_size, + sizeof(unsigned int), hash_ip); + s_p_pid_ip_hash = hash_alloc(256, sizeof(int), + s_ipaddr_size, hash_pid); + if (tunable_setproctitle_enable) + { + vsf_sysutil_setproctitle("LISTENER"); + } + vsf_sysutil_install_async_sighandler(kVSFSysUtilSigCHLD, handle_sigchld); + vsf_sysutil_install_async_sighandler(kVSFSysUtilSigHUP, handle_sighup); + if (tunable_listen) + { + struct vsf_sysutil_sockaddr* p_sockaddr = 0; + vsf_sysutil_sockaddr_alloc_ipv4(&p_sockaddr); + vsf_sysutil_sockaddr_set_port(p_sockaddr, tunable_listen_port); + if (!tunable_listen_address) + { + vsf_sysutil_sockaddr_set_any(p_sockaddr); + } + else + { + if (!vsf_sysutil_inet_aton(tunable_listen_address, p_sockaddr)) + { + die2("bad listen_address: ", tunable_listen_address); + } + } + retval = vsf_sysutil_bind(listen_sock, p_sockaddr); + vsf_sysutil_free(p_sockaddr); + if (vsf_sysutil_retval_is_error(retval)) + { + die("could not bind listening IPv4 socket"); + } + } + else + { + struct vsf_sysutil_sockaddr* p_sockaddr = 0; + vsf_sysutil_sockaddr_alloc_ipv6(&p_sockaddr); + vsf_sysutil_sockaddr_set_port(p_sockaddr, tunable_listen_port); + if (!tunable_listen_address6) + { + vsf_sysutil_sockaddr_set_any(p_sockaddr); + } + else + { + struct mystr addr_str = INIT_MYSTR; + const unsigned char* p_raw_addr; + str_alloc_text(&addr_str, tunable_listen_address6); + p_raw_addr = vsf_sysutil_parse_ipv6(&addr_str); + str_free(&addr_str); + if (!p_raw_addr) + { + die2("bad listen_address6: ", tunable_listen_address6); + } + vsf_sysutil_sockaddr_set_ipv6addr(p_sockaddr, p_raw_addr); + } + retval = vsf_sysutil_bind(listen_sock, p_sockaddr); + vsf_sysutil_free(p_sockaddr); + if (vsf_sysutil_retval_is_error(retval)) + { + die("could not bind listening IPv6 socket"); + } + } + vsf_sysutil_listen(listen_sock, VSFTP_LISTEN_BACKLOG); + vsf_sysutil_sockaddr_alloc(&p_accept_addr); + while (1) + { + struct vsf_client_launch child_info; + void* p_raw_addr; + int new_child; + int new_client_sock; + vsf_sysutil_unblock_sig(kVSFSysUtilSigCHLD); + vsf_sysutil_unblock_sig(kVSFSysUtilSigHUP); + new_client_sock = vsf_sysutil_accept_timeout( + listen_sock, p_accept_addr, 0); + vsf_sysutil_block_sig(kVSFSysUtilSigCHLD); + vsf_sysutil_block_sig(kVSFSysUtilSigHUP); + if (vsf_sysutil_retval_is_error(new_client_sock)) + { + continue; + } + ++s_children; + child_info.num_children = s_children; + child_info.num_this_ip = 0; + p_raw_addr = vsf_sysutil_sockaddr_get_raw_addr(p_accept_addr); + child_info.num_this_ip = handle_ip_count(p_raw_addr); + new_child = vsf_sysutil_fork_failok(); + if (new_child != 0) + { + /* Parent context */ + vsf_sysutil_close(new_client_sock); + if (new_child > 0) + { + hash_add_entry(s_p_pid_ip_hash, (void*)&new_child, p_raw_addr); + } + else + { + /* fork() failed, clear up! */ + --s_children; + drop_ip_count(p_raw_addr); + } + /* Fall through to while() loop and accept() again */ + } + else + { + /* Child context */ + vsf_sysutil_close(listen_sock); + prepare_child(new_client_sock); + if (tunable_plugin_architecture) { + if(reconnect_after_fork()) { + die("reconnect to plugin architecture failed after successful fork\n"); + } + } + /* By returning here we "launch" the child process with the same + * contract as xinetd would provide. + */ + return child_info; + } + } +} + +static void +prepare_child(int new_client_sock) +{ + /* We must satisfy the contract: command socket on fd 0, 1, 2 */ + vsf_sysutil_dupfd2(new_client_sock, 0); + vsf_sysutil_dupfd2(new_client_sock, 1); + vsf_sysutil_dupfd2(new_client_sock, 2); + if (new_client_sock > 2) + { + vsf_sysutil_close(new_client_sock); + } +} + +static void +drop_ip_count(void* p_raw_addr) +{ + unsigned int count; + unsigned int* p_count = + (unsigned int*)hash_lookup_entry(s_p_ip_count_hash, p_raw_addr); + if (!p_count) + { + bug("IP address missing from hash"); + } + count = *p_count; + if (!count) + { + bug("zero count for IP address"); + } + count--; + *p_count = count; + if (!count) + { + hash_free_entry(s_p_ip_count_hash, p_raw_addr); + } +} + +static void +handle_sigchld(int duff) +{ + unsigned int reap_one = 1; + (void) duff; + while (reap_one) + { + reap_one = (unsigned int)vsf_sysutil_wait_reap_one(); + if (reap_one) + { + struct vsf_sysutil_ipaddr* p_ip; + /* Account total number of instances */ + --s_children; + /* Account per-IP limit */ + p_ip = (struct vsf_sysutil_ipaddr*) + hash_lookup_entry(s_p_pid_ip_hash, (void*)&reap_one); + drop_ip_count(p_ip); + hash_free_entry(s_p_pid_ip_hash, (void*)&reap_one); + } + } +} + +static void +handle_sighup(int duff) +{ + (void) duff; + /* We don't crash the out the listener if an invalid config was added */ + vsf_parseconf_load_file(0, 0); +} + +static unsigned int +hash_ip(unsigned int buckets, void* p_key) +{ + const unsigned char* p_raw_ip = (const unsigned char*)p_key; + unsigned int val = 0; + int shift = 24; + unsigned int i; + for (i = 0; i < s_ipaddr_size; ++i) + { + val ^= p_raw_ip[i] << shift; + shift -= 8; + if (shift < 0) + { + shift = 24; + } + } + return val % buckets; +} + +static unsigned int +hash_pid(unsigned int buckets, void* p_key) +{ + unsigned int* p_pid = (unsigned int*)p_key; + return (*p_pid) % buckets; +} + +static unsigned int +handle_ip_count(void* p_ipaddr) +{ + unsigned int* p_count = + (unsigned int*)hash_lookup_entry(s_p_ip_count_hash, p_ipaddr); + unsigned int count; + if (!p_count) + { + count = 1; + hash_add_entry(s_p_ip_count_hash, p_ipaddr, (void*)&count); + } + else + { + count = *p_count; + count++; + *p_count = count; + } + return count; +} + diff --git a/standalone.h b/standalone.h new file mode 100644 index 0000000..9edcfde --- /dev/null +++ b/standalone.h @@ -0,0 +1,23 @@ +#ifndef VSF_STANDALONE_H +#define VSF_STANDALONE_H + +struct vsf_client_launch +{ + unsigned int num_children; + unsigned int num_this_ip; +}; + +/* vsf_standalone_main() + * PURPOSE + * This function starts listening on the network for incoming FTP connections. + * When it gets one, it returns to the caller in a new process, with file + * descriptor 0, 1 and 2 set to the network socket of the new client. + * + * RETURNS + * Returns a structure representing the current number of clients, and + * instances for this IP addresss. + */ +struct vsf_client_launch vsf_standalone_main(void); + +#endif /* VSF_STANDALONE_H */ + diff --git a/str.c b/str.c new file mode 100644 index 0000000..f6a9d9b --- /dev/null +++ b/str.c @@ -0,0 +1,668 @@ +/* + * Part of Very Secure FTPd + * Licence: GPL v2 + * Author: Chris Evans + * str.c + * + * Generic string handling functions. The fact that a string is implemented + * internally using a buffer is not exposed in the API. If you can't see + * the buffers, you can't handle them in a screwed way. Or so goes the + * theory, anyway... + */ + +/* Anti-lamer measures deployed, sir! */ +#define PRIVATE_HANDS_OFF_p_buf p_buf +#define PRIVATE_HANDS_OFF_len len +#define PRIVATE_HANDS_OFF_alloc_bytes alloc_bytes +#include "str.h" + +/* Ick. Its for die() */ +#include "utility.h" +#include "sysutil.h" + +/* File local functions */ +static void str_split_text_common(struct mystr* p_src, struct mystr* p_rhs, + const char* p_text, int is_reverse); +static int str_equal_internal(const char* p_buf1, unsigned int buf1_len, + const char* p_buf2, unsigned int buf2_len); + +/* Private functions */ +static void +s_setbuf(struct mystr* p_str, char* p_newbuf) +{ + if (p_str->p_buf != 0) + { + bug("p_buf not NULL when setting it"); + } + p_str->p_buf = p_newbuf; +} + +void +private_str_alloc_memchunk(struct mystr* p_str, const char* p_src, + unsigned int len) +{ + /* Make sure this will fit in the buffer */ + unsigned int buf_needed = len + 1; + if (buf_needed > p_str->alloc_bytes) + { + str_free(p_str); + s_setbuf(p_str, vsf_sysutil_malloc(buf_needed)); + p_str->alloc_bytes = buf_needed; + } + vsf_sysutil_memcpy(p_str->p_buf, p_src, len); + p_str->p_buf[len] = '\0'; + p_str->len = len; +} + +void +private_str_append_memchunk(struct mystr* p_str, const char* p_src, + unsigned int len) +{ + unsigned int buf_needed = p_str->len + len + 1; + if (buf_needed > p_str->alloc_bytes) + { + p_str->p_buf = vsf_sysutil_realloc(p_str->p_buf, buf_needed); + p_str->alloc_bytes = buf_needed; + } + vsf_sysutil_memcpy(p_str->p_buf + p_str->len, p_src, len); + p_str->p_buf[p_str->len + len] = '\0'; + p_str->len += len; +} + +/* Public functions */ +void +str_alloc_text(struct mystr* p_str, const char* p_src) +{ + unsigned int len = vsf_sysutil_strlen(p_src); + private_str_alloc_memchunk(p_str, p_src, len); +} + +void +str_copy(struct mystr* p_dest, const struct mystr* p_src) +{ + private_str_alloc_memchunk(p_dest, p_src->p_buf, p_src->len); +} + +const char* +str_strdup(const struct mystr* p_str) +{ + return vsf_sysutil_strdup(str_getbuf(p_str)); +} + +void +str_alloc_alt_term(struct mystr* p_str, const char* p_src, char term) +{ + const char* p_search = p_src; + unsigned int len = 0; + while (*p_search != term) + { + p_search++; + len++; + } + private_str_alloc_memchunk(p_str, p_src, len); +} + +void +str_alloc_ulong(struct mystr* p_str, unsigned long the_long) +{ + str_alloc_text(p_str, vsf_sysutil_ulong_to_str(the_long)); +} + +void +str_alloc_filesize_t(struct mystr* p_str, filesize_t the_filesize) +{ + str_alloc_text(p_str, vsf_sysutil_filesize_t_to_str(the_filesize)); +} + +void +str_free(struct mystr* p_str) +{ + if (p_str->p_buf != 0) + { + vsf_sysutil_free(p_str->p_buf); + } + p_str->p_buf = 0; + p_str->len = 0; + p_str->alloc_bytes = 0; +} + +void +str_empty(struct mystr* p_str) +{ + /* Ensure a buffer is allocated. */ + (void) str_getbuf(p_str); + str_trunc(p_str, 0); +} + +void +str_trunc(struct mystr* p_str, unsigned int trunc_len) +{ + if (trunc_len >= p_str->alloc_bytes) + { + bug("trunc_len not smaller than alloc_bytes in str_trunc"); + } + p_str->len = trunc_len; + p_str->p_buf[p_str->len] = '\0'; +} + +void +str_reserve(struct mystr* p_str, unsigned int res_len) +{ + /* Reserve space for the trailing zero as well. */ + res_len++; + if (res_len > p_str->alloc_bytes) + { + p_str->p_buf = vsf_sysutil_realloc(p_str->p_buf, res_len); + p_str->alloc_bytes = res_len; + } + p_str->p_buf[res_len - 1] = '\0'; +} + +int +str_isempty(const struct mystr* p_str) +{ + return (p_str->len == 0); +} + +unsigned int +str_getlen(const struct mystr* p_str) +{ + return p_str->len; +} + +const char* +str_getbuf(const struct mystr* p_str) +{ + if (p_str->p_buf == 0) + { + if (p_str->len != 0 || p_str->alloc_bytes != 0) + { + bug("p_buf NULL and len or alloc_bytes != 0 in str_getbuf"); + } + private_str_alloc_memchunk((struct mystr*)p_str, 0, 0); + } + return p_str->p_buf; +} + +int +str_strcmp(const struct mystr* p_str1, const struct mystr* p_str2) +{ + return str_equal_internal(p_str1->p_buf, p_str1->len, + p_str2->p_buf, p_str2->len); +} + +static int +str_equal_internal(const char* p_buf1, unsigned int buf1_len, + const char* p_buf2, unsigned int buf2_len) +{ + int retval; + unsigned int minlen = buf1_len; + if (buf2_len < minlen) + { + minlen = buf2_len; + } + retval = vsf_sysutil_memcmp(p_buf1, p_buf2, minlen); + if (retval != 0 || buf1_len == buf2_len) + { + return retval; + } + /* Strings equal but lengths differ. The greater one, then, is the longer */ + return (int) (buf1_len - buf2_len); +} + +int +str_equal(const struct mystr* p_str1, const struct mystr* p_str2) +{ + return (str_strcmp(p_str1, p_str2) == 0); +} + +int +str_equal_text(const struct mystr* p_str, const char* p_text) +{ + unsigned int cmplen = vsf_sysutil_strlen(p_text); + return (str_equal_internal(p_str->p_buf, p_str->len, p_text, cmplen) == 0); +} + +void +str_append_str(struct mystr* p_str, const struct mystr* p_other) +{ + private_str_append_memchunk(p_str, p_other->p_buf, p_other->len); +} + +void +str_append_text(struct mystr* p_str, const char* p_src) +{ + unsigned int len = vsf_sysutil_strlen(p_src); + private_str_append_memchunk(p_str, p_src, len); +} + +void +str_append_char(struct mystr* p_str, char the_char) +{ + private_str_append_memchunk(p_str, &the_char, sizeof(the_char)); +} + +void +str_append_ulong(struct mystr* p_str, unsigned long the_ulong) +{ + str_append_text(p_str, vsf_sysutil_ulong_to_str(the_ulong)); +} + +void +str_append_filesize_t(struct mystr* p_str, filesize_t the_filesize) +{ + str_append_text(p_str, vsf_sysutil_filesize_t_to_str(the_filesize)); +} + +void +str_append_double(struct mystr* p_str, double the_double) +{ + str_append_text(p_str, vsf_sysutil_double_to_str(the_double)); +} + +void +str_upper(struct mystr* p_str) +{ + unsigned int i; + for (i=0; i < p_str->len; i++) + { + p_str->p_buf[i] = vsf_sysutil_toupper(p_str->p_buf[i]); + } +} + +void +str_rpad(struct mystr* p_str, const unsigned int min_width) +{ + unsigned int to_pad; + if (p_str->len >= min_width) + { + return; + } + to_pad = min_width - p_str->len; + while (to_pad--) + { + str_append_char(p_str, ' '); + } +} + +void +str_lpad(struct mystr* p_str, const unsigned int min_width) +{ + static struct mystr s_tmp_str; + unsigned int to_pad; + if (p_str->len >= min_width) + { + return; + } + to_pad = min_width - p_str->len; + str_empty(&s_tmp_str); + while (to_pad--) + { + str_append_char(&s_tmp_str, ' '); + } + str_append_str(&s_tmp_str, p_str); + str_copy(p_str, &s_tmp_str); +} + +void +str_replace_char(struct mystr* p_str, char from, char to) +{ + unsigned int i; + for (i=0; i < p_str->len; i++) + { + if (p_str->p_buf[i] == from) + { + p_str->p_buf[i] = to; + } + } +} + +void +str_replace_text(struct mystr* p_str, const char* p_from, const char* p_to) +{ + static struct mystr s_lhs_chunk_str; + static struct mystr s_rhs_chunk_str; + unsigned int lhs_len; + str_copy(&s_lhs_chunk_str, p_str); + str_free(p_str); + do + { + lhs_len = str_getlen(&s_lhs_chunk_str); + str_split_text(&s_lhs_chunk_str, &s_rhs_chunk_str, p_from); + /* Copy lhs to destination */ + str_append_str(p_str, &s_lhs_chunk_str); + /* If this was a 'hit', append the 'to' text */ + if (str_getlen(&s_lhs_chunk_str) < lhs_len) + { + str_append_text(p_str, p_to); + } + /* Current rhs becomes new lhs */ + str_copy(&s_lhs_chunk_str, &s_rhs_chunk_str); + } while (!str_isempty(&s_lhs_chunk_str)); +} + +void +str_split_char(struct mystr* p_src, struct mystr* p_rhs, char c) +{ + /* Just use str_split_text */ + char ministr[2]; + ministr[0] = c; + ministr[1] = '\0'; + str_split_text(p_src, p_rhs, ministr); +} + +void +str_split_char_reverse(struct mystr* p_src, struct mystr* p_rhs, char c) +{ + /* Just use str_split_text_reverse */ + char ministr[2]; + ministr[0] = c; + ministr[1] = '\0'; + str_split_text_reverse(p_src, p_rhs, ministr); +} + +void +str_split_text(struct mystr* p_src, struct mystr* p_rhs, const char* p_text) +{ + str_split_text_common(p_src, p_rhs, p_text, 0); +} + +void +str_split_text_reverse(struct mystr* p_src, struct mystr* p_rhs, + const char* p_text) +{ + str_split_text_common(p_src, p_rhs, p_text, 1); +} + +static void +str_split_text_common(struct mystr* p_src, struct mystr* p_rhs, + const char* p_text, int is_reverse) +{ + struct str_locate_result locate_result; + unsigned int indexx; + unsigned int search_len = vsf_sysutil_strlen(p_text); + if (is_reverse) + { + locate_result = str_locate_text_reverse(p_src, p_text); + } + else + { + locate_result = str_locate_text(p_src, p_text); + } + /* Not found? */ + if (!locate_result.found) + { + str_empty(p_rhs); + return; + } + indexx = locate_result.index; + if (indexx + search_len > p_src->len) + { + bug("indexx invalid in str_split_text"); + } + /* Build rhs */ + private_str_alloc_memchunk(p_rhs, p_src->p_buf + indexx + search_len, + p_src->len - indexx - search_len); + /* Build lhs */ + str_trunc(p_src, indexx); +} + +struct str_locate_result +str_locate_str(const struct mystr* p_str, const struct mystr* p_look_str) +{ + return str_locate_text(p_str, str_getbuf(p_look_str)); +} + +struct str_locate_result +str_locate_str_reverse(const struct mystr* p_str, + const struct mystr* p_look_str) +{ + return str_locate_text_reverse(p_str, str_getbuf(p_look_str)); +} + +struct str_locate_result +str_locate_char(const struct mystr* p_str, char look_char) +{ + char look_str[2]; + look_str[0] = look_char; + look_str[1] = '\0'; + return str_locate_text(p_str, look_str); +} + +struct str_locate_result +str_locate_chars(const struct mystr* p_str, const char* p_chars) +{ + struct str_locate_result retval; + unsigned int num_chars = vsf_sysutil_strlen(p_chars); + unsigned int i = 0; + retval.found = 0; + retval.char_found = 0; + retval.index = 0; + for (; i < p_str->len; ++i) + { + unsigned int j = 0; + char this_char = p_str->p_buf[i]; + for (; j < num_chars; ++j) + { + if (p_chars[j] == this_char) + { + retval.found = 1; + retval.index = i; + retval.char_found = p_chars[j]; + return retval; + } + } + } + return retval; +} + +struct str_locate_result +str_locate_text(const struct mystr* p_str, const char* p_text) +{ + struct str_locate_result retval; + unsigned int i; + unsigned int text_len = vsf_sysutil_strlen(p_text); + retval.found = 0; + retval.char_found = 0; + retval.index = 0; + if (text_len == 0 || text_len > p_str->len) + { + /* Not found */ + return retval; + } + for (i=0; i <= (p_str->len - text_len); i++) + { + if (vsf_sysutil_memcmp(p_str->p_buf + i, p_text, text_len) == 0) + { + retval.found = 1; + retval.index = i; + return retval; + } + } + /* Not found */ + return retval; +} + +struct str_locate_result +str_locate_text_reverse(const struct mystr* p_str, const char* p_text) +{ + struct str_locate_result retval; + unsigned int i; + unsigned int text_len = vsf_sysutil_strlen(p_text); + retval.found = 0; + retval.char_found = 0; + retval.index = 0; + if (text_len == 0 || text_len > p_str->len) + { + return retval; + } + i = p_str->len - text_len; + /* Want to go through loop once even if i==0 */ + while (1) + { + if (vsf_sysutil_memcmp(p_str->p_buf + i, p_text, text_len) == 0) + { + retval.found = 1; + retval.index = i; + return retval; + } + if (i == 0) + { + break; + } + i--; + } + /* Not found */ + return retval; +} + +void +str_left(const struct mystr* p_str, struct mystr* p_out, unsigned int chars) +{ + if (chars > p_str->len) + { + bug("chars invalid in str_left"); + } + private_str_alloc_memchunk(p_out, p_str->p_buf, chars); +} + +void +str_right(const struct mystr* p_str, struct mystr* p_out, unsigned int chars) +{ + unsigned int indexx = p_str->len - chars; + if (chars > p_str->len) + { + bug("chars invalid in str_right"); + } + private_str_alloc_memchunk(p_out, p_str->p_buf + indexx, chars); +} + +void +str_mid_to_end(const struct mystr* p_str, struct mystr* p_out, + unsigned int indexx) +{ + if (indexx > p_str->len) + { + bug("invalid indexx in str_mid_to_end"); + } + private_str_alloc_memchunk(p_out, p_str->p_buf + indexx, + p_str->len - indexx); +} + +char +str_get_char_at(const struct mystr* p_str, const unsigned int indexx) +{ + if (indexx >= p_str->len) + { + bug("bad indexx in str_get_char_at"); + } + return p_str->p_buf[indexx]; +} + +int +str_contains_space(const struct mystr* p_str) +{ + unsigned int i; + for (i=0; i < p_str->len; i++) + { + if (vsf_sysutil_isspace(p_str->p_buf[i])) + { + return 1; + } + } + return 0; +} + +int +str_contains_unprintable(const struct mystr* p_str) +{ + unsigned int i; + for (i=0; i < p_str->len; i++) + { + if (!vsf_sysutil_isprint(p_str->p_buf[i])) + { + return 1; + } + } + return 0; +} + +int +str_atoi(const struct mystr* p_str) +{ + return vsf_sysutil_atoi(str_getbuf(p_str)); +} + +filesize_t +str_a_to_filesize_t(const struct mystr* p_str) +{ + return vsf_sysutil_a_to_filesize_t(str_getbuf(p_str)); +} + +unsigned int +str_octal_to_uint(const struct mystr* p_str) +{ + return vsf_sysutil_octal_to_uint(str_getbuf(p_str)); +} + +int +str_getline(const struct mystr* p_str, struct mystr* p_line_str, + unsigned int* p_pos) +{ + unsigned int start_pos = *p_pos; + unsigned int curr_pos = start_pos; + unsigned int buf_len = str_getlen(p_str); + const char* p_buf = str_getbuf(p_str); + unsigned int out_len; + if (start_pos > buf_len) + { + bug("p_pos out of range in str_getline"); + } + str_empty(p_line_str); + if (start_pos == buf_len) + { + return 0; + } + while (curr_pos < buf_len && p_buf[curr_pos] != '\n') + { + curr_pos++; + } + out_len = curr_pos - start_pos; + /* If we ended on a \n - skip it */ + if (curr_pos < buf_len && p_buf[curr_pos] == '\n') + { + curr_pos++; + } + private_str_alloc_memchunk(p_line_str, p_buf + start_pos, out_len); + *p_pos = curr_pos; + return 1; +} + +int +str_contains_line(const struct mystr* p_str, const struct mystr* p_line_str) +{ + static struct mystr s_curr_line_str; + unsigned int pos = 0; + while (str_getline(p_str, &s_curr_line_str, &pos)) + { + if (str_equal(&s_curr_line_str, p_line_str)) + { + return 1; + } + } + return 0; +} + +void +str_replace_unprintable(struct mystr* p_str, char new_char) +{ + unsigned int i; + for (i=0; i < p_str->len; i++) + { + if (!vsf_sysutil_isprint(p_str->p_buf[i])) + { + p_str->p_buf[i] = new_char; + } + } +} + diff --git a/str.h b/str.h new file mode 100644 index 0000000..2ad46d9 --- /dev/null +++ b/str.h @@ -0,0 +1,126 @@ +#ifndef VSFTP_STR_H +#define VSFTP_STR_H + +/* TODO - document these functions ;-) */ + +#ifndef VSF_FILESIZE_H +#include "filesize.h" +#endif + +#include "cpp_to_c_helpers.h" + +struct mystr +{ + char* PRIVATE_HANDS_OFF_p_buf; + /* Internally, EXCLUDES trailing null */ + unsigned int PRIVATE_HANDS_OFF_len; + unsigned int PRIVATE_HANDS_OFF_alloc_bytes; +}; + +#define INIT_MYSTR \ + { (void*)0, 0, 0 } + +#ifdef VSFTP_STRING_HELPER +#define str_alloc_memchunk private_str_alloc_memchunk +#endif +void private_str_alloc_memchunk(struct mystr* p_str, const char* p_src, + unsigned int len); + +C_COMPATIBLE_FUNCTION void str_alloc_text(struct mystr* p_str, const char* p_src); +/* NOTE: String buffer data does NOT include terminating character */ +void str_alloc_alt_term(struct mystr* p_str, const char* p_src, char term); +void str_alloc_ulong(struct mystr* p_str, unsigned long the_ulong); +void str_alloc_filesize_t(struct mystr* p_str, filesize_t the_filesize); +void str_copy(struct mystr* p_dest, const struct mystr* p_src); +C_COMPATIBLE_FUNCTION const char* str_strdup(const struct mystr* p_str); +void str_empty(struct mystr* p_str); +void str_free(struct mystr* p_str); +void str_trunc(struct mystr* p_str, unsigned int trunc_len); +void str_reserve(struct mystr* p_str, unsigned int res_len); + +int str_isempty(const struct mystr* p_str); +unsigned int str_getlen(const struct mystr* p_str); +C_COMPATIBLE_FUNCTION const char* str_getbuf(const struct mystr* p_str); + +int str_strcmp(const struct mystr* p_str1, const struct mystr* p_str2); +int str_equal(const struct mystr* p_str1, const struct mystr* p_str2); +int str_equal_text(const struct mystr* p_str, const char* p_text); + +void str_append_str(struct mystr* p_str, const struct mystr* p_other); +void str_append_text(struct mystr* p_str, const char* p_src); +void str_append_ulong(struct mystr* p_str, unsigned long the_long); +void str_append_filesize_t(struct mystr* p_str, filesize_t the_filesize); +void str_append_char(struct mystr* p_str, char the_char); +void str_append_double(struct mystr* p_str, double the_double); + +void str_upper(struct mystr* p_str); +void str_rpad(struct mystr* p_str, const unsigned int min_width); +void str_lpad(struct mystr* p_str, const unsigned int min_width); +void str_replace_char(struct mystr* p_str, char from, char to); +void str_replace_text(struct mystr* p_str, const char* p_from, + const char* p_to); + +void str_split_char(struct mystr* p_src, struct mystr* p_rhs, char c); +void str_split_char_reverse(struct mystr* p_src, struct mystr* p_rhs, char c); +void str_split_text(struct mystr* p_src, struct mystr* p_rhs, + const char* p_text); +void str_split_text_reverse(struct mystr* p_src, struct mystr* p_rhs, + const char* p_text); + +struct str_locate_result +{ + int found; + unsigned int index; + char char_found; +}; + +struct str_locate_result str_locate_char( + const struct mystr* p_str, char look_char); +struct str_locate_result str_locate_str( + const struct mystr* p_str, const struct mystr* p_look_str); +struct str_locate_result str_locate_str_reverse( + const struct mystr* p_str, const struct mystr* p_look_str); +struct str_locate_result str_locate_text( + const struct mystr* p_str, const char* p_text); +struct str_locate_result str_locate_text_reverse( + const struct mystr* p_str, const char* p_text); +struct str_locate_result str_locate_chars( + const struct mystr* p_str, const char* p_chars); + +void str_left(const struct mystr* p_str, struct mystr* p_out, + unsigned int chars); +void str_right(const struct mystr* p_str, struct mystr* p_out, + unsigned int chars); +void str_mid_to_end(const struct mystr* p_str, struct mystr* p_out, + unsigned int indexx); + +char str_get_char_at(const struct mystr* p_str, const unsigned int indexx); +int str_contains_space(const struct mystr* p_str); +int str_contains_unprintable(const struct mystr* p_str); +void str_replace_unprintable(struct mystr* p_str, char new_char); +int str_atoi(const struct mystr* p_str); +filesize_t str_a_to_filesize_t(const struct mystr* p_str); +unsigned int str_octal_to_uint(const struct mystr* p_str); + +/* PURPOSE: Extract a line of text (delimited by \n or EOF) from a string + * buffer, starting at character position 'p_pos'. The extracted line will + * not contain the '\n' terminator. + * + * RETURNS: 0 if no more lines are available, 1 otherwise. + * The extracted text line is stored in 'p_line_str', which is + * emptied if there are no more lines. 'p_pos' is updated to point to the + * first character after the end of the line just extracted. + */ +int str_getline(const struct mystr* p_str, struct mystr* p_line_str, + unsigned int* p_pos); + +/* PURPOSE: Detect whether or not a string buffer contains a specific line + * of text (delimited by \n or EOF). + * + * RETURNS: 1 if there is a matching line, 0 otherwise. + */ +int str_contains_line(const struct mystr* p_str, + const struct mystr* p_line_str); + +#endif /* VSFTP_STR_H */ + diff --git a/strlist.c b/strlist.c new file mode 100644 index 0000000..47d7476 --- /dev/null +++ b/strlist.c @@ -0,0 +1,174 @@ +/* + * Part of Very Secure FTPd + * Licence: GPL v2 + * Author: Chris Evans + * strlist.c + */ + +/* Anti-lamer measures deployed, sir! */ +#define PRIVATE_HANDS_OFF_alloc_len alloc_len +#define PRIVATE_HANDS_OFF_list_len list_len +#define PRIVATE_HANDS_OFF_p_nodes p_nodes +#include "strlist.h" + +#include "str.h" +#include "utility.h" +#include "sysutil.h" + +struct mystr_list_node +{ + struct mystr str; + struct mystr sort_key_str; +}; + +/* File locals */ +static struct mystr s_null_str; + +static int sort_compare_func(const void* p1, const void* p2); +static int sort_compare_func_reverse(const void* p1, const void* p2); +static int sort_compare_common(const void* p1, const void* p2, int reverse); + +void +str_list_free(struct mystr_list* p_list) +{ + unsigned int i; + for (i=0; i < p_list->list_len; ++i) + { + str_free(&p_list->p_nodes[i].str); + str_free(&p_list->p_nodes[i].sort_key_str); + } + p_list->list_len = 0; + p_list->alloc_len = 0; + if (p_list->p_nodes) + { + vsf_sysutil_free(p_list->p_nodes); + p_list->p_nodes = 0; + } +} + +int +str_list_get_length(const struct mystr_list* p_list) +{ + return p_list->list_len; +} + +int +str_list_contains_str(const struct mystr_list* p_list, + const struct mystr* p_str) +{ + unsigned int i; + for (i=0; i < p_list->list_len; ++i) + { + if (str_equal(p_str, &p_list->p_nodes[i].str)) + { + return 1; + } + } + return 0; +} + +void +str_list_add(struct mystr_list* p_list, const struct mystr* p_str, + const struct mystr* p_sort_key_str) +{ + struct mystr_list_node* p_node; + /* Expand the node allocation if we have to */ + if (p_list->list_len == p_list->alloc_len) + { + if (p_list->alloc_len == 0) + { + p_list->alloc_len = 32; + p_list->p_nodes = vsf_sysutil_malloc(p_list->alloc_len * + sizeof(struct mystr_list_node)); + } + else + { + p_list->alloc_len *= 2; + p_list->p_nodes = vsf_sysutil_realloc(p_list->p_nodes, + p_list->alloc_len * + sizeof(struct mystr_list_node)); + } + } + p_node = &p_list->p_nodes[p_list->list_len]; + p_node->str = s_null_str; + p_node->sort_key_str = s_null_str; + str_copy(&p_node->str, p_str); + if (p_sort_key_str) + { + str_copy(&p_node->sort_key_str, p_sort_key_str); + } + p_list->list_len++; +} + +void +str_list_sort(struct mystr_list* p_list, int reverse) +{ + if (!reverse) + { + vsf_sysutil_qsort(p_list->p_nodes, p_list->list_len, + sizeof(struct mystr_list_node), sort_compare_func); + } + else + { + vsf_sysutil_qsort(p_list->p_nodes, p_list->list_len, + sizeof(struct mystr_list_node), + sort_compare_func_reverse); + } +} + +static int +sort_compare_func(const void* p1, const void* p2) +{ + return sort_compare_common(p1, p2, 0); +} + +static int +sort_compare_func_reverse(const void* p1, const void* p2) +{ + return sort_compare_common(p1, p2, 1); +} + +static int +sort_compare_common(const void* p1, const void* p2, int reverse) +{ + const struct mystr* p_cmp1; + const struct mystr* p_cmp2; + const struct mystr_list_node* p_node1 = (const struct mystr_list_node*) p1; + const struct mystr_list_node* p_node2 = (const struct mystr_list_node*) p2; + if (!str_isempty(&p_node1->sort_key_str)) + { + p_cmp1 = &p_node1->sort_key_str; + } + else + { + p_cmp1 = &p_node1->str; + } + if (!str_isempty(&p_node2->sort_key_str)) + { + p_cmp2 = &p_node2->sort_key_str; + } + else + { + p_cmp2 = &p_node2->str; + } + + if (reverse) + { + return str_strcmp(p_cmp2, p_cmp1); + } + else + { + return str_strcmp(p_cmp1, p_cmp2); + } +} + +const struct mystr* +str_list_get_pstr(const struct mystr_list* p_list, unsigned int indexx) +{ + if (indexx >= p_list->list_len) + { + bug("indexx out of range in str_list_get_str"); + } + return &p_list->p_nodes[indexx].str; +} + diff --git a/strlist.h b/strlist.h new file mode 100644 index 0000000..2f4e960 --- /dev/null +++ b/strlist.h @@ -0,0 +1,32 @@ +#ifndef VSF_STRLIST_H +#define VSF_STRLIST_H + +/* Forward declarations */ +struct mystr; +struct mystr_list_node; + +struct mystr_list +{ + unsigned int PRIVATE_HANDS_OFF_alloc_len; + unsigned int PRIVATE_HANDS_OFF_list_len; + struct mystr_list_node* PRIVATE_HANDS_OFF_p_nodes; +}; + +#define INIT_STRLIST \ + { 0, 0, (void*)0 } + +void str_list_free(struct mystr_list* p_list); + +void str_list_add(struct mystr_list* p_list, const struct mystr* p_str, + const struct mystr* p_sort_key_str); +void str_list_sort(struct mystr_list* p_list, int reverse); + +int str_list_get_length(const struct mystr_list* p_list); +int str_list_contains_str(const struct mystr_list* p_list, + const struct mystr* p_str); + +const struct mystr* str_list_get_pstr(const struct mystr_list* p_list, + unsigned int indexx); + +#endif /* VSF_STRLIST_H */ + diff --git a/sysdeputil.c b/sysdeputil.c new file mode 100644 index 0000000..4b04120 --- /dev/null +++ b/sysdeputil.c @@ -0,0 +1,1193 @@ +/* + * Part of Very Secure FTPd + * Licence: GPL v2 + * Author: Chris Evans + * sysdeputil.c + * + * Highly system dependent utilities - e.g. authentication, capabilities. + */ + +#include "sysdeputil.h" +#include "str.h" +#include "sysutil.h" +#include "utility.h" +#include "secbuf.h" +#include "defs.h" +#include "tunables.h" +#include "builddefs.h" + +/* For Linux, this adds nothing :-) */ +#include "port/porting_junk.h" + +#if (defined(__FreeBSD__) && __FreeBSD__ >= 3) + #define _FILE_OFFSET_BITS 64 + #define _LARGEFILE_SOURCE 1 + #define _LARGEFILE64_SOURCE 1 +#endif + +/* For INT_MAX */ +#include + +/* For fd passing */ +#include +#include +/* For FreeBSD */ +#include +#include + +/* Configuration.. here are the possibilities */ +#undef VSF_SYSDEP_HAVE_CAPABILITIES +#undef VSF_SYSDEP_HAVE_SETKEEPCAPS +#undef VSF_SYSDEP_HAVE_LINUX_SENDFILE +#undef VSF_SYSDEP_HAVE_FREEBSD_SENDFILE +#undef VSF_SYSDEP_HAVE_HPUX_SENDFILE +#undef VSF_SYSDEP_HAVE_AIX_SENDFILE +#undef VSF_SYSDEP_HAVE_SETPROCTITLE +#undef VSF_SYSDEP_TRY_LINUX_SETPROCTITLE_HACK +#undef VSF_SYSDEP_HAVE_HPUX_SETPROCTITLE +#undef VSF_SYSDEP_HAVE_MAP_ANON +#undef VSF_SYSDEP_NEED_OLD_FD_PASSING +#ifdef VSF_BUILD_PAM + #define VSF_SYSDEP_HAVE_PAM +#endif +#define VSF_SYSDEP_HAVE_SHADOW +#define VSF_SYSDEP_HAVE_USERSHELL +#define VSF_SYSDEP_HAVE_LIBCAP +#define VSF_SYSDEP_HAVE_UTMPX + +#define __USE_GNU +#include + +/* BEGIN config */ +#if defined(__linux__) && !defined(__ia64__) && !defined(__s390__) + #define VSF_SYSDEP_TRY_LINUX_SETPROCTITLE_HACK + #include + #if defined(LINUX_VERSION_CODE) && defined(KERNEL_VERSION) + #if (LINUX_VERSION_CODE >= KERNEL_VERSION(2,2,0)) + #define VSF_SYSDEP_HAVE_CAPABILITIES + #define VSF_SYSDEP_HAVE_LINUX_SENDFILE + #include + #ifdef PR_SET_KEEPCAPS + #define VSF_SYSDEP_HAVE_SETKEEPCAPS + #endif + #endif + #endif +#endif + +#if (defined(__FreeBSD__) && __FreeBSD__ >= 3) + #define VSF_SYSDEP_HAVE_FREEBSD_SENDFILE + #define VSF_SYSDEP_HAVE_SETPROCTITLE +#endif + +#if defined(__NetBSD__) + #include + #define VSF_SYSDEP_HAVE_SETPROCTITLE + #include + #if __NetBSD_Version__ >= 106070000 + #define WTMPX_FILE _PATH_WTMPX + #else + #undef VSF_SYSDEP_HAVE_UTMPX + #endif +#endif + +#ifdef __hpux + #include + #ifdef SF_DISCONNECT + #define VSF_SYSDEP_HAVE_HPUX_SENDFILE + #endif + #include + #include + #ifdef PSTAT_SETCMD + #define VSF_SYSDEP_HAVE_HPUX_SETPROCTITLE + #endif + #undef VSF_SYSDEP_HAVE_UTMPX +#endif + +#include +#include +#ifdef MAP_ANON + #define VSF_SYSDEP_HAVE_MAP_ANON +#endif + +#ifdef __sgi + #undef VSF_SYSDEP_HAVE_USERSHELL + #undef VSF_SYSDEP_HAVE_LIBCAP +#endif + +#ifdef _AIX + #undef VSF_SYSDEP_HAVE_USERSHELL + #undef VSF_SYSDEP_HAVE_LIBCAP + #undef VSF_SYSDEP_HAVE_UTMPX + #undef VSF_SYSDEP_HAVE_PAM + #undef VSF_SYSDEP_HAVE_SHADOW + #undef VSF_SYSDEP_HAVE_SETPROCTITLE + #define VSF_SYSDEP_HAVE_AIX_SENDFILE + #define VSF_SYSDEP_TRY_LINUX_SETPROCTITLE_HACK + #define VSF_SYSDEP_HAVE_MAP_ANON +#endif + +#ifdef __osf__ + #undef VSF_SYSDEP_HAVE_USERSHELL +#endif + +#if (defined(__sgi) || defined(__hpux) || defined(__osf__)) + #define VSF_SYSDEP_NEED_OLD_FD_PASSING +#endif + +#ifdef __sun + #define VSF_SYSDEP_HAVE_SOLARIS_SENDFILE +#endif +/* END config */ + +/* PAM support - we include our own dummy version if the system lacks this */ +#include + +/* No PAM? Try getspnam() with a getpwnam() fallback */ +#ifndef VSF_SYSDEP_HAVE_PAM +/* This may hit our own "dummy" include and undef VSF_SYSDEP_HAVE_SHADOW */ +#include +#include +#include +#include +#endif + +/* Prefer libcap based capabilities over raw syscall capabilities */ +#include + +#if defined(VSF_SYSDEP_HAVE_CAPABILITIES) && !defined(VSF_SYSDEP_HAVE_LIBCAP) +#include +#include +#include +#include +_syscall2(int, capset, cap_user_header_t, header, const cap_user_data_t, data); +/* Gross HACK to avoid warnings - linux headers overlap glibc headers */ +#undef __NFDBITS +#undef __FDMASK +#endif /* VSF_SYSDEP_HAVE_CAPABILITIES */ + +#if defined(VSF_SYSDEP_HAVE_LINUX_SENDFILE) || \ + defined(VSF_SYSDEP_HAVE_SOLARIS_SENDFILE) +#include +#elif defined(VSF_SYSDEP_HAVE_FREEBSD_SENDFILE) +#include +#include +#elif defined(VSF_SYSDEP_HAVE_HPUX_SENDFILE) +#include +#else /* VSF_SYSDEP_HAVE_LINUX_SENDFILE */ +#include +#endif /* VSF_SYSDEP_HAVE_LINUX_SENDFILE */ + +#ifdef VSF_SYSDEP_HAVE_SETPROCTITLE +#include +#include +#endif + +#ifdef VSF_SYSDEP_TRY_LINUX_SETPROCTITLE_HACK +extern char** environ; +static unsigned int s_proctitle_space = 0; +static int s_proctitle_inited = 0; +static char* s_p_proctitle = 0; +#endif + +#ifndef VSF_SYSDEP_HAVE_MAP_ANON +#include +#include +#include +static int s_zero_fd = -1; +#endif + +/* File private functions/variables */ +static int do_sendfile(const int out_fd, const int in_fd, + unsigned int num_send, filesize_t start_pos); +static void vsf_sysutil_setproctitle_internal(const char* p_text); +static struct mystr s_proctitle_prefix_str; + +/* These two aren't static to avoid OpenBSD build warnings. */ +void vsf_insert_uwtmp(const struct mystr* p_user_str, + const struct mystr* p_host_str); +void vsf_remove_uwtmp(void); + +#ifndef VSF_SYSDEP_HAVE_PAM +int +vsf_sysdep_check_auth(const struct mystr* p_user_str, + const struct mystr* p_pass_str, + const struct mystr* p_remote_host) +{ + const char* p_crypted; + const struct passwd* p_pwd = getpwnam(str_getbuf(p_user_str)); + (void) p_remote_host; + if (p_pwd == NULL) + { + return 0; + } + #ifdef VSF_SYSDEP_HAVE_USERSHELL + if (tunable_check_shell) + { + const char* p_shell; + while ((p_shell = getusershell()) != NULL) + { + if (!vsf_sysutil_strcmp(p_shell, p_pwd->pw_shell)) + { + break; + } + } + endusershell(); + if (p_shell == NULL) + { + return 0; + } + } + #endif + #ifdef VSF_SYSDEP_HAVE_SHADOW + { + const struct spwd* p_spwd = getspnam(str_getbuf(p_user_str)); + if (p_spwd != NULL) + { + long curr_time; + int days; + vsf_sysutil_update_cached_time(); + curr_time = vsf_sysutil_get_cached_time_sec(); + days = curr_time / (60 * 60 * 24); + if (p_spwd->sp_expire > 0 && p_spwd->sp_expire < days) + { + return 0; + } + if (p_spwd->sp_lstchg > 0 && p_spwd->sp_max > 0 && + p_spwd->sp_lstchg + p_spwd->sp_max < days) + { + return 0; + } + p_crypted = crypt(str_getbuf(p_pass_str), p_spwd->sp_pwdp); + if (!vsf_sysutil_strcmp(p_crypted, p_spwd->sp_pwdp)) + { + return 1; + } + } + } + #endif /* VSF_SYSDEP_HAVE_SHADOW */ + p_crypted = crypt(str_getbuf(p_pass_str), p_pwd->pw_passwd); + if (!vsf_sysutil_strcmp(p_crypted, p_pwd->pw_passwd)) + { + return 1; + } + return 0; +} + +#else /* VSF_SYSDEP_HAVE_PAM */ + +static pam_handle_t* s_pamh; +static struct mystr s_pword_str; +static int pam_conv_func(int nmsg, const struct pam_message** p_msg, + struct pam_response** p_reply, void* p_addata); +static void vsf_auth_shutdown(void); + +int +vsf_sysdep_check_auth(const struct mystr* p_user_str, + const struct mystr* p_pass_str, + const struct mystr* p_remote_host) +{ + int retval; + struct pam_conv the_conv = + { + &pam_conv_func, + 0 + }; + if (s_pamh != 0) + { + bug("vsf_sysdep_check_auth"); + } + str_copy(&s_pword_str, p_pass_str); + retval = pam_start(tunable_pam_service_name, + str_getbuf(p_user_str), &the_conv, &s_pamh); + if (retval != PAM_SUCCESS) + { + s_pamh = 0; + return 0; + } +#ifdef PAM_RHOST + retval = pam_set_item(s_pamh, PAM_RHOST, str_getbuf(p_remote_host)); + if (retval != PAM_SUCCESS) + { + (void) pam_end(s_pamh, 0); + s_pamh = 0; + return 0; + } +#endif +#ifdef PAM_TTY + retval = pam_set_item(s_pamh, PAM_TTY, "ftp"); + if (retval != PAM_SUCCESS) + { + (void) pam_end(s_pamh, 0); + s_pamh = 0; + return 0; + } +#endif +#ifdef PAM_RUSER + retval = pam_set_item(s_pamh, PAM_RUSER, str_getbuf(p_user_str)); + if (retval != PAM_SUCCESS) + { + (void) pam_end(s_pamh, 0); + s_pamh = 0; + return 0; + } +#endif + retval = pam_authenticate(s_pamh, 0); + if (retval != PAM_SUCCESS) + { + (void) pam_end(s_pamh, 0); + s_pamh = 0; + return 0; + } + retval = pam_acct_mgmt(s_pamh, 0); + if (retval != PAM_SUCCESS) + { + (void) pam_end(s_pamh, 0); + s_pamh = 0; + return 0; + } + retval = pam_setcred(s_pamh, PAM_ESTABLISH_CRED); + if (retval != PAM_SUCCESS) + { + (void) pam_end(s_pamh, 0); + s_pamh = 0; + return 0; + } + if (!tunable_session_support) + { + /* You're in already! */ + (void) pam_end(s_pamh, 0); + s_pamh = 0; + return 1; + } + /* Must do this BEFORE opening a session for pam_limits to count us */ + vsf_insert_uwtmp(p_user_str, p_remote_host); + retval = pam_open_session(s_pamh, 0); + if (retval != PAM_SUCCESS) + { + vsf_remove_uwtmp(); + (void) pam_setcred(s_pamh, PAM_DELETE_CRED); + (void) pam_end(s_pamh, 0); + s_pamh = 0; + return 0; + } + /* We MUST ensure the PAM session, utmp, wtmp etc. are cleaned up, however + * we exit. + */ + vsf_sysutil_set_exit_func(vsf_auth_shutdown); + /* You're in dude */ + return 1; +} + +static void +vsf_auth_shutdown(void) +{ + if (s_pamh == 0) + { + bug("vsf_auth_shutdown"); + } + (void) pam_close_session(s_pamh, 0); + (void) pam_setcred(s_pamh, PAM_DELETE_CRED); + (void) pam_end(s_pamh, 0); + s_pamh = 0; + vsf_remove_uwtmp(); +} + +static int +pam_conv_func(int nmsg, const struct pam_message** p_msg, + struct pam_response** p_reply, void* p_addata) +{ + int i; + struct pam_response* p_resps = 0; + (void) p_addata; + if (nmsg < 0) + { + bug("dodgy nmsg in pam_conv_func"); + } + p_resps = vsf_sysutil_malloc(sizeof(struct pam_response) * nmsg); + for (i=0; imsg_style) + { + case PAM_PROMPT_ECHO_OFF: + p_resps[i].resp_retcode = PAM_SUCCESS; + p_resps[i].resp = (char*) str_strdup(&s_pword_str); + break; + case PAM_TEXT_INFO: + case PAM_ERROR_MSG: + p_resps[i].resp_retcode = PAM_SUCCESS; + p_resps[i].resp = 0; + break; + case PAM_PROMPT_ECHO_ON: + default: + vsf_sysutil_free(p_resps); + return PAM_CONV_ERR; + break; + } + } + *p_reply = p_resps; + return PAM_SUCCESS; +} + +#endif /* VSF_SYSDEP_HAVE_PAM */ + +/* Capabilities support (or lack thereof) */ +void +vsf_sysdep_keep_capabilities(void) +{ + if (!vsf_sysdep_has_capabilities_as_non_root()) + { + bug("asked to keep capabilities, but no support exists"); + } +#ifdef VSF_SYSDEP_HAVE_SETKEEPCAPS + { + int retval = prctl(PR_SET_KEEPCAPS, 1); + if (vsf_sysutil_retval_is_error(retval)) + { + die("prctl"); + } + } +#endif /* VSF_SYSDEP_HAVE_SETKEEPCAPS */ +} +#if !defined(VSF_SYSDEP_HAVE_CAPABILITIES) && !defined(VSF_SYSDEP_HAVE_LIBCAP) + +int +vsf_sysdep_has_capabilities(void) +{ + return 0; +} + +int +vsf_sysdep_has_capabilities_as_non_root(void) +{ + return 0; +} + +void +vsf_sysdep_adopt_capabilities(unsigned int caps) +{ + (void) caps; + bug("asked to adopt capabilities, but no support exists"); +} + +#else /* VSF_SYSDEP_HAVE_CAPABILITIES || VSF_SYSDEP_HAVE_LIBCAP */ + +static int do_checkcap(void); + +int +vsf_sysdep_has_capabilities_as_non_root(void) +{ + static int s_prctl_checked; + static int s_runtime_prctl_works; + if (!s_prctl_checked) + { + #ifdef VSF_SYSDEP_HAVE_SETKEEPCAPS + /* Clarity: note embedded call to prctl() syscall */ + if (!vsf_sysutil_retval_is_error(prctl(PR_SET_KEEPCAPS, 0))) + { + s_runtime_prctl_works = 1; + } + #endif /* VSF_SYSDEP_HAVE_SETKEEPCAPS */ + s_prctl_checked = 1; + } + return s_runtime_prctl_works; +} + +int +vsf_sysdep_has_capabilities(void) +{ + /* Even though compiled with capabilities, the runtime system may lack them. + * Also, RH7.0 kernel headers advertise a 2.4.0 box, but on a 2.2.x kernel! + */ + static int s_caps_checked; + static int s_runtime_has_caps; + if (!s_caps_checked) + { + s_runtime_has_caps = do_checkcap(); + s_caps_checked = 1; + } + return s_runtime_has_caps; +} + + #ifndef VSF_SYSDEP_HAVE_LIBCAP +static int +do_checkcap(void) +{ + /* EFAULT (EINVAL if page 0 mapped) vs. ENOSYS */ + int retval = capset(0, 0); + if (!vsf_sysutil_retval_is_error(retval) || + vsf_sysutil_get_error() != kVSFSysUtilErrNOSYS) + { + return 1; + } + return 0; +} + +void +vsf_sysdep_adopt_capabilities(unsigned int caps) +{ + /* n.b. yes I know I should be using libcap!! */ + int retval; + struct __user_cap_header_struct cap_head; + struct __user_cap_data_struct cap_data; + __u32 cap_mask = 0; + if (!caps) + { + bug("asked to adopt no capabilities"); + } + vsf_sysutil_memclr(&cap_head, sizeof(cap_head)); + vsf_sysutil_memclr(&cap_data, sizeof(cap_data)); + cap_head.version = _LINUX_CAPABILITY_VERSION; + cap_head.pid = 0; + if (caps & kCapabilityCAP_CHOWN) + { + cap_mask |= (1 << CAP_CHOWN); + } + if (caps & kCapabilityCAP_NET_BIND_SERVICE) + { + cap_mask |= (1 << CAP_NET_BIND_SERVICE); + } + cap_data.effective = cap_data.permitted = cap_mask; + cap_data.inheritable = 0; + retval = capset(&cap_head, &cap_data); + if (retval != 0) + { + die("capset"); + } +} + + #else /* VSF_SYSDEP_HAVE_LIBCAP */ +static int +do_checkcap(void) +{ + cap_t current_caps = cap_get_proc(); + cap_free(current_caps); + if (current_caps != NULL) + { + return 1; + } + return 0; +} + +void +vsf_sysdep_adopt_capabilities(unsigned int caps) +{ + int retval; + cap_value_t cap_value; + cap_t adopt_caps = cap_init(); + if (caps & kCapabilityCAP_CHOWN) + { + cap_value = CAP_CHOWN; + cap_set_flag(adopt_caps, CAP_EFFECTIVE, 1, &cap_value, CAP_SET); + cap_set_flag(adopt_caps, CAP_PERMITTED, 1, &cap_value, CAP_SET); + } + if (caps & kCapabilityCAP_NET_BIND_SERVICE) + { + cap_value = CAP_NET_BIND_SERVICE; + cap_set_flag(adopt_caps, CAP_EFFECTIVE, 1, &cap_value, CAP_SET); + cap_set_flag(adopt_caps, CAP_PERMITTED, 1, &cap_value, CAP_SET); + } + retval = cap_set_proc(adopt_caps); + if (retval != 0) + { +#warning LHXLHX LHX + die("cap_set_proc"); + } + cap_free(adopt_caps); +} + + #endif /* !VSF_SYSDEP_HAVE_LIBCAP */ +#endif /* VSF_SYSDEP_HAVE_CAPABILITIES || VSF_SYSDEP_HAVE_LIBCAP */ + +int +vsf_sysutil_sendfile(const int out_fd, const int in_fd, + filesize_t* p_offset, filesize_t num_send, + unsigned int max_chunk) +{ + /* Grr - why is off_t signed? */ + if (*p_offset < 0 || num_send < 0) + { + die("invalid offset or send count in vsf_sysutil_sendfile"); + } + if (max_chunk == 0) + { + max_chunk = INT_MAX; + } + while (num_send > 0) + { + int retval; + unsigned int send_this_time; + if (num_send > max_chunk) + { + send_this_time = max_chunk; + } + else + { + send_this_time = (unsigned int) num_send; + } + /* Keep input file position in line with sendfile() calls */ + vsf_sysutil_lseek_to(in_fd, *p_offset); + retval = do_sendfile(out_fd, in_fd, send_this_time, *p_offset); + if (vsf_sysutil_retval_is_error(retval) || retval == 0) + { + return retval; + } + num_send -= retval; + *p_offset += retval; + } + return 0; +} + +static int do_sendfile(const int out_fd, const int in_fd, + unsigned int num_send, filesize_t start_pos) +{ + /* Probably should one day be shared with instance in ftpdataio.c */ + static char* p_recvbuf; + unsigned int total_written = 0; + int retval; + enum EVSFSysUtilError error; + (void) start_pos; + (void) error; +#if defined(VSF_SYSDEP_HAVE_LINUX_SENDFILE) || \ + defined(VSF_SYSDEP_HAVE_FREEBSD_SENDFILE) || \ + defined(VSF_SYSDEP_HAVE_HPUX_SENDFILE) || \ + defined(VSF_SYSDEP_HAVE_AIX_SENDFILE) || \ + defined(VSF_SYSDEP_HAVE_SOLARIS_SENDFILE) + if (tunable_use_sendfile) + { + static int s_sendfile_checked; + static int s_runtime_sendfile_works; + if (!s_sendfile_checked || s_runtime_sendfile_works) + { + do + { + #ifdef VSF_SYSDEP_HAVE_LINUX_SENDFILE + retval = sendfile(out_fd, in_fd, NULL, num_send); + #elif defined(VSF_SYSDEP_HAVE_FREEBSD_SENDFILE) + { + /* XXX - start_pos will truncate on 32-bit machines - can we + * say "start from current pos"? + */ + off_t written = 0; + retval = sendfile(in_fd, out_fd, start_pos, num_send, NULL, + &written, 0); + /* Translate to Linux-like retval */ + if (written > 0) + { + retval = (int) written; + } + } + #elif defined(VSF_SYSDEP_HAVE_SOLARIS_SENDFILE) + { + size_t written = 0; + struct sendfilevec the_vec; + vsf_sysutil_memclr(&the_vec, sizeof(the_vec)); + the_vec.sfv_fd = in_fd; + the_vec.sfv_off = start_pos; + the_vec.sfv_len = num_send; + retval = sendfilev(out_fd, &the_vec, 1, &written); + /* Translate to Linux-like retval */ + if (written > 0) + { + retval = (int) written; + } + } + #elif defined(VSF_SYSDEP_HAVE_AIX_SENDFILE) + { + struct sf_parms sf_iobuf; + vsf_sysutil_memclr(&sf_iobuf, sizeof(sf_iobuf)); + sf_iobuf.header_data = NULL; + sf_iobuf.header_length = 0; + sf_iobuf.trailer_data = NULL; + sf_iobuf.trailer_length = 0; + sf_iobuf.file_descriptor = in_fd; + sf_iobuf.file_offset = start_pos; + sf_iobuf.file_bytes = num_send; + + retval = send_file((int*)&out_fd, &sf_iobuf, 0); + if (retval >= 0) + { + retval = sf_iobuf.bytes_sent; + } + } + #else /* must be VSF_SYSDEP_HAVE_HPUX_SENDFILE */ + { + retval = sendfile(out_fd, in_fd, start_pos, num_send, NULL, 0); + } + #endif /* VSF_SYSDEP_HAVE_LINUX_SENDFILE */ + error = vsf_sysutil_get_error(); + vsf_sysutil_check_pending_actions(kVSFSysUtilIO, retval, out_fd); + } + while (vsf_sysutil_retval_is_error(retval) && + error == kVSFSysUtilErrINTR); + if (!s_sendfile_checked) + { + s_sendfile_checked = 1; + if (!vsf_sysutil_retval_is_error(retval) || + error != kVSFSysUtilErrNOSYS) + { + s_runtime_sendfile_works = 1; + } + } + if (!vsf_sysutil_retval_is_error(retval)) + { + return retval; + } + if (s_runtime_sendfile_works && error != kVSFSysUtilErrINVAL && + error != kVSFSysUtilErrOPNOTSUPP) + { + return retval; + } + /* Fall thru to normal implementation. We won't check again. NOTE - + * also falls through if sendfile() is OK but it returns EINVAL. For + * Linux this means the file was not page cache backed. Original + * complaint was trying to serve files from an NTFS filesystem! + */ + } + } +#endif /* VSF_SYSDEP_HAVE_LINUX_SENDFILE || VSF_SYSDEP_HAVE_FREEBSD_SENDFILE */ + if (p_recvbuf == 0) + { + vsf_secbuf_alloc(&p_recvbuf, VSFTP_DATA_BUFSIZE); + } + while (1) + { + unsigned int num_read; + unsigned int num_written; + unsigned int num_read_this_time = VSFTP_DATA_BUFSIZE; + if (num_read_this_time > num_send) + { + num_read_this_time = num_send; + } + retval = vsf_sysutil_read(in_fd, p_recvbuf, num_read_this_time); + if (retval < 0) + { + return retval; + } + else if (retval == 0) + { + return -1; + } + num_read = (unsigned int) retval; + retval = vsf_sysutil_write_loop(out_fd, p_recvbuf, num_read); + if (retval < 0) + { + return retval; + } + num_written = (unsigned int) retval; + total_written += num_written; + if (num_written != num_read) + { + return num_written; + } + if (num_written > num_send) + { + bug("num_written bigger than num_send in do_sendfile"); + } + num_send -= num_written; + if (num_send == 0) + { + /* Bingo! */ + return total_written; + } + } +} + +void +vsf_sysutil_set_proctitle_prefix(const struct mystr* p_str) +{ + str_copy(&s_proctitle_prefix_str, p_str); +} + +/* This delegation is common to all setproctitle() implementations */ +void +vsf_sysutil_setproctitle_str(const struct mystr* p_str) +{ + vsf_sysutil_setproctitle(str_getbuf(p_str)); +} + +void +vsf_sysutil_setproctitle(const char* p_text) +{ + struct mystr proctitle_str = INIT_MYSTR; + str_copy(&proctitle_str, &s_proctitle_prefix_str); + if (!str_isempty(&proctitle_str)) + { + str_append_text(&proctitle_str, ": "); + } + str_append_text(&proctitle_str, p_text); + vsf_sysutil_setproctitle_internal(str_getbuf(&proctitle_str)); + str_free(&proctitle_str); +} + +#ifdef VSF_SYSDEP_HAVE_SETPROCTITLE +void +vsf_sysutil_setproctitle_init(int argc, const char* argv[]) +{ + (void) argc; + (void) argv; +} + +void +vsf_sysutil_setproctitle_internal(const char* p_buf) +{ + setproctitle("%s", p_buf); +} +#elif defined(VSF_SYSDEP_HAVE_HPUX_SETPROCTITLE) +void +vsf_sysutil_setproctitle_init(int argc, const char* argv[]) +{ + (void) argc; + (void) argv; +} + +void +vsf_sysutil_setproctitle_internal(const char* p_buf) +{ + struct mystr proctitle_str = INIT_MYSTR; + union pstun p; + str_alloc_text(&proctitle_str, "vsftpd: "); + str_append_text(&proctitle_str, p_buf); + p.pst_command = str_getbuf(&proctitle_str); + pstat(PSTAT_SETCMD, p, 0, 0, 0); + str_free(&proctitle_str); +} +#elif defined(VSF_SYSDEP_TRY_LINUX_SETPROCTITLE_HACK) +void +vsf_sysutil_setproctitle_init(int argc, const char* argv[]) +{ + int i; + char** p_env = environ; + if (s_proctitle_inited) + { + bug("vsf_sysutil_setproctitle_init called twice"); + } + s_proctitle_inited = 1; + if (argv[0] == 0) + { + die("no argv[0] in vsf_sysutil_setproctitle_init"); + } + for (i=0; i 0) + { + argv[i] = 0; + } + } + while (*p_env != 0) + { + s_proctitle_space += vsf_sysutil_strlen(*p_env) + 1; + p_env++; + } + /* Oops :-) */ + environ = 0; + s_p_proctitle = (char*) argv[0]; + vsf_sysutil_memclr(s_p_proctitle, s_proctitle_space); +} + +void +vsf_sysutil_setproctitle_internal(const char* p_buf) +{ + struct mystr proctitle_str = INIT_MYSTR; + unsigned int to_copy; + if (!s_proctitle_inited) + { + bug("vsf_sysutil_setproctitle: not initialized"); + } + vsf_sysutil_memclr(s_p_proctitle, s_proctitle_space); + if (s_proctitle_space < 32) + { + return; + } + str_alloc_text(&proctitle_str, "vsftpd: "); + str_append_text(&proctitle_str, p_buf); + to_copy = str_getlen(&proctitle_str); + if (to_copy > s_proctitle_space - 1) + { + to_copy = s_proctitle_space - 1; + } + vsf_sysutil_memcpy(s_p_proctitle, str_getbuf(&proctitle_str), to_copy); + str_free(&proctitle_str); + s_p_proctitle[to_copy] = '\0'; +} +#else /* VSF_SYSDEP_HAVE_SETPROCTITLE */ +void +vsf_sysutil_setproctitle_init(int argc, const char* argv[]) +{ + (void) argc; + (void) argv; +} + +void +vsf_sysutil_setproctitle_internal(const char* p_buf) +{ + (void) p_buf; +} +#endif /* VSF_SYSDEP_HAVE_SETPROCTITLE */ + +#ifdef VSF_SYSDEP_HAVE_MAP_ANON +void +vsf_sysutil_map_anon_pages_init(void) +{ +} + +void* +vsf_sysutil_map_anon_pages(unsigned int length) +{ + char* retval = mmap(0, length, PROT_READ | PROT_WRITE, + MAP_PRIVATE | MAP_ANON, -1, 0); + if (retval == MAP_FAILED) + { + die("mmap"); + } + return retval; +} +#else /* VSF_SYSDEP_HAVE_MAP_ANON */ +void +vsf_sysutil_map_anon_pages_init(void) +{ + if (s_zero_fd != -1) + { + bug("vsf_sysutil_map_anon_pages_init called twice"); + } + s_zero_fd = open("/dev/zero", O_RDWR); + if (s_zero_fd < 0) + { + die("could not open /dev/zero"); + } +} + +void* +vsf_sysutil_map_anon_pages(unsigned int length) +{ + char* retval = mmap(0, length, PROT_READ | PROT_WRITE, + MAP_PRIVATE, s_zero_fd, 0); + if (retval == MAP_FAILED) + { + die("mmap"); + } + return retval; +} +#endif /* VSF_SYSDEP_HAVE_MAP_ANON */ + +#ifndef VSF_SYSDEP_NEED_OLD_FD_PASSING + +void +vsf_sysutil_send_fd(int sock_fd, int send_fd) +{ + int retval; + struct msghdr msg; + struct cmsghdr* p_cmsg; + struct iovec vec; + char cmsgbuf[CMSG_SPACE(sizeof(send_fd))]; + int* p_fds; + char sendchar = 0; + msg.msg_control = cmsgbuf; + msg.msg_controllen = sizeof(cmsgbuf); + p_cmsg = CMSG_FIRSTHDR(&msg); + p_cmsg->cmsg_level = SOL_SOCKET; + p_cmsg->cmsg_type = SCM_RIGHTS; + p_cmsg->cmsg_len = CMSG_LEN(sizeof(send_fd)); + p_fds = (int*)CMSG_DATA(p_cmsg); + *p_fds = send_fd; + msg.msg_controllen = p_cmsg->cmsg_len; + msg.msg_name = NULL; + msg.msg_namelen = 0; + msg.msg_iov = &vec; + msg.msg_iovlen = 1; + msg.msg_flags = 0; + /* "To pass file descriptors or credentials you need to send/read at + * least on byte" (man 7 unix) + */ + vec.iov_base = &sendchar; + vec.iov_len = sizeof(sendchar); + retval = sendmsg(sock_fd, &msg, 0); + if (retval != 1) + { + die("sendmsg"); + } +} + +int +vsf_sysutil_recv_fd(const int sock_fd) +{ + int retval; + struct msghdr msg; + char recvchar; + struct iovec vec; + int recv_fd; + char cmsgbuf[CMSG_SPACE(sizeof(recv_fd))]; + struct cmsghdr* p_cmsg; + int* p_fd; + vec.iov_base = &recvchar; + vec.iov_len = sizeof(recvchar); + msg.msg_name = NULL; + msg.msg_namelen = 0; + msg.msg_iov = &vec; + msg.msg_iovlen = 1; + msg.msg_control = cmsgbuf; + msg.msg_controllen = sizeof(cmsgbuf); + msg.msg_flags = 0; + /* In case something goes wrong, set the fd to -1 before the syscall */ + p_fd = (int*)CMSG_DATA(CMSG_FIRSTHDR(&msg)); + *p_fd = -1; + retval = recvmsg(sock_fd, &msg, 0); + if (retval != 1) + { + die("recvmsg"); + } + p_cmsg = CMSG_FIRSTHDR(&msg); + if (p_cmsg == NULL) + { + die("no passed fd"); + } + /* We used to verify the returned cmsg_level, cmsg_type and cmsg_len here, + * but Linux 2.0 totally uselessly fails to fill these in. + */ + p_fd = (int*)CMSG_DATA(p_cmsg); + recv_fd = *p_fd; + if (recv_fd == -1) + { + die("no passed fd"); + } + return recv_fd; +} + +#else /* !VSF_SYSDEP_NEED_OLD_FD_PASSING */ + +void +vsf_sysutil_send_fd(int sock_fd, int send_fd) +{ + int retval; + char send_char = 0; + struct msghdr msg; + struct iovec vec; + vec.iov_base = &send_char; + vec.iov_len = 1; + msg.msg_name = NULL; + msg.msg_namelen = 0; + msg.msg_iov = &vec; + msg.msg_iovlen = 1; + msg.msg_accrights = (caddr_t) &send_fd; + msg.msg_accrightslen = sizeof(send_fd); + retval = sendmsg(sock_fd, &msg, 0); + if (retval != 1) + { + die("sendmsg"); + } +} + +int +vsf_sysutil_recv_fd(int sock_fd) +{ + int retval; + struct msghdr msg; + struct iovec vec; + char recv_char; + int recv_fd = -1; + vec.iov_base = &recv_char; + vec.iov_len = 1; + msg.msg_name = NULL; + msg.msg_namelen = 0; + msg.msg_iov = &vec; + msg.msg_iovlen = 1; + msg.msg_accrights = (caddr_t) &recv_fd; + msg.msg_accrightslen = sizeof(recv_fd); + retval = recvmsg(sock_fd, &msg, 0); + if (retval != 1) + { + die("recvmsg"); + } + if (recv_fd == -1) + { + die("no passed fd"); + } + return recv_fd; +} + +#endif /* !VSF_SYSDEP_NEED_OLD_FD_PASSING */ + +#ifndef VSF_SYSDEP_HAVE_UTMPX + +void +vsf_insert_uwtmp(const struct mystr* p_user_str, + const struct mystr* p_host_str) +{ + (void) p_user_str; + (void) p_host_str; +} + +void +vsf_remove_uwtmp(void) +{ +} + +#else /* !VSF_SYSDEP_HAVE_UTMPX */ + +/* IMHO, the pam_unix module REALLY should be doing this in its SM component */ +/* Statics */ +static int s_uwtmp_inserted; +static struct utmpx s_utent; + +void +vsf_insert_uwtmp(const struct mystr* p_user_str, + const struct mystr* p_host_str) +{ + if (sizeof(s_utent.ut_line) < 16) + { + return; + } + if (s_uwtmp_inserted) + { + bug("vsf_insert_uwtmp"); + } + { + struct mystr line_str = INIT_MYSTR; + str_alloc_text(&line_str, "vsftpd:"); + str_append_ulong(&line_str, vsf_sysutil_getpid()); + if (str_getlen(&line_str) >= sizeof(s_utent.ut_line)) + { + str_free(&line_str); + return; + } + vsf_sysutil_strcpy(s_utent.ut_line, str_getbuf(&line_str), + sizeof(s_utent.ut_line)); + str_free(&line_str); + } + s_uwtmp_inserted = 1; + s_utent.ut_type = USER_PROCESS; + s_utent.ut_pid = vsf_sysutil_getpid(); + vsf_sysutil_strcpy(s_utent.ut_user, str_getbuf(p_user_str), + sizeof(s_utent.ut_user)); + vsf_sysutil_strcpy(s_utent.ut_host, str_getbuf(p_host_str), + sizeof(s_utent.ut_host)); + vsf_sysutil_update_cached_time(); + s_utent.ut_tv.tv_sec = vsf_sysutil_get_cached_time_sec(); + setutxent(); + (void) pututxline(&s_utent); + endutxent(); + updwtmpx(WTMPX_FILE, &s_utent); +} + +void +vsf_remove_uwtmp(void) +{ + if (!s_uwtmp_inserted) + { + return; + } + s_uwtmp_inserted = 0; + s_utent.ut_type = DEAD_PROCESS; + vsf_sysutil_memclr(s_utent.ut_user, sizeof(s_utent.ut_user)); + vsf_sysutil_memclr(s_utent.ut_host, sizeof(s_utent.ut_host)); + s_utent.ut_tv.tv_sec = 0; + setutxent(); + (void) pututxline(&s_utent); + endutxent(); + vsf_sysutil_update_cached_time(); + s_utent.ut_tv.tv_sec = vsf_sysutil_get_cached_time_sec(); + updwtmpx(WTMPX_FILE, &s_utent); +} + +#endif /* !VSF_SYSDEP_HAVE_UTMPX */ + diff --git a/sysdeputil.h b/sysdeputil.h new file mode 100644 index 0000000..b19bfa3 --- /dev/null +++ b/sysdeputil.h @@ -0,0 +1,59 @@ +#ifndef VSF_SYSDEPUTIL_H +#define VSF_SYSDEPUTIL_H + +#ifndef VSF_FILESIZE_H +#include "filesize.h" +#endif + +/* VSF_SYSDEPUTIL_H: + * Support for highly system dependent features, and querying for support + * or lack thereof + * TODO: document functions! + */ + +struct mystr; + +/* Authentication of local users */ +/* Return 0 for fail, 1 for success */ +int vsf_sysdep_check_auth(const struct mystr* p_user, + const struct mystr* p_pass, + const struct mystr* p_remote_host); + +/* Support for fine grained privilege (capabilities) */ +int vsf_sysdep_has_capabilities(void); +int vsf_sysdep_has_capabilities_as_non_root(void); +void vsf_sysdep_keep_capabilities(void); +enum ESysdepCapabilities +{ + kCapabilityCAP_CHOWN = 1, + kCapabilityCAP_NET_BIND_SERVICE = 2 + /* NOTE - next one will be 4, this is a bitfield */ +}; +void vsf_sysdep_adopt_capabilities(unsigned int caps); + +/* Support for sendfile(), Linux-like interface. Collapses to a read/write + * loop under the covers if the target system lacks support. + */ +int vsf_sysutil_sendfile(const int out_fd, const int in_fd, + filesize_t* p_offset, filesize_t num_send, + unsigned int max_chunk); + +/* Support for changing the process name as reported by the operating system. + * A useful status monitor. NOTE - we don't guarantee that this call will + * have any effect. + */ +void vsf_sysutil_setproctitle_init(int argc, const char* argv[]); +void vsf_sysutil_setproctitle(const char* p_text); +void vsf_sysutil_setproctitle_str(const struct mystr* p_str); +void vsf_sysutil_set_proctitle_prefix(const struct mystr* p_str); + +/* For now, maps read/write private pages. API to be extended.. */ +void vsf_sysutil_map_anon_pages_init(void); +void* vsf_sysutil_map_anon_pages(unsigned int length); + +/* File descriptor passing/receiving */ +void vsf_sysutil_send_fd(int sock_fd, int send_fd); +int vsf_sysutil_recv_fd(int sock_fd); + +#endif /* VSF_SYSDEPUTIL_H */ + diff --git a/sysstr.c b/sysstr.c new file mode 100644 index 0000000..89a60d3 --- /dev/null +++ b/sysstr.c @@ -0,0 +1,183 @@ +/* + * Part of Very Secure FTPd + * Licence: GPL v2 + * Author: Chris Evans + * sysstr.c + * + * This file basically wraps system functions so that we can deal in our + * nice abstracted string buffer objects. + */ + +#include "sysstr.h" +#include "str.h" +#include "secbuf.h" +#include "sysutil.h" +#include "defs.h" +#include "utility.h" +#include "tunables.h" + +void +str_getcwd(struct mystr* p_str) +{ + static char* p_getcwd_buf; + char* p_ret; + if (p_getcwd_buf == 0) + { + vsf_secbuf_alloc(&p_getcwd_buf, VSFTP_PATH_MAX); + } + /* In case getcwd() fails */ + str_empty(p_str); + p_ret = vsf_sysutil_getcwd(p_getcwd_buf, VSFTP_PATH_MAX); + if (p_ret != 0) + { + str_alloc_text(p_str, p_getcwd_buf); + } +} + +int +str_write_loop(const struct mystr* p_str, const int fd) +{ + return vsf_sysutil_write_loop(fd, str_getbuf(p_str), str_getlen(p_str)); +} + +int +str_read_loop(struct mystr* p_str, const int fd) +{ + return vsf_sysutil_read_loop( + fd, (char*) str_getbuf(p_str), str_getlen(p_str)); +} + +int +str_mkdir(const struct mystr* p_str, const unsigned int mode) +{ + return vsf_sysutil_mkdir(str_getbuf(p_str), mode); +} + +int +str_rmdir(const struct mystr* p_str) +{ + return vsf_sysutil_rmdir(str_getbuf(p_str)); +} + +int +str_unlink(const struct mystr* p_str) +{ + return vsf_sysutil_unlink(str_getbuf(p_str)); +} + +int +str_chdir(const struct mystr* p_str) +{ + return vsf_sysutil_chdir(str_getbuf(p_str)); +} + +int +str_open(const struct mystr* p_str, const enum EVSFSysStrOpenMode mode) +{ + enum EVSFSysUtilOpenMode open_mode = kVSFSysStrOpenUnknown; + switch (mode) + { + case kVSFSysStrOpenReadOnly: + open_mode = kVSFSysUtilOpenReadOnly; + break; + default: + bug("unknown mode value in str_open"); + break; + } + return vsf_sysutil_open_file(str_getbuf(p_str), open_mode); +} + +int +str_stat(const struct mystr* p_str, struct vsf_sysutil_statbuf** p_ptr) +{ + return vsf_sysutil_stat(str_getbuf(p_str), p_ptr); +} + +int +str_lstat(const struct mystr* p_str, struct vsf_sysutil_statbuf** p_ptr) +{ + return vsf_sysutil_lstat(str_getbuf(p_str), p_ptr); +} + +int +str_create(const struct mystr* p_str) +{ + return vsf_sysutil_create_file(str_getbuf(p_str)); +} + +int +str_create_overwrite(const struct mystr* p_str) +{ + return vsf_sysutil_create_overwrite_file(str_getbuf(p_str)); +} + +int +str_create_append(const struct mystr* p_str) +{ + return vsf_sysutil_create_or_open_file( + str_getbuf(p_str), tunable_file_open_mode); +} + +int +str_chmod(const struct mystr* p_str, unsigned int mode) +{ + return vsf_sysutil_chmod(str_getbuf(p_str), mode); +} + +int +str_rename(const struct mystr* p_from_str, const struct mystr* p_to_str) +{ + return vsf_sysutil_rename(str_getbuf(p_from_str), str_getbuf(p_to_str)); +} + +struct vsf_sysutil_dir* +str_opendir(const struct mystr* p_str) +{ + return vsf_sysutil_opendir(str_getbuf(p_str)); +} + +void +str_next_dirent(struct mystr* p_filename_str, struct vsf_sysutil_dir* p_dir) +{ + const char* p_filename = vsf_sysutil_next_dirent(p_dir); + str_empty(p_filename_str); + if (p_filename != 0) + { + str_alloc_text(p_filename_str, p_filename); + } +} + +int +str_readlink(struct mystr* p_str, const struct mystr* p_filename_str) +{ + static char* p_readlink_buf; + int retval; + if (p_readlink_buf == 0) + { + vsf_secbuf_alloc(&p_readlink_buf, VSFTP_PATH_MAX); + } + /* In case readlink() fails */ + str_empty(p_str); + /* Note: readlink(2) does not NULL terminate, but our wrapper does */ + retval = vsf_sysutil_readlink(str_getbuf(p_filename_str), p_readlink_buf, + VSFTP_PATH_MAX); + if (vsf_sysutil_retval_is_error(retval)) + { + return retval; + } + str_alloc_text(p_str, p_readlink_buf); + return 0; +} + +struct vsf_sysutil_user* +str_getpwnam(const struct mystr* p_user_str) +{ + return vsf_sysutil_getpwnam(str_getbuf(p_user_str)); +} + +void +str_syslog(const struct mystr* p_str, int severe) +{ + vsf_sysutil_syslog(str_getbuf(p_str), severe); +} + diff --git a/sysstr.h b/sysstr.h new file mode 100644 index 0000000..56b9785 --- /dev/null +++ b/sysstr.h @@ -0,0 +1,40 @@ +#ifndef VSF_SYSSTR_H +#define VSF_SYSSTR_H + +/* Forward declarations */ +struct mystr; +struct vsf_sysutil_statbuf; +struct vsf_sysutil_dir; +struct vsf_sysutil_user; + +void str_getcwd(struct mystr* p_str); +int str_readlink(struct mystr* p_str, const struct mystr* p_filename_str); +int str_write_loop(const struct mystr* p_str, const int fd); +int str_read_loop(struct mystr* p_str, const int fd); +int str_mkdir(const struct mystr* p_str, const unsigned int mode); +int str_rmdir(const struct mystr* p_str); +int str_unlink(const struct mystr* p_str); +int str_chdir(const struct mystr* p_str); +enum EVSFSysStrOpenMode +{ + kVSFSysStrOpenUnknown = 0, + kVSFSysStrOpenReadOnly = 1 +}; +int str_open(const struct mystr* p_str, const enum EVSFSysStrOpenMode mode); +int str_create_append(const struct mystr* p_str); +int str_create(const struct mystr* p_str); +int str_create_overwrite(const struct mystr* p_str); +int str_chmod(const struct mystr* p_str, unsigned int mode); +int str_stat(const struct mystr* p_str, struct vsf_sysutil_statbuf** p_ptr); +int str_lstat(const struct mystr* p_str, struct vsf_sysutil_statbuf** p_ptr); +int str_rename(const struct mystr* p_from_str, const struct mystr* p_to_str); +struct vsf_sysutil_dir* str_opendir(const struct mystr* p_str); +void str_next_dirent(struct mystr* p_filename_str, + struct vsf_sysutil_dir* p_dir); + +struct vsf_sysutil_user* str_getpwnam(const struct mystr* p_user_str); + +void str_syslog(const struct mystr* p_str, int severe); + +#endif /* VSF_SYSSTR_H */ + diff --git a/sysutil.c b/sysutil.c new file mode 100644 index 0000000..0e05474 --- /dev/null +++ b/sysutil.c @@ -0,0 +1,2648 @@ +/* + * Part of Very Secure FTPd + * Licence: GPL v2 + * Author: Chris Evans + * + * sysutil.c + * + * Routines to make the libc/syscall API more pleasant to use. Long term, + * more libc/syscalls will go in here to reduce the number of .c files with + * dependencies on libc or syscalls. + */ + +#define PRIVATE_HANDS_OFF_syscall_retval syscall_retval +#define PRIVATE_HANDS_OFF_exit_status exit_status +#include "sysutil.h" +#include "utility.h" +#include "tunables.h" + +/* Activate 64-bit file support on Linux/32bit plus others */ +#define _FILE_OFFSET_BITS 64 +#define _LARGEFILE_SOURCE 1 +#define _LARGEFILE64_SOURCE 1 +#define _LARGE_FILES 1 + +/* For Linux, this adds nothing :-) */ +#include "port/porting_junk.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +/* Must be before netinet/ip.h. Found on FreeBSD, Solaris */ +#include +#include +#include +#include +#include +#include +#include +#include "event_c_api.h" +#include "request_c_api.h" + +/* Private variables to this file */ +/* Current umask() */ +static unsigned int s_current_umask; +/* Cached time */ +static struct timeval s_current_time; +/* Current pid */ +static int s_current_pid = -1; +/* Exit function */ +static exitfunc_t s_exit_func; +/* Difference in timezone from GMT in seconds */ +static long s_timezone; + +/* Our internal signal handling implementation details */ +static struct vsf_sysutil_sig_details +{ + vsf_sighandle_t sync_sig_handler; + void* p_private; + int pending; + int running; +} s_sig_details[NSIG]; + +static vsf_context_io_t s_io_handler; +static void* s_p_io_handler_private; +static int s_io_handler_running; + +struct vsf_sysutil_sockaddr +{ + union + { + struct sockaddr u_sockaddr; + struct sockaddr_in u_sockaddr_in; + struct sockaddr_in6 u_sockaddr_in6; + } u; +}; + +/* File locals */ +static void vsf_sysutil_common_sighandler(int signum); +static int vsf_sysutil_translate_sig(const enum EVSFSysUtilSignal sig); +static void vsf_sysutil_set_sighandler(int sig, void (*p_handlefunc)(int)); +static int vsf_sysutil_translate_memprot( + const enum EVSFSysUtilMapPermission perm); +static int vsf_sysutil_translate_openmode( + const enum EVSFSysUtilOpenMode mode); +static void vsf_sysutil_alloc_statbuf(struct vsf_sysutil_statbuf** p_ptr); +void vsf_sysutil_sockaddr_alloc(struct vsf_sysutil_sockaddr** p_sockptr); +static int lock_internal(int fd, int lock_type); + +static void +vsf_sysutil_common_sighandler(int signum) +{ + if (signum < 0 || signum >= NSIG) + { + bug("signal out of range in vsf_sysutil_common_sighandler"); + } + if (s_sig_details[signum].sync_sig_handler) + { + s_sig_details[signum].pending = 1; + } +} + +/* Notes. This signal check is evaluated after potentially blocking system + * calls, i.e. at highly defined points in the code. Since we only interrupt + * at these definite locations, the signal handlers can be non-trivial + * without us having to worry about re-entrancy. + * + * We guarantee that a handler for a given signal is not re-entrant. This + * is taken care of by the "running" flag. + * + * This call itself can only be re-entered once we dereference the actual + * hander function pointer, so we are safe with respect to races modifying + * the "running" flag. + */ +void +vsf_sysutil_check_pending_actions( + const enum EVSFSysUtilInterruptContext context, int retval, int fd) +{ + unsigned int i; + /* Check the i/o handler before the signal handlers */ + if (s_io_handler && !s_io_handler_running && context == kVSFSysUtilIO) + { + s_io_handler_running = 1; + (*s_io_handler)(retval, fd, s_p_io_handler_private); + s_io_handler_running = 0; + } + for (i=0; i < NSIG; ++i) + { + if (s_sig_details[i].pending && !s_sig_details[i].running) + { + s_sig_details[i].running = 1; + if (s_sig_details[i].sync_sig_handler) + { + s_sig_details[i].pending = 0; + (*(s_sig_details[i].sync_sig_handler))(s_sig_details[i].p_private); + } + s_sig_details[i].running = 0; + } + } +} + +static int +vsf_sysutil_translate_sig(const enum EVSFSysUtilSignal sig) +{ + int realsig = 0; + switch (sig) + { + case kVSFSysUtilSigALRM: + realsig = SIGALRM; + break; + case kVSFSysUtilSigTERM: + realsig = SIGTERM; + break; + case kVSFSysUtilSigCHLD: + realsig = SIGCHLD; + break; + case kVSFSysUtilSigPIPE: + realsig = SIGPIPE; + break; + case kVSFSysUtilSigURG: + realsig = SIGURG; + break; + case kVSFSysUtilSigHUP: + realsig = SIGHUP; + break; + default: + bug("unknown signal in vsf_sysutil_translate_sig"); + break; + } + if (realsig < 0 || realsig >= NSIG) + { + bug("signal out of range in vsf_sysutil_translate_sig"); + } + return realsig; +} + +void +vsf_sysutil_install_sighandler(const enum EVSFSysUtilSignal sig, + vsf_sighandle_t handler, void* p_private) +{ + int realsig = vsf_sysutil_translate_sig(sig); + s_sig_details[realsig].p_private = p_private; + s_sig_details[realsig].sync_sig_handler = handler; + vsf_sysutil_set_sighandler(realsig, vsf_sysutil_common_sighandler); +} + +void +vsf_sysutil_default_sig(const enum EVSFSysUtilSignal sig) +{ + int realsig = vsf_sysutil_translate_sig(sig); + vsf_sysutil_set_sighandler(realsig, SIG_DFL); + s_sig_details[realsig].p_private = NULL; + s_sig_details[realsig].sync_sig_handler = NULL; +} + +void +vsf_sysutil_install_null_sighandler(const enum EVSFSysUtilSignal sig) +{ + int realsig = vsf_sysutil_translate_sig(sig); + s_sig_details[realsig].p_private = NULL; + s_sig_details[realsig].sync_sig_handler = NULL; + vsf_sysutil_set_sighandler(realsig, vsf_sysutil_common_sighandler); +} + +void +vsf_sysutil_install_async_sighandler(const enum EVSFSysUtilSignal sig, + vsf_async_sighandle_t handler) +{ + int realsig = vsf_sysutil_translate_sig(sig); + s_sig_details[realsig].p_private = NULL; + s_sig_details[realsig].sync_sig_handler = NULL; + vsf_sysutil_block_sig(sig); + vsf_sysutil_set_sighandler(realsig, handler); +} + +static void +vsf_sysutil_set_sighandler(int sig, void (*p_handlefunc)(int)) +{ + int retval; + struct sigaction sigact; + vsf_sysutil_memclr(&sigact, sizeof(sigact)); + sigact.sa_handler = p_handlefunc; + retval = sigfillset(&sigact.sa_mask); + if (retval != 0) + { + die("sigfillset"); + } + retval = sigaction(sig, &sigact, NULL); + if (retval != 0) + { + die("sigaction"); + } +} + +void +vsf_sysutil_block_sig(const enum EVSFSysUtilSignal sig) +{ + sigset_t sset; + int retval; + int realsig = vsf_sysutil_translate_sig(sig); + retval = sigemptyset(&sset); + if (retval != 0) + { + die("sigemptyset"); + } + retval = sigaddset(&sset, realsig); + if (retval != 0) + { + die("sigaddset"); + } + retval = sigprocmask(SIG_BLOCK, &sset, NULL); + if (retval != 0) + { + die("sigprocmask"); + } +} + +void +vsf_sysutil_unblock_sig(const enum EVSFSysUtilSignal sig) +{ + sigset_t sset; + int retval; + int realsig = vsf_sysutil_translate_sig(sig); + retval = sigemptyset(&sset); + if (retval != 0) + { + die("sigemptyset"); + } + retval = sigaddset(&sset, realsig); + if (retval != 0) + { + die("sigaddset"); + } + retval = sigprocmask(SIG_UNBLOCK, &sset, NULL); + if (retval != 0) + { + die("sigprocmask"); + } +} +void +vsf_sysutil_install_io_handler(vsf_context_io_t handler, void* p_private) +{ + if (s_io_handler != NULL) + { + bug("double register of i/o handler"); + } + s_io_handler = handler; + s_p_io_handler_private = p_private; +} + +void +vsf_sysutil_uninstall_io_handler(void) +{ + if (s_io_handler == NULL) + { + bug("no i/o handler to unregister!"); + } + s_io_handler = NULL; + s_p_io_handler_private = NULL; +} + +void +vsf_sysutil_set_alarm(const unsigned int trigger_seconds) +{ + (void) alarm(trigger_seconds); +} + +void +vsf_sysutil_clear_alarm(void) +{ + vsf_sysutil_set_alarm(0); +} + +int +vsf_sysutil_read(const int fd, void* p_buf, const unsigned int size) +{ + while (1) + { + int retval = read(fd, p_buf, size); + int saved_errno = errno; + vsf_sysutil_check_pending_actions(kVSFSysUtilIO, retval, fd); + if (retval < 0 && saved_errno == EINTR) + { + continue; + } + return retval; + } +} + +int +vsf_sysutil_write(const int fd, const void* p_buf, const unsigned int size) +{ + while (1) + { + int retval = write(fd, p_buf, size); + int saved_errno = errno; + vsf_sysutil_check_pending_actions(kVSFSysUtilIO, retval, fd); + if (retval < 0 && saved_errno == EINTR) + { + continue; + } + return retval; + } +} + +int +vsf_sysutil_read_loop(const int fd, void* p_buf, unsigned int size) +{ + int retval; + int num_read = 0; + if (size > INT_MAX) + { + die("size too big in vsf_sysutil_read_loop"); + } + while (1) + { + retval = vsf_sysutil_read(fd, (char*)p_buf + num_read, size); + if (retval < 0) + { + return retval; + } + else if (retval == 0) + { + /* Read all we're going to read.. */ + return num_read; + } + if ((unsigned int) retval > size) + { + die("retval too big in vsf_sysutil_read_loop"); + } + num_read += retval; + size -= (unsigned int) retval; + if (size == 0) + { + /* Hit the read target, cool. */ + return num_read; + } + } +} + +int +vsf_sysutil_write_loop(const int fd, const void* p_buf, unsigned int size) +{ + int retval; + int num_written = 0; + if (size > INT_MAX) + { + die("size too big in vsf_sysutil_write_loop"); + } + while (1) + { + retval = vsf_sysutil_write(fd, (const char*)p_buf + num_written, size); + if (retval < 0) + { + /* Error */ + return retval; + } + else if (retval == 0) + { + /* Written all we're going to write.. */ + return num_written; + } + if ((unsigned int) retval > size) + { + die("retval too big in vsf_sysutil_read_loop"); + } + num_written += retval; + size -= (unsigned int) retval; + if (size == 0) + { + /* Hit the write target, cool. */ + return num_written; + } + } +} + +filesize_t +vsf_sysutil_get_file_offset(const int file_fd) +{ + filesize_t retval = lseek(file_fd, 0, SEEK_CUR); + if (retval < 0) + { + die("lseek"); + } + return retval; +} + +void +vsf_sysutil_lseek_to(const int fd, filesize_t seek_pos) +{ + filesize_t retval; + if (seek_pos < 0) + { + die("negative seek_pos in vsf_sysutil_lseek_to"); + } + retval = lseek(fd, seek_pos, SEEK_SET); + if (retval < 0) + { + die("lseek"); + } +} + +void* +vsf_sysutil_malloc(unsigned int size) +{ + void* p_ret; + /* Paranoia - what if we got an integer overflow/underflow? */ + if (size == 0 || size > INT_MAX) + { + bug("zero or big size in vsf_sysutil_malloc"); + } + p_ret = malloc(size); + if (p_ret == NULL) + { + die("malloc"); + } + return p_ret; +} + +void* +vsf_sysutil_realloc(void* p_ptr, unsigned int size) +{ + void* p_ret; + if (size == 0 || size > INT_MAX) + { + bug("zero or big size in vsf_sysutil_realloc"); + } + p_ret = realloc(p_ptr, size); + if (p_ret == NULL) + { + die("realloc"); + } + return p_ret; +} + +void +vsf_sysutil_free(void* p_ptr) +{ + if (p_ptr == NULL) + { + bug("vsf_sysutil_free got a null pointer"); + } + free(p_ptr); +} + +unsigned int +vsf_sysutil_getpid(void) +{ + if (s_current_pid == -1) + { + s_current_pid = getpid(); + } + return (unsigned int) s_current_pid; +} + +int +vsf_sysutil_fork(void) +{ + /* Child does NOT inherit exit function */ + exitfunc_t curr_func = s_exit_func; + int retval; + s_exit_func = 0; + retval = vsf_sysutil_fork_failok(); + if (retval != 0) + { + s_exit_func = curr_func; + } + if (retval < 0) + { + die("fork"); + } + return retval; +} + +int +vsf_sysutil_fork_failok(void) +{ + int retval = fork(); + if (retval == 0) + { + s_current_pid = -1; + } + return retval; +} + +void +vsf_sysutil_set_exit_func(exitfunc_t exitfunc) +{ + s_exit_func = exitfunc; +} + +void +vsf_sysutil_exit(int exit_code) +{ + if (s_exit_func) + { + exitfunc_t curr_func = s_exit_func; + /* Prevent recursion */ + s_exit_func = 0; + (*curr_func)(); + } + _exit(exit_code); +} + +struct vsf_sysutil_wait_retval +vsf_sysutil_wait(void) +{ + struct vsf_sysutil_wait_retval retval; + vsf_sysutil_memclr(&retval, sizeof(retval)); + while (1) + { + int sys_ret = wait(&retval.exit_status); + if (sys_ret < 0 && errno == EINTR) + { + vsf_sysutil_check_pending_actions(kVSFSysUtilUnknown, 0, 0); + continue; + } + retval.syscall_retval = sys_ret; + return retval; + } +} + +int +vsf_sysutil_wait_reap_one(void) +{ + int retval = waitpid(-1, NULL, WNOHANG); + if (retval == 0 || (retval < 0 && errno == ECHILD)) + { + /* No more children */ + return 0; + } + if (retval < 0) + { + die("waitpid"); + } + /* Got one */ + return retval; +} + +int +vsf_sysutil_wait_get_retval(const struct vsf_sysutil_wait_retval* p_waitret) +{ + return p_waitret->syscall_retval; +} + +int +vsf_sysutil_wait_exited_normally( + const struct vsf_sysutil_wait_retval* p_waitret) +{ + return WIFEXITED(p_waitret->exit_status); +} + +int +vsf_sysutil_wait_get_exitcode(const struct vsf_sysutil_wait_retval* p_waitret) +{ + if (!vsf_sysutil_wait_exited_normally(p_waitret)) + { + bug("not a normal exit in vsf_sysutil_wait_get_exitcode"); + } + return WEXITSTATUS(p_waitret->exit_status); +} + +void +vsf_sysutil_activate_keepalive(int fd) +{ + int keepalive = 1; + int retval = setsockopt(fd, SOL_SOCKET, SO_KEEPALIVE, &keepalive, + sizeof(keepalive)); + if (retval != 0) + { + die("setsockopt: keepalive"); + } +} + +void +vsf_sysutil_activate_reuseaddr(int fd) +{ + int reuseaddr = 1; + int retval = setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &reuseaddr, + sizeof(reuseaddr)); + if (retval != 0) + { + die("setsockopt: reuseaddr"); + } +} + +void +vsf_sysutil_set_nodelay(int fd) +{ + int nodelay = 1; + int retval = setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, &nodelay, + sizeof(nodelay)); + if (retval != 0) + { + die("setsockopt: nodelay"); + } +} + +void +vsf_sysutil_activate_sigurg(int fd) +{ + int retval = fcntl(fd, F_SETOWN, getpid()); + if (retval != 0) + { + die("fcntl"); + } +} + +void +vsf_sysutil_activate_oobinline(int fd) +{ + int oob_inline = 1; + int retval = setsockopt(fd, SOL_SOCKET, SO_OOBINLINE, &oob_inline, + sizeof(oob_inline)); + if (retval != 0) + { + die("setsockopt: oobinline"); + } +} + +void +vsf_sysutil_set_iptos_throughput(int fd) +{ + int tos = IPTOS_THROUGHPUT; + /* Ignore failure to set (maybe this IP stack demands privilege for this) */ + (void) setsockopt(fd, IPPROTO_IP, IP_TOS, &tos, sizeof(tos)); +} + +void +vsf_sysutil_activate_linger(int fd) +{ + int retval; + struct linger the_linger; + vsf_sysutil_memclr(&the_linger, sizeof(the_linger)); + the_linger.l_onoff = 1; + the_linger.l_linger = 32767; + retval = setsockopt(fd, SOL_SOCKET, SO_LINGER, &the_linger, + sizeof(the_linger)); + if (retval != 0) + { + die("setsockopt: linger"); + } +} + +void +vsf_sysutil_deactivate_linger_failok(int fd) +{ + struct linger the_linger; + the_linger.l_onoff = 0; + the_linger.l_linger = 0; + (void) setsockopt(fd, SOL_SOCKET, SO_LINGER, &the_linger, sizeof(the_linger)); +} + +void +vsf_sysutil_activate_noblock(int fd) +{ + int retval; + int curr_flags = fcntl(fd, F_GETFL); + if (vsf_sysutil_retval_is_error(curr_flags)) + { + die("fcntl"); + } + curr_flags |= O_NONBLOCK; + retval = fcntl(fd, F_SETFL, curr_flags); + if (retval != 0) + { + die("fcntl"); + } +} + +void +vsf_sysutil_deactivate_noblock(int fd) +{ + int retval; + int curr_flags = fcntl(fd, F_GETFL); + if (vsf_sysutil_retval_is_error(curr_flags)) + { + die("fcntl"); + } + curr_flags &= ~O_NONBLOCK; + retval = fcntl(fd, F_SETFL, curr_flags); + if (retval != 0) + { + die("fcntl"); + } +} + +int +vsf_sysutil_recv_peek(const int fd, void* p_buf, unsigned int len) +{ + while (1) + { + int retval = recv(fd, p_buf, len, MSG_PEEK); + int saved_errno = errno; + vsf_sysutil_check_pending_actions(kVSFSysUtilIO, retval, fd); + if (retval < 0 && saved_errno == EINTR) + { + continue; + } + return retval; + } +} + +int +vsf_sysutil_atoi(const char* p_str) +{ + return atoi(p_str); +} + +filesize_t +vsf_sysutil_a_to_filesize_t(const char* p_str) +{ + /* atoll() is C99 standard - but even modern FreeBSD, OpenBSD don't have + * it, so we'll supply our own + */ + filesize_t result = 0; + filesize_t mult = 1; + unsigned int len = vsf_sysutil_strlen(p_str); + unsigned int i; + /* Bail if the number is excessively big (petabytes!) */ + if (len > 15) + { + return 0; + } + for (i=0; i '9') + { + return 0; + } + val = the_char - '0'; + val *= mult; + result += val; + mult *= 10; + } + return result; +} + +const char* +vsf_sysutil_ulong_to_str(unsigned long the_ulong) +{ + static char ulong_buf[32]; + (void) snprintf(ulong_buf, sizeof(ulong_buf), "%lu", the_ulong); + return ulong_buf; +} + +const char* +vsf_sysutil_filesize_t_to_str(filesize_t the_filesize) +{ + static char filesize_buf[32]; + if (sizeof(long) == 8) + { + /* Avoid using non-standard %ll if we can */ + (void) snprintf(filesize_buf, sizeof(filesize_buf), "%ld", + (long) the_filesize); + } + else + { + (void) snprintf(filesize_buf, sizeof(filesize_buf), "%lld", the_filesize); + } + return filesize_buf; +} + +const char* +vsf_sysutil_double_to_str(double the_double) +{ + static char double_buf[32]; + (void) snprintf(double_buf, sizeof(double_buf), "%.2f", the_double); + return double_buf; +} + +const char* +vsf_sysutil_uint_to_octal(unsigned int the_uint) +{ + static char octal_buf[32]; + if (the_uint == 0) + { + octal_buf[0] = '0'; + octal_buf[1] = '\0'; + } + else + { + (void) snprintf(octal_buf, sizeof(octal_buf), "0%o", the_uint); + } + return octal_buf; +} + +unsigned int +vsf_sysutil_octal_to_uint(const char* p_str) +{ + /* NOTE - avoiding using sscanf() parser */ + unsigned int result = 0; + int seen_non_zero_digit = 0; + while (*p_str != '\0') + { + int digit = *p_str; + if (!isdigit(digit) || digit > '7') + { + break; + } + if (digit != '0') + { + seen_non_zero_digit = 1; + } + if (seen_non_zero_digit) + { + result <<= 3; + result += (digit - '0'); + } + p_str++; + } + return result; +} + +int +vsf_sysutil_toupper(int the_char) +{ + return toupper(the_char); +} + +int +vsf_sysutil_isspace(int the_char) +{ + return isspace(the_char); +} + +int +vsf_sysutil_isprint(int the_char) +{ + /* From Solar - we know better than some libc's! Don't let any potential + * control chars through + */ + unsigned char uc = (unsigned char) the_char; + if (uc <= 31) + { + return 0; + } + if (uc == 177) + { + return 0; + } + if (uc >= 128 && uc <= 159) + { + return 0; + } + return isprint(the_char); +} + +int +vsf_sysutil_isalnum(int the_char) +{ + return isalnum(the_char); +} + +int +vsf_sysutil_isdigit(int the_char) +{ + return isdigit(the_char); +} + +char* +vsf_sysutil_getcwd(char* p_dest, const unsigned int buf_size) +{ + char* p_retval = getcwd(p_dest, buf_size); + p_dest[buf_size - 1] = '\0'; + return p_retval; +} + +int +vsf_sysutil_mkdir(const char* p_dirname, const unsigned int mode) +{ + return mkdir(p_dirname, mode); +} + +int +vsf_sysutil_rmdir(const char* p_dirname) +{ + return rmdir(p_dirname); +} + +int +vsf_sysutil_chdir(const char* p_dirname) +{ + return chdir(p_dirname); +} + +int +vsf_sysutil_rename(const char* p_from, const char* p_to) +{ + return rename(p_from, p_to); +} + +struct vsf_sysutil_dir* +vsf_sysutil_opendir(const char* p_dirname) +{ + return (struct vsf_sysutil_dir*) opendir(p_dirname); +} + +void +vsf_sysutil_closedir(struct vsf_sysutil_dir* p_dir) +{ + DIR* p_real_dir = (DIR*) p_dir; + int retval = closedir(p_real_dir); + if (retval != 0) + { + die("closedir"); + } +} + +const char* +vsf_sysutil_next_dirent(struct vsf_sysutil_dir* p_dir) +{ + DIR* p_real_dir = (DIR*) p_dir; + struct dirent* p_dirent = readdir(p_real_dir); + if (p_dirent == NULL) + { + return NULL; + } + return p_dirent->d_name; +} + +unsigned int +vsf_sysutil_strlen(const char* p_text) +{ + unsigned int ret = strlen(p_text); + /* A defense in depth measure. */ + if (ret > INT_MAX / 8) + { + die("string suspiciously long"); + } + return ret; +} + +char* +vsf_sysutil_strdup(const char* p_str) +{ + return strdup(p_str); +} + +void +vsf_sysutil_memclr(void* p_dest, unsigned int size) +{ + /* Safety */ + if (size == 0) + { + return; + } + memset(p_dest, '\0', size); +} + +void +vsf_sysutil_memcpy(void* p_dest, const void* p_src, const unsigned int size) +{ + /* Safety */ + if (size == 0) + { + return; + } + /* Defense in depth */ + if (size > INT_MAX) + { + die("possible negative value to memcpy?"); + } + memcpy(p_dest, p_src, size); +} + +void +vsf_sysutil_strcpy(char* p_dest, const char* p_src, unsigned int maxsize) +{ + if (maxsize == 0) + { + return; + } + strncpy(p_dest, p_src, maxsize); + p_dest[maxsize - 1] = '\0'; +} + +int +vsf_sysutil_memcmp(const void* p_src1, const void* p_src2, unsigned int size) +{ + /* Safety */ + if (size == 0) + { + return 0; + } + return memcmp(p_src1, p_src2, size); +} + +int +vsf_sysutil_strcmp(const char* p_src1, const char* p_src2) +{ + return strcmp(p_src1, p_src2); +} + +unsigned int +vsf_sysutil_getpagesize(void) +{ + static unsigned int s_page_size; + if (s_page_size == 0) + { + s_page_size = getpagesize(); + if (s_page_size == 0) + { + die("getpagesize"); + } + } + return s_page_size; +} + +static int +vsf_sysutil_translate_memprot(const enum EVSFSysUtilMapPermission perm) +{ + int retval = 0; + switch (perm) + { + case kVSFSysUtilMapProtReadOnly: + retval = PROT_READ; + break; + case kVSFSysUtilMapProtNone: + retval = PROT_NONE; + break; + default: + bug("bad value in vsf_sysutil_translate_memprot"); + break; + } + return retval; +} + +void +vsf_sysutil_memprotect(void* p_addr, unsigned int len, + const enum EVSFSysUtilMapPermission perm) +{ + int prot = vsf_sysutil_translate_memprot(perm); + int retval = mprotect(p_addr, len, prot); + if (retval != 0) + { + die("mprotect"); + } +} + +void +vsf_sysutil_memunmap(void* p_start, unsigned int length) +{ + int retval = munmap(p_start, length); + if (retval != 0) + { + die("munmap"); + } +} + +static int +vsf_sysutil_translate_openmode(const enum EVSFSysUtilOpenMode mode) +{ + int retval = 0; + switch (mode) + { + case kVSFSysUtilOpenReadOnly: + retval = O_RDONLY; + break; + case kVSFSysUtilOpenWriteOnly: + retval = O_WRONLY; + break; + case kVSFSysUtilOpenReadWrite: + retval = O_RDWR; + break; + default: + bug("bad mode in vsf_sysutil_translate_openmode"); + break; + } + return retval; +} + +int +vsf_sysutil_open_file(const char* p_filename, + const enum EVSFSysUtilOpenMode mode) +{ + return open(p_filename, vsf_sysutil_translate_openmode(mode) | O_NONBLOCK); +} + +int +vsf_sysutil_create_file(const char* p_filename) +{ + /* umask() also contributes to end mode */ + return open(p_filename, O_CREAT | O_EXCL | O_WRONLY | O_APPEND, + tunable_file_open_mode); +} + +int +vsf_sysutil_create_overwrite_file(const char* p_filename) +{ + return open(p_filename, O_CREAT | O_TRUNC | O_WRONLY | + O_APPEND | O_NONBLOCK, + tunable_file_open_mode); +} + +int +vsf_sysutil_create_or_open_file(const char* p_filename, unsigned int mode) +{ + return open(p_filename, O_CREAT | O_WRONLY | O_APPEND | O_NONBLOCK, mode); +} + +void +vsf_sysutil_dupfd2(int old_fd, int new_fd) +{ + int retval; + if (old_fd == new_fd) + { + return; + } + retval = dup2(old_fd, new_fd); + if (retval != new_fd) + { + die("dup2"); + } +} + +void +vsf_sysutil_close(int fd) +{ + while (1) + { + int retval = close(fd); + if (retval != 0) + { + if (errno == EINTR) + { + vsf_sysutil_check_pending_actions(kVSFSysUtilUnknown, 0, 0); + continue; + } + die("close"); + } + return; + } +} + +int +vsf_sysutil_close_failok(int fd) +{ + return close(fd); +} + +int +vsf_sysutil_unlink(const char* p_dead) +{ + return unlink(p_dead); +} + +int +vsf_sysutil_write_access(const char* p_filename) +{ + int retval = access(p_filename, W_OK); + return (retval == 0); +} + +static void +vsf_sysutil_alloc_statbuf(struct vsf_sysutil_statbuf** p_ptr) +{ + if (*p_ptr == NULL) + { + *p_ptr = vsf_sysutil_malloc(sizeof(struct stat)); + } +} + +void +vsf_sysutil_fstat(int fd, struct vsf_sysutil_statbuf** p_ptr) +{ + int retval; + vsf_sysutil_alloc_statbuf(p_ptr); + retval = fstat(fd, (struct stat*) (*p_ptr)); + if (retval != 0) + { + die("fstat"); + } +} + +int +vsf_sysutil_stat(const char* p_name, struct vsf_sysutil_statbuf** p_ptr) +{ + vsf_sysutil_alloc_statbuf(p_ptr); + return stat(p_name, (struct stat*) (*p_ptr)); +} + +int +vsf_sysutil_lstat(const char* p_name, struct vsf_sysutil_statbuf** p_ptr) +{ + vsf_sysutil_alloc_statbuf(p_ptr); + return lstat(p_name, (struct stat*) (*p_ptr)); +} + +void +vsf_sysutil_dir_stat(const struct vsf_sysutil_dir* p_dir, + struct vsf_sysutil_statbuf** p_ptr) +{ + int fd = dirfd((DIR*) p_dir); + vsf_sysutil_fstat(fd, p_ptr); +} + +int +vsf_sysutil_statbuf_is_regfile(const struct vsf_sysutil_statbuf* p_stat) +{ + const struct stat* p_realstat = (const struct stat*) p_stat; + return S_ISREG(p_realstat->st_mode); +} + +int +vsf_sysutil_statbuf_is_symlink(const struct vsf_sysutil_statbuf* p_stat) +{ + const struct stat* p_realstat = (const struct stat*) p_stat; + return S_ISLNK(p_realstat->st_mode); +} + +int +vsf_sysutil_statbuf_is_socket(const struct vsf_sysutil_statbuf* p_stat) +{ + const struct stat* p_realstat = (const struct stat*) p_stat; + return S_ISSOCK(p_realstat->st_mode); +} + +int +vsf_sysutil_statbuf_is_dir(const struct vsf_sysutil_statbuf* p_stat) +{ + const struct stat* p_realstat = (const struct stat*) p_stat; + return S_ISDIR(p_realstat->st_mode); +} + +const char* +vsf_sysutil_statbuf_get_perms(const struct vsf_sysutil_statbuf* p_statbuf) +{ + static char perms[11]; + int i; + const struct stat* p_stat = (const struct stat*) p_statbuf; + for (i=0; i<10; i++) + { + perms[i] = '-'; + } + perms[0] = '?'; + switch (p_stat->st_mode & S_IFMT) + { + case S_IFREG: perms[0] = '-'; break; + case S_IFDIR: perms[0] = 'd'; break; + case S_IFLNK: perms[0] = 'l'; break; + case S_IFIFO: perms[0] = 'p'; break; + case S_IFSOCK: perms[0] = 's'; break; + case S_IFCHR: perms[0] = 'c'; break; + case S_IFBLK: perms[0] = 'b'; break; + } + if (p_stat->st_mode & S_IRUSR) perms[1] = 'r'; + if (p_stat->st_mode & S_IWUSR) perms[2] = 'w'; + if (p_stat->st_mode & S_IXUSR) perms[3] = 'x'; + if (p_stat->st_mode & S_IRGRP) perms[4] = 'r'; + if (p_stat->st_mode & S_IWGRP) perms[5] = 'w'; + if (p_stat->st_mode & S_IXGRP) perms[6] = 'x'; + if (p_stat->st_mode & S_IROTH) perms[7] = 'r'; + if (p_stat->st_mode & S_IWOTH) perms[8] = 'w'; + if (p_stat->st_mode & S_IXOTH) perms[9] = 'x'; + if (p_stat->st_mode & S_ISUID) perms[3] = (perms[3] == 'x') ? 's' : 'S'; + if (p_stat->st_mode & S_ISGID) perms[6] = (perms[6] == 'x') ? 's' : 'S'; + if (p_stat->st_mode & S_ISVTX) perms[9] = (perms[9] == 'x') ? 't' : 'T'; + perms[10] = '\0'; + return perms; +} + +const char* +vsf_sysutil_statbuf_get_date(const struct vsf_sysutil_statbuf* p_statbuf, + int use_localtime) +{ + static char datebuf[64]; + int retval; + struct tm* p_tm; + const struct stat* p_stat = (const struct stat*) p_statbuf; + long local_time = vsf_sysutil_get_cached_time_sec(); + const char* p_date_format = "%b %d %H:%M"; + if (!use_localtime) + { + p_tm = gmtime(&p_stat->st_mtime); + } + else + { + p_tm = localtime(&p_stat->st_mtime); + } + /* Is this a future or 6 months old date? If so, we drop to year format */ + if (p_stat->st_mtime > local_time || + (local_time - p_stat->st_mtime) > 60*60*24*182) + { + p_date_format = "%b %d %Y"; + } + retval = strftime(datebuf, sizeof(datebuf), p_date_format, p_tm); + datebuf[sizeof(datebuf)-1] = '\0'; + if (retval == 0) + { + die("strftime"); + } + return datebuf; +} + +const char* +vsf_sysutil_statbuf_get_numeric_date( + const struct vsf_sysutil_statbuf* p_statbuf, + int use_localtime) +{ + static char datebuf[15]; + const struct stat* p_stat = (const struct stat*) p_statbuf; + struct tm* p_tm; + int retval; + if (!use_localtime) + { + p_tm = gmtime(&p_stat->st_mtime); + } + else + { + p_tm = localtime(&p_stat->st_mtime); + } + retval = strftime(datebuf, sizeof(datebuf), "%Y%m%d%H%M%S", p_tm); + if (retval == 0) + { + die("strftime"); + } + return datebuf; +} + +filesize_t +vsf_sysutil_statbuf_get_size(const struct vsf_sysutil_statbuf* p_statbuf) +{ + const struct stat* p_stat = (const struct stat*) p_statbuf; + if (p_stat->st_size < 0) + { + die("invalid inode size in vsf_sysutil_statbuf_get_size"); + } + return p_stat->st_size; +} + +int +vsf_sysutil_statbuf_get_uid(const struct vsf_sysutil_statbuf* p_statbuf) +{ + const struct stat* p_stat = (const struct stat*) p_statbuf; + return p_stat->st_uid; +} + +int +vsf_sysutil_statbuf_get_gid(const struct vsf_sysutil_statbuf* p_statbuf) +{ + const struct stat* p_stat = (const struct stat*) p_statbuf; + return p_stat->st_gid; +} + +unsigned int +vsf_sysutil_statbuf_get_links(const struct vsf_sysutil_statbuf* p_statbuf) +{ + const struct stat* p_stat = (const struct stat*) p_statbuf; + return p_stat->st_nlink; +} + +int +vsf_sysutil_statbuf_is_readable_other( + const struct vsf_sysutil_statbuf* p_statbuf) +{ + const struct stat* p_stat = (const struct stat*) p_statbuf; + if (p_stat->st_mode & S_IROTH) + { + return 1; + } + return 0; +} + +const char* +vsf_sysutil_statbuf_get_sortkey_mtime( + const struct vsf_sysutil_statbuf* p_statbuf) +{ + static char intbuf[32]; + const struct stat* p_stat = (const struct stat*) p_statbuf; + /* This slight hack function must return a character date format such that + * more recent dates appear later in the alphabet! Most notably, we must + * make sure we pad to the same length with 0's + */ + snprintf(intbuf, sizeof(intbuf), "%030ld", (long) p_stat->st_mtime); + return intbuf; +} + +void +vsf_sysutil_fchown(const int fd, const int uid, const int gid) +{ + if (fchown(fd, uid, gid) != 0) + { + die("fchown"); + } +} + +void +vsf_sysutil_fchmod(const int fd, unsigned int mode) +{ + mode = mode & 0777; + if (fchmod(fd, mode)) + { + die("fchmod"); + } +} + +int +vsf_sysutil_chmod(const char* p_filename, unsigned int mode) +{ + /* Safety: mask "mode" to just access permissions, e.g. no suid setting! */ + mode = mode & 0777; + return chmod(p_filename, mode); +} + +int +vsf_sysutil_lock_file_write(int fd) +{ + return lock_internal(fd, F_WRLCK); +} + +int +vsf_sysutil_lock_file_read(int fd) +{ + return lock_internal(fd, F_RDLCK); +} + +static int +lock_internal(int fd, int lock_type) +{ + struct flock the_lock; + int retval; + int saved_errno; + vsf_sysutil_memclr(&the_lock, sizeof(the_lock)); + the_lock.l_type = lock_type; + the_lock.l_whence = SEEK_SET; + the_lock.l_start = 0; + the_lock.l_len = 0; + do + { + retval = fcntl(fd, F_SETLKW, &the_lock); + saved_errno = errno; + vsf_sysutil_check_pending_actions(kVSFSysUtilUnknown, 0, 0); + } + while (retval < 0 && saved_errno == EINTR); + return retval; +} + +void +vsf_sysutil_unlock_file(int fd) +{ + int retval; + struct flock the_lock; + vsf_sysutil_memclr(&the_lock, sizeof(the_lock)); + the_lock.l_type = F_UNLCK; + the_lock.l_whence = SEEK_SET; + the_lock.l_start = 0; + the_lock.l_len = 0; + retval = fcntl(fd, F_SETLK, &the_lock); + if (retval != 0) + { + die("fcntl"); + } +} + +int +vsf_sysutil_readlink(const char* p_filename, char* p_dest, unsigned int bufsiz) +{ + int retval = readlink(p_filename, p_dest, bufsiz - 1); + if (retval < 0) + { + return retval; + } + /* Ensure buffer is NULL terminated; readlink(2) doesn't do that */ + p_dest[retval] = '\0'; + return retval; +} + +int +vsf_sysutil_retval_is_error(int retval) +{ + if (retval < 0) + { + return 1; + } + return 0; +} + +enum EVSFSysUtilError +vsf_sysutil_get_error(void) +{ + enum EVSFSysUtilError retval = kVSFSysUtilErrUnknown; + switch (errno) + { + case EADDRINUSE: + retval = kVSFSysUtilErrADDRINUSE; + break; + case ENOSYS: + retval = kVSFSysUtilErrNOSYS; + break; + case EINTR: + retval = kVSFSysUtilErrINTR; + break; + case EINVAL: + retval = kVSFSysUtilErrINVAL; + break; + case EOPNOTSUPP: + retval = kVSFSysUtilErrOPNOTSUPP; + break; + } + return retval; +} + +int +vsf_sysutil_get_ipv4_sock(void) +{ + int retval = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP); + if (retval < 0) + { + die("socket"); + } + return retval; +} + +int +vsf_sysutil_get_ipv6_sock(void) +{ + int retval = socket(PF_INET6, SOCK_STREAM, IPPROTO_TCP); + if (retval < 0) + { + die("socket"); + } + return retval; +} + +struct vsf_sysutil_socketpair_retval +vsf_sysutil_unix_stream_socketpair(void) +{ + struct vsf_sysutil_socketpair_retval retval; + int the_sockets[2]; + int sys_retval = socketpair(PF_UNIX, SOCK_STREAM, 0, the_sockets); + if (sys_retval != 0) + { + die("socketpair"); + } + retval.socket_one = the_sockets[0]; + retval.socket_two = the_sockets[1]; + return retval; +} + +int +vsf_sysutil_bind(int fd, const struct vsf_sysutil_sockaddr* p_sockptr) +{ + const struct sockaddr* p_sockaddr = &p_sockptr->u.u_sockaddr; + int len = 0; + if (p_sockaddr->sa_family == AF_INET) + { + len = sizeof(struct sockaddr_in); + } + else if (p_sockaddr->sa_family == AF_INET6) + { + len = sizeof(struct sockaddr_in6); + } + else + { + die("can only support ipv4 and ipv6 currently"); + } + return bind(fd, p_sockaddr, len); +} + +void +vsf_sysutil_listen(int fd, const unsigned int backlog) +{ + int retval = listen(fd, backlog); + if (retval != 0) + { + die("listen"); + } +} + +/* Warning: callers of this function assume it does NOT make use of any + * non re-entrant calls such as malloc(). + */ +int +vsf_sysutil_accept_timeout(int fd, struct vsf_sysutil_sockaddr* p_sockaddr, + unsigned int wait_seconds) +{ + struct vsf_sysutil_sockaddr remote_addr; + int retval; + int saved_errno; + fd_set accept_fdset; + struct timeval timeout; + unsigned int socklen = sizeof(remote_addr); + if (p_sockaddr) + { + vsf_sysutil_memclr(p_sockaddr, sizeof(*p_sockaddr)); + } + if (wait_seconds > 0) + { + FD_ZERO(&accept_fdset); + FD_SET(fd, &accept_fdset); + timeout.tv_sec = wait_seconds; + timeout.tv_usec = 0; + do + { + retval = select(fd + 1, &accept_fdset, NULL, NULL, &timeout); + saved_errno = errno; + vsf_sysutil_check_pending_actions(kVSFSysUtilUnknown, 0, 0); + } while (retval < 0 && saved_errno == EINTR); + if (retval == 0) + { + errno = EAGAIN; + return -1; + } + } + retval = accept(fd, &remote_addr.u.u_sockaddr, &socklen); + vsf_sysutil_check_pending_actions(kVSFSysUtilUnknown, 0, 0); + if (retval < 0) + { + return retval; + } + /* FreeBSD bug / paranoia: ai32@drexel.edu */ + if (socklen == 0) + { + return -1; + } + if (remote_addr.u.u_sockaddr.sa_family != AF_INET && + remote_addr.u.u_sockaddr.sa_family != AF_INET6) + { + die("can only support ipv4 and ipv6 currently"); + } + if (p_sockaddr) + { + if (remote_addr.u.u_sockaddr.sa_family == AF_INET) + { + vsf_sysutil_memclr(&remote_addr.u.u_sockaddr_in.sin_zero, + sizeof(remote_addr.u.u_sockaddr_in.sin_zero)); + vsf_sysutil_memcpy(p_sockaddr, &remote_addr.u.u_sockaddr_in, + sizeof(remote_addr.u.u_sockaddr_in)); + } + else + { + vsf_sysutil_memcpy(p_sockaddr, &remote_addr.u.u_sockaddr_in6, + sizeof(remote_addr.u.u_sockaddr_in6)); + } + } + return retval; +} + +int +vsf_sysutil_connect_timeout(int fd, const struct vsf_sysutil_sockaddr* p_addr, + unsigned int wait_seconds) +{ + const struct sockaddr* p_sockaddr = &p_addr->u.u_sockaddr; + unsigned int addrlen = 0; + int retval; + int saved_errno; + if (p_sockaddr->sa_family == AF_INET) + { + addrlen = sizeof(p_addr->u.u_sockaddr_in); + } + else if (p_sockaddr->sa_family == AF_INET6) + { + addrlen = sizeof(p_addr->u.u_sockaddr_in6); + } + else + { + die("can only support ipv4 and ipv6 currently"); + } + if (wait_seconds > 0) + { + vsf_sysutil_activate_noblock(fd); + } + retval = connect(fd, p_sockaddr, addrlen); + if (retval < 0 && errno == EINPROGRESS) + { + fd_set connect_fdset; + struct timeval timeout; + FD_ZERO(&connect_fdset); + FD_SET(fd, &connect_fdset); + timeout.tv_sec = wait_seconds; + timeout.tv_usec = 0; + do + { + retval = select(fd + 1, NULL, &connect_fdset, NULL, &timeout); + saved_errno = errno; + vsf_sysutil_check_pending_actions(kVSFSysUtilUnknown, 0, 0); + } + while (retval < 0 && saved_errno == EINTR); + if (retval == 0) + { + retval = -1; + errno = EAGAIN; + } + else + { + socklen_t socklen = sizeof(retval); + int sockoptret = getsockopt(fd, SOL_SOCKET, SO_ERROR, &retval, &socklen); + if (sockoptret != 0) + { + die("getsockopt"); + } + } + } + if (wait_seconds > 0) + { + vsf_sysutil_deactivate_noblock(fd); + } + return retval; +} + +void +vsf_sysutil_getsockname(int fd, struct vsf_sysutil_sockaddr** p_sockptr) +{ + struct vsf_sysutil_sockaddr the_addr; + int retval; + unsigned int socklen = sizeof(the_addr); + vsf_sysutil_sockaddr_clear(p_sockptr); + retval = getsockname(fd, &the_addr.u.u_sockaddr, &socklen); + if (retval != 0) + { + die("getsockname"); + } + if (the_addr.u.u_sockaddr.sa_family != AF_INET && + the_addr.u.u_sockaddr.sa_family != AF_INET6) + { + die("can only support ipv4 and ipv6 currently"); + } + vsf_sysutil_sockaddr_alloc(p_sockptr); + if (socklen > sizeof(the_addr)) + { + socklen = sizeof(the_addr); + } + vsf_sysutil_memcpy(*p_sockptr, &the_addr, socklen); +} + +void +vsf_sysutil_getpeername(int fd, struct vsf_sysutil_sockaddr** p_sockptr) +{ + struct vsf_sysutil_sockaddr the_addr; + int retval; + unsigned int socklen = sizeof(the_addr); + vsf_sysutil_sockaddr_clear(p_sockptr); + retval = getpeername(fd, &the_addr.u.u_sockaddr, &socklen); + if (retval != 0) + { + die("getpeername"); + } + if (the_addr.u.u_sockaddr.sa_family != AF_INET && + the_addr.u.u_sockaddr.sa_family != AF_INET6) + { + die("can only support ipv4 and ipv6 currently"); + } + vsf_sysutil_sockaddr_alloc(p_sockptr); + if (socklen > sizeof(the_addr)) + { + socklen = sizeof(the_addr); + } + vsf_sysutil_memcpy(*p_sockptr, &the_addr, socklen); +} + +void +vsf_sysutil_shutdown_failok(int fd) +{ + /* SHUT_RDWR is a relatively new addition */ + #ifndef SHUT_RDWR + #define SHUT_RDWR 2 + #endif + (void) shutdown(fd, SHUT_RDWR); +} + +void +vsf_sysutil_shutdown_read_failok(int fd) +{ + /* SHUT_RD is a relatively new addition */ + #ifndef SHUT_RD + #define SHUT_RD 0 + #endif + (void) shutdown(fd, SHUT_RD); +} + +void +vsf_sysutil_sockaddr_clear(struct vsf_sysutil_sockaddr** p_sockptr) +{ + if (*p_sockptr != NULL) + { + vsf_sysutil_free(*p_sockptr); + *p_sockptr = NULL; + } +} + +void +vsf_sysutil_sockaddr_alloc(struct vsf_sysutil_sockaddr** p_sockptr) +{ + vsf_sysutil_sockaddr_clear(p_sockptr); + *p_sockptr = vsf_sysutil_malloc(sizeof(**p_sockptr)); + vsf_sysutil_memclr(*p_sockptr, sizeof(**p_sockptr)); +} + +void +vsf_sysutil_sockaddr_alloc_ipv4(struct vsf_sysutil_sockaddr** p_sockptr) +{ + vsf_sysutil_sockaddr_alloc(p_sockptr); + (*p_sockptr)->u.u_sockaddr.sa_family = AF_INET; +} + +void +vsf_sysutil_sockaddr_alloc_ipv6(struct vsf_sysutil_sockaddr** p_sockptr) +{ + vsf_sysutil_sockaddr_alloc(p_sockptr); + (*p_sockptr)->u.u_sockaddr.sa_family = AF_INET6; +} + +void +vsf_sysutil_sockaddr_clone(struct vsf_sysutil_sockaddr** p_sockptr, + const struct vsf_sysutil_sockaddr* p_src) +{ + struct vsf_sysutil_sockaddr* p_sockaddr = 0; + vsf_sysutil_sockaddr_alloc(p_sockptr); + p_sockaddr = *p_sockptr; + if (p_src->u.u_sockaddr.sa_family == AF_INET) + { + p_sockaddr->u.u_sockaddr.sa_family = AF_INET; + vsf_sysutil_memcpy(&p_sockaddr->u.u_sockaddr_in.sin_addr, + &p_src->u.u_sockaddr_in.sin_addr, + sizeof(p_sockaddr->u.u_sockaddr_in.sin_addr)); + } + else if (p_src->u.u_sockaddr.sa_family == AF_INET6) + { + p_sockaddr->u.u_sockaddr.sa_family = AF_INET6; + vsf_sysutil_memcpy(&p_sockaddr->u.u_sockaddr_in6.sin6_addr, + &p_src->u.u_sockaddr_in6.sin6_addr, + sizeof(p_sockaddr->u.u_sockaddr_in6.sin6_addr)); + } + else + { + die("can only support ipv4 and ipv6 currently"); + } +} + +int +vsf_sysutil_sockaddr_addr_equal(const struct vsf_sysutil_sockaddr* p1, + const struct vsf_sysutil_sockaddr* p2) +{ + int family1 = p1->u.u_sockaddr.sa_family; + int family2 = p2->u.u_sockaddr.sa_family; + if (family1 != family2) + { + if (family1 == AF_INET && family2 == AF_INET6) + { + const void* p_ipv4_addr = vsf_sysutil_sockaddr_ipv6_v4(p2); + if (p_ipv4_addr && + !vsf_sysutil_memcmp(p_ipv4_addr, &p1->u.u_sockaddr_in.sin_addr, + sizeof(p1->u.u_sockaddr_in.sin_addr))) + { + return 1; + } + } + else if (family1 == AF_INET6 && family2 == AF_INET) + { + const void* p_ipv4_addr = vsf_sysutil_sockaddr_ipv6_v4(p1); + if (p_ipv4_addr && + !vsf_sysutil_memcmp(p_ipv4_addr, &p2->u.u_sockaddr_in.sin_addr, + sizeof(p2->u.u_sockaddr_in.sin_addr))) + { + return 1; + } + } + return 0; + } + if (family1 == AF_INET) + { + if (vsf_sysutil_memcmp(&p1->u.u_sockaddr_in.sin_addr, + &p2->u.u_sockaddr_in.sin_addr, + sizeof(p1->u.u_sockaddr_in.sin_addr)) == 0) + { + return 1; + } + } + else if (family1 == AF_INET6) + { + if (vsf_sysutil_memcmp(&p1->u.u_sockaddr_in6.sin6_addr, + &p2->u.u_sockaddr_in6.sin6_addr, + sizeof(p1->u.u_sockaddr_in6.sin6_addr)) == 0) + { + return 1; + } + } + return 0; +} + +int +vsf_sysutil_sockaddr_is_ipv6(const struct vsf_sysutil_sockaddr* p_sockaddr) +{ + if (p_sockaddr->u.u_sockaddr.sa_family == AF_INET6) + { + return 1; + } + return 0; +} + +void +vsf_sysutil_sockaddr_set_ipv4addr(struct vsf_sysutil_sockaddr* p_sockptr, + const unsigned char* p_raw) +{ + if (p_sockptr->u.u_sockaddr.sa_family == AF_INET) + { + vsf_sysutil_memcpy(&p_sockptr->u.u_sockaddr_in.sin_addr, p_raw, + sizeof(p_sockptr->u.u_sockaddr_in.sin_addr)); + } + else if (p_sockptr->u.u_sockaddr.sa_family == AF_INET6) + { + static struct vsf_sysutil_sockaddr* s_p_sockaddr; + vsf_sysutil_sockaddr_alloc_ipv4(&s_p_sockaddr); + vsf_sysutil_memcpy(&s_p_sockaddr->u.u_sockaddr_in.sin_addr, p_raw, + sizeof(&s_p_sockaddr->u.u_sockaddr_in.sin_addr)); + vsf_sysutil_memcpy(&p_sockptr->u.u_sockaddr_in6.sin6_addr, + vsf_sysutil_sockaddr_ipv4_v6(s_p_sockaddr), + sizeof(p_sockptr->u.u_sockaddr_in6.sin6_addr)); + } + else + { + bug("bad family"); + } +} + +void +vsf_sysutil_sockaddr_set_ipv6addr(struct vsf_sysutil_sockaddr* p_sockptr, + const unsigned char* p_raw) +{ + if (p_sockptr->u.u_sockaddr.sa_family == AF_INET6) + { + vsf_sysutil_memcpy(&p_sockptr->u.u_sockaddr_in6.sin6_addr, p_raw, + sizeof(p_sockptr->u.u_sockaddr_in6.sin6_addr)); + } + else + { + bug("bad family"); + } +} + +const void* +vsf_sysutil_sockaddr_ipv6_v4(const struct vsf_sysutil_sockaddr* p_addr) +{ + static char pattern[12] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xFF, 0xFF }; + const unsigned char* p_addr_start; + if (p_addr->u.u_sockaddr.sa_family != AF_INET6) + { + return 0; + } + if (vsf_sysutil_memcmp(pattern, &p_addr->u.u_sockaddr_in6.sin6_addr, 12)) + { + return 0; + } + p_addr_start = (const unsigned char*)&p_addr->u.u_sockaddr_in6.sin6_addr; + return &p_addr_start[12]; +} + +const void* +vsf_sysutil_sockaddr_ipv4_v6(const struct vsf_sysutil_sockaddr* p_addr) +{ + static char ret[16] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xFF, 0xFF }; + if (p_addr->u.u_sockaddr.sa_family != AF_INET) + { + return 0; + } + vsf_sysutil_memcpy(&ret[12], &p_addr->u.u_sockaddr_in.sin_addr, 4); + return ret; +} + +void* +vsf_sysutil_sockaddr_get_raw_addr(struct vsf_sysutil_sockaddr* p_sockptr) +{ + if (p_sockptr->u.u_sockaddr.sa_family == AF_INET) + { + return &p_sockptr->u.u_sockaddr_in.sin_addr; + } + else if (p_sockptr->u.u_sockaddr.sa_family == AF_INET6) + { + return &p_sockptr->u.u_sockaddr_in6.sin6_addr; + } + else + { + bug("bad family"); + } + return 0; +} + +unsigned int +vsf_sysutil_get_ipaddr_size(void) +{ + struct vsf_sysutil_sockaddr addr; + unsigned int size = sizeof(addr.u.u_sockaddr_in.sin_addr); + unsigned int size2 = sizeof(addr.u.u_sockaddr_in6.sin6_addr); + if (size2 > size) + { + size = size2; + } + return size; +} + +int +vsf_sysutil_get_ipsock(const struct vsf_sysutil_sockaddr* p_addr) +{ + if (p_addr->u.u_sockaddr.sa_family == AF_INET) + { + return vsf_sysutil_get_ipv4_sock(); + } + else if (p_addr->u.u_sockaddr.sa_family == AF_INET6) + { + return vsf_sysutil_get_ipv6_sock(); + } + else + { + bug("bad family"); + } + return -1; +} + +void +vsf_sysutil_sockaddr_set_any(struct vsf_sysutil_sockaddr* p_sockaddr) +{ + if (p_sockaddr->u.u_sockaddr.sa_family == AF_INET) + { + vsf_sysutil_memclr(&p_sockaddr->u.u_sockaddr_in.sin_addr, + sizeof(p_sockaddr->u.u_sockaddr_in.sin_addr)); + } + else if (p_sockaddr->u.u_sockaddr.sa_family == AF_INET6) + { + vsf_sysutil_memclr(&p_sockaddr->u.u_sockaddr_in6.sin6_addr, + sizeof(p_sockaddr->u.u_sockaddr_in6.sin6_addr)); + } + else + { + bug("bad family"); + } +} + +void +vsf_sysutil_sockaddr_set_port(struct vsf_sysutil_sockaddr* p_sockptr, + unsigned short the_port) +{ + if (p_sockptr->u.u_sockaddr.sa_family == AF_INET) + { + p_sockptr->u.u_sockaddr_in.sin_port = htons(the_port); + } + else if (p_sockptr->u.u_sockaddr.sa_family == AF_INET6) + { + p_sockptr->u.u_sockaddr_in6.sin6_port = htons(the_port); + } + else + { + bug("bad family"); + } +} + +int +vsf_sysutil_is_port_reserved(unsigned short the_port) +{ + if (the_port < IPPORT_RESERVED) + { + return 1; + } + return 0; +} + +const char* +vsf_sysutil_inet_ntop(const struct vsf_sysutil_sockaddr* p_sockptr) +{ + const struct sockaddr* p_sockaddr = &p_sockptr->u.u_sockaddr; + if (p_sockaddr->sa_family == AF_INET) + { + return inet_ntoa(p_sockptr->u.u_sockaddr_in.sin_addr); + } + else if (p_sockaddr->sa_family == AF_INET6) + { + static char inaddr_buf[64]; + const char* p_ret = inet_ntop(AF_INET6, + &p_sockptr->u.u_sockaddr_in6.sin6_addr, + inaddr_buf, sizeof(inaddr_buf)); + inaddr_buf[sizeof(inaddr_buf) - 1] = '\0'; + if (p_ret == NULL) + { + inaddr_buf[0] = '\0'; + } + return inaddr_buf; + } + else + { + die("can only support ipv4 and ipv6 currently"); + return 0; + } +} + +const char* +vsf_sysutil_inet_ntoa(const void* p_raw_addr) +{ + return inet_ntoa(*((struct in_addr*)p_raw_addr)); +} + +int +vsf_sysutil_inet_aton(const char* p_text, struct vsf_sysutil_sockaddr* p_addr) +{ + struct in_addr sin_addr; + if (p_addr->u.u_sockaddr.sa_family != AF_INET) + { + bug("bad family"); + } + if (inet_aton(p_text, &sin_addr)) + { + vsf_sysutil_memcpy(&p_addr->u.u_sockaddr_in.sin_addr, + &sin_addr, sizeof(p_addr->u.u_sockaddr_in.sin_addr)); + return 1; + } + else + { + return 0; + } +} + +void +vsf_sysutil_dns_resolve(struct vsf_sysutil_sockaddr** p_sockptr, + const char* p_name) +{ + struct hostent* hent = gethostbyname(p_name); + if (hent == NULL) + { + die2("cannot resolve host:", p_name); + } + vsf_sysutil_sockaddr_clear(p_sockptr); + if (hent->h_addrtype == AF_INET) + { + unsigned int len = hent->h_length; + if (len > sizeof((*p_sockptr)->u.u_sockaddr_in.sin_addr)) + { + len = sizeof((*p_sockptr)->u.u_sockaddr_in.sin_addr); + } + vsf_sysutil_sockaddr_alloc_ipv4(p_sockptr); + vsf_sysutil_memcpy(&(*p_sockptr)->u.u_sockaddr_in.sin_addr, + hent->h_addr_list[0], len); + } + else if (hent->h_addrtype == AF_INET6) + { + unsigned int len = hent->h_length; + if (len > sizeof((*p_sockptr)->u.u_sockaddr_in6.sin6_addr)) + { + len = sizeof((*p_sockptr)->u.u_sockaddr_in6.sin6_addr); + } + vsf_sysutil_sockaddr_alloc_ipv6(p_sockptr); + vsf_sysutil_memcpy(&(*p_sockptr)->u.u_sockaddr_in6.sin6_addr, + hent->h_addr_list[0], len); + } + else + { + die("gethostbyname(): neither IPv4 nor IPv6"); + } +} + +struct vsf_sysutil_user* +vsf_sysutil_getpwuid(const int uid) +{ + if (uid < 0) + { + bug("negative uid in vsf_sysutil_getpwuid"); + } + return (struct vsf_sysutil_user*) getpwuid((unsigned int) uid); +} + +struct vsf_sysutil_user* +vsf_sysutil_getpwnam(const char* p_user) +{ + return (struct vsf_sysutil_user*) getpwnam(p_user); +} + +const char* +vsf_sysutil_user_getname(const struct vsf_sysutil_user* p_user) +{ + const struct passwd* p_passwd = (const struct passwd*) p_user; + return p_passwd->pw_name; +} + +const char* +vsf_sysutil_user_get_homedir(const struct vsf_sysutil_user* p_user) +{ + const struct passwd* p_passwd = (const struct passwd*) p_user; + return p_passwd->pw_dir; +} + +int +vsf_sysutil_user_getuid(const struct vsf_sysutil_user* p_user) +{ + const struct passwd* p_passwd = (const struct passwd*) p_user; + return p_passwd->pw_uid; +} + +int +vsf_sysutil_user_getgid(const struct vsf_sysutil_user* p_user) +{ + const struct passwd* p_passwd = (const struct passwd*) p_user; + return p_passwd->pw_gid; +} + +struct vsf_sysutil_group* +vsf_sysutil_getgrgid(const int gid) +{ + if (gid < 0) + { + die("negative gid in vsf_sysutil_getgrgid"); + } + return (struct vsf_sysutil_group*) getgrgid((unsigned int) gid); +} + +const char* +vsf_sysutil_group_getname(const struct vsf_sysutil_group* p_group) +{ + const struct group* p_grp = (const struct group*) p_group; + return p_grp->gr_name; +} + +unsigned char +vsf_sysutil_get_random_byte(void) +{ + static int seeded; + unsigned int uint_res; + unsigned char c1, c2, c3, c4; + if (!seeded) + { + struct timeval tv; + int retval = gettimeofday(&tv, NULL); + if (retval != 0) + { + die("gettimeofday"); + } + srand((unsigned)tv.tv_usec); + seeded = 1; + } + uint_res = rand(); + c1 = uint_res & 0x000000ff; + c2 = (uint_res >> 8) & 0x000000ff; + c3 = (uint_res >> 16) & 0x000000ff; + c4 = (uint_res >> 24) & 0x000000ff; + return c1 ^ c2 ^ c3 ^ c4; +} + +int +vsf_sysutil_running_as_root(void) +{ + return (getuid() == 0); +} + +void +vsf_sysutil_setuid(const struct vsf_sysutil_user* p_user) +{ + const struct passwd* p_passwd = (const struct passwd*) p_user; + vsf_sysutil_setuid_numeric(p_passwd->pw_uid); +} + +void +vsf_sysutil_setuid_numeric(int uid) +{ + int retval = setuid(uid); + if (retval != 0) + { + die("setuid"); + } +} + +void +vsf_sysutil_setgid(const struct vsf_sysutil_user* p_user) +{ + const struct passwd* p_passwd = (const struct passwd*) p_user; + vsf_sysutil_setgid_numeric(p_passwd->pw_gid); +} + +void +vsf_sysutil_setgid_numeric(int gid) +{ + int retval = setgid(gid); + if (retval != 0) + { + die("setgid"); + } +} + +int +vsf_sysutil_geteuid(void) +{ + int retval = geteuid(); + if (retval < 0) + { + die("geteuid"); + } + return retval; +} + +int +vsf_sysutil_getegid(void) +{ + int retval = getegid(); + if (retval < 0) + { + die("getegid"); + } + return retval; +} + +void +vsf_sysutil_seteuid(const struct vsf_sysutil_user* p_user) +{ + const struct passwd* p_passwd = (const struct passwd*) p_user; + vsf_sysutil_seteuid_numeric(p_passwd->pw_uid); +} + +void +vsf_sysutil_setegid(const struct vsf_sysutil_user* p_user) +{ + const struct passwd* p_passwd = (const struct passwd*) p_user; + vsf_sysutil_setegid_numeric(p_passwd->pw_gid); +} + +void +vsf_sysutil_seteuid_numeric(int uid) +{ + /* setreuid() would seem to be more portable than seteuid() */ + int retval = setreuid(-1, uid); + if (retval != 0) + { + die("seteuid"); + } +} + +void +vsf_sysutil_setegid_numeric(int gid) +{ + /* setregid() would seem to be more portable than setegid() */ + int retval = setregid(-1, gid); + if (retval != 0) + { + die("setegid"); + } +} + +void +vsf_sysutil_clear_supp_groups(void) +{ + int retval = setgroups(0, NULL); + if (retval != 0) + { + die("setgroups"); + } +} + +void +vsf_sysutil_initgroups(const struct vsf_sysutil_user* p_user) +{ + const struct passwd* p_passwd = (const struct passwd*) p_user; + int retval = initgroups(p_passwd->pw_name, p_passwd->pw_gid); + if (retval != 0) + { + die("initgroups"); + } +} + +void +vsf_sysutil_chroot(const char* p_root_path) +{ + int retval = chroot(p_root_path); + if (retval != 0) + { + die("chroot"); + } +} + +unsigned int +vsf_sysutil_get_umask(void) +{ + return s_current_umask; +} + +void +vsf_sysutil_set_umask(unsigned int new_umask) +{ + s_current_umask = (new_umask & 0777); + (void) umask(s_current_umask); +} + +void +vsf_sysutil_make_session_leader(void) +{ + /* This makes us the leader if we are not already */ + (void) setsid(); + /* Check we're the leader */ + if (getpid() != getpgrp()) + { + die("not session leader"); + } +} + +void +vsf_sysutil_tzset(void) +{ + int retval; + char tzbuf[sizeof("+HHMM!")]; + time_t the_time = time(NULL); + struct tm* p_tm; + tzset(); + p_tm = localtime(&the_time); + if (p_tm == NULL) + { + die("localtime"); + } + /* Set our timezone in the TZ environment variable to cater for the fact + * that modern glibc does not cache /etc/localtime (which becomes inaccessible + * when we chroot(). + */ + retval = strftime(tzbuf, sizeof(tzbuf), "%z", p_tm); + tzbuf[sizeof(tzbuf) - 1] = '\0'; + if (retval == 5) + { + /* Static because putenv() does not copy the string. */ + static char envtz[sizeof("TZ=UTC-hh:mm")]; + /* Insert a colon so we have e.g. -05:00 instead of -0500 */ + tzbuf[5] = tzbuf[4]; + tzbuf[4] = tzbuf[3]; + tzbuf[3] = ':'; + /* Invert the sign - we just got the offset _from_ UTC but for TZ, we need + * the offset _to_ UTC. + */ + if (tzbuf[0] == '+') + { + tzbuf[0] = '-'; + } + else + { + tzbuf[0] = '+'; + } + snprintf(envtz, sizeof(envtz), "TZ=UTC%s", tzbuf); + putenv(envtz); + s_timezone = ((tzbuf[1] - '0') * 10 + (tzbuf[2] - '0')) * 60 * 60; + s_timezone += ((tzbuf[4] - '0') * 10 + (tzbuf[5] - '0')) * 60; + if (tzbuf[0] == '-') + { + s_timezone *= -1; + } + } +} + +const char* +vsf_sysutil_get_current_date(void) +{ + static char datebuf[64]; + time_t curr_time; + const struct tm* p_tm; + int i = 0; + vsf_sysutil_update_cached_time(); + curr_time = vsf_sysutil_get_cached_time_sec(); + p_tm = localtime(&curr_time); + if (strftime(datebuf, sizeof(datebuf), "%a %b!%d %H:%M:%S %Y", p_tm) == 0) + { + die("strftime"); + } + datebuf[sizeof(datebuf) - 1] = '\0'; + /* This hack is because %e in strftime() isn't so portable */ + while (datebuf[i] != '!' && datebuf[i] != '\0') + { + ++i; + } + if (datebuf[i] == '!') + { + datebuf[i] = ' '; + if (datebuf[i+1] == '0') + { + datebuf[i+1] = ' '; + } + } + return datebuf; +} + +void +vsf_sysutil_update_cached_time(void) +{ + if (gettimeofday(&s_current_time, NULL) != 0) + { + die("gettimeofday"); + } +} + +long +vsf_sysutil_get_cached_time_sec(void) +{ + return s_current_time.tv_sec; +} + +long +vsf_sysutil_get_cached_time_usec(void) +{ + return s_current_time.tv_usec; +} + +void +vsf_sysutil_qsort(void* p_base, unsigned int num_elem, unsigned int elem_size, + int (*p_compar)(const void *, const void *)) +{ + qsort(p_base, num_elem, elem_size, p_compar); +} + +void +vsf_sysutil_sleep(double seconds) +{ + int retval; + int saved_errno; + double fractional; + time_t secs; + struct timespec ts; + secs = (time_t) seconds; + fractional = seconds - (double) secs; + ts.tv_sec = secs; + ts.tv_nsec = (long) (fractional * (double) 1000000000); + do + { + retval = nanosleep(&ts, &ts); + saved_errno = errno; + vsf_sysutil_check_pending_actions(kVSFSysUtilUnknown, 0, 0); + } while (retval == -1 && saved_errno == EINTR); +} + +char* +vsf_sysutil_getenv(const char* p_var) +{ + return getenv(p_var); +} + +void +vsf_sysutil_openlog(void) +{ + int facility = LOG_DAEMON; +#ifdef LOG_FTP + facility = LOG_FTP; +#endif + openlog("vsftpd", LOG_NDELAY, facility); +} + +void +vsf_sysutil_syslog(const char* p_text, int severe) +{ + int prio = LOG_INFO; + if (severe) + { + prio = LOG_WARNING; + } + syslog(prio, "%s", p_text); +} + +long +vsf_sysutil_parse_time(const char* p_text) +{ + struct tm the_time; + unsigned int len = vsf_sysutil_strlen(p_text); + vsf_sysutil_memclr(&the_time, sizeof(the_time)); + if (len >= 8) + { + char yr[5]; + char mon[3]; + char day[3]; + vsf_sysutil_strcpy(yr, p_text, 5); + vsf_sysutil_strcpy(mon, p_text + 4, 3); + vsf_sysutil_strcpy(day, p_text + 6, 3); + the_time.tm_year = vsf_sysutil_atoi(yr) - 1900; + the_time.tm_mon = vsf_sysutil_atoi(mon) - 1; + the_time.tm_mday = vsf_sysutil_atoi(day); + } + if (len >= 14) + { + char hr[3]; + char mins[3]; + char sec[3]; + vsf_sysutil_strcpy(hr, p_text + 8, 3); + vsf_sysutil_strcpy(mins, p_text + 10, 3); + vsf_sysutil_strcpy(sec, p_text + 12, 3); + the_time.tm_hour = vsf_sysutil_atoi(hr); + the_time.tm_min = vsf_sysutil_atoi(mins); + the_time.tm_sec = vsf_sysutil_atoi(sec); + } + return mktime(&the_time); +} + +int +vsf_sysutil_setmodtime(const char* p_file, long the_time, int is_localtime) +{ + struct utimbuf new_times; + if (!is_localtime) + { + the_time -= s_timezone; + } + vsf_sysutil_memclr(&new_times, sizeof(new_times)); + new_times.actime = the_time; + new_times.modtime = the_time; + return utime(p_file, &new_times); +} + diff --git a/sysutil.h b/sysutil.h new file mode 100644 index 0000000..4fced38 --- /dev/null +++ b/sysutil.h @@ -0,0 +1,334 @@ +#ifndef VSF_SYSUTIL_H +#define VSF_SYSUTIL_H + +/* TODO: these functions need proper documenting! */ + +#ifndef VSF_FILESIZE_H +#include "filesize.h" +#endif + +/* Return value queries */ +int vsf_sysutil_retval_is_error(int retval); +enum EVSFSysUtilError +{ + kVSFSysUtilErrUnknown = 1, + kVSFSysUtilErrADDRINUSE, + kVSFSysUtilErrNOSYS, + kVSFSysUtilErrINTR, + kVSFSysUtilErrINVAL, + kVSFSysUtilErrOPNOTSUPP +}; +enum EVSFSysUtilError vsf_sysutil_get_error(void); + +/* Signal handling utility functions */ +enum EVSFSysUtilSignal +{ + kVSFSysUtilSigALRM = 1, + kVSFSysUtilSigTERM, + kVSFSysUtilSigCHLD, + kVSFSysUtilSigPIPE, + kVSFSysUtilSigURG, + kVSFSysUtilSigHUP +}; +enum EVSFSysUtilInterruptContext +{ + kVSFSysUtilUnknown, + kVSFSysUtilIO +}; +typedef void (*vsf_sighandle_t)(void*); +typedef void (*vsf_async_sighandle_t)(int); +typedef void (*vsf_context_io_t)(int, int, void*); + +void vsf_sysutil_install_null_sighandler(const enum EVSFSysUtilSignal sig); +void vsf_sysutil_install_sighandler(const enum EVSFSysUtilSignal, + vsf_sighandle_t handler, void* p_private); +void vsf_sysutil_install_async_sighandler(const enum EVSFSysUtilSignal sig, + vsf_async_sighandle_t handler); +void vsf_sysutil_default_sig(const enum EVSFSysUtilSignal sig); +void vsf_sysutil_install_io_handler(vsf_context_io_t handler, void* p_private); +void vsf_sysutil_uninstall_io_handler(void); +void vsf_sysutil_check_pending_actions( + const enum EVSFSysUtilInterruptContext context, int retval, int fd); +void vsf_sysutil_block_sig(const enum EVSFSysUtilSignal sig); +void vsf_sysutil_unblock_sig(const enum EVSFSysUtilSignal sig); + +/* Alarm setting/clearing utility functions */ +void vsf_sysutil_set_alarm(const unsigned int trigger_seconds); +void vsf_sysutil_clear_alarm(void); + +/* Directory related things */ +char* vsf_sysutil_getcwd(char* p_dest, const unsigned int buf_size); +int vsf_sysutil_mkdir(const char* p_dirname, const unsigned int mode); +int vsf_sysutil_rmdir(const char* p_dirname); +int vsf_sysutil_chdir(const char* p_dirname); +int vsf_sysutil_rename(const char* p_from, const char* p_to); + +struct vsf_sysutil_dir; +struct vsf_sysutil_dir* vsf_sysutil_opendir(const char* p_dirname); +void vsf_sysutil_closedir(struct vsf_sysutil_dir* p_dir); +const char* vsf_sysutil_next_dirent(struct vsf_sysutil_dir* p_dir); + +/* File create/open/close etc. */ +enum EVSFSysUtilOpenMode +{ + kVSFSysUtilOpenReadOnly = 1, + kVSFSysUtilOpenWriteOnly, + kVSFSysUtilOpenReadWrite +}; +int vsf_sysutil_open_file(const char* p_filename, + const enum EVSFSysUtilOpenMode); +/* Fails if file already exists */ +int vsf_sysutil_create_file(const char* p_filename); +/* Overwrites if file already exists */ +int vsf_sysutil_create_overwrite_file(const char* p_filename); +/* Creates file or appends if already exists */ +int vsf_sysutil_create_append_file(const char* p_filename); +/* Creates or appends */ +int vsf_sysutil_create_or_open_file(const char* p_filename, unsigned int mode); +void vsf_sysutil_dupfd2(int old_fd, int new_fd); +void vsf_sysutil_close(int fd); +int vsf_sysutil_close_failok(int fd); +int vsf_sysutil_unlink(const char* p_dead); +int vsf_sysutil_write_access(const char* p_filename); + +/* Reading and writing */ +void vsf_sysutil_lseek_to(const int fd, filesize_t seek_pos); +filesize_t vsf_sysutil_get_file_offset(const int file_fd); +int vsf_sysutil_read(const int fd, void* p_buf, const unsigned int size); +int vsf_sysutil_write(const int fd, const void* p_buf, + const unsigned int size); +/* Reading and writing, with handling of interrupted system calls and partial + * reads/writes. Slightly more usable than the standard UNIX API! + */ +int vsf_sysutil_read_loop(const int fd, void* p_buf, unsigned int size); +int vsf_sysutil_write_loop(const int fd, const void* p_buf, unsigned int size); + +struct vsf_sysutil_statbuf; +int vsf_sysutil_stat(const char* p_name, struct vsf_sysutil_statbuf** p_ptr); +int vsf_sysutil_lstat(const char* p_name, struct vsf_sysutil_statbuf** p_ptr); +void vsf_sysutil_fstat(int fd, struct vsf_sysutil_statbuf** p_ptr); +void vsf_sysutil_dir_stat(const struct vsf_sysutil_dir* p_dir, + struct vsf_sysutil_statbuf** p_ptr); +int vsf_sysutil_statbuf_is_regfile(const struct vsf_sysutil_statbuf* p_stat); +int vsf_sysutil_statbuf_is_symlink(const struct vsf_sysutil_statbuf* p_stat); +int vsf_sysutil_statbuf_is_socket(const struct vsf_sysutil_statbuf* p_stat); +int vsf_sysutil_statbuf_is_dir(const struct vsf_sysutil_statbuf* p_stat); +filesize_t vsf_sysutil_statbuf_get_size( + const struct vsf_sysutil_statbuf* p_stat); +const char* vsf_sysutil_statbuf_get_perms( + const struct vsf_sysutil_statbuf* p_stat); +const char* vsf_sysutil_statbuf_get_date( + const struct vsf_sysutil_statbuf* p_stat, int use_localtime); +const char* vsf_sysutil_statbuf_get_numeric_date( + const struct vsf_sysutil_statbuf* p_stat, int use_localtime); +unsigned int vsf_sysutil_statbuf_get_links( + const struct vsf_sysutil_statbuf* p_stat); +int vsf_sysutil_statbuf_get_uid(const struct vsf_sysutil_statbuf* p_stat); +int vsf_sysutil_statbuf_get_gid(const struct vsf_sysutil_statbuf* p_stat); +int vsf_sysutil_statbuf_is_readable_other( + const struct vsf_sysutil_statbuf* p_stat); +const char* vsf_sysutil_statbuf_get_sortkey_mtime( + const struct vsf_sysutil_statbuf* p_stat); + +int vsf_sysutil_chmod(const char* p_filename, unsigned int mode); +void vsf_sysutil_fchown(const int fd, const int uid, const int gid); +void vsf_sysutil_fchmod(const int fd, unsigned int mode); +int vsf_sysutil_readlink(const char* p_filename, char* p_dest, + unsigned int bufsiz); + +/* Get / unget various locks. Lock gets are blocking. Write locks are + * exclusive; read locks are shared. + */ +int vsf_sysutil_lock_file_write(int fd); +int vsf_sysutil_lock_file_read(int fd); +void vsf_sysutil_unlock_file(int fd); + +/* Mapping/unmapping */ +enum EVSFSysUtilMapPermission +{ + kVSFSysUtilMapProtReadOnly = 1, + kVSFSysUtilMapProtNone +}; +void vsf_sysutil_memprotect(void* p_addr, unsigned int len, + const enum EVSFSysUtilMapPermission perm); +void vsf_sysutil_memunmap(void* p_start, unsigned int length); + +/* Memory allocating/freeing */ +void* vsf_sysutil_malloc(unsigned int size); +void* vsf_sysutil_realloc(void* p_ptr, unsigned int size); +void vsf_sysutil_free(void* p_ptr); + +/* Process creation/exit/process handling */ +unsigned int vsf_sysutil_getpid(void); +int vsf_sysutil_fork(void); +int vsf_sysutil_fork_failok(void); +void vsf_sysutil_exit(int exit_code); +struct vsf_sysutil_wait_retval +{ + int PRIVATE_HANDS_OFF_syscall_retval; + int PRIVATE_HANDS_OFF_exit_status; +}; +struct vsf_sysutil_wait_retval vsf_sysutil_wait(void); +int vsf_sysutil_wait_reap_one(void); +int vsf_sysutil_wait_get_retval( + const struct vsf_sysutil_wait_retval* p_waitret); +int vsf_sysutil_wait_exited_normally( + const struct vsf_sysutil_wait_retval* p_waitret); +int vsf_sysutil_wait_get_exitcode( + const struct vsf_sysutil_wait_retval* p_waitret); + +/* Various string functions */ +unsigned int vsf_sysutil_strlen(const char* p_text); +char* vsf_sysutil_strdup(const char* p_str); +void vsf_sysutil_memclr(void* p_dest, unsigned int size); +void vsf_sysutil_memcpy(void* p_dest, const void* p_src, + const unsigned int size); +void vsf_sysutil_strcpy(char* p_dest, const char* p_src, unsigned int maxsize); +int vsf_sysutil_memcmp(const void* p_src1, const void* p_src2, + unsigned int size); +int vsf_sysutil_strcmp(const char* p_src1, const char* p_src2); +int vsf_sysutil_atoi(const char* p_str); +filesize_t vsf_sysutil_a_to_filesize_t(const char* p_str); +const char* vsf_sysutil_ulong_to_str(unsigned long the_ulong); +const char* vsf_sysutil_filesize_t_to_str(filesize_t the_filesize); +const char* vsf_sysutil_double_to_str(double the_double); +const char* vsf_sysutil_uint_to_octal(unsigned int the_uint); +unsigned int vsf_sysutil_octal_to_uint(const char* p_str); +int vsf_sysutil_toupper(int the_char); +int vsf_sysutil_isspace(int the_char); +int vsf_sysutil_isprint(int the_char); +int vsf_sysutil_isalnum(int the_char); +int vsf_sysutil_isdigit(int the_char); + +/* Socket handling */ +struct vsf_sysutil_sockaddr; +struct vsf_sysutil_socketpair_retval +{ + int socket_one; + int socket_two; +}; +void vsf_sysutil_sockaddr_alloc(struct vsf_sysutil_sockaddr** p_sockptr); +void vsf_sysutil_sockaddr_clear(struct vsf_sysutil_sockaddr** p_sockptr); +void vsf_sysutil_sockaddr_alloc_ipv4(struct vsf_sysutil_sockaddr** p_sockptr); +void vsf_sysutil_sockaddr_alloc_ipv6(struct vsf_sysutil_sockaddr** p_sockptr); +void vsf_sysutil_sockaddr_clone( + struct vsf_sysutil_sockaddr** p_sockptr, + const struct vsf_sysutil_sockaddr* p_src); +int vsf_sysutil_sockaddr_addr_equal(const struct vsf_sysutil_sockaddr* p1, + const struct vsf_sysutil_sockaddr* p2); +int vsf_sysutil_sockaddr_is_ipv6( + const struct vsf_sysutil_sockaddr* p_sockaddr); +void vsf_sysutil_sockaddr_set_ipv4addr(struct vsf_sysutil_sockaddr* p_sockptr, + const unsigned char* p_raw); +void vsf_sysutil_sockaddr_set_ipv6addr(struct vsf_sysutil_sockaddr* p_sockptr, + const unsigned char* p_raw); +void vsf_sysutil_sockaddr_set_any(struct vsf_sysutil_sockaddr* p_sockaddr); +void vsf_sysutil_sockaddr_set_port(struct vsf_sysutil_sockaddr* p_sockptr, + unsigned short the_port); +int vsf_sysutil_is_port_reserved(unsigned short port); +int vsf_sysutil_get_ipsock(const struct vsf_sysutil_sockaddr* p_sockaddr); +unsigned int vsf_sysutil_get_ipaddr_size(void); +void* vsf_sysutil_sockaddr_get_raw_addr( + struct vsf_sysutil_sockaddr* p_sockaddr); +const void* vsf_sysutil_sockaddr_ipv6_v4( + const struct vsf_sysutil_sockaddr* p_sockaddr); +const void* vsf_sysutil_sockaddr_ipv4_v6( + const struct vsf_sysutil_sockaddr* p_sockaddr); +int vsf_sysutil_get_ipv4_sock(void); +int vsf_sysutil_get_ipv6_sock(void); +struct vsf_sysutil_socketpair_retval + vsf_sysutil_unix_stream_socketpair(void); +int vsf_sysutil_bind(int fd, const struct vsf_sysutil_sockaddr* p_sockptr); +void vsf_sysutil_listen(int fd, const unsigned int backlog); +void vsf_sysutil_getsockname(int fd, struct vsf_sysutil_sockaddr** p_sockptr); +void vsf_sysutil_getpeername(int fd, struct vsf_sysutil_sockaddr** p_sockptr); +int vsf_sysutil_accept_timeout(int fd, struct vsf_sysutil_sockaddr* p_sockaddr, + unsigned int wait_seconds); +int vsf_sysutil_connect_timeout(int fd, + const struct vsf_sysutil_sockaddr* p_sockaddr, + unsigned int wait_seconds); +void vsf_sysutil_dns_resolve(struct vsf_sysutil_sockaddr** p_sockptr, + const char* p_name); +/* Option setting on sockets */ +void vsf_sysutil_activate_keepalive(int fd); +void vsf_sysutil_set_iptos_throughput(int fd); +void vsf_sysutil_activate_reuseaddr(int fd); +void vsf_sysutil_set_nodelay(int fd); +void vsf_sysutil_activate_sigurg(int fd); +void vsf_sysutil_activate_oobinline(int fd); +void vsf_sysutil_activate_linger(int fd); +void vsf_sysutil_deactivate_linger_failok(int fd); +void vsf_sysutil_activate_noblock(int fd); +void vsf_sysutil_deactivate_noblock(int fd); +/* This does SHUT_RDWR */ +void vsf_sysutil_shutdown_failok(int fd); +/* And this does SHUT_RD */ +void vsf_sysutil_shutdown_read_failok(int fd); +int vsf_sysutil_recv_peek(const int fd, void* p_buf, unsigned int len); + +const char* vsf_sysutil_inet_ntop( + const struct vsf_sysutil_sockaddr* p_sockptr); +const char* vsf_sysutil_inet_ntoa(const void* p_raw_addr); +int vsf_sysutil_inet_aton( + const char* p_text, struct vsf_sysutil_sockaddr* p_addr); + +/* User database queries etc. */ +struct vsf_sysutil_user; +struct vsf_sysutil_group; + +struct vsf_sysutil_user* vsf_sysutil_getpwuid(const int uid); +struct vsf_sysutil_user* vsf_sysutil_getpwnam(const char* p_user); +const char* vsf_sysutil_user_getname(const struct vsf_sysutil_user* p_user); +const char* vsf_sysutil_user_get_homedir( + const struct vsf_sysutil_user* p_user); +int vsf_sysutil_user_getuid(const struct vsf_sysutil_user* p_user); +int vsf_sysutil_user_getgid(const struct vsf_sysutil_user* p_user); + +struct vsf_sysutil_group* vsf_sysutil_getgrgid(const int gid); +const char* vsf_sysutil_group_getname(const struct vsf_sysutil_group* p_group); + +/* More random things */ +unsigned int vsf_sysutil_getpagesize(void); +unsigned char vsf_sysutil_get_random_byte(void); +unsigned int vsf_sysutil_get_umask(void); +void vsf_sysutil_set_umask(unsigned int umask); +void vsf_sysutil_make_session_leader(void); +void vsf_sysutil_tzset(void); +const char* vsf_sysutil_get_current_date(void); +void vsf_sysutil_qsort(void* p_base, unsigned int num_elem, + unsigned int elem_size, + int (*p_compar)(const void *, const void *)); +char* vsf_sysutil_getenv(const char* p_var); +typedef void (*exitfunc_t)(void); +void vsf_sysutil_set_exit_func(exitfunc_t exitfunc); + +/* Syslogging (bah) */ +void vsf_sysutil_openlog(void); +void vsf_sysutil_syslog(const char* p_text, int severe); + +/* Credentials handling */ +int vsf_sysutil_running_as_root(void); +void vsf_sysutil_setuid(const struct vsf_sysutil_user* p_user); +void vsf_sysutil_setgid(const struct vsf_sysutil_user* p_user); +void vsf_sysutil_setuid_numeric(int uid); +void vsf_sysutil_setgid_numeric(int gid); +int vsf_sysutil_geteuid(void); +int vsf_sysutil_getegid(void); +void vsf_sysutil_seteuid(const struct vsf_sysutil_user* p_user); +void vsf_sysutil_setegid(const struct vsf_sysutil_user* p_user); +void vsf_sysutil_seteuid_numeric(int uid); +void vsf_sysutil_setegid_numeric(int gid); +void vsf_sysutil_clear_supp_groups(void); +void vsf_sysutil_initgroups(const struct vsf_sysutil_user* p_user); +void vsf_sysutil_chroot(const char* p_root_path); + +/* Time handling */ +void vsf_sysutil_update_cached_time(void); +long vsf_sysutil_get_cached_time_sec(void); +long vsf_sysutil_get_cached_time_usec(void); +long vsf_sysutil_parse_time(const char* p_text); +void vsf_sysutil_sleep(double seconds); +int vsf_sysutil_setmodtime(const char* p_file, long the_time, int is_localtime); + +#endif /* VSF_SYSUTIL_H */ + diff --git a/tcpwrap.c b/tcpwrap.c new file mode 100644 index 0000000..3b37e04 --- /dev/null +++ b/tcpwrap.c @@ -0,0 +1,49 @@ +/* + * Part of Very Secure FTPd + * Licence: GPL v2 + * Author: Chris Evans + * tcpwrap.c + * + * Routines to encapsulate the usage of tcp_wrappers. + */ + +#include "tcpwrap.h" +#include "builddefs.h" +#include "utility.h" + +#ifdef VSF_BUILD_TCPWRAPPERS + #include +#endif + +#ifdef VSF_BUILD_TCPWRAPPERS + +#include + +int deny_severity = LOG_WARNING; +int allow_severity = LOG_INFO; + +int +vsf_tcp_wrapper_ok(int remote_fd) +{ + struct request_info req; + request_init(&req, RQ_DAEMON, "vsftpd", RQ_FILE, remote_fd, 0); + fromhost(&req); + if (!hosts_access(&req)) + { + return 0; + } + return 1; +} + +#else /* VSF_BUILD_TCPWRAPPERS */ + +int +vsf_tcp_wrapper_ok(int remote_fd) +{ + (void) remote_fd; + die("tcp_wrappers is set to YES but no tcp wrapper support compiled in"); + return 0; +} + +#endif /* VSF_BUILD_TCPWRAPPERS */ + diff --git a/tcpwrap.h b/tcpwrap.h new file mode 100644 index 0000000..d7dc048 --- /dev/null +++ b/tcpwrap.h @@ -0,0 +1,7 @@ +#ifndef VSF_TCPWRAP_H +#define VSF_TCPWRAP_H + +int vsf_tcp_wrapper_ok(int remote_fd); + +#endif /* VSF_TCPWRAP_H */ + diff --git a/tunables.c b/tunables.c new file mode 100644 index 0000000..0c45a2e --- /dev/null +++ b/tunables.c @@ -0,0 +1,144 @@ +/* + * Part of Very Secure FTPd + * Licence: GPL v2 + * Author: Chris Evans + * tunables.c + */ + +#include "tunables.h" + +int tunable_anonymous_enable = 1; +int tunable_local_enable = 0; +int tunable_pasv_enable = 1; +int tunable_port_enable = 1; +int tunable_chroot_local_user = 0; +int tunable_write_enable = 0; +int tunable_anon_upload_enable = 0; +int tunable_anon_mkdir_write_enable = 0; +int tunable_anon_other_write_enable = 0; +int tunable_chown_uploads = 0; +int tunable_connect_from_port_20 = 0; +int tunable_xferlog_enable = 0; +int tunable_dirmessage_enable = 0; +int tunable_anon_world_readable_only = 1; +int tunable_async_abor_enable = 0; +int tunable_ascii_upload_enable = 0; +int tunable_ascii_download_enable = 0; +int tunable_one_process_model = 0; +int tunable_xferlog_std_format = 0; +int tunable_pasv_promiscuous = 0; +int tunable_deny_email_enable = 0; +int tunable_chroot_list_enable = 0; +int tunable_setproctitle_enable = 0; +int tunable_text_userdb_names = 0; +int tunable_ls_recurse_enable = 0; +int tunable_log_ftp_protocol = 0; +int tunable_guest_enable = 0; +int tunable_userlist_enable = 0; +int tunable_userlist_deny = 1; +int tunable_use_localtime = 0; +int tunable_check_shell = 1; +int tunable_hide_ids = 0; +int tunable_listen = 0; +int tunable_port_promiscuous = 0; +int tunable_passwd_chroot_enable = 0; +int tunable_no_anon_password = 0; +int tunable_tcp_wrappers = 0; +int tunable_use_sendfile = 1; +int tunable_force_dot_files = 0; +int tunable_listen_ipv6 = 0; +int tunable_dual_log_enable = 0; +int tunable_syslog_enable = 0; +int tunable_background = 0; +int tunable_virtual_use_local_privs = 0; +int tunable_session_support = 0; +int tunable_download_enable = 1; +int tunable_dirlist_enable = 1; +int tunable_chmod_enable = 1; +int tunable_secure_email_list_enable = 0; +int tunable_run_as_launching_user = 0; +int tunable_no_log_lock = 0; +int tunable_ssl_enable = 0; +int tunable_allow_anon_ssl = 0; +int tunable_force_local_logins_ssl = 1; +int tunable_force_local_data_ssl = 1; +int tunable_sslv2 = 0; +int tunable_sslv3 = 0; +int tunable_tlsv1 = 1; +int tunable_tilde_user_enable = 0; +int tunable_force_anon_logins_ssl = 0; +int tunable_force_anon_data_ssl = 0; +int tunable_mdtm_write = 1; +int tunable_lock_upload_files = 1; +int tunable_pasv_addr_resolve = 0; +int tunable_plugin_architecture = 1; + +unsigned int tunable_accept_timeout = 60; +unsigned int tunable_connect_timeout = 60; +unsigned int tunable_local_umask = 077; +unsigned int tunable_anon_umask = 077; +unsigned int tunable_ftp_data_port = 20; +unsigned int tunable_idle_session_timeout = 300; +unsigned int tunable_data_connection_timeout = 300; +/* IPPORT_USERRESERVED + 1 */ +unsigned int tunable_pasv_min_port = 5001; +unsigned int tunable_pasv_max_port = 0; +unsigned int tunable_anon_max_rate = 0; +unsigned int tunable_local_max_rate = 0; +/* IPPORT_FTP */ +unsigned int tunable_listen_port = 21; +unsigned int tunable_max_clients = 0; +/* -rw-rw-rw- */ +unsigned int tunable_file_open_mode = 0666; +unsigned int tunable_max_per_ip = 0; +unsigned int tunable_trans_chunk_size = 0; +unsigned int tunable_delay_failed_login = 1; +unsigned int tunable_delay_successful_login = 0; +unsigned int tunable_max_login_fails = 3; + +const char* tunable_secure_chroot_dir = "/usr/share/empty"; +const char* tunable_ftp_username = "ftp"; +const char* tunable_chown_username = "root"; +const char* tunable_xferlog_file = "/var/log/xferlog"; +const char* tunable_vsftpd_log_file = "/var/log/vsftpd.log"; +const char* tunable_message_file = ".message"; +const char* tunable_nopriv_user = "nobody"; +const char* tunable_ftpd_banner = 0; +const char* tunable_banned_email_file = "/etc/vsftpd.banned_emails"; +const char* tunable_chroot_list_file = "/etc/vsftpd.chroot_list"; +const char* tunable_pam_service_name = "ftp"; +const char* tunable_guest_username = "ftp"; +const char* tunable_userlist_file = "/etc/vsftpd.user_list"; +const char* tunable_anon_root = 0; +const char* tunable_local_root = 0; +const char* tunable_banner_file = 0; +const char* tunable_pasv_address = 0; +const char* tunable_listen_address = 0; +const char* tunable_user_config_dir = 0; +const char* tunable_listen_address6 = 0; +const char* tunable_cmds_allowed = 0; +const char* tunable_hide_file = 0; +const char* tunable_deny_file = 0; +const char* tunable_user_sub_token = 0; +const char* tunable_email_password_file = "/etc/vsftpd.email_passwords"; +const char* tunable_rsa_cert_file = "/usr/share/ssl/certs/vsftpd.pem"; +const char* tunable_dsa_cert_file = 0; +const char* tunable_ssl_ciphers = "DES-CBC3-SHA"; +const char* tunable_rsa_private_key_file = 0; +const char* tunable_dsa_private_key_file = 0; +const char* tunable_sql_plugin = "GenerateErrorPlugin"; + +/* DB plugins vars */ +const char* tunable_mysql_database_username="scott"; +const char* tunable_mysql_database_password="tiger"; +const char* tunable_mysql_database_host="localhost"; +unsigned int tunable_mysql_database_port=0; //use built-in value +const char* tunable_mysql_database_socketname=0; //use built-in value +const char* tunable_mysql_database_dbname="vsftp"; +unsigned int tunable_mysql_database_connectionflags=0; //none + +const char* tunable_pgsql_database_username="scott"; +const char* tunable_pgsql_database_password="tiger"; +const char* tunable_pgsql_database_host="localhost"; +unsigned int tunable_pgsql_database_port=0; +const char* tunable_pgsql_database_dbname="vsftp"; diff --git a/tunables.h b/tunables.h new file mode 100644 index 0000000..0fb05f7 --- /dev/null +++ b/tunables.h @@ -0,0 +1,142 @@ +#ifndef VSF_TUNABLES_H +#define VSF_TUNABLES_H + +/* Configurable preferences */ +/* Booleans */ +extern int tunable_anonymous_enable; /* Allow anon logins */ +extern int tunable_local_enable; /* Allow local logins */ +extern int tunable_pasv_enable; /* Allow PASV */ +extern int tunable_port_enable; /* Allow PORT */ +extern int tunable_chroot_local_user; /* Restrict local to home dir */ +extern int tunable_write_enable; /* Global enable writes */ +extern int tunable_anon_upload_enable; /* Enable STOR for anon users */ +extern int tunable_anon_mkdir_write_enable; /* MKD for anon */ +extern int tunable_anon_other_write_enable; /* APPE DELE RMD RNFR for anon */ +extern int tunable_chown_uploads; /* chown() anon uploaded files */ +extern int tunable_connect_from_port_20; /* PORT connects from port 20 */ +extern int tunable_xferlog_enable; /* Log transfers to a file */ +extern int tunable_dirmessage_enable; /* Look for + output .message */ +extern int tunable_anon_world_readable_only; /* Only serve world readable */ +extern int tunable_async_abor_enable; /* Enable async ABOR requests */ +extern int tunable_ascii_upload_enable; /* Permit ASCII upload */ +extern int tunable_ascii_download_enable; /* Permit ASCII download */ +extern int tunable_one_process_model; /* Go faster stripes ;-) */ +extern int tunable_xferlog_std_format; /* Log details like wu-ftpd */ +extern int tunable_pasv_promiscuous; /* Allow any PASV connect IP */ +extern int tunable_deny_email_enable; /* Ban a list of anon e-mails */ +extern int tunable_chroot_list_enable; /* chroot() based on list file */ +extern int tunable_setproctitle_enable; /* Try to use setproctitle() */ +extern int tunable_text_userdb_names; /* For "ls", lookup text names */ +extern int tunable_ls_recurse_enable; /* Allow ls -R */ +extern int tunable_log_ftp_protocol; /* Log FTP requests/responses */ +extern int tunable_guest_enable; /* Remap guest users */ +extern int tunable_userlist_enable; /* Explicit user allow or deny */ +extern int tunable_userlist_deny; /* Is user list allow or deny? */ +extern int tunable_use_localtime; /* Use local time or GMT? */ +extern int tunable_check_shell; /* Use /etc/shells for non-PAM */ +extern int tunable_hide_ids; /* Show "ftp" in ls listings */ +extern int tunable_listen; /* Standalone (no inetd) mode? */ +extern int tunable_port_promiscuous; /* Any any PORT connect IP */ +extern int tunable_passwd_chroot_enable; /* chroot() based on passwd */ +extern int tunable_no_anon_password; /* Do not ask for anon pword */ +extern int tunable_tcp_wrappers; /* Standalone: do tcp wrappers */ +extern int tunable_use_sendfile; /* Use sendfile() if we can */ +extern int tunable_force_dot_files; /* Show dotfiles without -a */ +extern int tunable_listen_ipv6; /* Standalone with IPv6 listen */ +extern int tunable_dual_log_enable; /* Log vsftpd.log AND xferlog */ +extern int tunable_syslog_enable; /* Use syslog not vsftpd.log */ +extern int tunable_background; /* Background listener process */ +extern int tunable_virtual_use_local_privs; /* Virtual user => local privs */ +extern int tunable_session_support; /* utmp, wtmp, pam_session */ +extern int tunable_download_enable; /* Can download anything? */ +extern int tunable_dirlist_enable; /* Can see any dirs? */ +extern int tunable_chmod_enable; /* Is CHMOD allowed? (local) */ +extern int tunable_secure_email_list_enable; /* Require specific anon email */ +extern int tunable_run_as_launching_user; /* Runs as launching user */ +extern int tunable_no_log_lock; /* Don't lock log files */ +extern int tunable_ssl_enable; /* Allow SSL/TLS AUTH */ +extern int tunable_allow_anon_ssl; /* Allow anonymous use of SSL */ +extern int tunable_force_local_logins_ssl; /* Require local logins use SSL */ +extern int tunable_force_local_data_ssl; /* Require local data uses SSL */ +extern int tunable_sslv2; /* Allow SSLv2 */ +extern int tunable_sslv3; /* Allow SSLv3 */ +extern int tunable_tlsv1; /* Allow TLSv1 */ +extern int tunable_tilde_user_enable; /* Support e.g. ~chris */ +extern int tunable_force_anon_logins_ssl; /* Require anon logins use SSL */ +extern int tunable_force_anon_data_ssl; /* Require anon data uses SSL */ +extern int tunable_mdtm_write; /* Allow MDTM to set timestamps */ +extern int tunable_lock_upload_files; /* Lock uploading files */ +extern int tunable_pasv_addr_resolve; /* DNS resolve pasv_addr */ +extern int tunable_plugin_architecture; /* Enables the plugin subsystem */ + +/* Integer/numeric defines */ +extern unsigned int tunable_accept_timeout; +extern unsigned int tunable_connect_timeout; +extern unsigned int tunable_local_umask; +extern unsigned int tunable_anon_umask; +extern unsigned int tunable_ftp_data_port; +extern unsigned int tunable_idle_session_timeout; +extern unsigned int tunable_data_connection_timeout; +extern unsigned int tunable_pasv_min_port; +extern unsigned int tunable_pasv_max_port; +extern unsigned int tunable_anon_max_rate; +extern unsigned int tunable_local_max_rate; +extern unsigned int tunable_listen_port; +extern unsigned int tunable_max_clients; +extern unsigned int tunable_file_open_mode; +extern unsigned int tunable_max_per_ip; +extern unsigned int tunable_trans_chunk_size; +extern unsigned int tunable_delay_failed_login; +extern unsigned int tunable_delay_successful_login; +extern unsigned int tunable_max_login_fails; + +/* String defines */ +extern const char* tunable_secure_chroot_dir; +extern const char* tunable_ftp_username; +extern const char* tunable_chown_username; +extern const char* tunable_xferlog_file; +extern const char* tunable_vsftpd_log_file; +extern const char* tunable_message_file; +extern const char* tunable_nopriv_user; +extern const char* tunable_ftpd_banner; +extern const char* tunable_banned_email_file; +extern const char* tunable_chroot_list_file; +extern const char* tunable_pam_service_name; +extern const char* tunable_guest_username; +extern const char* tunable_userlist_file; +extern const char* tunable_anon_root; +extern const char* tunable_local_root; +extern const char* tunable_banner_file; +extern const char* tunable_pasv_address; +extern const char* tunable_listen_address; +extern const char* tunable_user_config_dir; +extern const char* tunable_listen_address6; +extern const char* tunable_cmds_allowed; +extern const char* tunable_hide_file; +extern const char* tunable_deny_file; +extern const char* tunable_user_sub_token; +extern const char* tunable_email_password_file; +extern const char* tunable_rsa_cert_file; +extern const char* tunable_dsa_cert_file; +extern const char* tunable_ssl_ciphers; +extern const char* tunable_rsa_private_key_file; +extern const char* tunable_dsa_private_key_file; +extern const char* tunable_sql_plugin; /* Enables the sql management and administration plugin */ + +/* DB plugins defines */ +extern const char* tunable_mysql_database_username; +extern const char* tunable_mysql_database_password; +extern const char* tunable_mysql_database_host; +extern unsigned int tunable_mysql_database_port; +extern const char* tunable_mysql_database_dbname; +extern const char* tunable_mysql_database_socketname; +extern unsigned int tunable_mysql_database_connectionflags; + +extern const char* tunable_pgsql_database_username; +extern const char* tunable_pgsql_database_password; +extern const char* tunable_pgsql_database_host; +extern unsigned int tunable_pgsql_database_port; +extern const char* tunable_pgsql_database_dbname; + +#endif /* VSF_TUNABLES_H */ + diff --git a/twoprocess.c b/twoprocess.c new file mode 100644 index 0000000..012c64b --- /dev/null +++ b/twoprocess.c @@ -0,0 +1,492 @@ +/* + * Part of Very Secure FTPd + * License: GPL v2 + * Author: Chris Evans + * twoprocess.c + * + * Code implementing the standard, secure two process security model. + */ + +#include "twoprocess.h" +#include "privops.h" +#include "prelogin.h" +#include "postlogin.h" +#include "postprivparent.h" +#include "session.h" +#include "privsock.h" +#include "secutil.h" +#include "filestr.h" +#include "str.h" +#include "sysstr.h" +#include "utility.h" +#include "tunables.h" +#include "defs.h" +#include "parseconf.h" +#include "ssl.h" +#include "readwrite.h" +#include "sysutil.h" +#include "sysdeputil.h" +#include +#include + +void drop_all_privs(void); +static void handle_sigchld(int duff); +static void process_login_req(struct vsf_session* p_sess); +static void process_ssl_slave_req(struct vsf_session* p_sess); +static void common_do_login(struct vsf_session* p_sess, + const struct mystr* p_user_str, int do_chroot, + int anon); +static void handle_per_user_config(const struct mystr* p_user_str); +static void calculate_chdir_dir(int anon, struct mystr* p_userdir_str, + struct mystr* p_chroot_str, + struct mystr* p_chdir_str, + const struct mystr* p_user_str, + const struct mystr* p_orig_user_str); + +extern int slave_pid; +extern void handle_slave_signal(); + +static void +handle_sigchld(int duff) +{ + #warning TODO : move this to vsf_sysutil + if (slave_pid > 0) + { + int status; + if (waitpid(slave_pid, &status, WNOHANG) > 0) + { + handle_slave_signal(); + return; + } + } + struct vsf_sysutil_wait_retval wait_retval = vsf_sysutil_wait(); + (void) duff; + /* Child died, so we'll do the same! Report it as an error unless the child + * exited normally with zero exit code + */ + if (vsf_sysutil_retval_is_error(vsf_sysutil_wait_get_retval(&wait_retval)) || + !vsf_sysutil_wait_exited_normally(&wait_retval) || + vsf_sysutil_wait_get_exitcode(&wait_retval) != 0) + { + die("child died"); + } + else + { + vsf_sysutil_exit(0); + } +} + +void +vsf_two_process_start(struct vsf_session* p_sess) +{ + /* Create the comms channel between privileged parent and no-priv child */ + priv_sock_init(p_sess); + if (tunable_ssl_enable) + { + /* Create the comms channel between the no-priv SSL child and the low-priv + * protocol handling child. + */ + ssl_comm_channel_init(p_sess); + } + vsf_sysutil_install_async_sighandler(kVSFSysUtilSigCHLD, handle_sigchld); + { + int newpid = vsf_sysutil_fork(); + if (newpid != 0) + { + /* Parent - go into pre-login parent process mode */ + while (1) + { + process_login_req(p_sess); + } + } + } + /* Child process - time to lose as much privilege as possible and do the + * login processing + */ + vsf_sysutil_close(p_sess->parent_fd); + if (tunable_ssl_enable) + { + vsf_sysutil_close(p_sess->ssl_consumer_fd); + } + if (tunable_local_enable && tunable_userlist_enable) + { + int retval = str_fileread(&p_sess->userlist_str, tunable_userlist_file, + VSFTP_CONF_FILE_MAX); + if (vsf_sysutil_retval_is_error(retval)) + { + die2("cannot open user list file:", tunable_userlist_file); + } + } + drop_all_privs(); + init_connection(p_sess); + /* NOTREACHED */ +} + +void +drop_all_privs(void) +{ + struct mystr user_str = INIT_MYSTR; + struct mystr dir_str = INIT_MYSTR; + str_alloc_text(&user_str, tunable_nopriv_user); + str_alloc_text(&dir_str, tunable_secure_chroot_dir); + /* Be kind: give good error message if the secure dir is missing */ + { + struct vsf_sysutil_statbuf* p_statbuf = 0; + if (vsf_sysutil_retval_is_error(str_lstat(&dir_str, &p_statbuf))) + { + die2("vsftpd: not found: directory given in 'secure_chroot_dir':", + tunable_secure_chroot_dir); + } + vsf_sysutil_free(p_statbuf); + } + vsf_secutil_change_credentials(&user_str, &dir_str, 0, 0, + VSF_SECUTIL_OPTION_CHROOT); + str_free(&user_str); + str_free(&dir_str); +} + +void +vsf_two_process_login(struct vsf_session* p_sess, + const struct mystr* p_pass_str) +{ + char result; + priv_sock_send_cmd(p_sess->child_fd, PRIV_SOCK_LOGIN); + priv_sock_send_str(p_sess->child_fd, &p_sess->user_str); + priv_sock_send_str(p_sess->child_fd, p_pass_str); + priv_sock_send_int(p_sess->child_fd, p_sess->control_use_ssl); + priv_sock_send_int(p_sess->child_fd, p_sess->data_use_ssl); + result = priv_sock_get_result(p_sess->child_fd); + if (result == PRIV_SOCK_RESULT_OK) + { + /* Miracle. We don't emit the success message here. That is left to + * process_post_login(). + * Exit normally, unless we are remaining as the SSL read / write child. + */ + if (!p_sess->control_use_ssl) + { + vsf_sysutil_exit(0); + } + else + { + vsf_sysutil_clear_alarm(); + vsf_sysutil_close(p_sess->child_fd); + if (tunable_setproctitle_enable) + { + vsf_sysutil_setproctitle("SSL handler"); + } + process_ssl_slave_req(p_sess); + } + /* NOTREACHED */ + } + else if (result == PRIV_SOCK_RESULT_BAD) + { + /* Continue the processing loop.. */ + return; + } + else + { + die("priv_sock_get_result"); + } +} + +int +vsf_two_process_get_priv_data_sock(struct vsf_session* p_sess) +{ + char res; + priv_sock_send_cmd(p_sess->child_fd, PRIV_SOCK_GET_DATA_SOCK); + res = priv_sock_get_result(p_sess->child_fd); + if (res != PRIV_SOCK_RESULT_OK) + { + die("could not get privileged socket"); + } + return priv_sock_recv_fd(p_sess->child_fd); +} + +void +vsf_two_process_chown_upload(struct vsf_session* p_sess, int fd) +{ + char res; + priv_sock_send_cmd(p_sess->child_fd, PRIV_SOCK_CHOWN); + priv_sock_send_fd(p_sess->child_fd, fd); + res = priv_sock_get_result(p_sess->child_fd); + if (res != PRIV_SOCK_RESULT_OK) + { + die("unexpected failure in vsf_two_process_chown_upload"); + } +} + +static void +process_login_req(struct vsf_session* p_sess) +{ + enum EVSFPrivopLoginResult e_login_result = kVSFLoginNull; + char cmd; + vsf_sysutil_unblock_sig(kVSFSysUtilSigCHLD); + /* Blocks */ + cmd = priv_sock_get_cmd(p_sess->parent_fd); + vsf_sysutil_block_sig(kVSFSysUtilSigCHLD); + if (cmd != PRIV_SOCK_LOGIN) + { + die("bad request"); + } + /* Get username and password - we must distrust these */ + { + struct mystr password_str = INIT_MYSTR; + priv_sock_get_str(p_sess->parent_fd, &p_sess->user_str); + priv_sock_get_str(p_sess->parent_fd, &password_str); + p_sess->control_use_ssl = priv_sock_get_int(p_sess->parent_fd); + p_sess->data_use_ssl = priv_sock_get_int(p_sess->parent_fd); + if (!tunable_ssl_enable) + { + p_sess->control_use_ssl = 0; + p_sess->data_use_ssl = 0; + } + e_login_result = vsf_privop_do_login(p_sess, &password_str); + str_free(&password_str); + } + switch (e_login_result) + { + case kVSFLoginFail: + priv_sock_send_result(p_sess->parent_fd, PRIV_SOCK_RESULT_BAD); + return; + break; + case kVSFLoginAnon: + str_alloc_text(&p_sess->user_str, tunable_ftp_username); + common_do_login(p_sess, &p_sess->user_str, 1, 1); + break; + case kVSFLoginReal: + { + int do_chroot = 0; + if (tunable_chroot_local_user) + { + do_chroot = 1; + } + if (tunable_chroot_list_enable) + { + struct mystr chroot_list_file = INIT_MYSTR; + int retval = str_fileread(&chroot_list_file, + tunable_chroot_list_file, + VSFTP_CONF_FILE_MAX); + if (vsf_sysutil_retval_is_error(retval)) + { + die2("could not open chroot() list file:", + tunable_chroot_list_file); + } + if (str_contains_line(&chroot_list_file, &p_sess->user_str)) + { + if (do_chroot) + { + do_chroot = 0; + } + else + { + do_chroot = 1; + } + } + str_free(&chroot_list_file); + } + common_do_login(p_sess, &p_sess->user_str, do_chroot, 0); + } + break; + default: + bug("weird state in process_login_request"); + break; + } + /* NOTREACHED */ +} + +static void +process_ssl_slave_req(struct vsf_session* p_sess) +{ + while (1) + { + char cmd = priv_sock_get_cmd(p_sess->ssl_slave_fd); + int retval; + if (cmd == PRIV_SOCK_GET_USER_CMD) + { + ftp_getline(p_sess, &p_sess->ftp_cmd_str, p_sess->p_control_line_buf); + priv_sock_send_str(p_sess->ssl_slave_fd, &p_sess->ftp_cmd_str); + } + else if (cmd == PRIV_SOCK_WRITE_USER_RESP) + { + priv_sock_get_str(p_sess->ssl_slave_fd, &p_sess->ftp_cmd_str); + retval = ftp_write_str(p_sess, &p_sess->ftp_cmd_str, kVSFRWControl); + priv_sock_send_int(p_sess->ssl_slave_fd, retval); + } + else + { + die("bad request in process_ssl_slave_req"); + } + } +} + +static void +common_do_login(struct vsf_session* p_sess, const struct mystr* p_user_str, + int do_chroot, int anon) +{ + int was_anon = anon; + const struct mystr* p_orig_user_str = p_user_str; + int newpid; + vsf_sysutil_install_null_sighandler(kVSFSysUtilSigCHLD); + /* Tells the pre-login child all is OK (it may exit in response) */ + priv_sock_send_result(p_sess->parent_fd, PRIV_SOCK_RESULT_OK); + if (!p_sess->control_use_ssl) + { + (void) vsf_sysutil_wait(); + } + else + { + p_sess->ssl_slave_active = 1; + } + /* Absorb the SIGCHLD */ + vsf_sysutil_unblock_sig(kVSFSysUtilSigCHLD); + /* Handle loading per-user config options */ + handle_per_user_config(p_user_str); + /* Set this before we fork */ + p_sess->is_anonymous = anon; + vsf_sysutil_install_async_sighandler(kVSFSysUtilSigCHLD, handle_sigchld); + newpid = vsf_sysutil_fork(); + if (newpid == 0) + { + struct mystr guest_user_str = INIT_MYSTR; + struct mystr chroot_str = INIT_MYSTR; + struct mystr chdir_str = INIT_MYSTR; + struct mystr userdir_str = INIT_MYSTR; + unsigned int secutil_option = VSF_SECUTIL_OPTION_USE_GROUPS; + /* Child - drop privs and start proper FTP! */ + vsf_sysutil_close(p_sess->parent_fd); + if (tunable_ssl_enable) + { + vsf_sysutil_close(p_sess->ssl_slave_fd); + } + if (tunable_guest_enable && !anon) + { + /* Remap to the guest user */ + str_alloc_text(&guest_user_str, tunable_guest_username); + p_user_str = &guest_user_str; + if (!tunable_virtual_use_local_privs) + { + anon = 1; + do_chroot = 1; + } + } + if (do_chroot) + { + secutil_option |= VSF_SECUTIL_OPTION_CHROOT; + } + if (!anon) + { + secutil_option |= VSF_SECUTIL_OPTION_CHANGE_EUID; + } + calculate_chdir_dir(was_anon, &userdir_str, &chroot_str, &chdir_str, + p_user_str, p_orig_user_str); + vsf_secutil_change_credentials(p_user_str, &userdir_str, &chroot_str, + 0, secutil_option); + if (!str_isempty(&chdir_str)) + { + (void) str_chdir(&chdir_str); + } + str_free(&guest_user_str); + str_free(&chroot_str); + str_free(&chdir_str); + str_free(&userdir_str); + /* Guard against the config error of having the anonymous ftp tree owned + * by the user we are running as + */ + if (was_anon && vsf_sysutil_write_access("/")) + { + die("vsftpd: refusing to run with writable anonymous root"); + } + p_sess->is_anonymous = anon; + process_post_login(p_sess); + bug("should not get here: common_do_login"); + } + /* Parent */ + if (tunable_ssl_enable) + { + vsf_sysutil_close(p_sess->ssl_consumer_fd); + /* Keep the SSL slave fd around so we can shutdown() upon exit */ + } + vsf_priv_parent_postlogin(p_sess); + bug("should not get here in common_do_login"); +} + +static void +handle_per_user_config(const struct mystr* p_user_str) +{ + struct mystr filename_str = INIT_MYSTR; + struct vsf_sysutil_statbuf* p_statbuf = 0; + struct str_locate_result loc_result; + int retval; + if (!tunable_user_config_dir) + { + return; + } + /* Security paranoia - ignore if user has a / in it. */ + loc_result = str_locate_char(p_user_str, '/'); + if (loc_result.found) + { + return; + } + str_alloc_text(&filename_str, tunable_user_config_dir); + str_append_char(&filename_str, '/'); + str_append_str(&filename_str, p_user_str); + retval = str_stat(&filename_str, &p_statbuf); + /* Security - ignore unless owned by root */ + if (!vsf_sysutil_retval_is_error(retval) && + vsf_sysutil_statbuf_get_uid(p_statbuf) == VSFTP_ROOT_UID) + { + vsf_parseconf_load_file(str_getbuf(&filename_str), 1); + } + str_free(&filename_str); + vsf_sysutil_free(p_statbuf); +} + +static void +calculate_chdir_dir(int anon_login, struct mystr* p_userdir_str, + struct mystr* p_chroot_str, + struct mystr* p_chdir_str, + const struct mystr* p_user_str, + const struct mystr* p_orig_user_str) +{ + if (!anon_login) + { + const struct vsf_sysutil_user* p_user = str_getpwnam(p_user_str); + if (p_user == 0) + { + die2("cannot locate user entry:", str_getbuf(p_user_str)); + } + str_alloc_text(p_userdir_str, vsf_sysutil_user_get_homedir(p_user)); + if (tunable_user_sub_token) + { + str_replace_text(p_userdir_str, tunable_user_sub_token, + str_getbuf(p_orig_user_str)); + } + } + if (anon_login && tunable_anon_root) + { + str_alloc_text(p_chroot_str, tunable_anon_root); + } + else if (!anon_login && tunable_local_root) + { + str_alloc_text(p_chroot_str, tunable_local_root); + if (tunable_user_sub_token) + { + str_replace_text(p_chroot_str, tunable_user_sub_token, + str_getbuf(p_orig_user_str)); + } + } + /* If enabled, the chroot() location embedded in the HOMEDIR takes + * precedence. + */ + if (!anon_login && tunable_passwd_chroot_enable) + { + struct str_locate_result loc_result; + loc_result = str_locate_text(p_userdir_str, "/./"); + if (loc_result.found) + { + str_split_text(p_userdir_str, p_chdir_str, "/./"); + str_copy(p_chroot_str, p_userdir_str); + } + } +} + diff --git a/twoprocess.h b/twoprocess.h new file mode 100644 index 0000000..d41566f --- /dev/null +++ b/twoprocess.h @@ -0,0 +1,46 @@ +#ifndef VSF_TWOPROCESS_H +#define VSF_TWOPROCESS_H + +struct mystr; +struct vsf_session; + +/* vsf_two_process_start() + * PURPOSE + * Called to start FTP login processing using the two process model. This + * launches the unprivileged child to process the FTP login. + * PARAMETERS + * p_sess - the current session object + */ +void vsf_two_process_start(struct vsf_session* p_sess); + +/* vsf_two_process_login() + * PURPOSE + * Called to propose a login using the two process model. + * PARAMETERS + * p_sess - the current session object + * p_pass_str - the proposed password + */ +void vsf_two_process_login(struct vsf_session* p_sess, + const struct mystr* p_pass_str); + +/* vsf_two_process_get_priv_data_sock() + * PURPOSE + * Get a privileged port 20 bound data socket using the two process model. + * PARAMETERS + * p_sess - the current session object + * RETURNS + * The file descriptor of the privileged socket + */ +int vsf_two_process_get_priv_data_sock(struct vsf_session* p_sess); + +/* vsf_two_process_chown_upload() + * PURPOSE + * Change ownership of an uploaded file using the two process model. + * PARAMETERS + * p_sess - the current session object + * fd - the file descriptor to change ownership on + */ +void vsf_two_process_chown_upload(struct vsf_session* p_sess, int fd); + +#endif /* VSF_TWOPROCESS_H */ + diff --git a/typeid_hash.cpp b/typeid_hash.cpp new file mode 100644 index 0000000..a39e4f5 --- /dev/null +++ b/typeid_hash.cpp @@ -0,0 +1,23 @@ +#include "typeid_hash.h" +#include + +static std::map hashmap; + +inline int xorsum_string(const char * str) { + int retval = 0; + for (unsigned int i = 0; i < strlen(str); ++i) + { + retval ^= ((int)str[i]) << ((i % 4)*4); + } + return retval; +} + +int TypeIDHashtable::getHash(const std::type_info & typenfo) { + if ( hashmap.find(typenfo.name()) != hashmap.end() ) + return hashmap[typenfo.name()]; + else { + int xoredsum = xorsum_string(typenfo.name()); + hashmap[typenfo.name()] = xoredsum; + return xoredsum; + } +} diff --git a/typeid_hash.h b/typeid_hash.h new file mode 100644 index 0000000..d9851f2 --- /dev/null +++ b/typeid_hash.h @@ -0,0 +1,11 @@ +#ifndef TYPEID_HASH_H +#define TYPEID_HASH_H + +#include + +class TypeIDHashtable { +public: + static int getHash(const std::type_info & typenfo); +}; + +#endif //TYPEID_HASH_H diff --git a/udsrf.h b/udsrf.h new file mode 100644 index 0000000..7570ede --- /dev/null +++ b/udsrf.h @@ -0,0 +1,102 @@ +#ifndef VSF_UDSRF_H +#define VSF_UDSRF_H + +/* + The Universal Debuggable Sender - Receiver Functions :) +*/ + +#include "exceptions.h" +#include "unknown_object.h" +#include "typeid_hash.h" + +/** sendData() + * PURPOSE + * Provides a shorthand for sending data, also enables limited typechecking based on typeid(T).name() + * PARAMETERS + * socket - the socket which we use for sending data + * to_send - the data we wish to send + */ +template void sendData(int socket, const T & to_send) throw(std::exception); + +/** recvData() + * PURPOSE + * Provides a shorthand for receiving data, also enables limited typechecking based on typeid(T).name() + * PARAMETERS + * socket - socket used fot receiving data + * to_recv_to - an existing object, may be not initialised, which will be populated with + * the received data + */ +template void recvData(int socket, const T & to_recv_to) throw(std::exception); + + +/******************************* implementation **********************************************/ + +#include +#include +#include +#include +#include +#include + +template inline void sendData(int socket, const T & to_send) throw(std::exception) { + int xoredsum = TypeIDHashtable::getHash(typeid(to_send)); + if (send(socket, &xoredsum, sizeof(int), 0) != sizeof(int)) + { + throw SendRecvException(); + } + + if (send(socket, &to_send, sizeof(T),0) != sizeof(T)) + { + perror(0); + throw SendRecvException(); + } +} + +template<> inline void sendData(int socket, const UnknownObject & to_send) throw(std::exception) { + int xoredsum = TypeIDHashtable::getHash(typeid(to_send)); + if (send(socket, &xoredsum, sizeof(int), 0) != sizeof(int)) + { + throw SendRecvException(); + } + to_send.writeToSocket(socket); +} + +template void recvData(int socket, T & to_recv_to) throw(std::exception) { + int my_xorsum = TypeIDHashtable::getHash(typeid(to_recv_to)); + int xorsum_recvd; + if(recv(socket, &xorsum_recvd, sizeof(int), 0) != sizeof(int)) + { + throw SendRecvException(); + } + if (xorsum_recvd != my_xorsum) + { + throw InconsistentTypeException(); + } + + if (recv(socket, &to_recv_to, sizeof(T),0) != sizeof(T)) + { + perror(0); + throw SendRecvException(); + } +} + +template<> inline void recvData(int socket, UnknownObject & to_recv_to) throw(std::exception) { + int my_xorsum = TypeIDHashtable::getHash(typeid(to_recv_to)); + int xorsum_recvd; + if(recv(socket, &xorsum_recvd, sizeof(int), 0) != sizeof(int)) + { + throw SendRecvException(); + } + if (xorsum_recvd != my_xorsum) + { + throw InconsistentTypeException(); + } + + to_recv_to = *UnknownObject::readFromSocket(socket); +} + +#include "event_c_api.h" +#include "request_c_api.h" +#include "udsrf_auto.h" + +#endif diff --git a/udsrf_auto.h b/udsrf_auto.h new file mode 100644 index 0000000..8b88e36 --- /dev/null +++ b/udsrf_auto.h @@ -0,0 +1,1179 @@ +template<> inline void sendData(int socket, const Event_Shared & to_send) throw(std::exception) { + int xoredsum = TypeIDHashtable::getHash(typeid(to_send)); + if (send(socket, &xoredsum, sizeof(int), 0) != sizeof(int)) + { + throw SendRecvException(); + } + sendData(socket, to_send.timestamp); + sendData(socket, to_send.remote_ip); + { + int len = strlen(to_send.user_name) + 1; + if (len > 1024) + throw TooLongString(); + if(send(socket, &len, sizeof(len), 0) != sizeof(len)) { + throw SendRecvException(); + } + if(send(socket, to_send.user_name, len, 0) != len) { + throw SendRecvException(); + } + } +} +template<> inline void sendData(int socket, const Event_FileData_Shared & to_send) throw(std::exception) { + int xoredsum = TypeIDHashtable::getHash(typeid(to_send)); + if (send(socket, &xoredsum, sizeof(int), 0) != sizeof(int)) + { + throw SendRecvException(); + } + { + int len = strlen(to_send.path) + 1; + if (len > 1024) + throw TooLongString(); + if(send(socket, &len, sizeof(len), 0) != sizeof(len)) { + throw SendRecvException(); + } + if(send(socket, to_send.path, len, 0) != len) { + throw SendRecvException(); + } + } + { + int len = strlen(to_send.name) + 1; + if (len > 1024) + throw TooLongString(); + if(send(socket, &len, sizeof(len), 0) != sizeof(len)) { + throw SendRecvException(); + } + if(send(socket, to_send.name, len, 0) != len) { + throw SendRecvException(); + } + } + if(send(socket, &to_send.size, sizeof(unsigned int), 0) != sizeof(unsigned int)) { + throw SendRecvException(); + } +} +template<> inline void sendData(int socket, const Event_Login & to_send) throw(std::exception) { + int xoredsum = TypeIDHashtable::getHash(typeid(to_send)); + if (send(socket, &xoredsum, sizeof(int), 0) != sizeof(int)) + { + throw SendRecvException(); + } + sendData(socket, to_send.sharedInfo); + if(send(socket, &to_send.is_anonymous, sizeof(int), 0) != sizeof(int)) { + throw SendRecvException(); + } +} +template<> inline void sendData(int socket, const Event_FailedLogin & to_send) throw(std::exception) { + int xoredsum = TypeIDHashtable::getHash(typeid(to_send)); + if (send(socket, &xoredsum, sizeof(int), 0) != sizeof(int)) + { + throw SendRecvException(); + } + sendData(socket, to_send.sharedInfo); + { + int len = strlen(to_send.failed_password) + 1; + if (len > 1024) + throw TooLongString(); + if(send(socket, &len, sizeof(len), 0) != sizeof(len)) { + throw SendRecvException(); + } + if(send(socket, to_send.failed_password, len, 0) != len) { + throw SendRecvException(); + } + } +} +template<> inline void sendData(int socket, const Event_Download & to_send) throw(std::exception) { + int xoredsum = TypeIDHashtable::getHash(typeid(to_send)); + if (send(socket, &xoredsum, sizeof(int), 0) != sizeof(int)) + { + throw SendRecvException(); + } + sendData(socket, to_send.sharedInfo); + sendData(socket, to_send.fileInfo); + if(send(socket, &to_send.is_complete, sizeof(int), 0) != sizeof(int)) { + throw SendRecvException(); + } +} +template<> inline void sendData(int socket, const Event_Upload & to_send) throw(std::exception) { + int xoredsum = TypeIDHashtable::getHash(typeid(to_send)); + if (send(socket, &xoredsum, sizeof(int), 0) != sizeof(int)) + { + throw SendRecvException(); + } + sendData(socket, to_send.sharedInfo); + sendData(socket, to_send.fileInfo); + if(send(socket, &to_send.is_complete, sizeof(int), 0) != sizeof(int)) { + throw SendRecvException(); + } + if(send(socket, &to_send.is_append, sizeof(int), 0) != sizeof(int)) { + throw SendRecvException(); + } + if(send(socket, &to_send.is_overwrite, sizeof(int), 0) != sizeof(int)) { + throw SendRecvException(); + } +} +template<> inline void sendData(int socket, const Event_Mkdir & to_send) throw(std::exception) { + int xoredsum = TypeIDHashtable::getHash(typeid(to_send)); + if (send(socket, &xoredsum, sizeof(int), 0) != sizeof(int)) + { + throw SendRecvException(); + } + sendData(socket, to_send.sharedInfo); + { + int len = strlen(to_send.path) + 1; + if (len > 1024) + throw TooLongString(); + if(send(socket, &len, sizeof(len), 0) != sizeof(len)) { + throw SendRecvException(); + } + if(send(socket, to_send.path, len, 0) != len) { + throw SendRecvException(); + } + } + if(send(socket, &to_send.succeeded, sizeof(int), 0) != sizeof(int)) { + throw SendRecvException(); + } +} +template<> inline void sendData(int socket, const Event_Connection & to_send) throw(std::exception) { + int xoredsum = TypeIDHashtable::getHash(typeid(to_send)); + if (send(socket, &xoredsum, sizeof(int), 0) != sizeof(int)) + { + throw SendRecvException(); + } + sendData(socket, to_send.sharedInfo); + { + int len = strlen(to_send.reason) + 1; + if (len > 1024) + throw TooLongString(); + if(send(socket, &len, sizeof(len), 0) != sizeof(len)) { + throw SendRecvException(); + } + if(send(socket, to_send.reason, len, 0) != len) { + throw SendRecvException(); + } + } +} +template<> inline void sendData(int socket, const Event_Delete & to_send) throw(std::exception) { + int xoredsum = TypeIDHashtable::getHash(typeid(to_send)); + if (send(socket, &xoredsum, sizeof(int), 0) != sizeof(int)) + { + throw SendRecvException(); + } + sendData(socket, to_send.sharedInfo); + { + int len = strlen(to_send.path) + 1; + if (len > 1024) + throw TooLongString(); + if(send(socket, &len, sizeof(len), 0) != sizeof(len)) { + throw SendRecvException(); + } + if(send(socket, to_send.path, len, 0) != len) { + throw SendRecvException(); + } + } + if(send(socket, &to_send.succeeded, sizeof(int), 0) != sizeof(int)) { + throw SendRecvException(); + } +} +template<> inline void sendData(int socket, const Event_Rename & to_send) throw(std::exception) { + int xoredsum = TypeIDHashtable::getHash(typeid(to_send)); + if (send(socket, &xoredsum, sizeof(int), 0) != sizeof(int)) + { + throw SendRecvException(); + } + sendData(socket, to_send.sharedInfo); + { + int len = strlen(to_send.old_name) + 1; + if (len > 1024) + throw TooLongString(); + if(send(socket, &len, sizeof(len), 0) != sizeof(len)) { + throw SendRecvException(); + } + if(send(socket, to_send.old_name, len, 0) != len) { + throw SendRecvException(); + } + } + { + int len = strlen(to_send.new_name) + 1; + if (len > 1024) + throw TooLongString(); + if(send(socket, &len, sizeof(len), 0) != sizeof(len)) { + throw SendRecvException(); + } + if(send(socket, to_send.new_name, len, 0) != len) { + throw SendRecvException(); + } + } + if(send(socket, &to_send.succeeded, sizeof(int), 0) != sizeof(int)) { + throw SendRecvException(); + } +} +template<> inline void sendData(int socket, const Event_Rmdir & to_send) throw(std::exception) { + int xoredsum = TypeIDHashtable::getHash(typeid(to_send)); + if (send(socket, &xoredsum, sizeof(int), 0) != sizeof(int)) + { + throw SendRecvException(); + } + sendData(socket, to_send.sharedInfo); + { + int len = strlen(to_send.path) + 1; + if (len > 1024) + throw TooLongString(); + if(send(socket, &len, sizeof(len), 0) != sizeof(len)) { + throw SendRecvException(); + } + if(send(socket, to_send.path, len, 0) != len) { + throw SendRecvException(); + } + } + if(send(socket, &to_send.succeeded, sizeof(int), 0) != sizeof(int)) { + throw SendRecvException(); + } +} +template<> inline void sendData(int socket, const Event_Chmod & to_send) throw(std::exception) { + int xoredsum = TypeIDHashtable::getHash(typeid(to_send)); + if (send(socket, &xoredsum, sizeof(int), 0) != sizeof(int)) + { + throw SendRecvException(); + } + sendData(socket, to_send.sharedInfo); + { + int len = strlen(to_send.path) + 1; + if (len > 1024) + throw TooLongString(); + if(send(socket, &len, sizeof(len), 0) != sizeof(len)) { + throw SendRecvException(); + } + if(send(socket, to_send.path, len, 0) != len) { + throw SendRecvException(); + } + } + { + int len = strlen(to_send.name) + 1; + if (len > 1024) + throw TooLongString(); + if(send(socket, &len, sizeof(len), 0) != sizeof(len)) { + throw SendRecvException(); + } + if(send(socket, to_send.name, len, 0) != len) { + throw SendRecvException(); + } + } + { + int len = strlen(to_send.new_attrib) + 1; + if (len > 1024) + throw TooLongString(); + if(send(socket, &len, sizeof(len), 0) != sizeof(len)) { + throw SendRecvException(); + } + if(send(socket, to_send.new_attrib, len, 0) != len) { + throw SendRecvException(); + } + } + if(send(socket, &to_send.succeeded, sizeof(int), 0) != sizeof(int)) { + throw SendRecvException(); + } +} +template<> inline void sendData(int socket, const Event_CliEnd & to_send) throw(std::exception) { + int xoredsum = TypeIDHashtable::getHash(typeid(to_send)); + if (send(socket, &xoredsum, sizeof(int), 0) != sizeof(int)) + { + throw SendRecvException(); + } + sendData(socket, to_send.timestamp); +} +template<> inline void sendData(int socket, const Event_ServQuit & to_send) throw(std::exception) { + int xoredsum = TypeIDHashtable::getHash(typeid(to_send)); + if (send(socket, &xoredsum, sizeof(int), 0) != sizeof(int)) + { + throw SendRecvException(); + } +} +template<> inline void recvData(int socket, Event_Shared & to_recv_to) throw(std::exception) { + int my_xorsum = TypeIDHashtable::getHash(typeid(to_recv_to)); + int xorsum_recvd; + if(recv(socket, &xorsum_recvd, sizeof(int), 0) != sizeof(int)) + { + throw SendRecvException(); + } + if (xorsum_recvd != my_xorsum) + { + throw InconsistentTypeException(); + } + recvData(socket, to_recv_to.timestamp); + recvData(socket, to_recv_to.remote_ip); + { + int len; + char * strnk; + if(recv(socket, &len, sizeof(len), 0) != sizeof(len)) { + throw SendRecvException(); + } + if (len > 1024) + throw TooLongString(); + strnk = new char[len]; + if(recv(socket, strnk, len, 0) != len) { + throw SendRecvException(); + } + to_recv_to.user_name = strnk; + } +} +template<> inline void recvData(int socket, Event_FileData_Shared & to_recv_to) throw(std::exception) { + int my_xorsum = TypeIDHashtable::getHash(typeid(to_recv_to)); + int xorsum_recvd; + if(recv(socket, &xorsum_recvd, sizeof(int), 0) != sizeof(int)) + { + throw SendRecvException(); + } + if (xorsum_recvd != my_xorsum) + { + throw InconsistentTypeException(); + } + { + int len; + char * strnk; + if(recv(socket, &len, sizeof(len), 0) != sizeof(len)) { + throw SendRecvException(); + } + if (len > 1024) + throw TooLongString(); + strnk = new char[len]; + if(recv(socket, strnk, len, 0) != len) { + throw SendRecvException(); + } + to_recv_to.path = strnk; + } + { + int len; + char * strnk; + if(recv(socket, &len, sizeof(len), 0) != sizeof(len)) { + throw SendRecvException(); + } + if (len > 1024) + throw TooLongString(); + strnk = new char[len]; + if(recv(socket, strnk, len, 0) != len) { + throw SendRecvException(); + } + to_recv_to.name = strnk; + } + if(recv(socket, &to_recv_to.size, sizeof(unsigned int), 0) != sizeof(unsigned int)) { + throw SendRecvException(); + } +} +template<> inline void recvData(int socket, Event_Login & to_recv_to) throw(std::exception) { + int my_xorsum = TypeIDHashtable::getHash(typeid(to_recv_to)); + int xorsum_recvd; + if(recv(socket, &xorsum_recvd, sizeof(int), 0) != sizeof(int)) + { + throw SendRecvException(); + } + if (xorsum_recvd != my_xorsum) + { + throw InconsistentTypeException(); + } + recvData(socket, to_recv_to.sharedInfo); + if(recv(socket, &to_recv_to.is_anonymous, sizeof(int), 0) != sizeof(int)) { + throw SendRecvException(); + } +} +template<> inline void recvData(int socket, Event_FailedLogin & to_recv_to) throw(std::exception) { + int my_xorsum = TypeIDHashtable::getHash(typeid(to_recv_to)); + int xorsum_recvd; + if(recv(socket, &xorsum_recvd, sizeof(int), 0) != sizeof(int)) + { + throw SendRecvException(); + } + if (xorsum_recvd != my_xorsum) + { + throw InconsistentTypeException(); + } + recvData(socket, to_recv_to.sharedInfo); + { + int len; + char * strnk; + if(recv(socket, &len, sizeof(len), 0) != sizeof(len)) { + throw SendRecvException(); + } + if (len > 1024) + throw TooLongString(); + strnk = new char[len]; + if(recv(socket, strnk, len, 0) != len) { + throw SendRecvException(); + } + to_recv_to.failed_password = strnk; + } +} +template<> inline void recvData(int socket, Event_Download & to_recv_to) throw(std::exception) { + int my_xorsum = TypeIDHashtable::getHash(typeid(to_recv_to)); + int xorsum_recvd; + if(recv(socket, &xorsum_recvd, sizeof(int), 0) != sizeof(int)) + { + throw SendRecvException(); + } + if (xorsum_recvd != my_xorsum) + { + throw InconsistentTypeException(); + } + recvData(socket, to_recv_to.sharedInfo); + recvData(socket, to_recv_to.fileInfo); + if(recv(socket, &to_recv_to.is_complete, sizeof(int), 0) != sizeof(int)) { + throw SendRecvException(); + } +} +template<> inline void recvData(int socket, Event_Upload & to_recv_to) throw(std::exception) { + int my_xorsum = TypeIDHashtable::getHash(typeid(to_recv_to)); + int xorsum_recvd; + if(recv(socket, &xorsum_recvd, sizeof(int), 0) != sizeof(int)) + { + throw SendRecvException(); + } + if (xorsum_recvd != my_xorsum) + { + throw InconsistentTypeException(); + } + recvData(socket, to_recv_to.sharedInfo); + recvData(socket, to_recv_to.fileInfo); + if(recv(socket, &to_recv_to.is_complete, sizeof(int), 0) != sizeof(int)) { + throw SendRecvException(); + } + if(recv(socket, &to_recv_to.is_append, sizeof(int), 0) != sizeof(int)) { + throw SendRecvException(); + } + if(recv(socket, &to_recv_to.is_overwrite, sizeof(int), 0) != sizeof(int)) { + throw SendRecvException(); + } +} +template<> inline void recvData(int socket, Event_Mkdir & to_recv_to) throw(std::exception) { + int my_xorsum = TypeIDHashtable::getHash(typeid(to_recv_to)); + int xorsum_recvd; + if(recv(socket, &xorsum_recvd, sizeof(int), 0) != sizeof(int)) + { + throw SendRecvException(); + } + if (xorsum_recvd != my_xorsum) + { + throw InconsistentTypeException(); + } + recvData(socket, to_recv_to.sharedInfo); + { + int len; + char * strnk; + if(recv(socket, &len, sizeof(len), 0) != sizeof(len)) { + throw SendRecvException(); + } + if (len > 1024) + throw TooLongString(); + strnk = new char[len]; + if(recv(socket, strnk, len, 0) != len) { + throw SendRecvException(); + } + to_recv_to.path = strnk; + } + if(recv(socket, &to_recv_to.succeeded, sizeof(int), 0) != sizeof(int)) { + throw SendRecvException(); + } +} +template<> inline void recvData(int socket, Event_Connection & to_recv_to) throw(std::exception) { + int my_xorsum = TypeIDHashtable::getHash(typeid(to_recv_to)); + int xorsum_recvd; + if(recv(socket, &xorsum_recvd, sizeof(int), 0) != sizeof(int)) + { + throw SendRecvException(); + } + if (xorsum_recvd != my_xorsum) + { + throw InconsistentTypeException(); + } + recvData(socket, to_recv_to.sharedInfo); + { + int len; + char * strnk; + if(recv(socket, &len, sizeof(len), 0) != sizeof(len)) { + throw SendRecvException(); + } + if (len > 1024) + throw TooLongString(); + strnk = new char[len]; + if(recv(socket, strnk, len, 0) != len) { + throw SendRecvException(); + } + to_recv_to.reason = strnk; + } +} +template<> inline void recvData(int socket, Event_Delete & to_recv_to) throw(std::exception) { + int my_xorsum = TypeIDHashtable::getHash(typeid(to_recv_to)); + int xorsum_recvd; + if(recv(socket, &xorsum_recvd, sizeof(int), 0) != sizeof(int)) + { + throw SendRecvException(); + } + if (xorsum_recvd != my_xorsum) + { + throw InconsistentTypeException(); + } + recvData(socket, to_recv_to.sharedInfo); + { + int len; + char * strnk; + if(recv(socket, &len, sizeof(len), 0) != sizeof(len)) { + throw SendRecvException(); + } + if (len > 1024) + throw TooLongString(); + strnk = new char[len]; + if(recv(socket, strnk, len, 0) != len) { + throw SendRecvException(); + } + to_recv_to.path = strnk; + } + if(recv(socket, &to_recv_to.succeeded, sizeof(int), 0) != sizeof(int)) { + throw SendRecvException(); + } +} +template<> inline void recvData(int socket, Event_Rename & to_recv_to) throw(std::exception) { + int my_xorsum = TypeIDHashtable::getHash(typeid(to_recv_to)); + int xorsum_recvd; + if(recv(socket, &xorsum_recvd, sizeof(int), 0) != sizeof(int)) + { + throw SendRecvException(); + } + if (xorsum_recvd != my_xorsum) + { + throw InconsistentTypeException(); + } + recvData(socket, to_recv_to.sharedInfo); + { + int len; + char * strnk; + if(recv(socket, &len, sizeof(len), 0) != sizeof(len)) { + throw SendRecvException(); + } + if (len > 1024) + throw TooLongString(); + strnk = new char[len]; + if(recv(socket, strnk, len, 0) != len) { + throw SendRecvException(); + } + to_recv_to.old_name = strnk; + } + { + int len; + char * strnk; + if(recv(socket, &len, sizeof(len), 0) != sizeof(len)) { + throw SendRecvException(); + } + if (len > 1024) + throw TooLongString(); + strnk = new char[len]; + if(recv(socket, strnk, len, 0) != len) { + throw SendRecvException(); + } + to_recv_to.new_name = strnk; + } + if(recv(socket, &to_recv_to.succeeded, sizeof(int), 0) != sizeof(int)) { + throw SendRecvException(); + } +} +template<> inline void recvData(int socket, Event_Rmdir & to_recv_to) throw(std::exception) { + int my_xorsum = TypeIDHashtable::getHash(typeid(to_recv_to)); + int xorsum_recvd; + if(recv(socket, &xorsum_recvd, sizeof(int), 0) != sizeof(int)) + { + throw SendRecvException(); + } + if (xorsum_recvd != my_xorsum) + { + throw InconsistentTypeException(); + } + recvData(socket, to_recv_to.sharedInfo); + { + int len; + char * strnk; + if(recv(socket, &len, sizeof(len), 0) != sizeof(len)) { + throw SendRecvException(); + } + if (len > 1024) + throw TooLongString(); + strnk = new char[len]; + if(recv(socket, strnk, len, 0) != len) { + throw SendRecvException(); + } + to_recv_to.path = strnk; + } + if(recv(socket, &to_recv_to.succeeded, sizeof(int), 0) != sizeof(int)) { + throw SendRecvException(); + } +} +template<> inline void recvData(int socket, Event_Chmod & to_recv_to) throw(std::exception) { + int my_xorsum = TypeIDHashtable::getHash(typeid(to_recv_to)); + int xorsum_recvd; + if(recv(socket, &xorsum_recvd, sizeof(int), 0) != sizeof(int)) + { + throw SendRecvException(); + } + if (xorsum_recvd != my_xorsum) + { + throw InconsistentTypeException(); + } + recvData(socket, to_recv_to.sharedInfo); + { + int len; + char * strnk; + if(recv(socket, &len, sizeof(len), 0) != sizeof(len)) { + throw SendRecvException(); + } + if (len > 1024) + throw TooLongString(); + strnk = new char[len]; + if(recv(socket, strnk, len, 0) != len) { + throw SendRecvException(); + } + to_recv_to.path = strnk; + } + { + int len; + char * strnk; + if(recv(socket, &len, sizeof(len), 0) != sizeof(len)) { + throw SendRecvException(); + } + if (len > 1024) + throw TooLongString(); + strnk = new char[len]; + if(recv(socket, strnk, len, 0) != len) { + throw SendRecvException(); + } + to_recv_to.name = strnk; + } + { + int len; + char * strnk; + if(recv(socket, &len, sizeof(len), 0) != sizeof(len)) { + throw SendRecvException(); + } + if (len > 1024) + throw TooLongString(); + strnk = new char[len]; + if(recv(socket, strnk, len, 0) != len) { + throw SendRecvException(); + } + to_recv_to.new_attrib = strnk; + } + if(recv(socket, &to_recv_to.succeeded, sizeof(int), 0) != sizeof(int)) { + throw SendRecvException(); + } +} +template<> inline void recvData(int socket, Event_CliEnd & to_recv_to) throw(std::exception) { + int my_xorsum = TypeIDHashtable::getHash(typeid(to_recv_to)); + int xorsum_recvd; + if(recv(socket, &xorsum_recvd, sizeof(int), 0) != sizeof(int)) + { + throw SendRecvException(); + } + if (xorsum_recvd != my_xorsum) + { + throw InconsistentTypeException(); + } + recvData(socket, to_recv_to.timestamp); +} +template<> inline void recvData(int socket, Event_ServQuit & to_recv_to) throw(std::exception) { + int my_xorsum = TypeIDHashtable::getHash(typeid(to_recv_to)); + int xorsum_recvd; + if(recv(socket, &xorsum_recvd, sizeof(int), 0) != sizeof(int)) + { + throw SendRecvException(); + } + if (xorsum_recvd != my_xorsum) + { + throw InconsistentTypeException(); + } +} +template<> inline void sendData(int socket, const Request_Shared & to_send) throw(std::exception) { + int xoredsum = TypeIDHashtable::getHash(typeid(to_send)); + if (send(socket, &xoredsum, sizeof(int), 0) != sizeof(int)) + { + throw SendRecvException(); + } + sendData(socket, to_send.timestamp); + sendData(socket, to_send.remote_ip); + { + int len = strlen(to_send.user_name) + 1; + if (len > 1024) + throw TooLongString(); + if(send(socket, &len, sizeof(len), 0) != sizeof(len)) { + throw SendRecvException(); + } + if(send(socket, to_send.user_name, len, 0) != len) { + throw SendRecvException(); + } + } +} +template<> inline void sendData(int socket, const Request_Login & to_send) throw(std::exception) { + int xoredsum = TypeIDHashtable::getHash(typeid(to_send)); + if (send(socket, &xoredsum, sizeof(int), 0) != sizeof(int)) + { + throw SendRecvException(); + } + sendData(socket, to_send.sharedInfo); + { + int len = strlen(to_send.login) + 1; + if (len > 1024) + throw TooLongString(); + if(send(socket, &len, sizeof(len), 0) != sizeof(len)) { + throw SendRecvException(); + } + if(send(socket, to_send.login, len, 0) != len) { + throw SendRecvException(); + } + } + { + int len = strlen(to_send.password) + 1; + if (len > 1024) + throw TooLongString(); + if(send(socket, &len, sizeof(len), 0) != sizeof(len)) { + throw SendRecvException(); + } + if(send(socket, to_send.password, len, 0) != len) { + throw SendRecvException(); + } + } +} +template<> inline void sendData(int socket, const Request_Homedir & to_send) throw(std::exception) { + int xoredsum = TypeIDHashtable::getHash(typeid(to_send)); + if (send(socket, &xoredsum, sizeof(int), 0) != sizeof(int)) + { + throw SendRecvException(); + } + sendData(socket, to_send.sharedInfo); +} +template<> inline void sendData(int socket, const Request_Read_File & to_send) throw(std::exception) { + int xoredsum = TypeIDHashtable::getHash(typeid(to_send)); + if (send(socket, &xoredsum, sizeof(int), 0) != sizeof(int)) + { + throw SendRecvException(); + } + sendData(socket, to_send.sharedInfo); + { + int len = strlen(to_send.path) + 1; + if (len > 1024) + throw TooLongString(); + if(send(socket, &len, sizeof(len), 0) != sizeof(len)) { + throw SendRecvException(); + } + if(send(socket, to_send.path, len, 0) != len) { + throw SendRecvException(); + } + } + { + int len = strlen(to_send.name) + 1; + if (len > 1024) + throw TooLongString(); + if(send(socket, &len, sizeof(len), 0) != sizeof(len)) { + throw SendRecvException(); + } + if(send(socket, to_send.name, len, 0) != len) { + throw SendRecvException(); + } + } + { + int len = strlen(to_send.fullName) + 1; + if (len > 1024) + throw TooLongString(); + if(send(socket, &len, sizeof(len), 0) != sizeof(len)) { + throw SendRecvException(); + } + if(send(socket, to_send.fullName, len, 0) != len) { + throw SendRecvException(); + } + } + if(send(socket, &to_send.size, sizeof(unsigned int), 0) != sizeof(unsigned int)) { + throw SendRecvException(); + } +} +template<> inline void sendData(int socket, const Request_Read_Directory & to_send) throw(std::exception) { + int xoredsum = TypeIDHashtable::getHash(typeid(to_send)); + if (send(socket, &xoredsum, sizeof(int), 0) != sizeof(int)) + { + throw SendRecvException(); + } + sendData(socket, to_send.sharedInfo); + { + int len = strlen(to_send.parentPath) + 1; + if (len > 1024) + throw TooLongString(); + if(send(socket, &len, sizeof(len), 0) != sizeof(len)) { + throw SendRecvException(); + } + if(send(socket, to_send.parentPath, len, 0) != len) { + throw SendRecvException(); + } + } + { + int len = strlen(to_send.name) + 1; + if (len > 1024) + throw TooLongString(); + if(send(socket, &len, sizeof(len), 0) != sizeof(len)) { + throw SendRecvException(); + } + if(send(socket, to_send.name, len, 0) != len) { + throw SendRecvException(); + } + } +} +template<> inline void sendData(int socket, const Request_Write_Directory & to_send) throw(std::exception) { + int xoredsum = TypeIDHashtable::getHash(typeid(to_send)); + if (send(socket, &xoredsum, sizeof(int), 0) != sizeof(int)) + { + throw SendRecvException(); + } + sendData(socket, to_send.sharedInfo); + { + int len = strlen(to_send.parentPath) + 1; + if (len > 1024) + throw TooLongString(); + if(send(socket, &len, sizeof(len), 0) != sizeof(len)) { + throw SendRecvException(); + } + if(send(socket, to_send.parentPath, len, 0) != len) { + throw SendRecvException(); + } + } + { + int len = strlen(to_send.name) + 1; + if (len > 1024) + throw TooLongString(); + if(send(socket, &len, sizeof(len), 0) != sizeof(len)) { + throw SendRecvException(); + } + if(send(socket, to_send.name, len, 0) != len) { + throw SendRecvException(); + } + } + if(send(socket, &to_send.does_exist, sizeof(int), 0) != sizeof(int)) { + throw SendRecvException(); + } +} +template<> inline void sendData(int socket, const Request_Write_File & to_send) throw(std::exception) { + int xoredsum = TypeIDHashtable::getHash(typeid(to_send)); + if (send(socket, &xoredsum, sizeof(int), 0) != sizeof(int)) + { + throw SendRecvException(); + } + sendData(socket, to_send.sharedInfo); + { + int len = strlen(to_send.path) + 1; + if (len > 1024) + throw TooLongString(); + if(send(socket, &len, sizeof(len), 0) != sizeof(len)) { + throw SendRecvException(); + } + if(send(socket, to_send.path, len, 0) != len) { + throw SendRecvException(); + } + } + { + int len = strlen(to_send.name) + 1; + if (len > 1024) + throw TooLongString(); + if(send(socket, &len, sizeof(len), 0) != sizeof(len)) { + throw SendRecvException(); + } + if(send(socket, to_send.name, len, 0) != len) { + throw SendRecvException(); + } + } + { + int len = strlen(to_send.fullName) + 1; + if (len > 1024) + throw TooLongString(); + if(send(socket, &len, sizeof(len), 0) != sizeof(len)) { + throw SendRecvException(); + } + if(send(socket, to_send.fullName, len, 0) != len) { + throw SendRecvException(); + } + } + if(send(socket, &to_send.does_exist, sizeof(int), 0) != sizeof(int)) { + throw SendRecvException(); + } +} +template<> inline void recvData(int socket, Request_Shared & to_recv_to) throw(std::exception) { + int my_xorsum = TypeIDHashtable::getHash(typeid(to_recv_to)); + int xorsum_recvd; + if(recv(socket, &xorsum_recvd, sizeof(int), 0) != sizeof(int)) + { + throw SendRecvException(); + } + if (xorsum_recvd != my_xorsum) + { + throw InconsistentTypeException(); + } + recvData(socket, to_recv_to.timestamp); + recvData(socket, to_recv_to.remote_ip); + { + int len; + char * strnk; + if(recv(socket, &len, sizeof(len), 0) != sizeof(len)) { + throw SendRecvException(); + } + if (len > 1024) + throw TooLongString(); + strnk = new char[len]; + if(recv(socket, strnk, len, 0) != len) { + throw SendRecvException(); + } + to_recv_to.user_name = strnk; + } +} +template<> inline void recvData(int socket, Request_Login & to_recv_to) throw(std::exception) { + int my_xorsum = TypeIDHashtable::getHash(typeid(to_recv_to)); + int xorsum_recvd; + if(recv(socket, &xorsum_recvd, sizeof(int), 0) != sizeof(int)) + { + throw SendRecvException(); + } + if (xorsum_recvd != my_xorsum) + { + throw InconsistentTypeException(); + } + recvData(socket, to_recv_to.sharedInfo); + { + int len; + char * strnk; + if(recv(socket, &len, sizeof(len), 0) != sizeof(len)) { + throw SendRecvException(); + } + if (len > 1024) + throw TooLongString(); + strnk = new char[len]; + if(recv(socket, strnk, len, 0) != len) { + throw SendRecvException(); + } + to_recv_to.login = strnk; + } + { + int len; + char * strnk; + if(recv(socket, &len, sizeof(len), 0) != sizeof(len)) { + throw SendRecvException(); + } + if (len > 1024) + throw TooLongString(); + strnk = new char[len]; + if(recv(socket, strnk, len, 0) != len) { + throw SendRecvException(); + } + to_recv_to.password = strnk; + } +} +template<> inline void recvData(int socket, Request_Homedir & to_recv_to) throw(std::exception) { + int my_xorsum = TypeIDHashtable::getHash(typeid(to_recv_to)); + int xorsum_recvd; + if(recv(socket, &xorsum_recvd, sizeof(int), 0) != sizeof(int)) + { + throw SendRecvException(); + } + if (xorsum_recvd != my_xorsum) + { + throw InconsistentTypeException(); + } + recvData(socket, to_recv_to.sharedInfo); +} +template<> inline void recvData(int socket, Request_Read_File & to_recv_to) throw(std::exception) { + int my_xorsum = TypeIDHashtable::getHash(typeid(to_recv_to)); + int xorsum_recvd; + if(recv(socket, &xorsum_recvd, sizeof(int), 0) != sizeof(int)) + { + throw SendRecvException(); + } + if (xorsum_recvd != my_xorsum) + { + throw InconsistentTypeException(); + } + recvData(socket, to_recv_to.sharedInfo); + { + int len; + char * strnk; + if(recv(socket, &len, sizeof(len), 0) != sizeof(len)) { + throw SendRecvException(); + } + if (len > 1024) + throw TooLongString(); + strnk = new char[len]; + if(recv(socket, strnk, len, 0) != len) { + throw SendRecvException(); + } + to_recv_to.path = strnk; + } + { + int len; + char * strnk; + if(recv(socket, &len, sizeof(len), 0) != sizeof(len)) { + throw SendRecvException(); + } + if (len > 1024) + throw TooLongString(); + strnk = new char[len]; + if(recv(socket, strnk, len, 0) != len) { + throw SendRecvException(); + } + to_recv_to.name = strnk; + } + { + int len; + char * strnk; + if(recv(socket, &len, sizeof(len), 0) != sizeof(len)) { + throw SendRecvException(); + } + if (len > 1024) + throw TooLongString(); + strnk = new char[len]; + if(recv(socket, strnk, len, 0) != len) { + throw SendRecvException(); + } + to_recv_to.fullName = strnk; + } + if(recv(socket, &to_recv_to.size, sizeof(unsigned int), 0) != sizeof(unsigned int)) { + throw SendRecvException(); + } +} +template<> inline void recvData(int socket, Request_Read_Directory & to_recv_to) throw(std::exception) { + int my_xorsum = TypeIDHashtable::getHash(typeid(to_recv_to)); + int xorsum_recvd; + if(recv(socket, &xorsum_recvd, sizeof(int), 0) != sizeof(int)) + { + throw SendRecvException(); + } + if (xorsum_recvd != my_xorsum) + { + throw InconsistentTypeException(); + } + recvData(socket, to_recv_to.sharedInfo); + { + int len; + char * strnk; + if(recv(socket, &len, sizeof(len), 0) != sizeof(len)) { + throw SendRecvException(); + } + if (len > 1024) + throw TooLongString(); + strnk = new char[len]; + if(recv(socket, strnk, len, 0) != len) { + throw SendRecvException(); + } + to_recv_to.parentPath = strnk; + } + { + int len; + char * strnk; + if(recv(socket, &len, sizeof(len), 0) != sizeof(len)) { + throw SendRecvException(); + } + if (len > 1024) + throw TooLongString(); + strnk = new char[len]; + if(recv(socket, strnk, len, 0) != len) { + throw SendRecvException(); + } + to_recv_to.name = strnk; + } +} +template<> inline void recvData(int socket, Request_Write_Directory & to_recv_to) throw(std::exception) { + int my_xorsum = TypeIDHashtable::getHash(typeid(to_recv_to)); + int xorsum_recvd; + if(recv(socket, &xorsum_recvd, sizeof(int), 0) != sizeof(int)) + { + throw SendRecvException(); + } + if (xorsum_recvd != my_xorsum) + { + throw InconsistentTypeException(); + } + recvData(socket, to_recv_to.sharedInfo); + { + int len; + char * strnk; + if(recv(socket, &len, sizeof(len), 0) != sizeof(len)) { + throw SendRecvException(); + } + if (len > 1024) + throw TooLongString(); + strnk = new char[len]; + if(recv(socket, strnk, len, 0) != len) { + throw SendRecvException(); + } + to_recv_to.parentPath = strnk; + } + { + int len; + char * strnk; + if(recv(socket, &len, sizeof(len), 0) != sizeof(len)) { + throw SendRecvException(); + } + if (len > 1024) + throw TooLongString(); + strnk = new char[len]; + if(recv(socket, strnk, len, 0) != len) { + throw SendRecvException(); + } + to_recv_to.name = strnk; + } + if(recv(socket, &to_recv_to.does_exist, sizeof(int), 0) != sizeof(int)) { + throw SendRecvException(); + } +} +template<> inline void recvData(int socket, Request_Write_File & to_recv_to) throw(std::exception) { + int my_xorsum = TypeIDHashtable::getHash(typeid(to_recv_to)); + int xorsum_recvd; + if(recv(socket, &xorsum_recvd, sizeof(int), 0) != sizeof(int)) + { + throw SendRecvException(); + } + if (xorsum_recvd != my_xorsum) + { + throw InconsistentTypeException(); + } + recvData(socket, to_recv_to.sharedInfo); + { + int len; + char * strnk; + if(recv(socket, &len, sizeof(len), 0) != sizeof(len)) { + throw SendRecvException(); + } + if (len > 1024) + throw TooLongString(); + strnk = new char[len]; + if(recv(socket, strnk, len, 0) != len) { + throw SendRecvException(); + } + to_recv_to.path = strnk; + } + { + int len; + char * strnk; + if(recv(socket, &len, sizeof(len), 0) != sizeof(len)) { + throw SendRecvException(); + } + if (len > 1024) + throw TooLongString(); + strnk = new char[len]; + if(recv(socket, strnk, len, 0) != len) { + throw SendRecvException(); + } + to_recv_to.name = strnk; + } + { + int len; + char * strnk; + if(recv(socket, &len, sizeof(len), 0) != sizeof(len)) { + throw SendRecvException(); + } + if (len > 1024) + throw TooLongString(); + strnk = new char[len]; + if(recv(socket, strnk, len, 0) != len) { + throw SendRecvException(); + } + to_recv_to.fullName = strnk; + } + if(recv(socket, &to_recv_to.does_exist, sizeof(int), 0) != sizeof(int)) { + throw SendRecvException(); + } +} diff --git a/unknown_object.h b/unknown_object.h new file mode 100644 index 0000000..3df8430 --- /dev/null +++ b/unknown_object.h @@ -0,0 +1,134 @@ +#ifndef UNKNOWN_OBJECT_H +#define UNKNOWN_OBJECT_H + +#include +#include +#include +#include +#include +#include "exceptions.h" +#include "typeid_hash.h" +#include "request_c_api.h" +#include "countedPtr.h" + +/** + * converts the object to data whic can be sent via sockets + */ + +template inline std::pair serialize(const T & object) { + char * memory = new char [sizeof(object) + sizeof(int)]; + int * xorsum = (int*)memory; + std::pair toRet; + *xorsum = TypeIDHashtable::getHash(typeid(object)); + memcpy(memory + sizeof(int), &object, sizeof(object)); + + toRet.first = memory; + toRet.second = sizeof(object) + sizeof(int); + return toRet; +} + +/** + * writes the serialized data to an object + */ +template inline void deserialize(const std::pair & data, T & object) { + int * xorsum = (int*)data.first; + + if (sizeof(object) != data.second) + { + throw DeserializationMemoryInconsistencyException(); + } + if (*xorsum != TypeIDHashtable::getHash(typeid(object))) + { + throw InconsistentTypeException(); + } + + memcpy(&object, data.first + sizeof(int), sizeof(T)); +} + +#include "serialization_auto.h" + +/** + * Provides means of returning different data with different types. + * This must be so, because we use one handler method in the pluginArchitecure... + * Anyway - it's a lot better than (void *) :) + */ +class UnknownObject { +public: + template UnknownObject(const T & object) { + std::pair serialized = serialize(object); + size = serialized.second; + data = std::auto_ptr(serialized.first); + name = typeid(object).name(); + } + + UnknownObject(const UnknownObject & object) { //copy constructor + size = object.size; + char * d = new char[size]; + memcpy(d, object.data.get(), size); + data = std::auto_ptr(d); + name = object.name; + } + + UnknownObject & operator=(const UnknownObject & object) { + size = object.size; + char * d = new char[size]; + memcpy(d, object.data.get(), size); + data = std::auto_ptr(d); + name = object.name; + return *this; + } + + unsigned int getDataSize() const { + return size; + } + + void * getData() { + return data.get(); + } + + std::string getTypeName() const{ + return name; + } + + static std::auto_ptr readFromSocket(int socket_number) { + std::auto_ptr toRet(new UnknownObject()); + int namelen; + if(recv(socket_number, &namelen, sizeof(namelen), 0) != sizeof(namelen)) + throw SendRecvException(); + if (recv(socket_number, &toRet->size, sizeof(toRet->size), 0) != sizeof(toRet->size)) + throw RecvException(); + + auto_arr str(new char[namelen]); + auto_arr dta(new char[toRet->size]); + if (recv(socket_number, str.get(), namelen, 0) != namelen) + throw RecvException(); + if (recv(socket_number, dta.get(), toRet->size, 0) != toRet->size) + throw RecvException(); + + toRet->data = dta; + toRet->name = std::string(str.get()); + + return toRet; + } + + void writeToSocket(int socket_number) const { + int len = name.length()+1; + printf("writing to sock\n"); + if (send(socket_number, &len, sizeof(len), 0) != sizeof(len)) + throw SendException(); + if(send(socket_number, &size, sizeof(size), 0) != sizeof(size)) + throw SendException(); + if (send(socket_number, name.c_str(), len, 0) != len) + throw SendException(); + if (send(socket_number, data.get(), size, 0) != size) + throw SendException(); + printf("writing to sock done\n"); + } +private: + UnknownObject() : size(0), data(0) {} + int size; + auto_arr data; + std::string name; +}; + +#endif //UNKNOWN_OBJECT_H diff --git a/utility.c b/utility.c new file mode 100644 index 0000000..bdbfcf0 --- /dev/null +++ b/utility.c @@ -0,0 +1,52 @@ +/* + * Part of Very Secure FTPd + * Licence: GPL v2 + * Author: Chris Evans + * utility.c + */ + +#include "utility.h" +#include "sysutil.h" +#include "str.h" +#include "defs.h" + +#define DIE_DEBUG + +void +die(const char* p_text) +{ +#ifdef DIE_DEBUG + bug(p_text); +#endif + vsf_sysutil_exit(1); +} + +void +die2(const char* p_text1, const char* p_text2) +{ + struct mystr die_str = INIT_MYSTR; + str_alloc_text(&die_str, p_text1); + str_append_text(&die_str, p_text2); + die(str_getbuf(&die_str)); +} + +void +bug(const char* p_text) +{ + /* Rats. Try and write the reason to the network for diagnostics */ + vsf_sysutil_activate_noblock(VSFTP_COMMAND_FD); + (void) vsf_sysutil_write_loop(VSFTP_COMMAND_FD, "500 OOPS: ", 10); + (void) vsf_sysutil_write_loop(VSFTP_COMMAND_FD, p_text, + vsf_sysutil_strlen(p_text)); + (void) vsf_sysutil_write_loop(VSFTP_COMMAND_FD, "\r\n", 2); + vsf_sysutil_exit(1); +} + +void +vsf_exit(const char* p_text) +{ + (void) vsf_sysutil_write_loop(VSFTP_COMMAND_FD, p_text, + vsf_sysutil_strlen(p_text)); + vsf_sysutil_exit(0); +} + diff --git a/utility.h b/utility.h new file mode 100644 index 0000000..aae3052 --- /dev/null +++ b/utility.h @@ -0,0 +1,44 @@ +#ifndef VSF_UTILITY_H +#define VSF_UTILITY_H + +struct mystr; + +/* die() + * PURPOSE + * Terminate execution of the process, due to an abnormal (but non-bug) + * situation. + * PARAMETERS + * p_text - text string describing why the process is exiting + */ +void die(const char* p_text); + +/* die2() + * PURPOSE + * Terminate execution of the process, due to an abnormal (but non-bug) + * situation. + * PARAMETERS + * p_text1 - text string describing why the process is exiting + * p_text2 - text to safely concatenate to p_text1 + */ +void die2(const char* p_text1, const char* p_text2); + +/* bug() + * PURPOSE + * Terminate execution of the process, due to a suspected bug, trying to emit + * the reason this happened down the network in FTP response format. + * PARAMETERS + * p_text - text string describing what bug trap has triggered + * */ +void bug(const char* p_text); + +/* vsf_exit() + * PURPOSE + * Terminate execution of the process, writing out the specified text string + * in the process. + * PARAMETERS + * p_text - text string describing why the process is exiting + */ +void vsf_exit(const char* p_text); + +#endif + diff --git a/vsf_findlibs.sh b/vsf_findlibs.sh new file mode 100755 index 0000000..3ae030b --- /dev/null +++ b/vsf_findlibs.sh @@ -0,0 +1,65 @@ +#!/bin/sh +# Cheesy hacky location of additional link libraries. + +locate_library() { [ ! "$1*" = "`echo $1*`" ]; } +find_func() { egrep $1 $2 >/dev/null; } + +if find_func hosts_access tcpwrap.o; then + echo "-lwrap"; + locate_library /lib/libnsl.so && echo "-lnsl"; +fi + +# Look for PAM (done weirdly due to distribution bugs (e.g. Debian) or the +# crypt library. +if find_func pam_start sysdeputil.o; then + locate_library /lib/libpam.so.0 && echo "/lib/libpam.so.0"; + locate_library /usr/lib/libpam.so && echo "-lpam"; + # HP-UX ends shared libraries with .sl + locate_library /usr/lib/libpam.sl && echo "-lpam"; + # AIX ends shared libraries with .a + locate_library /usr/lib/libpam.a && echo "-lpam"; +else + locate_library /lib/libcrypt.so && echo "-lcrypt"; + locate_library /usr/lib/libcrypt.so && echo "-lcrypt"; +fi + +# Look for the dynamic linker library. Needed by older RedHat when +# you link in PAM +locate_library /lib/libdl.so && echo "-ldl"; + +# Look for libsocket. Solaris needs this. +locate_library /lib/libsocket.so && echo "-lsocket"; + +# Look for libnsl. Solaris needs this. +locate_library /lib/libnsl.so && echo "-lnsl"; + +# Look for libresolv. Solaris needs this. +locate_library /lib/libresolv.so && echo "-lresolv"; + +# Look for libutil. Older FreeBSD need this for setproctitle(). +locate_library /usr/lib/libutil.so && echo "-lutil"; + +# For older HP-UX... +locate_library /usr/lib/libsec.sl && echo "-lsec"; + +# Look for libcap (capabilities) +locate_library /lib/libcap.so.1 && echo "/lib/libcap.so.1"; +locate_library /usr/lib/libcap.so && echo "-lcap"; + +# Solaris needs this for nanosleep().. +locate_library /lib/libposix4.so && echo "-lposix4"; +locate_library /usr/lib/libposix4.so && echo "-lposix4"; + +# Tru64 (nanosleep) +locate_library /usr/shlib/librt.so && echo "-lrt"; + +# Solaris sendfile +locate_library /usr/lib/libsendfile.so && echo "-lsendfile"; + +# OpenSSL +if find_func SSL_library_init ssl.o; then + echo "-lssl -lcrypto"; +fi + +exit 0; + diff --git a/vsftpd.8 b/vsftpd.8 new file mode 100644 index 0000000..8066ea6 --- /dev/null +++ b/vsftpd.8 @@ -0,0 +1,34 @@ +.\" Copyright (c) 2001 Daniel Jacobowitz +.Dd March 8, 2001 +.Dt VSFTPD 8 +.Sh NAME +.Nm vsftpd +.Nd Very Secure FTP Daemon +.Sh SYNOPSIS +.Nm vsftpd +.Op Ar configuration file +.Sh DESCRIPTION +.Nm vsftpd +is the Very Secure File Transfer Protocol Daemon. The server can be launched +via a +.Dq super-server +such as +.Xr inetd 8 +or +.Xr xinetd 8 . +Alternatively, vsftpd can be launched in standalone mode, in which case vsftpd +itself will listen on the network. This latter mode is easier to use, and +recommended. It is activated by setting +.Pa listen=YES +in +.Pa /etc/vsftpd.conf . +Direct execution of the +.Nm vsftpd +binary will then launch the FTP service ready for immediate client connections. +.Sh OPTIONS +An optional +.Op configuration file +may be given on the command line. The default configuration file is +.Pa /etc/vsftpd.conf . +.Sh SEE ALSO +.Xr vsftpd.conf 5 diff --git a/vsftpd.conf b/vsftpd.conf new file mode 100644 index 0000000..c5e7492 --- /dev/null +++ b/vsftpd.conf @@ -0,0 +1,117 @@ +# Example config file /etc/vsftpd.conf +# +# The default compiled in settings are fairly paranoid. This sample file +# loosens things up a bit, to make the ftp daemon more usable. +# Please see vsftpd.conf.5 for all compiled in defaults. +# +# READ THIS: This example file is NOT an exhaustive list of vsftpd options. +# Please read the vsftpd.conf.5 manual page to get a full idea of vsftpd's +# capabilities. +# +# Allow anonymous FTP? (Beware - allowed by default if you comment this out). +anonymous_enable=YES +anon_umask=022 +# +# Uncomment this to allow local users to log in. +local_enable=YES +# +# Uncomment this to enable any form of FTP write command. +write_enable=YES +# +# Default umask for local users is 077. You may wish to change this to 022, +# if your users expect that (022 is used by most other ftpd's) +#local_umask=022 +# +# Uncomment this to allow the anonymous FTP user to upload files. This only +# has an effect if the above global write enable is activated. Also, you will +# obviously need to create a directory writable by the FTP user. +anon_upload_enable=YES +# +# Uncomment this if you want the anonymous FTP user to be able to create +# new directories. +anon_mkdir_write_enable=YES +anon_other_write_enable=YES +# +# Activate directory messages - messages given to remote users when they +# go into a certain directory. +dirmessage_enable=YES +# +# Activate logging of uploads/downloads. +xferlog_enable=YES +# +# Make sure PORT transfer connections originate from port 20 (ftp-data). +connect_from_port_20=YES +# +# If you want, you can arrange for uploaded anonymous files to be owned by +# a different user. Note! Using "root" for uploaded files is not +# recommended! +#chown_uploads=YES +#chown_username=whoever +# +# You may override where the log file goes if you like. The default is shown +# below. +#xferlog_file=/var/log/vsftpd.log +# +# If you want, you can have your log file in standard ftpd xferlog format +#xferlog_std_format=YES +# +# You may change the default value for timing out an idle session. +#idle_session_timeout=600 +# +# You may change the default value for timing out a data connection. +#data_connection_timeout=120 +# +# It is recommended that you define on your system a unique user which the +# ftp server can use as a totally isolated and unprivileged user. +#nopriv_user=ftpsecure +# +# Enable this and the server will recognise asynchronous ABOR requests. Not +# recommended for security (the code is non-trivial). Not enabling it, +# however, may confuse older FTP clients. +#async_abor_enable=YES +# +# By default the server will pretend to allow ASCII mode but in fact ignore +# the request. Turn on the below options to have the server actually do ASCII +# mangling on files when in ASCII mode. +# Beware that on some FTP servers, ASCII support allows a denial of service +# attack (DoS) via the command "SIZE /big/file" in ASCII mode. vsftpd +# predicted this attack and has always been safe, reporting the size of the +# raw file. +# ASCII mangling is a horrible feature of the protocol. +#ascii_upload_enable=YES +#ascii_download_enable=YES +# +# You may fully customise the login banner string: +ftpd_banner=Welcome to blah FTP service. +# +# You may specify a file of disallowed anonymous e-mail addresses. Apparently +# useful for combatting certain DoS attacks. +#deny_email_enable=YES +# (default follows) +#banned_email_file=/etc/vsftpd.banned_emails +# +# You may specify an explicit list of local users to chroot() to their home +# directory. If chroot_local_user is YES, then this list becomes a list of +# users to NOT chroot(). +#chroot_list_enable=YES +# (default follows) +#chroot_list_file=/etc/vsftpd.chroot_list +# +# You may activate the "-R" option to the builtin ls. This is disabled by +# default to avoid remote users being able to cause excessive I/O on large +# sites. However, some broken FTP clients such as "ncftp" and "mirror" assume +# the presence of the "-R" option, so there is a strong case for enabling it. +#ls_recurse_enable=YES +listen=YES +#one_process_model=YES +#sql_plugin=VSFPgSQLPlugin +sql_plugin=VSFMySQLPlugin +#sql_plugin=VSFFileLoggerPlugin + +#plugin_architecture=NO + +pgsql_database_dbname=vsftp +pgsql_database_host=localhost +pgsql_database_port=5432 +pgsql_database_username=scott +pgsql_database_password=tiger diff --git a/vsftpd.conf.5 b/vsftpd.conf.5 new file mode 100644 index 0000000..cfe53e3 --- /dev/null +++ b/vsftpd.conf.5 @@ -0,0 +1,966 @@ +.TH VSFTPD.CONF 5 +.SH NAME +vsftpd.conf \- config file for vsftpd +.SH DESCRIPTION +vsftpd.conf may be used to control various aspects of vsftpd's behaviour. By +default, vsftpd looks for this file at the location +.BR /etc/vsftpd.conf . +However, you may override this by specifying a command line argument to +vsftpd. The command line argument is the pathname of the configuration file +for vsftpd. This behaviour is useful because you may wish to use an advanced +inetd such as +.BR xinetd +to launch vsftpd with different configuration files on a per virtual host +basis. + +.SH FORMAT +The format of vsftpd.conf is very simple. Each line is either a comment or +a directive. Comment lines start with a # and are ignored. A directive line +has the format: + +option=value + +It is important to note that it is an error to put any space between the +option, = and value. + +Each setting has a compiled in default which may be modified in the +configuration file. + +.SH BOOLEAN OPTIONS +Below is a list of boolean options. The value for a boolean option may be set +to +.BR YES +or +.BR NO . + +.TP +.B allow_anon_ssl +Only applies if +.BR ssl_enable +is active. If set to YES, anonymous users will be allowed to use secured SSL +connections. + +Default: NO +.TP +.B anon_mkdir_write_enable +If set to YES, anonymous users will be permitted to create new directories +under certain conditions. For this to work, the option +.BR write_enable +must be activated, and the anonymous ftp user must have write permission on +the parent directory. + +Default: NO +.TP +.B anon_other_write_enable +If set to YES, anonymous users will be permitted to perform write operations +other than upload and create directory, such as deletion and renaming. This +is generally not recommended but included for completeness. + +Default: NO +.TP +.B anon_upload_enable +If set to YES, anonymous users will be permitted to upload files under certain +conditions. For this to work, the option +.BR write_enable +must be activated, and the anonymous ftp user must have write permission on +desired upload locations. This setting is also required for virtual users to +upload; by default, virtual users are treated with anonymous (i.e. maximally +restricted) privilege. + +Default: NO +.TP +.B anon_world_readable_only +When enabled, anonymous users will only be allowed to download files which +are world readable. This is recognising that the ftp user may own files, +especially in the presence of uploads. + +Default: YES +.TP +.B anonymous_enable +Controls whether anonymous logins are permitted or not. If enabled, +both the usernames +.BR ftp +and +.BR anonymous +are recognised as anonymous logins. + +Default: YES +.TP +.B ascii_download_enable +When enabled, ASCII mode data transfers will be honoured on downloads. + +Default: NO +.TP +.B ascii_upload_enable +When enabled, ASCII mode data transfers will be honoured on uploads. + +Default: NO +.TP +.B async_abor_enable +When enabled, a special FTP command known as "async ABOR" will be enabled. +Only ill advised FTP clients will use this feature. Additionally, this feature +is awkward to handle, so it is disabled by default. Unfortunately, some FTP +clients will hang when cancelling a transfer unless this feature is available, +so you may wish to enable it. + +Default: NO +.TP +.B background +When enabled, and vsftpd is started in "listen" mode, vsftpd will background +the listener process. i.e. control will immediately be returned to the shell +which launched vsftpd. + +Default: NO +.TP +.B check_shell +Note! This option only has an effect for non-PAM builds of vsftpd. If disabled, +vsftpd will not check /etc/shells for a valid user shell for local logins. + +Default: YES +.TP +.B chmod_enable +When enables, allows use of the SITE CHMOD command. NOTE! This only applies +to local users. Anonymous users never get to use SITE CHMOD. + +Default: YES +.TP +.B chown_uploads +If enabled, all anonymously uploaded files will have the ownership changed +to the user specified in the setting +.BR chown_username . +This is useful from an administrative, and perhaps security, standpoint. + +Default: NO +.TP +.B chroot_list_enable +If activated, you may provide a list of local users who are placed in a +chroot() jail in their home directory upon login. The meaning is slightly +different if chroot_local_user is set to YES. In this case, the list becomes +a list of users which are NOT to be placed in a chroot() jail. +By default, the file containing this list is +/etc/vsftpd.chroot_list, but you may override this with the +.BR chroot_list_file +setting. + +Default: NO +.TP +.B chroot_local_user +If set to YES, local users will be (by default) placed in a chroot() jail in +their home directory after login. +.BR Warning: +This option has security implications, especially if the users have upload +permission, or shell access. Only enable if you know what you are doing. +Note that these security implications are not vsftpd specific. They apply to +all FTP daemons which offer to put local users in chroot() jails. + +Default: NO +.TP +.B connect_from_port_20 +This controls whether PORT style data connections use port 20 (ftp-data) on +the server machine. For security reasons, some clients may insist that this +is the case. Conversely, disabling this option enables vsftpd to run with +slightly less privilege. + +Default: NO (but the sample config file enables it) +.TP +.B deny_email_enable +If activated, you may provide a list of anonymous password e-mail responses +which cause login to be denied. By default, the file containing this list is +/etc/vsftpd.banned_emails, but you may override this with the +.BR banned_email_file +setting. + +Default: NO +.TP +.B dirlist_enable +If set to NO, all directory list commands will give permission denied. + +Default: YES +.TP +.B dirmessage_enable +If enabled, users of the FTP server can be shown messages when they first +enter a new directory. By default, a directory is scanned for the +file .message, but that may be overridden with the configuration setting +.BR message_file . + +Default: NO (but the sample config file enables it) +.TP +.B download_enable +If set to NO, all download requests will give permission denied. + +Default: YES +.TP +.B dual_log_enable +If enabled, two log files are generated in parallel, going by default to +.BR /var/log/xferlog +and +.BR /var/log/vsftpd.log . +The former is a wu-ftpd style transfer log, parseable by standard tools. The +latter is vsftpd's own style log. + +Default: NO +.TP +.B force_dot_files +If activated, files and directories starting with . will be shown in directory +listings even if the "a" flag was not used by the client. This override +excludes the "." and ".." entries. + +Default: NO +.TP +.B force_anon_data_ssl +Only applies if +.BR ssl_enable +is activated. If activated, all anonymous logins are forced to use a secure +SSL connection in order to send and receive data on data connections. + +Default: NO +.TP +.B force_anon_logins_ssl +Only applies if +.BR ssl_enable +is activated. If activated, all anonymous logins are forced to use a secure +SSL connection in order to send the password. + +Default: NO +.TP +.B force_local_data_ssl +Only applies if +.BR ssl_enable +is activated. If activated, all non-anonymous logins are forced to use a secure +SSL connection in order to send and receive data on data connections. + +Default: YES +.TP +.B force_local_logins_ssl +Only applies if +.BR ssl_enable +is activated. If activated, all non-anonymous logins are forced to use a secure +SSL connection in order to send the password. + +Default: YES +.TP +.B guest_enable +If enabled, all non-anonymous logins are classed as "guest" logins. A guest +login is remapped to the user specified in the +.BR guest_username +setting. + +Default: NO +.TP +.B hide_ids +If enabled, all user and group information in directory listings will be +displayed as "ftp". + +Default: NO +.TP +.B listen +If enabled, vsftpd will run in standalone mode. This means that vsftpd must +not be run from an inetd of some kind. Instead, the vsftpd executable is +run once directly. vsftpd itself will then take care of listening for and +handling incoming connections. + +Default: NO +.TP +.B listen_ipv6 +Like the listen parameter, except vsftpd will listen on an IPv6 socket instead +of an IPv4 one. This parameter and the listen parameter are mutually +exclusive. + +Default: NO +.TP +.B local_enable +Controls whether local logins are permitted or not. If enabled, normal +user accounts in /etc/passwd (or wherever your PAM config references) may be +used to log in. This must be enable for any non-anonymous login to work, +including virtual users. + +Default: NO +.TP +.B lock_upload_files +When enabled, all uploads proceed with a write lock on the upload file. All +downloads proceed with a shared read lock on the download file. WARNING! +Before enabling this, be aware that malicious readers could starve a writer +wanting to e.g. append a file. + +Default: NO +.TP +.B log_ftp_protocol +When enabled, all FTP requests and responses are logged, providing the option +xferlog_std_format is not enabled. Useful for debugging. + +Default: NO +.TP +.B ls_recurse_enable +When enabled, this setting will allow the use of "ls -R". This is a minor +security risk, because a ls -R at the top level of a large site may consume +a lot of resources. + +Default: NO +.TP +.B mdtm_write +When enabled, this setting will allow MDTM to set file modification times +(subject to the usual access checks). + +Default: YES +.TP +.B no_anon_password +When enabled, this prevents vsftpd from asking for an anonymous password - +the anonymous user will log straight in. + +Default: NO +.TP +.B no_log_lock +When enabled, this prevents vsftpd from taking a file lock when writing to log +files. This option should generally not be enabled. It exists to workaround +operating system bugs such as the Solaris / Veritas filesystem combination +which has been observed to sometimes exhibit hangs trying to lock log files. + +Default: NO +.TP +.B one_process_model +If you have a Linux 2.4 kernel, it is possible to use a different security +model which only uses one process per connection. It is a less pure security +model, but gains you performance. You really don't want to enable this unless +you know what you are doing, and your site supports huge numbers of +simultaneously connected users. + +Default: NO +.TP +.B passwd_chroot_enable +If enabled, along with +.BR chroot_local_user +, then a chroot() jail location may be specified on a per-user basis. Each +user's jail is derived from their home directory string in /etc/passwd. The +occurrence of /./ in the home directory string denotes that the jail is at that +particular location in the path. + +Default: NO +.TP +.B pasv_addr_resolve +Set to YES if you want to use a hostname (as opposed to IP address) in the +.BR pasv_address +option. + +Default: NO +.TP +.B pasv_enable +Set to NO if you want to disallow the PASV method of obtaining a data +connection. + +Default: YES +.TP +.B pasv_promiscuous +Set to YES if you want to disable the PASV security check that ensures the +data connection originates from the same IP address as the control connection. +Only enable if you know what you are doing! The only legitimate use for this +is in some form of secure tunnelling scheme, or perhaps to facilitate FXP +support. + +Default: NO +.TP +.B port_enable +Set to NO if you want to disallow the PORT method of obtaining a data +connection. + +Default: YES +.TP +.B port_promiscuous +Set to YES if you want to disable the PORT security check that ensures that +outgoing data connections can only connect to the client. Only enable if +you know what you are doing! + +Default: NO +.TP +.B run_as_launching_user +Set to YES if you want vsftpd to run as the user which launched vsftpd. This is +useful where root access is not available. MASSIVE WARNING! Do NOT enable this +option unless you totally know what you are doing, as naive use of this option +can create massive security problems. Specifically, vsftpd does not / cannot +use chroot technology to restrict file access when this option is set (even if +launched by root). A poor substitute could be to use a +.BR deny_file +setting such as {/*,*..*}, but the reliability of this cannot compare to +chroot, and should not be relied on. +If using this option, many restrictions on other options +apply. For example, options requiring privilege such as non-anonymous logins, +upload ownership changing, connecting from port 20 and listen ports less than +1024 are not expected to work. Other options may be impacted. + +Default: NO +.TP +.B secure_email_list_enable +Set to YES if you want only a specified list of e-mail passwords for anonymous +logins to be accepted. This is useful as a low-hassle way of restricting +access to low-security content without needing virtual users. When enabled, +anonymous logins are prevented unless the password provided is listed in the +file specified by the +.BR email_password_file +setting. The file format is one password per line, no extra whitespace. The +default filename is /etc/vsftpd.email_passwords. + +Default: NO +.TP +.B session_support +This controls whether vsftpd attempts to maintain sessions for logins. If +vsftpd is maintaining sessions, it will try and update utmp and wtmp. It +will also open a pam_session if using PAM to authenticate, and only close +this upon logout. You may wish to disable this if you do not need session +logging, and you wish to give vsftpd more opportunity to run with less +processes and / or less privilege. NOTE - utmp and wtmp support is only +provided with PAM enabled builds. + +Default: NO +.TP +.B setproctitle_enable +If enabled, vsftpd will try and show session status information in the system +process listing. In other words, the reported name of the process will change +to reflect what a vsftpd session is doing (idle, downloading etc). You +probably want to leave this off for security purposes. + +Default: NO +.TP +.B ssl_enable +If enabled, and vsftpd was compiled against OpenSSL, vsftpd will support secure +connections via SSL. This applies to the control connection (including login) +and also data connections. You'll need a client with SSL support too. NOTE!! +Beware enabling this option. Only enable it if you need it. vsftpd can make no +guarantees about the security of the OpenSSL libraries. By enabling this +option, you are declaring that you trust the security of your installed +OpenSSL library. + +Default: NO +.TP +.B ssl_sslv2 +Only applies if +.BR ssl_enable +is activated. If enabled, this option will permit SSL v2 protocol connections. +TLS v1 connections are preferred. + +Default: NO +.TP +.B ssl_sslv3 +Only applies if +.BR ssl_enable +is activated. If enabled, this option will permit SSL v3 protocol connections. +TLS v1 connections are preferred. + +Default: NO +.TP +.B ssl_tlsv1 +Only applies if +.BR ssl_enable +is activated. If enabled, this option will permit TLS v1 protocol connections. +TLS v1 connections are preferred. + +Default: YES +.TP +.B syslog_enable +If enabled, then any log output which would have gone to /var/log/vsftpd.log +goes to the system log instead. Logging is done under the FTPD facility. + +Default: NO +.TP +.B tcp_wrappers +If enabled, and vsftpd was compiled with tcp_wrappers support, incoming +connections will be fed through tcp_wrappers access control. Furthermore, +there is a mechanism for per-IP based configuration. If tcp_wrappers sets +the VSFTPD_LOAD_CONF environment variable, then the vsftpd session will try +and load the vsftpd configuration file specified in this variable. + +Default: NO +.TP +.B text_userdb_names +By default, numeric IDs are shown in the user and group fields of directory +listings. You can get textual names by enabling this parameter. It is off +by default for performance reasons. + +Default: NO +.TP +.B tilde_user_enable +If enabled, vsftpd will try and resolve pathnames such as ~chris/pics, i.e. a +tilde followed by a username. Note that vsftpd will always resolve the +pathnames ~ and ~/something (in this case the ~ resolves to the initial +login directory). Note that ~user paths will only resolve if the file +.BR /etc/passwd +may be found within the _current_ chroot() jail. + +Default: NO +.TP +.B use_localtime +If enabled, vsftpd will display directory listings with the time in your +local time zone. The default is to display GMT. The times returned by the +MDTM FTP command are also affected by this option. + +Default: NO +.TP +.B use_sendfile +An internal setting used for testing the relative benefit of using the +sendfile() system call on your platform. + +Default: YES +.TP +.B userlist_deny +This option is examined if +.B userlist_enable +is activated. If you set this setting to NO, then users will be denied login +unless they are explicitly listed in the file specified by +.BR userlist_file . +When login is denied, the denial is issued before the user is asked for a +password. + +Default: YES +.TP +.B userlist_enable +If enabled, vsftpd will load a list of usernames, from the filename given by +.BR userlist_file . +If a user tries to log in using a name in this file, they will be denied +before they are asked for a password. This may be useful in preventing +cleartext passwords being transmitted. See also +.BR userlist_deny . + +Default: NO +.TP +.B virtual_use_local_privs +If enabled, virtual users will use the same privileges as local users. By +default, virtual users will use the same privileges as anonymous users, which +tends to be more restrictive (especially in terms of write access). + +Default: NO +.TP +.B write_enable +This controls whether any FTP commands which change the filesystem are allowed +or not. These commands are: STOR, DELE, RNFR, RNTO, MKD, RMD, APPE and SITE. + +Default: NO +.TP +.B xferlog_enable +If enabled, a log file will be maintained detailling uploads and downloads. +By default, this file will be placed at /var/log/vsftpd.log, but this location +may be overridden using the configuration setting +.BR vsftpd_log_file . + +Default: NO (but the sample config file enables it) +.TP +.B xferlog_std_format +If enabled, the transfer log file will be written in standard xferlog format, +as used by wu-ftpd. This is useful because you can reuse existing transfer +statistics generators. The default format is more readable, however. The +default location for this style of log file is /var/log/xferlog, but you may +change it with the setting +.BR xferlog_file . + +Default: NO + +.SH NUMERIC OPTIONS +Below is a list of numeric options. A numeric option must be set to a non +negative integer. Octal numbers are supported, for convenience of the umask +options. To specify an octal number, use 0 as the first digit of the number. + +.TP +.B accept_timeout +The timeout, in seconds, for a remote client to establish connection with +a PASV style data connection. + +Default: 60 +.TP +.B anon_max_rate +The maximum data transfer rate permitted, in bytes per second, for anonymous +clients. + +Default: 0 (unlimited) +.TP +.B anon_umask +The value that the umask for file creation is set to for anonymous users. NOTE! If you want to specify octal values, remember the "0" prefix otherwise the +value will be treated as a base 10 integer! + +Default: 077 +.TP +.B connect_timeout +The timeout, in seconds, for a remote client to respond to our PORT style +data connection. + +Default: 60 +.TP +.B data_connection_timeout +The timeout, in seconds, which is roughly the maximum time we permit data +transfers to stall for with no progress. If the timeout triggers, the remote +client is kicked off. + +Default: 300 +.TP +.B delay_failed_logins +The number of seconds to pause prior to reporting a failed login. + +Default: 1 +.TP +.B delay_successful_logins +The number of seconds to pause prior to allowing a successful login. + +Default: 0 +.TP +.B file_open_mode +The permissions with which uploaded files are created. Umasks are applied +on top of this value. You may wish to change to 0777 if you want uploaded +files to be executable. + +Default: 0666 +.TP +.B ftp_data_port +The port from which PORT style connections originate (as long as the poorly +named +.BR connect_from_port_20 +is enabled). + +Default: 20 +.TP +.B idle_session_timeout +The timeout, in seconds, which is the maximum time a remote client may spend +between FTP commands. If the timeout triggers, the remote client is kicked +off. + +Default: 300 +.TP +.B listen_port +If vsftpd is in standalone mode, this is the port it will listen on for +incoming FTP connections. + +Default: 21 +.TP +.B local_max_rate +The maximum data transfer rate permitted, in bytes per second, for local +authenticated users. + +Default: 0 (unlimited) +.TP +.B local_umask +The value that the umask for file creation is set to for local users. NOTE! If +you want to specify octal values, remember the "0" prefix otherwise the value +will be treated as a base 10 integer! + +Default: 077 +.TP +.B max_clients +If vsftpd is in standalone mode, this is the maximum number of clients which +may be connected. Any additional clients connecting will get an error message. + +Default: 0 (unlimited) +.TP +.B max_login_fails +After this many login failures, the session is killed. + +Default: 3 +.TP +.B max_per_ip +If vsftpd is in standalone mode, this is the maximum number of clients which +may be connected from the same source internet address. A client will get an +error message if they go over this limit. + +Default: 0 (unlimited) +.TP +.B pasv_max_port +The maximum port to allocate for PASV style data connections. Can be used to +specify a narrow port range to assist firewalling. + +Default: 0 (use any port) +.TP +.B pasv_min_port +The minimum port to allocate for PASV style data connections. Can be used to +specify a narrow port range to assist firewalling. + +Default: 0 (use any port) +.TP +.B trans_chunk_size +You probably don't want to change this, but try setting it to something like +8192 for a much smoother bandwidth limiter. + +Default: 0 (let vsftpd pick a sensible setting) + +.SH STRING OPTIONS +Below is a list of string options. + +.TP +.B anon_root +This option represents a directory which vsftpd will try to change into +after an anonymous login. Failure is silently ignored. + +Default: (none) +.TP +.B banned_email_file +This option is the name of a file containing a list of anonymous e-mail +passwords which are not permitted. This file is consulted if the option +.BR deny_email_enable +is enabled. + +Default: /etc/vsftpd.banned_emails +.TP +.B banner_file +This option is the name of a file containing text to display when someone +connects to the server. If set, it overrides the banner string provided by +the +.BR ftpd_banner +option. + +Default: (none) +.TP +.B chown_username +This is the name of the user who is given ownership of anonymously uploaded +files. This option is only relevant if another option, +.BR chown_uploads , +is set. + +Default: root +.TP +.B chroot_list_file +The option is the name of a file containing a list of local users which +will be placed in a chroot() jail in their home directory. This option is +only relevant if the option +.BR chroot_list_enable +is enabled. If the option +.BR chroot_local_user +is enabled, then the list file becomes a list of users to NOT place in a +chroot() jail. + +Default: /etc/vsftpd.chroot_list +.TP +.B cmds_allowed +This options specifies a comma separated list of allowed FTP commands (post +login. USER, PASS and QUIT are always allowed pre-login). Other +commands are rejected. This is a powerful method of really locking down an +FTP server. Example: cmds_allowed=PASV,RETR,QUIT + +Default: (none) +.TP +.B deny_file +This option can be used to set a pattern for filenames (and directory names +etc.) which should not be accessible in any way. The affected items are not +hidden, but any attempt to do anything to them (download, change into +directory, affect something within directory etc.) will be denied. This option +is very simple, and should not be used for serious access control - the +filesystem's permissions should be used in preference. However, this option +may be useful in certain virtual user setups. In particular aware that if +a filename is accessible by a variety of names (perhaps due to symbolic +links or hard links), then care must be taken to deny access to all the names. +Access will be denied to items if their name contains the string given by +hide_file, or if they match the regular expression specified by hide_file. +Note that vsftpd's regular expression matching code is a simple implementation +which is a subset of full regular expression functionality. Because of this, +you will need to carefully and exhaustively test any application of this +option. And you are recommended to use filesystem permissions for any +important security policies due to their greater reliability. Supported +regex syntax is any number of *, ? and unnested {,} operators. Regex +matching is only supported on the last component of a path, e.g. a/b/? is +supported but a/?/c is not. +Example: deny_file={*.mp3,*.mov,.private} + +Default: (none) +.TP +.B dsa_cert_file +This option specifies the location of the DSA certificate to use for SSL +encrypted connections. + +Default: (none - an RSA certificate suffices) +.TP +.B dsa_private_key_file +This option specifies the location of the DSA private key to use for SSL +encrypted connections. If this option is not set, the private key is expected +to be in the same file as the certificate. + +Default: (none) +.TP +.B email_password_file +This option can be used to provide an alternate file for usage by the +.BR secure_email_list_enable +setting. + +Default: /etc/vsftpd.email_passwords +.TP +.B ftp_username +This is the name of the user we use for handling anonymous FTP. The home +directory of this user is the root of the anonymous FTP area. + +Default: ftp +.TP +.B ftpd_banner +This string option allows you to override the greeting banner displayed +by vsftpd when a connection first comes in. + +Default: (none - default vsftpd banner is displayed) +.TP +.B guest_username +See the boolean setting +.BR guest_enable +for a description of what constitutes a guest login. This setting is the +real username which guest users are mapped to. + +Default: ftp +.TP +.B hide_file +This option can be used to set a pattern for filenames (and directory names +etc.) which should be hidden from directory listings. Despite being hidden, +the files / directories etc. are fully accessible to clients who know what +names to actually use. Items will be hidden if their names contain the string +given by hide_file, or if they match the regular expression specified by +hide_file. Note that vsftpd's regular expression matching code is a simple +implementation which is a subset of full regular expression functionality. +See +.BR deny_file +for details of exactly what regex syntax is supported. +Example: hide_file={*.mp3,.hidden,hide*,h?} + +Default: (none) +.TP +.B listen_address +If vsftpd is in standalone mode, the default listen address (of all local +interfaces) may be overridden by this setting. Provide a numeric IP address. + +Default: (none) +.TP +.B listen_address6 +Like listen_address, but specifies a default listen address for the IPv6 +listener (which is used if listen_ipv6 is set). Format is standard IPv6 +address format. + +Default: (none) +.TP +.B local_root +This option represents a directory which vsftpd will try to change into +after a local (i.e. non-anonymous) login. Failure is silently ignored. + +Default: (none) +.TP +.B message_file +This option is the name of the file we look for when a new directory is +entered. The contents are displayed to the remote user. This option is +only relevant if the option +.BR dirmessage_enable +is enabled. + +Default: .message +.TP +.B nopriv_user +This is the name of the user that is used by vsftpd when it wants to be +totally unprivileged. Note that this should be a dedicated user, rather +than nobody. The user nobody tends to be used for rather a lot of important +things on most machines. + +Default: nobody +.TP +.B pam_service_name +This string is the name of the PAM service vsftpd will use. + +Default: ftp +.TP +.B pasv_address +Use this option to override the IP address that vsftpd will advertise in +response to the PASV command. Provide a numeric IP address, unless +.BR pasv_addr_resolve +is enabled, in which case you can provide a hostname which will be DNS +resolved for you at startup. + +Default: (none - the address is taken from the incoming connected socket) +.TP +.B rsa_cert_file +This option specifies the location of the RSA certificate to use for SSL +encrypted connections. + +Default: /usr/share/ssl/certs/vsftpd.pem +.TP +.B rsa_private_key_file +This option specifies the location of the RSA private key to use for SSL +encrypted connections. If this option is not set, the private key is expected +to be in the same file as the certificate. + +Default: (none) +.TP +.B secure_chroot_dir +This option should be the name of a directory which is empty. Also, the +directory should not be writable by the ftp user. This directory is used +as a secure chroot() jail at times vsftpd does not require filesystem access. + +Default: /usr/share/empty +.TP +.B ssl_ciphers +This option can be used to select which SSL ciphers vsftpd will allow for +encrpyted SSL connections. See the +.BR ciphers +man page for further details. Note that restricting ciphers can be a useful +security precaution as it prevents malicious remote parties forcing a cipher +which they have found problems with. + +Default: DES-CBC3-SHA +.TP +.B user_config_dir +This powerful option allows the override of any config option specified in +the manual page, on a per-user basis. Usage is simple, and is best illustrated +with an example. If you set +.BR user_config_dir +to be +.BR /etc/vsftpd_user_conf +and then log on as the user "chris", then vsftpd will apply the settings in +the file +.BR /etc/vsftpd_user_conf/chris +for the duration of the session. The format of this file is as detailed in +this manual page! PLEASE NOTE that not all settings are effective on a +per-user basis. For example, many settings only prior to the user's session +being started. Examples of settings which will not affect any behviour on +a per-user basis include listen_address, banner_file, max_per_ip, max_clients, +xferlog_file, etc. + +Default: (none) +.TP +.B user_sub_token +This option is useful is conjunction with virtual users. It is used to +automatically generate a home directory for each virtual user, based on a +template. For example, if the home directory of the real user specified via +.BR guest_username +is +.BR /home/virtual/$USER , +and +.BR user_sub_token +is set to +.BR $USER , +then when virtual user fred logs in, he will end up (usually chroot()'ed) in +the directory +.BR /home/virtual/fred . +This option also takes affect if +.BR local_root +contains +.BR user_sub_token . + +Default: (none) +.TP +.B userlist_file +This option is the name of the file loaded when the +.BR userlist_enable +option is active. + +Default: /etc/vsftpd.user_list +.TP +.B vsftpd_log_file +This option is the name of the file to which we write the vsftpd style +log file. This log is only written if the option +.BR xferlog_enable +is set, and +.BR xferlog_std_format +is NOT set. Alternatively, it is written if you have set the option +.BR dual_log_enable . +One further complication - if you have set +.BR syslog_enable , +then this file is not written and output is sent to the system log instead. + +Default: /var/log/vsftpd.log +.TP +.B xferlog_file +This option is the name of the file to which we write the wu-ftpd style +transfer log. The transfer log is only written if the option +.BR xferlog_enable +is set, along with +.BR xferlog_std_format . +Alternatively, it is written if you have set the option +.BR dual_log_enable . + +Default: /var/log/xferlog + +.SH AUTHOR +chris@scary.beasts.org + diff --git a/vsftpver.h b/vsftpver.h new file mode 100644 index 0000000..6949e10 --- /dev/null +++ b/vsftpver.h @@ -0,0 +1,7 @@ +#ifndef VSF_VERSION_H +#define VSF_VERSION_H + +#define VSF_VERSION "2.0.5" + +#endif /* VSF_VERSION_H */ + diff --git a/vsfutils.cpp b/vsfutils.cpp new file mode 100644 index 0000000..f00c748 --- /dev/null +++ b/vsfutils.cpp @@ -0,0 +1,145 @@ +#include "vsfutils.h" +using namespace std; + +void VSFUtils::tokenize(const string& str, + vector& tokens, + const string& delimiters) +{ + // Skip delimiters at beginning. + string::size_type lastPos = str.find_first_not_of(delimiters, 0); + // Find first "non-delimiter". + string::size_type pos = str.find_first_of(delimiters, lastPos); + + while (string::npos != pos || string::npos != lastPos) + { + // Found a token, add it to the vector. + tokens.push_back(str.substr(lastPos, pos - lastPos)); + // Skip delimiters. Note the "not_of" + lastPos = str.find_first_not_of(delimiters, pos); + // Find next "non-delimiter" + pos = str.find_first_of(delimiters, lastPos); + } +} + +void VSFUtils::tokenize(const string& str, + vector& tokens) +{tokenize(str,tokens,"/");} + +std::string VSFUtils::trim(std::string& s) +{ + std::string r=s.erase(s.find_last_not_of(" ")+1); + return r.erase(0,r.find_first_not_of(" ")); +} +std::pair VSFUtils::sanitizePath(const char * src_path_c,const char * src_name_c) +{ + std::string src_path; + std::string src_name; + src_path=src_path_c; + src_name=src_name_c; + return sanitizePath(src_path,src_name); +} +std::pair VSFUtils::sanitizePath(std::string& src_path,std::string& src_name) +{ + std::pair result; + std::string temp; + std::string res_path; + std::string res_name; + std::vector tokens; + std::vector tokensBis; + vector::iterator tempIterator; + int path_is_global; + + //trimming + src_path=trim(src_path); + src_name=trim(src_name); + //linking + temp=src_path+"/"+src_name; + if(temp[0]=='/') + { + path_is_global=1; + } + else + { + path_is_global=0; + } + //tokenizing + tokenize(temp,tokens); + //removing unwanted tokens + for(tempIterator=tokens.begin();tempIterator != tokens.end(); tempIterator++ ) + { + //removing multiple slash (//) + if ((*tempIterator).length()==0) + { + continue; + } + //removing selfpaths (/./) + if ((*tempIterator)==".") + { + continue; + } + //removing updirs (/../) + if ((*tempIterator)=="..") + { + if (!tokensBis.empty()) + tokensBis.pop_back(); + continue; + } + tokensBis.push_back(*tempIterator); + } + + if (!tokensBis.empty()) + { + res_name = tokensBis.back(); + tokensBis.pop_back(); + } + else + { + res_name = "."; + } + if(path_is_global) + { + res_path="/"; + } + for (tempIterator=tokensBis.begin();tempIterator != tokensBis.end(); tempIterator++) + { + res_path += *tempIterator + "/"; + } + //reassembling + result.first=res_path; + result.second=res_name; + return result; +} + +C_COMPATIBLE_FUNCTION void sanitize_path_and_name(const char** path, const char** name) { + string sPath(*path); + string sName(*name); + pair ret = VSFUtils::sanitizePath(sPath, sName); + free(const_cast(*path)); + free(const_cast(*name)); + *path = strdup(ret.first.c_str()); + *name = strdup(ret.second.c_str()); +} + +C_COMPATIBLE_FUNCTION void sanitize_path(const char** pathAndName) { + string sPath(*pathAndName); + string sName("name"); + pair ret = VSFUtils::sanitizePath(sPath, sName); + free(const_cast(*pathAndName)); + *pathAndName = strdup(ret.first.substr(0,ret.first.length()-1).c_str()); + //*pathAndName = strdup(ret.first.c_str()); +} + + +/* +//main function... for testing purposes only! +int main() +{ + string p; + string n; + cout << "PATH?:"; + cin >> p; + cout << "NAME?:"; + cin >> n; + cout << "\nRESULT:\nPATH:" << VSFUtils::sanitizePath(p,n).first << endl << "NAME:" << VSFUtils::sanitizePath(p,n).second << endl; +} +//*/ diff --git a/vsfutils.h b/vsfutils.h new file mode 100644 index 0000000..1cac897 --- /dev/null +++ b/vsfutils.h @@ -0,0 +1,19 @@ +#ifndef VSFUTILS_H +#define VSFUTILS_H +#include +#include +#include +#include "cpp_to_c_helpers.h" +#include "vsfutils_c.h" + +class VSFUtils { +private: + static void tokenize(const std::string& str, std::vector& tokens, const std::string& delimiters); + static void tokenize(const std::string& str, std::vector& tokens); + static std::string trim(std::string& s); +public: + static std::pair sanitizePath(std::string&,std::string& ); + static std::pair sanitizePath(const char * ,const char * ); +}; + +#endif diff --git a/vsfutils_c.h b/vsfutils_c.h new file mode 100644 index 0000000..044fe7d --- /dev/null +++ b/vsfutils_c.h @@ -0,0 +1,8 @@ +#ifndef VSFUTILS_C_H +#define VSFUTILS_C_H + +C_COMPATIBLE_FUNCTION void sanitize_path_and_name(const char** path, const char** name); +C_COMPATIBLE_FUNCTION void sanitize_path(const char** pathAndName); + +#endif //VSFUTILS_C_H +