1 /*
2 * SessionOptions.cpp
3 *
4 * Copyright (C) 2021 by RStudio, PBC
5 *
6 * Unless you have received this program directly from RStudio pursuant
7 * to the terms of a commercial license agreement with RStudio, then
8 * this program is licensed to you under the terms of version 3 of the
9 * GNU Affero General Public License. This program is distributed WITHOUT
10 * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
11 * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
12 * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
13 *
14 */
15
16 #include <session/SessionOptions.hpp>
17
18 #include <boost/algorithm/string/trim.hpp>
19
20 #include <boost/property_tree/ptree.hpp>
21 #include <boost/property_tree/ini_parser.hpp>
22
23 #include <shared_core/FilePath.hpp>
24 #include <core/ProgramStatus.hpp>
25 #include <shared_core/SafeConvert.hpp>
26 #include <core/system/Crypto.hpp>
27 #include <core/system/System.hpp>
28 #include <core/system/Environment.hpp>
29 #include <core/system/Xdg.hpp>
30
31 #include <shared_core/Error.hpp>
32 #include <core/Log.hpp>
33
34 #include <core/r_util/RProjectFile.hpp>
35 #include <core/r_util/RUserData.hpp>
36 #include <core/r_util/RSessionContext.hpp>
37 #include <core/r_util/RActiveSessions.hpp>
38 #include <core/r_util/RVersionsPosix.hpp>
39
40 #include <monitor/MonitorConstants.hpp>
41
42 #include <r/session/RSession.hpp>
43
44 #include <session/SessionConstants.hpp>
45 #include <session/SessionScopes.hpp>
46 #include <session/projects/SessionProjectSharing.hpp>
47
48 #include "session-config.h"
49
50 using namespace rstudio::core;
51
52 namespace rstudio {
53 namespace session {
54
55 namespace {
56
ensureDefaultDirectory(std::string * pDirectory,const std::string & userHomePath)57 void ensureDefaultDirectory(std::string* pDirectory,
58 const std::string& userHomePath)
59 {
60 if (*pDirectory != "~")
61 {
62 FilePath dir = FilePath::resolveAliasedPath(*pDirectory,
63 FilePath(userHomePath));
64 Error error = dir.ensureDirectory();
65 if (error)
66 {
67 LOG_ERROR(error);
68 *pDirectory = "~";
69 }
70 }
71 }
72
73 } // anonymous namespace
74
options()75 Options& options()
76 {
77 static Options instance;
78 return instance;
79 }
80
read(int argc,char * const argv[],std::ostream & osWarnings)81 core::ProgramStatus Options::read(int argc, char * const argv[], std::ostream& osWarnings)
82 {
83 using namespace boost::program_options;
84
85 // get the shared secret
86 monitorSharedSecret_ = core::system::getenv(kMonitorSharedSecretEnvVar);
87 core::system::unsetenv(kMonitorSharedSecretEnvVar);
88
89 // compute the resource path
90 Error error = core::system::installPath("..", argv[0], &resourcePath_);
91 if (error)
92 {
93 LOG_ERROR_MESSAGE("Unable to determine install path: "+error.getSummary());
94 return ProgramStatus::exitFailure();
95 }
96
97 // detect running in OSX bundle and tweak resource path
98 #ifdef __APPLE__
99 if (resourcePath_.completePath("Info.plist").exists())
100 resourcePath_ = resourcePath_.completePath("Resources");
101 #endif
102
103 // detect running in x86 directory and tweak resource path
104 #ifdef _WIN32
105 if (resourcePath_.completePath("x86").exists())
106 {
107 resourcePath_ = resourcePath_.getParent();
108 }
109 #endif
110
111 // build options
112 options_description runTests("tests");
113 options_description runScript("script");
114 options_description verify("verify");
115 options_description version("version");
116 options_description program("program");
117 options_description log("log");
118 options_description docs("docs");
119 options_description www("www");
120 options_description session("session");
121 options_description allow("allow");
122 options_description r("r");
123 options_description limits("limits");
124 options_description external("external");
125 options_description git("git");
126 options_description user("user");
127 options_description misc("misc");
128 std::string saveActionDefault;
129 int sameSite;
130
131 program_options::OptionsDescription optionsDesc =
132 buildOptions(&runTests, &runScript, &verify, &version, &program, &log, &docs, &www,
133 &session, &allow, &r, &limits, &external, &git, &user, &misc,
134 &saveActionDefault, &sameSite);
135
136 addOverlayOptions(&misc);
137
138 optionsDesc.commandLine.add(verify);
139 optionsDesc.commandLine.add(version);
140 optionsDesc.commandLine.add(runTests);
141 optionsDesc.commandLine.add(runScript);
142 optionsDesc.commandLine.add(program);
143 optionsDesc.commandLine.add(log);
144 optionsDesc.commandLine.add(docs);
145 optionsDesc.commandLine.add(www);
146 optionsDesc.commandLine.add(session);
147 optionsDesc.commandLine.add(allow);
148 optionsDesc.commandLine.add(r);
149 optionsDesc.commandLine.add(limits);
150 optionsDesc.commandLine.add(external);
151 optionsDesc.commandLine.add(git);
152 optionsDesc.commandLine.add(user);
153 optionsDesc.commandLine.add(misc);
154
155 // define groups included in config-file processing
156 optionsDesc.configFile.add(program);
157 optionsDesc.configFile.add(log);
158 optionsDesc.configFile.add(docs);
159 optionsDesc.configFile.add(www);
160 optionsDesc.configFile.add(session);
161 optionsDesc.configFile.add(allow);
162 optionsDesc.configFile.add(r);
163 optionsDesc.configFile.add(limits);
164 optionsDesc.configFile.add(external);
165 optionsDesc.configFile.add(user);
166 optionsDesc.configFile.add(misc);
167
168 // read configuration
169 ProgramStatus status = core::program_options::read(optionsDesc, argc,argv);
170 if (status.exit())
171 return status;
172
173 // make sure the program mode is valid
174 if (programMode_ != kSessionProgramModeDesktop &&
175 programMode_ != kSessionProgramModeServer)
176 {
177 LOG_ERROR_MESSAGE("invalid program mode: " + programMode_);
178 return ProgramStatus::exitFailure();
179 }
180
181 // resolve scope
182 scope_ = r_util::SessionScope::fromProjectId(projectId_, scopeId_);
183 scopeState_ = core::r_util::ScopeValid;
184
185 sameSite_ = static_cast<rstudio::core::http::Cookie::SameSite>(sameSite);
186
187 // call overlay hooks
188 resolveOverlayOptions();
189 std::string errMsg;
190 if (!validateOverlayOptions(&errMsg, osWarnings))
191 {
192 program_options::reportError(errMsg, ERROR_LOCATION);
193 return ProgramStatus::exitFailure();
194 }
195
196 // compute program identity
197 programIdentity_ = "rsession-" + userIdentity_;
198
199 // provide special home path in temp directory if we are verifying
200 bool isLauncherSession = getBoolOverlayOption(kLauncherSessionOption);
201 if (verifyInstallation_ && !isLauncherSession)
202 {
203 // we create a special home directory in server mode (since the
204 // user we are running under might not have a home directory)
205 // we do not do this for launcher sessions since launcher verification
206 // must be run as a specific user with the normal home drive setup
207 if (programMode_ == kSessionProgramModeServer)
208 {
209 verifyInstallationHomeDir_ = "/tmp/rstudio-verify-installation";
210 Error error = FilePath(verifyInstallationHomeDir_).ensureDirectory();
211 if (error)
212 {
213 LOG_ERROR(error);
214 return ProgramStatus::exitFailure();
215 }
216 core::system::setenv("R_USER", verifyInstallationHomeDir_);
217 }
218 }
219
220 // resolve home directory from env vars
221 userHomePath_ = core::system::userHomePath("R_USER|HOME").getAbsolutePath();
222
223 // use XDG data directory (usually ~/.local/share/rstudio, or LOCALAPPDATA
224 // on Windows) as the scratch path
225 userScratchPath_ = core::system::xdg::userDataDir().getAbsolutePath();
226
227 // migrate data from old state directory to new directory
228 error = core::r_util::migrateUserStateIfNecessary(
229 programMode_ == kSessionProgramModeServer ?
230 core::r_util::SessionTypeServer :
231 core::r_util::SessionTypeDesktop);
232 if (error)
233 {
234 LOG_ERROR(error);
235 }
236
237
238 // set HOME if we are in standalone mode (this enables us to reflect
239 // R_USER back into HOME on Linux)
240 if (standalone())
241 core::system::setenv("HOME", userHomePath_);
242
243 // ensure that default working dir and default project dir exist
244 ensureDefaultDirectory(&defaultWorkingDir_, userHomePath_);
245 ensureDefaultDirectory(&deprecatedDefaultProjectDir_, userHomePath_);
246
247 // session timeout seconds is always -1 in desktop mode
248 if (programMode_ == kSessionProgramModeDesktop)
249 timeoutMinutes_ = 0;
250
251 // convert string save action default to intenger
252 if (saveActionDefault == "yes")
253 saveActionDefault_ = r::session::kSaveActionSave;
254 else if (saveActionDefault == "no")
255 saveActionDefault_ = r::session::kSaveActionNoSave;
256 else if (saveActionDefault == "ask" || saveActionDefault.empty())
257 saveActionDefault_ = r::session::kSaveActionAsk;
258 else
259 {
260 program_options::reportWarnings(
261 "Invalid value '" + saveActionDefault + "' for "
262 "session-save-action-default. Valid values are yes, no, and ask.",
263 ERROR_LOCATION);
264 saveActionDefault_ = r::session::kSaveActionAsk;
265 }
266
267 // convert relative paths by completing from the app resource path
268 resolvePath(resourcePath_, &rResourcesPath_);
269 resolvePath(resourcePath_, &wwwLocalPath_);
270 resolvePath(resourcePath_, &wwwSymbolMapsPath_);
271 resolvePath(resourcePath_, &coreRSourcePath_);
272 resolvePath(resourcePath_, &modulesRSourcePath_);
273 resolvePath(resourcePath_, &sessionLibraryPath_);
274 resolvePath(resourcePath_, &sessionPackageArchivesPath_);
275 resolvePostbackPath(resourcePath_, &rpostbackPath_);
276 #ifdef _WIN32
277 resolvePath(resourcePath_, &consoleIoPath_);
278 resolvePath(resourcePath_, &gnudiffPath_);
279 resolvePath(resourcePath_, &gnugrepPath_);
280 resolvePath(resourcePath_, &msysSshPath_);
281 resolvePath(resourcePath_, &sumatraPath_);
282 resolvePath(resourcePath_, &winutilsPath_);
283 resolvePath(resourcePath_, &winptyPath_);
284
285 // winpty.dll lives next to rsession.exe on a full install; otherwise
286 // it lives in a directory named 32 or 64
287 core::FilePath pty(winptyPath_);
288 std::string completion;
289 if (pty.isWithin(resourcePath_))
290 {
291 #ifdef _WIN64
292 completion = "winpty.dll";
293 #else
294 completion = "x86/winpty.dll";
295 #endif
296 }
297 else
298 {
299 #ifdef _WIN64
300 completion = "64/bin/winpty.dll";
301 #else
302 completion = "32/bin/winpty.dll";
303 #endif
304 }
305 winptyPath_ = pty.completePath(completion).getAbsolutePath();
306 #endif // _WIN32
307 resolvePath(resourcePath_, &hunspellDictionariesPath_);
308 resolvePath(resourcePath_, &mathjaxPath_);
309 resolvePath(resourcePath_, &libclangHeadersPath_);
310 resolvePandocPath(resourcePath_, &pandocPath_);
311
312 // rsclang
313 if (libclangPath_ != kDefaultRsclangPath)
314 {
315 libclangPath_ += "/5.0.2";
316 }
317 resolveRsclangPath(resourcePath_, &libclangPath_);
318
319 // shared secret with parent
320 secret_ = core::system::getenv("RS_SHARED_SECRET");
321 /* SECURITY: Need RS_SHARED_SECRET to be available to
322 rpostback. However, we really ought to communicate
323 it in a more secure manner than this, at least on
324 Windows where even within the same user session some
325 processes can have different priviliges (integrity
326 levels) than others. For example, using a named pipe
327 with proper SACL to retrieve the shared secret, where
328 the name of the pipe is in an environment variable. */
329 //core::system::unsetenv("RS_SHARED_SECRET");
330
331 // show user home page
332 showUserHomePage_ = core::system::getenv(kRStudioUserHomePage) == "1";
333 core::system::unsetenv(kRStudioUserHomePage);
334
335 // multi session
336 multiSession_ = (programMode_ == kSessionProgramModeDesktop) ||
337 (core::system::getenv(kRStudioMultiSession) == "1");
338
339 // initial working dir override
340 initialWorkingDirOverride_ = core::system::getenv(kRStudioInitialWorkingDir);
341 core::system::unsetenv(kRStudioInitialWorkingDir);
342
343 // initial environment file override
344 initialEnvironmentFileOverride_ = core::system::getenv(kRStudioInitialEnvironment);
345 core::system::unsetenv(kRStudioInitialEnvironment);
346
347 // project sharing enabled
348 projectSharingEnabled_ =
349 core::system::getenv(kRStudioDisableProjectSharing).empty();
350
351 // initial project (can either be a command line param or via env)
352 r_util::SessionScope scope = sessionScope();
353 if (!scope.empty())
354 {
355 scopeState_ = r_util::validateSessionScope(
356 scope,
357 userHomePath(),
358 userScratchPath(),
359 session::projectIdToFilePath(userScratchPath(),
360 FilePath(getOverlayOption(
361 kSessionSharedStoragePath))),
362 projectSharingEnabled(),
363 &initialProjectPath_);
364 }
365 else
366 {
367 initialProjectPath_ = core::system::getenv(kRStudioInitialProject);
368 core::system::unsetenv(kRStudioInitialProject);
369 }
370
371 // limit rpc client uid
372 limitRpcClientUid_ = -1;
373 std::string limitUid = core::system::getenv(kRStudioLimitRpcClientUid);
374 if (!limitUid.empty())
375 {
376 limitRpcClientUid_ = core::safe_convert::stringTo<int>(limitUid, -1);
377 core::system::unsetenv(kRStudioLimitRpcClientUid);
378 }
379
380 // get R versions path
381 rVersionsPath_ = core::system::getenv(kRStudioRVersionsPath);
382 core::system::unsetenv(kRStudioRVersionsPath);
383
384 // capture default R version environment variables
385 defaultRVersion_ = core::system::getenv(kRStudioDefaultRVersion);
386 core::system::unsetenv(kRStudioDefaultRVersion);
387 defaultRVersionHome_ = core::system::getenv(kRStudioDefaultRVersionHome);
388 core::system::unsetenv(kRStudioDefaultRVersionHome);
389
390 // capture auth environment variables
391 authMinimumUserId_ = 0;
392 if (programMode_ == kSessionProgramModeServer)
393 {
394 authRequiredUserGroup_ = core::system::getenv(kRStudioRequiredUserGroup);
395 core::system::unsetenv(kRStudioRequiredUserGroup);
396
397 authMinimumUserId_ = safe_convert::stringTo<unsigned int>(
398 core::system::getenv(kRStudioMinimumUserId), 100);
399
400 #ifndef _WIN32
401 r_util::setMinUid(authMinimumUserId_);
402 #endif
403 core::system::unsetenv(kRStudioMinimumUserId);
404 }
405
406 // signing key - used for verifying incoming RPC requests
407 // in standalone mode
408 signingKey_ = core::system::getenv(kRStudioSigningKey);
409
410 if (verifySignatures_)
411 {
412 // generate our own signing key to be used when posting back to ourselves
413 // this key is kept secret within this process and any child processes,
414 // and only allows communication from this rsession process and its children
415 error = core::system::crypto::generateRsaKeyPair(&sessionRsaPublicKey_, &sessionRsaPrivateKey_);
416 if (error)
417 LOG_ERROR(error);
418
419 core::system::setenv(kRSessionRsaPublicKey, sessionRsaPublicKey_);
420 core::system::setenv(kRSessionRsaPrivateKey, sessionRsaPrivateKey_);
421 }
422
423 // load cran options from repos.conf
424 FilePath reposFile(rCRANReposFile());
425 rCRANMultipleRepos_ = parseReposConfig(reposFile);
426
427 // return status
428 return status;
429 }
430
parseReposConfig(FilePath reposFile)431 std::string Options::parseReposConfig(FilePath reposFile)
432 {
433 using namespace boost::property_tree;
434
435 if (!reposFile.exists())
436 return "";
437
438 std::shared_ptr<std::istream> pIfs;
439 Error error = FilePath(reposFile).openForRead(pIfs);
440 if (error)
441 {
442 core::program_options::reportError("Unable to open repos file: " + reposFile.getAbsolutePath(),
443 ERROR_LOCATION);
444
445 return "";
446 }
447
448 try
449 {
450 ptree pt;
451 ini_parser::read_ini(reposFile.getAbsolutePath(), pt);
452
453 if (!pt.get_child_optional("CRAN"))
454 {
455 LOG_ERROR_MESSAGE("Repos file " + reposFile.getAbsolutePath() + " is missing CRAN entry.");
456 return "";
457 }
458
459 std::stringstream ss;
460
461 for (ptree::iterator it = pt.begin(); it != pt.end(); it++)
462 {
463 if (it != pt.begin())
464 {
465 ss << "|";
466 }
467
468 ss << it->first << "|" << it->second.get_value<std::string>();
469 }
470
471 return ss.str();
472 }
473 catch(const std::exception& e)
474 {
475 core::program_options::reportError(
476 "Error reading " + reposFile.getAbsolutePath() + ": " + std::string(e.what()),
477 ERROR_LOCATION);
478
479 return "";
480 }
481 }
482
getBoolOverlayOption(const std::string & name)483 bool Options::getBoolOverlayOption(const std::string& name)
484 {
485 std::string optionValue = getOverlayOption(name);
486 return boost::algorithm::trim_copy(optionValue) == "1";
487 }
488
resolvePath(const FilePath & resourcePath,std::string * pPath)489 void Options::resolvePath(const FilePath& resourcePath,
490 std::string* pPath)
491 {
492 if (!pPath->empty())
493 *pPath = resourcePath.completePath(*pPath).getAbsolutePath();
494 }
495
496 #ifdef __APPLE__
497
resolvePostbackPath(const FilePath & resourcePath,std::string * pPath)498 void Options::resolvePostbackPath(const FilePath& resourcePath,
499 std::string* pPath)
500 {
501 // On OSX we keep the postback scripts over in the MacOS directory
502 // rather than in the Resources directory -- make this adjustment
503 // when the default postback path has been passed
504 if (*pPath == kDefaultPostbackPath && programMode() == kSessionProgramModeDesktop)
505 {
506 FilePath path = resourcePath.getParent().completePath("MacOS/postback/rpostback");
507 *pPath = path.getAbsolutePath();
508 }
509 else
510 {
511 resolvePath(resourcePath, pPath);
512 }
513 }
514
resolvePandocPath(const FilePath & resourcePath,std::string * pPath)515 void Options::resolvePandocPath(const FilePath& resourcePath,
516 std::string* pPath)
517 {
518 if (*pPath == kDefaultPandocPath && programMode() == kSessionProgramModeDesktop)
519 {
520 FilePath path = resourcePath.getParent().completePath("MacOS/pandoc");
521 *pPath = path.getAbsolutePath();
522 }
523 else
524 {
525 resolvePath(resourcePath, pPath);
526 }
527 }
528
resolveRsclangPath(const FilePath & resourcePath,std::string * pPath)529 void Options::resolveRsclangPath(const FilePath& resourcePath,
530 std::string* pPath)
531 {
532 if (*pPath == kDefaultRsclangPath && programMode() == kSessionProgramModeDesktop)
533 {
534 FilePath path = resourcePath.getParent().completePath("MacOS/rsclang");
535 *pPath = path.getAbsolutePath();
536 }
537 else
538 {
539 resolvePath(resourcePath, pPath);
540 }
541 }
542
543 #else
544
resolvePostbackPath(const FilePath & resourcePath,std::string * pPath)545 void Options::resolvePostbackPath(const FilePath& resourcePath,
546 std::string* pPath)
547 {
548 resolvePath(resourcePath, pPath);
549 }
550
resolvePandocPath(const FilePath & resourcePath,std::string * pPath)551 void Options::resolvePandocPath(const FilePath& resourcePath,
552 std::string* pPath)
553 {
554 resolvePath(resourcePath, pPath);
555 }
556
resolveRsclangPath(const FilePath & resourcePath,std::string * pPath)557 void Options::resolveRsclangPath(const FilePath& resourcePath,
558 std::string* pPath)
559 {
560 resolvePath(resourcePath, pPath);
561 }
562 #endif
563
564 } // namespace session
565 } // namespace rstudio
566