PageRenderTime 44ms CodeModel.GetById 16ms RepoModel.GetById 0ms app.codeStats 0ms

/app/server/vendor/rugged/vendor/libgit2/src/transports/ssh.c

https://gitlab.com/hwhelchel/sonic-pi
C | 617 lines | 458 code | 136 blank | 23 comment | 78 complexity | 5389609b5915023ace6976a721deeb3c MD5 | raw file
  1. /*
  2. * Copyright (C) the libgit2 contributors. All rights reserved.
  3. *
  4. * This file is part of libgit2, distributed under the GNU GPL v2 with
  5. * a Linking Exception. For full terms see the included COPYING file.
  6. */
  7. #include "git2.h"
  8. #include "buffer.h"
  9. #include "netops.h"
  10. #include "smart.h"
  11. #ifdef GIT_SSH
  12. #include <libssh2.h>
  13. #define OWNING_SUBTRANSPORT(s) ((ssh_subtransport *)(s)->parent.subtransport)
  14. static const char prefix_ssh[] = "ssh://";
  15. static const char cmd_uploadpack[] = "git-upload-pack";
  16. static const char cmd_receivepack[] = "git-receive-pack";
  17. typedef struct {
  18. git_smart_subtransport_stream parent;
  19. gitno_socket socket;
  20. LIBSSH2_SESSION *session;
  21. LIBSSH2_CHANNEL *channel;
  22. const char *cmd;
  23. char *url;
  24. unsigned sent_command : 1;
  25. } ssh_stream;
  26. typedef struct {
  27. git_smart_subtransport parent;
  28. transport_smart *owner;
  29. ssh_stream *current_stream;
  30. git_cred *cred;
  31. } ssh_subtransport;
  32. static void ssh_error(LIBSSH2_SESSION *session, const char *errmsg)
  33. {
  34. char *ssherr;
  35. libssh2_session_last_error(session, &ssherr, NULL, 0);
  36. giterr_set(GITERR_SSH, "%s: %s", errmsg, ssherr);
  37. }
  38. /*
  39. * Create a git protocol request.
  40. *
  41. * For example: git-upload-pack '/libgit2/libgit2'
  42. */
  43. static int gen_proto(git_buf *request, const char *cmd, const char *url)
  44. {
  45. char *repo;
  46. int len;
  47. if (!git__prefixcmp(url, prefix_ssh)) {
  48. url = url + strlen(prefix_ssh);
  49. repo = strchr(url, '/');
  50. } else {
  51. repo = strchr(url, ':');
  52. if (repo) repo++;
  53. }
  54. if (!repo) {
  55. giterr_set(GITERR_NET, "Malformed git protocol URL");
  56. return -1;
  57. }
  58. len = strlen(cmd) + 1 /* Space */ + 1 /* Quote */ + strlen(repo) + 1 /* Quote */ + 1;
  59. git_buf_grow(request, len);
  60. git_buf_printf(request, "%s '%s'", cmd, repo);
  61. git_buf_putc(request, '\0');
  62. if (git_buf_oom(request))
  63. return -1;
  64. return 0;
  65. }
  66. static int send_command(ssh_stream *s)
  67. {
  68. int error;
  69. git_buf request = GIT_BUF_INIT;
  70. error = gen_proto(&request, s->cmd, s->url);
  71. if (error < 0)
  72. goto cleanup;
  73. error = libssh2_channel_exec(s->channel, request.ptr);
  74. if (error < LIBSSH2_ERROR_NONE) {
  75. ssh_error(s->session, "SSH could not execute request");
  76. goto cleanup;
  77. }
  78. s->sent_command = 1;
  79. cleanup:
  80. git_buf_free(&request);
  81. return error;
  82. }
  83. static int ssh_stream_read(
  84. git_smart_subtransport_stream *stream,
  85. char *buffer,
  86. size_t buf_size,
  87. size_t *bytes_read)
  88. {
  89. int rc;
  90. ssh_stream *s = (ssh_stream *)stream;
  91. *bytes_read = 0;
  92. if (!s->sent_command && send_command(s) < 0)
  93. return -1;
  94. if ((rc = libssh2_channel_read(s->channel, buffer, buf_size)) < LIBSSH2_ERROR_NONE) {
  95. ssh_error(s->session, "SSH could not read data");;
  96. return -1;
  97. }
  98. *bytes_read = rc;
  99. return 0;
  100. }
  101. static int ssh_stream_write(
  102. git_smart_subtransport_stream *stream,
  103. const char *buffer,
  104. size_t len)
  105. {
  106. ssh_stream *s = (ssh_stream *)stream;
  107. if (!s->sent_command && send_command(s) < 0)
  108. return -1;
  109. if (libssh2_channel_write(s->channel, buffer, len) < LIBSSH2_ERROR_NONE) {
  110. ssh_error(s->session, "SSH could not write data");
  111. return -1;
  112. }
  113. return 0;
  114. }
  115. static void ssh_stream_free(git_smart_subtransport_stream *stream)
  116. {
  117. ssh_stream *s = (ssh_stream *)stream;
  118. ssh_subtransport *t = OWNING_SUBTRANSPORT(s);
  119. int ret;
  120. GIT_UNUSED(ret);
  121. t->current_stream = NULL;
  122. if (s->channel) {
  123. libssh2_channel_close(s->channel);
  124. libssh2_channel_free(s->channel);
  125. s->channel = NULL;
  126. }
  127. if (s->session) {
  128. libssh2_session_free(s->session);
  129. s->session = NULL;
  130. }
  131. if (s->socket.socket) {
  132. (void)gitno_close(&s->socket);
  133. /* can't do anything here with error return value */
  134. }
  135. git__free(s->url);
  136. git__free(s);
  137. }
  138. static int ssh_stream_alloc(
  139. ssh_subtransport *t,
  140. const char *url,
  141. const char *cmd,
  142. git_smart_subtransport_stream **stream)
  143. {
  144. ssh_stream *s;
  145. assert(stream);
  146. s = git__calloc(sizeof(ssh_stream), 1);
  147. GITERR_CHECK_ALLOC(s);
  148. s->parent.subtransport = &t->parent;
  149. s->parent.read = ssh_stream_read;
  150. s->parent.write = ssh_stream_write;
  151. s->parent.free = ssh_stream_free;
  152. s->cmd = cmd;
  153. s->url = git__strdup(url);
  154. if (!s->url) {
  155. git__free(s);
  156. return -1;
  157. }
  158. *stream = &s->parent;
  159. return 0;
  160. }
  161. static int git_ssh_extract_url_parts(
  162. char **host,
  163. char **username,
  164. const char *url)
  165. {
  166. char *colon, *at;
  167. const char *start;
  168. colon = strchr(url, ':');
  169. at = strchr(url, '@');
  170. if (at) {
  171. start = at + 1;
  172. *username = git__substrdup(url, at - url);
  173. GITERR_CHECK_ALLOC(*username);
  174. } else {
  175. start = url;
  176. *username = NULL;
  177. }
  178. if (colon == NULL || (colon < start)) {
  179. giterr_set(GITERR_NET, "Malformed URL");
  180. return -1;
  181. }
  182. *host = git__substrdup(start, colon - start);
  183. GITERR_CHECK_ALLOC(*host);
  184. return 0;
  185. }
  186. static int ssh_agent_auth(LIBSSH2_SESSION *session, git_cred_ssh_key *c) {
  187. int rc = LIBSSH2_ERROR_NONE;
  188. struct libssh2_agent_publickey *curr, *prev = NULL;
  189. LIBSSH2_AGENT *agent = libssh2_agent_init(session);
  190. if (agent == NULL)
  191. return -1;
  192. rc = libssh2_agent_connect(agent);
  193. if (rc != LIBSSH2_ERROR_NONE)
  194. goto shutdown;
  195. rc = libssh2_agent_list_identities(agent);
  196. if (rc != LIBSSH2_ERROR_NONE)
  197. goto shutdown;
  198. while (1) {
  199. rc = libssh2_agent_get_identity(agent, &curr, prev);
  200. if (rc < 0)
  201. goto shutdown;
  202. if (rc == 1)
  203. goto shutdown;
  204. rc = libssh2_agent_userauth(agent, c->username, curr);
  205. if (rc == 0)
  206. break;
  207. prev = curr;
  208. }
  209. shutdown:
  210. libssh2_agent_disconnect(agent);
  211. libssh2_agent_free(agent);
  212. return rc;
  213. }
  214. static int _git_ssh_authenticate_session(
  215. LIBSSH2_SESSION* session,
  216. git_cred* cred)
  217. {
  218. int rc;
  219. do {
  220. switch (cred->credtype) {
  221. case GIT_CREDTYPE_USERPASS_PLAINTEXT: {
  222. git_cred_userpass_plaintext *c = (git_cred_userpass_plaintext *)cred;
  223. rc = libssh2_userauth_password(session, c->username, c->password);
  224. break;
  225. }
  226. case GIT_CREDTYPE_SSH_KEY: {
  227. git_cred_ssh_key *c = (git_cred_ssh_key *)cred;
  228. if (c->privatekey)
  229. rc = libssh2_userauth_publickey_fromfile(
  230. session, c->username, c->publickey,
  231. c->privatekey, c->passphrase);
  232. else
  233. rc = ssh_agent_auth(session, c);
  234. break;
  235. }
  236. case GIT_CREDTYPE_SSH_CUSTOM: {
  237. git_cred_ssh_custom *c = (git_cred_ssh_custom *)cred;
  238. rc = libssh2_userauth_publickey(
  239. session, c->username, (const unsigned char *)c->publickey,
  240. c->publickey_len, c->sign_callback, &c->payload);
  241. break;
  242. }
  243. case GIT_CREDTYPE_SSH_INTERACTIVE: {
  244. void **abstract = libssh2_session_abstract(session);
  245. git_cred_ssh_interactive *c = (git_cred_ssh_interactive *)cred;
  246. /* ideally, we should be able to set this by calling
  247. * libssh2_session_init_ex() instead of libssh2_session_init().
  248. * libssh2's API is inconsistent here i.e. libssh2_userauth_publickey()
  249. * allows you to pass the `abstract` as part of the call, whereas
  250. * libssh2_userauth_keyboard_interactive() does not!
  251. *
  252. * The only way to set the `abstract` pointer is by calling
  253. * libssh2_session_abstract(), which will replace the existing
  254. * pointer as is done below. This is safe for now (at time of writing),
  255. * but may not be valid in future.
  256. */
  257. *abstract = c->payload;
  258. rc = libssh2_userauth_keyboard_interactive(
  259. session, c->username, c->prompt_callback);
  260. break;
  261. }
  262. default:
  263. rc = LIBSSH2_ERROR_AUTHENTICATION_FAILED;
  264. }
  265. } while (LIBSSH2_ERROR_EAGAIN == rc || LIBSSH2_ERROR_TIMEOUT == rc);
  266. if (rc != LIBSSH2_ERROR_NONE) {
  267. ssh_error(session, "Failed to authenticate SSH session");
  268. return -1;
  269. }
  270. return 0;
  271. }
  272. static int _git_ssh_session_create(
  273. LIBSSH2_SESSION** session,
  274. gitno_socket socket)
  275. {
  276. int rc = 0;
  277. LIBSSH2_SESSION* s;
  278. assert(session);
  279. s = libssh2_session_init();
  280. if (!s) {
  281. giterr_set(GITERR_NET, "Failed to initialize SSH session");
  282. return -1;
  283. }
  284. do {
  285. rc = libssh2_session_startup(s, socket.socket);
  286. } while (LIBSSH2_ERROR_EAGAIN == rc || LIBSSH2_ERROR_TIMEOUT == rc);
  287. if (rc != LIBSSH2_ERROR_NONE) {
  288. ssh_error(s, "Failed to start SSH session");
  289. libssh2_session_free(s);
  290. return -1;
  291. }
  292. libssh2_session_set_blocking(s, 1);
  293. *session = s;
  294. return 0;
  295. }
  296. static int _git_ssh_setup_conn(
  297. ssh_subtransport *t,
  298. const char *url,
  299. const char *cmd,
  300. git_smart_subtransport_stream **stream)
  301. {
  302. char *host=NULL, *port=NULL, *path=NULL, *user=NULL, *pass=NULL;
  303. const char *default_port="22";
  304. int no_callback = 0;
  305. ssh_stream *s;
  306. LIBSSH2_SESSION* session=NULL;
  307. LIBSSH2_CHANNEL* channel=NULL;
  308. *stream = NULL;
  309. if (ssh_stream_alloc(t, url, cmd, stream) < 0)
  310. return -1;
  311. s = (ssh_stream *)*stream;
  312. if (!git__prefixcmp(url, prefix_ssh)) {
  313. if (gitno_extract_url_parts(&host, &port, &path, &user, &pass, url, default_port) < 0)
  314. goto on_error;
  315. } else {
  316. if (git_ssh_extract_url_parts(&host, &user, url) < 0)
  317. goto on_error;
  318. port = git__strdup(default_port);
  319. GITERR_CHECK_ALLOC(port);
  320. }
  321. if (gitno_connect(&s->socket, host, port, 0) < 0)
  322. goto on_error;
  323. if (user && pass) {
  324. if (git_cred_userpass_plaintext_new(&t->cred, user, pass) < 0)
  325. goto on_error;
  326. } else if (!t->owner->cred_acquire_cb) {
  327. no_callback = 1;
  328. } else {
  329. int error;
  330. error = t->owner->cred_acquire_cb(&t->cred, t->owner->url, user,
  331. GIT_CREDTYPE_USERPASS_PLAINTEXT |
  332. GIT_CREDTYPE_SSH_KEY | GIT_CREDTYPE_SSH_CUSTOM |
  333. GIT_CREDTYPE_SSH_INTERACTIVE,
  334. t->owner->cred_acquire_payload);
  335. if (error == GIT_PASSTHROUGH)
  336. no_callback = 1;
  337. else if (error < 0)
  338. goto on_error;
  339. else if (!t->cred) {
  340. giterr_set(GITERR_SSH, "Callback failed to initialize SSH credentials");
  341. goto on_error;
  342. }
  343. }
  344. if (no_callback) {
  345. giterr_set(GITERR_SSH, "authentication required but no callback set");
  346. goto on_error;
  347. }
  348. assert(t->cred);
  349. if (_git_ssh_session_create(&session, s->socket) < 0)
  350. goto on_error;
  351. if (_git_ssh_authenticate_session(session, t->cred) < 0)
  352. goto on_error;
  353. channel = libssh2_channel_open_session(session);
  354. if (!channel) {
  355. ssh_error(session, "Failed to open SSH channel");
  356. goto on_error;
  357. }
  358. libssh2_channel_set_blocking(channel, 1);
  359. s->session = session;
  360. s->channel = channel;
  361. t->current_stream = s;
  362. git__free(host);
  363. git__free(port);
  364. git__free(path);
  365. git__free(user);
  366. git__free(pass);
  367. return 0;
  368. on_error:
  369. s->session = NULL;
  370. s->channel = NULL;
  371. t->current_stream = NULL;
  372. if (*stream)
  373. ssh_stream_free(*stream);
  374. git__free(host);
  375. git__free(port);
  376. git__free(user);
  377. git__free(pass);
  378. if (session)
  379. libssh2_session_free(session);
  380. return -1;
  381. }
  382. static int ssh_uploadpack_ls(
  383. ssh_subtransport *t,
  384. const char *url,
  385. git_smart_subtransport_stream **stream)
  386. {
  387. if (_git_ssh_setup_conn(t, url, cmd_uploadpack, stream) < 0)
  388. return -1;
  389. return 0;
  390. }
  391. static int ssh_uploadpack(
  392. ssh_subtransport *t,
  393. const char *url,
  394. git_smart_subtransport_stream **stream)
  395. {
  396. GIT_UNUSED(url);
  397. if (t->current_stream) {
  398. *stream = &t->current_stream->parent;
  399. return 0;
  400. }
  401. giterr_set(GITERR_NET, "Must call UPLOADPACK_LS before UPLOADPACK");
  402. return -1;
  403. }
  404. static int ssh_receivepack_ls(
  405. ssh_subtransport *t,
  406. const char *url,
  407. git_smart_subtransport_stream **stream)
  408. {
  409. if (_git_ssh_setup_conn(t, url, cmd_receivepack, stream) < 0)
  410. return -1;
  411. return 0;
  412. }
  413. static int ssh_receivepack(
  414. ssh_subtransport *t,
  415. const char *url,
  416. git_smart_subtransport_stream **stream)
  417. {
  418. GIT_UNUSED(url);
  419. if (t->current_stream) {
  420. *stream = &t->current_stream->parent;
  421. return 0;
  422. }
  423. giterr_set(GITERR_NET, "Must call RECEIVEPACK_LS before RECEIVEPACK");
  424. return -1;
  425. }
  426. static int _ssh_action(
  427. git_smart_subtransport_stream **stream,
  428. git_smart_subtransport *subtransport,
  429. const char *url,
  430. git_smart_service_t action)
  431. {
  432. ssh_subtransport *t = (ssh_subtransport *) subtransport;
  433. switch (action) {
  434. case GIT_SERVICE_UPLOADPACK_LS:
  435. return ssh_uploadpack_ls(t, url, stream);
  436. case GIT_SERVICE_UPLOADPACK:
  437. return ssh_uploadpack(t, url, stream);
  438. case GIT_SERVICE_RECEIVEPACK_LS:
  439. return ssh_receivepack_ls(t, url, stream);
  440. case GIT_SERVICE_RECEIVEPACK:
  441. return ssh_receivepack(t, url, stream);
  442. }
  443. *stream = NULL;
  444. return -1;
  445. }
  446. static int _ssh_close(git_smart_subtransport *subtransport)
  447. {
  448. ssh_subtransport *t = (ssh_subtransport *) subtransport;
  449. assert(!t->current_stream);
  450. GIT_UNUSED(t);
  451. return 0;
  452. }
  453. static void _ssh_free(git_smart_subtransport *subtransport)
  454. {
  455. ssh_subtransport *t = (ssh_subtransport *) subtransport;
  456. assert(!t->current_stream);
  457. git__free(t);
  458. }
  459. #endif
  460. int git_smart_subtransport_ssh(
  461. git_smart_subtransport **out, git_transport *owner)
  462. {
  463. #ifdef GIT_SSH
  464. ssh_subtransport *t;
  465. assert(out);
  466. t = git__calloc(sizeof(ssh_subtransport), 1);
  467. GITERR_CHECK_ALLOC(t);
  468. t->owner = (transport_smart *)owner;
  469. t->parent.action = _ssh_action;
  470. t->parent.close = _ssh_close;
  471. t->parent.free = _ssh_free;
  472. *out = (git_smart_subtransport *) t;
  473. return 0;
  474. #else
  475. GIT_UNUSED(owner);
  476. assert(out);
  477. *out = NULL;
  478. giterr_set(GITERR_INVALID, "Cannot create SSH transport. Library was built without SSH support");
  479. return -1;
  480. #endif
  481. }