- djm@cvs.openbsd.org 2010/12/04 00:18:01
     [sftp-server.c sftp.1 sftp-client.h sftp.c PROTOCOL sftp-client.c]
     add a protocol extension to support a hard link operation. It is
     available through the "ln" command in the client. The old "ln"
     behaviour of creating a symlink is available using its "-s" option
     or through the preexisting "symlink" command; based on a patch from
     miklos AT szeredi.hu in bz#1555; ok markus@
diff --git a/sftp.c b/sftp.c
index d605505..ab667f5 100644
--- a/sftp.c
+++ b/sftp.c
@@ -1,4 +1,4 @@
-/* $OpenBSD: sftp.c,v 1.131 2010/10/23 22:06:12 sthen Exp $ */
+/* $OpenBSD: sftp.c,v 1.132 2010/12/04 00:18:01 djm Exp $ */
 /*
  * Copyright (c) 2001-2004 Damien Miller <djm@openbsd.org>
  *
@@ -132,6 +132,7 @@
 #define I_GET		5
 #define I_HELP		6
 #define I_LCHDIR	7
+#define I_LINK		25
 #define I_LLS		8
 #define I_LMKDIR	9
 #define I_LPWD		10
@@ -176,7 +177,7 @@
 	{ "lchdir",	I_LCHDIR,	LOCAL	},
 	{ "lls",	I_LLS,		LOCAL	},
 	{ "lmkdir",	I_LMKDIR,	LOCAL	},
-	{ "ln",		I_SYMLINK,	REMOTE	},
+	{ "ln",		I_LINK,		REMOTE	},
 	{ "lpwd",	I_LPWD,		LOCAL	},
 	{ "ls",		I_LS,		REMOTE	},
 	{ "lumask",	I_LUMASK,	NOARGS	},
@@ -240,7 +241,7 @@
 	    "lcd path                           Change local directory to 'path'\n"
 	    "lls [ls-options [path]]            Display local directory listing\n"
 	    "lmkdir path                        Create local directory\n"
-	    "ln oldpath newpath                 Symlink remote file\n"
+	    "ln [-s] oldpath newpath            Link remote file (-s for symlink)\n"
 	    "lpwd                               Print local working directory\n"
 	    "ls [-1afhlnrSt] [path]             Display remote directory listing\n"
 	    "lumask umask                       Set local umask to 'umask'\n"
@@ -377,6 +378,30 @@
 }
 
 static int
+parse_link_flags(const char *cmd, char **argv, int argc, int *sflag)
+{
+	extern int opterr, optind, optopt, optreset;
+	int ch;
+
+	optind = optreset = 1;
+	opterr = 0;
+
+	*sflag = 0;
+	while ((ch = getopt(argc, argv, "s")) != -1) {
+		switch (ch) {
+		case 's':
+			*sflag = 1;
+			break;
+		default:
+			error("%s: Invalid flag -%c", cmd, optopt);
+			return -1;
+		}
+	}
+
+	return optind;
+}
+
+static int
 parse_ls_flags(char **argv, int argc, int *lflag)
 {
 	extern int opterr, optind, optopt, optreset;
@@ -1088,7 +1113,7 @@
 
 static int
 parse_args(const char **cpp, int *pflag, int *rflag, int *lflag, int *iflag,
-    int *hflag, unsigned long *n_arg, char **path1, char **path2)
+    int *hflag, int *sflag, unsigned long *n_arg, char **path1, char **path2)
 {
 	const char *cmd, *cp = *cpp;
 	char *cp2, **argv;
@@ -1138,7 +1163,8 @@
 	switch (cmdnum) {
 	case I_GET:
 	case I_PUT:
-		if ((optidx = parse_getput_flags(cmd, argv, argc, pflag, rflag)) == -1)
+		if ((optidx = parse_getput_flags(cmd, argv, argc,
+		    pflag, rflag)) == -1)
 			return -1;
 		/* Get first pathname (mandatory) */
 		if (argc - optidx < 1) {
@@ -1154,8 +1180,11 @@
 			undo_glob_escape(*path2);
 		}
 		break;
-	case I_RENAME:
+	case I_LINK:
+		if ((optidx = parse_link_flags(cmd, argv, argc, sflag)) == -1)
+			return -1;
 	case I_SYMLINK:
+	case I_RENAME:
 		if (argc - optidx < 2) {
 			error("You must specify two paths after a %s "
 			    "command.", cmd);
@@ -1258,7 +1287,8 @@
     int err_abort)
 {
 	char *path1, *path2, *tmp;
-	int pflag = 0, rflag = 0, lflag = 0, iflag = 0, hflag = 0, cmdnum, i;
+	int pflag = 0, rflag = 0, lflag = 0, iflag = 0, hflag = 0, sflag = 0;
+	int cmdnum, i;
 	unsigned long n_arg = 0;
 	Attrib a, *aa;
 	char path_buf[MAXPATHLEN];
@@ -1266,8 +1296,8 @@
 	glob_t g;
 
 	path1 = path2 = NULL;
-	cmdnum = parse_args(&cmd, &pflag, &rflag, &lflag, &iflag, &hflag, &n_arg,
-	    &path1, &path2);
+	cmdnum = parse_args(&cmd, &pflag, &rflag, &lflag, &iflag, &hflag,
+	    &sflag, &n_arg, &path1, &path2);
 
 	if (iflag != 0)
 		err_abort = 0;
@@ -1295,8 +1325,11 @@
 		err = do_rename(conn, path1, path2);
 		break;
 	case I_SYMLINK:
+		sflag = 1;
+	case I_LINK:
+		path1 = make_absolute(path1, *pwd);
 		path2 = make_absolute(path2, *pwd);
-		err = do_symlink(conn, path1, path2);
+		err = (sflag ? do_symlink : do_hardlink)(conn, path1, path2);
 		break;
 	case I_RM:
 		path1 = make_absolute(path1, *pwd);