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