"Fossies" - the Fresh Open Source Software Archive 
Member "scponly-20110526/scponly.c" (27 May 2011, 21262 Bytes) of package /linux/privat/old/scponly-20110526.tgz:
As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) C and C++ source code syntax highlighting (style:
standard) with prefixed line numbers and
code folding option.
Alternatively you can here
view or
download the uninterpreted source code file.
See also the latest
Fossies "Diffs" side-by-side code changes report for "scponly.c":
4.8_vs_20110526.
1 /*
2 * scponly.c
3 *
4 * http://sublimation.org/scponly
5 * joe@sublimation.org
6 *
7 * see CONTRIB for additional credits
8 */
9
10 #include <stdio.h> /* io */
11 #include <string.h> /* for str* */
12 #include <sys/types.h> /* for fork, wait, stat */
13 #include <sys/stat.h> /* for stat */
14 #include <sys/wait.h> /* for wait */
15 #include <unistd.h> /* for exit, fork */
16 #include <stdlib.h> /* EXIT_* */
17 #include <errno.h>
18 #include <syslog.h>
19 #include "scponly.h"
20
21 int debuglevel=0;
22 int winscp_mode=0;
23 int chrooted=0;
24 char username[MAX_USERNAME];
25 char homedir[FILENAME_MAX];
26 char chrootdir[FILENAME_MAX];
27 char *safeenv[MAX_ENV];
28
29 /* will point to syslog or a noop */
30 void (*debug)(int priority, const char* format, ...);
31 int (*scponly_getopt_long)(
32 int argc,
33 char * const argv[],
34 const char *optstring,
35 const struct option *longopts,
36 int *longindex
37 );
38
39 cmd_t commands[] =
40 {
41 #ifdef ENABLE_SFTP
42 { PROG_SFTP_SERVER, 1 },
43 #endif /*ENABLE_SFTP*/
44 #ifdef ENABLE_SCP2
45 { PROG_LS, 1 },
46 { PROG_CHMOD, 1 },
47 { PROG_CHOWN, 1 },
48 { PROG_CHGRP, 1 },
49 { PROG_MKDIR, 1 },
50 { PROG_RMDIR, 1 },
51 { PROG_SCP, 1 },
52 { PROG_LN, 1 },
53 { PROG_MV, 1 },
54 { PROG_RM, 1 },
55 { PROG_CD, 1 },
56 #endif /*ENABLE_SCP2*/
57
58 #ifdef WINSCP_COMPAT
59 { PROG_GROUPS, 0 },
60 { PROG_PWD, 0 },
61 { PROG_ECHO, 1 },
62 #endif /*WINSCP_COMPAT*/
63
64 #ifdef UNISON_COMPAT
65 { PROG_UNISON, 1 },
66 #endif /*ENABLE_UNISON*/
67
68 #ifdef RSYNC_COMPAT
69 { PROG_RSYNC, 1 },
70 #endif /*ENABLE_RSYNC*/
71
72 #ifdef PASSWD_COMPAT
73 { PROG_PASSWD, 1 },
74 #endif /*ENABLE_PASSWD*/
75
76 #ifdef QUOTA_COMPAT
77 { PROG_QUOTA, 1 },
78 #endif /*QUOTA_COMPAT*/
79
80 #ifdef SVN_COMPAT
81 { PROG_SVN, 1 },
82 #endif /*ENABLE_SVN*/
83
84 #ifdef SVNSERV_COMPAT
85 { PROG_SVNSERV, 1 },
86 #endif /*ENABLE_SVNSERV*/
87
88 { NULL }
89 };
90
91 /*
92 * The array of longopts to be used for validation
93 * longopts := (name, has_args, *flag, val)
94 */
95 struct option empty_longopts[] = {
96 { NULL, 0, NULL, 0 },
97 };
98
99 #ifdef RSYNC_COMPAT
100 struct option rsync_longopts[] = {
101 /* options we need to know about that are safe */
102 {"server", 0, 0, (int)'s'},
103 /* the following options have behaviors we don't want to see */
104 {"rsh", 1, 0, (int)'r'},
105 /* the following are disabled because they use daemon mode */
106 {"daemon", 0, 0, (int)'d'},
107 {"rsync-path", 1, 0, (int)'d'},
108 {"address", 1, 0, (int)'d'},
109 {"port", 1, 0, (int)'d'},
110 {"sockopts", 1, 0, (int)'d'},
111 {"config", 1, 0, (int)'d'},
112 {"no-detach", 0, 0, (int)'d'},
113 { NULL, 0, NULL, 0 },
114 };
115 #endif
116
117 #ifdef SVNSERV_COMPAT
118 struct option svnserv_longopts[] = {
119 /* bad */
120 {"daemon", 0, NULL, (int)'d' },
121 {"listen-port", 1, NULL, (int)'d' },
122 {"listen-host", 1, NULL, (int)'d' },
123 {"foreground", 0, NULL, (int)'d' },
124 {"inetd", 0, NULL, (int)'i' },
125 {"threads", 0, NULL, (int)'T' },
126 {"listen-once", 0, NULL, (int)'X' },
127 /* good */
128 {"read-only", 0, NULL, (int)'R' },
129 {"help", 0, NULL, (int)'h' },
130 {"root", 0, NULL, (int)'r' },
131 {"tunnel", 0, NULL, (int)'t' },
132 {"tunnel-user", 1, NULL, 0 },
133 { NULL, 0, NULL, 0 },
134 };
135 #endif
136
137 #ifdef SVN_COMPAT
138 struct option svn_longopts[] = {
139 /* bad */
140 {"editor-cmd", 1, NULL, (int)'X' },
141 {"diff-cmd", 1, NULL, (int)'X' },
142 {"diff3-cmd", 1, NULL, (int)'X' },
143 {"config-dir", 1, NULL, (int)'X' },
144 { NULL, 0, NULL, 0 },
145 };
146 #endif
147
148 /*
149 * several binaries support arbitrary command execution in their arguments
150 * to prevent this we have to check the arguments for these binaries before
151 * invoking them.
152 */
153 cmd_arg_t dangerous_args[] =
154 {
155 /*
156 * 'oplist' only neccesary where 'use getopt' is 1
157 * 'strict optlist' only applicable where 'use getopt?' is 1
158 * 'badarg' is a string to look for if not in rsync mode, if in rsync mode a list of invalid options
159 *
160 * program name use getopt? strict optlist? badarg optlist longopts\n
161 */
162 #ifdef ENABLE_SFTP
163 { PROG_SFTP_SERVER, 1, 1, NULL, "f:l:u:", empty_longopts },
164 #endif
165 #ifdef ENABLE_SCP2
166 { PROG_SCP, 1, 1, "SoF", "dfl:prtvBCc:i:P:q1246S:o:F:", empty_longopts },
167 #endif
168 #ifdef RSYNC_COMPAT
169 { PROG_RSYNC, 1, 0, "rde", "e::", rsync_longopts },
170 #endif
171 #ifdef UNISON_COMPAT
172 { PROG_UNISON, 0, 0, "-rshcmd", NULL, empty_longopts },
173 { PROG_UNISON, 0, 0, "-sshcmd", NULL, empty_longopts },
174 { PROG_UNISON, 0, 0, "-servercmd", NULL, empty_longopts },
175 { PROG_UNISON, 0, 0, "-addversionno",NULL, empty_longopts },
176 #endif
177 #ifdef SVNSERV_COMPAT
178 { PROG_SVNSERV, 1, 1, "diTX", "dihr:RtTX", svnserv_longopts },
179 #endif
180 #ifdef SVN_COMPAT
181 { PROG_SVN, 1, 0, "Xx", "NvxuRr:qm:F:", svn_longopts },
182 #endif
183 #ifdef QUOTA_COMPAT
184 { PROG_QUOTA, 1, 1, NULL, "-F:guvsilqQ", empty_longopts },
185 #endif
186 { NULL }
187 };
188
189 /*
190 * SFTP logging requires that the following environment variables be
191 * defined in order to work:
192 *
193 * LOG_SFTP
194 * USER
195 * SFTP_UMASK
196 * SFTP_PERMIT_CHMOD
197 * SFTP_PERMIT_CHOWN
198 * SFTP_LOG_LEVEL
199 * SFTP_LOG_FACILITY
200 */
201 char * allowed_env_vars[] =
202 {
203 #ifdef SFTP_LOGGING
204 "LOG_SFTP",
205 "USER",
206 "SFTP_UMASK",
207 "SFTP_PERMIT_CHMOD",
208 "SFTP_PERMIT_CHOWN",
209 "SFTP_LOG_LEVEL",
210 "SFTP_LOG_FACILITY",
211 #endif
212 #ifdef UNISON_COMPAT
213 "HOME",
214 #endif
215 NULL
216 };
217
218 int process_ssh_request(char *request);
219 int process_pre_chroot_request(char **av);
220 int winscp_regular_request(char *request);
221 int winscp_transit_request(char *request);
222 int process_winscp_requests(void);
223
224 int main (int argc, char **argv)
225 {
226 FILE *debugfile;
227 int logopts = LOG_PID|LOG_NDELAY;
228 int chars_read = 0;
229 #ifdef CHROOT_CHECKDIR
230 struct stat homedirstat;
231 #endif
232
233 /*
234 * set debuglevel. any nonzero number will result in debugging info to log
235 */
236 if (NULL!=(debugfile=fopen(DEBUGFILE,"r")))
237 {
238 chars_read = fscanf(debugfile,"%d",&debuglevel);
239 if (chars_read < 1)
240 debuglevel = 0;
241 fclose(debugfile);
242 }
243 #ifndef UNIX_COMPAT
244 if (debuglevel > 1) /* debuglevel 1 will still log to syslog */
245 logopts |= LOG_PERROR;
246 #endif
247
248 #ifdef UNIX_COMPAT
249 openlog(PACKAGE_NAME, logopts, LOG_AUTH);
250 #elif IRIX_COMPAT
251 openlog(PACKAGE_NAME, logopts, LOG_AUTH);
252 #else
253 if (debuglevel > 1) /* debuglevel 1 will still log to syslog */
254 logopts |= LOG_PERROR;
255 openlog(PACKAGE_NAME, logopts, LOG_AUTHPRIV);
256 #endif
257
258 if (debuglevel > 0)
259 debug = syslog;
260 else
261 debug = noop_syslog;
262
263 #ifdef HAVE_GETOPT_H
264 scponly_getopt_long = getopt_long;
265 #else
266 debug(LOG_INFO, "using netbsd's bundled getopt_long");
267 scponly_getopt_long = netbsd_getopt_long;
268 #endif
269
270 #ifdef CHROOTED_NAME
271 /*
272 * is this a chroot'ed scponly installation?
273 */
274 #ifdef WINSCP_COMPAT
275 if ((argc==3 && (0==strncmp(argv[0],CHROOTED_NAME,FILENAME_MAX)) ) ||
276 ( argc==1 && (0==strncmp(&argv[0][1],CHROOTED_NAME,FILENAME_MAX ))))
277
278 #else
279 if (0==strncmp(argv[0],CHROOTED_NAME,FILENAME_MAX))
280 #endif
281 {
282 debug(LOG_INFO, "chrooted binary in place, will chroot()");
283 chrooted=1;
284 }
285 #endif /* CHROOTED_NAME */
286
287 if (debuglevel)
288 {
289 int i;
290 syslog(LOG_DEBUG, "%d arguments in total.", argc);
291 for (i=0;i<argc;i++)
292 syslog(LOG_DEBUG, "\targ %u is %s", i, argv[i]);
293 }
294
295 #ifdef UNIX_COMPAT
296 debug(LOG_DEBUG, "opened log at LOG_AUTH, opts 0x%08x", logopts);
297 #else
298 debug(LOG_DEBUG, "opened log at LOG_AUTHPRIV, opts 0x%08x", logopts);
299 #endif
300
301 if (getuid()==0)
302 {
303 syslog(LOG_ERR, "root login denied [%s]", logstamp());
304 exit(EXIT_FAILURE);
305 }
306
307 #ifdef WINSCP_COMPAT
308 if ((argc!=3) && (argc!=1))
309 #else
310 if (argc!=3)
311 #endif
312 {
313 debug(LOG_ERR, "incorrect number of args");
314 exit(EXIT_FAILURE);
315 }
316 if (!get_uservar())
317 {
318 syslog(LOG_ERR, "%s is misconfigured. contact sysadmin.", argv[0]);
319 exit (EXIT_FAILURE);
320 }
321
322 #ifdef CHROOTED_NAME
323 if (chrooted)
324 {
325 char **av = NULL;
326 char *tmprequest = NULL;
327 char *root_dir = chrootdir;
328 char chdir_path[FILENAME_MAX];
329
330
331 strcpy(chrootdir, homedir);
332 strcpy(chdir_path, "/");
333 while((root_dir = strchr(root_dir, '/')) != NULL)
334 {
335 if (strncmp(root_dir, "//", 2) == 0)
336 {
337 snprintf(chdir_path, FILENAME_MAX, "%s", root_dir + 1);
338 /* make sure HOME will be set to something correct if used*/
339 debug(LOG_DEBUG, "Setting homedir to %s", chdir_path);
340 strcpy(homedir, chdir_path);
341 *root_dir = '\0';
342 break;
343 }
344 root_dir++;
345 }
346 #ifdef CHROOT_CHECKDIR
347 bzero(&homedirstat, sizeof(struct stat));
348 if (-1 == stat(chrootdir, &homedirstat))
349 {
350 syslog (LOG_ERR, "couldnt stat chroot dir: %s with errno %u", chrootdir, errno);
351 exit(EXIT_FAILURE);
352 }
353 if (0 == (homedirstat.st_mode | S_IFDIR))
354 {
355 syslog (LOG_ERR, "chroot dir is not a directory: %s", chrootdir);
356 exit(EXIT_FAILURE);
357 }
358 if (homedirstat.st_uid != 0)
359 {
360 syslog (LOG_ERR, "chroot dir not owned by root: %s", chrootdir);
361 exit(EXIT_FAILURE);
362 }
363 if (0 != (homedirstat.st_mode & S_IWOTH))
364 {
365 syslog (LOG_ERR, "chroot dir writable by other: %s", chrootdir);
366 exit(EXIT_FAILURE);
367 }
368 if (0 != (homedirstat.st_mode & S_IWGRP))
369 {
370 syslog (LOG_ERR, "chroot dir writable by group: %s", chrootdir);
371 exit(EXIT_FAILURE);
372 }
373 #endif
374
375 /* already within CHROOTED_NAME block */
376 #if defined(PASSWD_COMPAT) || defined(QUOTA_COMPAT)
377
378 /*
379 * perhaps we need to refactor so we don't have to exit right
380 * in the middle of the code, but we can't chroot and expect to be
381 * able to change the password and have it be of any use unless
382 * there is some additional process that scponly is unaware of
383 * happening on the back end.
384 */
385 tmprequest = strdup(argv[2]);
386 av = build_arg_vector(tmprequest);
387 free(tmprequest);
388 if (
389 #ifdef PASSWD_COMPAT
390 (exact_match(av[0],"passwd"))
391 || (exact_match(av[0],PROG_PASSWD))
392 #else
393 0
394 #endif
395 #ifdef QUOTA_COMPAT
396 || (exact_match(av[0],"quota"))
397 || (exact_match(av[0],PROG_QUOTA))
398 #endif
399 ) {
400 int status = process_pre_chroot_request(av);
401 discard_vector(av);
402
403 if (status) {
404 syslog(LOG_ERR, "process_pre_chroot_request(%s) failed with code %i [%s]",
405 argv[2],WEXITSTATUS(status),logstamp());
406 exit(EXIT_FAILURE);
407 }
408 debug(LOG_DEBUG, "scponly completed");
409 exit(EXIT_SUCCESS);
410 } else {
411 discard_vector(av);
412 }
413
414 #endif /* passwd or quota */
415
416 debug(LOG_DEBUG, "chrooting to dir: \"%s\"", chrootdir);
417 if (-1==(chroot(chrootdir)))
418 {
419 debug(LOG_ERR, "chroot: %m");
420 syslog(LOG_ERR, "couldn't chroot to %s [%s]", chrootdir, logstamp());
421 exit(EXIT_FAILURE);
422 }
423
424 debug(LOG_DEBUG, "chdiring to dir: \"%s\"", chdir_path);
425 if (-1==(chdir(chdir_path)))
426 {
427 debug(LOG_ERR, "chdir: %m");
428 syslog (LOG_ERR, "couldn't chdir to %s [%s]", chdir_path, logstamp());
429 exit(EXIT_FAILURE);
430 }
431 }
432 #endif /* CHROOTED_NAME */
433
434 debug(LOG_DEBUG, "setting uid to %u", getuid());
435 if (-1==(seteuid(getuid())))
436 {
437 syslog(LOG_ERR, "couldn't revert to my real uid. seteuid: %m");
438 exit(EXIT_FAILURE);
439 }
440
441 #ifdef WINSCP_COMPAT
442 if (argc==1)
443 {
444 debug(LOG_DEBUG, "entering WinSCP compatibility mode [%s]",logstamp());
445 if (-1==process_winscp_requests())
446 {
447 syslog(LOG_ERR, "failed WinSCP compatibility mode [%s]", logstamp());
448 exit(EXIT_FAILURE);
449 }
450 }
451 #else
452 if (0) {} /* placeholder */
453 #endif
454 else if (-1==process_ssh_request(argv[2]))
455 {
456 syslog(LOG_ERR, "bad request: %s [%s]", argv[2], logstamp());
457 exit(EXIT_FAILURE);
458 }
459 debug(LOG_DEBUG, "scponly completed");
460 exit(EXIT_SUCCESS);
461 }
462
463 #ifdef CHROOTED_NAME
464 #if defined(PASSWD_COMPAT) || defined(QUOTA_COMPAT)
465
466 int process_pre_chroot_request(char ** av) {
467
468 char *flat_request = NULL;
469 char *env[2] = { NULL, NULL };
470 int retval = -1;
471
472 /* revert to real user so I'm not changing the passwd as root */
473 debug(LOG_DEBUG, "handling pre-chroot request");
474 debug(LOG_DEBUG, "setting uid to %u", getuid());
475
476 if (-1==(seteuid(getuid())))
477 {
478 syslog (LOG_ERR, "couldn't revert to my real uid. seteuid: %m");
479 exit(EXIT_FAILURE);
480 }
481
482 av[0] = substitute_known_path(av[0]);
483 flat_request = flatten_vector(av);
484
485 /*
486 * sanity check, substitute_known_path should have given the exact path,
487 * ONLY execute if an exact match was found
488 */
489 if (
490 #ifdef PASSWD_COMPAT
491 (!exact_match(av[0], PROG_PASSWD))
492 #else
493 1
494 #endif
495 #ifdef QUOTA_COMPAT
496 && (!exact_match(av[0], PROG_QUOTA))
497 #endif
498 ) {
499 syslog(LOG_ERR, "Invalid pre-chroot request attempted: '%s' [%s]", av[0], logstamp());
500 exit(EXIT_FAILURE);
501 }
502
503 if(check_dangerous_args(av))
504 {
505 syslog(LOG_ERR, "requested command (%s) tried to use disallowed argument [%s])",
506 flat_request, logstamp());
507 exit(EXIT_FAILURE);
508 }
509
510 if (valid_arg_vector(av))
511 {
512 int status = 0;
513
514 debug(LOG_DEBUG, "about to fork (%s) [%s]",flat_request,logstamp());
515
516 if (fork() == 0)
517 retval=execve(av[0],av,env);
518 else
519 {
520 wait(&status);
521 fflush(stdout);
522 fflush(stderr);
523 free(flat_request);
524
525 debug(LOG_DEBUG, "forked child returned... [%s]",logstamp());
526
527 return WEXITSTATUS(status);
528 }
529
530 } else {
531 syslog(LOG_ERR, "invalid argument vector (%s) [%s]",
532 flat_request,logstamp());
533 free(flat_request);
534
535 exit(EXIT_FAILURE);
536 }
537 return retval;
538 }
539
540 #endif /* passwd or quota support */
541 #endif /* chrooted support */
542
543 #ifdef WINSCP_COMPAT
544
545 int winscp_transit_request(char *request)
546 {
547 char *new_request=NULL;
548
549 /*
550 * for scp -t and scp -f commands, winscp preceeds each
551 * command with a "begin-of-file" marker we need to
552 * provide, everything after that can be handled by
553 * winscp_regular_request.
554 */
555
556 new_request=strbeg(request,WINSCP_BOF_REQ);
557 if (NULL == new_request)
558 {
559 return(-1); /* improper transit cmd */
560 }
561 printf ("%s\n",WINSCP_BOF); /* start transfer */
562 fflush(stdout);
563 return(winscp_regular_request(new_request));
564 }
565
566 int winscp_regular_request(char *request)
567 {
568 /*
569 * winscp uses one of two requests to terminate each command.
570 * we must determine which (if any) is terminating this request. fun!
571 */
572 char *new_request=NULL;
573 int retval=0;
574 int retzero=1;
575 new_request=strend(request, WINSCP_EOF_REQ_ZERO);
576 if (NULL == new_request)
577 {
578 retzero=0;
579 new_request=strend(request, WINSCP_EOF_REQ_STATUS);
580 if (NULL == new_request)
581 {
582 new_request=strend(request, WINSCP_EOF_REQ_RETVAL);
583 if (NULL == new_request)
584 {
585 printf ("command wasn't terminated with %s, %s or %s\n",
586 WINSCP_EOF_REQ_RETVAL, WINSCP_EOF_REQ_ZERO, WINSCP_EOF_REQ_STATUS);
587 return(-1); /* bogus termination */
588 }
589 }
590 }
591 /*
592 * here is where we fool winscp clients into believing we are a real shell
593 */
594 if ((exact_match(new_request, "echo \"$status\"")) ||
595 (exact_match(new_request, "echo \"$?\"")))
596 {
597 printf ("0\n");
598 fflush(stdout);
599 }
600 /*
601 * ignore unalias and unset commands
602 */
603 else if ((NULL!=strbeg(new_request,"unset ")) ||
604 (NULL!=strbeg(new_request,"unalias ")))
605 retval=0;
606 else
607 {
608 retval=process_ssh_request(new_request);
609 if (retzero) /* ignore actual retval if winscp wants us to */
610 retval=0;
611 }
612 free(new_request);
613 return(retval);
614 }
615
616 int process_winscp_requests(void)
617 {
618 char linebuf[MAX_REQUEST];
619 int count=0; /* num of semicolons in cmd */
620 int ack=0;
621
622 winscp_mode=1;
623
624 fflush(stdout);
625
626 #ifdef ENABLE_DEFAULT_CHDIR
627 syslog(LOG_INFO, "changing initial directory to %s", DEFAULT_CHDIR);
628 chdir(DEFAULT_CHDIR);
629 #endif
630
631 /*
632 * now process commands interactively
633 */
634 while (fgets(&linebuf[0],MAX_REQUEST, stdin) != NULL)
635 {
636 ack=0;
637
638 if (strlen(linebuf)==0)
639 return(-1);
640
641 linebuf[strlen(linebuf)-1]=0; /* drop the trailing CR */
642 count=cntchr(linebuf,';');
643
644 if (count==1) /* regular cmd */
645 {
646 ack=winscp_regular_request(linebuf);
647 }
648 else if (count==2) /* transit command */
649 {
650 ack=winscp_transit_request(linebuf);
651 }
652 else
653 ack=0; /* winscp always sends 2 or 3 cmds at once */
654
655 printf ("%s%d\n",WINSCP_EOF,ack); /* respond to client */
656 fflush(stdout);
657 }
658 return 0;
659 }
660
661 #endif
662
663 int process_ssh_request(char *request)
664 {
665 char **av, **tmp_av, **tenv;
666 char *flat_request,*tmpstring, *tmprequest;
667 char bad_winscp3str[] = "test -x /usr/lib/sftp-server && exec /usr/lib/sftp-server test -x /usr/local/lib/sftp-server && exec /usr/local/lib/sftp-server exec sftp-server";
668 int retval;
669 int reqlen=strlen(request);
670 char **env = NULL;
671
672 debug(LOG_DEBUG, "processing request: \"%s\"\n", request);
673
674 tmprequest=strdup(request);
675
676 #ifdef WINSCP_COMPAT
677
678 bad_winscp3str[57]=10;
679 bad_winscp3str[127]=10;
680
681 if(strcmp(request,bad_winscp3str)==0)
682 {
683 /*
684 * switch out the command to use, winscp wont know the difference
685 */
686 free(tmprequest);
687 tmprequest=strdup(PROG_SFTP_SERVER);
688 syslog(LOG_DEBUG, "winscp3 compat correcting to: \"[%s]\"\n", PROG_SFTP_SERVER);
689 }
690 #endif
691
692
693 #ifdef GFTP_COMPAT
694 /*
695 * gFTP compatibility hack
696 */
697 if (NULL != (tmpstring=strbeg(request, "echo -n xsftp ; ")))
698 {
699 free(tmprequest);
700 tmprequest=strdup(tmpstring);
701 printf("xsftp");
702 fflush(stdout);
703 }
704 #endif
705
706 #ifdef RESTRICTIVE_FILENAMES
707 /*
708 * we flat out reject special chars
709 */
710 if (!valid_chars(tmprequest))
711 {
712 debug(LOG_DEBUG, "rejected because of invalid chars (%s)", logstamp());
713 free(tmprequest);
714 return(-1);
715 }
716 #endif
717
718 #ifdef WINSCP_COMPAT
719 if (strbeg(tmprequest,PROG_CD))
720 {
721 char *destdir=(char *)malloc(reqlen);
722 if (destdir == NULL)
723 {
724 perror("malloc");
725 exit(EXIT_FAILURE);
726 }
727
728 /*
729 * well, now that scponly is a persistent shell
730 * i have to maintain a $PWD. damn.
731 * we're going to INSIST upon a double quote
732 * encapsulated new directory to change to.
733 */
734 if ((tmprequest[(reqlen-1)]=='"') && (tmprequest[3]=='"'))
735 {
736 bzero(destdir,reqlen);
737 strncpy(destdir,&tmprequest[4],reqlen-5);
738 debug(LOG_INFO, "chdir: %s (%s)", tmprequest, logstamp());
739 retval=chdir(destdir);
740 free(destdir);
741 free(tmprequest);
742 return(retval);
743 }
744 syslog(LOG_ERR, "bogus chdir request: %s (%s)", tmprequest, logstamp());
745 free(tmprequest);
746 return(-1);
747 }
748 #endif
749
750 /*
751 * convert request string to an arg_vector
752 */
753 av = build_arg_vector(tmprequest);
754
755 /*
756 * clean any path info from request and substitute our known pathnames
757 */
758 av[0] = substitute_known_path(av[0]);
759
760 /*
761 * we only process wildcards for scp commands
762 */
763 #ifdef ENABLE_WILDCARDS
764 #ifdef ENABLE_SCP2
765 if (exact_match(av[0],PROG_SCP))
766 av = expand_wildcards(av);
767 #endif
768 #endif
769
770 /*
771 * check for a compile time chdir configuration
772 */
773 #ifdef ENABLE_DEFAULT_CHDIR
774 if (exact_match(av[0],PROG_SFTP_SERVER))
775 {
776 syslog(LOG_INFO, "changing initial directory to %s", DEFAULT_CHDIR);
777 chdir(DEFAULT_CHDIR);
778 }
779 #endif
780
781
782 flat_request = flatten_vector(av);
783
784 /*
785 * Use a temp arg vector since getopt will permute the command line arguments
786 * for anything that it does not know about. If all rsync options are well
787 * defined this isn't necessary.
788 */
789 tmp_av = build_arg_vector(flat_request);
790 if(check_dangerous_args(tmp_av))
791 {
792 syslog(LOG_ERR, "requested command (%s) tried to use disallowed argument (%s))",
793 flat_request, logstamp());
794 exit(EXIT_FAILURE);
795 }
796 discard_vector(tmp_av);
797
798 if (valid_arg_vector(av))
799 {
800
801 /*
802 * Unison needs the HOME environment variable be set to the directory
803 * where the .unison directory resides.
804 */
805 #ifdef USE_SAFE_ENVIRONMENT
806 safeenv[0] = NULL;
807 filter_allowed_env_vars();
808 tenv = safeenv;
809 if (debuglevel) {
810 while (NULL != *tenv) {
811 syslog(LOG_DEBUG, "Environment contains \"%s\"", *tenv++);
812 }
813 }
814 env = safeenv;
815 #endif
816
817 #ifdef UNISON_COMPAT
818 /* the HOME environment variable should have been set above, but I need to make sure
819 * that it's value as read from the environment is replaced with the actual value
820 * as it exists within the chroot, which is what the applications will expect to see.
821 */
822 if (replace_env_entry("HOME",homedir) && (((strlen(homedir) + 6 ) > FILENAME_MAX) || !mysetenv("HOME",homedir)))
823 {
824 syslog(LOG_ERR, "could not set HOME environment variable (%s)", logstamp());
825 exit(EXIT_FAILURE);
826 }
827 debug(LOG_DEBUG, "set non-chrooted HOME environment variable to %s (%s)", homedir, logstamp());
828 #endif
829 syslog(LOG_INFO, "running: %s (%s)", flat_request, logstamp());
830
831 #ifdef WINSCP_COMPAT
832 if (winscp_mode)
833 {
834 int status=0;
835 if (fork() == 0)
836 retval=execve(av[0],av,env);
837 else
838 {
839 wait(&status);
840 fflush(stdout);
841 fflush(stderr);
842 discard_vector(av);
843 #ifdef USE_SAFE_ENVIRONMENT
844 discard_child_vectors(safeenv);
845 #endif
846 free(flat_request);
847 free(tmprequest);
848 return(WEXITSTATUS(status));
849 }
850 }
851 else
852 #endif
853 {
854 debug(LOG_DEBUG, "about to exec \"%s\" (%s)", av[0], logstamp());
855 retval=execve(av[0],av,env);
856 }
857 syslog(LOG_ERR, "failed: %s with error %s(%u) (%s)", flat_request, strerror(errno), errno, logstamp());
858 free(flat_request);
859 discard_vector(av);
860 #ifdef USE_SAFE_ENVIRONMENT
861 discard_child_vectors(safeenv);
862 #endif
863 #ifdef WINSCP_COMPAT
864 if (winscp_mode)
865 {
866 free(tmprequest);
867 return(-1);
868 }
869 else
870 #endif
871 exit(errno);
872 }
873
874 /*
875 * reaching this point in the code means the request isnt one of
876 * our accepted commands
877 */
878 if (debuglevel)
879 {
880 if (exact_match(flat_request,tmprequest))
881 syslog (LOG_ERR, "denied request: %s [%s]", tmprequest, logstamp());
882 else
883 syslog (LOG_ERR, "denied request: %s (resolved to: %s) [%s]", tmprequest, flat_request, logstamp());
884 }
885 free(flat_request);
886 #ifdef WINSCP_COMPAT
887 if (winscp_mode)
888 {
889 printf ("command not permitted by scponly\n");
890 free(tmprequest);
891 return(-1);
892 }
893 else
894 #endif
895 exit(EXIT_FAILURE);
896 }
897
898 /* vim: set noet sw=4 ts=4: */