diff --git a/LICENSES/GPL-2.0-or-later.txt b/LICENSES/GPL-2.0-or-later.txt new file mode 100644 index 00000000..1d80ac36 --- /dev/null +++ b/LICENSES/GPL-2.0-or-later.txt @@ -0,0 +1,319 @@ +GNU GENERAL PUBLIC LICENSE + +Version 2, June 1991 + +Copyright (C) 1989, 1991 Free Software Foundation, Inc. + +51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA + +Everyone is permitted to copy and distribute verbatim copies of this license +document, but changing it is not allowed. + +Preamble + +The licenses for most software are designed to take away your freedom to share +and change it. By contrast, the GNU General Public License is intended to +guarantee your freedom to share and change free software--to make sure the +software is free for all its users. This General Public License applies to +most of the Free Software Foundation's software and to any other program whose +authors commit to using it. (Some other Free Software Foundation software +is covered by the GNU Lesser General Public License instead.) You can apply +it to your programs, too. + +When we speak of free software, we are referring to freedom, not price. Our +General Public Licenses are designed to make sure that you have the freedom +to distribute copies of free software (and charge for this service if you +wish), that you receive source code or can get it if you want it, that you +can change the software or use pieces of it in new free programs; and that +you know you can do these things. + +To protect your rights, we need to make restrictions that forbid anyone to +deny you these rights or to ask you to surrender the rights. These restrictions +translate to certain responsibilities for you if you distribute copies of +the software, or if you modify it. + +For example, if you distribute copies of such a program, whether gratis or +for a fee, you must give the recipients all the rights that you have. You +must make sure that they, too, receive or can get the source code. And you +must show them these terms so they know their rights. + +We protect your rights with two steps: (1) copyright the software, and (2) +offer you this license which gives you legal permission to copy, distribute +and/or modify the software. + +Also, for each author's protection and ours, we want to make certain that +everyone understands that there is no warranty for this free software. If +the software is modified by someone else and passed on, we want its recipients +to know that what they have is not the original, so that any problems introduced +by others will not reflect on the original authors' reputations. + +Finally, any free program is threatened constantly by software patents. We +wish to avoid the danger that redistributors of a free program will individually +obtain patent licenses, in effect making the program proprietary. To prevent +this, we have made it clear that any patent must be licensed for everyone's +free use or not licensed at all. + +The precise terms and conditions for copying, distribution and modification +follow. + +TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + +0. This License applies to any program or other work which contains a notice +placed by the copyright holder saying it may be distributed under the terms +of this General Public License. The "Program", below, refers to any such program +or work, and a "work based on the Program" means either the Program or any +derivative work under copyright law: that is to say, a work containing the +Program or a portion of it, either verbatim or with modifications and/or translated +into another language. (Hereinafter, translation is included without limitation +in the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not covered +by this License; they are outside its scope. The act of running the Program +is not restricted, and the output from the Program is covered only if its +contents constitute a work based on the Program (independent of having been +made by running the Program). Whether that is true depends on what the Program +does. + +1. You may copy and distribute verbatim copies of the Program's source code +as you receive it, in any medium, provided that you conspicuously and appropriately +publish on each copy an appropriate copyright notice and disclaimer of warranty; +keep intact all the notices that refer to this License and to the absence +of any warranty; and give any other recipients of the Program a copy of this +License along with the Program. + +You may charge a fee for the physical act of transferring a copy, and you +may at your option offer warranty protection in exchange for a fee. + +2. You may modify your copy or copies of the Program or any portion of it, +thus forming a work based on the Program, and copy and distribute such modifications +or work under the terms of Section 1 above, provided that you also meet all +of these conditions: + +a) You must cause the modified files to carry prominent notices stating that +you changed the files and the date of any change. + +b) You must cause any work that you distribute or publish, that in whole or +in part contains or is derived from the Program or any part thereof, to be +licensed as a whole at no charge to all third parties under the terms of this +License. + +c) If the modified program normally reads commands interactively when run, +you must cause it, when started running for such interactive use in the most +ordinary way, to print or display an announcement including an appropriate +copyright notice and a notice that there is no warranty (or else, saying that +you provide a warranty) and that users may redistribute the program under +these conditions, and telling the user how to view a copy of this License. +(Exception: if the Program itself is interactive but does not normally print +such an announcement, your work based on the Program is not required to print +an announcement.) + +These requirements apply to the modified work as a whole. If identifiable +sections of that work are not derived from the Program, and can be reasonably +considered independent and separate works in themselves, then this License, +and its terms, do not apply to those sections when you distribute them as +separate works. But when you distribute the same sections as part of a whole +which is a work based on the Program, the distribution of the whole must be +on the terms of this License, whose permissions for other licensees extend +to the entire whole, and thus to each and every part regardless of who wrote +it. + +Thus, it is not the intent of this section to claim rights or contest your +rights to work written entirely by you; rather, the intent is to exercise +the right to control the distribution of derivative or collective works based +on the Program. + +In addition, mere aggregation of another work not based on the Program with +the Program (or with a work based on the Program) on a volume of a storage +or distribution medium does not bring the other work under the scope of this +License. + +3. You may copy and distribute the Program (or a work based on it, under Section +2) in object code or executable form under the terms of Sections 1 and 2 above +provided that you also do one of the following: + +a) Accompany it with the complete corresponding machine-readable source code, +which must be distributed under the terms of Sections 1 and 2 above on a medium +customarily used for software interchange; or, + +b) Accompany it with a written offer, valid for at least three years, to give +any third party, for a charge no more than your cost of physically performing +source distribution, a complete machine-readable copy of the corresponding +source code, to be distributed under the terms of Sections 1 and 2 above on +a medium customarily used for software interchange; or, + +c) Accompany it with the information you received as to the offer to distribute +corresponding source code. (This alternative is allowed only for noncommercial +distribution and only if you received the program in object code or executable +form with such an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for making +modifications to it. For an executable work, complete source code means all +the source code for all modules it contains, plus any associated interface +definition files, plus the scripts used to control compilation and installation +of the executable. However, as a special exception, the source code distributed +need not include anything that is normally distributed (in either source or +binary form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component itself +accompanies the executable. + +If distribution of executable or object code is made by offering access to +copy from a designated place, then offering equivalent access to copy the +source code from the same place counts as distribution of the source code, +even though third parties are not compelled to copy the source along with +the object code. + +4. You may not copy, modify, sublicense, or distribute the Program except +as expressly provided under this License. Any attempt otherwise to copy, modify, +sublicense or distribute the Program is void, and will automatically terminate +your rights under this License. However, parties who have received copies, +or rights, from you under this License will not have their licenses terminated +so long as such parties remain in full compliance. + +5. You are not required to accept this License, since you have not signed +it. However, nothing else grants you permission to modify or distribute the +Program or its derivative works. These actions are prohibited by law if you +do not accept this License. Therefore, by modifying or distributing the Program +(or any work based on the Program), you indicate your acceptance of this License +to do so, and all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + +6. Each time you redistribute the Program (or any work based on the Program), +the recipient automatically receives a license from the original licensor +to copy, distribute or modify the Program subject to these terms and conditions. +You may not impose any further restrictions on the recipients' exercise of +the rights granted herein. You are not responsible for enforcing compliance +by third parties to this License. + +7. If, as a consequence of a court judgment or allegation of patent infringement +or for any other reason (not limited to patent issues), conditions are imposed +on you (whether by court order, agreement or otherwise) that contradict the +conditions of this License, they do not excuse you from the conditions of +this License. If you cannot distribute so as to satisfy simultaneously your +obligations under this License and any other pertinent obligations, then as +a consequence you may not distribute the Program at all. For example, if a +patent license would not permit royalty-free redistribution of the Program +by all those who receive copies directly or indirectly through you, then the +only way you could satisfy both it and this License would be to refrain entirely +from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under any +particular circumstance, the balance of the section is intended to apply and +the section as a whole is intended to apply in other circumstances. + +It is not the purpose of this section to induce you to infringe any patents +or other property right claims or to contest validity of any such claims; +this section has the sole purpose of protecting the integrity of the free +software distribution system, which is implemented by public license practices. +Many people have made generous contributions to the wide range of software +distributed through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing to +distribute software through any other system and a licensee cannot impose +that choice. + +This section is intended to make thoroughly clear what is believed to be a +consequence of the rest of this License. + +8. If the distribution and/or use of the Program is restricted in certain +countries either by patents or by copyrighted interfaces, the original copyright +holder who places the Program under this License may add an explicit geographical +distribution limitation excluding those countries, so that distribution is +permitted only in or among countries not thus excluded. In such case, this +License incorporates the limitation as if written in the body of this License. + +9. The Free Software Foundation may publish revised and/or new versions of +the General Public License from time to time. Such new versions will be similar +in spirit to the present version, but may differ in detail to address new +problems or concerns. + +Each version is given a distinguishing version number. If the Program specifies +a version number of this License which applies to it and "any later version", +you have the option of following the terms and conditions either of that version +or of any later version published by the Free Software Foundation. If the +Program does not specify a version number of this License, you may choose +any version ever published by the Free Software Foundation. + +10. If you wish to incorporate parts of the Program into other free programs +whose distribution conditions are different, write to the author to ask for +permission. For software which is copyrighted by the Free Software Foundation, +write to the Free Software Foundation; we sometimes make exceptions for this. +Our decision will be guided by the two goals of preserving the free status +of all derivatives of our free software and of promoting the sharing and reuse +of software generally. + + NO WARRANTY + +11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR +THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE +STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM +"AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, +BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS +FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE +OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME +THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + +12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE +OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA +OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES +OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH +HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. +END OF TERMS AND CONDITIONS + +How to Apply These Terms to Your New Programs + +If you develop a new program, and you want it to be of the greatest possible +use to the public, the best way to achieve this is to make it free software +which everyone can redistribute and change under these terms. + +To do so, attach the following notices to the program. It is safest to attach +them to the start of each source file to most effectively convey the exclusion +of warranty; and each file should have at least the "copyright" line and a +pointer to where the full notice is found. + + + +Copyright (C) + +This program is free software; you can redistribute it and/or modify it under +the terms of the GNU General Public License as published by the Free Software +Foundation; either version 2 of the License, or (at your option) any later +version. + +This program is distributed in the hope that it will be useful, but WITHOUT +ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License along with +this program; if not, write to the Free Software Foundation, Inc., 51 Franklin +Street, Fifth Floor, Boston, MA 02110-1301, USA. + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this when +it starts in an interactive mode: + +Gnomovision version 69, Copyright (C) year name of author Gnomovision comes +with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, +and you are welcome to redistribute it under certain conditions; type `show +c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may be +called something other than `show w' and `show c'; they could even be mouse-clicks +or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your school, +if any, to sign a "copyright disclaimer" for the program, if necessary. Here +is a sample; alter the names: + +Yoyodyne, Inc., hereby disclaims all copyright interest in the program `Gnomovision' +(which makes passes at compilers) written by James Hacker. + +, 1 April 1989 Ty Coon, President of Vice This General +Public License does not permit incorporating your program into proprietary +programs. If your program is a subroutine library, you may consider it more +useful to permit linking proprietary applications with the library. If this +is what you want to do, use the GNU Lesser General Public License instead +of this License. diff --git a/LICENSES/LGPL-2.0-or-later.txt b/LICENSES/LGPL-2.0-or-later.txt new file mode 100644 index 00000000..5c96471a --- /dev/null +++ b/LICENSES/LGPL-2.0-or-later.txt @@ -0,0 +1,446 @@ +GNU LIBRARY GENERAL PUBLIC LICENSE + +Version 2, June 1991 Copyright (C) 1991 Free Software Foundation, Inc. + +51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA + +Everyone is permitted to copy and distribute verbatim copies of this license +document, but changing it is not allowed. + +[This is the first released version of the library GPL. It is numbered 2 because +it goes with version 2 of the ordinary GPL.] + +Preamble + +The licenses for most software are designed to take away your freedom to share +and change it. By contrast, the GNU General Public Licenses are intended to +guarantee your freedom to share and change free software--to make sure the +software is free for all its users. + +This license, the Library General Public License, applies to some specially +designated Free Software Foundation software, and to any other libraries whose +authors decide to use it. You can use it for your libraries, 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 library, or if you modify it. + +For example, if you distribute copies of the library, whether gratis or for +a fee, you must give the recipients all the rights that we gave you. You must +make sure that they, too, receive or can get the source code. If you link +a program with the library, you must provide complete object files to the +recipients so that they can relink them with the library, after making changes +to the library and recompiling it. And you must show them these terms so they +know their rights. + +Our method of protecting your rights has two steps: (1) copyright the library, +and (2) offer you this license which gives you legal permission to copy, distribute +and/or modify the library. + +Also, for each distributor's protection, we want to make certain that everyone +understands that there is no warranty for this free library. If the library +is modified by someone else and passed on, we want its recipients to know +that what they have is not the original version, 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 companies distributing free software will individually +obtain patent licenses, thus in effect transforming the program into proprietary +software. To prevent this, we have made it clear that any patent must be licensed +for everyone's free use or not licensed at all. + +Most GNU software, including some libraries, is covered by the ordinary GNU +General Public License, which was designed for utility programs. This license, +the GNU Library General Public License, applies to certain designated libraries. +This license is quite different from the ordinary one; be sure to read it +in full, and don't assume that anything in it is the same as in the ordinary +license. + +The reason we have a separate public license for some libraries is that they +blur the distinction we usually make between modifying or adding to a program +and simply using it. Linking a program with a library, without changing the +library, is in some sense simply using the library, and is analogous to running +a utility program or application program. However, in a textual and legal +sense, the linked executable is a combined work, a derivative of the original +library, and the ordinary General Public License treats it as such. + +Because of this blurred distinction, using the ordinary General Public License +for libraries did not effectively promote software sharing, because most developers +did not use the libraries. We concluded that weaker conditions might promote +sharing better. + +However, unrestricted linking of non-free programs would deprive the users +of those programs of all benefit from the free status of the libraries themselves. +This Library General Public License is intended to permit developers of non-free +programs to use free libraries, while preserving your freedom as a user of +such programs to change the free libraries that are incorporated in them. +(We have not seen how to achieve this as regards changes in header files, +but we have achieved it as regards changes in the actual functions of the +Library.) The hope is that this will lead to faster development of free libraries. + +The precise terms and conditions for copying, distribution and modification +follow. Pay close attention to the difference between a "work based on the +library" and a "work that uses the library". The former contains code derived +from the library, while the latter only works together with the library. + +Note that it is possible for a library to be covered by the ordinary General +Public License rather than by this special one. + +TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + +0. This License Agreement applies to any software library which contains a +notice placed by the copyright holder or other authorized party saying it +may be distributed under the terms of this Library General Public License +(also called "this License"). Each licensee is addressed as "you". + +A "library" means a collection of software functions and/or data prepared +so as to be conveniently linked with application programs (which use some +of those functions and data) to form executables. + +The "Library", below, refers to any such software library or work which has +been distributed under these terms. A "work based on the Library" means either +the Library or any derivative work under copyright law: that is to say, a +work containing the Library or a portion of it, either verbatim or with modifications +and/or translated straightforwardly into another language. (Hereinafter, translation +is included without limitation in the term "modification".) + +"Source code" for a work means the preferred form of the work for making modifications +to it. For a library, 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 library. + +Activities other than copying, distribution and modification are not covered +by this License; they are outside its scope. The act of running a program +using the Library is not restricted, and output from such a program is covered +only if its contents constitute a work based on the Library (independent of +the use of the Library in a tool for writing it). Whether that is true depends +on what the Library does and what the program that uses the Library does. + +1. You may copy and distribute verbatim copies of the Library's complete 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 distribute a copy of this License along with +the Library. + +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 Library or any portion of it, +thus forming a work based on the Library, 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) The modified work must itself be a software library. + +b) You must cause the files modified to carry prominent notices stating that +you changed the files and the date of any change. + +c) You must cause the whole of the work to be licensed at no charge to all +third parties under the terms of this License. + +d) If a facility in the modified Library refers to a function or a table of +data to be supplied by an application program that uses the facility, other +than as an argument passed when the facility is invoked, then you must make +a good faith effort to ensure that, in the event an application does not supply +such function or table, the facility still operates, and performs whatever +part of its purpose remains meaningful. + +(For example, a function in a library to compute square roots has a purpose +that is entirely well-defined independent of the application. Therefore, Subsection +2d requires that any application-supplied function or table used by this function +must be optional: if the application does not supply it, the square root function +must still compute square roots.) + +These requirements apply to the modified work as a whole. If identifiable +sections of that work are not derived from the Library, 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 Library, 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 Library. + +In addition, mere aggregation of another work not based on the Library with +the Library (or with a work based on the Library) on a volume of a storage +or distribution medium does not bring the other work under the scope of this +License. + +3. You may opt to apply the terms of the ordinary GNU General Public License +instead of this License to a given copy of the Library. To do this, you must +alter all the notices that refer to this License, so that they refer to the +ordinary GNU General Public License, version 2, instead of to this License. +(If a newer version than version 2 of the ordinary GNU General Public License +has appeared, then you can specify that version instead if you wish.) Do not +make any other change in these notices. + +Once this change is made in a given copy, it is irreversible for that copy, +so the ordinary GNU General Public License applies to all subsequent copies +and derivative works made from that copy. + +This option is useful when you wish to copy part of the code of the Library +into a program that is not a library. + +4. You may copy and distribute the Library (or a portion or derivative of +it, under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you 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. + +If distribution of 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 satisfies the requirement to distribute the source code, even though +third parties are not compelled to copy the source along with the object code. + +5. A program that contains no derivative of any portion of the Library, but +is designed to work with the Library by being compiled or linked with it, +is called a "work that uses the Library". Such a work, in isolation, is not +a derivative work of the Library, and therefore falls outside the scope of +this License. + +However, linking a "work that uses the Library" with the Library creates an +executable that is a derivative of the Library (because it contains portions +of the Library), rather than a "work that uses the library". The executable +is therefore covered by this License. Section 6 states terms for distribution +of such executables. + +When a "work that uses the Library" uses material from a header file that +is part of the Library, the object code for the work may be a derivative work +of the Library even though the source code is not. Whether this is true is +especially significant if the work can be linked without the Library, or if +the work is itself a library. The threshold for this to be true is not precisely +defined by law. + +If such an object file uses only numerical parameters, data structure layouts +and accessors, and small macros and small inline functions (ten lines or less +in length), then the use of the object file is unrestricted, regardless of +whether it is legally a derivative work. (Executables containing this object +code plus portions of the Library will still fall under Section 6.) + +Otherwise, if the work is a derivative of the Library, you may distribute +the object code for the work under the terms of Section 6. Any executables +containing that work also fall under Section 6, whether or not they are linked +directly with the Library itself. + +6. As an exception to the Sections above, you may also compile or link a "work +that uses the Library" with the Library to produce a work containing portions +of the Library, and distribute that work under terms of your choice, provided +that the terms permit modification of the work for the customer's own use +and reverse engineering for debugging such modifications. + +You must give prominent notice with each copy of the work that the Library +is used in it and that the Library and its use are covered by this License. +You must supply a copy of this License. If the work during execution displays +copyright notices, you must include the copyright notice for the Library among +them, as well as a reference directing the user to the copy of this License. +Also, you must do one of these things: + +a) Accompany the work with the complete corresponding machine-readable source +code for the Library including whatever changes were used in the work (which +must be distributed under Sections 1 and 2 above); and, if the work is an +executable linked with the Library, with the complete machine-readable "work +that uses the Library", as object code and/or source code, so that the user +can modify the Library and then relink to produce a modified executable containing +the modified Library. (It is understood that the user who changes the contents +of definitions files in the Library will not necessarily be able to recompile +the application to use the modified definitions.) + +b) Accompany the work with a written offer, valid for at least three years, +to give the same user the materials specified in Subsection 6a, above, for +a charge no more than the cost of performing this distribution. + +c) If distribution of the work is made by offering access to copy from a designated +place, offer equivalent access to copy the above specified materials from +the same place. + +d) Verify that the user has already received a copy of these materials or +that you have already sent this user a copy. + +For an executable, the required form of the "work that uses the Library" must +include any data and utility programs needed for reproducing the executable +from it. 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. + +It may happen that this requirement contradicts the license restrictions of +other proprietary libraries that do not normally accompany the operating system. +Such a contradiction means you cannot use both them and the Library together +in an executable that you distribute. + +7. You may place library facilities that are a work based on the Library side-by-side +in a single library together with other library facilities not covered by +this License, and distribute such a combined library, provided that the separate +distribution of the work based on the Library and of the other library facilities +is otherwise permitted, and provided that you do these two things: + +a) Accompany the combined library with a copy of the same work based on the +Library, uncombined with any other library facilities. This must be distributed +under the terms of the Sections above. + +b) Give prominent notice with the combined library of the fact that part of +it is a work based on the Library, and explaining where to find the accompanying +uncombined form of the same work. + +8. You may not copy, modify, sublicense, link with, or distribute the Library +except as expressly provided under this License. Any attempt otherwise to +copy, modify, sublicense, link with, or distribute the Library 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. + +9. 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 +Library or its derivative works. These actions are prohibited by law if you +do not accept this License. Therefore, by modifying or distributing the Library +(or any work based on the Library), you indicate your acceptance of this License +to do so, and all its terms and conditions for copying, distributing or modifying +the Library or works based on it. + +10. Each time you redistribute the Library (or any work based on the Library), +the recipient automatically receives a license from the original licensor +to copy, distribute, link with or modify the Library 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. + +11. 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 Library at all. For example, if a +patent license would not permit royalty-free redistribution of the Library +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 Library. + +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. + +12. If the distribution and/or use of the Library is restricted in certain +countries either by patents or by copyrighted interfaces, the original copyright +holder who places the Library 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. + +13. The Free Software Foundation may publish revised and/or new versions of +the Library 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 Library 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 +Library does not specify a license version number, you may choose any version +ever published by the Free Software Foundation. + +14. If you wish to incorporate parts of the Library into other free programs +whose distribution conditions are incompatible with these, 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 + +15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR +THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE +STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE LIBRARY +"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 LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME +THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + +16. 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 LIBRARY 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 LIBRARY (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 LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), 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 Libraries + +If you develop a new library, and you want it to be of the greatest possible +use to the public, we recommend making it free software that everyone can +redistribute and change. You can do so by permitting redistribution under +these terms (or, alternatively, under the terms of the ordinary General Public +License). + +To apply these terms, attach the following notices to the library. It is safest +to attach them to the start of each source file to most effectively convey +the exclusion of warranty; and each file should have at least the "copyright" +line and a pointer to where the full notice is found. + +one line to give the library's name and an idea of what it does. + +Copyright (C) year name of author + +This library is free software; you can redistribute it and/or modify it under +the terms of the GNU Library General Public License as published by the Free +Software Foundation; either version 2 of the License, or (at your option) +any later version. + +This library 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 Library General Public License for more +details. + +You should have received a copy of the GNU Library General Public License +along with this library; if not, write to the Free Software Foundation, Inc., +51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. + +Also add information on how to contact you by electronic and paper mail. + +You should also get your employer (if you work as a programmer) or your school, +if any, to sign a "copyright disclaimer" for the library, if necessary. Here +is a sample; alter the names: + +Yoyodyne, Inc., hereby disclaims all copyright interest in + +the library `Frob' (a library for tweaking knobs) written + +by James Random Hacker. + +signature of Ty Coon, 1 April 1990 + +Ty Coon, President of Vice + +That's all there is to it! diff --git a/smb/autotests/smburltest.cpp b/smb/autotests/smburltest.cpp index c417cc9a..d2d5691a 100644 --- a/smb/autotests/smburltest.cpp +++ b/smb/autotests/smburltest.cpp @@ -1,131 +1,131 @@ /* - SPDX-FileCopyrightText: 2020 Harald Sitter SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL + SPDX-FileCopyrightText: 2020 Harald Sitter */ #include #include #include "smburl.h" class SMBUrlTest : public QObject { Q_OBJECT private Q_SLOTS: void testMinimalToSmbcValid() { // libsmbclient is a bit picky. make sure we convert to minimal applicable form { SMBUrl url(QUrl("smb:/")); QCOMPARE(url.toSmbcUrl(), "smb://"); } // But at the same time it will happily deal with smb: { SMBUrl url(QUrl("smb:")); QCOMPARE(url.toSmbcUrl(), "smb:"); } } void testType() { QCOMPARE(SMBUrl(QUrl("smb://")).getType(), SMBURLTYPE_ENTIRE_NETWORK); QCOMPARE(SMBUrl(QUrl("smb://host")).getType(), SMBURLTYPE_WORKGROUP_OR_SERVER); QCOMPARE(SMBUrl(QUrl("smb://host/share/file")).getType(), SMBURLTYPE_SHARE_OR_PATH); QCOMPARE(SMBUrl(QUrl()).getType(), SMBURLTYPE_UNKNOWN); } void testPart() { SMBUrl url(QUrl("smb://host/share/file")); QCOMPARE(url.partUrl().toString(), "smb://host/share/file.part"); } void testUp() { SMBUrl url(QUrl("smb://host/share/file")); url.cdUp(); QCOMPARE(url.toSmbcUrl(), "smb://host/share"); } void testAddPath() { SMBUrl url(QUrl("smb://host/share")); url.addPath("file"); QCOMPARE(url.toSmbcUrl(), "smb://host/share/file"); } void testCifs() { // We treat cifs as an alias but need to translate it to smb. // https://bugs.kde.org/show_bug.cgi?id=327295 SMBUrl url(QUrl("cifs://host/share/file")); QCOMPARE(url.toSmbcUrl(), "smb://host/share/file"); } void testIPv6Literal() { // https://bugs.kde.org/show_bug.cgi?id=417682 // Samba cannot deal with RFC5952 IPv6 notation (e.g. ::1%lo) // to work around we convert to windows ipv6 literals. // The actual represented URL should not change! // i.e. towards the KIO client we do not leak the IPv6 // literal when returning an URL. QCOMPARE(SMBUrl(QUrl("smb://[::1]/share")).toString(), "smb://[::1]/share"); // The internal smbc representation should be literal though: // :: prefix QCOMPARE(SMBUrl(QUrl("smb://[::1]/share")).toSmbcUrl(), "smb://0--1.ipv6-literal.net/share"); // :: suffix QCOMPARE(SMBUrl(QUrl("smb://[fe80::]/share")).toSmbcUrl(), "smb://fe80--0.ipv6-literal.net/share"); // %lo scope QCOMPARE(SMBUrl(QUrl("smb://[::1%lo]/share")).toSmbcUrl(), "smb://0--1slo.ipv6-literal.net/share"); // random valid addr QCOMPARE(SMBUrl(QUrl("smb://[fe80::9cd7:32c7:faeb:f23d]/share")).toSmbcUrl(), "smb://fe80--9cd7-32c7-faeb-f23d.ipv6-literal.net/share"); } void testWorkgroupWithSpaces() { // Workgroups can have spaces but QUrls cannot, so we have a hack // that puts the workgroup info into a query. // Only applicable to SMB1 pretty much, we do not do workgroup browsing // for 2+. // https://bugs.kde.org/show_bug.cgi?id=204423 // wg QCOMPARE(SMBUrl(QUrl("smb://?kio-workgroup=hax max")).toSmbcUrl(), "smb://hax max/"); // wg and query QCOMPARE(SMBUrl(QUrl("smb://?kio-workgroup=hax max&q=a")).toSmbcUrl(), "smb://hax max/?q=a"); // host and wg and query QCOMPARE(SMBUrl(QUrl("smb://host/?kio-workgroup=hax max&q=a")).toSmbcUrl(), "smb://hax max/host?q=a"); // host and wg and query QCOMPARE(SMBUrl(QUrl("smb://host/share?kio-workgroup=hax max")).toSmbcUrl(), "smb://hax max/host/share"); // Non-empty path. libsmbc hates unclean paths QCOMPARE(SMBUrl(QUrl("smb:///////?kio-workgroup=hax max")).toSmbcUrl(), "smb://hax max/"); // % character - run through .url() to simulate behavior of our listDir() QCOMPARE(SMBUrl(QUrl(QUrl("smb://?kio-workgroup=HAX%25MAX").url())).toSmbcUrl(), "smb://HAX%25MAX/"); // !ascii - run through .url() to simulate behavior of our listDir() QCOMPARE(SMBUrl(QUrl(QUrl("smb:///?kio-workgroup=DOMÄNE A").url())).toSmbcUrl(), "smb://DOMÄNE A/"); // works as-is with smbc. // Also make sure type detection knows about this QCOMPARE(SMBUrl(QUrl("smb:/?kio-workgroup=hax max")).getType(), SMBURLTYPE_WORKGROUP_OR_SERVER); } }; QTEST_GUILESS_MAIN(SMBUrlTest) #include "smburltest.moc" diff --git a/smb/autotests/transfertest.cpp b/smb/autotests/transfertest.cpp index 74c23090..bdd4707b 100644 --- a/smb/autotests/transfertest.cpp +++ b/smb/autotests/transfertest.cpp @@ -1,151 +1,151 @@ /* - SPDX-FileCopyrightText: 2020 Harald Sitter SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL + SPDX-FileCopyrightText: 2020 Harald Sitter */ #include #include #include "transfer.h" class TransferTest : public QObject { Q_OBJECT private Q_SLOTS: void testSegmentOnSmallFile() { // Files smaller than our minimal segment size ought to be transferred in one go // otherwise we have a chance of degrading performance. QCOMPARE(TransferSegment(1).buf.size(), 1); } void testMaxSegment() { // Large files may only use up to a given maximum. QCOMPARE(TransferSegment(512 * 1024 * 1024).buf.size(), c_maxSegmentSize); } void testIdealSegmentSize() { QCOMPARE(TransferSegment(64 * 1024 * 1024).buf.size(), 1342177); } void testSegment() { TransferSegment s(8); QCOMPARE(s.buf.size(), 8); memset(s.buf.data(), 1, 8); QCOMPARE(s.buf.data()[0], 1); } void testRing() { TransferRingBuffer ring(8); for (auto i = 0; i <= 32; ++i) { { auto s = ring.nextFree(); memset(s->buf.data(), i, 8); ring.push(); } { auto s = ring.pop(); QCOMPARE(s->buf.data()[0], static_cast(i)); ring.unpop(); } } } void testRingThreadedSlowPush() { const auto runs = 127; const auto fileSize = 8; TransferRingBuffer ring(fileSize); std::atomic abort(false); auto pullFuture = std::async(std::launch::async, [&ring, &abort]() -> bool { for (auto i = 0; i <= runs && !abort; ++i) { auto s = ring.pop(); if (!QTest::qCompare(s->buf.data()[0], static_cast(i), qPrintable(QStringLiteral("On pull iteration %1").arg(i)), "", __FILE__, __LINE__)) { abort = true; return false; } ring.unpop(); } return true; }); auto pushFuture = std::async(std::launch::async, [&ring, &abort]() -> bool { for (auto i = 0; i <= runs && !abort; ++i) { auto s = ring.nextFree(); memset(s->buf.data(), i, fileSize); ring.push(); if (abort) { ring.done(); return false; } // Slow down this thread to simulate slow network reads. std::this_thread::sleep_for(std::chrono::milliseconds(5)); } ring.done(); return true; }); pushFuture.wait(); pullFuture.wait(); QVERIFY(pushFuture.get()); QVERIFY(pullFuture.get()); } void testRingThreadedSlowPull() { const auto runs = 127; const auto fileSize = 8; TransferRingBuffer ring(fileSize); std::atomic abort(false); auto pullFuture = std::async(std::launch::async, [&ring, &abort]() -> bool { for (auto i = 0; i <= runs && !abort; ++i) { auto s = ring.pop(); if (!QTest::qCompare(s->buf.data()[0], static_cast(i), qPrintable(QStringLiteral("On pull iteration %1").arg(i)), "", __FILE__, __LINE__)) { abort = true; } // Slow down this thread to simulate slow local writes. std::this_thread::sleep_for(std::chrono::milliseconds(5)); ring.unpop(); } return true; }); auto pushFuture = std::async(std::launch::async, [&ring, &abort]() -> bool { for (auto i = 0; i <= runs && !abort; ++i) { auto s = ring.nextFree(); memset(s->buf.data(), i, fileSize); if (abort) { ring.done(); return false; } ring.push(); } ring.done(); return true; }); pushFuture.wait(); pullFuture.wait(); QVERIFY(pushFuture.get()); QVERIFY(pullFuture.get()); } }; QTEST_GUILESS_MAIN(TransferTest) #include "transfertest.moc" diff --git a/smb/discovery.cpp b/smb/discovery.cpp index 918662e3..fa1b8c82 100644 --- a/smb/discovery.cpp +++ b/smb/discovery.cpp @@ -1,31 +1,16 @@ /* - Copyright 2019 Harald Sitter - - This program is free software; you can redistribute it and/or - modify it under the terms of the GNU General Public License as - published by the Free Software Foundation; either version 3 of - the License or any later version accepted by the membership of - KDE e.V. (or its successor approved by the membership of KDE - e.V.), which shall act as a proxy defined in Section 14 of - version 3 of the license. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . + SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL + SPDX-FileCopyrightText: 2019 Harald Sitter */ #include "discovery.h" Discovery::Discovery() { qRegisterMetaType("Discovery::Ptr"); } Discovery::~Discovery() = default; Discoverer::Discoverer() = default; Discoverer::~Discoverer() = default; diff --git a/smb/discovery.h b/smb/discovery.h index 1d9e921d..b78c1b91 100644 --- a/smb/discovery.h +++ b/smb/discovery.h @@ -1,53 +1,38 @@ /* - Copyright 2019 Harald Sitter - - This program is free software; you can redistribute it and/or - modify it under the terms of the GNU General Public License as - published by the Free Software Foundation; either version 3 of - the License or any later version accepted by the membership of - KDE e.V. (or its successor approved by the membership of KDE - e.V.), which shall act as a proxy defined in Section 14 of - version 3 of the license. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . + SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL + SPDX-FileCopyrightText: 2019 Harald Sitter */ #ifndef DISCOVERY_H #define DISCOVERY_H #include #include class Discovery { public: typedef QSharedPointer Ptr; Discovery(); virtual ~Discovery(); virtual QString udsName() const = 0; virtual KIO::UDSEntry toEntry() const = 0; }; class Discoverer { public: Discoverer(); virtual ~Discoverer(); virtual void start() = 0; virtual void stop() = 0; virtual bool isFinished() const = 0; // Implement as signal! virtual void newDiscovery(Discovery::Ptr discovery) = 0; virtual void finished() = 0; }; #endif // DISCOVERY_H diff --git a/smb/dnssddiscoverer.cpp b/smb/dnssddiscoverer.cpp index 48eba27e..c2ca0e6a 100644 --- a/smb/dnssddiscoverer.cpp +++ b/smb/dnssddiscoverer.cpp @@ -1,124 +1,109 @@ /* - Copyright 2019 Harald Sitter - - This program is free software; you can redistribute it and/or - modify it under the terms of the GNU General Public License as - published by the Free Software Foundation; either version 3 of - the License or any later version accepted by the membership of - KDE e.V. (or its successor approved by the membership of KDE - e.V.), which shall act as a proxy defined in Section 14 of - version 3 of the license. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . + SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL + SPDX-FileCopyrightText: 2019 Harald Sitter */ #include "dnssddiscoverer.h" #include "kio_smb.h" DNSSDDiscovery::DNSSDDiscovery(KDNSSD::RemoteService::Ptr service) : m_service(service) { } QString DNSSDDiscovery::udsName() const { return m_service->serviceName(); } KIO::UDSEntry DNSSDDiscovery::toEntry() const { KIO::UDSEntry entry; entry.reserve(6); entry.fastInsert(KIO::UDSEntry::UDS_NAME, udsName()); entry.fastInsert(KIO::UDSEntry::UDS_FILE_TYPE, S_IFDIR); entry.fastInsert(KIO::UDSEntry::UDS_ACCESS, (S_IRUSR | S_IXUSR | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH)); entry.fastInsert(KIO::UDSEntry::UDS_ICON_NAME, "network-server"); // TODO: it may be better to resolve the host to an ip address. dnssd // being able to find a service doesn't mean name resolution is // properly set up for its domain. So, we may not be able to resolve // this without help from avahi. OTOH KDNSSD doesn't have API for this // and from a platform POV we should probably assume that if avahi // is functional it is also set up as resolution provider. // Given the plugin design on glibc's libnss however I am not sure // that assumption will be true all the time. ~sitter, 2018 QUrl u; u.setScheme(QStringLiteral("smb")); u.setHost(m_service->hostName()); const int defaultPort = 445; if (m_service->port() > 0 && m_service->port() != defaultPort) { u.setPort(m_service->port()); } u.setPath("/"); // https://bugs.kde.org/show_bug.cgi?id=388922 entry.fastInsert(KIO::UDSEntry::UDS_URL, u.url()); entry.fastInsert(KIO::UDSEntry::UDS_MIME_TYPE, QStringLiteral("application/x-smb-server")); return entry; } DNSSDDiscoverer::DNSSDDiscoverer() { connect(&m_browser, &KDNSSD::ServiceBrowser::serviceAdded, this, [=](KDNSSD::RemoteService::Ptr service){ qCDebug(KIO_SMB_LOG) << "DNSSD added:" << service->serviceName() << service->type() << service->domain() << service->hostName() << service->port(); // Manual contains check. We need to use the == of the underlying // objects, not the pointers. The same service may have >1 // RemoteService* instances representing it, so the == impl of // RemoteService::Ptr is useless here. for (const auto &servicePtr : qAsConst(m_services)) { if (*service == *servicePtr) { return; } } connect(service.data(), &KDNSSD::RemoteService::resolved, this, [=] { ++m_resolvedCount; emit newDiscovery(Discovery::Ptr(new DNSSDDiscovery(service))); maybeFinish(); }); // Schedule resolution of hostname. We'll later call resolve // which will block until the resolution is done. This basically // gives us a head start on discovery. service->resolveAsync(); m_services.append(service); }); connect(&m_browser, &KDNSSD::ServiceBrowser::finished, this, &DNSSDDiscoverer::stop); } void DNSSDDiscoverer::start() { m_browser.startBrowse(); } void DNSSDDiscoverer::stop() { m_browser.disconnect(); m_disconnected = true; maybeFinish(); } bool DNSSDDiscoverer::isFinished() const { return m_disconnected && m_services.count() == m_resolvedCount; } void DNSSDDiscoverer::maybeFinish() { if (isFinished()) { emit finished(); } } diff --git a/smb/dnssddiscoverer.h b/smb/dnssddiscoverer.h index bf652a3c..951d18c9 100644 --- a/smb/dnssddiscoverer.h +++ b/smb/dnssddiscoverer.h @@ -1,65 +1,50 @@ /* - Copyright 2019 Harald Sitter - - This program is free software; you can redistribute it and/or - modify it under the terms of the GNU General Public License as - published by the Free Software Foundation; either version 3 of - the License or any later version accepted by the membership of - KDE e.V. (or its successor approved by the membership of KDE - e.V.), which shall act as a proxy defined in Section 14 of - version 3 of the license. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . + SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL + SPDX-FileCopyrightText: 2019 Harald Sitter */ #ifndef DNSSDDISCOVERER_H #define DNSSDDISCOVERER_H #include #include #include #include "discovery.h" class DNSSDDiscovery : public Discovery { public: DNSSDDiscovery(KDNSSD::RemoteService::Ptr service); QString udsName() const override; KIO::UDSEntry toEntry() const override; private: KDNSSD::RemoteService::Ptr m_service; }; class DNSSDDiscoverer : public QObject, public Discoverer { Q_OBJECT public: DNSSDDiscoverer(); void start() override; bool isFinished() const override; signals: void newDiscovery(Discovery::Ptr discovery) override; void finished() override; private: void stop() override; void maybeFinish(); KDNSSD::ServiceBrowser m_browser {QStringLiteral("_smb._tcp")}; QList m_services; int m_resolvedCount = 0; bool m_disconnected = false; }; #endif // DNSSDDISCOVERER_H diff --git a/smb/kio_smb.cpp b/smb/kio_smb.cpp index d097a4c5..59e35d11 100644 --- a/smb/kio_smb.cpp +++ b/smb/kio_smb.cpp @@ -1,101 +1,77 @@ -///////////////////////////////////////////////////////////////////////////// -// -// Project: SMB kioslave for KDE -// -// File: Top level implementation file for kio_smb.cpp -// -// Abstract: member function implementations for SMBSlave -// -// Author(s): Matthew Peterson -// -//--------------------------------------------------------------------------- -// -// Copyright (c) 2000 Caldera Systems, Inc. -// -// 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.1 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 Lesser General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program; see the file COPYING. If not, please obtain -// a copy from https://www.gnu.org/copyleft/gpl.html -// -///////////////////////////////////////////////////////////////////////////// +/* + SPDX-License-Identifier: GPL-2.0-or-later + SPDX-FileCopyrightText: 2000 Caldera Systems, Inc. + SPDX-FileContributor: Matthew Peterson +*/ #include "kio_smb.h" #include "smburl.h" #include #include // Pseudo plugin class to embed meta data class KIOPluginForMetaData : public QObject { Q_OBJECT Q_PLUGIN_METADATA(IID "org.kde.kio.slave.smb" FILE "smb.json") }; bool needsEEXISTWorkaround() { /* There is an issue with some libsmbclient versions that return EEXIST * return code from smbc_opendir() instead of EPERM when the user * tries to access a resource that requires login authetification. * We are working around the issue by treating EEXIST as a special case * of "invalid/unavailable credentials" if we detect that we are using * the affected versions of libsmbclient * * Upstream bug report: https://bugzilla.samba.org/show_bug.cgi?id=13050 */ static const QVersionNumber firstBrokenVer {4, 7, 0}; static const QVersionNumber lastBrokenVer {4, 7, 6}; const QVersionNumber currentVer = QVersionNumber::fromString(smbc_version()); qCDebug(KIO_SMB_LOG) << "Using libsmbclient library version" << currentVer; if (currentVer >= firstBrokenVer && currentVer <= lastBrokenVer) { qCDebug(KIO_SMB_LOG) << "Detected broken libsmbclient version" << currentVer; return true; } return false; } SMBSlave::SMBSlave(const QByteArray &pool, const QByteArray &app) : SlaveBase("smb", pool, app) , m_openFd(-1) , m_enableEEXISTWorkaround(needsEEXISTWorkaround()) { m_initialized_smbc = false; // read in the default workgroup info... reparseConfiguration(); // initialize the library... auth_initialize_smbc(); } SMBSlave::~SMBSlave() = default; void SMBSlave::virtual_hook(int id, void *data) { switch (id) { case SlaveBase::GetFileSystemFreeSpace: { QUrl *url = static_cast(data); fileSystemFreeSpace(*url); } break; case SlaveBase::Truncate: { auto length = static_cast(data); truncate(*length); } break; default: { SlaveBase::virtual_hook(id, data); } break; } } #include "kio_smb.moc" diff --git a/smb/kio_smb.h b/smb/kio_smb.h index a610c3ee..b621044f 100644 --- a/smb/kio_smb.h +++ b/smb/kio_smb.h @@ -1,297 +1,269 @@ -///////////////////////////////////////////////////////////////////////////// -// -// Project: SMB kioslave for KDE -// -// File: kio_smb.h -// -// Abstract: The main kio slave class declaration. For convenience, -// in concurrent development, the implementation for this class -// is separated into several .cpp files -- the file containing -// the implementation should be noted in the comments for each -// member function. -// -// Author(s): Matthew Peterson -// -//--------------------------------------------------------------------------- -// -// Copyright (c) 2000 Caldera Systems, Inc. -// -// 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.1 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 Lesser General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program; see the file COPYING. If not, please obtain -// a copy from https://www.gnu.org/copyleft/gpl.html -// -///////////////////////////////////////////////////////////////////////////// +/* + SPDX-License-Identifier: GPL-2.0-or-later + SPDX-FileCopyrightText: 2000 Caldera Systems, Inc. + SPDX-FileContributor: Matthew Peterson +*/ #ifndef KIO_SMB_H_INCLUDED #define KIO_SMB_H_INCLUDED #include #include "smb-logsettings.h" //-------------- // KDE includes //-------------- #include #include //----------------------------- // Standard C library includes //----------------------------- #include #include #include #include #include #include #include #include #include #include //----------------------------- // Qt includes //----------------------------- #include #include #include //------------------------------- // Samba client library includes //------------------------------- extern "C" { #include } //--------------------------- // kio_smb internal includes //--------------------------- #include "smburl.h" using namespace KIO; class SMBSlave : public QObject, public KIO::SlaveBase { Q_OBJECT friend class SMBCDiscoverer; private: class SMBError { public: int kioErrorId; QString errorString; }; //--------------------------------------------------------------------- // please make sure your private data does not duplicate existing data //--------------------------------------------------------------------- bool m_initialized_smbc; /** * From Controlcenter */ QString m_default_user; QString m_default_workgroup = QStringLiteral("WORKGROUP"); // overwritten with value from smbc QString m_default_password; QString m_default_encoding; /** * we store the current url, it's needed for * callback authorization method */ SMBUrl m_current_url; /** * From Controlcenter, show SHARE$ or not */ // bool m_showHiddenShares; //currently unused, Alex /** * libsmbclient need global variables to store in, * else it crashes on exit next method after use cache_stat, * looks like gcc (C/C++) failure */ struct stat st { }; protected: //--------------------------------------------- // Authentication functions (kio_smb_auth.cpp) //--------------------------------------------- // (please prefix functions with auth) /** * Description : Initializes the libsmbclient * Return : true on success false with errno set on error */ bool auth_initialize_smbc(); int checkPassword(SMBUrl &url); //--------------------------------------------- // Cache functions (kio_smb_auth.cpp) //--------------------------------------------- // Stat methods //----------------------------------------- // Browsing functions (kio_smb_browse.cpp) //----------------------------------------- // (please prefix functions with browse) /** * Description : Return a stat of given SMBUrl. Calls cache_stat and * pack it in UDSEntry. UDSEntry will not be cleared * Parameter : SMBUrl the url to stat * Return : cache_stat() return code */ int browse_stat_path(const SMBUrl &url, UDSEntry &udsentry); /** * Description : call smbc_stat and return stats of the url * Parameter : SMBUrl the url to stat * Return : stat* of the url * Note : it has some problems with stat in method, looks like * something leave(or removed) on the stack. If your * method segfault on returning try to change the stat* * variable */ int cache_stat(const SMBUrl &url, struct stat *st); //--------------------------------------------- // Configuration functions (kio_smb_config.cpp) //--------------------------------------------- // (please prefix functions with config) //--------------------------------------- // Directory functions (kio_smb_dir.cpp) //--------------------------------------- // (please prefix functions with dir) //-------------------------------------- // File IO functions (kio_smb_file.cpp) //-------------------------------------- // (please prefix functions with file) //---------------------------- // Misc functions (this file) //---------------------------- /** * Description : correct a given URL * valid URL's are * * smb://[[domain;]user[:password]@]server[:port][/share[/path[/file]]] * smb:/[[domain;]user[:password]@][group/[server[/share[/path[/file]]]]] * domain = workgroup(domain) of the user * user = username * password = password of useraccount * group = workgroup(domain) of server * server = host to connect * share = a share of the server (host) * path = a path of the share * Parameter : QUrl the url to check * Return : new QUrl if it is corrected. else the same QUrl */ QUrl checkURL(const QUrl &kurl) const; void reportError(const SMBUrl &url, const int errNum); void reportWarning(const SMBUrl &url, const int errNum); public: //----------------------------------------------------------------------- // smbclient authentication callback (note that this is called by the // global ::auth_smbc_get_data() call. void auth_smbc_get_data(const char *server, const char *share, char *workgroup, int wgmaxlen, char *username, int unmaxlen, char *password, int pwmaxlen); //----------------------------------------------------------------------- // Overwritten functions from the base class that define the operation of // this slave. (See the base class headerfile slavebase.h for more // details) //----------------------------------------------------------------------- // Functions overwritten in kio_smb.cpp SMBSlave(const QByteArray &pool, const QByteArray &app); ~SMBSlave() override; // Functions overwritten in kio_smb_browse.cpp void listDir(const QUrl &url) override; void stat(const QUrl &url) override; // Functions overwritten in kio_smb_config.cpp void reparseConfiguration() override; // Functions overwritten in kio_smb_dir.cpp void copy(const QUrl &src, const QUrl &dst, int permissions, KIO::JobFlags flags) override; void del(const QUrl &kurl, bool isfile) override; void mkdir(const QUrl &kurl, int permissions) override; void rename(const QUrl &src, const QUrl &dest, KIO::JobFlags flags) override; // Functions overwritten in kio_smb_file.cpp void get(const QUrl &kurl) override; void put(const QUrl &kurl, int permissions, KIO::JobFlags flags) override; void open(const QUrl &kurl, QIODevice::OpenMode mode) override; void read(KIO::filesize_t bytesRequested) override; void write(const QByteArray &fileData) override; void seek(KIO::filesize_t offset) override; void truncate(KIO::filesize_t length); void close() override; // Functions not implemented (yet) // virtual void setHost(const QString& host, int port, const QString& user, const QString& pass); // virtual void openConnection(); // virtual void closeConnection(); // virtual void slave_status(); void special(const QByteArray &) override; protected: void virtual_hook(int id, void *data) override; private: SMBError errnumToKioError(const SMBUrl &url, const int errNum); void smbCopy(const QUrl &src, const QUrl &dst, int permissions, KIO::JobFlags flags); void smbCopyGet(const QUrl &ksrc, const QUrl &kdst, int permissions, KIO::JobFlags flags); void smbCopyPut(const QUrl &ksrc, const QUrl &kdst, int permissions, KIO::JobFlags flags); bool workaroundEEXIST(const int errNum) const; int statToUDSEntry(const QUrl &url, const struct stat &st, KIO::UDSEntry &udsentry); void fileSystemFreeSpace(const QUrl &url); /** * Used in open(), read(), write(), and close() * FIXME Placing these in the private section above causes m_openUrl = kurl * to fail in SMBSlave::open. Need to find out why this is. */ int m_openFd; SMBUrl m_openUrl; const bool m_enableEEXISTWorkaround; /* Enables a workaround for some broken libsmbclient versions */ // Close without calling finish(). Use this to close after error. void closeWithoutFinish(); }; //========================================================================== // the global libsmbclient authentication callback function extern "C" { void auth_smbc_get_data(SMBCCTX *context, const char *server, const char *share, char *workgroup, int wgmaxlen, char *username, int unmaxlen, char *password, int pwmaxlen); } //=========================================================================== // Main slave entrypoint (see kio_smb.cpp) extern "C" { int kdemain(int argc, char **argv); } #endif //#endif KIO_SMB_H_INCLUDED diff --git a/smb/kio_smb_auth.cpp b/smb/kio_smb_auth.cpp index d42765c5..19e10a65 100644 --- a/smb/kio_smb_auth.cpp +++ b/smb/kio_smb_auth.cpp @@ -1,234 +1,209 @@ -///////////////////////////////////////////////////////////////////////////// -// -// Project: SMB kioslave for KDE2 -// -// File: kio_smb_auth.cpp -// -// Abstract: member function implementations for SMBSlave that deal with -// SMB directory access -// -// Author(s): Matthew Peterson -// -//--------------------------------------------------------------------------- -// -// Copyright (c) 2000 Caldera Systems, Inc. -// -// 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.1 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 Lesser General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program; see the file COPYING. If not, please obtain -// a copy from https://www.gnu.org/copyleft/gpl.html -// -///////////////////////////////////////////////////////////////////////////// +/* + SPDX-License-Identifier: GPL-2.0-or-later + SPDX-FileCopyrightText: 2000 Caldera Systems, Inc. + SPDX-FileContributor: Matthew Peterson +*/ #include "kio_smb.h" #include "smburl.h" #include #include #include #include void auth_smbc_get_data(SMBCCTX * context, const char *server,const char *share, char *workgroup, int wgmaxlen, char *username, int unmaxlen, char *password, int pwmaxlen) { if (context != nullptr) { #ifdef DEPRECATED_SMBC_INTERFACE auto *theSlave = static_cast(smbc_getOptionUserData(context)); #else auto *theSlave = static_cast(smbc_option_get(context, "user_data")); #endif theSlave->auth_smbc_get_data(server, share, workgroup,wgmaxlen, username, unmaxlen, password, pwmaxlen); } } void SMBSlave::auth_smbc_get_data(const char *server,const char *share, char *workgroup, int wgmaxlen, char *username, int unmaxlen, char *password, int pwmaxlen) { // check this to see if we "really" need to authenticate... SMBUrlType t = m_current_url.getType(); if (t == SMBURLTYPE_ENTIRE_NETWORK) { qCDebug(KIO_SMB_LOG) << "we don't really need to authenticate for this top level url, returning"; return; } qCDebug(KIO_SMB_LOG) << "auth_smbc_get_dat: set user=" << username << ", workgroup=" << workgroup << " server=" << server << ", share=" << share; QString s_server = QString::fromUtf8(server); QString s_share = QString::fromUtf8(share); workgroup[wgmaxlen - 1] = 0; QString s_workgroup = QString::fromUtf8(workgroup); username[unmaxlen - 1] = 0; QString s_username = QString::fromUtf8(username); password[pwmaxlen - 1] = 0; QString s_password = QString::fromUtf8(password); KIO::AuthInfo info; info.url = QUrl("smb:///"); info.url.setHost(s_server); info.url.setPath('/' + s_share); info.username = s_username; info.password = s_password; info.verifyPath = true; info.setExtraField("domain", s_workgroup); qCDebug(KIO_SMB_LOG) << "libsmb-auth-callback URL:" << info.url; if (!checkCachedAuthentication(info)) { if (m_default_user.isEmpty()) { // ok, we do not know the password. Let's try anonymous before we try for real info.username = "anonymous"; info.password.clear(); } else { // user defined a default username/password in kcontrol; try this info.username = m_default_user; info.password = m_default_password; } qCDebug(KIO_SMB_LOG) << "trying defaults for user" << info.username; } else qCDebug(KIO_SMB_LOG) << "got password through cache"; // Make sure it'll be safe to cast to size_t (unsigned) Q_ASSERT(unmaxlen > 0); Q_ASSERT(pwmaxlen > 0); Q_ASSERT(wgmaxlen > 0); strncpy(username, info.username.toUtf8(), static_cast(unmaxlen - 1)); strncpy(password, info.password.toUtf8(), static_cast(pwmaxlen - 1)); // TODO: isEmpty guard can be removed in 20.08+ // It is only here to prevent us setting an empty work group if a user updates // but doesn't restart so kiod5 could hold an old cache without domain // field. In that event we'll leave the input workgroup as-is. const QString domain = info.getExtraField("domain").toString(); if (!domain.isEmpty()) { strncpy(workgroup, domain.toUtf8(), static_cast(wgmaxlen - 1)); } } int SMBSlave::checkPassword(SMBUrl &url) { qCDebug(KIO_SMB_LOG) << "checkPassword for " << url; KIO::AuthInfo info; info.url = QUrl("smb:///"); info.url.setHost(url.host()); QString share = url.path(); int index = share.indexOf('/', 1); if (index > 1) share = share.left(index); if (share.at(0) == '/') share = share.mid(1); info.url.setPath('/' + share); info.verifyPath = true; info.keepPassword = true; info.setExtraField("anonymous", true); // arbitrary default for dialog info.setExtraField("domain", m_default_workgroup); if (share.isEmpty()) info.prompt = i18n("Please enter authentication information for %1", url.host()); else info.prompt = i18n( "Please enter authentication information for:\n" "Server = %1\n" "Share = %2", url.host(), share); info.username = url.userName(); qCDebug(KIO_SMB_LOG) << "call openPasswordDialog for " << info.url; const int passwordDialogErrorCode = openPasswordDialogV2(info); if (passwordDialogErrorCode == KJob::NoError) { qCDebug(KIO_SMB_LOG) << "openPasswordDialog returned " << info.username; url.setUser(info.username); if (info.keepPassword) { qCDebug(KIO_SMB_LOG) << "Caching info.username = " << info.username << ", info.url = " << info.url.toDisplayString(); cacheAuthentication(info); } return KJob::NoError; } qCDebug(KIO_SMB_LOG) << "no value from openPasswordDialog; error:" << passwordDialogErrorCode; return passwordDialogErrorCode; } bool SMBSlave::auth_initialize_smbc() { if (m_initialized_smbc) { return true; } qCDebug(KIO_SMB_LOG) << "auth_initialize_smbc"; KConfig cfg("kioslaverc", KConfig::SimpleConfig); int debug_level = cfg.group("SMB").readEntry("DebugLevel", 0); qCDebug(KIO_SMB_LOG) << "smbc_new_context call"; SMBCCTX *smb_context = smbc_new_context(); if (!smb_context) { SlaveBase::error(ERR_INTERNAL, i18n("libsmbclient failed to create context")); return false; } #ifdef DEPRECATED_SMBC_INTERFACE // defined by libsmbclient.h of Samba 3.2 /* New libsmbclient interface of Samba 3.2 */ qCDebug(KIO_SMB_LOG) << "Setting debug level to:" << debug_level; smbc_setDebug(smb_context, debug_level); smbc_setFunctionAuthDataWithContext(smb_context, ::auth_smbc_get_data); smbc_setOptionUserData(smb_context, this); /* Enable Kerberos support */ smbc_setOptionUseKerberos(smb_context, 1); smbc_setOptionFallbackAfterKerberos(smb_context, 1); #else smb_context->debug = debug_level; smb_context->callbacks.auth_fn = NULL; smbc_option_set(smb_context, "auth_function", (void *)::auth_smbc_get_data); smbc_option_set(smb_context, "user_data", this); #if defined(SMB_CTX_FLAG_USE_KERBEROS) && defined(SMB_CTX_FLAG_FALLBACK_AFTER_KERBEROS) smb_context->flags |= SMB_CTX_FLAG_USE_KERBEROS | SMB_CTX_FLAG_FALLBACK_AFTER_KERBEROS; #endif #endif /* DEPRECATED_SMBC_INTERFACE */ if (!smbc_init_context(smb_context)) { smbc_free_context(smb_context, 0); smb_context = nullptr; SlaveBase::error(ERR_INTERNAL, i18n("libsmbclient failed to initialize context")); return false; } smbc_set_context(smb_context); // TODO: refactor; checkPassword should query this on // demand to not run into situations where we may have cached // the workgroup early on and it changed since. Needs context // being held in the slave though, which opens us up to nullptr // problems should checkPassword be called without init first. m_default_workgroup = smbc_getWorkgroup(smb_context); m_initialized_smbc = true; return true; } diff --git a/smb/kio_smb_browse.cpp b/smb/kio_smb_browse.cpp index c3ee9813..a7cc15ee 100644 --- a/smb/kio_smb_browse.cpp +++ b/smb/kio_smb_browse.cpp @@ -1,583 +1,557 @@ - -///////////////////////////////////////////////////////////////////////////// -// -// Project: SMB kioslave for KDE2 -// -// File: kio_smb_browse.cpp -// -// Abstract: member function implementations for SMBSlave that deal with -// SMB browsing -// -// Author(s): Matthew Peterson -// -//--------------------------------------------------------------------------- -// -// Copyright (c) 2000 Caldera Systems, Inc. -// Copyright (c) 2018-2020 Harald Sitter -// -// 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.1 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 Lesser General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program; see the file COPYING. If not, please obtain -// a copy from https://www.gnu.org/copyleft/gpl.html -// -///////////////////////////////////////////////////////////////////////////// +/* + SPDX-License-Identifier: GPL-2.0-or-later + SPDX-FileCopyrightText: 2000 Caldera Systems, Inc. + SPDX-FileCopyrightText: 2018-2020 Harald Sitter + SPDX-FileContributor: Matthew Peterson +*/ #include "kio_smb.h" #include "smburl.h" #include #include #include #include #include #include #include #include #include "dnssddiscoverer.h" #include "smbcdiscoverer.h" #include "wsdiscoverer.h" #include using namespace KIO; int SMBSlave::cache_stat(const SMBUrl &url, struct stat *st) { int cacheStatErr; int result = smbc_stat(url.toSmbcUrl(), st); if (result == 0) { cacheStatErr = 0; } else { cacheStatErr = errno; } qCDebug(KIO_SMB_LOG) << "size " << static_cast(st->st_size); return cacheStatErr; } int SMBSlave::browse_stat_path(const SMBUrl &url, UDSEntry &udsentry) { int cacheStatErr = cache_stat(url, &st); if (cacheStatErr == 0) { return statToUDSEntry(url, st, udsentry); } return cacheStatErr; } int SMBSlave::statToUDSEntry(const QUrl &url, const struct stat &st, KIO::UDSEntry &udsentry) { if (!S_ISDIR(st.st_mode) && !S_ISREG(st.st_mode)) { qCDebug(KIO_SMB_LOG) << "mode: "<< st.st_mode; warning(i18n("%1:\n" "Unknown file type, neither directory or file.", url.toDisplayString())); return EINVAL; } if (!S_ISDIR(st.st_mode)) { // Awkwardly documented at // https://www.samba.org/samba/docs/using_samba/ch08.html // libsmb_stat.c assigns special meaning to +x permissions // (obviously only on files, all dirs are +x so this hacky representation // wouldn't work!): // - S_IXUSR = DOS archive: This file has been touched since the last DOS backup was performed on it. // - S_IXGRP = DOS system: This file has a specific purpose required by the operating system. // - S_IXOTH = DOS hidden: This file has been marked to be invisible to the user, unless the operating system is explicitly set to show it. // Only hiding has backing through KIO right now. if (st.st_mode & S_IXOTH) { // DOS hidden udsentry.fastInsert(KIO::UDSEntry::UDS_HIDDEN, true); } } // UID and GID **must** not be mapped. The values returned by libsmbclient are // simply the getuid/getgid of the process. They mean absolutely nothing. // Also see libsmb_stat.c. // Related: https://bugs.kde.org/show_bug.cgi?id=212801 // POSIX Access mode must not be mapped either! // It's meaningless for smb shares and downright disadvantagous. // The mode attributes outside the ones used and document above are // useless. The only one actively set is readonlyness. // // BUT the READONLY attribute does nothing on NT systems: // https://support.microsoft.com/en-us/help/326549/you-cannot-view-or-change-the-read-only-or-the-system-attributes-of-fo // The Read-only and System attributes is only used by Windows Explorer to determine // whether the folder is a special folder, such as a system folder that has its view // customized by Windows (for example, My Documents, Favorites, Fonts, Downloaded Program Files), // or a folder that you customized by using the Customize tab of the folder's Properties dialog box. // // As such respecting it on a KIO level is actually wrong as it doesn't indicate actual // readonlyness since the 90s and causes us to show readonly UI states when in fact // the directory is perfectly writable. // https://bugs.kde.org/show_bug.cgi?id=414482 // // Should we ever want to parse desktop.ini like we do .directory we'd only want to when a // dir is readonly as per the above microsoft support article. // Also see: // https://docs.microsoft.com/en-us/windows/win32/shell/how-to-customize-folders-with-desktop-ini udsentry.fastInsert(KIO::UDSEntry::UDS_FILE_TYPE, st.st_mode & S_IFMT); udsentry.fastInsert(KIO::UDSEntry::UDS_SIZE, st.st_size); udsentry.fastInsert(KIO::UDSEntry::UDS_MODIFICATION_TIME, st.st_mtime); udsentry.fastInsert(KIO::UDSEntry::UDS_ACCESS_TIME, st.st_atime); // No, st_ctime is not UDS_CREATION_TIME... return 0; } void SMBSlave::stat(const QUrl &kurl) { qCDebug(KIO_SMB_LOG) << kurl; // make a valid URL QUrl url = checkURL(kurl); // if URL is not valid we have to redirect to correct URL if (url != kurl) { qCDebug(KIO_SMB_LOG) << "redirection " << url; redirection(url); finished(); return; } m_current_url = url; UDSEntry udsentry; // Set name udsentry.fastInsert(KIO::UDSEntry::UDS_NAME, kurl.fileName()); switch (m_current_url.getType()) { case SMBURLTYPE_UNKNOWN: error(ERR_MALFORMED_URL, url.toDisplayString()); return; case SMBURLTYPE_ENTIRE_NETWORK: case SMBURLTYPE_WORKGROUP_OR_SERVER: udsentry.fastInsert(KIO::UDSEntry::UDS_FILE_TYPE, S_IFDIR); statEntry(udsentry); finished(); return; case SMBURLTYPE_SHARE_OR_PATH: { int ret = browse_stat_path(m_current_url, udsentry); if (ret == EPERM || ret == EACCES || workaroundEEXIST(ret)) { SMBUrl smbUrl(url); const int passwordError = checkPassword(smbUrl); if (passwordError == KJob::NoError) { redirection(smbUrl); finished(); } else if (passwordError == KIO::ERR_USER_CANCELED) { reportError(url, ret); } else { error(passwordError, url.toString()); } return; } else if (ret != 0) { qCDebug(KIO_SMB_LOG) << "stat() error" << ret << url; reportError(url, ret); return; } statEntry(udsentry); finished(); return; } } qCDebug(KIO_SMB_LOG) << "UNKNOWN " << url; finished(); } // TODO: complete checking <-- what does that even mean? // TODO: why is this not part of SMBUrl or at the very least URL validation should // be 100% shared between this and SMBUrl. Notably SMBUrl has code that looks // to do a similar thing but is much less complete. QUrl SMBSlave::checkURL(const QUrl &kurl_) const { qCDebug(KIO_SMB_LOG) << "checkURL " << kurl_; QUrl kurl(kurl_); // We treat cifs as an alias but need to translate it to smb. // https://bugs.kde.org/show_bug.cgi?id=327295 // It's not IANA registered and also libsmbc internally expects // smb URIs so we do very broadly coerce cifs to smb. // Also see SMBUrl. if (kurl.scheme() == "cifs") { kurl.setScheme("smb"); } // For WS-Discovered hosts we assume they'll respond to DNSSD names on .local but // they may only respond to llmnr/netbios names. Transparently fall back. // // Desktop linuxes tend to have llmnr disabled, by contrast win10 has dnssd enabled, // so chances are we'll be able to find a host.local more reliably. // Attempt to resolve foo.local natively, if that works use it, otherwise default to // the presumed LLMNR/netbios name found during discovery. // This should then yield reasonable results with any combination of WSD/DNSSD/LLMNR support. // - WSD+Avahi (on linux) // - WSD+Win10 (i.e. dnssd + llmnr) // - WSD+CrappyNAS (e.g. llmnr or netbios only) // // NB: smbc has no way to resolve a name without also triggering auth etc.: we must // rely on the system's ability to resolve DNSSD for this check. const QLatin1String wsdSuffix(".kio-discovery-wsd"); if (kurl.host().endsWith(wsdSuffix)) { QString host = kurl.host(); host.chop(wsdSuffix.size()); const QString dnssd(host + ".local"); auto dnssdHost = KDNSSD::ServiceBrowser::resolveHostName(dnssd); if (!dnssdHost.isNull()) { qCDebug(KIO_SMB_LOG) << "Resolved DNSSD name:" << dnssd; host = dnssd; } else { qCDebug(KIO_SMB_LOG) << "Failed to resolve DNSSD name:" << dnssd; qCDebug(KIO_SMB_LOG) << "Falling back to LLMNR name:" << host; } kurl.setHost(host); } QString surl = kurl.url(); // transform any links in the form smb:/ into smb:// if (surl.startsWith(QLatin1String("smb:/"))) { if (surl.length() == 5) { return QUrl("smb://"); } if (surl.at(5) != '/') { surl = "smb://" + surl.mid(5); qCDebug(KIO_SMB_LOG) << "checkURL return1 " << surl << " " << QUrl(surl); return QUrl(surl); } } if (surl == QLatin1String("smb://")) { return kurl; // unchanged } // smb:// normally have no userinfo // we must redirect ourself to remove the username and password if (surl.contains('@') && !surl.contains("smb://")) { QUrl url(kurl); url.setPath('/' + kurl.url().right(kurl.url().length() - kurl.url().indexOf('@') - 1)); QString userinfo = kurl.url().mid(5, kurl.url().indexOf('@') - 5); if (userinfo.contains(':')) { url.setUserName(userinfo.left(userinfo.indexOf(':'))); url.setPassword(userinfo.right(userinfo.length() - userinfo.indexOf(':') - 1)); } else { url.setUserName(userinfo); } qCDebug(KIO_SMB_LOG) << "checkURL return2 " << url; return url; } // if there's a valid host, don't have an empty path QUrl url(kurl); if (url.path().isEmpty()) url.setPath("/"); qCDebug(KIO_SMB_LOG) << "checkURL return3 " << url; return url; } SMBSlave::SMBError SMBSlave::errnumToKioError(const SMBUrl &url, const int errNum) { qCDebug(KIO_SMB_LOG) << "errNum" << errNum; switch (errNum) { case ENOENT: if (url.getType() == SMBURLTYPE_ENTIRE_NETWORK) return SMBError {ERR_SLAVE_DEFINED, i18n("Unable to find any workgroups in your local network. This might be caused by an enabled firewall.")}; else return SMBError {ERR_DOES_NOT_EXIST, url.toDisplayString()}; #ifdef ENOMEDIUM case ENOMEDIUM: return SMBError {ERR_SLAVE_DEFINED, i18n("No media in device for %1", url.toDisplayString())}; #endif #ifdef EHOSTDOWN case EHOSTDOWN: #endif case ECONNREFUSED: return SMBError {ERR_SLAVE_DEFINED, i18n("Could not connect to host for %1", url.toDisplayString())}; case ENOTDIR: return SMBError {ERR_CANNOT_ENTER_DIRECTORY, url.toDisplayString()}; case EFAULT: case EINVAL: return SMBError {ERR_DOES_NOT_EXIST, url.toDisplayString()}; case EPERM: case EACCES: return SMBError {ERR_ACCESS_DENIED, url.toDisplayString()}; case EIO: case ENETUNREACH: if (url.getType() == SMBURLTYPE_ENTIRE_NETWORK || url.getType() == SMBURLTYPE_WORKGROUP_OR_SERVER) return SMBError {ERR_SLAVE_DEFINED, i18n("Error while connecting to server responsible for %1", url.toDisplayString())}; else return SMBError {ERR_CONNECTION_BROKEN, url.toDisplayString()}; case ENOMEM: return SMBError {ERR_OUT_OF_MEMORY, url.toDisplayString()}; case ENODEV: return SMBError {ERR_SLAVE_DEFINED, i18n("Share could not be found on given server")}; case EBADF: return SMBError {ERR_INTERNAL, i18n("Bad file descriptor")}; case ETIMEDOUT: return SMBError {ERR_SERVER_TIMEOUT, url.host()}; case ENOTEMPTY: return SMBError {ERR_CANNOT_RMDIR, url.toDisplayString()}; #ifdef ENOTUNIQ case ENOTUNIQ: return SMBError {ERR_SLAVE_DEFINED, i18n("The given name could not be resolved to a unique server. " "Make sure your network is setup without any name conflicts " "between names used by Windows and by UNIX name resolution.")}; #endif case ECONNABORTED: return SMBError {ERR_CONNECTION_BROKEN, url.host()}; case EHOSTUNREACH: return SMBError {ERR_CANNOT_CONNECT, i18nc("@info:status smb failed to reach the server (e.g. server offline or network failure). %1 is an ip address or hostname", "%1: Host unreachable", url.host())}; case 0: // success return SMBError {ERR_INTERNAL, i18n("libsmbclient reported an error, but did not specify " "what the problem is. This might indicate a severe problem " "with your network - but also might indicate a problem with " "libsmbclient.\n" "If you want to help us, please provide a tcpdump of the " "network interface while you try to browse (be aware that " "it might contain private data, so do not post it if you are " "unsure about that - you can send it privately to the developers " "if they ask for it)")}; default: return SMBError { ERR_INTERNAL, i18nc("%1 is an error number, %2 either a pretty string or the number", "Unknown error condition: [%1] %2", QString::number(errNum), QString::fromLocal8Bit(strerror(errNum))) }; } } void SMBSlave::reportError(const SMBUrl &url, const int errNum) { const SMBError smbErr = errnumToKioError(url, errNum); error(smbErr.kioErrorId, smbErr.errorString); } void SMBSlave::reportWarning(const SMBUrl &url, const int errNum) { const SMBError smbErr = errnumToKioError(url, errNum); const QString errorString = buildErrorString(smbErr.kioErrorId, smbErr.errorString); warning(xi18n("Error occurred while trying to access %1%2", url.url(), errorString)); } void SMBSlave::listDir(const QUrl &kurl) { qCDebug(KIO_SMB_LOG) << kurl; // check (correct) URL QUrl url = checkURL(kurl); // if URL is not valid we have to redirect to correct URL if (url != kurl) { redirection(url); finished(); return; } m_current_url = kurl; QEventLoop e; UDSEntryList list; QStringList discoveredNames; const auto flushEntries = [this, &list]() { if (list.isEmpty()) { return; } listEntries(list); list.clear(); }; const auto quitLoop = [&e, &flushEntries]() { flushEntries(); e.quit(); }; // Since slavebase has no eventloop it wont publish results // on a timer, since we do not know how long our discovery // will take this is super meh because we may appear // stuck for a while. Implement our own listing system // based on QTimer to mitigate. QTimer sendTimer; sendTimer.setInterval(300); connect(&sendTimer, &QTimer::timeout, this, flushEntries); sendTimer.start(); QSharedPointer smbc(new SMBCDiscoverer(m_current_url, &e, this)); QVector> discoverers; discoverers << smbc; auto appendDiscovery = [&](const Discovery::Ptr &discovery) { if (discoveredNames.contains(discovery->udsName())) { return; } discoveredNames << discovery->udsName(); list.append(discovery->toEntry()); }; auto maybeFinished = [&] { // finishes if all discoveries finished bool allFinished = true; for (auto discoverer : discoverers) { allFinished = allFinished && discoverer->isFinished(); } if (allFinished) { quitLoop(); } }; connect(smbc.data(), &SMBCDiscoverer::newDiscovery, this, appendDiscovery); connect(smbc.data(), &SMBCDiscoverer::finished, this, maybeFinished); // Run service discovery if the path is root. This augments // "native" results from libsmbclient. // Also, should native resolution have encountered an error it will not matter. if (m_current_url.getType() == SMBURLTYPE_ENTIRE_NETWORK) { QSharedPointer dnssd(new DNSSDDiscoverer); QSharedPointer wsd(new WSDiscoverer); discoverers << dnssd << wsd; qCDebug(KIO_SMB_LOG) << "Adding modern discovery (dnssd/wsdiscovery)"; connect(dnssd.data(), &DNSSDDiscoverer::newDiscovery, this, appendDiscovery); connect(wsd.data(), &WSDiscoverer::newDiscovery, this, appendDiscovery); connect(dnssd.data(), &DNSSDDiscoverer::finished, this, maybeFinished); connect(wsd.data(), &WSDiscoverer::finished, this, maybeFinished); dnssd->start(); wsd->start(); qCDebug(KIO_SMB_LOG) << "Modern discovery set up."; } qCDebug(KIO_SMB_LOG) << "Starting discovery."; smbc->start(); QTimer::singleShot(16000, &e, quitLoop); // max execution time! e.exec(); qCDebug(KIO_SMB_LOG) << "Discovery finished."; if (m_current_url.getType() != SMBURLTYPE_ENTIRE_NETWORK && smbc->error() != 0) { // not smb:// and had an error -> handle it const int err = smbc->error(); if (err == EPERM || err == EACCES || workaroundEEXIST(err)) { qCDebug(KIO_SMB_LOG) << "trying checkPassword"; const int passwordError = checkPassword(m_current_url); if (passwordError == KJob::NoError) { redirection(m_current_url); finished(); } else if (passwordError == KIO::ERR_USER_CANCELED) { qCDebug(KIO_SMB_LOG) << "user cancelled password request"; reportError(m_current_url, err); } else { qCDebug(KIO_SMB_LOG) << "generic password error:" << passwordError; error(passwordError, m_current_url.toString()); } return; } qCDebug(KIO_SMB_LOG) << "reporting generic error:" << err; reportError(m_current_url, err); return; } UDSEntry udsentry; if (smbc->dirWasRoot()) { udsentry.fastInsert(KIO::UDSEntry::UDS_FILE_TYPE, S_IFDIR); udsentry.fastInsert(KIO::UDSEntry::UDS_NAME, "."); udsentry.fastInsert(KIO::UDSEntry::UDS_ACCESS, (S_IRUSR | S_IRGRP | S_IROTH | S_IXUSR | S_IXGRP | S_IXOTH)); } else { udsentry.fastInsert(KIO::UDSEntry::UDS_NAME, "."); const int statErr = browse_stat_path(m_current_url, udsentry); if (statErr) { if (statErr == ENOENT || statErr == ENOTDIR) { reportWarning(m_current_url, statErr); } // Create a default UDSEntry if we could not stat the actual directory udsentry.fastInsert(KIO::UDSEntry::UDS_FILE_TYPE, S_IFDIR); udsentry.fastInsert(KIO::UDSEntry::UDS_ACCESS, (S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH)); } } listEntry(udsentry); finished(); } void SMBSlave::fileSystemFreeSpace(const QUrl &url) { if (url.host().endsWith("kio-discovery-wsd")) { error(KIO::ERR_UNKNOWN_HOST, url.url()); return; } qCDebug(KIO_SMB_LOG) << url; // Avoid crashing in smbc_fstatvfs below when // requesting free space for smb:// which doesn't // make sense to do to begin with if (url.host().isEmpty()) { error(KIO::ERR_CANNOT_STAT, url.url()); return; } SMBUrl smbcUrl = url; struct statvfs dirStat { }; memset(&dirStat, 0, sizeof(struct statvfs)); const int err = smbc_statvfs(smbcUrl.toSmbcUrl().data(), &dirStat); if (err < 0) { error(KIO::ERR_CANNOT_STAT, url.url()); return; } // libsmb_stat.c has very awkward conditional branching that results // in data meaning different things based on context: // A samba host with unix extensions has f_frsize==0 and the f_bsize is // the actual block size. Any other server (such as windows) has a non-zero // f_frsize denoting the amount of sectors in a block and the f_bsize is // the amount of bytes in a sector. As such frsize*bsize is the actual // block size. // This was also broken in different ways throughout history, so depending // on the specific libsmbc versions the milage will vary. 4.7 to 4.11 are // at least behaving as described though. // https://bugs.kde.org/show_bug.cgi?id=298801 const auto frames = (dirStat.f_frsize == 0) ? 1 : dirStat.f_frsize; const auto blockSize = dirStat.f_bsize * frames; // Further more on older versions of samba f_bavail may not be set... const auto total = blockSize * dirStat.f_blocks; const auto available = blockSize * ((dirStat.f_bavail != 0) ? dirStat.f_bavail : dirStat.f_bfree); setMetaData("total", QString::number(total)); setMetaData("available", QString::number(available)); finished(); } bool SMBSlave::workaroundEEXIST(const int errNum) const { return (errNum == EEXIST) && m_enableEEXISTWorkaround; } diff --git a/smb/kio_smb_config.cpp b/smb/kio_smb_config.cpp index 5701fad0..3576e9ca 100644 --- a/smb/kio_smb_config.cpp +++ b/smb/kio_smb_config.cpp @@ -1,64 +1,39 @@ -///////////////////////////////////////////////////////////////////////////// -// -// Project: SMB kioslave for KDE2 -// -// File: kio_smb_config.cpp -// -// Abstract: member function implementations for SMBSlave that deal with -// KDE/SMB slave configuration -// -// Author(s): Matthew Peterson -// -//--------------------------------------------------------------------------- -// -// Copyright (c) 2000 Caldera Systems, Inc. -// -// 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.1 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 Lesser General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program; see the file COPYING. If not, please obtain -// a copy from https://www.gnu.org/copyleft/gpl.html -// -///////////////////////////////////////////////////////////////////////////// +/* + SPDX-License-Identifier: GPL-2.0-or-later + SPDX-FileCopyrightText: 2000 Caldera Systems, Inc. + SPDX-FileContributor: Matthew Peterson +*/ #include "kio_smb.h" #include "smburl.h" #include #include #include void SMBSlave::reparseConfiguration() { KConfig cfg("kioslaverc", KConfig::NoGlobals); const KConfigGroup group = cfg.group("Browser Settings/SMBro"); m_default_user = group.readEntry("User"); // m_default_workgroup=group.readEntry("Workgroup"); // m_showHiddenShares=group.readEntry("ShowHiddenShares", QVariant(false)).toBool(); QString m_encoding = QTextCodec::codecForLocale()->name(); m_default_encoding = group.readEntry("Encoding", m_encoding.toLower()); // unscramble, taken from Nicola Brodu's smb ioslave // not really secure, but better than storing the plain password QString scrambled = group.readEntry("Password"); m_default_password = ""; for (int i = 0; i < scrambled.length() / 3; i++) { QChar qc1 = scrambled[i * 3]; QChar qc2 = scrambled[i * 3 + 1]; QChar qc3 = scrambled[i * 3 + 2]; unsigned int a1 = qc1.toLatin1() - '0'; unsigned int a2 = qc2.toLatin1() - 'A'; unsigned int a3 = qc3.toLatin1() - '0'; unsigned int num = ((a1 & 0x3F) << 10) | ((a2 & 0x1F) << 5) | (a3 & 0x1F); m_default_password[i] = QChar((uchar)((num - 17) ^ 173)); // restore } } diff --git a/smb/kio_smb_dir.cpp b/smb/kio_smb_dir.cpp index abc9f806..ef016266 100644 --- a/smb/kio_smb_dir.cpp +++ b/smb/kio_smb_dir.cpp @@ -1,735 +1,710 @@ -///////////////////////////////////////////////////////////////////////////// -// -// Project: SMB kioslave for KDE2 -// -// File: kio_smb_dir.cpp -// -// Abstract: member function implementations for SMBSlave that deal with -// SMB directory access -// -// Author(s): Matthew Peterson -// -////--------------------------------------------------------------------------- -// -// Copyright (c) 2000 Caldera Systems, Inc. -// -// 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.1 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 Lesser General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program; see the file COPYING. If not, please obtain -// a copy from https://www.gnu.org/copyleft/gpl.html -// -///////////////////////////////////////////////////////////////////////////// +/* + SPDX-License-Identifier: GPL-2.0-or-later + SPDX-FileCopyrightText: 2000 Caldera Systems, Inc. + SPDX-FileContributor: Matthew Peterson +*/ #include "kio_smb.h" #include "smburl.h" #include #include #include #include #include #include #include "transfer.h" void SMBSlave::copy(const QUrl &src, const QUrl &dst, int permissions, KIO::JobFlags flags) { const bool isSourceLocal = src.isLocalFile(); const bool isDestinationLocal = dst.isLocalFile(); if (!isSourceLocal && isDestinationLocal) { smbCopyGet(src, dst, permissions, flags); } else if (isSourceLocal && !isDestinationLocal) { smbCopyPut(src, dst, permissions, flags); } else { smbCopy(src, dst, permissions, flags); } } void SMBSlave::smbCopy(const QUrl &ksrc, const QUrl &kdst, int permissions, KIO::JobFlags flags) { qCDebug(KIO_SMB_LOG) << "SMBSlave::copy with src = " << ksrc << "and dest = " << kdst; // setup urls SMBUrl src = ksrc; SMBUrl dst = kdst; // Obtain information about source int errNum = cache_stat(src, &st); if (errNum != 0) { if (errNum == EACCES) { error(KIO::ERR_ACCESS_DENIED, src.toDisplayString()); } else { error(KIO::ERR_DOES_NOT_EXIST, src.toDisplayString()); } return; } if (S_ISDIR(st.st_mode)) { error(KIO::ERR_IS_DIRECTORY, src.toDisplayString()); return; } const auto srcSize = st.st_size; totalSize(srcSize); // Check to se if the destination exists errNum = cache_stat(dst, &st); if (errNum == 0) { if (S_ISDIR(st.st_mode)) { error(KIO::ERR_DIR_ALREADY_EXIST, dst.toDisplayString()); return; } if (!(flags & KIO::Overwrite)) { error(KIO::ERR_FILE_ALREADY_EXIST, dst.toDisplayString()); return; } } // Open the source file int srcfd = smbc_open(src.toSmbcUrl(), O_RDONLY, 0); if (srcfd < 0) { errNum = errno; } else { errNum = 0; } if (srcfd < 0) { if (errNum == EACCES) { error(KIO::ERR_ACCESS_DENIED, src.toDisplayString()); } else { error(KIO::ERR_DOES_NOT_EXIST, src.toDisplayString()); } return; } mode_t initialmode = 0; // Determine initial creation mode if (permissions != -1) { initialmode = permissions | S_IWUSR; } else { initialmode = 0 | S_IWUSR; // 0666; } // Open the destination file int dstflags = O_CREAT | O_TRUNC | O_WRONLY; if (!(flags & KIO::Overwrite)) { dstflags |= O_EXCL; } int dstfd = smbc_open(dst.toSmbcUrl(), dstflags, initialmode); if (dstfd < 0) { errNum = errno; } else { errNum = 0; } if (dstfd < 0) { if (errNum == EACCES) { error(KIO::ERR_WRITE_ACCESS_DENIED, dst.toDisplayString()); } else { error(KIO::ERR_CANNOT_OPEN_FOR_READING, dst.toDisplayString()); } if (srcfd >= 0) { smbc_close(srcfd); } return; } // Perform copy // TODO: if and when smb_context becomes thread-safe, use two contexts connected with // a ring buffer to optimize transfer speed (also see smbCopyGet) // https://bugzilla.samba.org/show_bug.cgi?id=11413 KIO::filesize_t processed_size = 0; TransferSegment segment(srcSize); while (true) { ssize_t n = smbc_read(srcfd, segment.buf.data(), segment.buf.size()); if (n > 0) { n = smbc_write(dstfd, segment.buf.data(), n); if (n == -1) { qCDebug(KIO_SMB_LOG) << "SMBSlave::copy copy now KIO::ERR_CANNOT_WRITE"; error(KIO::ERR_CANNOT_WRITE, dst.toDisplayString()); break; } processed_size += n; processedSize(processed_size); } else if (n == 0) { break; // finished } else { error(KIO::ERR_CANNOT_READ, src.toDisplayString()); break; } } // FINISHED: if (srcfd >= 0) { smbc_close(srcfd); } if (dstfd >= 0) { if (smbc_close(dstfd) == 0) { // TODO: set final permissions } else { error(KIO::ERR_CANNOT_WRITE, dst.toDisplayString()); return; } } finished(); } void SMBSlave::smbCopyGet(const QUrl &ksrc, const QUrl &kdst, int permissions, KIO::JobFlags flags) { qCDebug(KIO_SMB_LOG) << "src = " << ksrc << ", dest = " << kdst; // check if destination is ok ... const QString dstFile = kdst.toLocalFile(); const QFileInfo dstInfo(dstFile); if (dstInfo.exists()) { if (dstInfo.isDir()) { error(ERR_IS_DIRECTORY, kdst.toDisplayString()); return; } if (!(flags & KIO::Overwrite)) { error(ERR_FILE_ALREADY_EXIST, kdst.toDisplayString()); return; } } bool bResume = false; const QFileInfo partInfo(dstFile + QLatin1String(".part")); const bool bPartExists = partInfo.exists(); const bool bMarkPartial = configValue(QStringLiteral("MarkPartial"), true); if (bMarkPartial && bPartExists && partInfo.size() > 0) { if (partInfo.isDir()) { error(ERR_IS_DIRECTORY, partInfo.absoluteFilePath()); return; } bResume = canResume(partInfo.size()); } if (bPartExists && !bResume) // get rid of an unwanted ".part" file QFile::remove(partInfo.absoluteFilePath()); // open the output file... QFile::OpenMode mode; QString filename; if (bResume) { filename = partInfo.absoluteFilePath(); mode = QFile::WriteOnly | QFile::Append; } else { filename = (bMarkPartial ? partInfo.absoluteFilePath() : dstFile); mode = QFile::WriteOnly | QFile::Truncate; } QFile file(filename); if (!bResume) { QFile::Permissions perms; if (permissions == -1) { perms = QFile::ReadOwner | QFile::WriteOwner; } else { perms = KIO::convertPermissions(permissions | QFile::WriteOwner); } file.setPermissions(perms); } if (!file.open(mode)) { qCDebug(KIO_SMB_LOG) << "could not write to" << dstFile; switch (file.error()) { case QFile::OpenError: if (bResume) { error(ERR_CANNOT_RESUME, kdst.toDisplayString()); } else { error(ERR_CANNOT_OPEN_FOR_WRITING, kdst.toDisplayString()); } break; case QFile::PermissionsError: error(ERR_WRITE_ACCESS_DENIED, kdst.toDisplayString()); break; default: error(ERR_CANNOT_OPEN_FOR_WRITING, kdst.toDisplayString()); break; } return; } // setup the source urls const SMBUrl src(ksrc); // Obtain information about source int errNum = cache_stat(src, &st); if (errNum != 0) { if (errNum == EACCES) { error(KIO::ERR_ACCESS_DENIED, src.toDisplayString()); } else { error(KIO::ERR_DOES_NOT_EXIST, src.toDisplayString()); } return; } if (S_ISDIR(st.st_mode)) { error(KIO::ERR_IS_DIRECTORY, src.toDisplayString()); return; } totalSize(st.st_size); // Open the source file KIO::filesize_t processed_size = 0; int srcfd = smbc_open(src.toSmbcUrl(), O_RDONLY, 0); if (srcfd < 0) { errNum = errno; } else { errNum = 0; if (bResume) { qCDebug(KIO_SMB_LOG) << "seeking to size" << partInfo.size(); off_t offset = smbc_lseek(srcfd, partInfo.size(), SEEK_SET); if (offset == -1) { error(KIO::ERR_CANNOT_SEEK, src.toDisplayString()); smbc_close(srcfd); return; } else { processed_size += offset; } } } if (srcfd < 0) { if (errNum == EACCES) { error(KIO::ERR_ACCESS_DENIED, src.toDisplayString()); } else { error(KIO::ERR_DOES_NOT_EXIST, src.toDisplayString()); } return; } std::atomic isErr(false); TransferRingBuffer buffer(st.st_size); auto future = std::async(std::launch::async, [&buffer, &srcfd, &isErr]() -> int { while (!isErr) { TransferSegment *segment = buffer.nextFree(); segment->size = smbc_read(srcfd, segment->buf.data(), segment->buf.capacity()); if (segment->size <= 0) { buffer.push(); buffer.done(); if (segment->size < 0) { return KIO::ERR_COULD_NOT_READ; } break; } buffer.push(); } return KJob::NoError; }); while (true) { TransferSegment *segment = buffer.pop(); if (!segment) { // done, no more segments pending break; } const qint64 bytesWritten = file.write(segment->buf.data(), segment->size); if (bytesWritten == -1) { qCDebug(KIO_SMB_LOG) << "copy now KIO::ERR_CANNOT_WRITE"; error(KIO::ERR_CANNOT_WRITE, kdst.toDisplayString()); isErr = true; buffer.unpop(); break; } processed_size += bytesWritten; processedSize(processed_size); buffer.unpop(); } if (isErr) { // writing failed future.wait(); } else if (future.get() != KJob::NoError) { // check if read had an error error(future.get(), ksrc.toDisplayString()); isErr = true; } // FINISHED smbc_close(srcfd); // Handle error condition. if (isErr) { const QString sPart = partInfo.absoluteFilePath(); if (bMarkPartial) { const int size = configValue(QStringLiteral("MinimumKeepSize"), DEFAULT_MINIMUM_KEEP_SIZE); if (partInfo.size() < size) { QFile::remove(sPart); } } return; } // Rename partial file to its original name. if (bMarkPartial) { const QString sPart = partInfo.absoluteFilePath(); // Remove old dest file if it exists.. if (dstInfo.exists()) { QFile::remove(dstFile); } if (!QFile::rename(sPart, dstFile)) { qCDebug(KIO_SMB_LOG) << "failed to rename" << sPart << "to" << dstFile; error(ERR_CANNOT_RENAME_PARTIAL, sPart); return; } } // Restore the mtime on the file. const QString mtimeStr = metaData("modified"); qCDebug(KIO_SMB_LOG) << "modified:" << mtimeStr; if (!mtimeStr.isEmpty()) { QDateTime dt = QDateTime::fromString(mtimeStr, Qt::ISODate); if (dt.isValid()) { struct utimbuf utbuf { }; utbuf.actime = QFileInfo(dstFile).lastRead().toSecsSinceEpoch(); // access time, unchanged utbuf.modtime = dt.toSecsSinceEpoch(); // modification time utime(QFile::encodeName(dstFile).constData(), &utbuf); } } finished(); } void SMBSlave::smbCopyPut(const QUrl &ksrc, const QUrl &kdst, int permissions, KIO::JobFlags flags) { qCDebug(KIO_SMB_LOG) << "src = " << ksrc << ", dest = " << kdst; QFile srcFile(ksrc.toLocalFile()); const QFileInfo srcInfo(srcFile); if (srcInfo.exists()) { if (srcInfo.isDir()) { error(KIO::ERR_IS_DIRECTORY, ksrc.toDisplayString()); return; } } else { error(KIO::ERR_DOES_NOT_EXIST, ksrc.toDisplayString()); return; } if (!srcFile.open(QFile::ReadOnly)) { qCDebug(KIO_SMB_LOG) << "could not read from" << ksrc; switch (srcFile.error()) { case QFile::PermissionsError: error(KIO::ERR_WRITE_ACCESS_DENIED, ksrc.toDisplayString()); break; case QFile::OpenError: default: error(KIO::ERR_CANNOT_OPEN_FOR_READING, ksrc.toDisplayString()); break; } return; } totalSize(static_cast(srcInfo.size())); bool bResume = false; bool bPartExists = false; const bool bMarkPartial = configValue(QStringLiteral("MarkPartial"), true); const SMBUrl dstOrigUrl(kdst); if (bMarkPartial) { const int errNum = cache_stat(dstOrigUrl.partUrl(), &st); bPartExists = (errNum == 0); if (bPartExists) { if (!(flags & KIO::Overwrite) && !(flags & KIO::Resume)) { bResume = canResume(st.st_size); } else { bResume = (flags & KIO::Resume); } } } int dstfd = -1; int errNum = cache_stat(dstOrigUrl, &st); if (errNum == 0 && !(flags & KIO::Overwrite) && !(flags & KIO::Resume)) { if (S_ISDIR(st.st_mode)) { error(KIO::ERR_IS_DIRECTORY, dstOrigUrl.toDisplayString()); } else { error(KIO::ERR_FILE_ALREADY_EXIST, dstOrigUrl.toDisplayString()); } return; } KIO::filesize_t processed_size = 0; const SMBUrl dstUrl(bMarkPartial ? dstOrigUrl.partUrl() : dstOrigUrl); if (bResume) { // append if resuming qCDebug(KIO_SMB_LOG) << "resume" << dstUrl; dstfd = smbc_open(dstUrl.toSmbcUrl(), O_RDWR, 0); if (dstfd < 0) { errNum = errno; } else { const off_t offset = smbc_lseek(dstfd, 0, SEEK_END); if (offset == (off_t)-1) { error(KIO::ERR_CANNOT_SEEK, dstUrl.toDisplayString()); smbc_close(dstfd); return; } else { processed_size = offset; } } } else { mode_t mode; if (permissions == -1) { mode = 600; } else { mode = permissions | S_IRUSR | S_IWUSR; } qCDebug(KIO_SMB_LOG) << "NO resume" << dstUrl; dstfd = smbc_open(dstUrl.toSmbcUrl(), O_CREAT | O_TRUNC | O_WRONLY, mode); if (dstfd < 0) { errNum = errno; } } if (dstfd < 0) { if (errNum == EACCES) { qCDebug(KIO_SMB_LOG) << "access denied"; error(KIO::ERR_WRITE_ACCESS_DENIED, dstUrl.toDisplayString()); } else { qCDebug(KIO_SMB_LOG) << "can not open for writing"; error(KIO::ERR_CANNOT_OPEN_FOR_WRITING, dstUrl.toDisplayString()); } return; } bool isErr = false; if (processed_size == 0 || srcFile.seek(processed_size)) { // Perform the copy TransferSegment segment(srcInfo.size()); while (true) { const ssize_t bytesRead = srcFile.read(segment.buf.data(), segment.buf.size()); if (bytesRead <= 0) { if (bytesRead < 0) { error(KIO::ERR_CANNOT_READ, ksrc.toDisplayString()); isErr = true; } break; } const qint64 bytesWritten = smbc_write(dstfd, segment.buf.data(), bytesRead); if (bytesWritten == -1) { error(KIO::ERR_CANNOT_WRITE, kdst.toDisplayString()); isErr = true; break; } processed_size += bytesWritten; processedSize(processed_size); } } else { isErr = true; error(KIO::ERR_CANNOT_SEEK, ksrc.toDisplayString()); } // FINISHED if (smbc_close(dstfd) < 0) { qCDebug(KIO_SMB_LOG) << dstUrl << "could not write"; error(KIO::ERR_CANNOT_WRITE, dstUrl.toDisplayString()); return; } // Handle error condition. if (isErr) { if (bMarkPartial) { const int size = configValue(QStringLiteral("MinimumKeepSize"), DEFAULT_MINIMUM_KEEP_SIZE); const int errNum = cache_stat(dstUrl, &st); if (errNum == 0 && st.st_size < size) { smbc_unlink(dstUrl.toSmbcUrl()); } } return; } // Rename partial file to its original name. if (bMarkPartial) { smbc_unlink(dstOrigUrl.toSmbcUrl()); if (smbc_rename(dstUrl.toSmbcUrl(), dstOrigUrl.toSmbcUrl()) < 0) { qCDebug(KIO_SMB_LOG) << "failed to rename" << dstUrl << "to" << dstOrigUrl << "->" << strerror(errno); error(ERR_CANNOT_RENAME_PARTIAL, dstUrl.toDisplayString()); return; } } #ifdef HAVE_UTIME_H // set modification time const QString mtimeStr = metaData("modified"); if (!mtimeStr.isEmpty()) { QDateTime dt = QDateTime::fromString(mtimeStr, Qt::ISODate); if (dt.isValid()) { struct utimbuf utbuf { }; utbuf.actime = st.st_atime; // access time, unchanged utbuf.modtime = dt.toSecsSinceEpoch(); // modification time smbc_utime(dstOrigUrl.toSmbcUrl(), &utbuf); } } #endif // We have done our job => finish finished(); } void SMBSlave::del(const QUrl &kurl, bool isfile) { qCDebug(KIO_SMB_LOG) << kurl; m_current_url = kurl; int errNum = 0; int retVal = 0; if (isfile) { // Delete file qCDebug(KIO_SMB_LOG) << "Deleting file" << kurl; retVal = smbc_unlink(m_current_url.toSmbcUrl()); if (retVal < 0) { errNum = errno; } else { errNum = 0; } } else { qCDebug(KIO_SMB_LOG) << "Deleting directory" << kurl; // Delete directory retVal = smbc_rmdir(m_current_url.toSmbcUrl()); if (retVal < 0) { errNum = errno; } else { errNum = 0; } } if (errNum != 0) { reportError(kurl, errNum); } else { finished(); } } void SMBSlave::mkdir(const QUrl &kurl, int permissions) { qCDebug(KIO_SMB_LOG) << kurl; int errNum = 0; int retVal = 0; m_current_url = kurl; retVal = smbc_mkdir(m_current_url.toSmbcUrl(), 0777); if (retVal < 0) { errNum = errno; } else { errNum = 0; } if (retVal < 0) { if (errNum == EEXIST) { errNum = cache_stat(m_current_url, &st); if (errNum == 0 && S_ISDIR(st.st_mode)) { error(KIO::ERR_DIR_ALREADY_EXIST, m_current_url.toDisplayString()); } else { error(KIO::ERR_FILE_ALREADY_EXIST, m_current_url.toDisplayString()); } } else { reportError(kurl, errNum); } qCDebug(KIO_SMB_LOG) << "exit with error " << kurl; } else // success { if (permissions != -1) { // TODO enable the following when complete // smbc_chmod( url.toSmbcUrl(), permissions ); } finished(); } } void SMBSlave::rename(const QUrl &ksrc, const QUrl &kdest, KIO::JobFlags flags) { SMBUrl src; SMBUrl dst; int errNum = 0; int retVal = 0; qCDebug(KIO_SMB_LOG) << "old name = " << ksrc << ", new name = " << kdest; src = ksrc; dst = kdest; // Check to se if the destination exists qCDebug(KIO_SMB_LOG) << "stat dst"; errNum = cache_stat(dst, &st); if (errNum == 0) { if (S_ISDIR(st.st_mode)) { qCDebug(KIO_SMB_LOG) << "KIO::ERR_DIR_ALREADY_EXIST"; error(KIO::ERR_DIR_ALREADY_EXIST, dst.toDisplayString()); return; } if (!(flags & KIO::Overwrite)) { qCDebug(KIO_SMB_LOG) << "KIO::ERR_FILE_ALREADY_EXIST"; error(KIO::ERR_FILE_ALREADY_EXIST, dst.toDisplayString()); return; } } qCDebug(KIO_SMB_LOG) << "smbc_rename " << src.toSmbcUrl() << " " << dst.toSmbcUrl(); retVal = smbc_rename(src.toSmbcUrl(), dst.toSmbcUrl()); if (retVal < 0) { errNum = errno; } else { errNum = 0; } if (retVal < 0) { qCDebug(KIO_SMB_LOG) << "failed "; switch (errNum) { case ENOENT: errNum = cache_stat(src, &st); if (errNum != 0) { if (errNum == EACCES) { qCDebug(KIO_SMB_LOG) << "KIO::ERR_ACCESS_DENIED"; error(KIO::ERR_ACCESS_DENIED, src.toDisplayString()); } else { qCDebug(KIO_SMB_LOG) << "KIO::ERR_DOES_NOT_EXIST"; error(KIO::ERR_DOES_NOT_EXIST, src.toDisplayString()); } } break; case EACCES: case EPERM: qCDebug(KIO_SMB_LOG) << "KIO::ERR_ACCESS_DENIED"; error(KIO::ERR_ACCESS_DENIED, dst.toDisplayString()); break; default: qCDebug(KIO_SMB_LOG) << "KIO::ERR_CANNOT_RENAME"; error(KIO::ERR_CANNOT_RENAME, src.toDisplayString()); } qCDebug(KIO_SMB_LOG) << "exit with error"; return; } qCDebug(KIO_SMB_LOG) << "everything fine\n"; finished(); } diff --git a/smb/kio_smb_file.cpp b/smb/kio_smb_file.cpp index 3b8119bc..5697e360 100644 --- a/smb/kio_smb_file.cpp +++ b/smb/kio_smb_file.cpp @@ -1,447 +1,423 @@ -//////////////////////////////////////////////////////////////////////////// -// -// Project: SMB kioslave for KDE2 -// -// File: kio_smb_file.cpp -// -// Abstract: member function implementations for SMBSlave that deal with -// SMB file access -// -// Author(s): Matthew Peterson -// -//--------------------------------------------------------------------------- -// -// Copyright (c) 2000 Caldera Systems, Inc. -// -// 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.1 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 Lesser General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program; see the file COPYING. If not, please obtain -// a copy from https://www.gnu.org/copyleft/gpl.html -// -///////////////////////////////////////////////////////////////////////////// +/* + SPDX-License-Identifier: GPL-2.0-or-later + SPDX-FileCopyrightText: 2000 Caldera Systems, Inc. + SPDX-FileCopyrightText: 2018-2020 Harald Sitter + SPDX-FileContributor: Matthew Peterson +*/ #include "kio_smb.h" #include "smburl.h" #include #include #include #include #include #include "transfer.h" void SMBSlave::get(const QUrl &kurl) { qCDebug(KIO_SMB_LOG) << kurl; // check (correct) URL QUrl kvurl = checkURL(kurl); // if URL is not valid we have to redirect to correct URL if (kvurl != kurl) { redirection(kvurl); finished(); return; } if (!auth_initialize_smbc()) return; // Stat SMBUrl url = kurl; int errNum = cache_stat(url, &st); if (errNum != 0) { if (errNum == EACCES) error(KIO::ERR_ACCESS_DENIED, url.toDisplayString()); else error(KIO::ERR_DOES_NOT_EXIST, url.toDisplayString()); return; } if (S_ISDIR(st.st_mode)) { error(KIO::ERR_IS_DIRECTORY, url.toDisplayString()); return; } // Set the total size totalSize(st.st_size); // Open and read the file int filefd = smbc_open(url.toSmbcUrl(), O_RDONLY, 0); if (filefd < 0) { error(KIO::ERR_CANNOT_OPEN_FOR_READING, url.toDisplayString()); return; } KIO::filesize_t totalbytesread = 0; QByteArray filedata; bool isFirstPacket = true; TransferRingBuffer buffer(st.st_size); auto future = std::async(std::launch::async, [&buffer, &filefd]() -> int { while (true) { TransferSegment *s = buffer.nextFree(); s->size = smbc_read(filefd, s->buf.data(), s->buf.capacity()); if (s->size <= 0) { buffer.push(); buffer.done(); if (s->size < 0) { return KIO::ERR_COULD_NOT_READ; } break; } buffer.push(); } return KJob::NoError; }); while (true) { TransferSegment *s = buffer.pop(); if (!s) { // done, no more segments pending break; } filedata = QByteArray::fromRawData(s->buf.data(), s->size); if (isFirstPacket) { QMimeDatabase db; QMimeType type = db.mimeTypeForFileNameAndData(url.fileName(), filedata); mimeType(type.name()); isFirstPacket = false; } data(filedata); filedata.clear(); // increment total bytes read totalbytesread += s->size; processedSize(totalbytesread); buffer.unpop(); } if (future.get() != KJob::NoError) { // check if read had an error error(future.get(), url.toDisplayString()); } smbc_close(filefd); data(QByteArray()); if (totalbytesread != static_cast(st.st_size)) { qCWarning(KIO_SMB_LOG) << "Got" << totalbytesread << "bytes but expected" << st.st_size; } processedSize(static_cast(st.st_size)); finished(); } void SMBSlave::open(const QUrl &kurl, QIODevice::OpenMode mode) { int errNum = 0; qCDebug(KIO_SMB_LOG) << kurl; // check (correct) URL QUrl kvurl = checkURL(kurl); // if URL is not valid we have to redirect to correct URL if (kvurl != kurl) { redirection(kvurl); finished(); return; } if (!auth_initialize_smbc()) { error(KIO::ERR_ACCESS_DENIED, kurl.toDisplayString()); return; } // Save the URL as a private member // FIXME For some reason m_openUrl has be be declared in bottom private // section of the class SMBSlave declaration instead of the top section // or else this assignment fails m_openUrl = kurl; // Stat errNum = cache_stat(m_openUrl, &st); if (errNum != 0) { if (errNum == EACCES) error(KIO::ERR_ACCESS_DENIED, m_openUrl.toDisplayString()); else error(KIO::ERR_DOES_NOT_EXIST, m_openUrl.toDisplayString()); return; } if (S_ISDIR(st.st_mode)) { error(KIO::ERR_IS_DIRECTORY, m_openUrl.toDisplayString()); return; } // Set the total size totalSize(st.st_size); // Convert permissions int flags = 0; if (mode & QIODevice::ReadOnly) { if (mode & QIODevice::WriteOnly) { flags = O_RDWR | O_CREAT; } else { flags = O_RDONLY; } } else if (mode & QIODevice::WriteOnly) { flags = O_WRONLY | O_CREAT; } if (mode & QIODevice::Append) { flags |= O_APPEND; } else if (mode & QIODevice::Truncate) { flags |= O_TRUNC; } // Open the file m_openFd = smbc_open(m_openUrl.toSmbcUrl(), flags, 0); if (m_openFd < 0) { error(KIO::ERR_CANNOT_OPEN_FOR_READING, m_openUrl.toDisplayString()); return; } // Determine the mimetype of the file to be retrieved, and emit it. // This is mandatory in all slaves (for KRun/BrowserRun to work). // If we're not opening the file ReadOnly or ReadWrite, don't attempt to // read the file and send the mimetype. if (mode & QIODevice::ReadOnly) { ssize_t bytesRequested = 1024; ssize_t bytesRead = 0; QVarLengthArray buffer(bytesRequested); bytesRead = smbc_read(m_openFd, buffer.data(), bytesRequested); if (bytesRead < 0) { error(KIO::ERR_CANNOT_READ, m_openUrl.toDisplayString()); closeWithoutFinish(); return; } else { QByteArray fileData = QByteArray::fromRawData(buffer.data(), bytesRead); QMimeDatabase db; QMimeType type = db.mimeTypeForFileNameAndData(m_openUrl.fileName(), fileData); mimeType(type.name()); off_t res = smbc_lseek(m_openFd, 0, SEEK_SET); if (res == (off_t)-1) { error(KIO::ERR_CANNOT_SEEK, m_openUrl.path()); closeWithoutFinish(); return; } } } position(0); opened(); } void SMBSlave::read(KIO::filesize_t bytesRequested) { Q_ASSERT(m_openFd != -1); QVarLengthArray buffer(bytesRequested); ssize_t bytesRead = 0; bytesRead = smbc_read(m_openFd, buffer.data(), bytesRequested); Q_ASSERT(bytesRead <= static_cast(bytesRequested)); if (bytesRead < 0) { qCDebug(KIO_SMB_LOG) << "Could not read " << m_openUrl; error(KIO::ERR_CANNOT_READ, m_openUrl.toDisplayString()); closeWithoutFinish(); return; } QByteArray fileData = QByteArray::fromRawData(buffer.data(), bytesRead); data(fileData); } void SMBSlave::write(const QByteArray &fileData) { Q_ASSERT(m_openFd != -1); QByteArray buf(fileData); ssize_t size = smbc_write(m_openFd, buf.data(), buf.size()); if (size < 0) { qCDebug(KIO_SMB_LOG) << "Could not write to " << m_openUrl; error(KIO::ERR_CANNOT_WRITE, m_openUrl.toDisplayString()); closeWithoutFinish(); return; } written(size); } void SMBSlave::seek(KIO::filesize_t offset) { off_t res = smbc_lseek(m_openFd, static_cast(offset), SEEK_SET); if (res == (off_t)-1) { error(KIO::ERR_CANNOT_SEEK, m_openUrl.path()); closeWithoutFinish(); } else { qCDebug(KIO_SMB_LOG) << "res" << res; position(res); } } void SMBSlave::truncate(KIO::filesize_t length) { off_t res = smbc_ftruncate(m_openFd, static_cast(length)); if (res < 0) { error(KIO::ERR_CANNOT_TRUNCATE, m_openUrl.path()); closeWithoutFinish(); } else { qCDebug(KIO_SMB_LOG) << "res" << res; truncated(length); } } void SMBSlave::closeWithoutFinish() { smbc_close(m_openFd); } void SMBSlave::close() { closeWithoutFinish(); finished(); } void SMBSlave::put(const QUrl &kurl, int permissions, KIO::JobFlags flags) { void *buf; size_t bufsize; m_current_url = kurl; int filefd; bool exists; int errNum = 0; off_t retValLSeek = 0; mode_t mode; QByteArray filedata; qCDebug(KIO_SMB_LOG) << kurl; errNum = cache_stat(m_current_url, &st); exists = (errNum == 0); if (exists && !(flags & KIO::Overwrite) && !(flags & KIO::Resume)) { if (S_ISDIR(st.st_mode)) { qCDebug(KIO_SMB_LOG) << kurl << " already isdir !!"; error(KIO::ERR_DIR_ALREADY_EXIST, m_current_url.toDisplayString()); } else { qCDebug(KIO_SMB_LOG) << kurl << " already exist !!"; error(KIO::ERR_FILE_ALREADY_EXIST, m_current_url.toDisplayString()); } return; } if (exists && !(flags & KIO::Resume) && (flags & KIO::Overwrite)) { qCDebug(KIO_SMB_LOG) << "exists try to remove " << m_current_url.toSmbcUrl(); // remove(m_current_url.url().toLocal8Bit()); } if (flags & KIO::Resume) { // append if resuming qCDebug(KIO_SMB_LOG) << "resume " << m_current_url.toSmbcUrl(); filefd = smbc_open(m_current_url.toSmbcUrl(), O_RDWR, 0); if (filefd < 0) { errNum = errno; } else { errNum = 0; } retValLSeek = smbc_lseek(filefd, 0, SEEK_END); if (retValLSeek == (off_t)-1) { errNum = errno; } else { errNum = 0; } } else { if (permissions != -1) { mode = permissions | S_IWUSR | S_IRUSR; } else { mode = 600; // 0666; } qCDebug(KIO_SMB_LOG) << "NO resume " << m_current_url.toSmbcUrl(); filefd = smbc_open(m_current_url.toSmbcUrl(), O_CREAT | O_TRUNC | O_WRONLY, mode); if (filefd < 0) { errNum = errno; } else { errNum = 0; } } if (filefd < 0) { if (errNum == EACCES) { qCDebug(KIO_SMB_LOG) << "error " << kurl << " access denied !!"; error(KIO::ERR_WRITE_ACCESS_DENIED, m_current_url.toDisplayString()); } else { qCDebug(KIO_SMB_LOG) << "error " << kurl << " can not open for writing !!"; error(KIO::ERR_CANNOT_OPEN_FOR_WRITING, m_current_url.toDisplayString()); } return; } // Loop until we got 0 (end of data) while (true) { qCDebug(KIO_SMB_LOG) << "request data "; dataReq(); // Request for data qCDebug(KIO_SMB_LOG) << "write " << m_current_url.toSmbcUrl(); if (readData(filedata) <= 0) { qCDebug(KIO_SMB_LOG) << "readData <= 0"; break; } qCDebug(KIO_SMB_LOG) << "write " << m_current_url.toSmbcUrl(); buf = filedata.data(); bufsize = filedata.size(); ssize_t size = smbc_write(filefd, buf, bufsize); if (size < 0) { qCDebug(KIO_SMB_LOG) << "error " << kurl << "could not write !!"; error(KIO::ERR_CANNOT_WRITE, m_current_url.toDisplayString()); return; } qCDebug(KIO_SMB_LOG) << "wrote " << size; } qCDebug(KIO_SMB_LOG) << "close " << m_current_url.toSmbcUrl(); if (smbc_close(filefd) < 0) { qCDebug(KIO_SMB_LOG) << kurl << "could not write !!"; error(KIO::ERR_CANNOT_WRITE, m_current_url.toDisplayString()); return; } // set final permissions, if the file was just created if (permissions != -1 && !exists) { // TODO: did the smbc_chmod fail? // TODO: put in call to chmod when it is working! // smbc_chmod(url.toSmbcUrl(),permissions); } #ifdef HAVE_UTIME_H // set modification time const QString mtimeStr = metaData("modified"); if (!mtimeStr.isEmpty()) { QDateTime dt = QDateTime::fromString(mtimeStr, Qt::ISODate); if (dt.isValid()) { if (cache_stat(m_current_url, &st) == 0) { struct utimbuf utbuf { }; utbuf.actime = st.st_atime; // access time, unchanged utbuf.modtime = dt.toSecsSinceEpoch(); // modification time smbc_utime(m_current_url.toSmbcUrl(), &utbuf); } } } #endif // We have done our job => finish finished(); } diff --git a/smb/kio_smb_mount.cpp b/smb/kio_smb_mount.cpp index 29ef38ec..b0caf2a8 100644 --- a/smb/kio_smb_mount.cpp +++ b/smb/kio_smb_mount.cpp @@ -1,168 +1,153 @@ -/* This file is part of the KDE project - - Copyright (C) 2000 Alexander Neundorf - - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Library General Public - License as published by the Free Software Foundation; either - version 2 of the License, or (at your option) any later version. - - This library 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 - Library General Public License for more details. - - You should have received a copy of the GNU Library General Public License - along with this library; see the file COPYING.LIB. If not, write to - the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, - Boston, MA 02110-1301, USA. +/* + SPDX-License-Identifier: LGPL-2.0-or-later + SPDX-FileCopyrightText: 2000 Alexander Neundorf */ #include "kio_smb.h" #include #include #include #include #include #include #include void SMBSlave::special(const QByteArray &data) { qCDebug(KIO_SMB_LOG) << "Smb::special()"; int tmp; QDataStream stream(data); stream >> tmp; // mounting and umounting are both blocking, "guarded" by a SIGALARM in the future switch (tmp) { case 1: case 3: { QString remotePath; QString mountPoint; QString user; stream >> remotePath >> mountPoint; QStringList sl = remotePath.split('/'); QString share; QString host; if (sl.count() >= 2) { host = sl.at(0).mid(2); share = sl.at(1); qCDebug(KIO_SMB_LOG) << "special() host -" << host << "- share -" << share << "-"; } remotePath.replace('\\', '/'); // smbmounterplugin sends \\host/share qCDebug(KIO_SMB_LOG) << "mounting: " << remotePath.toLocal8Bit() << " to " << mountPoint.toLocal8Bit(); if (tmp == 3) { if (!QDir().mkpath(mountPoint)) { error(KIO::ERR_CANNOT_MKDIR, mountPoint); return; } } SMBUrl smburl(QUrl("smb:///")); smburl.setHost(host); smburl.setPath('/' + share); const int passwordError = checkPassword(smburl); if (passwordError != KJob::NoError && passwordError != KIO::ERR_USER_CANCELED) { error(passwordError, smburl.toString()); return; } // using smbmount instead of "mount -t smbfs", because mount does not allow a non-root // user to do a mount, but a suid smbmnt does allow this KProcess proc; proc.setOutputChannelMode(KProcess::SeparateChannels); proc << "smbmount"; QString options; if (smburl.userName().isEmpty()) { user = "guest"; options = "guest"; } else { options = "username=" + smburl.userName(); user = smburl.userName(); if (!smburl.password().isEmpty()) options += ",password=" + smburl.password(); } // TODO: check why the control center uses encodings with a blank char, e.g. "cp 1250" // if ( ! m_default_encoding.isEmpty() ) // options += ",codepage=" + KShell::quoteArg(m_default_encoding); proc << remotePath; proc << mountPoint; proc << "-o" << options; proc.start(); if (!proc.waitForFinished()) { error(KIO::ERR_CANNOT_LAUNCH_PROCESS, "smbmount" + i18n("\nMake sure that the samba package is installed properly on your system.")); return; } QString mybuf = QString::fromLocal8Bit(proc.readAllStandardOutput()); QString mystderr = QString::fromLocal8Bit(proc.readAllStandardError()); qCDebug(KIO_SMB_LOG) << "mount exit " << proc.exitCode() << "stdout:" << mybuf << "\nstderr:" << mystderr; if (proc.exitCode() != 0) { error(KIO::ERR_CANNOT_MOUNT, i18n("Mounting of share \"%1\" from host \"%2\" by user \"%3\" failed.\n%4", share, host, user, mybuf + '\n' + mystderr)); return; } } break; case 2: case 4: { QString mountPoint; stream >> mountPoint; KProcess proc; proc.setOutputChannelMode(KProcess::SeparateChannels); proc << "smbumount"; proc << mountPoint; proc.start(); if (!proc.waitForFinished()) { error(KIO::ERR_CANNOT_LAUNCH_PROCESS, "smbumount" + i18n("\nMake sure that the samba package is installed properly on your system.")); return; } QString mybuf = QString::fromLocal8Bit(proc.readAllStandardOutput()); QString mystderr = QString::fromLocal8Bit(proc.readAllStandardError()); qCDebug(KIO_SMB_LOG) << "smbumount exit " << proc.exitCode() << "stdout:" << mybuf << "\nstderr:" << mystderr; if (proc.exitCode() != 0) { error(KIO::ERR_CANNOT_UNMOUNT, i18n("Unmounting of mountpoint \"%1\" failed.\n%2", mountPoint, mybuf + '\n' + mystderr)); return; } if (tmp == 4) { bool ok; QDir dir(mountPoint); dir.cdUp(); ok = dir.rmdir(mountPoint); if (ok) { QString p = dir.path(); dir.cdUp(); ok = dir.rmdir(p); } if (!ok) { error(KIO::ERR_CANNOT_RMDIR, mountPoint); return; } } } break; default: break; } finished(); } diff --git a/smb/main.cpp b/smb/main.cpp index 979aab71..157f3f9e 100644 --- a/smb/main.cpp +++ b/smb/main.cpp @@ -1,46 +1,25 @@ -///////////////////////////////////////////////////////////////////////////// -// -// Project: SMB kioslave for KDE -// -// Abstract: member function implementations for SMBSlave -// -// Author(s): Matthew Peterson -// -//--------------------------------------------------------------------------- -// -// Copyright (c) 2000 Caldera Systems, Inc. -// -// 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.1 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 Lesser General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program; see the file COPYING. If not, please obtain -// a copy from https://www.gnu.org/copyleft/gpl.html -// -///////////////////////////////////////////////////////////////////////////// +/* + SPDX-License-Identifier: GPL-2.0-or-later + SPDX-FileCopyrightText: 2000 Caldera Systems, Inc. + SPDX-FileCopyrightText: 2020 Harald Sitter + SPDX-FileContributor: Matthew Peterson +*/ #include #include "kio_smb.h" extern "C" int Q_DECL_EXPORT kdemain(int argc, char **argv) { QCoreApplication app(argc, argv); if (argc != 4) { qCDebug(KIO_SMB_LOG) << "Usage: kio_smb protocol domain-socket1 domain-socket2"; return -1; } SMBSlave slave(argv[2], argv[3]); slave.dispatchLoop(); return 0; } diff --git a/smb/smbcdiscoverer.cpp b/smb/smbcdiscoverer.cpp index c744868f..2159a29e 100644 --- a/smb/smbcdiscoverer.cpp +++ b/smb/smbcdiscoverer.cpp @@ -1,300 +1,300 @@ /* - SPDX-FileCopyrightText: 2020 Harald Sitter SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL + SPDX-FileCopyrightText: 2020 Harald Sitter */ #include #include #include #include "smbcdiscoverer.h" static QEvent::Type LoopEvent = QEvent::User; class SMBCServerDiscovery : public SMBCDiscovery { public: SMBCServerDiscovery(const UDSEntry &entry) : SMBCDiscovery(entry) { m_entry.fastInsert(KIO::UDSEntry::UDS_FILE_TYPE, S_IFDIR); m_entry.fastInsert(KIO::UDSEntry::UDS_ACCESS, (S_IRUSR | S_IXUSR | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH)); m_entry.fastInsert(KIO::UDSEntry::UDS_URL, url()); m_entry.fastInsert(KIO::UDSEntry::UDS_MIME_TYPE, QString::fromLatin1("application/x-smb-server")); m_entry.fastInsert(KIO::UDSEntry::UDS_ICON_NAME, "network-server"); } QString url() { QUrl u("smb://"); u.setHost(udsName()); return u.url(); } }; class SMBCShareDiscovery : public SMBCDiscovery { public: SMBCShareDiscovery(const UDSEntry &entry) : SMBCDiscovery(entry) { m_entry.fastInsert(KIO::UDSEntry::UDS_FILE_TYPE, S_IFDIR); m_entry.fastInsert(KIO::UDSEntry::UDS_ACCESS, (S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH)); } }; class SMBCWorkgroupDiscovery : public SMBCDiscovery { public: SMBCWorkgroupDiscovery(const UDSEntry &entry) : SMBCDiscovery(entry) { m_entry.fastInsert(KIO::UDSEntry::UDS_FILE_TYPE, S_IFDIR); m_entry.fastInsert(KIO::UDSEntry::UDS_ACCESS, (S_IRUSR | S_IRGRP | S_IROTH | S_IXUSR | S_IXGRP | S_IXOTH)); m_entry.fastInsert(KIO::UDSEntry::UDS_MIME_TYPE, QString::fromLatin1("application/x-smb-workgroup")); m_entry.fastInsert(KIO::UDSEntry::UDS_URL, url()); } QString url() { QUrl u("smb://"); u.setHost(udsName()); if (!u.isValid()) { // In the event that the workgroup contains bad characters, put it in a query instead. // This is transparently handled by SMBUrl when we get this as input again. // Also see documentation there. // https://bugs.kde.org/show_bug.cgi?id=204423 u.setHost(QString()); QUrlQuery q; q.addQueryItem("kio-workgroup", udsName()); u.setQuery(q); } return u.url(); } }; SMBCDiscovery::SMBCDiscovery(const UDSEntry &entry) : m_entry(entry) // cache the name, it may get accessed more than once , m_name(entry.stringValue(KIO::UDSEntry::UDS_NAME)) { } QString SMBCDiscovery::udsName() const { return m_name; } KIO::UDSEntry SMBCDiscovery::toEntry() const { return m_entry; } SMBCDiscoverer::SMBCDiscoverer(const SMBUrl &url, QEventLoop *loop, SMBSlave *slave) : m_url(url) , m_loop(loop) , m_slave(slave) { } SMBCDiscoverer::~SMBCDiscoverer() { if (m_dirFd > 0) { smbc_closedir(m_dirFd); } } void SMBCDiscoverer::start() { queue(); } bool SMBCDiscoverer::discoverNextFileInfo() { #ifdef HAVE_READDIRPLUS2 // Readdirplus2 dir/file listing. Becomes noop when at end of data associated with dirfd. // If readdirplus2 isn't available the regular dirent listing is done. // readdirplus2 improves performance by giving us a stat without separate call (Samba>=4.12) struct stat st; const struct libsmb_file_info *fileInfo = smbc_readdirplus2(m_dirFd, &st); if (fileInfo) { const QString name = QString::fromUtf8(fileInfo->name); qCDebug(KIO_SMB_LOG) << "fileInfo" << "name:" << name; if (name == ".") { return true; } else if (name == "..") { m_dirWasRoot = false; return true; } UDSEntry entry; entry.reserve(5); // Minimal size. stat will set at least 4 fields. entry.fastInsert(KIO::UDSEntry::UDS_NAME, name); m_url.addPath(name); m_slave->statToUDSEntry(m_url, st, entry); // won't produce useful error emit newDiscovery(Discovery::Ptr(new SMBCDiscovery(entry))); m_url.cdUp(); return true; } #endif // HAVE_READDIRPLUS2 return false; } void SMBCDiscoverer::discoverNext() { // Poor man's concurrency. smbc isn't thread safe so we'd hold up other // discoverers until we are done. While that will likely happen anyway // because smbc_opendir (usually?) blocks until it actually has all // the data to loop on, meaning the actual looping after open is fairly // fast. Even so, there's benefit in letting other discoverers do // their work in the meantime because they may do more atomic // requests that are async and can take a while due to network latency. // To get somewhat reasonable behavior we simulate an async smbc discovery // by posting loop events to the eventloop and each loop run we process // a single dirent. // This effectively unblocks the eventloop between iterations. // Once we are out of entries this discoverer is considered finished. // Always queue a new iteration when returning so we don't forget to. auto autoQueue = qScopeGuard([this] { queue(); }); if (m_dirFd == -1) { init(); Q_ASSERT(m_dirFd || m_finished); return; } if (discoverNextFileInfo()) { return; } qCDebug(KIO_SMB_LOG) << "smbc_readdir "; struct smbc_dirent *dirp = smbc_readdir(m_dirFd); if (dirp == nullptr) { qCDebug(KIO_SMB_LOG) << "done with smbc"; stop(); return; } const QString name = QString::fromUtf8(dirp->name); // We cannot trust dirp->commentlen has it might be with or without the NUL character // See KDE bug #111430 and Samba bug #3030 const QString comment = QString::fromUtf8(dirp->comment); qCDebug(KIO_SMB_LOG) << "dirent " << "name:" << name << "comment:" << comment << "type:" << dirp->smbc_type; UDSEntry entry; // Minimal potential size. The actual size depends on this function, // possibly the stat function, and lastly the Discovery objects themselves. // The smallest will be a ShareDiscovery with 5 fields. entry.reserve(5); entry.fastInsert(KIO::UDSEntry::UDS_NAME, name); entry.fastInsert(KIO::UDSEntry::UDS_COMMENT, comment); // Ensure system shares are marked hidden. if (name.endsWith(QLatin1Char('$'))) { entry.fastInsert(KIO::UDSEntry::UDS_HIDDEN, 1); } #if !defined(HAVE_READDIRPLUS2) // . and .. are always of the dir type so they are of no consequence outside // actual dir listing and that'd be done by readdirplus2 already if (name == ".") { // Skip the "." entry // Mind the way m_currentUrl is handled in the loop } else if (name == "..") { m_dirWasRoot = false; } else if (dirp->smbc_type == SMBC_FILE || dirp->smbc_type == SMBC_DIR) { // Set stat information m_url.addPath(name); const int statErr = m_slave->browse_stat_path(m_url, entry); if (statErr) { if (statErr == ENOENT || statErr == ENOTDIR) { m_slave->reportWarning(m_url, statErr); } } else { emit newDiscovery(Discovery::Ptr(new SMBCDiscovery(entry))); } m_url.cdUp(); } #endif // HAVE_READDIRPLUS2 if (dirp->smbc_type == SMBC_SERVER) { emit newDiscovery(Discovery::Ptr(new SMBCServerDiscovery(entry))); } else if (dirp->smbc_type == SMBC_FILE_SHARE) { emit newDiscovery(Discovery::Ptr(new SMBCShareDiscovery(entry))); } else if (dirp->smbc_type == SMBC_WORKGROUP) { emit newDiscovery(Discovery::Ptr(new SMBCWorkgroupDiscovery(entry))); } else { qCDebug(KIO_SMB_LOG) << "SMBC_UNKNOWN :" << name; } } void SMBCDiscoverer::customEvent(QEvent *event) { if (event->type() == LoopEvent) { if (!m_finished) { discoverNext(); } return; } QObject::customEvent(event); } void SMBCDiscoverer::stop() { m_finished = true; emit finished(); } bool SMBCDiscoverer::isFinished() const { return m_finished; } bool SMBCDiscoverer::dirWasRoot() const { return m_dirWasRoot; } int SMBCDiscoverer::error() const { return m_error; } void SMBCDiscoverer::init() { Q_ASSERT(m_dirFd < 0); m_dirFd = smbc_opendir(m_url.toSmbcUrl()); if (m_dirFd >= 0) { m_error = 0; } else { m_error = errno; stop(); } qCDebug(KIO_SMB_LOG) << "open" << m_url.toSmbcUrl() << "url-type:" << m_url.getType() << "dirfd:" << m_dirFd << "errNum:" << m_error; return; } void SMBCDiscoverer::queue() { if (m_finished) { return; } // Queue low priority events. For server discovery (that is: other discoverers run as well) // we want the modern discoverers to be peferred. For other discoveries only // SMBC is running on the loop and so the priority has no negative impact. QCoreApplication::postEvent(this, new QEvent(LoopEvent), Qt::LowEventPriority); } diff --git a/smb/smburl.cpp b/smb/smburl.cpp index 21aa914b..8203103f 100644 --- a/smb/smburl.cpp +++ b/smb/smburl.cpp @@ -1,213 +1,189 @@ -///////////////////////////////////////////////////////////////////////////// -// -// Project: SMB kioslave for KDE2 -// -// File: smburl.cpp -// -// Abstract: Utility class implementation used by SMBSlave -// -// Author(s): Matthew Peterson -// -//--------------------------------------------------------------------------- -// -// Copyright (c) 2000 Caldera Systems, Inc. -// Copyright (c) 2020 Harald Sitter -// -// 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.1 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 Lesser General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program; see the file COPYING. If not, please obtain -// a copy from https://www.gnu.org/copyleft/gpl.html -// -///////////////////////////////////////////////////////////////////////////// +/* + SPDX-License-Identifier: GPL-2.0-or-later + SPDX-FileCopyrightText: 2000 Caldera Systems, Inc. + SPDX-FileCopyrightText: 2020 Harald Sitter + SPDX-FileContributor: Matthew Peterson +*/ #include "smburl.h" #include "smb-logsettings.h" #include #include #include #include #include SMBUrl::SMBUrl(const QUrl &kurl) : QUrl(kurl) { // We treat cifs as an alias but need to translate it to smb. // https://bugs.kde.org/show_bug.cgi?id=327295 // It's not IANA registered and also libsmbc internally expects // smb URIs so we do very broadly coerce cifs to smb. // Also see SMBSlave::checkURL. if (scheme() == "cifs") { setScheme("smb"); } updateCache(); } SMBUrl::SMBUrl() = default; SMBUrl::SMBUrl(const SMBUrl &other) = default; SMBUrl::~SMBUrl() = default; SMBUrl &SMBUrl::operator=(const SMBUrl &) = default; void SMBUrl::addPath(const QString &filedir) { if (path().length() > 0 && path().at(path().length() - 1) != QLatin1Char('/')) { QUrl::setPath(path() + QLatin1Char('/') + filedir); } else { QUrl::setPath(path() + filedir); } updateCache(); } void SMBUrl::cdUp() { setUrl(KIO::upUrl(*this).url()); updateCache(); } void SMBUrl::updateCache() { QUrl::setPath(QDir::cleanPath(path())); // SMB URLs are UTF-8 encoded qCDebug(KIO_SMB_LOG) << "updateCache " << QUrl::path(); QUrl sambaUrl(*this); const QHostAddress address(sambaUrl.host()); switch (address.protocol()) { case QAbstractSocket::IPv6Protocol: { // Convert to Windows IPv6 literal to bypass limitations in samba. // https://bugzilla.samba.org/show_bug.cgi?id=14297 // https://docs.microsoft.com/en-us/windows/win32/api/winnetwk/nf-winnetwk-wnetaddconnection2a // https://devblogs.microsoft.com/oldnewthing/20100915-00/?p=12863 // https://www.samba.org/~idra/code/nss-ipv6literal/README.html // https://ipv6-literal.com QString literal = address.toString(); literal.replace(':', '-'); // address literal.replace('%', 's'); // scope if (literal.front() == '-') { // Special prefix for [::f] so it doesn't start with a dash. literal.prepend('0'); } if (literal.back() == '-') { // Special suffix, also cannot end with a dash. literal.append('0'); } literal += ".ipv6-literal.net"; // reserved host host qCDebug(KIO_SMB_LOG) << "converting IPv6 to literal " << host() << literal; sambaUrl.setHost(literal); break; } case QAbstractSocket::IPv4Protocol: case QAbstractSocket::AnyIPProtocol: case QAbstractSocket::UnknownNetworkLayerProtocol: break; } // NetBios workgroup names may contain characters that QUrl will not // allow in a host. Yet the SMB URI requires us to have the workgroup // in the host field when browsing a workgroup. // As a hacky workaround we'll not set a host but use a query param // when encountering a workgroup that causes QUrl to error out. // For libsmbc we then need to translate the query back to SMB URI. // Since this is super daft string construction it will doubltlessly // be imperfect and so we do still prefer deferring the string // construction to QUrl whenever possible. // https://support.microsoft.com/en-gb/help/909264/naming-conventions-in-active-directory-for-computers-domains-sites-and // https://bugs.kde.org/show_bug.cgi?id=204423 // // Should we ever stop supporting workgroup browsing this entire // hack can be removed. QUrlQuery query(sambaUrl); const QString workgroup = query.queryItemValue("kio-workgroup"); if (workgroup.isEmpty()) { // If we don't have a hack to apply we can simply defer to QUrl if (sambaUrl.url() == "smb:/") { m_surl = "smb://"; } else { m_surl = sambaUrl.toString(QUrl::PrettyDecoded).toUtf8(); } } else { // If we have a workgroup hack to apply we need to manually construct // the stringy URI. query.removeQueryItem("kio-workgroup"); sambaUrl.setQuery(query); QString url; url = "smb://"; if (!sambaUrl.userInfo().isEmpty()) { url += sambaUrl.userInfo() + "@"; } url += workgroup; // Workgroups can have ports per the IANA definition of smb. if (sambaUrl.port() != -1) { url += ':' + QString::number(sambaUrl.port()); } // Make sure to only use clear paths. libsmbc is allergic to excess slashes. QString path('/'); if (!sambaUrl.host().isEmpty()) { path += sambaUrl.host(); } if (!sambaUrl.path().isEmpty()) { path += sambaUrl.path(); } url += QDir::cleanPath(path); if (!sambaUrl.query().isEmpty()) { url += '?' + sambaUrl.query(); } if (!sambaUrl.fragment().isEmpty()) { url += '#' + sambaUrl.fragment(); } m_surl = QUrl(url).toString(QUrl::PrettyDecoded).toUtf8(); } m_type = SMBURLTYPE_UNKNOWN; // update m_type (void)getType(); } SMBUrlType SMBUrl::getType() const { if (m_type != SMBURLTYPE_UNKNOWN) return m_type; if (scheme() != "smb") { m_type = SMBURLTYPE_UNKNOWN; return m_type; } if (path().isEmpty() || path(QUrl::FullyDecoded) == "/") { if (host().isEmpty() && !query().contains("kio-workgroup")) m_type = SMBURLTYPE_ENTIRE_NETWORK; else m_type = SMBURLTYPE_WORKGROUP_OR_SERVER; return m_type; } // Check for the path if we get this far m_type = SMBURLTYPE_SHARE_OR_PATH; return m_type; } SMBUrl SMBUrl::partUrl() const { if (m_type == SMBURLTYPE_SHARE_OR_PATH && !fileName().isEmpty()) { SMBUrl url(*this); url.setPath(path() + QLatin1String(".part")); return url; } return SMBUrl(); } diff --git a/smb/transfer.cpp b/smb/transfer.cpp index 43eb2860..b8522033 100644 --- a/smb/transfer.cpp +++ b/smb/transfer.cpp @@ -1,105 +1,105 @@ /* - SPDX-FileCopyrightText: 2020 Harald Sitter SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL + SPDX-FileCopyrightText: 2020 Harald Sitter */ #include "transfer.h" #include TransferSegment::TransferSegment(const off_t fileSize) : buf(segmentSizeForFileSize(fileSize)) { } off_t TransferSegment::segmentSizeForFileSize(const off_t fileSize_) { const off_t fileSize = qMax(0, fileSize_); // read() internally splits our read requests into multiple server // requests and then assembles the responses into our buffer. // The larger the chunks we request the better the performance. // At the same time we'll want a semblence of progress reporting // and also not eat too much RAM. It's a balancing act :| off_t segmentSize = c_minSegmentSize; // The segment size is largely arbitrary and sacrifices better throughput for // greater memory use. // This only goes up to a maxiumum because bigger transfer blobs directly // translate to more RAM use. Mind that the effective RAM use will // be (segmentSize * (segments + 1)). The +1 is because smbc internally will also // allocate up to a full segment for one read() call. // // Unfortunately we have no way of knowing what size smbc will use for the // network requests, so we can't use a multiple of that. Which means we'll // almost never reach best performance. // // TODO: perhaps it would actually make sense to read at a multiple of // the target drive's block size? const off_t idealSegmentSize = qMin(fileSize / 50, c_maxSegmentSize); segmentSize = qMax(segmentSize, idealSegmentSize); // If the segment size is larger than the file size it appears we can // actually degrade performance, so pick the smaller of the two. if (fileSize != 0) { segmentSize = qMin(segmentSize, fileSize); } return segmentSize; } TransferRingBuffer::TransferRingBuffer(const off_t fileSize) { for (size_t i = 0; i < m_capacity; ++i) { m_buffer[i] = std::unique_ptr(new TransferSegment(fileSize)); } } TransferSegment *TransferRingBuffer::pop() { std::unique_lock lock(m_mutex); while (head == tail) { if (!m_done) { m_cond.wait(lock); } else { return nullptr; } } auto segment = m_buffer[tail].get(); m_cond.notify_all(); return segment; } void TransferRingBuffer::unpop() { std::unique_lock lock(m_mutex); tail = ++tail % m_capacity; m_cond.notify_all(); } TransferSegment *TransferRingBuffer::nextFree() { // This does not require synchronization. As soon // as we pushed the last item we gained exclusive lock // on the new item. m_cond.notify_all(); return m_buffer[head].get(); } void TransferRingBuffer::push() { const auto newHead = (head + 1) % m_capacity; std::unique_lock lock(m_mutex); while (newHead == tail) { // do not move to the item the reading thread is on m_cond.wait(lock); } head = newHead; m_cond.notify_all(); } void TransferRingBuffer::done() { std::unique_lock lock(m_mutex); m_done = true; m_cond.notify_all(); } diff --git a/smb/transfer.h b/smb/transfer.h index f533d54c..b5ace647 100644 --- a/smb/transfer.h +++ b/smb/transfer.h @@ -1,75 +1,75 @@ /* - SPDX-FileCopyrightText: 2020 Harald Sitter SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL + SPDX-FileCopyrightText: 2020 Harald Sitter */ #ifndef TRANSFER_H #define TRANSFER_H #include #include #include #include constexpr off_t c_minSegmentSize = 64 * 1024; // minimal size on stack constexpr off_t c_maxSegmentSize = 4L * 1024 * 1024; // 4MiB is the largest request we make struct TransferSegment { explicit TransferSegment(const off_t fileSize); ssize_t size = 0; // current size (i.e. the size that was put into buf) QVarLengthArray buf; // data buffer, only filled up to size! private: static off_t segmentSizeForFileSize(const off_t fileSize_); }; // Naive ring buffer. // Segment instances are held in the buffer, i.e. only alloc'd once at // beginning of the operation. Kind of a mix between ring and pool. // // The popping thread cannot pop while the pushing thread is still on // an element. As such we need at least 3 elements to prevent dead locks. class TransferRingBuffer { public: // fileSize is the stat'd file size of the source file. explicit TransferRingBuffer(const off_t fileSize_); ~TransferRingBuffer() = default; // Pops an item into the pull thread. This blocks // when the push thread is also currently on that index. // This can return nullptr if the push thread set the done state. // @note once done unpop() needs calling TransferSegment *pop(); // Frees the item used by the pull thread. So it may be used by the // push thread. void unpop(); // Simply returns a ptr to the item the current push thread marker is // at. i.e. the item "locked" for reading. // @note once done push() needs calling TransferSegment *nextFree(); // Pushes ahead from the item obtained by nextFree. // This effectively allows the pull thread to pop() this item again. void push(); // Only called by push thread to mark the buffer done and wake waiting // threads. void done(); private: bool m_done = false; std::mutex m_mutex; std::condition_variable m_cond; static const size_t m_capacity = 4; std::array, m_capacity> m_buffer; size_t head = 0; // index of push thread (prevents pop() from pull thread) size_t tail = 0; // index of pull thread (prevents push() from push thread) }; #endif // TRANSFER_H diff --git a/smb/wsdiscoverer.cpp b/smb/wsdiscoverer.cpp index 9123c56b..4f81708f 100644 --- a/smb/wsdiscoverer.cpp +++ b/smb/wsdiscoverer.cpp @@ -1,331 +1,316 @@ /* - Copyright 2019-2020 Harald Sitter - - This program is free software; you can redistribute it and/or - modify it under the terms of the GNU General Public License as - published by the Free Software Foundation; either version 3 of - the License or any later version accepted by the membership of - KDE e.V. (or its successor approved by the membership of KDE - e.V.), which shall act as a proxy defined in Section 14 of - version 3 of the license. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . + SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL + SPDX-FileCopyrightText: 2019-2020 Harald Sitter */ #include "wsdiscoverer.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include "kio_smb.h" // Publication service data resolver! // Specifically we'll ask the endpoint for PBSData via ws-transfer/Get. // The implementation is the bare minimum for our purposes! // https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-pbsd class PBSDResolver : public QObject { Q_OBJECT signals: void resolved(Discovery::Ptr discovery); public: /** * @param endpointUrl valid xaddr as advertised over ws-discovery (http://$ip/$referenceUuid) * @param destination endpoint reference urn as sent over ws-discovery ($referenceUuid) */ PBSDResolver(const QUrl &endpointUrl, const QString &destination, QObject *parent = nullptr) : QObject(parent) , m_endpointUrl(endpointUrl) , m_destination(destination) { } static QString nameFromComputerInfo(const QString &info) { // NB: spec says to use \ or / based on context, but in reality they are used // interchangibly in implementations. static QRegularExpression domainExpression("(?.+)[\\/]Domain:(?.+)"); static QRegularExpression workgroupExpression("(?.+)[\\/]Workgroup:(?.+)"); static QRegularExpression notJoinedExpression("(?.+)[\\/]NotJoined"); // We don't do anything with WG or domain info because windows10 doesn't seem to either. const auto joinedMatch = notJoinedExpression.match(info); if (joinedMatch.hasMatch()) { return joinedMatch.captured("name"); } const auto domainMatch = domainExpression.match(info); if (domainMatch.hasMatch()) { return domainMatch.captured("name"); } const auto workgroupMatch = workgroupExpression.match(info); if (workgroupMatch.hasMatch()) { return workgroupMatch.captured("name"); } return info; } // This must always set m_discovery and it must also time out on its own! void run() { // NB: when windows talks to windows they use lms:LargeMetadataSupport we probably don't // need this for the data we want, so it's left out. The actual messagse a windows // machine creates would be using "http://schemas.microsoft.com/windows/lms/2007/08" // as messageNamespace and set an additional header on the message. // https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-dpwssn/f700463d-cbbf-4545-ab47-b9a6fbf1ac7b KDSoapClientInterface client(m_endpointUrl.toString(), QStringLiteral("http://schemas.xmlsoap.org/ws/2004/09/transfer")); client.setSoapVersion(KDSoapClientInterface::SoapVersion::SOAP1_2); client.setTimeout(8000); KDSoapMessage message; KDSoapMessageAddressingProperties addressing; addressing.setAddressingNamespace(KDSoapMessageAddressingProperties::Addressing200408); addressing.setAction(QStringLiteral("http://schemas.xmlsoap.org/ws/2004/09/transfer/Get")); addressing.setMessageID(QStringLiteral("urn:uuid:") + QUuid::createUuid().toString(QUuid::WithoutBraces)); addressing.setDestination(m_destination); addressing.setReplyEndpointAddress(KDSoapMessageAddressingProperties::predefinedAddressToString( KDSoapMessageAddressingProperties::Anonymous, KDSoapMessageAddressingProperties::Addressing200408)); addressing.setSourceEndpointAddress(QStringLiteral("urn:uuid:") + QUuid::createUuid().toString(QUuid::WithoutBraces)); message.setMessageAddressingProperties(addressing); QString computer; KDSoapMessage response = client.call(QString(), message); if (response.isFault()) { qCDebug(KIO_SMB_LOG) << "Failed to obtain PBSD response" << m_endpointUrl.host() << m_destination << response.arguments() << response.faultAsString(); // No return! We'd disqualify systems that do not implement pbsd. } else { // The response xml would be nesting Metdata%1", host); } else { // If we got a DNSSD name, use that, otherwise redirect to on-demand resolution. host = computer.endsWith(".local") ? computer : computer + ".kio-discovery-wsd"; } m_discovery.reset(new WSDiscovery(computer, host)); emit resolved(m_discovery); } private: const QUrl m_endpointUrl; const QString m_destination; Discovery::Ptr m_discovery; }; // Utilizes WSDiscoveryClient to probe and resolve WSD services. WSDiscoverer::WSDiscoverer() : m_client(new WSDiscoveryClient(this)) { connect(m_client, &WSDiscoveryClient::probeMatchReceived, this, &WSDiscoverer::matchReceived); connect(m_client, &WSDiscoveryClient::resolveMatchReceived, this, &WSDiscoverer::resolveReceived); // If we haven't had a probematch in some seconds there's likely no more replies // coming and all hosts are known. Naturally resolvers may still be running and // get blocked on during stop(). Resolvers themselves have a timeout via // kdsoap. // NB: only started after first match! If we have no matches the slave will // stop us eventually anyway. m_probeMatchTimer.setInterval(2000); m_probeMatchTimer.setSingleShot(true); connect(&m_probeMatchTimer, &QTimer::timeout, this, &WSDiscoverer::stop); } void WSDiscoverer::start() { m_client->start(); // We only want devices. // We technically would probably also want to filter pub:Computer. // But! I am not sure if e.g. a NAS would publish itself as computer. // https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-pbsd KDQName type("wsdp:Device"); type.setNameSpace("http://schemas.xmlsoap.org/ws/2006/02/devprof"); m_client->sendProbe({type}, {}); } void WSDiscoverer::stop() { m_startedTimer = true; disconnect(&m_probeMatchTimer); m_probeMatchTimer.stop(); maybeFinish(); } bool WSDiscoverer::isFinished() const { return m_startedTimer && !m_probeMatchTimer.isActive() && m_resolvers.count() == m_resolvedCount; } void WSDiscoverer::matchReceived(const WSDiscoveryTargetService &matchedService) { // (re)start match timer to finish-early if at all possible. m_probeMatchTimer.start(); m_startedTimer = true; if (matchedService.xAddrList().isEmpty()) { // Has no addresses -> needs resolving still m_client->sendResolve(matchedService.endpointReference()); return; } resolveReceived(matchedService); } void WSDiscoverer::resolveReceived(const WSDiscoveryTargetService &service) { // (re)start match timer to finish-early if at all possible. m_probeMatchTimer.start(); m_startedTimer = true; if (m_seenEndpoints.contains(service.endpointReference())) { return; } m_seenEndpoints << service.endpointReference(); QUrl addr; for (const auto &xAddr : service.xAddrList()) { // https://docs.microsoft.com/en-us/windows/win32/wsdapi/xaddr-validation-rules // "At least one IP address included in the XAddrs (or IP address resolved from // a hostname included in the XAddrs) must be on the same subnet as the adapter // over which the ProbeMatches or ResolveMatches message was received." const auto hostInfo = QHostInfo::fromName(xAddr.host()); if (hostInfo.error() == QHostInfo::NoError) { addr = xAddr; break; } } if (addr.isEmpty()) { qCWarning(KIO_SMB_LOG) << "Failed to resolve any WS transport address." << "This suggests that DNS resolution may be broken." << service.xAddrList(); return; } PBSDResolver *resolver = new PBSDResolver(addr, service.endpointReference(), this); connect(resolver, &PBSDResolver::resolved, this, [this](Discovery::Ptr discovery) { ++m_resolvedCount; emit newDiscovery(discovery); maybeFinish(); }); QTimer::singleShot(0, resolver, &PBSDResolver::run); m_resolvers << resolver; } void WSDiscoverer::maybeFinish() { if (isFinished()) { emit finished(); } } WSDiscovery::WSDiscovery(const QString &computer, const QString &remote) : m_computer(computer) , m_remote(remote) { } QString WSDiscovery::udsName() const { return m_computer; } KIO::UDSEntry WSDiscovery::toEntry() const { KIO::UDSEntry entry; entry.reserve(6); entry.fastInsert(KIO::UDSEntry::UDS_NAME, udsName()); entry.fastInsert(KIO::UDSEntry::UDS_FILE_TYPE, S_IFDIR); entry.fastInsert(KIO::UDSEntry::UDS_ACCESS, (S_IRUSR | S_IXUSR | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH)); entry.fastInsert(KIO::UDSEntry::UDS_ICON_NAME, "network-server"); QUrl u; u.setScheme(QStringLiteral("smb")); u.setHost(m_remote); u.setPath("/"); // https://bugs.kde.org/show_bug.cgi?id=388922 entry.fastInsert(KIO::UDSEntry::UDS_URL, u.url()); entry.fastInsert(KIO::UDSEntry::UDS_MIME_TYPE, QStringLiteral("application/x-smb-server")); return entry; } #include "wsdiscoverer.moc" diff --git a/smb/wsdiscoverer.h b/smb/wsdiscoverer.h index 840366c2..20f230c7 100644 --- a/smb/wsdiscoverer.h +++ b/smb/wsdiscoverer.h @@ -1,77 +1,62 @@ /* - Copyright 2019-2020 Harald Sitter - - This program is free software; you can redistribute it and/or - modify it under the terms of the GNU General Public License as - published by the Free Software Foundation; either version 3 of - the License or any later version accepted by the membership of - KDE e.V. (or its successor approved by the membership of KDE - e.V.), which shall act as a proxy defined in Section 14 of - version 3 of the license. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . + SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL + SPDX-FileCopyrightText: 2019-2020 Harald Sitter */ #ifndef WSDISCOVERER_H #define WSDISCOVERER_H #include "discovery.h" #include #include class WSDiscoveryClient; class WSDiscoveryTargetService; class PBSDResolver; namespace KIO { class UDSEntry; } class WSDiscovery : public Discovery { const QString m_computer; const QString m_remote; public: WSDiscovery(const QString &computer, const QString &remote); QString udsName() const override; KIO::UDSEntry toEntry() const override; }; class WSDiscoverer : public QObject, public Discoverer { Q_OBJECT public: WSDiscoverer(); void start() override; bool isFinished() const override; signals: void newDiscovery(Discovery::Ptr discovery) override; void finished() override; private slots: void matchReceived(const WSDiscoveryTargetService &matchedService); void resolveReceived(const WSDiscoveryTargetService &matchedService); private: void stop() override; void maybeFinish(); WSDiscoveryClient *m_client = nullptr; bool m_startedTimer = false; QTimer m_probeMatchTimer; QStringList m_seenEndpoints; QList m_resolvers; int m_resolvedCount = 0; }; #endif // WSDISCOVERER_H