1 /*
2    sitecopy sftp/ssh protocol driver module (forked from rshdriver.c)
3    Copyright (C) 2000-2005, Joe Orton <joe@manyfish.co.uk>
4    Copyright (C) 2004, Nobuyuki Tsuchimura <tutimura@nn.iij4u.or.jp>
5 
6    This program is free software; you can redistribute it and/or modify
7    it under the terms of the GNU General Public License as published by
8    the Free Software Foundation; either version 2 of the License, or
9    (at your option) any later version.
10 
11    This program is distributed in the hope that it will be useful,
12    but WITHOUT ANY WARRANTY; without even the implied warranty of
13    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14    GNU General Public License for more details.
15 
16    You should have received a copy of the GNU General Public License
17    along with this program; if not, write to the Free Software
18    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
19 */
20 
21 /* TODO:
22  * - handle ssh directly instead of handling sftp....(?)
23  */
24 
25 #include <config.h>
26 
27 #include <sys/types.h>
28 #include <sys/wait.h>
29 #include <sys/socket.h>
30 #include <errno.h>
31 
32 #ifdef HAVE_STDLIB_H
33 #include <stdlib.h>
34 #endif
35 #ifdef HAVE_STDARG_H
36 #include <stdarg.h>
37 #endif
38 #ifdef HAVE_STRING_H
39 #include <string.h>
40 #endif
41 #ifdef HAVE_UNISTD_H
42 #include <unistd.h>
43 #endif
44 
45 #include <ne_alloc.h>
46 #include <ne_string.h>
47 #include <ne_socket.h>
48 
49 #include "protocol.h"
50 #include "lsparser.h"
51 
52 #ifndef HAVE_SOCKETPAIR
53 #define USE_PIPES
54 #endif
55 
56 typedef struct {
57     struct site *site;
58     const char *sftp_cmd, *ssh_cmd;
59     char buf[BUFSIZ];
60     int connected, fd_in, fd_out, sftp_pid;
61     FILE *fp;  /* popen for sftp client */
62 } sftp_session;
63 
64 static int run_sftp(sftp_session *sess, const char *template, ...)
65     ne_attribute((format (printf, 2, 3)));
66 
read_sftp(sftp_session * sess)67 static int read_sftp(sftp_session *sess) {
68     size_t pos = 0;
69 
70     do {
71         ssize_t ret = read(sess->fd_in, sess->buf+pos, BUFSIZ-pos);
72         if (ret < 0) return SITE_FAILED;
73         if (ret == 0) break;
74         pos += ret;
75         sess->buf[pos] = '\0';
76     } while (strchr(sess->buf, '>') == NULL && pos < BUFSIZ);
77     NE_DEBUG(DEBUG_SFTP, "(%s)", sess->buf);
78     return SITE_OK;
79 }
80 
exec_sftp(sftp_session * sess)81 static void exec_sftp(sftp_session *sess)
82 {
83     size_t len;
84     char *username;
85 
86     username = sess->site->server.username;
87     len = ne_snprintf(sess->buf, BUFSIZ, "%s%s%s",
88                       (username != NULL ? username : ""),
89                       (username != NULL ? "@" : ""),
90                       sess->site->server.hostname);
91     if (len + 1 >= BUFSIZ) {
92         NE_DEBUG(DEBUG_SFTP, "sftp: user/host name too long.\n");
93         return;
94     }
95 
96     execlp(sess->sftp_cmd, sess->sftp_cmd, sess->buf, NULL);
97     NE_DEBUG(DEBUG_SFTP, "sftp exec: %s %s: %s\n",
98              sess->sftp_cmd, sess->buf, strerror(errno));
99 }
100 
sftp_connect(sftp_session * sess)101 static int sftp_connect(sftp_session *sess)
102 {
103     int c_in, c_out;
104 #ifdef USE_PIPES
105     int pin[2], pout[2];
106     if ((pipe(pin) == -1) || (pipe(pout) == -1)) {
107         NE_DEBUG(DEBUG_SFTP, "sftp pipe: %s", strerror(errno));
108         return SITE_FAILED;
109     }
110     sess->fd_in  = pin[0];
111     sess->fd_out = pout[1];
112     c_in = pout[0];
113     c_out = pin[1];
114 #else /* !USE_PIPES */
115     int inout[2];
116     if (socketpair(AF_UNIX, SOCK_STREAM, 0, inout) == -1) {
117         NE_DEBUG(DEBUG_SFTP, "sftp socketpair: %s", strerror(errno));
118         return SITE_FAILED;
119     }
120     sess->fd_in = sess->fd_out = inout[0];
121     c_in = c_out = inout[1];
122 #endif /* USE_PIPES */
123 
124     sess->connected = true;
125     sess->sftp_pid = fork();
126     switch (sess->sftp_pid) {
127     case -1:
128         NE_DEBUG(DEBUG_SFTP, "sftp: fork: %s", strerror(errno));
129         return SITE_FAILED;
130     case 0:
131         if ((dup2(c_in, STDIN_FILENO) == -1) ||
132             (dup2(c_out, STDOUT_FILENO) == -1)) {
133             NE_DEBUG(DEBUG_SFTP, "sftp: dup2: %s\n", strerror(errno));
134             return SITE_FAILED;
135         }
136         close(sess->fd_in); close(sess->fd_out);
137         close(c_in);        close(c_out);
138         exec_sftp(sess);
139         return SITE_FAILED;
140     }
141     close(c_in);
142     close(c_out);
143     read_sftp(sess);    /* wait for prompt */
144     return SITE_OK;
145 }
146 
sftp_disconnect(sftp_session * sess)147 static int sftp_disconnect(sftp_session *sess)
148 {
149 #ifndef USE_PIPES
150 #ifndef SHUT_RDWR
151 #define SHUT_RDWR 2
152 #endif
153     shutdown(sess->fd_in,  SHUT_RDWR);
154     shutdown(sess->fd_out, SHUT_RDWR);
155 #endif /* USE_PIPES */
156     close(sess->fd_in);
157     close(sess->fd_out);
158 
159     if (waitpid(sess->sftp_pid, NULL, 0) == -1) {
160         NE_DEBUG(DEBUG_SFTP, "sftp: Couldn't wait for sftp process: %s",
161                  strerror(errno));
162         return SITE_FAILED;
163     }
164     return SITE_OK;
165 }
166 
167 
run_sftp(sftp_session * sess,const char * template,...)168 static int run_sftp(sftp_session *sess, const char *template, ...)
169 {
170     va_list params;
171     if (!sess->connected) sftp_connect(sess);
172 
173     va_start(params, template);
174     ne_vsnprintf(sess->buf, BUFSIZ, template, params);
175     va_end(params);
176 
177     if (strlen(sess->buf) + 2 >= BUFSIZ) { /* will be added '\n' */
178         NE_DEBUG(DEBUG_SFTP, "sftp: sftp: too long command '%s'.\n", sess->buf);
179         return SITE_FAILED;  /* buff over flow */
180     }
181     NE_DEBUG(DEBUG_SFTP, "SFTP: '%s'\n", sess->buf);
182 
183     strcat(sess->buf, "\n");
184     if (write(sess->fd_out, sess->buf, strlen(sess->buf)) == -1) {
185 	return SITE_FAILED;
186     }
187     read_sftp(sess);    /* wait for prompt */
188     return SITE_OK;
189 }
190 
191 
192 static int run_ssh(sftp_session *sess, const char *template, ...)
193     ne_attribute((format (printf, 2, 3)));
194 
run_ssh(sftp_session * sess,const char * template,...)195 static int run_ssh(sftp_session *sess, const char *template, ...)
196 {
197     va_list params;
198     char *buf = sess->buf;
199     size_t len;
200     char *username = sess->site->server.username;
201 
202     len = ne_snprintf(buf, BUFSIZ, "%s%s%s %s \\\\",
203                       sess->ssh_cmd,
204                       (username != NULL ? " -l " : ""),
205                       (username != NULL ? username : ""),
206                       sess->site->server.hostname);
207 
208     va_start(params, template);
209     len += ne_vsnprintf(buf + len, BUFSIZ - len, template, params);
210     va_end(params);
211 
212     len += ne_snprintf(buf + len, BUFSIZ - len, " 2> /dev/null" );
213     if (len + 1 >= BUFSIZ) return SITE_FAILED;  /* buff over flow */
214     NE_DEBUG(DEBUG_SFTP, "rcmd: %s\n", buf);
215 
216     sess->fp = popen(buf, "r");
217     if (sess->fp != NULL) {
218         return SITE_OK;
219     } else {
220         return SITE_FAILED;
221     }
222     /* why not free(cmd)? */
223 }
224 
ssh_finish(sftp_session * sess)225 static int ssh_finish(sftp_session *sess)
226 {
227     if (pclose(sess->fp) == 0) {
228         sess->fp = NULL;
229 	return SITE_OK;
230     } else {
231 	return SITE_FAILED;
232     }
233 }
234 
init(void ** session,struct site * site)235 static int init(void **session, struct site *site)
236 {
237     sftp_session *sess = ne_calloc(sizeof *sess);
238     *session = sess;
239     if (site->rcp_cmd != NULL) {
240 	sess->sftp_cmd = site->rcp_cmd;
241     } else {
242 	sess->sftp_cmd = "sftp";
243     }
244     if (site->rsh_cmd != NULL) {
245 	sess->ssh_cmd = site->rsh_cmd;
246     } else {
247 	sess->ssh_cmd = "ssh";
248     }
249     sess->site = site;
250     sess->connected = false;
251     return SITE_OK;
252 }
253 
finish(void * session)254 static void finish(void *session)
255 {
256     sftp_session *sess = session;
257 
258     if (sess->connected) sftp_disconnect(sess);
259     if (sess->fp != NULL) ssh_finish(sess);
260     free(sess);
261 }
262 
file_move(void * session,const char * from,const char * to)263 static int file_move(void *session, const char *from, const char *to)
264 {
265     sftp_session *sess = session;
266     return run_sftp(sess, "rename %s %s", from, to);
267 }
268 
file_upload(void * session,const char * local,const char * remote,int ascii)269 static int file_upload(void *session, const char *local, const char *remote,
270                        int ascii)
271 {
272     sftp_session *sess = session;
273     return run_sftp(sess, "put %s %s", local, remote);
274 }
275 
file_upload_cond(void * session,const char * local,const char * remote,int ascii,time_t t)276 static int file_upload_cond(void *session,
277 			    const char *local, const char *remote,
278 			    int ascii, time_t t)
279 {
280     return SITE_UNSUPPORTED;
281 }
282 
file_get_modtime(void * sess,const char * remote,time_t * modtime)283 static int file_get_modtime(void *sess, const char *remote, time_t *modtime)
284 {
285     return SITE_UNSUPPORTED;
286 }
287 
file_download(void * session,const char * local,const char * remote,int ascii)288 static int file_download(void *session, const char *local, const char *remote,
289 			 int ascii)
290 {
291     sftp_session *sess = session;
292     return run_sftp(sess, "get %s %s", remote, local);
293 }
294 
295 
file_read(void * session,const char * remote,ne_block_reader reader,void * userdata)296 static int file_read(void *session, const char *remote,
297                      ne_block_reader reader, void *userdata)
298 {
299     sftp_session *sess = session;
300     size_t len;
301 
302     if (run_ssh(sess, "cat '%s'", remote) != SITE_OK)
303         return SITE_FAILED;
304 
305     while ((len = fread(sess->buf, 1, BUFSIZ, sess->fp)) > 0) {
306         reader(userdata, sess->buf, len);
307     }
308 
309     return ssh_finish(sess);
310 }
311 
file_delete(void * session,const char * filename)312 static int file_delete(void *session, const char *filename)
313 {
314     sftp_session *sess = session;
315     return run_sftp(sess, "rm %s", filename);
316 }
317 
file_chmod(void * session,const char * fname,const mode_t mode)318 static int file_chmod(void *session, const char *fname, const mode_t mode)
319 {
320     sftp_session *sess = session;
321     return run_sftp(sess, "chmod %03o %s", mode, fname);
322 }
323 
dir_create(void * session,const char * dirname)324 static int dir_create(void *session, const char *dirname)
325 {
326     sftp_session *sess = session;
327     return run_sftp(sess, "mkdir %s", dirname);
328 }
329 
dir_remove(void * session,const char * dirname)330 static int dir_remove(void *session, const char *dirname)
331 {
332     sftp_session *sess = session;
333     return run_sftp(sess, "rmdir %s", dirname);
334 }
335 
error(void * session)336 static const char *error(void *session)
337 {
338     return "An error occurred.";
339 }
340 
get_dummy_port(struct site * site)341 static int get_dummy_port(struct site *site)
342 {
343     return 0;
344 }
345 
ssh_fetch(sftp_session * sess,const char * startdir,struct proto_file ** list)346 static int ssh_fetch(sftp_session *sess, const char *startdir,
347                      struct proto_file **list)
348 {
349     struct proto_file *tail = NULL;
350     int success = SITE_OK;
351     ls_context_t *lsctx = ls_init(startdir);
352 
353     memset(sess->buf, 0, BUFSIZ);
354 
355     while (fgets(sess->buf, BUFSIZ, sess->fp) != NULL) {
356         enum ls_result res;
357         struct ls_file lfile;
358 
359         res = ls_parse(lsctx, sess->buf, &lfile);
360 
361         switch (res) {
362         case ls_error:
363             NE_DEBUG(DEBUG_SFTP, "Could not parse line.\n");
364             success = SITE_ERRORS;
365             break;
366 
367         default:
368             ls_pflist_add(list, &tail, &lfile, res);
369             break;
370 	}
371     }
372 
373     ls_destroy(lsctx);
374 
375     NE_DEBUG(DEBUG_SFTP, "Fetch finished successfully.\n");
376     return success;
377 }
378 
fetch_list(void * session,const char * dirname,int need_modtimes,struct proto_file ** files)379 static int fetch_list(void *session, const char *dirname, int need_modtimes,
380                      struct proto_file **files)
381 {
382     sftp_session *sess = session;
383     int ret;
384 
385     ret = run_ssh(sess, "ls -la '%s'", dirname);
386     if (ret == SITE_OK) {
387         ssh_fetch(sess, dirname, files);
388         ret = ssh_finish(sess);
389     }
390 
391     /*
392     if (ret == SITE_OK && need_modtimes) {
393 	ret = sftp_fetch_modtimes(sess, dirname, *files);
394     }
395     */
396 
397     return ret;
398 }
399 
400 /* The protocol driver */
401 const struct proto_driver sftp_driver = {
402     init,
403     finish,
404     file_move,
405     file_upload,
406     file_upload_cond,
407     file_get_modtime,
408     file_download,
409     file_read,
410     file_delete,
411     file_chmod,
412     dir_create,
413     dir_remove,
414     NULL, /* create link */
415     NULL, /* change link target */
416     NULL, /* delete link */
417     fetch_list, /* fetch list. */
418     error,
419     get_dummy_port,
420     get_dummy_port,
421     "sftp/ssh"
422 };
423