1 /*---------------------------------------------------------------------\
3 | |__ / \ / / . \ . \ |
8 \---------------------------------------------------------------------*/
9 /** \file zypp/media/MediaCIFS.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"
24 #include "zypp/ZConfig.h"
26 #include "zypp/media/MediaCIFS.h"
28 #include <sys/types.h>
29 #include <sys/mount.h>
38 /******************************************************************
41 ** FUNCTION NAME : getShare
42 ** FUNCTION TYPE : inline Pathname
44 ** Get the 1st path component (CIFS share name).
46 inline string getShare( Pathname spath_r )
48 if ( spath_r.empty() )
51 string share( spath_r.absolutename().asString() );
52 string::size_type sep = share.find( "/", 1 );
53 if ( sep == string::npos )
54 share = share.erase( 0, 1 ); // nothing but the share name in spath_r
56 share = share.substr( 1, sep-1 );
58 // deescape %2f in sharename
59 while ( (sep = share.find( "%2f" )) != string::npos ) {
60 share.replace( sep, 3, "/" );
66 /******************************************************************
69 ** FUNCTION NAME : stripShare
70 ** FUNCTION TYPE : inline Pathname
72 ** Strip off the 1st path component (CIFS share name).
74 inline Pathname stripShare( Pathname spath_r )
76 if ( spath_r.empty() )
79 string striped( spath_r.absolutename().asString() );
80 string::size_type sep = striped.find( "/", 1 );
81 if ( sep == string::npos )
82 return "/"; // nothing but the share name in spath_r
84 return striped.substr( sep );
87 ///////////////////////////////////////////////////////////////////
89 // CLASS NAME : MediaCIFS
91 ///////////////////////////////////////////////////////////////////
93 ///////////////////////////////////////////////////////////////////
96 // METHOD NAME : MediaCIFS::MediaCIFS
97 // METHOD TYPE : Constructor
101 MediaCIFS::MediaCIFS( const Url & url_r,
102 const Pathname & attach_point_hint_r )
103 : MediaHandler( url_r, attach_point_hint_r,
104 stripShare( url_r.getPathName() ), // urlpath WITHOUT share name at attachpoint
105 false ) // does_download
107 MIL << "MediaCIFS::MediaCIFS(" << url_r << ", " << attach_point_hint_r << ")" << endl;
110 ///////////////////////////////////////////////////////////////////
113 // METHOD NAME : MediaCIFS::attachTo
114 // METHOD TYPE : PMError
116 * Asserted that not already attached, and attachPoint is a directory.
118 * Authentication: credentials can be specified in the following few ways
119 * (the first has the highest preference).
120 * - URL username:password
121 * - mountoptions URL query parameter (see man mount.cifs)
122 * - CredentialManager - either previously saved credentials will be used
123 * or the user will be promted for them via AuthenticationReport callback.
125 * \note The implementation currently serves both, "smb" and
126 * and "cifs" URL's, but passes "cifs" to the mount command
129 void MediaCIFS::attachTo(bool next)
131 if(_url.getHost().empty())
132 ZYPP_THROW(MediaBadUrlEmptyHostException(_url));
134 ZYPP_THROW(MediaNotSupportedException(_url));
137 path += _url.getHost() + "/" + getShare( _url.getPathName() );
139 MediaSourceRef media( new MediaSource( "cifs", path));
140 AttachedMedia ret( findAttachedMedia( media));
142 if( ret.mediaSource &&
144 !ret.attachPoint->empty())
146 DBG << "Using a shared media "
147 << ret.mediaSource->name
149 << ret.attachPoint->path
153 setAttachPoint(ret.attachPoint);
154 setMediaSource(ret.mediaSource);
158 std::string mountpoint = attachPoint().asString();
159 if( !isUseableAttachPoint(attachPoint()))
161 mountpoint = createAttachPoint().asString();
162 if( mountpoint.empty())
163 ZYPP_THROW( MediaBadAttachPointException(url()));
164 setAttachPoint( mountpoint, true);
168 CredentialManager cm;
170 Mount::Options options( _url.getQueryParam("mountoptions") );
171 string username = _url.getUsername();
172 string password = _url.getPassword();
174 if ( ! options.has( "rw" ) ) {
178 // look for a workgroup
179 string workgroup = _url.getQueryParam("workgroup");
180 if ( workgroup.empty() )
181 workgroup = _url.getQueryParam("domain");
182 if ( !workgroup.empty() )
183 options["domain"] = workgroup;
185 // extract 'username', do not overwrite any _url.username
187 Mount::Options::iterator toEnv;
188 toEnv = options.find("username");
189 if ( toEnv != options.end() ) {
190 if ( username.empty() )
191 username = toEnv->second;
192 options.erase( toEnv );
195 toEnv = options.find("user"); // actually cifs specific
196 if ( toEnv != options.end() ) {
197 if ( username.empty() )
198 username = toEnv->second;
199 options.erase( toEnv );
202 // extract 'password', do not overwrite any _url.password
204 toEnv = options.find("password");
205 if ( toEnv != options.end() ) {
206 if ( password.empty() )
207 password = toEnv->second;
208 options.erase( toEnv );
211 toEnv = options.find("pass"); // actually cifs specific
212 if ( toEnv != options.end() ) {
213 if ( password.empty() )
214 password = toEnv->second;
215 options.erase( toEnv );
218 if ( username.empty() || password.empty() )
220 AuthData_Ptr c = cm.getCred(_url);
223 username = c->username();
224 password = c->password();
228 bool firstTry = true;
229 bool authRequired = false;
231 do // repeat this while the mount returns "Permission denied" error
233 // get credentials from authenicate()
236 username = authdata.username();
237 password = authdata.password();
240 // pass 'username' and 'password' via environment
241 Mount::Environment environment;
242 if ( !username.empty() )
243 environment["USER"] = username;
244 if ( !password.empty() )
245 environment["PASSWD"] = password;
247 //////////////////////////////////////////////////////
248 // In case we need a tmpfile, credentials will remove
249 // it in it's destructor after the mout call below.
250 filesystem::TmpPath credentials;
251 if ( !username.empty() || !password.empty() )
253 filesystem::TmpFile tmp;
254 ofstream outs( tmp.path().asString().c_str() );
255 outs << "username=" << username << endl;
256 outs << "password=" << password << endl;
260 options["credentials"] = credentials.path().asString();
264 // Use 'guest' option unless explicitly disabled (bnc #547354)
265 if ( options.has( "noguest" ) )
266 options.erase( "noguest" );
268 // prevent smbmount from asking for password
269 // only add this option if 'credentials' is not used (bnc #560496)
274 //////////////////////////////////////////////////////
278 mount.mount( path, mountpoint, "cifs",
279 options.asString(), environment );
280 setMediaSource(media);
283 catch (const MediaMountException & e)
287 if ( e.mountError() == "Permission denied" )
288 authRequired = authenticate( authdata, firstTry );
295 while ( authRequired );
297 // wait for /etc/mtab update ...
298 // (shouldn't be needed)
301 while( !(mountsucceeded=isAttached()) && --limit)
304 if ( !mountsucceeded )
306 setMediaSource(MediaSourceRef());
309 mount.umount(attachPoint().asString());
311 catch (const MediaException & excpt_r)
313 ZYPP_CAUGHT(excpt_r);
315 ZYPP_THROW(MediaMountException(
316 "Unable to verify that the media was mounted",
322 ///////////////////////////////////////////////////////////////////
324 // METHOD NAME : MediaCIFS::isAttached
325 // METHOD TYPE : bool
327 // DESCRIPTION : Override check if media is attached.
330 MediaCIFS::isAttached() const
332 return checkAttached(true);
335 ///////////////////////////////////////////////////////////////////
338 // METHOD NAME : MediaCIFS::releaseFrom
339 // METHOD TYPE : PMError
341 // DESCRIPTION : Asserted that media is attached.
343 void MediaCIFS::releaseFrom( const std::string & ejectDev )
346 mount.umount(attachPoint().asString());
349 ///////////////////////////////////////////////////////////////////
351 // METHOD NAME : MediaCIFS::getFile
352 // METHOD TYPE : PMError
354 // DESCRIPTION : Asserted that media is attached.
356 void MediaCIFS::getFile (const Pathname & filename) const
358 MediaHandler::getFile( filename );
361 ///////////////////////////////////////////////////////////////////
363 // METHOD NAME : MediaCIFS::getDir
364 // METHOD TYPE : PMError
366 // DESCRIPTION : Asserted that media is attached.
368 void MediaCIFS::getDir( const Pathname & dirname, bool recurse_r ) const
370 MediaHandler::getDir( dirname, recurse_r );
373 ///////////////////////////////////////////////////////////////////
376 // METHOD NAME : MediaCIFS::getDirInfo
377 // METHOD TYPE : PMError
379 // DESCRIPTION : Asserted that media is attached and retlist is empty.
381 void MediaCIFS::getDirInfo( std::list<std::string> & retlist,
382 const Pathname & dirname, bool dots ) const
384 MediaHandler::getDirInfo( retlist, dirname, dots );
387 ///////////////////////////////////////////////////////////////////
390 // METHOD NAME : MediaCIFS::getDirInfo
391 // METHOD TYPE : PMError
393 // DESCRIPTION : Asserted that media is attached and retlist is empty.
395 void MediaCIFS::getDirInfo( filesystem::DirContent & retlist,
396 const Pathname & dirname, bool dots ) const
398 MediaHandler::getDirInfo( retlist, dirname, dots );
401 bool MediaCIFS::getDoesFileExist( const Pathname & filename ) const
403 return MediaHandler::getDoesFileExist( filename );
406 bool MediaCIFS::authenticate(AuthData & authdata, bool firstTry) const
408 //! \todo need a way to pass different CredManagerOptions here
409 CredentialManager cm(CredManagerOptions(ZConfig::instance().systemRoot()));
411 // get stored credentials
412 AuthData_Ptr cmcred = cm.getCred(_url);
414 AuthData_Ptr smbcred;
415 smbcred.reset(new AuthData());
416 callback::SendReport<AuthenticationReport> auth_report;
418 // preset the username if present in current url
419 if (!_url.getUsername().empty() && firstTry)
420 smbcred->setUsername(_url.getUsername());
421 // if CM has found some credentials, preset the username from there
423 smbcred->setUsername(cmcred->username());
425 // indicate we have no good credentials from CM
428 string prompt_msg = str::form(
429 //!\todo add comma to the message for the next release
430 _("Authentication required for '%s'"), _url.asString().c_str());
433 if (auth_report->prompt(_url, prompt_msg, *smbcred))
435 DBG << "callback answer: retry" << endl
436 << "AuthData: " << *smbcred << endl;
438 if (smbcred->valid())
441 // if (credentials->username() != _url.getUsername())
442 // _url.setUsername(credentials->username());
444 * \todo find a way to save the url with changed username
445 * back to repoinfo or dont store urls with username
446 * (and either forbid more repos with the same url and different
447 * user, or return a set of credentials from CM and try them one
453 DBG << "callback answer: cancel" << endl;
455 // set username and password
458 authdata.setUsername(cmcred->username());
459 authdata.setPassword(cmcred->password());
461 // save the credentials
462 cmcred->setUrl(_url);