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