While testing #602520: Make FileTransfer form have one option for FTP (ftp_extension prefered)., I noticed that if you turn off the FTP extension in PHP (well, or you hack the code to pretend that you did), the Updater class can't update your site if your sites/default/modules directly doesn't already exist. It's currently assuming it can always chmod(), but that's not necessarily the case. We don't *need* to be attempting those chmod() calls, either. We can just try to mkdir(), and if it works, great. If not, we can bail at that point. Here's the relevant code:
includes/updater.inc, inside Updater::prepareInstallDirectory():
try {
$old_perms = substr(sprintf('%o', fileperms($parent_dir)), -4);
$filetransfer->chmod($parent_dir, 0755);
$filetransfer->createDirectory($directory);
$this->makeWorldReadable($filetransfer, $directory);
// Put the permissions back.
$filetransfer->chmod($parent_dir, intval($old_perms, 8));
}
catch (FileTransferException $e) {
$message = t($e->getMessage(), $e->arguments);
$throw_message = t('Unable to create %directory due to the following: %reason', array('%directory' => $install_location, '%reason' => $message));
throw new UpdaterException($throw_message);
}
...
(Note: the $throw_message inside that catch also contains a PHP notice, since $install_location is undefined and should really be $directory).
| Comment | File | Size | Author |
|---|---|---|---|
| #45 | 612546-45.death-to-FileTransferFTPWrapper.patch | 4.35 KB | dww |
| #37 | 612546-36.patch | 59.46 KB | dhthwy |
| #36 | 612546-36.patch | 59.46 KB | dhthwy |
| #34 | 612546-33.patch | 59.59 KB | dhthwy |
| #18 | ftp.patch | 56.75 KB | dhthwy |
Comments
Comment #1
dwwIn IRC just now, Bojhan posted a "WTF is this error?" style question and I'm pretty sure it's because he hit this bug. We can't ship 7.0 like this, so I'm bumping this to critical...
Comment #2
Bojhan commentedAn AJAX HTTP error occurred. HTTP Result Code: 200 Debugging information follows. Path: /authorize.php?batch=1&id=6&op=do StatusText: OK ResponseText: Warning: ftp_mkdir(): /sites/default/modules: No such file or directory in /includes/filetransfer/ftp.inc on line 129 Warning: Cannot modify header information - headers already sent by (output started at /includes/filetransfer/ftp.inc:129) in /bootstrap.inc on line 1003 {"status":true,"percentage":"100","message":"Completed 1 of 1.\u003cbr \/\u003e"}
I would imagine this beign a critical bug indeed, as otherwise we require another php exstention to exist.
Comment #3
jpmckinney commentedIt looks like prepareInstallDirectory doesn't create $directory (sites/all/modules) unless both the directory is missing and the parent directory is unwritable. But prepareInstallDirectory should also create the directory if the directory is missing and the parent directory is writable. I fixed this logic and I can no longer reproduce the error.
(I also replaced $install_location with $directory as mentioned by the OP).
Comment #4
jpmckinney commentedThe title for this issue should probably change to "prepareInstallDirectory doesn't create directory unless it's parent is unwritable", but as I'm not 100% sure I'm fixing the same problem brought up by the OP, I'm leaving it as-is.
Comment #5
jpmckinney commentedI can no longer replicate this issue at HEAD, with or without the above patch.
Comment #6
dww@jpmckinney: as per my original post, to reproduce this error, you either have to reconfigure PHP on your test site to disable the FTP extension and force it to use the FTP stream wrapper, or "hack the code to pretend that you did"...
Comment #7
jpmckinney commentedIn ftp.inc, I changed:
to:
Is that enough?
Comment #8
dwwYup, that should do. ;) Weird that it's magically working now -- I don't know of anything that changed that would have fixed this.
Comment #9
sunSo what's the status of this? Already fixed due to other changes?
Comment #10
catchComment #11
dwwHere's a screenshot of the final landing page on authorize.php if you do the hack in #7 and try to update a module. #fail. Still broken. I'll take a look at this right now to do my part on the actual critical queue...
Comment #12
jpmckinney commentedI can no longer make that landing page show up like that. There must be some other condition to reproduce the error.
Comment #13
dww@jpmckinney: Looking at the code, I don't see how that's possible. If you're using FileTransferFTPWrapper, it does *not* implement the FileTransferChmodInterface. When the Updater class calls makeWorldReadable(), that automatically calls FileTransfer::chmod(), which immediately throws an exception in this case. I don't see any way around that. The same is true with both install and update.
That said, I must say: crap. This stuff all totally sucks. One possibly different condition is that on my laptop, when I connect via ftp, the umask is set to 027, so things aren't world-readable that are uploaded by FTP. So, in that case, we fail. And, in fact, the code is already written in such a way as to try to just create the directory first if possible and only try the chmod() if we need it... so, the thing I proposed in the original post won't work here.
Looking at it, the chmod() logic is all fubar. I've got too many irons in the fire right now and am too distracted by a dozen other threads to deal with this right here. Maybe I'll be more inspired later tonight at the coder lounge. ;)
Comment #14
yesct commentedIn preparation for the new "major" issue priority, I'm tagging this (not actually critical) issue as priority-major.
See: http://drupal.org/node/669048#comment-3019824 (#669048: We should have four priorities not three (introduce a new 'major' priority)) for more information about the coming major issue priority.
Comment #15
dhthwy commentedPHP's FTP stream wrapper does not support chmod so you simply and absolutely _cannot_ use it for installing modules. This is why FileTransferFTPWrapper doesn't implement chmod. It can't. Why its even in Drupal is a mystery to me. FTP without chmod? Totally broken and useless.
Now sure it can create directories and files but the user will still be required to use an FTP client or their web hosts 'file manager' to set the appropriate permissions which defeats the whole purpose.
I propose to:
Not use the FTP stream wrapper for this and implement a pure PHP FTP client to use as an alternative for when PHP's FTP extension can't be used (Wordpress does this).
Comment #16
chx commentedWe have not added the raw socket based FTP implementation because it seemed a lot of work and it seemed since PHP5 you could do overwrites with the FTP stream wrapper and it seemed enough. Guess what? We were wrong. Happens. We should write / borrow from Joomla a socket based raw FTP implementation. Sucks!
Comment #17
dhthwy commentedTo elaborate on my "FTP without chmod? Totally broken and useless." remark:
In order for PHP's FTP stream wrapper to work for web browser based module installation you must be able to chmod the files it transfers in order for them to be read by Drupal. Most servers don't set the umask for new files to be world readable and world executable for directories. This is done for security reasons. For example say you have a private directory in your home directory called 'private'. You don't want files in this directory to be readable by everyone when you upload them via FTP. And an FTP client that doesn't support chmod is in my opinion broken and not very useful -- because I feel it is a critical feature.
I'll assign this issue to myself since I already have a pure PHP FTP client API implementation. My FTP class API mirrors PHP's FTP extension functions for the most part (function names, parameters and return values). There is also an option to return messages sent by the FTP server on failure instead of returning FALSE (so you can find out exactly why something failed). I don't know if I can get it worthy for Drupal but I'll try. Lots of work to do.
Comment #18
dhthwy commentedOk, finally, here is the patch I said I'd make. I held off on it for awhile.
The replaced FTP wrapper class uses the same code as used in the FTP extension class except that it calls methods in a new FTP class provided in the patch. There is an equivalent method for most of FTP extension's functions (the most important ones anyway). I'll provide an exact list of FTP functions supported in another post.
Comment #19
Anonymous (not verified) commentedsubscribe.
Comment #20
aspilicious commentedNice code comments!
Found a lot of trailing white spaces, you better find a tool or script to remove them, if you gonna do it manual here is a list with the problems I found!
trailing white space
(repeat review issue 12x)
Comment #21
sun@aspilicious: 1) Dreditor allows to annotate multiple code line selections. 2) In such cases, it's far more helpful to just clarify that you're facing the same review issue all over again in the patch, e.g., by using a "(and elsewhere)" prefix, or whatever else makes sense for you. But anyway, thanks for reviewing! :)
Comment #22
aspilicious commentedSun should I delete most of the lines to keep things readable?
Comment #23
dhthwy commentedlol you could've just said 'there's a ton of white spaces' , but thanks for taking the time to go thru all that wow.
feel free to nitpick, and I kinda suck at writing documentation so if anything doesn't make sense please let me know.
so this is both needs work and needs review I guess.
Comment #24
dhthwy commentedListed are the FTP extension functions and equivalent methods implemented by the class. Method parameters and return values are also equivalent for the most part, and any differences are noted.
Not implemented. Allocates space for a file upload. Most servers don't support this.
DrupalFTP::cdup()
DrupalFTP::chdir()
DrupalFTP::chmod()
DrupalFTP::logout()
DrupalFTP::connect() does not take a $timeout parameter as ftp_connect() does, instead the timeout is set as an option passed into the class constructor or DrupalFTP::setOptions().
DrupalFTP::delete()
Not implemented. I don't think most FTP servers support this.
Not implemented. Same as ftp_get() and ftp_put() except it takes an open file to save to when downloading, or read from when uploading.
DrupalFTP::get()
DrupalFTP::login()
Not implemented (yet). Gets a file's last modification time.
DrupalFTP::mkdir()
Not implemented. These are the extension's non blocking functions. Since stream_select() is broken for early PHP 5.2 versions the class can't reliability support non blocking IO.
DrupalFTP::nlist()
This tells the extension to use passive mode instead of active mode. The class always uses passive mode so there is no equivalent method.
DrupalFTP::put()
DrupalFTP::pwd()
DrupalFTP::logout()
Not implemented. From the PHP doc, this function: sends an arbitrary command to an FTP server.
DrupalFTP::rawList() -- an unparsed directory listing.
Class also provides DrupalFTP::getList() and DrupalFTP::stat() (better to use if the server supports it) which return parsed directory listings.
DrupalFTP::rename()
DrupalFTP::rmdir()
DrupalFTP::site()
DrupalFTP::size()
DrupalFTP::connect has a $secure parameter which turns on SSL for the control connection only.
DrupalFTP::sslEnableData() turns on SSL encryption for data connections. These are separate because data transfers may not work when encrypted due to FTP server configuration.
Not implemented (yet). Gets the FTP server's system type (Unix, Windows, etc). Don't think it'll have much use. One use for it would be in parsing a directory listing, since Windows has a different format than Unix, however the format used is really FTP server specific and not system specific. So going by the system type command likely won't be reliable.
Comment #25
dhthwy commentedBtw I haven't looked into writing tests for this and I'm not sure if it's even possible. It's possible if
a) The test servers all have an FTP server with the proper setup.
b) Every FTP server has the same username & password associated with the Drupal installation, or some way to get that information.
c) To test SSL, each FTP server has to be configured for SSL.
With that said, I've tested the class extensively myself, each and every method, with several FTP servers on Win and Unix.
In order to test this patch you need to enable the Update Manager module and patch your Drupal with the patch over at http://drupal.org/node/747252#comment-3188930
You'll also need to disable the FTP extension. Or hack includes/filetransfer/ftp.inc by changing FileTransferFTPExtension with FileTransferDrupalFTP (the easiest way, assuming stream_socket_client isn't being disallowed) on line 31.
After doing this just head on over to the 'Install a module' link or whatever it says at the top of the modules page.
And I'm not going to risk any further time on this patch until someone steps in and says it will be considered as a fix for this issue as I've already spent a lot of time documenting this just for Drupal.
Comment #26
marcingy commentedChanging to major as per tag.
Comment #27
dhthwy commentedooo I forgot this was tagged as major.
Since Drupal is close to a beta release and fixing this requires an API change/addition (either by removing the FTP stream wrapper, or by adding a pure-PHP FTP API), I don't see a fix for this getting into 7.
Comment #28
sunThis seems to be an important issue. Let's discuss possibilities for D7 a bit more.
Can't we test whether the FTP stream wrapper is available, and if not, dynamically load the proposed drop-in replacement?
Comment #29
mustanggb commentedTag update
Comment #30
dhthwy commented@sun
Did you mean provide a drop-in replacement for when the FTP extension isn't available? My patch does this.
Let me provide a brief overview:
The FTP stream wrapper doesn't support chmod (PHP limitation) which is really important for FTP (especially for web files) and the reason this issue exists.
The patch I provided gives Drupal a pure PHP FTP drop-in replacement for when the FTP extension isn't available. Both Joomla and Wordpress do this too. chx told me that they were going to write a pure PHP FTP implementation but they thought the FTP stream wrapper would suffice, which turned out to be wrong as he noted in #16.
Here's the current flow (does not take SSH into account, most users won't be able to use that):
If SuPHP is enabled PHP will be executed as the user's ID so Drupal will transfer the files locally using the FileTransferLocal class. SuPHP is enabled on many web hosts (particularly on shared hosting), but there's also many web hosts that do not use it (especially people with VPSs, dedicated servers).
If there's no SuPHP, it checks for the FTP extension, if that isn't enabled, it tries to use the FTP stream wrapper. Again there's many web hosts which have the FTP extension and many that do not. For those without the extension, it's broken.
So far dww and bohjan have been bitten by this, and surely there will be many many more people affected. My guess is that it's going to affect around 20-30% of users, and that's not a very small number to me.
Comment #31
sunSadly, many hosting providers still don't provide any means of SSH and just FTP. Oddly enough, at the same time, PHP's FTP extension is rarely seen. Based on my gut feeling, those percentage values are much higher. I suspect that the on-site module installer/updater won't be usable for many users without this patch. At the same time, I don't know of any contributed module that would be affected by this API addition (minimal change).
Comment #32
dhthwy commentedThanks for your input sun. It would be great if webchick or dries chimed in to let us know if they support this API addition. I found that I could write tests for the class, but it's going to eat lots of time so I'd rather write them after I know the class is going to get used by Drupal in some way, or maybe as a contrib module. In the meantime I'll fixup the whitespaces.
It's really hard to say how many users are going to be affected, but you're probably right in that the numbers are going to be higher than 20-30%. If that's the case then this is really critical (even if only 20% I think) since the major priority implies this only affects a small number of users.
Comment #33
Bojhan commentedComment #34
dhthwy commentedHopefully fixed all the white spaces. Added equivalent methods for the PHP extension's ftp_systype and ftp_mdtm functions and tweaked some areas of the documentation.
Comment #35
dhthwy commentedComment #36
dhthwy commentedComment #37
dhthwy commentedfixed a comment and white spaces.
Comment #38
tstoecklerMarking critical per #31 / #32
Comment #39
moshe weitzman commentedBack to major priority as it was for a while (using the tag)
Hosting providers that only provide ftp are forcing their clients to send their passwords in plain text. This is a host you want to move off of anyway. I'll bet that most of them default to php4 which won't run D7 anyway. Basically, we could even ship without FTP support. We don't want to, but thats a different matter.
Comment #40
dhthwy commentedmoshe, let's not forget that most FTP servers nowadays support SSL, and there are web hosts that do provide this as enabling it is just a matter of an SSL certificate and a slight configuration tweak. Most web hosts provide alternative means to manage files such as via a control panel too. In fact it is far more likely that a web host supports FTP-SSL plus the PHP openSSL extension than support for SCP _and_ the SSH PHP extension.
In any case, if the web host doesn't use suPHP then Drupal will need SCP access plus the PHP SSH extension in order to login securely. Frankly I don't know why FTP-SSL support wasn't added when the updator class was written if security was in mind whatsoever. Adding the code for this is trivial and minimal and something I considered doing, however, this issue has been waiting for committer feedback for over a month and it's getting later everyday so I'm not really interested in messing with this further.
So we need a patch that simply removes the FTP stream wrapper class since it won't work for reasons cited in above comments. Simple patch, perfect for someone to get their feet wet.
Comment #41
dwwAt DCSF webchick, Dries and I discussed this issue. Webchick even did a poll about it (via twitter and such, I don't have the links and I'm not on twitter) and the resounding answer was almost no webhosting companies that users replied to her poll about did *not* have the FTP extension complied into PHP. Therefore, they were willing to just drop the (obviously broken) FTP stream wrapper support and say that core only handles FTP if the PHP extension is there.
Therefore, this is perfectly correct:
"So we need a patch that simply removes the FTP stream wrapper class since it won't work for reasons cited in above comments. Simple patch, perfect for someone to get their feet wet."
In terms of adding FTP-SSL support, it'd be *fantastic* if someone wanted to get that working via a contrib module. That would both demonstrate that all this pluggable updater crap is actually fully pluggable. And, if the code works and is small enough, there's still a (small) chance that it could be quickly moved into core in time for 7.0.
Comment #42
dwwOh, sorry, I also need to reply to moshe #39:
Yes, FTP is insecure and it sucks. But, the target audience for the Update manager aren't people who are comfortable ssh'ing into their sites and managing their code via drush and version control. That's obviously the better solution. ;) But, it's not for everyone. The Update manager is meant to help people keep their sites up to date if they're *not* drushistas. If they happen to be on a host that only allows FTP access, that doesn't mean Drupal should throw them under the bus. It still appears to be a lot more common for a host to allow FTP access than ssh. In fact, if Update manager in core *didn't* support FTP, almost no one would be able to use it.
Comment #43
dhthwy commentedThank you very much dww for replying :)
I do think it would be a good idea to implement FTP-SSL. Turning it on with the PHP FTP extension is very simple, although before choosing to use it you'd want to check to ensure the server really does support SSL so it can fallback to plain-old FTP, that would probably be the bulk of the code, but it doesn't need much. On the other hand if we're relying on the FTP extension and it is true that most web hosts don't have it installed, then it is probably going to be largely a wasted effort.
With a pure PHP FTP implemention you don't need any FTP extension and you only need OpenSSL to use FTP-SSL, so it would be usable by a much much greater number of users, especially for newcomers that are probably gonna want to use it.
Comment #44
dwwSorry if I wasn't clear. webchick's poll indicates that *most* webhosts *do* have the FTP extension compiled into PHP.
I'm not sure we want to implement our own version of FTP. Drupal core is already huge and getting huger. Having our own copy "just in case" seems like a lot of work and code and potential for bugs/exploits for a relatively small group of people who want/need to use FTP but don't have access to a version of PHP with the FTP extension already enabled.
But, again, I'd be psyched to see a link to a D7 contrib project that implements FTP-SSL...
Comment #45
dwwOne could argue that we no longer need the complication of the split between the FileTransferFTP and FileTransferFTPExtension classes anymore, now that core only has a single FileTransferFTP subclass. If so, we need a bigger patch.
My test environment is currently broken. I'm rebuilding that now. So, hopefully I'll be able to test this more extensively later, but I wanted to at least get a patch up to see if the testbot finds any problems.
Comment #46
chx commentedSad but true
Comment #47
dries commentedCommitted to CVS HEAD. Thanks.