Upstream version 8.36.161.0
[platform/framework/web/crosswalk.git] / src / third_party / chromite / lib / sudo.py
1 # Copyright (c) 2011-2012 The Chromium OS Authors. All rights reserved.
2 # Use of this source code is governed by a BSD-style license that can be
3 # found in the LICENSE file.
4
5 """Helper methods and classes related to managing sudo."""
6
7 import os
8 import sys
9 import errno
10 import signal
11 import subprocess
12 import cros_build_lib
13
14
15 class SudoKeepAlive(cros_build_lib.MasterPidContextManager):
16   """Keep sudo auth cookie fresh.
17
18   This refreshes the sudo auth cookie; this is implemented this
19   way to ensure that sudo has access to both invoking tty, and
20   will update the user's tty-less cookie.
21   see crosbug/18393.
22   """
23
24   def __init__(self, ttyless_sudo=True, repeat_interval=4):
25     """Run sudo with a noop, to reset the sudo timestamp.
26
27     Args:
28       ttyless_sudo: Whether to update the tty-less cookie.
29       repeat_interval: In minutes, the frequency to run the update.
30     """
31     cros_build_lib.MasterPidContextManager.__init__(self)
32     self._ttyless_sudo = ttyless_sudo
33     self._repeat_interval = repeat_interval
34     self._proc = None
35     self._existing_keepalive_value = None
36
37   @staticmethod
38   def _IdentifyTTY():
39     for source in (sys.stdin, sys.stdout, sys.stderr):
40       try:
41         return os.ttyname(source.fileno())
42       except EnvironmentError as e:
43         if e.errno not in (errno.EINVAL, errno.ENOTTY):
44           raise
45
46     return 'unknown'
47
48   def _DaemonNeeded(self):
49     """Discern which TTYs require sudo keep alive code.
50
51     Returns:
52       A string representing the set of ttys we need daemons for.
53       This will be the empty string if no daemon is needed.
54     """
55     existing = os.environ.get('CROS_SUDO_KEEP_ALIVE')
56     needed = set([self._IdentifyTTY()])
57     if self._ttyless_sudo:
58       needed.add('unknown')
59     if existing is not None:
60       needed -= set(existing.split(':'))
61     return ':'.join(needed)
62
63   def _enter(self):
64     if os.getuid() == 0:
65       cros_build_lib.Die('This script cannot be run as root.')
66
67     start_for_tty = self._DaemonNeeded()
68     if not start_for_tty:
69       # Daemon is already started.
70       return
71
72     # Note despite the impulse to use 'sudo -v' instead of 'sudo true', the
73     # builder's sudoers configuration is slightly whacked resulting in it
74     # asking for password everytime.  As such use 'sudo true' instead.
75     cmds = ['sudo -n true 2>/dev/null',
76             'sudo -n true < /dev/null > /dev/null 2>&1']
77
78     # First check to see if we're already authed.  If so, then we don't
79     # need to prompt the user for their password.
80     for idx, cmd in enumerate(cmds):
81       ret = cros_build_lib.RunCommand(
82           cmd, print_cmd=False, shell=True, error_code_ok=True)
83
84       if ret.returncode != 0:
85         tty_msg = 'Please disable tty_tickets using these instructions: %s'
86         if os.path.exists("/etc/goobuntu"):
87           url = 'https://goto.google.com/chromeos-sudoers'
88         else:
89           url = 'https://goo.gl/fz9YW'
90
91         # If ttyless sudo is not strictly required for this script, don't
92         # prompt for a password a second time. Instead, just complain.
93         if idx > 0:
94           cros_build_lib.Error(tty_msg, url)
95           if not self._ttyless_sudo:
96             break
97
98         # We need to go interactive and allow sudo to ask for credentials.
99         interactive_cmd = cmd.replace(' -n', '')
100         cros_build_lib.RunCommand(interactive_cmd, shell=True, print_cmd=False)
101
102         # Verify that sudo access is set up properly.
103         try:
104           cros_build_lib.RunCommand(cmd, shell=True, print_cmd=False)
105         except cros_build_lib.RunCommandError:
106           if idx == 0:
107             raise
108           cros_build_lib.Die('tty_tickets must be disabled. ' + tty_msg, url)
109
110     # Anything other than a timeout results in us shutting down.
111     repeat_interval = self._repeat_interval * 60
112     cmd = ('while :; do read -t %i; [ $? -le 128 ] && exit; %s; done' %
113            (repeat_interval, '; '.join(cmds)))
114
115     def ignore_sigint():
116       # We don't want our sudo process shutdown till we shut it down;
117       # since it's part of the session group it however gets SIGINT.
118       # Thus suppress it (which bash then inherits).
119       signal.signal(signal.SIGINT, signal.SIG_IGN)
120
121     self._proc = subprocess.Popen(['bash', '-c', cmd], shell=False,
122                                   close_fds=True, preexec_fn=ignore_sigint,
123                                   stdin=subprocess.PIPE)
124
125     self._existing_keepalive_value = os.environ.get('CROS_SUDO_KEEP_ALIVE')
126     os.environ['CROS_SUDO_KEEP_ALIVE'] = start_for_tty
127
128   # pylint: disable=W0613
129   def _exit(self, exc_type, exc_value, traceback):
130     if self._proc is None:
131       return
132
133     try:
134       self._proc.terminate()
135       self._proc.wait()
136     except EnvironmentError as e:
137       if e.errno != errno.ESRCH:
138         raise
139
140     if self._existing_keepalive_value is not None:
141       os.environ['CROS_SUDO_KEEP_ALIVE'] = self._existing_keepalive_value
142     else:
143       os.environ.pop('CROS_SUDO_KEEP_ALIVE', None)
144
145
146 def SetFileContents(path, value, cwd=None):
147   """Set a given filepath contents w/ the passed in value."""
148   cros_build_lib.SudoRunCommand(['tee', path], redirect_stdout=True,
149                                 print_cmd=False, input=value, cwd=cwd)