@@ -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+
166236static bool is_stdout (const char * path )
167237{
168238 if (!path )
@@ -394,29 +464,37 @@ static int subprocess(char * const *argv)
394464
395465static 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
415493out :
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+
448592static 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