1 /*---------------------------------------------------------------------\
3 | |__ / \ / / . \ . \ |
8 \---------------------------------------------------------------------*/
9 /** \file zypp/media/MediaSMB.cc
16 #include "zypp/base/Logger.h"
17 #include "zypp/base/Gettext.h"
18 #include "zypp/TmpPath.h"
19 #include "zypp/KVMap.h"
20 #include "zypp/media/Mount.h"
21 #include "zypp/media/MediaUserAuth.h"
22 #include "zypp/media/CredentialManager.h"
23 #include "zypp/ZYppCallbacks.h"
25 #warning FIXME: get rid of this dependency on Target
26 #include "zypp/ZYppFactory.h" // for target->root()
27 #include "zypp/Target.h" // for zypp->target->root()
29 #include "zypp/media/MediaSMB.h"
31 #include <sys/types.h>
32 #include <sys/mount.h>
41 /******************************************************************
44 ** FUNCTION NAME : getShare
45 ** FUNCTION TYPE : inline Pathname
47 ** Get the 1st path component (CIFS share name).
49 inline string getShare( Pathname spath_r )
51 if ( spath_r.empty() )
54 string share( spath_r.absolutename().asString() );
55 string::size_type sep = share.find( "/", 1 );
56 if ( sep == string::npos )
57 share = share.erase( 0, 1 ); // nothing but the share name in spath_r
59 share = share.substr( 1, sep-1 );
61 // deescape %2f in sharename
62 while ( (sep = share.find( "%2f" )) != string::npos ) {
63 share.replace( sep, 3, "/" );
69 /******************************************************************
72 ** FUNCTION NAME : stripShare
73 ** FUNCTION TYPE : inline Pathname
75 ** Strip off the 1st path component (CIFS share name).
77 inline Pathname stripShare( Pathname spath_r )
79 if ( spath_r.empty() )
82 string striped( spath_r.absolutename().asString() );
83 string::size_type sep = striped.find( "/", 1 );
84 if ( sep == string::npos )
85 return "/"; // nothing but the share name in spath_r
87 return striped.substr( sep );
90 ///////////////////////////////////////////////////////////////////
92 // CLASS NAME : MediaSMB
94 ///////////////////////////////////////////////////////////////////
96 ///////////////////////////////////////////////////////////////////
99 // METHOD NAME : MediaSMB::MediaSMB
100 // METHOD TYPE : Constructor
104 MediaSMB::MediaSMB( const Url & url_r,
105 const Pathname & attach_point_hint_r )
106 : MediaHandler( url_r, attach_point_hint_r,
107 stripShare( url_r.getPathName() ), // urlpath WITHOUT share name at attachpoint
108 false ) // does_download
111 MIL << "MediaSMB::MediaSMB(" << url_r << ", " << attach_point_hint_r << ")" << endl;
114 ///////////////////////////////////////////////////////////////////
117 // METHOD NAME : MediaSMB::attachTo
118 // METHOD TYPE : PMError
120 * Asserted that not already attached, and attachPoint is a directory.
122 * Authentication: credentials can be specified in the following few ways
123 * (the first has the highest preference).
124 * - URL username:password
125 * - mountoptions URL query parameter (see man mount.cifs)
126 * - CredentialManager - either previously saved credentials will be used
127 * or the user will be promted for them via AuthenticationReport callback.
129 * \note The implementation currently serves both, "smb" and
130 * and "cifs" URL's, but passes "cifs" to the mount command
133 void MediaSMB::attachTo(bool next)
135 if(_url.getHost().empty())
136 ZYPP_THROW(MediaBadUrlEmptyHostException(_url));
138 ZYPP_THROW(MediaNotSupportedException(_url));
141 path += _url.getHost() + "/" + getShare( _url.getPathName() );
143 MediaSourceRef media( new MediaSource( _vfstype, path));
144 AttachedMedia ret( findAttachedMedia( media));
146 if( ret.mediaSource &&
148 !ret.attachPoint->empty())
150 DBG << "Using a shared media "
151 << ret.mediaSource->name
153 << ret.attachPoint->path
157 setAttachPoint(ret.attachPoint);
158 setMediaSource(ret.mediaSource);
162 std::string mountpoint = attachPoint().asString();
163 if( !isUseableAttachPoint(attachPoint()))
165 mountpoint = createAttachPoint().asString();
166 if( mountpoint.empty())
167 ZYPP_THROW( MediaBadAttachPointException(url()));
168 setAttachPoint( mountpoint, true);
172 CredentialManager cm;
174 Mount::Options options( _url.getQueryParam("mountoptions") );
175 string username = _url.getUsername();
176 string password = _url.getPassword();
178 options["guest"]; // prevent smbmount from asking for password
180 if ( ! options.has( "rw" ) ) {
184 // look for a workgroup
186 string workgroup = _url.getQueryParam("workgroup");
187 if ( !workgroup.empty() )
188 options["workgroup"] = workgroup;
191 // extract 'username', do not overwrite any _url.username
193 Mount::Options::iterator toEnv;
194 toEnv = options.find("username");
195 if ( toEnv != options.end() ) {
196 if ( username.empty() )
197 username = toEnv->second;
198 options.erase( toEnv );
201 toEnv = options.find("user"); // actually cifs specific
202 if ( toEnv != options.end() ) {
203 if ( username.empty() )
204 username = toEnv->second;
205 options.erase( toEnv );
208 // extract 'password', do not overwrite any _url.password
210 toEnv = options.find("password");
211 if ( toEnv != options.end() ) {
212 if ( password.empty() )
213 password = toEnv->second;
214 options.erase( toEnv );
217 toEnv = options.find("pass"); // actually cifs specific
218 if ( toEnv != options.end() ) {
219 if ( password.empty() )
220 password = toEnv->second;
221 options.erase( toEnv );
224 if ( username.empty() || password.empty() )
226 AuthData_Ptr c = cm.getCred(_url);
229 username = c->username();
230 password = c->password();
234 bool firstTry = true;
235 bool authRequired = false;
237 do // repeat this while the mount returns "Permission denied" error
239 // get credentials from authenicate()
242 username = authdata.username();
243 password = authdata.password();
246 // pass 'username' and 'password' via environment
247 Mount::Environment environment;
248 if ( !username.empty() )
249 environment["USER"] = username;
250 if ( !password.empty() )
251 environment["PASSWD"] = password;
253 //////////////////////////////////////////////////////
254 // In case we need a tmpfile, credentials will remove
255 // it in it's destructor after the mout call below.
256 filesystem::TmpPath credentials;
257 if ( !username.empty() || !password.empty() )
259 filesystem::TmpFile tmp;
260 ofstream outs( tmp.path().asString().c_str() );
261 outs << "username=" << username << endl;
262 outs << "password=" << password << endl;
266 options["credentials"] = credentials.path().asString();
269 //////////////////////////////////////////////////////
273 mount.mount( path, mountpoint, _vfstype,
274 options.asString(), environment );
275 setMediaSource(media);
278 catch (const MediaMountException & e)
282 if ( e.mountError() == "Permission denied" )
283 authRequired = authenticate( authdata, firstTry );
290 while ( authRequired );
292 // wait for /etc/mtab update ...
293 // (shouldn't be needed)
296 while( !(mountsucceeded=isAttached()) && --limit)
299 if ( !mountsucceeded )
301 setMediaSource(MediaSourceRef());
304 mount.umount(attachPoint().asString());
306 catch (const MediaException & excpt_r)
308 ZYPP_CAUGHT(excpt_r);
310 ZYPP_THROW(MediaMountException(
311 "Unable to verify that the media was mounted",
317 ///////////////////////////////////////////////////////////////////
319 // METHOD NAME : MediaSMB::isAttached
320 // METHOD TYPE : bool
322 // DESCRIPTION : Override check if media is attached.
325 MediaSMB::isAttached() const
327 return checkAttached(true);
330 ///////////////////////////////////////////////////////////////////
333 // METHOD NAME : MediaSMB::releaseFrom
334 // METHOD TYPE : PMError
336 // DESCRIPTION : Asserted that media is attached.
338 void MediaSMB::releaseFrom( const std::string & ejectDev )
341 mount.umount(attachPoint().asString());
344 ///////////////////////////////////////////////////////////////////
346 // METHOD NAME : MediaSMB::getFile
347 // METHOD TYPE : PMError
349 // DESCRIPTION : Asserted that media is attached.
351 void MediaSMB::getFile (const Pathname & filename) const
353 MediaHandler::getFile( filename );
356 ///////////////////////////////////////////////////////////////////
358 // METHOD NAME : MediaSMB::getDir
359 // METHOD TYPE : PMError
361 // DESCRIPTION : Asserted that media is attached.
363 void MediaSMB::getDir( const Pathname & dirname, bool recurse_r ) const
365 MediaHandler::getDir( dirname, recurse_r );
368 ///////////////////////////////////////////////////////////////////
371 // METHOD NAME : MediaSMB::getDirInfo
372 // METHOD TYPE : PMError
374 // DESCRIPTION : Asserted that media is attached and retlist is empty.
376 void MediaSMB::getDirInfo( std::list<std::string> & retlist,
377 const Pathname & dirname, bool dots ) const
379 MediaHandler::getDirInfo( retlist, dirname, dots );
382 ///////////////////////////////////////////////////////////////////
385 // METHOD NAME : MediaSMB::getDirInfo
386 // METHOD TYPE : PMError
388 // DESCRIPTION : Asserted that media is attached and retlist is empty.
390 void MediaSMB::getDirInfo( filesystem::DirContent & retlist,
391 const Pathname & dirname, bool dots ) const
393 MediaHandler::getDirInfo( retlist, dirname, dots );
396 bool MediaSMB::getDoesFileExist( const Pathname & filename ) const
398 return MediaHandler::getDoesFileExist( filename );
401 bool MediaSMB::authenticate(AuthData & authdata, bool firstTry) const
403 //! \todo need a way to pass different CredManagerOptions here
404 Target_Ptr target = zypp::getZYpp()->getTarget();
405 CredentialManager cm(CredManagerOptions(target ? target->root() : ""));
407 // get stored credentials
408 AuthData_Ptr cmcred = cm.getCred(_url);
410 AuthData_Ptr smbcred;
411 smbcred.reset(new AuthData());
412 callback::SendReport<AuthenticationReport> auth_report;
414 // preset the username if present in current url
415 if (!_url.getUsername().empty() && firstTry)
416 smbcred->setUsername(_url.getUsername());
417 // if CM has found some credentials, preset the username from there
419 smbcred->setUsername(cmcred->username());
421 // indicate we have no good credentials from CM
424 string prompt_msg = str::form(
425 //!\todo add comma to the message for the next release
426 _("Authentication required for '%s'"), _url.asString().c_str());
429 if (auth_report->prompt(_url, prompt_msg, *smbcred))
431 DBG << "callback answer: retry" << endl
432 << "AuthData: " << *smbcred << endl;
434 if (smbcred->valid())
437 // if (credentials->username() != _url.getUsername())
438 // _url.setUsername(credentials->username());
440 * \todo find a way to save the url with changed username
441 * back to repoinfo or dont store urls with username
442 * (and either forbid more repos with the same url and different
443 * user, or return a set of credentials from CM and try them one
449 DBG << "callback answer: cancel" << endl;
451 // set username and password
454 authdata.setUsername(cmcred->username());
455 authdata.setPassword(cmcred->password());
457 // save the credentials
458 cmcred->setUrl(_url);