Skip to content

Commit b3e158f

Browse files
committed
copy: use scp(1) for scp:// and sftp:// transfers
curl's SCP/SFTP backend (libssh2) does not support interactive password prompting — it fails with an authentication error unless credentials are supplied non-interactively. Replace the curl path for SSH URIs with native scp(1), which inherits the TTY and prompts the user for a password as expected. Both scp:// and sftp:// URIs are handled by parsing the URI into a [user@]host:path argument. Port is extracted if present and passed via -P. If no user is embedded in the URI, the -u flag value (remote_user) is used instead. Host-key verification is skipped with -o StrictHostKeyChecking=no, the same trade-off already made by the test framework. When the destination URI ends with '/', a meaningful remote filename is derived from the source: the datastore's on-disk filename (e.g., startup-config.cfg), the datastore short-name for in-memory stores, or the source file's own basename for plain file copies. Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
1 parent d08c341 commit b3e158f

1 file changed

Lines changed: 190 additions & 14 deletions

File tree

src/bin/copy.c

Lines changed: 190 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -163,6 +163,76 @@ static bool is_uri(const char *str)
163163
return strstr(str, "://") != NULL;
164164
}
165165

166+
static bool is_ssh_uri(const char *uri)
167+
{
168+
return !strncmp(uri, "scp://", 6) || !strncmp(uri, "sftp://", 7);
169+
}
170+
171+
/*
172+
* Parse scp:// or sftp:// URI into an scp(1)-compatible remote string
173+
* of the form [user@]host:path, and optionally extract the port.
174+
*
175+
* URI format: scheme://[user@]host[:port]/path
176+
*
177+
* Returns a heap-allocated string the caller must free, and sets *portp
178+
* to a heap-allocated port string (or NULL) the caller must also free.
179+
* Returns NULL on parse failure.
180+
*/
181+
static char *ssh_uri_to_remote(const char *uri, char **portp)
182+
{
183+
const char *p, *slash, *path;
184+
char auth[256], *auth_at, *auth_colon, *host_start;
185+
char *user, *remote = NULL, *port = NULL;
186+
size_t auth_len;
187+
188+
*portp = NULL;
189+
190+
if (!strncmp(uri, "scp://", 6))
191+
p = uri + 6;
192+
else if (!strncmp(uri, "sftp://", 7))
193+
p = uri + 7;
194+
else
195+
return NULL;
196+
197+
slash = strchr(p, '/');
198+
if (!slash)
199+
slash = p + strlen(p);
200+
201+
auth_len = (size_t)(slash - p);
202+
if (auth_len >= sizeof(auth))
203+
return NULL;
204+
memcpy(auth, p, auth_len);
205+
auth[auth_len] = '\0';
206+
207+
auth_at = strchr(auth, '@');
208+
if (auth_at) {
209+
*auth_at = '\0';
210+
user = auth;
211+
host_start = auth_at + 1;
212+
} else {
213+
user = (char *)remote_user; /* may be NULL */
214+
host_start = auth;
215+
}
216+
217+
auth_colon = strchr(host_start, ':');
218+
if (auth_colon) {
219+
*auth_colon = '\0';
220+
port = strdup(auth_colon + 1);
221+
if (!port)
222+
return NULL;
223+
}
224+
225+
path = *slash ? slash : "/";
226+
227+
if (user)
228+
(void)asprintf(&remote, "%s@%s:%s", user, host_start, path);
229+
else
230+
(void)asprintf(&remote, "%s:%s", host_start, path);
231+
232+
*portp = port;
233+
return remote;
234+
}
235+
166236
static bool is_stdout(const char *path)
167237
{
168238
if (!path)
@@ -394,29 +464,37 @@ static int subprocess(char * const *argv)
394464

395465
static int curl(char *op, const char *path, const char *uri)
396466
{
397-
char *argv[] = {
398-
"curl", "-L", op, NULL, NULL, NULL, NULL, NULL,
399-
};
400-
int err = 1;
467+
char *argv[10] = { "curl", "-L", NULL };
468+
int err = 1, i = 2;
469+
int path_i, uri_i, user_i = 0;
470+
471+
argv[i++] = op;
401472

402-
argv[3] = strdup(path);
403-
argv[4] = strdup(uri);
404-
if (!(argv[3] && argv[4]))
473+
path_i = i;
474+
argv[i] = strdup(path);
475+
if (!argv[i++])
476+
goto out;
477+
478+
uri_i = i;
479+
argv[i] = strdup(uri);
480+
if (!argv[i++])
405481
goto out;
406482

407483
if (remote_user) {
408-
argv[5] = strdup("-u");
409-
argv[6] = strdup(remote_user);
410-
if (!(argv[5] && argv[6]))
484+
argv[i++] = "-u";
485+
user_i = i;
486+
argv[i] = strdup(remote_user);
487+
if (!argv[i++])
411488
goto out;
412489
}
490+
413491
err = subprocess(argv);
414492

415493
out:
416-
free(argv[6]);
417-
free(argv[5]);
418-
free(argv[4]);
419-
free(argv[3]);
494+
free(argv[path_i]);
495+
free(argv[uri_i]);
496+
if (user_i)
497+
free(argv[user_i]);
420498
return err;
421499
}
422500

@@ -445,6 +523,72 @@ static int curl_download(const char *uri, const char *dstpath)
445523
return 0;
446524
}
447525

