Cleanup and remove deprecated interface methods
[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     ///////////////////////////////////////////////////////////////////
345     //
346     //  METHOD NAME : MediaSMB::getFile
347     //  METHOD TYPE : PMError
348     //
349     //  DESCRIPTION : Asserted that media is attached.
350     //
351     void MediaSMB::getFile (const Pathname & filename) const
352     {
353       MediaHandler::getFile( filename );
354     }
355
356     ///////////////////////////////////////////////////////////////////
357     //
358     //  METHOD NAME : MediaSMB::getDir
359     //  METHOD TYPE : PMError
360     //
361     //  DESCRIPTION : Asserted that media is attached.
362     //
363     void MediaSMB::getDir( const Pathname & dirname, bool recurse_r ) const
364     {
365       MediaHandler::getDir( dirname, recurse_r );
366     }
367
368     ///////////////////////////////////////////////////////////////////
369     //
370     //
371     //  METHOD NAME : MediaSMB::getDirInfo
372     //  METHOD TYPE : PMError
373     //
374     //  DESCRIPTION : Asserted that media is attached and retlist is empty.
375     //
376     void MediaSMB::getDirInfo( std::list<std::string> & retlist,
377                                const Pathname & dirname, bool dots ) const
378     {
379       MediaHandler::getDirInfo( retlist, dirname, dots );
380     }
381
382     ///////////////////////////////////////////////////////////////////
383     //
384     //
385     //  METHOD NAME : MediaSMB::getDirInfo
386     //  METHOD TYPE : PMError
387     //
388     //  DESCRIPTION : Asserted that media is attached and retlist is empty.
389     //
390     void MediaSMB::getDirInfo( filesystem::DirContent & retlist,
391                                const Pathname & dirname, bool dots ) const
392     {
393       MediaHandler::getDirInfo( retlist, dirname, dots );
394     }
395
396     bool MediaSMB::getDoesFileExist( const Pathname & filename ) const
397     {
398       return MediaHandler::getDoesFileExist( filename );
399     }
400
401     bool MediaSMB::authenticate(AuthData & authdata, bool firstTry) const
402     {
403       //! \todo need a way to pass different CredManagerOptions here
404       Target_Ptr target = zypp::getZYpp()->getTarget();
405       CredentialManager cm(CredManagerOptions(target ? target->root() : ""));
406
407       // get stored credentials
408       AuthData_Ptr cmcred = cm.getCred(_url);
409
410       AuthData_Ptr smbcred;
411       smbcred.reset(new AuthData());
412       callback::SendReport<AuthenticationReport> auth_report;
413
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
418       else if (cmcred)
419         smbcred->setUsername(cmcred->username());
420
421       // indicate we have no good credentials from CM
422       cmcred.reset();
423
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());
427
428       // ask user
429       if (auth_report->prompt(_url, prompt_msg, *smbcred))
430       {
431         DBG << "callback answer: retry" << endl
432             << "AuthData: " << *smbcred << endl;
433
434         if (smbcred->valid())
435         {
436           cmcred = smbcred;
437             // if (credentials->username() != _url.getUsername())
438             //   _url.setUsername(credentials->username());
439             /**
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
444              *  by one)
445              */
446         }
447       }
448       else
449         DBG << "callback answer: cancel" << endl;
450
451       // set username and password
452       if (cmcred)
453       {
454         authdata.setUsername(cmcred->username());
455         authdata.setPassword(cmcred->password());
456
457         // save the credentials
458         cmcred->setUrl(_url);
459         cm.addCred(*cmcred);
460         cm.save();
461
462         return true;
463       }
464
465       return false;
466     }
467
468
469   } // namespace media
470 } // namespace zypp