1 /*
2  *  Copyright (C) 2005-2018 Team Kodi
3  *  This file is part of Kodi - https://kodi.tv
4  *
5  *  SPDX-License-Identifier: GPL-2.0-or-later
6  *  See LICENSES/README.md for more information.
7  */
8 
9 // SMBFile.cpp: implementation of the CSMBFile class.
10 //
11 //////////////////////////////////////////////////////////////////////
12 
13 #include "SMBFile.h"
14 
15 #include "PasswordManager.h"
16 #include "SMBDirectory.h"
17 #include "ServiceBroker.h"
18 #include "Util.h"
19 #include "commons/Exception.h"
20 #include "filesystem/SpecialProtocol.h"
21 #include "network/DNSNameCache.h"
22 #include "settings/AdvancedSettings.h"
23 #include "settings/Settings.h"
24 #include "settings/SettingsComponent.h"
25 #include "threads/SingleLock.h"
26 #include "utils/StringUtils.h"
27 #include "utils/TimeUtils.h"
28 #include "utils/URIUtils.h"
29 #include "utils/log.h"
30 
31 #include <inttypes.h>
32 
33 #include <libsmbclient.h>
34 
35 using namespace XFILE;
36 
xb_smbc_log(const char * msg)37 void xb_smbc_log(const char* msg)
38 {
39   CLog::Log(LOGINFO, "%s%s", "smb: ", msg);
40 }
41 
xb_smbc_auth(const char * srv,const char * shr,char * wg,int wglen,char * un,int unlen,char * pw,int pwlen)42 void xb_smbc_auth(const char *srv, const char *shr, char *wg, int wglen,
43                   char *un, int unlen, char *pw, int pwlen)
44 {
45 }
46 
47 // WTF is this ?, we get the original server cache only
48 // to set the server cache to this function which call the
49 // original one anyway. Seems quite silly.
50 smbc_get_cached_srv_fn orig_cache;
xb_smbc_cache(SMBCCTX * c,const char * server,const char * share,const char * workgroup,const char * username)51 SMBCSRV* xb_smbc_cache(SMBCCTX* c, const char* server, const char* share, const char* workgroup, const char* username)
52 {
53   return orig_cache(c, server, share, workgroup, username);
54 }
55 
56 bool CSMB::IsFirstInit = true;
57 
CSMB()58 CSMB::CSMB()
59 {
60   m_context = NULL;
61   m_OpenConnections = 0;
62   m_IdleTimeout = 0;
63 }
64 
~CSMB()65 CSMB::~CSMB()
66 {
67   Deinit();
68 }
69 
Deinit()70 void CSMB::Deinit()
71 {
72   CSingleLock lock(*this);
73 
74   /* samba goes loco if deinited while it has some files opened */
75   if (m_context)
76   {
77     smbc_set_context(NULL);
78     smbc_free_context(m_context, 1);
79     m_context = NULL;
80   }
81 }
82 
Init()83 void CSMB::Init()
84 {
85   CSingleLock lock(*this);
86 
87   if (!m_context)
88   {
89     const std::shared_ptr<CSettings> settings = CServiceBroker::GetSettingsComponent()->GetSettings();
90 
91     // force libsmbclient to use our own smb.conf by overriding HOME
92     std::string truehome(getenv("HOME"));
93     setenv("HOME", CSpecialProtocol::TranslatePath("special://home").c_str(), 1);
94 
95     // Create ~/.kodi/.smb/smb.conf. This file is used by libsmbclient.
96     // http://us1.samba.org/samba/docs/man/manpages-3/libsmbclient.7.html
97     // http://us1.samba.org/samba/docs/man/manpages-3/smb.conf.5.html
98     std::string smb_conf;
99     std::string home(getenv("HOME"));
100     URIUtils::RemoveSlashAtEnd(home);
101     smb_conf = home + "/.smb";
102     int result = mkdir(smb_conf.c_str(), 0755);
103     if (result == 0 || (errno == EEXIST && IsFirstInit))
104     {
105       smb_conf += "/smb.conf";
106       FILE* f = fopen(smb_conf.c_str(), "w");
107       if (f != NULL)
108       {
109         fprintf(f, "[global]\n");
110 
111         fprintf(f, "\tlock directory = %s/.smb/\n", home.c_str());
112 
113         // set minimum smbclient protocol version
114         if (settings->GetInt(CSettings::SETTING_SMB_MINPROTOCOL) > 0)
115         {
116           if (settings->GetInt(CSettings::SETTING_SMB_MINPROTOCOL) == 1)
117             fprintf(f, "\tclient min protocol = NT1\n");
118           else
119             fprintf(f, "\tclient min protocol = SMB%d\n", settings->GetInt(CSettings::SETTING_SMB_MINPROTOCOL));
120         }
121 
122         // set maximum smbclient protocol version
123         if (settings->GetInt(CSettings::SETTING_SMB_MAXPROTOCOL) > 0)
124         {
125           if (settings->GetInt(CSettings::SETTING_SMB_MAXPROTOCOL) == 1)
126             fprintf(f, "\tclient max protocol = NT1\n");
127           else
128             fprintf(f, "\tclient max protocol = SMB%d\n", settings->GetInt(CSettings::SETTING_SMB_MAXPROTOCOL));
129         }
130 
131         // set legacy security options
132         if (settings->GetBool(CSettings::SETTING_SMB_LEGACYSECURITY) && (settings->GetInt(CSettings::SETTING_SMB_MAXPROTOCOL) == 1))
133         {
134           fprintf(f, "\tclient NTLMv2 auth = no\n");
135           fprintf(f, "\tclient use spnego = no\n");
136         }
137 
138         // set wins server if there's one. name resolve order defaults to 'lmhosts host wins bcast'.
139         // if no WINS server has been specified the wins method will be ignored.
140         if (settings->GetString(CSettings::SETTING_SMB_WINSSERVER).length() > 0 && !StringUtils::EqualsNoCase(settings->GetString(CSettings::SETTING_SMB_WINSSERVER), "0.0.0.0") )
141         {
142           fprintf(f, "\twins server = %s\n", settings->GetString(CSettings::SETTING_SMB_WINSSERVER).c_str());
143           fprintf(f, "\tname resolve order = bcast wins host\n");
144         }
145         else
146           fprintf(f, "\tname resolve order = bcast host\n");
147 
148         // use user-configured charset. if no charset is specified,
149         // samba tries to use charset 850 but falls back to ASCII in case it is not available
150         if (CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_sambadoscodepage.length() > 0)
151           fprintf(f, "\tdos charset = %s\n", CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_sambadoscodepage.c_str());
152 
153         // include users configuration if available
154         fprintf(f, "\tinclude = %s/.smb/user.conf\n", home.c_str());
155 
156         fclose(f);
157       }
158     }
159 
160     // reads smb.conf so this MUST be after we create smb.conf
161     // multiple smbc_init calls are ignored by libsmbclient.
162     // note: this is important as it initializes the smb old
163     // interface compatibility. Samba 3.4.0 or higher has the new interface.
164     // note: we leak the following here once, not sure why yet.
165     // 48 bytes -> smb_xmalloc_array
166     // 32 bytes -> set_param_opt
167     // 16 bytes -> set_param_opt
168     smbc_init(xb_smbc_auth, 0);
169 
170     // setup our context
171     m_context = smbc_new_context();
172 
173     // restore HOME
174     setenv("HOME", truehome.c_str(), 1);
175 
176 #ifdef DEPRECATED_SMBC_INTERFACE
177     smbc_setDebug(m_context, CServiceBroker::GetLogging().CanLogComponent(LOGSAMBA) ? 10 : 0);
178     smbc_setFunctionAuthData(m_context, xb_smbc_auth);
179     orig_cache = smbc_getFunctionGetCachedServer(m_context);
180     smbc_setFunctionGetCachedServer(m_context, xb_smbc_cache);
181     smbc_setOptionOneSharePerServer(m_context, false);
182     smbc_setOptionBrowseMaxLmbCount(m_context, 0);
183     smbc_setTimeout(m_context, CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_sambaclienttimeout * 1000);
184     // we do not need to strdup these, smbc_setXXX below will make their own copies
185     if (settings->GetString(CSettings::SETTING_SMB_WORKGROUP).length() > 0)
186       //! @bug libsmbclient < 4.9 isn't const correct
187       smbc_setWorkgroup(m_context, const_cast<char*>(settings->GetString(CSettings::SETTING_SMB_WORKGROUP).c_str()));
188     std::string guest = "guest";
189     //! @bug libsmbclient < 4.8 isn't const correct
190     smbc_setUser(m_context, const_cast<char*>(guest.c_str()));
191 #else
192     m_context->debug = (CServiceBroker::GetLogging().CanLogComponent(LOGSAMBA) ? 10 : 0);
193     m_context->callbacks.auth_fn = xb_smbc_auth;
194     orig_cache = m_context->callbacks.get_cached_srv_fn;
195     m_context->callbacks.get_cached_srv_fn = xb_smbc_cache;
196     m_context->options.one_share_per_server = false;
197     m_context->options.browse_max_lmb_count = 0;
198     m_context->timeout = CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_sambaclienttimeout * 1000;
199     // we need to strdup these, they will get free'd on smbc_free_context
200     if (settings->GetString(CSettings::SETTING_SMB_WORKGROUP).length() > 0)
201       m_context->workgroup = strdup(settings->GetString(CSettings::SETTING_SMB_WORKGROUP).c_str());
202     m_context->user = strdup("guest");
203 #endif
204 
205     // initialize samba and do some hacking into the settings
206     if (smbc_init_context(m_context))
207     {
208       // setup context using the smb old interface compatibility
209       SMBCCTX *old_context = smbc_set_context(m_context);
210       // free previous context or we leak it, this comes from smbc_init above.
211       // there is a bug in smbclient (old interface), if we init/set a context
212       // then set(null)/free it in DeInit above, the next smbc_set_context
213       // return the already freed previous context, free again and bang, crash.
214       // so we setup a stic bool to track the first init so we can free the
215       // context associated with the initial smbc_init.
216       if (old_context && IsFirstInit)
217       {
218         smbc_free_context(old_context, 1);
219         IsFirstInit = false;
220       }
221     }
222     else
223     {
224       smbc_free_context(m_context, 1);
225       m_context = NULL;
226     }
227   }
228   m_IdleTimeout = 180;
229 }
230 
URLEncode(const CURL & url)231 std::string CSMB::URLEncode(const CURL &url)
232 {
233   /* due to smb wanting encoded urls we have to build it manually */
234 
235   std::string flat = "smb://";
236 
237   /* samba messes up of password is set but no username is set. don't know why yet */
238   /* probably the url parser that goes crazy */
239   if(url.GetUserName().length() > 0 /* || url.GetPassWord().length() > 0 */)
240   {
241     if(!url.GetDomain().empty())
242     {
243       flat += URLEncode(url.GetDomain());
244       flat += ";";
245     }
246     flat += URLEncode(url.GetUserName());
247     if(url.GetPassWord().length() > 0)
248     {
249       flat += ":";
250       flat += URLEncode(url.GetPassWord());
251     }
252     flat += "@";
253   }
254   flat += URLEncode(url.GetHostName());
255 
256   if (url.HasPort())
257   {
258     flat += StringUtils::Format(":%i", url.GetPort());
259   }
260 
261   /* okey sadly since a slash is an invalid name we have to tokenize */
262   std::vector<std::string> parts;
263   StringUtils::Tokenize(url.GetFileName(), parts, "/");
264   for (const std::string& it : parts)
265   {
266     flat += "/";
267     flat += URLEncode((it));
268   }
269 
270   /* okey options should go here, thou current samba doesn't support any */
271 
272   return flat;
273 }
274 
URLEncode(const std::string & value)275 std::string CSMB::URLEncode(const std::string &value)
276 {
277   return CURL::Encode(value);
278 }
279 
280 /* This is called from CApplication::ProcessSlow() and is used to tell if smbclient have been idle for too long */
CheckIfIdle()281 void CSMB::CheckIfIdle()
282 {
283 /* We check if there are open connections. This is done without a lock to not halt the mainthread. It should be thread safe as
284    worst case scenario is that m_OpenConnections could read 0 and then changed to 1 if this happens it will enter the if wich will lead to another check, wich is locked.  */
285   if (m_OpenConnections == 0)
286   { /* I've set the the maximum IDLE time to be 1 min and 30 sec. */
287     CSingleLock lock(*this);
288     if (m_OpenConnections == 0 /* check again - when locked */ && m_context != NULL)
289     {
290       if (m_IdleTimeout > 0)
291 	  {
292         m_IdleTimeout--;
293       }
294 	  else
295 	  {
296             CLog::Log(LOGINFO, "Samba is idle. Closing the remaining connections");
297             smb.Deinit();
298       }
299     }
300   }
301 }
302 
SetActivityTime()303 void CSMB::SetActivityTime()
304 {
305   /* Since we get called every 500ms from ProcessSlow we limit the tick count to 180 */
306   /* That means we have 2 ticks per second which equals 180/2 == 90 seconds */
307   m_IdleTimeout = 180;
308 }
309 
310 /* The following two function is used to keep track on how many Opened files/directories there are.
311    This makes the idle timer not count if a movie is paused for example */
AddActiveConnection()312 void CSMB::AddActiveConnection()
313 {
314   CSingleLock lock(*this);
315   m_OpenConnections++;
316 }
AddIdleConnection()317 void CSMB::AddIdleConnection()
318 {
319   CSingleLock lock(*this);
320   m_OpenConnections--;
321   /* If we close a file we reset the idle timer so that we don't have any wierd behaviours if a user
322      leaves the movie paused for a long while and then press stop */
323   m_IdleTimeout = 180;
324 }
325 
GetResolvedUrl(const CURL & url)326 CURL CSMB::GetResolvedUrl(const CURL& url)
327 {
328   CURL tmpUrl(url);
329   std::string resolvedHostName;
330 
331   if (CDNSNameCache::Lookup(tmpUrl.GetHostName(), resolvedHostName))
332     tmpUrl.SetHostName(resolvedHostName);
333 
334   return tmpUrl;
335 }
336 
337 CSMB smb;
338 
CSMBFile()339 CSMBFile::CSMBFile()
340 {
341   smb.Init();
342   m_fd = -1;
343   smb.AddActiveConnection();
344   m_allowRetry = true;
345 }
346 
~CSMBFile()347 CSMBFile::~CSMBFile()
348 {
349   Close();
350   smb.AddIdleConnection();
351 }
352 
GetPosition()353 int64_t CSMBFile::GetPosition()
354 {
355   if (m_fd == -1)
356     return -1;
357   CSingleLock lock(smb);
358   if (!smb.IsSmbValid())
359     return -1;
360   return smbc_lseek(m_fd, 0, SEEK_CUR);
361 }
362 
GetLength()363 int64_t CSMBFile::GetLength()
364 {
365   if (m_fd == -1)
366     return -1;
367   return m_fileSize;
368 }
369 
Open(const CURL & url)370 bool CSMBFile::Open(const CURL& url)
371 {
372   Close();
373 
374   // we can't open files like smb://file.f or smb://server/file.f
375   // if a file matches the if below return false, it can't exist on a samba share.
376   if (!IsValidFile(url.GetFileName()))
377   {
378     CLog::Log(LOGINFO, "SMBFile->Open: Bad URL : '%s'", url.GetRedacted().c_str());
379     return false;
380   }
381   m_url = url;
382 
383   // opening a file to another computer share will create a new session
384   // when opening smb://server xbms will try to find folder.jpg in all shares
385   // listed, which will create lot's of open sessions.
386 
387   std::string strFileName;
388   m_fd = OpenFile(url, strFileName);
389 
390   CLog::Log(LOGDEBUG,"CSMBFile::Open - opened %s, fd=%d",url.GetRedacted().c_str(), m_fd);
391   if (m_fd == -1)
392   {
393     // write error to logfile
394     CLog::Log(LOGINFO, "SMBFile->Open: Unable to open file : '%s'\nunix_err:'%x' error : '%s'", CURL::GetRedacted(strFileName).c_str(), errno, strerror(errno));
395     return false;
396   }
397 
398   CSingleLock lock(smb);
399   if (!smb.IsSmbValid())
400     return false;
401   struct stat tmpBuffer;
402   if (smbc_stat(strFileName.c_str(), &tmpBuffer) < 0)
403   {
404     smbc_close(m_fd);
405     m_fd = -1;
406     return false;
407   }
408 
409   m_fileSize = tmpBuffer.st_size;
410 
411   int64_t ret = smbc_lseek(m_fd, 0, SEEK_SET);
412   if ( ret < 0 )
413   {
414     smbc_close(m_fd);
415     m_fd = -1;
416     return false;
417   }
418   // We've successfully opened the file!
419   return true;
420 }
421 
422 
423 /// \brief Checks authentication against SAMBA share. Reads password cache created in CSMBDirectory::OpenDir().
424 /// \param strAuth The SMB style path
425 /// \return SMB file descriptor
426 /*
427 int CSMBFile::OpenFile(std::string& strAuth)
428 {
429   int fd = -1;
430 
431   std::string strPath = g_passwordManager.GetSMBAuthFilename(strAuth);
432 
433   fd = smbc_open(strPath.c_str(), O_RDONLY, 0);
434   //! @todo Run a loop here that prompts for our username/password as appropriate?
435   //! We have the ability to run a file (eg from a button action) without browsing to
436   //! the directory first.  In the case of a password protected share that we do
437   //! not have the authentication information for, the above smbc_open() will have
438   //! returned negative, and the file will not be opened.  While this is not a particular
439   //! likely scenario, we might want to implement prompting for the password in this case.
440   //! The code from SMBDirectory can be used for this.
441   if(fd >= 0)
442     strAuth = strPath;
443 
444   return fd;
445 }
446 */
447 
OpenFile(const CURL & url,std::string & strAuth)448 int CSMBFile::OpenFile(const CURL &url, std::string& strAuth)
449 {
450   int fd = -1;
451   smb.Init();
452 
453   strAuth = GetAuthenticatedPath(CSMB::GetResolvedUrl(url));
454   std::string strPath = strAuth;
455 
456   {
457     CSingleLock lock(smb);
458     if (smb.IsSmbValid())
459       fd = smbc_open(strPath.c_str(), O_RDONLY, 0);
460   }
461 
462   if (fd >= 0)
463     strAuth = strPath;
464 
465   return fd;
466 }
467 
Exists(const CURL & url)468 bool CSMBFile::Exists(const CURL& url)
469 {
470   // we can't open files like smb://file.f or smb://server/file.f
471   // if a file matches the if below return false, it can't exist on a samba share.
472   if (!IsValidFile(url.GetFileName())) return false;
473 
474   smb.Init();
475   std::string strFileName = GetAuthenticatedPath(CSMB::GetResolvedUrl(url));
476 
477   struct stat info;
478 
479   CSingleLock lock(smb);
480   if (!smb.IsSmbValid())
481     return false;
482   int iResult = smbc_stat(strFileName.c_str(), &info);
483 
484   if (iResult < 0) return false;
485   return true;
486 }
487 
Stat(struct __stat64 * buffer)488 int CSMBFile::Stat(struct __stat64* buffer)
489 {
490   if (m_fd == -1)
491     return -1;
492 
493   struct stat tmpBuffer = {0};
494 
495   CSingleLock lock(smb);
496   if (!smb.IsSmbValid())
497     return -1;
498   int iResult = smbc_fstat(m_fd, &tmpBuffer);
499   CUtil::StatToStat64(buffer, &tmpBuffer);
500   return iResult;
501 }
502 
Stat(const CURL & url,struct __stat64 * buffer)503 int CSMBFile::Stat(const CURL& url, struct __stat64* buffer)
504 {
505   smb.Init();
506   std::string strFileName = GetAuthenticatedPath(CSMB::GetResolvedUrl(url));
507   CSingleLock lock(smb);
508 
509   if (!smb.IsSmbValid())
510     return -1;
511   struct stat tmpBuffer = {0};
512   int iResult = smbc_stat(strFileName.c_str(), &tmpBuffer);
513   CUtil::StatToStat64(buffer, &tmpBuffer);
514   return iResult;
515 }
516 
Truncate(int64_t size)517 int CSMBFile::Truncate(int64_t size)
518 {
519   if (m_fd == -1) return 0;
520 /*
521  * This would force us to be dependant on SMBv3.2 which is GPLv3
522  * This is only used by the TagLib writers, which are not currently in use
523  * So log and warn until we implement TagLib writing & can re-implement this better.
524   CSingleLock lock(smb); // Init not called since it has to be "inited" by now
525 
526 #if defined(TARGET_ANDROID)
527   int iResult = 0;
528 #else
529   int iResult = smbc_ftruncate(m_fd, size);
530 #endif
531 */
532   CLog::Log(LOGWARNING, "%s - Warning(smbc_ftruncate called and not implemented)", __FUNCTION__);
533   return 0;
534 }
535 
Read(void * lpBuf,size_t uiBufSize)536 ssize_t CSMBFile::Read(void *lpBuf, size_t uiBufSize)
537 {
538   if (uiBufSize > SSIZE_MAX)
539     uiBufSize = SSIZE_MAX;
540 
541   if (m_fd == -1)
542     return -1;
543 
544   // Some external libs (libass) use test read with zero size and
545   // null buffer pointer to check whether file is readable, but
546   // libsmbclient always return "-1" if called with null buffer
547   // regardless of buffer size.
548   // To overcome this, force return "0" in that case.
549   if (uiBufSize == 0 && lpBuf == NULL)
550     return 0;
551 
552   CSingleLock lock(smb); // Init not called since it has to be "inited" by now
553   if (!smb.IsSmbValid())
554     return -1;
555   smb.SetActivityTime();
556 
557   ssize_t bytesRead = smbc_read(m_fd, lpBuf, (int)uiBufSize);
558 
559   if (m_allowRetry && bytesRead < 0 && errno == EINVAL )
560   {
561     CLog::Log(LOGERROR, "{} - Error( {}, {}, {} ) - Retrying", __FUNCTION__, bytesRead, errno,
562               strerror(errno));
563     bytesRead = smbc_read(m_fd, lpBuf, (int)uiBufSize);
564   }
565 
566   if ( bytesRead < 0 )
567     CLog::Log(LOGERROR, "{} - Error( {}, {}, {} )", __FUNCTION__, bytesRead, errno,
568               strerror(errno));
569 
570   return bytesRead;
571 }
572 
Seek(int64_t iFilePosition,int iWhence)573 int64_t CSMBFile::Seek(int64_t iFilePosition, int iWhence)
574 {
575   if (m_fd == -1) return -1;
576 
577   CSingleLock lock(smb); // Init not called since it has to be "inited" by now
578   if (!smb.IsSmbValid())
579     return -1;
580   smb.SetActivityTime();
581   int64_t pos = smbc_lseek(m_fd, iFilePosition, iWhence);
582 
583   if ( pos < 0 )
584   {
585     CLog::Log(LOGERROR, "%s - Error( %" PRId64", %d, %s )", __FUNCTION__, pos, errno, strerror(errno));
586     return -1;
587   }
588 
589   return pos;
590 }
591 
Close()592 void CSMBFile::Close()
593 {
594   if (m_fd != -1)
595   {
596     CLog::Log(LOGDEBUG,"CSMBFile::Close closing fd %d", m_fd);
597     CSingleLock lock(smb);
598     if (!smb.IsSmbValid())
599       return;
600     smbc_close(m_fd);
601   }
602   m_fd = -1;
603 }
604 
Write(const void * lpBuf,size_t uiBufSize)605 ssize_t CSMBFile::Write(const void* lpBuf, size_t uiBufSize)
606 {
607   if (m_fd == -1) return -1;
608 
609   // lpBuf can be safely casted to void* since xbmc_write will only read from it.
610   CSingleLock lock(smb);
611   if (!smb.IsSmbValid())
612     return -1;
613 
614   return  smbc_write(m_fd, lpBuf, uiBufSize);
615 }
616 
Delete(const CURL & url)617 bool CSMBFile::Delete(const CURL& url)
618 {
619   smb.Init();
620   std::string strFile = GetAuthenticatedPath(CSMB::GetResolvedUrl(url));
621 
622   CSingleLock lock(smb);
623   if (!smb.IsSmbValid())
624     return -1;
625 
626   int result = smbc_unlink(strFile.c_str());
627 
628   if(result != 0)
629     CLog::Log(LOGERROR, "%s - Error( %s )", __FUNCTION__, strerror(errno));
630 
631   return (result == 0);
632 }
633 
Rename(const CURL & url,const CURL & urlnew)634 bool CSMBFile::Rename(const CURL& url, const CURL& urlnew)
635 {
636   smb.Init();
637   std::string strFile = GetAuthenticatedPath(CSMB::GetResolvedUrl(url));
638   std::string strFileNew = GetAuthenticatedPath(CSMB::GetResolvedUrl(urlnew));
639   CSingleLock lock(smb);
640   if (!smb.IsSmbValid())
641     return false;
642 
643   int result = smbc_rename(strFile.c_str(), strFileNew.c_str());
644 
645   if(result != 0)
646     CLog::Log(LOGERROR, "%s - Error( %s )", __FUNCTION__, strerror(errno));
647 
648   return (result == 0);
649 }
650 
OpenForWrite(const CURL & url,bool bOverWrite)651 bool CSMBFile::OpenForWrite(const CURL& url, bool bOverWrite)
652 {
653   m_fileSize = 0;
654 
655   Close();
656 
657   // we can't open files like smb://file.f or smb://server/file.f
658   // if a file matches the if below return false, it can't exist on a samba share.
659   if (!IsValidFile(url.GetFileName())) return false;
660 
661   std::string strFileName = GetAuthenticatedPath(CSMB::GetResolvedUrl(url));
662   CSingleLock lock(smb);
663   if (!smb.IsSmbValid())
664     return false;
665 
666   if (bOverWrite)
667   {
668     CLog::Log(LOGWARNING, "SMBFile::OpenForWrite() called with overwriting enabled! - %s", CURL::GetRedacted(strFileName).c_str());
669     m_fd = smbc_creat(strFileName.c_str(), 0);
670   }
671   else
672   {
673     m_fd = smbc_open(strFileName.c_str(), O_RDWR, 0);
674   }
675 
676   if (m_fd == -1)
677   {
678     // write error to logfile
679     CLog::Log(LOGERROR, "SMBFile->Open: Unable to open file : '%s'\nunix_err:'%x' error : '%s'", CURL::GetRedacted(strFileName).c_str(), errno, strerror(errno));
680     return false;
681   }
682 
683   // We've successfully opened the file!
684   return true;
685 }
686 
IsValidFile(const std::string & strFileName)687 bool CSMBFile::IsValidFile(const std::string& strFileName)
688 {
689   if (strFileName.find('/') == std::string::npos || /* doesn't have sharename */
690       StringUtils::EndsWith(strFileName, "/.") || /* not current folder */
691       StringUtils::EndsWith(strFileName, "/.."))  /* not parent folder */
692       return false;
693   return true;
694 }
695 
GetAuthenticatedPath(const CURL & url)696 std::string CSMBFile::GetAuthenticatedPath(const CURL &url)
697 {
698   CURL authURL(CSMB::GetResolvedUrl(url));
699   CPasswordManager::GetInstance().AuthenticateURL(authURL);
700   return smb.URLEncode(authURL);
701 }
702 
IoControl(EIoControl request,void * param)703 int CSMBFile::IoControl(EIoControl request, void* param)
704 {
705   if (request == IOCTRL_SEEK_POSSIBLE)
706     return 1;
707 
708   if (request == IOCTRL_SET_RETRY)
709   {
710     m_allowRetry = *(bool*) param;
711     return 0;
712   }
713 
714   return -1;
715 }
716 
717