526+
static int scp_upload(const char *srcpath, const char *uri)
527+
{
528+
char *argv[12] = { "scp", "-o", "StrictHostKeyChecking=no", NULL };
529+
int i = 3, err = 1;
530+
char *remote = NULL, *port = NULL, *src_dup = NULL;
531+
532+
remote = ssh_uri_to_remote(uri, &port);
533+
if (!remote) {
534+
warnx("failed to parse URI: %s", uri);
535+
goto out;
536+
}
537+
538+
if (port) {
539+
argv[i++] = "-P";
540+
argv[i++] = port;
541+
}
542+
543+
src_dup = strdup(srcpath);
544+
if (!src_dup)
545+
goto out;
546+
argv[i++] = src_dup;
547+
argv[i++] = remote;
548+
549+
err = subprocess(argv);
550+
if (err)
551+
warnx("upload to %s failed", uri);
552+
out:
553+
free(src_dup);
554+
free(port);
555+
free(remote);
556+
return err;
557+
}
558+
559+
static int scp_download(const char *uri, const char *dstpath)
560+
{
561+
char *argv[12] = { "scp", "-o", "StrictHostKeyChecking=no", NULL };
562+
int i = 3, err = 1;
563+
char *remote = NULL, *port = NULL, *dst_dup = NULL;
564+
565+
remote = ssh_uri_to_remote(uri, &port);
566+
if (!remote) {
567+
warnx("failed to parse URI: %s", uri);
568+
goto out;
569+
}
570+
571+
if (port) {
572+
argv[i++] = "-P";
573+
argv[i++] = port;
574+
}
575+
576+
argv[i++] = remote;
577+
dst_dup = strdup(dstpath);
578+
if (!dst_dup)
579+
goto out;
580+
argv[i++] = dst_dup;
581+
582+
err = subprocess(argv);
583+
if (err)
584+
warnx("download of %s failed", uri);
585+
out:
586+
free(dst_dup);
587+
free(port);
588+
free(remote);
589+
return err;
590+
}
591+
448592
static int cat(const char *srcpath)
449593
{
450594
char *argv[] = { "cat", NULL, NULL };
@@ -492,6 +636,8 @@ static int put(const char *srcpath, const char *dst,
492636
err = sysrepo_import(ds, srcpath);
493637
else if (is_stdout(dst))
494638
err = cat(srcpath);
639+
else if (is_ssh_uri(dst))
640+
err = scp_upload(srcpath, dst);
495641
else if (is_uri(dst))
496642
err = curl_upload(srcpath, dst);
497643

@@ -513,6 +659,8 @@ static int get(const char *src, const struct infix_ds *ds, const char *path)
513659

514660
if (ds)
515661
err = sysrepo_export(ds, path);
662+
else if (is_ssh_uri(src))
663+
err = scp_download(src, path);
516664
else if (is_uri(src))
517665
err = curl_download(src, path);
518666

@@ -581,6 +729,7 @@ static int copy(const char *src, const char *dst)
581729
{
582730
const struct infix_ds *srcds = NULL, *dstds = NULL;
583731
char *srcpath = NULL, *dstpath = NULL;
732+
char *dst_uri = NULL;
584733
bool rmsrc = false;
585734
mode_t oldmask;
586735
int err = 1;
@@ -597,6 +746,32 @@ static int copy(const char *src, const char *dst)
597746
if (err)
598747
goto err;
599748

749+
/*
750+
* When uploading to an SSH URI ending in '/', scp(1) would use the
751+
* basename of the local (temp) file as the remote name. Append a
752+
* meaningful name instead: the datastore's on-disk filename, or the
753+
* source file's own basename.
754+
*/
755+
if (is_ssh_uri(dst) && dst[strlen(dst) - 1] == '/') {
756+
const char *bn, *slash;
757+
758+
if (srcds && srcds->path) {
759+
slash = strrchr(srcds->path, '/');
760+
bn = slash ? slash + 1 : srcds->path;
761+
} else if (srcds) {
762+
bn = srcds->name;
763+
} else {
764+
slash = strrchr(srcpath, '/');
765+
bn = slash ? slash + 1 : srcpath;
766+
}
767+
768+
if (asprintf(&dst_uri, "%s%s", dst, bn) < 0) {
769+
err = 1;
770+
goto err;
771+
}
772+
dst = dst_uri;
773+
}
774+
600775
err = resolve_dst(&dst, &dstds, &dstpath);
601776
if (err)
602777
goto err;
@@ -614,6 +789,7 @@ static int copy(const char *src, const char *dst)
614789
if (rmsrc)
615790
rmtmp(srcpath);
616791

792+
free(dst_uri);
617793
if (dstpath)
618794
free(dstpath);
619795
free(srcpath);

0 commit comments

Comments
 (0)