OLD | NEW |
---|---|
(Empty) | |
1 // Copyright (c) 2011 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 #include "vpn-manager/ipsec_manager.h" | |
6 | |
7 #include <arpa/inet.h> // for inet_ntop and inet_pton | |
8 #include <grp.h> | |
9 #include <netdb.h> // for getaddrinfo | |
10 #include <sys/types.h> | |
11 #include <sys/wait.h> | |
12 #include <unistd.h> | |
13 | |
14 #include <string> | |
15 #include <vector> | |
16 | |
17 #include "base/eintr_wrapper.h" | |
18 #include "base/file_util.h" | |
19 #include "base/logging.h" | |
20 #include "base/string_util.h" | |
21 #include "chromeos/process.h" | |
22 #include "gflags/gflags.h" | |
23 | |
24 #pragma GCC diagnostic ignored "-Wstrict-aliasing" | |
25 DEFINE_int32(ipsec_timeout, 10, "timeout for ipsec to be established"); | |
26 DEFINE_string(leftprotoport, "17/1701", "client protocol/port"); | |
27 DEFINE_bool(pfs, false, "pfs"); | |
28 DEFINE_bool(rekey, false, "rekey"); | |
29 DEFINE_string(rightprotoport, "17/1701", "server protocol/port"); | |
30 #pragma GCC diagnostic error "-Wstrict-aliasing" | |
31 | |
32 const char kIpsecConnectionName[] = "ipsec_managed"; | |
33 const char kIpsecGroupName[] = "ipsec"; | |
34 const char kIpsecRunPath[] = "/var/run/ipsec"; | |
35 const char kIpsecUpFile[] = "/var/run/ipsec/up"; | |
36 const char kIpsecServiceName[] = "ipsec"; | |
37 const char kStarterPidFile[] = "/var/run/starter.pid"; | |
38 const mode_t kIpsecRunPathMode = (S_IRUSR | S_IWUSR | S_IXUSR | | |
39 S_IRGRP | S_IWGRP | S_IXGRP); | |
40 const char kStatefulContainer[] = "/mnt/stateful_partition/etc"; | |
41 | |
42 // Give IPsec layer 2 seconds to shut down before killing it. | |
43 const int kTermTimeout = 2; | |
44 | |
45 using ::chromeos::Process; | |
46 using ::chromeos::ProcessImpl; | |
47 | |
48 IpsecManager::IpsecManager() | |
49 : ServiceManager(kIpsecServiceName), | |
50 force_local_address_(NULL), | |
51 output_fd_(-1), | |
52 ike_version_(0), | |
53 ipsec_group_(0), | |
54 stateful_container_(kStatefulContainer), | |
55 ipsec_run_path_(kIpsecRunPath), | |
56 ipsec_up_file_(kIpsecUpFile), | |
57 starter_pid_file_(kStarterPidFile), | |
58 starter_(new ProcessImpl) { | |
59 } | |
60 | |
61 bool IpsecManager::Initialize(int ike_version, | |
62 const std::string& remote_address, | |
63 const std::string& psk_file, | |
64 const std::string& server_ca_file, | |
65 const std::string& client_key_file, | |
66 const std::string& client_cert_file) { | |
67 if (remote_address.empty()) { | |
68 LOG(ERROR) << "Missing remote address to IPsec layer"; | |
69 return false; | |
70 } | |
71 remote_address_ = remote_address; | |
72 | |
73 if (psk_file.empty()) { | |
74 if (server_ca_file.empty() && client_key_file.empty() && | |
75 client_cert_file.empty()) { | |
76 LOG(ERROR) << "Must specify either PSK or certificates for IPsec layer"; | |
77 return false; | |
78 } | |
79 | |
80 // Must be a certificate based connection. | |
81 if (!file_util::PathExists(FilePath(server_ca_file))) { | |
82 LOG(ERROR) << "Invalid server CA file for IPsec layer: " | |
83 << server_ca_file; | |
84 return false; | |
85 } | |
86 server_ca_file_ = server_ca_file; | |
87 | |
88 if (!file_util::PathExists(FilePath(client_key_file))) { | |
89 LOG(ERROR) << "Invalid client key file for IPsec layer: " | |
90 << client_key_file; | |
91 return false; | |
92 } | |
93 client_key_file_ = client_key_file; | |
94 | |
95 if (!file_util::PathExists(FilePath(client_cert_file))) { | |
96 LOG(ERROR) << "Invalid client certificate file for IPsec layer: " | |
97 << client_key_file; | |
98 return false; | |
99 } | |
100 client_cert_file_ = client_cert_file; | |
101 } else { | |
102 if (!server_ca_file.empty() || | |
103 !client_key_file.empty() || | |
104 !client_cert_file.empty()) { | |
105 LOG(ERROR) << "Specified both PSK and certificates for IPsec layer"; | |
106 return false; | |
107 } | |
108 if (!file_util::PathExists(FilePath(psk_file))) { | |
109 LOG(ERROR) << "Invalid PSK file for IPsec layer: " << psk_file; | |
110 return false; | |
111 } | |
112 psk_file_ = psk_file; | |
113 } | |
114 | |
115 if (ike_version != 1 && ike_version != 2) { | |
116 LOG(ERROR) << "Unsupported IKE version" << ike_version; | |
117 return false; | |
118 } | |
119 ike_version_ = ike_version; | |
120 | |
121 file_util::Delete(FilePath(kIpsecUpFile), false); | |
122 | |
123 return true; | |
124 } | |
125 | |
126 bool IpsecManager::GetLocalAddressForRemote(const std::string& remote_address_te xt, | |
petkov
2011/03/11 19:12:53
80 chars
kmixter1
2011/03/11 20:53:04
Done.
| |
127 std::string* local_address_text) { | |
128 static const char kService[] = "80"; | |
129 if (force_local_address_ != NULL) { | |
130 *local_address_text = force_local_address_; | |
131 return true; | |
132 } | |
133 struct addrinfo *remote_address; | |
134 int s = getaddrinfo(remote_address_text.c_str(), kService, NULL, | |
135 &remote_address); | |
136 if (s != 0) { | |
137 LOG(ERROR) << "getaddrinfo failed: " << gai_strerror(s); | |
138 return false; | |
139 } | |
140 int sock = HANDLE_EINTR(socket(AF_INET, SOCK_DGRAM, 0)); | |
141 if (sock < 0) { | |
142 LOG(ERROR) << "Unable to create socket"; | |
143 return false; | |
144 } | |
145 if (HANDLE_EINTR( | |
146 connect(sock, remote_address->ai_addr, sizeof(sockaddr))) != 0) { | |
147 LOG(ERROR) << "Unable to connect"; | |
148 HANDLE_EINTR(close(sock)); | |
149 return false; | |
150 } | |
151 bool result = false; | |
152 struct sockaddr local_address; | |
153 socklen_t addr_len = sizeof(local_address); | |
154 char str[INET6_ADDRSTRLEN] = { 0 }; | |
155 if (getsockname(sock, &local_address, &addr_len) != 0) { | |
156 int saved_errno = errno; | |
157 LOG(ERROR) << "getsockname failed on socket connecting to " | |
158 << remote_address_text << ": " << saved_errno; | |
159 goto error_label; | |
petkov
2011/03/11 19:12:53
You could define a ScopedSocketCloser and ScopedAd
kmixter1
2011/03/11 20:53:04
Noted. If I need to write this kind of code again
| |
160 } | |
161 // convert local_address to local_address_text. | |
162 switch (local_address.sa_family) { | |
163 case AF_INET: | |
164 if (!inet_ntop(AF_INET, &reinterpret_cast<sockaddr_in*>( | |
165 &local_address)->sin_addr, str, INET6_ADDRSTRLEN)) { | |
166 LOG(ERROR) << "inet_ntop failed on " << remote_address_text; | |
167 goto error_label; | |
168 } | |
169 break; | |
170 case AF_INET6: | |
171 if (!inet_ntop(AF_INET6, &reinterpret_cast<sockaddr_in6*>( | |
172 &local_address)->sin6_addr, str, INET6_ADDRSTRLEN)) { | |
173 LOG(ERROR) << "inet_ntop failed on " << remote_address_text; | |
174 goto error_label; | |
175 } | |
176 break; | |
177 default: | |
178 LOG(ERROR) << "Unknown address family converting " << remote_address_text; | |
179 goto error_label; | |
180 } | |
181 *local_address_text = str; | |
182 LOG(INFO) << "Remote address " << remote_address_text << " has local address " | |
183 << *local_address_text; | |
184 result = true; | |
185 | |
186 error_label: | |
187 HANDLE_EINTR(close(sock)); | |
188 freeaddrinfo(remote_address); | |
189 return result; | |
190 } | |
191 | |
192 bool IpsecManager::FormatPsk(const FilePath& input_file, | |
193 std::string* formatted) { | |
194 std::string psk; | |
195 if (!file_util::ReadFileToString(input_file, &psk)) { | |
196 LOG(ERROR) << "Unable to read PSK from " << input_file.value(); | |
197 return false; | |
198 } | |
199 std::string local_address; | |
200 if (!GetLocalAddressForRemote(remote_address_, &local_address)) { | |
201 LOG(ERROR) << "Local IP address could not be determined for PSK mode"; | |
202 return false; | |
203 } | |
204 TrimWhitespaceASCII(psk, TRIM_TRAILING, &psk); | |
205 *formatted = | |
206 StringPrintf("%s %s : PSK \"%s\"\n", local_address.c_str(), | |
207 remote_address_.c_str(), psk.c_str()); | |
208 return true; | |
209 } | |
210 | |
211 void IpsecManager::KillCurrentlyRunning() { | |
212 if (!file_util::PathExists(FilePath(starter_pid_file_))) | |
213 return; | |
214 starter_->ResetPidByFile(starter_pid_file_); | |
215 if (Process::ProcessExists(starter_->pid())) | |
216 starter_->Reset(0); | |
217 else | |
218 starter_->Release(); | |
219 file_util::Delete(FilePath(starter_pid_file_), false); | |
220 } | |
221 | |
222 bool IpsecManager::StartStarter() { | |
223 KillCurrentlyRunning(); | |
224 LOG(INFO) << "Starting starter"; | |
225 starter_->AddArg(IPSEC_STARTER); | |
226 starter_->AddArg("--nofork"); | |
227 starter_->RedirectUsingPipe(STDERR_FILENO, false); | |
228 if (!starter_->Start()) { | |
229 LOG(ERROR) << "Starter did not start successfully"; | |
230 return false; | |
231 } | |
232 output_fd_ = starter_->GetPipe(STDERR_FILENO); | |
233 pid_t starter_pid = starter_->pid(); | |
234 LOG(INFO) << "Starter started as pid " << starter_pid; | |
235 ipsec_prefix_ = StringPrintf("ipsec[%d]: ", starter_pid); | |
236 return true; | |
237 } | |
238 | |
239 inline void AppendBoolSetting(std::string* config, const char* key, | |
240 bool value) { | |
241 config->append(StringPrintf("\t%s=%s\n", key, value ? "yes" : "no")); | |
242 } | |
243 | |
244 inline void AppendStringSetting(std::string* config, const char* key, | |
245 const std::string& value) { | |
246 config->append(StringPrintf("\t%s=%s\n", key, value.c_str())); | |
247 } | |
248 | |
249 inline void AppendIntSetting(std::string* config, const char* key, | |
250 int value) { | |
251 config->append(StringPrintf("\t%s=%d\n", key, value)); | |
252 } | |
253 | |
254 std::string IpsecManager::FormatStarterConfigFile() { | |
255 std::string config; | |
256 config.append("config setup\n"); | |
257 if (ike_version_ == 1) { | |
258 AppendBoolSetting(&config, "charonstart", false); | |
259 } else { | |
260 AppendBoolSetting(&config, "plutostart", false); | |
261 } | |
262 config.append("conn managed\n"); | |
263 AppendStringSetting(&config, "keyexchange", | |
264 ike_version_ == 1 ? "ikev1" : "ikev2"); | |
265 if (!psk_file_.empty()) AppendStringSetting(&config, "authby", "psk"); | |
266 AppendBoolSetting(&config, "pfs", FLAGS_pfs); | |
267 AppendBoolSetting(&config, "rekey", FLAGS_rekey); | |
268 AppendStringSetting(&config, "left", "%defaultroute"); | |
269 AppendStringSetting(&config, "leftprotoport", FLAGS_leftprotoport); | |
270 AppendStringSetting(&config, "leftupdown", IPSEC_UPDOWN); | |
271 AppendStringSetting(&config, "right", remote_address_); | |
272 AppendStringSetting(&config, "rightprotoport", FLAGS_rightprotoport); | |
273 AppendStringSetting(&config, "auto", "start"); | |
274 return config; | |
275 } | |
276 | |
277 bool IpsecManager::SetIpsecGroup(const FilePath& file_path) { | |
278 return chown(file_path.value().c_str(), getuid(), ipsec_group_) == 0; | |
279 } | |
280 | |
281 bool IpsecManager::WriteConfigFiles() { | |
282 // We need to keep secrets in /mnt/stateful_partition/etc for now | |
283 // because pluto loses permissions to /home/chronos before it tries | |
284 // reading secrets. | |
285 // TODO(kmixter): write this via a fifo. | |
286 FilePath secrets_path_ = FilePath(stateful_container_). | |
287 Append("ipsec.secrets"); | |
288 file_util::Delete(secrets_path_, false); | |
289 if (!psk_file_.empty()) { | |
290 std::string formatted; | |
291 if (!FormatPsk(FilePath(psk_file_), &formatted)) { | |
292 LOG(ERROR) << "Unable to create secrets contents"; | |
293 return false; | |
294 } | |
295 if (!file_util::WriteFile(secrets_path_, formatted.c_str(), | |
296 formatted.length()) || | |
297 !SetIpsecGroup(secrets_path_)) { | |
298 LOG(ERROR) << "Unable to write secrets file " << secrets_path_.value(); | |
299 return false; | |
300 } | |
301 } else { | |
302 LOG(FATAL) << "Certificate mode not yet implemented"; | |
303 } | |
304 FilePath starter_config_path = temp_path()->Append("ipsec.conf"); | |
305 std::string starter_config = FormatStarterConfigFile(); | |
306 if (!file_util::WriteFile(starter_config_path, starter_config.c_str(), | |
307 starter_config.size()) || | |
308 !SetIpsecGroup(starter_config_path)) { | |
309 LOG(ERROR) << "Unable to write ipsec config files"; | |
310 return false; | |
311 } | |
312 FilePath config_symlink_path = FilePath(stateful_container_). | |
313 Append("ipsec.conf"); | |
314 // Use unlink to remove the symlink directly since file_util::Delete | |
315 // cannot delete dangling symlinks. | |
316 unlink(config_symlink_path.value().c_str()); | |
317 if (file_util::PathExists(config_symlink_path)) { | |
318 LOG(ERROR) << "Unable to remove existing file " | |
319 << config_symlink_path.value(); | |
320 return false; | |
321 } | |
322 if (symlink(starter_config_path.value().c_str(), | |
323 config_symlink_path.value().c_str()) < 0) { | |
324 int saved_errno = errno; | |
325 LOG(ERROR) << "Unable to symlink config file " | |
326 << config_symlink_path.value() << " -> " | |
327 << starter_config_path.value() << ": " << saved_errno; | |
328 return false; | |
329 } | |
330 return true; | |
331 } | |
332 | |
333 bool IpsecManager::CreateIpsecRunDirectory() { | |
334 if (!file_util::CreateDirectory(FilePath(ipsec_run_path_)) || | |
335 !SetIpsecGroup(FilePath(ipsec_run_path_)) || | |
336 chmod(ipsec_run_path_.c_str(), kIpsecRunPathMode) != 0) { | |
337 LOG(ERROR) << "Unable to create " << ipsec_run_path_; | |
338 return false; | |
339 } | |
340 return true; | |
341 } | |
342 | |
343 bool IpsecManager::Start() { | |
344 if (!ipsec_group_) { | |
345 struct group group_buffer; | |
346 struct group* group_result = NULL; | |
347 char buffer[256]; | |
348 if (getgrnam_r(kIpsecGroupName, &group_buffer, buffer, | |
349 sizeof(buffer), &group_result) != 0 || !group_result) { | |
350 LOG(ERROR) << "Cannot find group id for " << kIpsecGroupName; | |
351 return false; | |
352 } | |
353 ipsec_group_ = group_result->gr_gid; | |
354 DLOG(INFO) << "Using ipsec group " << ipsec_group_; | |
355 } | |
356 if (!WriteConfigFiles()) | |
357 return false; | |
358 if (!CreateIpsecRunDirectory()) | |
359 return false; | |
360 if (!StartStarter()) | |
361 return false; | |
362 | |
363 start_ticks_ = base::TimeTicks::Now(); | |
364 | |
365 return true; | |
366 } | |
367 | |
368 int IpsecManager::Poll() { | |
369 if (is_running()) return -1; | |
370 if (start_ticks_.is_null()) return -1; | |
371 if (!file_util::PathExists(FilePath(ipsec_up_file_))) { | |
372 if (base::TimeTicks::Now() - start_ticks_ > | |
373 base::TimeDelta::FromSeconds(FLAGS_ipsec_timeout)) { | |
374 LOG(ERROR) << "IPsec connection timed out"; | |
375 OnStopped(false); | |
376 // Poll in 1 second in order to check exit conditions. | |
377 } | |
378 return 1000; | |
379 } | |
380 | |
381 // This indicates that the connection came up successfully. | |
382 LOG(INFO) << "IPsec connection now up"; | |
383 OnStarted(); | |
384 return -1; | |
385 } | |
386 | |
387 void IpsecManager::ProcessOutput() { | |
388 ServiceManager::WriteFdToSyslog(output_fd_, ipsec_prefix_, | |
389 &partial_output_line_); | |
390 } | |
391 | |
392 bool IpsecManager::IsChild(pid_t pid) { | |
393 return pid == starter_->pid(); | |
394 } | |
395 | |
396 void IpsecManager::Stop() { | |
397 if (starter_->pid() == 0) { | |
398 return; | |
399 } | |
400 | |
401 if (!starter_->Kill(SIGTERM, kTermTimeout)) { | |
402 starter_->Kill(SIGKILL, 0); | |
403 OnStopped(true); | |
404 return; | |
405 } | |
406 OnStopped(false); | |
407 } | |
OLD | NEW |