8ba2b9d353683ecd38d912cb122259478e2f4b42
[platform/upstream/libzypp.git] / zypp / media / MediaSMB.cc
1 /*---------------------------------------------------------------------\
2 |                          ____ _   __ __ ___                          |
3 |                         |__  / \ / / . \ . \                         |
4 |                           / / \ V /|  _/  _/                         |
5 |                          / /__ | | | | | |                           |
6 |                         /_____||_| |_| |_|                           |
7 |                                                                      |
8 \---------------------------------------------------------------------*/
9 /** \file zypp/media/MediaSMB.cc
10  *
11 */
12
13 #include <iostream>
14 #include <fstream>
15
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
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()
28
29 #include "zypp/media/MediaSMB.h"
30
31 #include <sys/types.h>
32 #include <sys/mount.h>
33 #include <errno.h>
34 #include <dirent.h>
35
36 using namespace std;
37
38 namespace zypp {
39   namespace media {
40
41     /******************************************************************
42     **
43     **
44     **  FUNCTION NAME : getShare
45     **  FUNCTION TYPE : inline Pathname
46     **
47     ** Get the 1st path component (CIFS share name).
48     */
49     inline string getShare( Pathname spath_r )
50     {
51       if ( spath_r.empty() )
52         return string();
53
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
58       else
59         share = share.substr( 1, sep-1 );
60
61       // deescape %2f in sharename
62       while ( (sep = share.find( "%2f" )) != string::npos ) {
63         share.replace( sep, 3, "/" );
64       }
65
66       return share;
67     }
68
69     /******************************************************************
70     **
71     **
72     **  FUNCTION NAME : stripShare
73     **  FUNCTION TYPE : inline Pathname
74     **
75     ** Strip off the 1st path component (CIFS share name).
76     */
77     inline Pathname stripShare( Pathname spath_r )
78     {
79       if ( spath_r.empty() )
80         return Pathname();
81
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
86
87       return striped.substr( sep );
88     }
89
90     ///////////////////////////////////////////////////////////////////
91     //
92     //  CLASS NAME : MediaSMB
93     //
94     ///////////////////////////////////////////////////////////////////
95
96     ///////////////////////////////////////////////////////////////////
97     //
98     //
99     //  METHOD NAME : MediaSMB::MediaSMB
100     //  METHOD TYPE : Constructor
101     //
102     //  DESCRIPTION :
103     //
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
109         , _vfstype( "cifs" )
110     {
111         MIL << "MediaSMB::MediaSMB(" << url_r << ", " << attach_point_hint_r << ")" << endl;
112     }
113
114     ///////////////////////////////////////////////////////////////////
115     //
116     //
117     //  METHOD NAME : MediaSMB::attachTo
118     //  METHOD TYPE : PMError
119     /**
120      * Asserted that not already attached, and attachPoint is a directory.
121      *
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.
128      *
129      * \note The implementation currently serves both, "smb" and
130      *      and "cifs" URL's, but passes "cifs" to the mount command
131      *      in any case.
132      */
133     void MediaSMB::attachTo(bool next)
134     {
135       if(_url.getHost().empty())
136         ZYPP_THROW(MediaBadUrlEmptyHostException(_url));
137       if(next)
138         ZYPP_THROW(MediaNotSupportedException(_url));
139
140       string path = "//";
141       path += _url.getHost() + "/" + getShare( _url.getPathName() );
142
143       MediaSourceRef media( new MediaSource( _vfstype, path));
144       AttachedMedia  ret( findAttachedMedia( media));
145
146       if( ret.mediaSource &&
147           ret.attachPoint &&
148           !ret.attachPoint->empty())
149       {
150         DBG << "Using a shared media "
151             << ret.mediaSource->name
152             << " attached on "
153             << ret.attachPoint->path
154             << endl;
155
156         removeAttachPoint();
157         setAttachPoint(ret.attachPoint);
158         setMediaSource(ret.mediaSource);
159         return;
160       }
161
162       std::string mountpoint = attachPoint().asString();
163       if( !isUseableAttachPoint(attachPoint()))
164       {
165         mountpoint = createAttachPoint().asString();
166         if( mountpoint.empty())
167           ZYPP_THROW( MediaBadAttachPointException(url()));
168         setAttachPoint( mountpoint, true);
169       }
170
171       Mount mount;
172       CredentialManager cm;
173
174       Mount::Options options( _url.getQueryParam("mountoptions") );
175       string username = _url.getUsername();
176       string password = _url.getPassword();
177
178       options["guest"]; // prevent smbmount from asking for password
179
180       if ( ! options.has( "rw" ) ) {
181         options["ro"];
182       }
183
184       // look for a workgroup
185       /*
186       string workgroup = _url.getQueryParam("workgroup");
187       if ( !workgroup.empty() )
188         options["workgroup"] = workgroup;
189       */
190
191       // extract 'username', do not overwrite any _url.username
192
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 );
199       }
200
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 );
206       }
207
208       // extract 'password', do not overwrite any _url.password
209
210       toEnv = options.find("password");
211       if ( toEnv != options.end() ) {
212         if ( password.empty() )
213           password = toEnv->second;
214         options.erase( toEnv );
215       }
216
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 );
222       }
223
224       if ( username.empty() || password.empty() )
225       {
226         AuthData_Ptr c = cm.getCred(_url);
227         if (c)
228         {
229           username = c->username();
230           password = c->password();
231         }
232       }
233
234       bool firstTry = true;
235       bool authRequired = false;
236       AuthData authdata;
237       do // repeat this while the mount returns "Permission denied" error
238       {
239         // get credentials from authenicate()
240         if ( !firstTry )
241         {
242           username = authdata.username();
243           password = authdata.password();
244         }
245
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;
252
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() )
258         {
259           filesystem::TmpFile tmp;
260           ofstream outs( tmp.path().asString().c_str() );
261           outs << "username=" <<  username << endl;
262           outs << "password=" <<  password << endl;
263           outs.close();
264
265           credentials = tmp;
266           options["credentials"] = credentials.path().asString();
267         }
268         //
269         //////////////////////////////////////////////////////
270
271         try
272         {
273           mount.mount( path, mountpoint, _vfstype,
274                        options.asString(), environment );
275           setMediaSource(media);
276           break;
277         }
278         catch (const MediaMountException & e)
279         {
280           ZYPP_CAUGHT( e );
281
282           if ( e.mountError() == "Permission denied" )
283             authRequired = authenticate( authdata, firstTry );
284           else
285             ZYPP_RETHROW( e );
286         }
287
288         firstTry = false;
289       }
290       while ( authRequired );
291
292       // wait for /etc/mtab update ...
293       // (shouldn't be needed)
294       int limit = 3;
295       bool mountsucceeded;
296       while( !(mountsucceeded=isAttached()) && --limit)
297         sleep(1);
298
299       if ( !mountsucceeded )
300       {
301         setMediaSource(MediaSourceRef());
302         try
303         {
304           mount.umount(attachPoint().asString());
305         }
306         catch (const MediaException & excpt_r)
307         {
308           ZYPP_CAUGHT(excpt_r);
309         }
310         ZYPP_THROW(MediaMountException(
311           "Unable to verify that the media was mounted",
312           path, mountpoint
313         ));
314       }
315     }
316
317     ///////////////////////////////////////////////////////////////////
318     //
319     //  METHOD NAME : MediaSMB::isAttached
320     //  METHOD TYPE : bool
321     //
322     //  DESCRIPTION : Override check if media is attached.
323     //
324     bool
325     MediaSMB::isAttached() const
326     {
327       return checkAttached(true);
328     }
329
330     ///////////////////////////////////////////////////////////////////
331     //
332     //
333     //  METHOD NAME : MediaSMB::releaseFrom
334     //  METHOD TYPE : PMError
335     //
336     //  DESCRIPTION : Asserted that media is attached.
337     //
338     void MediaSMB::releaseFrom( const std::string & ejectDev )
339     {
340       Mount mount;
341       mount.umount(attachPoint().asString());
342     }
343
344     // deprecated
345     void MediaSMB::releaseFrom( bool eject )
346     {
347       releaseFrom( "" );
348     }
349
350     ///////////////////////////////////////////////////////////////////
351     //
352     //  METHOD NAME : MediaSMB::getFile
353     //  METHOD TYPE : PMError
354     //
355     //  DESCRIPTION : Asserted that media is attached.
356     //
357     void MediaSMB::getFile (const Pathname & filename) const
358     {
359       MediaHandler::getFile( filename );
360     }
361
362     ///////////////////////////////////////////////////////////////////
363     //
364     //  METHOD NAME : MediaSMB::getDir
365     //  METHOD TYPE : PMError
366     //
367     //  DESCRIPTION : Asserted that media is attached.
368     //
369     void MediaSMB::getDir( const Pathname & dirname, bool recurse_r ) const
370     {
371       MediaHandler::getDir( dirname, recurse_r );
372     }
373
374     ///////////////////////////////////////////////////////////////////
375     //
376     //
377     //  METHOD NAME : MediaSMB::getDirInfo
378     //  METHOD TYPE : PMError
379     //
380     //  DESCRIPTION : Asserted that media is attached and retlist is empty.
381     //
382     void MediaSMB::getDirInfo( std::list<std::string> & retlist,
383                                const Pathname & dirname, bool dots ) const
384     {
385       MediaHandler::getDirInfo( retlist, dirname, dots );
386     }
387
388     ///////////////////////////////////////////////////////////////////
389     //
390     //
391     //  METHOD NAME : MediaSMB::getDirInfo
392     //  METHOD TYPE : PMError
393     //
394     //  DESCRIPTION : Asserted that media is attached and retlist is empty.
395     //
396     void MediaSMB::getDirInfo( filesystem::DirContent & retlist,
397                                const Pathname & dirname, bool dots ) const
398     {
399       MediaHandler::getDirInfo( retlist, dirname, dots );
400     }
401
402     bool MediaSMB::getDoesFileExist( const Pathname & filename ) const
403     {
404       return MediaHandler::getDoesFileExist( filename );
405     }
406
407     bool MediaSMB::authenticate(AuthData & authdata, bool firstTry) const
408     {
409       //! \todo need a way to pass different CredManagerOptions here
410       Target_Ptr target = zypp::getZYpp()->getTarget();
411       CredentialManager cm(CredManagerOptions(target ? target->root() : ""));
412
413       // get stored credentials
414       AuthData_Ptr cmcred = cm.getCred(_url);
415
416       AuthData_Ptr smbcred;
417       smbcred.reset(new AuthData());
418       callback::SendReport<AuthenticationReport> auth_report;
419
420       // preset the username if present in current url
421       if (!_url.getUsername().empty() && firstTry)
422         smbcred->setUsername(_url.getUsername());
423       // if CM has found some credentials, preset the username from there
424       else if (cmcred)
425         smbcred->setUsername(cmcred->username());
426
427       // indicate we have no good credentials from CM
428       cmcred.reset();
429
430       string prompt_msg = str::form(
431         //!\todo add comma to the message for the next release
432         _("Authentication required for '%s'"), _url.asString().c_str());
433
434       // ask user
435       if (auth_report->prompt(_url, prompt_msg, *smbcred))
436       {
437         DBG << "callback answer: retry" << endl
438             << "AuthData: " << *smbcred << endl;
439
440         if (smbcred->valid())
441         {
442           cmcred = smbcred;
443             // if (credentials->username() != _url.getUsername())
444             //   _url.setUsername(credentials->username());
445             /**
446              *  \todo find a way to save the url with changed username
447              *  back to repoinfo or dont store urls with username
448              *  (and either forbid more repos with the same url and different
449              *  user, or return a set of credentials from CM and try them one
450              *  by one)
451              */
452         }
453       }
454       else
455         DBG << "callback answer: cancel" << endl;
456
457       // set username and password
458       if (cmcred)
459       {
460         authdata.setUsername(cmcred->username());
461         authdata.setPassword(cmcred->password());
462
463         // save the credentials
464         cmcred->setUrl(_url);
465         cm.addCred(*cmcred);
466         cm.save();
467
468         return true;
469       }
470
471       return false;
472     }
473
474
475   } // namespace media
476 } // namespace zypp