applied the except and raise fixers to the master branch to close the gap with py3
[nepi.git] / src / nepi / util / environ.py
1 #
2 #    NEPI, a framework to manage network experiments
3 #    Copyright (C) 2013 INRIA
4 #
5 #    This program is free software: you can redistribute it and/or modify
6 #    it under the terms of the GNU General Public License version 2 as
7 #    published by the Free Software Foundation;
8 #
9 #    This program is distributed in the hope that it will be useful,
10 #    but WITHOUT ANY WARRANTY; without even the implied warranty of
11 #    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12 #    GNU General Public License for more details.
13 #
14 #    You should have received a copy of the GNU General Public License
15 #    along with this program.  If not, see <http://www.gnu.org/licenses/>.
16 #
17 # Author: Alina Quereilhac <alina.quereilhac@inria.fr>
18 #         Martin Ferrari <martin.ferrari@inria.fr>
19
20
21
22 import ctypes
23 import imp
24 import sys
25
26 import os, os.path, re, signal, shutil, socket, subprocess, tempfile
27
28 __all__ =  ["python", "ssh_path"]
29 __all__ += ["rsh", "tcpdump_path", "sshd_path"]
30 __all__ += ["execute", "backticks"]
31
32
33 # Unittest from Python 2.6 doesn't have these decorators
34 def _bannerwrap(f, text):
35     name = f.__name__
36     def banner(*args, **kwargs):
37         sys.stderr.write("*** WARNING: Skipping test %s: `%s'\n" %
38                 (name, text))
39         return None
40     return banner
41
42 def skip(text):
43     return lambda f: _bannerwrap(f, text)
44
45 def skipUnless(cond, text):
46     return (lambda f: _bannerwrap(f, text)) if not cond else lambda f: f
47
48 def skipIf(cond, text):
49     return (lambda f: _bannerwrap(f, text)) if cond else lambda f: f
50
51 def find_bin(name, extra_path = None):
52     search = []
53     if "PATH" in os.environ:
54         search += os.environ["PATH"].split(":")
55     for pref in ("/", "/usr/", "/usr/local/"):
56         for d in ("bin", "sbin"):
57             search.append(pref + d)
58     if extra_path:
59         search += extra_path
60
61     for d in search:
62             try:
63                 os.stat(d + "/" + name)
64                 return d + "/" + name
65             except OSError as e:
66                 if e.errno != os.errno.ENOENT:
67                     raise
68     return None
69
70 def find_bin_or_die(name, extra_path = None):
71     r = find_bin(name)
72     if not r:
73         raise RuntimeError(("Cannot find `%s' command, impossible to " +
74                 "continue.") % name)
75     return r
76
77 def find_bin(name, extra_path = None):
78     search = []
79     if "PATH" in os.environ:
80         search += os.environ["PATH"].split(":")
81     for pref in ("/", "/usr/", "/usr/local/"):
82         for d in ("bin", "sbin"):
83             search.append(pref + d)
84     if extra_path:
85         search += extra_path
86
87     for d in search:
88             try:
89                 os.stat(d + "/" + name)
90                 return d + "/" + name
91             except OSError as e:
92                 if e.errno != os.errno.ENOENT:
93                     raise
94     return None
95
96 ssh_path = find_bin_or_die("ssh")
97 python_path = find_bin_or_die("python")
98
99 # Optional tools
100 rsh_path = find_bin("rsh")
101 tcpdump_path = find_bin("tcpdump")
102 sshd_path = find_bin("sshd")
103
104 def execute(cmd):
105     # FIXME: create a global debug variable
106     #print "[pid %d]" % os.getpid(), " ".join(cmd)
107     with open("/dev/null", "r+") as null:
108         p = subprocess.Popen(cmd, stdout = null, stderr = subprocess.PIPE)
109         out, err = p.communicate()
110         if p.returncode != 0:
111             raise RuntimeError("Error executing `%s': %s" % (" ".join(cmd), err))
112
113 def backticks(cmd):
114     p = subprocess.Popen(cmd, stdout = subprocess.PIPE,
115             stderr = subprocess.PIPE)
116     out, err = p.communicate()
117     if p.returncode != 0:
118         raise RuntimeError("Error executing `%s': %s" % (" ".join(cmd), err))
119     return out
120
121
122 # SSH stuff
123
124 def gen_ssh_keypair(filename):
125     ssh_keygen = nepi.util.environ.find_bin_or_die("ssh-keygen")
126     args = [ssh_keygen, '-q', '-N', '', '-f', filename]
127     assert subprocess.Popen(args).wait() == 0
128     return filename, "%s.pub" % filename
129
130 def add_key_to_agent(filename):
131     ssh_add = nepi.util.environ.find_bin_or_die("ssh-add")
132     args = [ssh_add, filename]
133     with open("/dev/null", "w") as null:
134         assert subprocess.Popen(args, stderr = null).wait() == 0
135
136 def get_free_port():
137     s = socket.socket()
138     s.bind(("127.0.0.1", 0))
139     port = s.getsockname()[1]
140     return port
141
142 _SSH_CONF = """ListenAddress 127.0.0.1:%d
143 Protocol 2
144 HostKey %s
145 UsePrivilegeSeparation no
146 PubkeyAuthentication yes
147 PasswordAuthentication no
148 AuthorizedKeysFile %s
149 UsePAM no
150 AllowAgentForwarding yes
151 PermitRootLogin yes
152 StrictModes no
153 PermitUserEnvironment yes
154 """
155
156 def gen_sshd_config(filename, port, server_key, auth_keys):
157     with open(filename, "w") as conf:
158         text = _SSH_CONF % (port, server_key, auth_keys)
159         conf.write(text)
160     return filename
161
162 def gen_auth_keys(pubkey, output, environ):
163     #opts = ['from="127.0.0.1/32"'] # fails in stupid yans setup
164     opts = []
165     for k, v in environ.items():
166         opts.append('environment="%s=%s"' % (k, v))
167
168     with open(pubkey) as f:
169         lines = f.readlines()
170     pubkey = lines[0].split()[0:2]
171     with open(output, "w") as out:
172         out.write("%s %s %s\n" % (",".join(opts), pubkey[0], pubkey[1]))
173     return output
174
175 def start_ssh_agent():
176     ssh_agent = nepi.util.environ.find_bin_or_die("ssh-agent")
177     proc = subprocess.Popen([ssh_agent], stdout = subprocess.PIPE)
178     (out, foo) = proc.communicate()
179     assert proc.returncode == 0
180     d = {}
181     for l in out.split("\n"):
182         match = re.search("^(\w+)=([^ ;]+);.*", l)
183         if not match:
184             continue
185         k, v = match.groups()
186         os.environ[k] = v
187         d[k] = v
188     return d
189
190 def stop_ssh_agent(data):
191     # No need to gather the pid, ssh-agent knows how to kill itself; after we
192     # had set up the environment
193     ssh_agent = nepi.util.environ.find_bin_or_die("ssh-agent")
194     with open("/dev/null", "w") as null:
195         proc = subprocess.Popen([ssh_agent, "-k"], stdout = null)
196         assert proc.wait() == 0
197     for k in data:
198         del os.environ[k]
199