Fix Werrors with GCC-14.1.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 std::endl;
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 std::string getShare( Pathname spath_r )
47     {
48       if ( spath_r.empty() )
49         return std::string();
50
51       std::string share( spath_r.absolutename().asString() );
52       std::string::size_type sep = share.find( "/", 1 );
53       if ( sep == std::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" )) != std::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       std::string striped( spath_r.absolutename().asString() );
80       std::string::size_type sep = striped.find( "/", 1 );
81       if ( sep == std::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" URLs, 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       std::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       if( !isUseableAttachPoint( attachPoint() ) )
159       {
160         setAttachPoint( createAttachPoint(), true );
161       }
162       std::string mountpoint( attachPoint().asString() );
163
164       Mount mount;
165       CredentialManager cm(CredManagerOptions(ZConfig::instance().repoManagerRoot()));
166
167       Mount::Options options( _url.getQueryParam("mountoptions") );
168       std::string username = _url.getUsername();
169       std::string password = _url.getPassword();
170
171       if ( ! options.has( "rw" ) ) {
172         options["ro"];
173       }
174
175       // look for a workgroup
176       std::string workgroup = _url.getQueryParam("workgroup");
177       if ( workgroup.empty() )
178         workgroup = _url.getQueryParam("domain");
179       if ( !workgroup.empty() )
180         options["domain"] = workgroup;
181
182       // extract 'username', do not overwrite any _url.username
183
184       Mount::Options::iterator toEnv;
185       toEnv = options.find("username");
186       if ( toEnv != options.end() ) {
187         if ( username.empty() )
188           username = toEnv->second;
189         options.erase( toEnv );
190       }
191
192       toEnv = options.find("user"); // actually cifs specific
193       if ( toEnv != options.end() ) {
194         if ( username.empty() )
195           username = toEnv->second;
196         options.erase( toEnv );
197       }
198
199       // extract 'password', do not overwrite any _url.password
200
201       toEnv = options.find("password");
202       if ( toEnv != options.end() ) {
203         if ( password.empty() )
204           password = toEnv->second;
205         options.erase( toEnv );
206       }
207
208       toEnv = options.find("pass"); // actually cifs specific
209       if ( toEnv != options.end() ) {
210         if ( password.empty() )
211           password = toEnv->second;
212         options.erase( toEnv );
213       }
214
215       if ( username.empty() || password.empty() )
216       {
217         AuthData_Ptr c = cm.getCred(_url);
218         if (c)
219         {
220           username = c->username();
221           password = c->password();
222         }
223       }
224
225       bool firstTry = true;
226       bool authRequired = false;
227       AuthData authdata;
228       do // repeat this while the mount returns "Permission denied" error
229       {
230         // get credentials from authenicate()
231         if ( !firstTry )
232         {
233           username = authdata.username();
234           password = authdata.password();
235         }
236
237         // pass 'username' and 'password' via environment
238         Mount::Environment environment;
239         if ( !username.empty() )
240           environment["USER"] = username;
241         if ( !password.empty() )
242           environment["PASSWD"] = password;
243
244         //////////////////////////////////////////////////////
245         // In case we need a tmpfile, credentials will remove
246         // it in it's destructor after the mout call below.
247         filesystem::TmpPath credentials;
248         if ( !username.empty() || !password.empty() )
249         {
250           filesystem::TmpFile tmp;
251           std::ofstream outs( tmp.path().asString().c_str() );
252           outs << "username=" <<  username << endl;
253           outs << "password=" <<  password << endl;
254           outs.close();
255
256           credentials = tmp;
257           options["credentials"] = credentials.path().asString();
258         }
259         else
260         {
261           // Use 'guest' option unless explicitly disabled (bnc #547354)
262           if ( options.has( "noguest" ) )
263             options.erase( "noguest" );
264           else
265             // prevent smbmount from asking for password
266             // only add this option if 'credentials' is not used (bnc #560496)
267             options["guest"];
268         }
269
270         //
271         //////////////////////////////////////////////////////
272
273         try
274         {
275           mount.mount( path, mountpoint, "cifs",
276                        options.asString(), environment );
277           setMediaSource(media);
278           break;
279         }
280         catch (const MediaMountException & e)
281         {
282           ZYPP_CAUGHT( e );
283
284           if ( e.mountError() == "Permission denied" )
285             authRequired = authenticate( authdata, firstTry );
286           else
287             ZYPP_RETHROW( e );
288         }
289
290         firstTry = false;
291       }
292       while ( authRequired );
293
294       // wait for /etc/mtab update ...
295       // (shouldn't be needed)
296       int limit = 3;
297       bool mountsucceeded;
298       while( !(mountsucceeded=isAttached()) && --limit)
299         sleep(1);
300
301       if ( !mountsucceeded )
302       {
303         setMediaSource(MediaSourceRef());
304         try
305         {
306           mount.umount(attachPoint().asString());
307         }
308         catch (const MediaException & excpt_r)
309         {
310           ZYPP_CAUGHT(excpt_r);
311         }
312         ZYPP_THROW(MediaMountException(
313           "Unable to verify that the media was mounted",
314           path, mountpoint
315         ));
316       }
317     }
318
319     ///////////////////////////////////////////////////////////////////
320     //
321     //  METHOD NAME : MediaCIFS::isAttached
322     //  METHOD TYPE : bool
323     //
324     //  DESCRIPTION : Override check if media is attached.
325     //
326     bool
327     MediaCIFS::isAttached() const
328     {
329       return checkAttached(true);
330     }
331
332     ///////////////////////////////////////////////////////////////////
333     //
334     //
335     //  METHOD NAME : MediaCIFS::releaseFrom
336     //  METHOD TYPE : PMError
337     //
338     //  DESCRIPTION : Asserted that media is attached.
339     //
340     void MediaCIFS::releaseFrom( const std::string & ejectDev )
341     {
342       Mount mount;
343       mount.umount(attachPoint().asString());
344     }
345
346     ///////////////////////////////////////////////////////////////////
347     //
348     //  METHOD NAME : MediaCIFS::getFile
349     //  METHOD TYPE : PMError
350     //
351     //  DESCRIPTION : Asserted that media is attached.
352     //
353     void MediaCIFS::getFile (const Pathname & filename, const ByteCount &expectedFileSize_r) const
354     {
355       MediaHandler::getFile( filename, expectedFileSize_r );
356     }
357
358     ///////////////////////////////////////////////////////////////////
359     //
360     //  METHOD NAME : MediaCIFS::getDir
361     //  METHOD TYPE : PMError
362     //
363     //  DESCRIPTION : Asserted that media is attached.
364     //
365     void MediaCIFS::getDir( const Pathname & dirname, bool recurse_r ) const
366     {
367       MediaHandler::getDir( dirname, recurse_r );
368     }
369
370     ///////////////////////////////////////////////////////////////////
371     //
372     //
373     //  METHOD NAME : MediaCIFS::getDirInfo
374     //  METHOD TYPE : PMError
375     //
376     //  DESCRIPTION : Asserted that media is attached and retlist is empty.
377     //
378     void MediaCIFS::getDirInfo( std::list<std::string> & retlist,
379                                const Pathname & dirname, bool dots ) const
380     {
381       MediaHandler::getDirInfo( retlist, dirname, dots );
382     }
383
384     ///////////////////////////////////////////////////////////////////
385     //
386     //
387     //  METHOD NAME : MediaCIFS::getDirInfo
388     //  METHOD TYPE : PMError
389     //
390     //  DESCRIPTION : Asserted that media is attached and retlist is empty.
391     //
392     void MediaCIFS::getDirInfo( filesystem::DirContent & retlist,
393                                const Pathname & dirname, bool dots ) const
394     {
395       MediaHandler::getDirInfo( retlist, dirname, dots );
396     }
397
398     bool MediaCIFS::getDoesFileExist( const Pathname & filename ) const
399     {
400       return MediaHandler::getDoesFileExist( filename );
401     }
402
403     bool MediaCIFS::authenticate(AuthData & authdata, bool firstTry) const
404     {
405       //! \todo need a way to pass different CredManagerOptions here
406       CredentialManager cm(CredManagerOptions(ZConfig::instance().repoManagerRoot()));
407
408       // get stored credentials
409       AuthData_Ptr cmcred = cm.getCred(_url);
410
411       AuthData_Ptr smbcred;
412       smbcred.reset(new AuthData());
413       callback::SendReport<AuthenticationReport> auth_report;
414
415       // preset the username if present in current url
416       if (!_url.getUsername().empty() && firstTry)
417         smbcred->setUsername(_url.getUsername());
418       // if CM has found some credentials, preset the username from there
419       else if (cmcred)
420         smbcred->setUsername(cmcred->username());
421
422       // indicate we have no good credentials from CM
423       cmcred.reset();
424
425       std::string prompt_msg = str::form(
426         //!\todo add comma to the message for the next release
427         _("Authentication required for '%s'"), _url.asString().c_str());
428
429       // ask user
430       if (auth_report->prompt(_url, prompt_msg, *smbcred))
431       {
432         DBG << "callback answer: retry" << endl
433             << "AuthData: " << *smbcred << endl;
434
435         if (smbcred->valid())
436         {
437           cmcred = smbcred;
438             // if (credentials->username() != _url.getUsername())
439             //   _url.setUsername(credentials->username());
440             /**
441              *  \todo find a way to save the url with changed username
442              *  back to repoinfo or dont store urls with username
443              *  (and either forbid more repos with the same url and different
444              *  user, or return a set of credentials from CM and try them one
445              *  by one)
446              */
447         }
448       }
449       else
450         DBG << "callback answer: cancel" << endl;
451
452       // set username and password
453       if (cmcred)
454       {
455         authdata.setUsername(cmcred->username());
456         authdata.setPassword(cmcred->password());
457
458         // save the credentials
459         cmcred->setUrl(_url);
460         cm.addCred(*cmcred);
461         cm.save();
462
463         return true;
464       }
465
466       return false;
467     }
468
469
470   } // namespace media
471 } // namespace zypp