diff options
author | rgrimes <rgrimes@FreeBSD.org> | 1994-05-27 12:39:25 +0000 |
---|---|---|
committer | rgrimes <rgrimes@FreeBSD.org> | 1994-05-27 12:39:25 +0000 |
commit | 7d07d2de2f52d4e2eba169e5563165309a795128 (patch) | |
tree | c3590f60f61233b4a571cfe3bfc08f6ab6591c88 /libexec/ftpd | |
parent | f9ab90d9d6d02989a075d0f0074496d5b1045e4b (diff) | |
download | FreeBSD-src-7d07d2de2f52d4e2eba169e5563165309a795128.zip FreeBSD-src-7d07d2de2f52d4e2eba169e5563165309a795128.tar.gz |
BSD 4.4 Lite Libexec Sources
Diffstat (limited to 'libexec/ftpd')
-rw-r--r-- | libexec/ftpd/Makefile | 9 | ||||
-rw-r--r-- | libexec/ftpd/extern.h | 65 | ||||
-rw-r--r-- | libexec/ftpd/ftpcmd.y | 1266 | ||||
-rw-r--r-- | libexec/ftpd/ftpd.8 | 290 | ||||
-rw-r--r-- | libexec/ftpd/ftpd.c | 1654 | ||||
-rw-r--r-- | libexec/ftpd/logwtmp.c | 75 | ||||
-rw-r--r-- | libexec/ftpd/pathnames.h | 40 | ||||
-rw-r--r-- | libexec/ftpd/popen.c | 171 |
8 files changed, 3570 insertions, 0 deletions
diff --git a/libexec/ftpd/Makefile b/libexec/ftpd/Makefile new file mode 100644 index 0000000..946aab7 --- /dev/null +++ b/libexec/ftpd/Makefile @@ -0,0 +1,9 @@ +# @(#)Makefile 8.2 (Berkeley) 4/4/94 + +PROG= ftpd +CFLAGS+=-DSETPROCTITLE +SRCS= ftpd.c ftpcmd.c logwtmp.c popen.c +MAN8= ftpd.0 +CLEANFILES+=ftpcmd.c y.tab.h + +.include <bsd.prog.mk> diff --git a/libexec/ftpd/extern.h b/libexec/ftpd/extern.h new file mode 100644 index 0000000..e3336b5 --- /dev/null +++ b/libexec/ftpd/extern.h @@ -0,0 +1,65 @@ +/*- + * Copyright (c) 1992, 1993 + * The Regents of the University of California. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by the University of + * California, Berkeley and its contributors. + * 4. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * @(#)extern.h 8.2 (Berkeley) 4/4/94 + */ + +void blkfree __P((char **)); +char **copyblk __P((char **)); +void cwd __P((char *)); +void delete __P((char *)); +void dologout __P((int)); +void fatal __P((char *)); +int ftpd_pclose __P((FILE *)); +FILE *ftpd_popen __P((char *, char *)); +char *getline __P((char *, int, FILE *)); +void logwtmp __P((char *, char *, char *)); +void lreply __P((int, const char *, ...)); +void makedir __P((char *)); +void nack __P((char *)); +void pass __P((char *)); +void passive __P((void)); +void perror_reply __P((int, char *)); +void pwd __P((void)); +void removedir __P((char *)); +void renamecmd __P((char *, char *)); +char *renamefrom __P((char *)); +void reply __P((int, const char *, ...)); +void retrieve __P((char *, char *)); +void send_file_list __P((char *)); +void setproctitle __P((const char *, ...)); +void statcmd __P((void)); +void statfilecmd __P((char *)); +void store __P((char *, char *, int)); +void upper __P((char *)); +void user __P((char *)); +void yyerror __P((char *)); diff --git a/libexec/ftpd/ftpcmd.y b/libexec/ftpd/ftpcmd.y new file mode 100644 index 0000000..6ec3d25 --- /dev/null +++ b/libexec/ftpd/ftpcmd.y @@ -0,0 +1,1266 @@ +/* + * Copyright (c) 1985, 1988, 1993, 1994 + * The Regents of the University of California. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by the University of + * California, Berkeley and its contributors. + * 4. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * @(#)ftpcmd.y 8.3 (Berkeley) 4/6/94 + */ + +/* + * Grammar for FTP commands. + * See RFC 959. + */ + +%{ + +#ifndef lint +static char sccsid[] = "@(#)ftpcmd.y 8.3 (Berkeley) 4/6/94"; +#endif /* not lint */ + +#include <sys/param.h> +#include <sys/socket.h> +#include <sys/stat.h> + +#include <netinet/in.h> +#include <arpa/ftp.h> + +#include <ctype.h> +#include <errno.h> +#include <glob.h> +#include <pwd.h> +#include <setjmp.h> +#include <signal.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <syslog.h> +#include <time.h> +#include <unistd.h> + +#include "extern.h" + +extern struct sockaddr_in data_dest; +extern int logged_in; +extern struct passwd *pw; +extern int guest; +extern int logging; +extern int type; +extern int form; +extern int debug; +extern int timeout; +extern int maxtimeout; +extern int pdata; +extern char hostname[], remotehost[]; +extern char proctitle[]; +extern int usedefault; +extern int transflag; +extern char tmpline[]; + +off_t restart_point; + +static int cmd_type; +static int cmd_form; +static int cmd_bytesz; +char cbuf[512]; +char *fromname; + +%} + +%union { + int i; + char *s; +} + +%token + A B C E F I + L N P R S T + + SP CRLF COMMA + + USER PASS ACCT REIN QUIT PORT + PASV TYPE STRU MODE RETR STOR + APPE MLFL MAIL MSND MSOM MSAM + MRSQ MRCP ALLO REST RNFR RNTO + ABOR DELE CWD LIST NLST SITE + STAT HELP NOOP MKD RMD PWD + CDUP STOU SMNT SYST SIZE MDTM + + UMASK IDLE CHMOD + + LEXERR + +%token <s> STRING +%token <i> NUMBER + +%type <i> check_login octal_number byte_size +%type <i> struct_code mode_code type_code form_code +%type <s> pathstring pathname password username + +%start cmd_list + +%% + +cmd_list + : /* empty */ + | cmd_list cmd + { + fromname = (char *) 0; + restart_point = (off_t) 0; + } + | cmd_list rcmd + ; + +cmd + : USER SP username CRLF + { + user($3); + free($3); + } + | PASS SP password CRLF + { + pass($3); + free($3); + } + | PORT SP host_port CRLF + { + usedefault = 0; + if (pdata >= 0) { + (void) close(pdata); + pdata = -1; + } + reply(200, "PORT command successful."); + } + | PASV CRLF + { + passive(); + } + | TYPE SP type_code CRLF + { + switch (cmd_type) { + + case TYPE_A: + if (cmd_form == FORM_N) { + reply(200, "Type set to A."); + type = cmd_type; + form = cmd_form; + } else + reply(504, "Form must be N."); + break; + + case TYPE_E: + reply(504, "Type E not implemented."); + break; + + case TYPE_I: + reply(200, "Type set to I."); + type = cmd_type; + break; + + case TYPE_L: +#if NBBY == 8 + if (cmd_bytesz == 8) { + reply(200, + "Type set to L (byte size 8)."); + type = cmd_type; + } else + reply(504, "Byte size must be 8."); +#else /* NBBY == 8 */ + UNIMPLEMENTED for NBBY != 8 +#endif /* NBBY == 8 */ + } + } + | STRU SP struct_code CRLF + { + switch ($3) { + + case STRU_F: + reply(200, "STRU F ok."); + break; + + default: + reply(504, "Unimplemented STRU type."); + } + } + | MODE SP mode_code CRLF + { + switch ($3) { + + case MODE_S: + reply(200, "MODE S ok."); + break; + + default: + reply(502, "Unimplemented MODE type."); + } + } + | ALLO SP NUMBER CRLF + { + reply(202, "ALLO command ignored."); + } + | ALLO SP NUMBER SP R SP NUMBER CRLF + { + reply(202, "ALLO command ignored."); + } + | RETR check_login SP pathname CRLF + { + if ($2 && $4 != NULL) + retrieve((char *) 0, $4); + if ($4 != NULL) + free($4); + } + | STOR check_login SP pathname CRLF + { + if ($2 && $4 != NULL) + store($4, "w", 0); + if ($4 != NULL) + free($4); + } + | APPE check_login SP pathname CRLF + { + if ($2 && $4 != NULL) + store($4, "a", 0); + if ($4 != NULL) + free($4); + } + | NLST check_login CRLF + { + if ($2) + send_file_list("."); + } + | NLST check_login SP STRING CRLF + { + if ($2 && $4 != NULL) + send_file_list($4); + if ($4 != NULL) + free($4); + } + | LIST check_login CRLF + { + if ($2) + retrieve("/bin/ls -lgA", ""); + } + | LIST check_login SP pathname CRLF + { + if ($2 && $4 != NULL) + retrieve("/bin/ls -lgA %s", $4); + if ($4 != NULL) + free($4); + } + | STAT check_login SP pathname CRLF + { + if ($2 && $4 != NULL) + statfilecmd($4); + if ($4 != NULL) + free($4); + } + | STAT CRLF + { + statcmd(); + } + | DELE check_login SP pathname CRLF + { + if ($2 && $4 != NULL) + delete($4); + if ($4 != NULL) + free($4); + } + | RNTO SP pathname CRLF + { + if (fromname) { + renamecmd(fromname, $3); + free(fromname); + fromname = (char *) 0; + } else { + reply(503, "Bad sequence of commands."); + } + free($3); + } + | ABOR CRLF + { + reply(225, "ABOR command successful."); + } + | CWD check_login CRLF + { + if ($2) + cwd(pw->pw_dir); + } + | CWD check_login SP pathname CRLF + { + if ($2 && $4 != NULL) + cwd($4); + if ($4 != NULL) + free($4); + } + | HELP CRLF + { + help(cmdtab, (char *) 0); + } + | HELP SP STRING CRLF + { + char *cp = $3; + + if (strncasecmp(cp, "SITE", 4) == 0) { + cp = $3 + 4; + if (*cp == ' ') + cp++; + if (*cp) + help(sitetab, cp); + else + help(sitetab, (char *) 0); + } else + help(cmdtab, $3); + } + | NOOP CRLF + { + reply(200, "NOOP command successful."); + } + | MKD check_login SP pathname CRLF + { + if ($2 && $4 != NULL) + makedir($4); + if ($4 != NULL) + free($4); + } + | RMD check_login SP pathname CRLF + { + if ($2 && $4 != NULL) + removedir($4); + if ($4 != NULL) + free($4); + } + | PWD check_login CRLF + { + if ($2) + pwd(); + } + | CDUP check_login CRLF + { + if ($2) + cwd(".."); + } + | SITE SP HELP CRLF + { + help(sitetab, (char *) 0); + } + | SITE SP HELP SP STRING CRLF + { + help(sitetab, $5); + } + | SITE SP UMASK check_login CRLF + { + int oldmask; + + if ($4) { + oldmask = umask(0); + (void) umask(oldmask); + reply(200, "Current UMASK is %03o", oldmask); + } + } + | SITE SP UMASK check_login SP octal_number CRLF + { + int oldmask; + + if ($4) { + if (($6 == -1) || ($6 > 0777)) { + reply(501, "Bad UMASK value"); + } else { + oldmask = umask($6); + reply(200, + "UMASK set to %03o (was %03o)", + $6, oldmask); + } + } + } + | SITE SP CHMOD check_login SP octal_number SP pathname CRLF + { + if ($4 && ($8 != NULL)) { + if ($6 > 0777) + reply(501, + "CHMOD: Mode value must be between 0 and 0777"); + else if (chmod($8, $6) < 0) + perror_reply(550, $8); + else + reply(200, "CHMOD command successful."); + } + if ($8 != NULL) + free($8); + } + | SITE SP IDLE CRLF + { + reply(200, + "Current IDLE time limit is %d seconds; max %d", + timeout, maxtimeout); + } + | SITE SP IDLE SP NUMBER CRLF + { + if ($5 < 30 || $5 > maxtimeout) { + reply(501, + "Maximum IDLE time must be between 30 and %d seconds", + maxtimeout); + } else { + timeout = $5; + (void) alarm((unsigned) timeout); + reply(200, + "Maximum IDLE time set to %d seconds", + timeout); + } + } + | STOU check_login SP pathname CRLF + { + if ($2 && $4 != NULL) + store($4, "w", 1); + if ($4 != NULL) + free($4); + } + | SYST CRLF + { +#ifdef unix +#ifdef BSD + reply(215, "UNIX Type: L%d Version: BSD-%d", + NBBY, BSD); +#else /* BSD */ + reply(215, "UNIX Type: L%d", NBBY); +#endif /* BSD */ +#else /* unix */ + reply(215, "UNKNOWN Type: L%d", NBBY); +#endif /* unix */ + } + + /* + * SIZE is not in RFC959, but Postel has blessed it and + * it will be in the updated RFC. + * + * Return size of file in a format suitable for + * using with RESTART (we just count bytes). + */ + | SIZE check_login SP pathname CRLF + { + if ($2 && $4 != NULL) + sizecmd($4); + if ($4 != NULL) + free($4); + } + + /* + * MDTM is not in RFC959, but Postel has blessed it and + * it will be in the updated RFC. + * + * Return modification time of file as an ISO 3307 + * style time. E.g. YYYYMMDDHHMMSS or YYYYMMDDHHMMSS.xxx + * where xxx is the fractional second (of any precision, + * not necessarily 3 digits) + */ + | MDTM check_login SP pathname CRLF + { + if ($2 && $4 != NULL) { + struct stat stbuf; + if (stat($4, &stbuf) < 0) + reply(550, "%s: %s", + $4, strerror(errno)); + else if (!S_ISREG(stbuf.st_mode)) { + reply(550, "%s: not a plain file.", $4); + } else { + struct tm *t; + t = gmtime(&stbuf.st_mtime); + reply(213, + "19%02d%02d%02d%02d%02d%02d", + t->tm_year, t->tm_mon+1, t->tm_mday, + t->tm_hour, t->tm_min, t->tm_sec); + } + } + if ($4 != NULL) + free($4); + } + | QUIT CRLF + { + reply(221, "Goodbye."); + dologout(0); + } + | error CRLF + { + yyerrok; + } + ; +rcmd + : RNFR check_login SP pathname CRLF + { + char *renamefrom(); + + restart_point = (off_t) 0; + if ($2 && $4) { + fromname = renamefrom($4); + if (fromname == (char *) 0 && $4) { + free($4); + } + } + } + | REST SP byte_size CRLF + { + fromname = (char *) 0; + restart_point = $3; /* XXX $3 is only "int" */ + reply(350, "Restarting at %qd. %s", restart_point, + "Send STORE or RETRIEVE to initiate transfer."); + } + ; + +username + : STRING + ; + +password + : /* empty */ + { + $$ = (char *)calloc(1, sizeof(char)); + } + | STRING + ; + +byte_size + : NUMBER + ; + +host_port + : NUMBER COMMA NUMBER COMMA NUMBER COMMA NUMBER COMMA + NUMBER COMMA NUMBER + { + char *a, *p; + + a = (char *)&data_dest.sin_addr; + a[0] = $1; a[1] = $3; a[2] = $5; a[3] = $7; + p = (char *)&data_dest.sin_port; + p[0] = $9; p[1] = $11; + data_dest.sin_family = AF_INET; + } + ; + +form_code + : N + { + $$ = FORM_N; + } + | T + { + $$ = FORM_T; + } + | C + { + $$ = FORM_C; + } + ; + +type_code + : A + { + cmd_type = TYPE_A; + cmd_form = FORM_N; + } + | A SP form_code + { + cmd_type = TYPE_A; + cmd_form = $3; + } + | E + { + cmd_type = TYPE_E; + cmd_form = FORM_N; + } + | E SP form_code + { + cmd_type = TYPE_E; + cmd_form = $3; + } + | I + { + cmd_type = TYPE_I; + } + | L + { + cmd_type = TYPE_L; + cmd_bytesz = NBBY; + } + | L SP byte_size + { + cmd_type = TYPE_L; + cmd_bytesz = $3; + } + /* this is for a bug in the BBN ftp */ + | L byte_size + { + cmd_type = TYPE_L; + cmd_bytesz = $2; + } + ; + +struct_code + : F + { + $$ = STRU_F; + } + | R + { + $$ = STRU_R; + } + | P + { + $$ = STRU_P; + } + ; + +mode_code + : S + { + $$ = MODE_S; + } + | B + { + $$ = MODE_B; + } + | C + { + $$ = MODE_C; + } + ; + +pathname + : pathstring + { + /* + * Problem: this production is used for all pathname + * processing, but only gives a 550 error reply. + * This is a valid reply in some cases but not in others. + */ + if (logged_in && $1 && *$1 == '~') { + glob_t gl; + int flags = + GLOB_BRACE|GLOB_NOCHECK|GLOB_QUOTE|GLOB_TILDE; + + memset(&gl, 0, sizeof(gl)); + if (glob($1, flags, NULL, &gl) || + gl.gl_pathc == 0) { + reply(550, "not found"); + $$ = NULL; + } else { + $$ = strdup(gl.gl_pathv[0]); + } + globfree(&gl); + free($1); + } else + $$ = $1; + } + ; + +pathstring + : STRING + ; + +octal_number + : NUMBER + { + int ret, dec, multby, digit; + + /* + * Convert a number that was read as decimal number + * to what it would be if it had been read as octal. + */ + dec = $1; + multby = 1; + ret = 0; + while (dec) { + digit = dec%10; + if (digit > 7) { + ret = -1; + break; + } + ret += digit * multby; + multby *= 8; + dec /= 10; + } + $$ = ret; + } + ; + + +check_login + : /* empty */ + { + if (logged_in) + $$ = 1; + else { + reply(530, "Please login with USER and PASS."); + $$ = 0; + } + } + ; + +%% + +extern jmp_buf errcatch; + +#define CMD 0 /* beginning of command */ +#define ARGS 1 /* expect miscellaneous arguments */ +#define STR1 2 /* expect SP followed by STRING */ +#define STR2 3 /* expect STRING */ +#define OSTR 4 /* optional SP then STRING */ +#define ZSTR1 5 /* SP then optional STRING */ +#define ZSTR2 6 /* optional STRING after SP */ +#define SITECMD 7 /* SITE command */ +#define NSTR 8 /* Number followed by a string */ + +struct tab { + char *name; + short token; + short state; + short implemented; /* 1 if command is implemented */ + char *help; +}; + +struct tab cmdtab[] = { /* In order defined in RFC 765 */ + { "USER", USER, STR1, 1, "<sp> username" }, + { "PASS", PASS, ZSTR1, 1, "<sp> password" }, + { "ACCT", ACCT, STR1, 0, "(specify account)" }, + { "SMNT", SMNT, ARGS, 0, "(structure mount)" }, + { "REIN", REIN, ARGS, 0, "(reinitialize server state)" }, + { "QUIT", QUIT, ARGS, 1, "(terminate service)", }, + { "PORT", PORT, ARGS, 1, "<sp> b0, b1, b2, b3, b4" }, + { "PASV", PASV, ARGS, 1, "(set server in passive mode)" }, + { "TYPE", TYPE, ARGS, 1, "<sp> [ A | E | I | L ]" }, + { "STRU", STRU, ARGS, 1, "(specify file structure)" }, + { "MODE", MODE, ARGS, 1, "(specify transfer mode)" }, + { "RETR", RETR, STR1, 1, "<sp> file-name" }, + { "STOR", STOR, STR1, 1, "<sp> file-name" }, + { "APPE", APPE, STR1, 1, "<sp> file-name" }, + { "MLFL", MLFL, OSTR, 0, "(mail file)" }, + { "MAIL", MAIL, OSTR, 0, "(mail to user)" }, + { "MSND", MSND, OSTR, 0, "(mail send to terminal)" }, + { "MSOM", MSOM, OSTR, 0, "(mail send to terminal or mailbox)" }, + { "MSAM", MSAM, OSTR, 0, "(mail send to terminal and mailbox)" }, + { "MRSQ", MRSQ, OSTR, 0, "(mail recipient scheme question)" }, + { "MRCP", MRCP, STR1, 0, "(mail recipient)" }, + { "ALLO", ALLO, ARGS, 1, "allocate storage (vacuously)" }, + { "REST", REST, ARGS, 1, "<sp> offset (restart command)" }, + { "RNFR", RNFR, STR1, 1, "<sp> file-name" }, + { "RNTO", RNTO, STR1, 1, "<sp> file-name" }, + { "ABOR", ABOR, ARGS, 1, "(abort operation)" }, + { "DELE", DELE, STR1, 1, "<sp> file-name" }, + { "CWD", CWD, OSTR, 1, "[ <sp> directory-name ]" }, + { "XCWD", CWD, OSTR, 1, "[ <sp> directory-name ]" }, + { "LIST", LIST, OSTR, 1, "[ <sp> path-name ]" }, + { "NLST", NLST, OSTR, 1, "[ <sp> path-name ]" }, + { "SITE", SITE, SITECMD, 1, "site-cmd [ <sp> arguments ]" }, + { "SYST", SYST, ARGS, 1, "(get type of operating system)" }, + { "STAT", STAT, OSTR, 1, "[ <sp> path-name ]" }, + { "HELP", HELP, OSTR, 1, "[ <sp> <string> ]" }, + { "NOOP", NOOP, ARGS, 1, "" }, + { "MKD", MKD, STR1, 1, "<sp> path-name" }, + { "XMKD", MKD, STR1, 1, "<sp> path-name" }, + { "RMD", RMD, STR1, 1, "<sp> path-name" }, + { "XRMD", RMD, STR1, 1, "<sp> path-name" }, + { "PWD", PWD, ARGS, 1, "(return current directory)" }, + { "XPWD", PWD, ARGS, 1, "(return current directory)" }, + { "CDUP", CDUP, ARGS, 1, "(change to parent directory)" }, + { "XCUP", CDUP, ARGS, 1, "(change to parent directory)" }, + { "STOU", STOU, STR1, 1, "<sp> file-name" }, + { "SIZE", SIZE, OSTR, 1, "<sp> path-name" }, + { "MDTM", MDTM, OSTR, 1, "<sp> path-name" }, + { NULL, 0, 0, 0, 0 } +}; + +struct tab sitetab[] = { + { "UMASK", UMASK, ARGS, 1, "[ <sp> umask ]" }, + { "IDLE", IDLE, ARGS, 1, "[ <sp> maximum-idle-time ]" }, + { "CHMOD", CHMOD, NSTR, 1, "<sp> mode <sp> file-name" }, + { "HELP", HELP, OSTR, 1, "[ <sp> <string> ]" }, + { NULL, 0, 0, 0, 0 } +}; + +static char *copy __P((char *)); +static void help __P((struct tab *, char *)); +static struct tab * + lookup __P((struct tab *, char *)); +static void sizecmd __P((char *)); +static void toolong __P((int)); +static int yylex __P((void)); + +static struct tab * +lookup(p, cmd) + struct tab *p; + char *cmd; +{ + + for (; p->name != NULL; p++) + if (strcmp(cmd, p->name) == 0) + return (p); + return (0); +} + +#include <arpa/telnet.h> + +/* + * getline - a hacked up version of fgets to ignore TELNET escape codes. + */ +char * +getline(s, n, iop) + char *s; + int n; + FILE *iop; +{ + int c; + register char *cs; + + cs = s; +/* tmpline may contain saved command from urgent mode interruption */ + for (c = 0; tmpline[c] != '\0' && --n > 0; ++c) { + *cs++ = tmpline[c]; + if (tmpline[c] == '\n') { + *cs++ = '\0'; + if (debug) + syslog(LOG_DEBUG, "command: %s", s); + tmpline[0] = '\0'; + return(s); + } + if (c == 0) + tmpline[0] = '\0'; + } + while ((c = getc(iop)) != EOF) { + c &= 0377; + if (c == IAC) { + if ((c = getc(iop)) != EOF) { + c &= 0377; + switch (c) { + case WILL: + case WONT: + c = getc(iop); + printf("%c%c%c", IAC, DONT, 0377&c); + (void) fflush(stdout); + continue; + case DO: + case DONT: + c = getc(iop); + printf("%c%c%c", IAC, WONT, 0377&c); + (void) fflush(stdout); + continue; + case IAC: + break; + default: + continue; /* ignore command */ + } + } + } + *cs++ = c; + if (--n <= 0 || c == '\n') + break; + } + if (c == EOF && cs == s) + return (NULL); + *cs++ = '\0'; + if (debug) { + if (!guest && strncasecmp("pass ", s, 5) == 0) { + /* Don't syslog passwords */ + syslog(LOG_DEBUG, "command: %.5s ???", s); + } else { + register char *cp; + register int len; + + /* Don't syslog trailing CR-LF */ + len = strlen(s); + cp = s + len - 1; + while (cp >= s && (*cp == '\n' || *cp == '\r')) { + --cp; + --len; + } + syslog(LOG_DEBUG, "command: %.*s", len, s); + } + } + return (s); +} + +static void +toolong(signo) + int signo; +{ + + reply(421, + "Timeout (%d seconds): closing control connection.", timeout); + if (logging) + syslog(LOG_INFO, "User %s timed out after %d seconds", + (pw ? pw -> pw_name : "unknown"), timeout); + dologout(1); +} + +static int +yylex() +{ + static int cpos, state; + char *cp, *cp2; + struct tab *p; + int n; + char c; + + for (;;) { + switch (state) { + + case CMD: + (void) signal(SIGALRM, toolong); + (void) alarm((unsigned) timeout); + if (getline(cbuf, sizeof(cbuf)-1, stdin) == NULL) { + reply(221, "You could at least say goodbye."); + dologout(0); + } + (void) alarm(0); +#ifdef SETPROCTITLE + if (strncasecmp(cbuf, "PASS", 4) != NULL) + setproctitle("%s: %s", proctitle, cbuf); +#endif /* SETPROCTITLE */ + if ((cp = strchr(cbuf, '\r'))) { + *cp++ = '\n'; + *cp = '\0'; + } + if ((cp = strpbrk(cbuf, " \n"))) + cpos = cp - cbuf; + if (cpos == 0) + cpos = 4; + c = cbuf[cpos]; + cbuf[cpos] = '\0'; + upper(cbuf); + p = lookup(cmdtab, cbuf); + cbuf[cpos] = c; + if (p != 0) { + if (p->implemented == 0) { + nack(p->name); + longjmp(errcatch,0); + /* NOTREACHED */ + } + state = p->state; + yylval.s = p->name; + return (p->token); + } + break; + + case SITECMD: + if (cbuf[cpos] == ' ') { + cpos++; + return (SP); + } + cp = &cbuf[cpos]; + if ((cp2 = strpbrk(cp, " \n"))) + cpos = cp2 - cbuf; + c = cbuf[cpos]; + cbuf[cpos] = '\0'; + upper(cp); + p = lookup(sitetab, cp); + cbuf[cpos] = c; + if (p != 0) { + if (p->implemented == 0) { + state = CMD; + nack(p->name); + longjmp(errcatch,0); + /* NOTREACHED */ + } + state = p->state; + yylval.s = p->name; + return (p->token); + } + state = CMD; + break; + + case OSTR: + if (cbuf[cpos] == '\n') { + state = CMD; + return (CRLF); + } + /* FALLTHROUGH */ + + case STR1: + case ZSTR1: + dostr1: + if (cbuf[cpos] == ' ') { + cpos++; + state = state == OSTR ? STR2 : ++state; + return (SP); + } + break; + + case ZSTR2: + if (cbuf[cpos] == '\n') { + state = CMD; + return (CRLF); + } + /* FALLTHROUGH */ + + case STR2: + cp = &cbuf[cpos]; + n = strlen(cp); + cpos += n - 1; + /* + * Make sure the string is nonempty and \n terminated. + */ + if (n > 1 && cbuf[cpos] == '\n') { + cbuf[cpos] = '\0'; + yylval.s = copy(cp); + cbuf[cpos] = '\n'; + state = ARGS; + return (STRING); + } + break; + + case NSTR: + if (cbuf[cpos] == ' ') { + cpos++; + return (SP); + } + if (isdigit(cbuf[cpos])) { + cp = &cbuf[cpos]; + while (isdigit(cbuf[++cpos])) + ; + c = cbuf[cpos]; + cbuf[cpos] = '\0'; + yylval.i = atoi(cp); + cbuf[cpos] = c; + state = STR1; + return (NUMBER); + } + state = STR1; + goto dostr1; + + case ARGS: + if (isdigit(cbuf[cpos])) { + cp = &cbuf[cpos]; + while (isdigit(cbuf[++cpos])) + ; + c = cbuf[cpos]; + cbuf[cpos] = '\0'; + yylval.i = atoi(cp); + cbuf[cpos] = c; + return (NUMBER); + } + switch (cbuf[cpos++]) { + + case '\n': + state = CMD; + return (CRLF); + + case ' ': + return (SP); + + case ',': + return (COMMA); + + case 'A': + case 'a': + return (A); + + case 'B': + case 'b': + return (B); + + case 'C': + case 'c': + return (C); + + case 'E': + case 'e': + return (E); + + case 'F': + case 'f': + return (F); + + case 'I': + case 'i': + return (I); + + case 'L': + case 'l': + return (L); + + case 'N': + case 'n': + return (N); + + case 'P': + case 'p': + return (P); + + case 'R': + case 'r': + return (R); + + case 'S': + case 's': + return (S); + + case 'T': + case 't': + return (T); + + } + break; + + default: + fatal("Unknown state in scanner."); + } + yyerror((char *) 0); + state = CMD; + longjmp(errcatch,0); + } +} + +void +upper(s) + char *s; +{ + while (*s != '\0') { + if (islower(*s)) + *s = toupper(*s); + s++; + } +} + +static char * +copy(s) + char *s; +{ + char *p; + + p = malloc((unsigned) strlen(s) + 1); + if (p == NULL) + fatal("Ran out of memory."); + (void) strcpy(p, s); + return (p); +} + +static void +help(ctab, s) + struct tab *ctab; + char *s; +{ + struct tab *c; + int width, NCMDS; + char *type; + + if (ctab == sitetab) + type = "SITE "; + else + type = ""; + width = 0, NCMDS = 0; + for (c = ctab; c->name != NULL; c++) { + int len = strlen(c->name); + + if (len > width) + width = len; + NCMDS++; + } + width = (width + 8) &~ 7; + if (s == 0) { + int i, j, w; + int columns, lines; + + lreply(214, "The following %scommands are recognized %s.", + type, "(* =>'s unimplemented)"); + columns = 76 / width; + if (columns == 0) + columns = 1; + lines = (NCMDS + columns - 1) / columns; + for (i = 0; i < lines; i++) { + printf(" "); + for (j = 0; j < columns; j++) { + c = ctab + j * lines + i; + printf("%s%c", c->name, + c->implemented ? ' ' : '*'); + if (c + lines >= &ctab[NCMDS]) + break; + w = strlen(c->name) + 1; + while (w < width) { + putchar(' '); + w++; + } + } + printf("\r\n"); + } + (void) fflush(stdout); + reply(214, "Direct comments to ftp-bugs@%s.", hostname); + return; + } + upper(s); + c = lookup(ctab, s); + if (c == (struct tab *)0) { + reply(502, "Unknown command %s.", s); + return; + } + if (c->implemented) + reply(214, "Syntax: %s%s %s", type, c->name, c->help); + else + reply(214, "%s%-*s\t%s; unimplemented.", type, width, + c->name, c->help); +} + +static void +sizecmd(filename) + char *filename; +{ + switch (type) { + case TYPE_L: + case TYPE_I: { + struct stat stbuf; + if (stat(filename, &stbuf) < 0 || !S_ISREG(stbuf.st_mode)) + reply(550, "%s: not a plain file.", filename); + else + reply(213, "%qu", stbuf.st_size); + break; } + case TYPE_A: { + FILE *fin; + int c; + off_t count; + struct stat stbuf; + fin = fopen(filename, "r"); + if (fin == NULL) { + perror_reply(550, filename); + return; + } + if (fstat(fileno(fin), &stbuf) < 0 || !S_ISREG(stbuf.st_mode)) { + reply(550, "%s: not a plain file.", filename); + (void) fclose(fin); + return; + } + + count = 0; + while((c=getc(fin)) != EOF) { + if (c == '\n') /* will get expanded to \r\n */ + count++; + count++; + } + (void) fclose(fin); + + reply(213, "%qd", count); + break; } + default: + reply(504, "SIZE not implemented for Type %c.", "?AEIL"[type]); + } +} diff --git a/libexec/ftpd/ftpd.8 b/libexec/ftpd/ftpd.8 new file mode 100644 index 0000000..a7c5cae --- /dev/null +++ b/libexec/ftpd/ftpd.8 @@ -0,0 +1,290 @@ +.\" Copyright (c) 1985, 1988, 1991, 1993 +.\" The Regents of the University of California. All rights reserved. +.\" +.\" Redistribution and use in source and binary forms, with or without +.\" modification, are permitted provided that the following conditions +.\" are met: +.\" 1. Redistributions of source code must retain the above copyright +.\" notice, this list of conditions and the following disclaimer. +.\" 2. Redistributions in binary form must reproduce the above copyright +.\" notice, this list of conditions and the following disclaimer in the +.\" documentation and/or other materials provided with the distribution. +.\" 3. All advertising materials mentioning features or use of this software +.\" must display the following acknowledgement: +.\" This product includes software developed by the University of +.\" California, Berkeley and its contributors. +.\" 4. Neither the name of the University nor the names of its contributors +.\" may be used to endorse or promote products derived from this software +.\" without specific prior written permission. +.\" +.\" THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND +.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +.\" ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE +.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +.\" SUCH DAMAGE. +.\" +.\" @(#)ftpd.8 8.2 (Berkeley) 4/19/94 +.\" +.Dd April 19, 1994 +.Dt FTPD 8 +.Os BSD 4.2 +.Sh NAME +.Nm ftpd +.Nd +Internet File Transfer Protocol server +.Sh SYNOPSIS +.Nm ftpd +.Op Fl dl +.Op Fl T Ar maxtimeout +.Op Fl t Ar timeout +.Sh DESCRIPTION +.Nm Ftpd +is the +Internet File Transfer Protocol +server process. The server uses the +.Tn TCP +protocol +and listens at the port specified in the +.Dq ftp +service specification; see +.Xr services 5 . +.Pp +Available options: +.Bl -tag -width Ds +.It Fl d +Debugging information is written to the syslog using LOG_FTP. +.It Fl l +Each successful and failed +.Xr ftp 1 +session is logged using syslog with a facility of LOG_FTP. +If this option is specified twice, the retrieve (get), store (put), append, +delete, make directory, remove directory and rename operations and +their filename arguments are also logged. +.It Fl T +A client may also request a different timeout period; +the maximum period allowed may be set to +.Ar timeout +seconds with the +.Fl T +option. +The default limit is 2 hours. +.It Fl t +The inactivity timeout period is set to +.Ar timeout +seconds (the default is 15 minutes). +.El +.Pp +The file +.Pa /etc/nologin +can be used to disable ftp access. +If the file exists, +.Nm +displays it and exits. +If the file +.Pa /etc/ftpwelcome +exists, +.Nm +prints it before issuing the +.Dq ready +message. +If the file +.Pa /etc/motd +exists, +.Nm +prints it after a successful login. +.Pp +The ftp server currently supports the following ftp requests. +The case of the requests is ignored. +.Bl -column "Request" -offset indent +.It Request Ta "Description" +.It ABOR Ta "abort previous command" +.It ACCT Ta "specify account (ignored)" +.It ALLO Ta "allocate storage (vacuously)" +.It APPE Ta "append to a file" +.It CDUP Ta "change to parent of current working directory" +.It CWD Ta "change working directory" +.It DELE Ta "delete a file" +.It HELP Ta "give help information" +.It LIST Ta "give list files in a directory" Pq Dq Li "ls -lgA" +.It MKD Ta "make a directory" +.It MDTM Ta "show last modification time of file" +.It MODE Ta "specify data transfer" Em mode +.It NLST Ta "give name list of files in directory" +.It NOOP Ta "do nothing" +.It PASS Ta "specify password" +.It PASV Ta "prepare for server-to-server transfer" +.It PORT Ta "specify data connection port" +.It PWD Ta "print the current working directory" +.It QUIT Ta "terminate session" +.It REST Ta "restart incomplete transfer" +.It RETR Ta "retrieve a file" +.It RMD Ta "remove a directory" +.It RNFR Ta "specify rename-from file name" +.It RNTO Ta "specify rename-to file name" +.It SITE Ta "non-standard commands (see next section)" +.It SIZE Ta "return size of file" +.It STAT Ta "return status of server" +.It STOR Ta "store a file" +.It STOU Ta "store a file with a unique name" +.It STRU Ta "specify data transfer" Em structure +.It SYST Ta "show operating system type of server system" +.It TYPE Ta "specify data transfer" Em type +.It USER Ta "specify user name" +.It XCUP Ta "change to parent of current working directory (deprecated)" +.It XCWD Ta "change working directory (deprecated)" +.It XMKD Ta "make a directory (deprecated)" +.It XPWD Ta "print the current working directory (deprecated)" +.It XRMD Ta "remove a directory (deprecated)" +.El +.Pp +The following non-standard or +.Tn UNIX +specific commands are supported +by the +SITE request. +.Pp +.Bl -column Request -offset indent +.It Sy Request Ta Sy Description +.It UMASK Ta change umask, e.g. ``SITE UMASK 002'' +.It IDLE Ta set idle-timer, e.g. ``SITE IDLE 60'' +.It CHMOD Ta change mode of a file, e.g. ``SITE CHMOD 755 filename'' +.It HELP Ta give help information. +.El +.Pp +The remaining ftp requests specified in Internet RFC 959 +are +recognized, but not implemented. +MDTM and SIZE are not specified in RFC 959, but will appear in the +next updated FTP RFC. +.Pp +The ftp server will abort an active file transfer only when the +ABOR +command is preceded by a Telnet "Interrupt Process" (IP) +signal and a Telnet "Synch" signal in the command Telnet stream, +as described in Internet RFC 959. +If a +STAT +command is received during a data transfer, preceded by a Telnet IP +and Synch, transfer status will be returned. +.Pp +.Nm Ftpd +interprets file names according to the +.Dq globbing +conventions used by +.Xr csh 1 . +This allows users to utilize the metacharacters +.Dq Li \&*?[]{}~ . +.Pp +.Nm Ftpd +authenticates users according to three rules. +.Pp +.Bl -enum -offset indent +.It +The login name must be in the password data base, +.Pa /etc/passwd , +and not have a null password. +In this case a password must be provided by the client before any +file operations may be performed. +.It +The login name must not appear in the file +.Pa /etc/ftpusers . +.It +The user must have a standard shell returned by +.Xr getusershell 3 . +.It +If the user name is +.Dq anonymous +or +.Dq ftp , +an +anonymous ftp account must be present in the password +file (user +.Dq ftp ) . +In this case the user is allowed +to log in by specifying any password (by convention an email address for +the user should be used as the password). +.El +.Pp +In the last case, +.Nm ftpd +takes special measures to restrict the client's access privileges. +The server performs a +.Xr chroot 2 +to the home directory of the +.Dq ftp +user. +In order that system security is not breached, it is recommended +that the +.Dq ftp +subtree be constructed with care, following these rules: +.Bl -tag -width "~ftp/pub" -offset indent +.It Pa ~ftp +Make the home directory owned by +.Dq root +and unwritable by anyone. +.It Pa ~ftp/bin +Make this directory owned by +.Dq root +and unwritable by anyone (mode 555). +The program +.Xr ls 1 +must be present to support the list command. +This program should be mode 111. +.It Pa ~ftp/etc +Make this directory owned by +.Dq root +and unwritable by anyone (mode 555). +The files +.Xr passwd 5 +and +.Xr group 5 +must be present for the +.Xr ls +command to be able to produce owner names rather than numbers. +The password field in +.Xr passwd +is not used, and should not contain real passwords. +The file +.Pa motd , +if present, will be printed after a successful login. +These files should be mode 444. +.It Pa ~ftp/pub +Make this directory mode 777 and owned by +.Dq ftp . +Guests +can then place files which are to be accessible via the anonymous +account in this directory. +.El +.Sh FILES +.Bl -tag -width /etc/ftpwelcome -compact +.It Pa /etc/ftpusers +List of unwelcome/restricted users. +.It Pa /etc/ftpwelcome +Welcome notice. +.It Pa /etc/motd +Welcome notice after login. +.It Pa /etc/nologin +Displayed and access refused. +.El +.Sh SEE ALSO +.Xr ftp 1 , +.Xr getusershell 3 , +.Xr syslogd 8 +.Sh BUGS +The server must run as the super-user +to create sockets with privileged port numbers. It maintains +an effective user id of the logged in user, reverting to +the super-user only when binding addresses to sockets. The +possible security holes have been extensively +scrutinized, but are possibly incomplete. +.Sh HISTORY +The +.Nm +command appeared in +.Bx 4.2 . diff --git a/libexec/ftpd/ftpd.c b/libexec/ftpd/ftpd.c new file mode 100644 index 0000000..6e23bd7 --- /dev/null +++ b/libexec/ftpd/ftpd.c @@ -0,0 +1,1654 @@ +/* + * Copyright (c) 1985, 1988, 1990, 1992, 1993, 1994 + * The Regents of the University of California. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by the University of + * California, Berkeley and its contributors. + * 4. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef lint +static char copyright[] = +"@(#) Copyright (c) 1985, 1988, 1990, 1992, 1993, 1994\n\ + The Regents of the University of California. All rights reserved.\n"; +#endif /* not lint */ + +#ifndef lint +static char sccsid[] = "@(#)ftpd.c 8.4 (Berkeley) 4/16/94"; +#endif /* not lint */ + +/* + * FTP server. + */ +#include <sys/param.h> +#include <sys/stat.h> +#include <sys/ioctl.h> +#include <sys/socket.h> +#include <sys/wait.h> + +#include <netinet/in.h> +#include <netinet/in_systm.h> +#include <netinet/ip.h> + +#define FTP_NAMES +#include <arpa/ftp.h> +#include <arpa/inet.h> +#include <arpa/telnet.h> + +#include <ctype.h> +#include <dirent.h> +#include <err.h> +#include <errno.h> +#include <fcntl.h> +#include <glob.h> +#include <limits.h> +#include <netdb.h> +#include <pwd.h> +#include <setjmp.h> +#include <signal.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <syslog.h> +#include <time.h> +#include <unistd.h> + +#include "pathnames.h" +#include "extern.h" + +#if __STDC__ +#include <stdarg.h> +#else +#include <varargs.h> +#endif + +static char version[] = "Version 6.00"; + +extern off_t restart_point; +extern char cbuf[]; + +struct sockaddr_in ctrl_addr; +struct sockaddr_in data_source; +struct sockaddr_in data_dest; +struct sockaddr_in his_addr; +struct sockaddr_in pasv_addr; + +int data; +jmp_buf errcatch, urgcatch; +int logged_in; +struct passwd *pw; +int debug; +int timeout = 900; /* timeout after 15 minutes of inactivity */ +int maxtimeout = 7200;/* don't allow idle time to be set beyond 2 hours */ +int logging; +int guest; +int type; +int form; +int stru; /* avoid C keyword */ +int mode; +int usedefault = 1; /* for data transfers */ +int pdata = -1; /* for passive mode */ +sig_atomic_t transflag; +off_t file_size; +off_t byte_count; +#if !defined(CMASK) || CMASK == 0 +#undef CMASK +#define CMASK 027 +#endif +int defumask = CMASK; /* default umask value */ +char tmpline[7]; +char hostname[MAXHOSTNAMELEN]; +char remotehost[MAXHOSTNAMELEN]; + +/* + * Timeout intervals for retrying connections + * to hosts that don't accept PORT cmds. This + * is a kludge, but given the problems with TCP... + */ +#define SWAITMAX 90 /* wait at most 90 seconds */ +#define SWAITINT 5 /* interval between retries */ + +int swaitmax = SWAITMAX; +int swaitint = SWAITINT; + +#ifdef SETPROCTITLE +char **Argv = NULL; /* pointer to argument vector */ +char *LastArgv = NULL; /* end of argv */ +char proctitle[LINE_MAX]; /* initial part of title */ +#endif /* SETPROCTITLE */ + +#define LOGCMD(cmd, file) \ + if (logging > 1) \ + syslog(LOG_INFO,"%s %s%s", cmd, \ + *(file) == '/' ? "" : curdir(), file); +#define LOGCMD2(cmd, file1, file2) \ + if (logging > 1) \ + syslog(LOG_INFO,"%s %s%s %s%s", cmd, \ + *(file1) == '/' ? "" : curdir(), file1, \ + *(file2) == '/' ? "" : curdir(), file2); +#define LOGBYTES(cmd, file, cnt) \ + if (logging > 1) { \ + if (cnt == (off_t)-1) \ + syslog(LOG_INFO,"%s %s%s", cmd, \ + *(file) == '/' ? "" : curdir(), file); \ + else \ + syslog(LOG_INFO, "%s %s%s = %qd bytes", \ + cmd, (*(file) == '/') ? "" : curdir(), file, cnt); \ + } + +static void ack __P((char *)); +static void myoob __P((int)); +static int checkuser __P((char *)); +static FILE *dataconn __P((char *, off_t, char *)); +static void dolog __P((struct sockaddr_in *)); +static char *curdir __P((void)); +static void end_login __P((void)); +static FILE *getdatasock __P((char *)); +static char *gunique __P((char *)); +static void lostconn __P((int)); +static int receive_data __P((FILE *, FILE *)); +static void send_data __P((FILE *, FILE *, off_t)); +static struct passwd * + sgetpwnam __P((char *)); +static char *sgetsave __P((char *)); + +static char * +curdir() +{ + static char path[MAXPATHLEN+1+1]; /* path + '/' + '\0' */ + + if (getcwd(path, sizeof(path)-2) == NULL) + return (""); + if (path[1] != '\0') /* special case for root dir. */ + strcat(path, "/"); + /* For guest account, skip / since it's chrooted */ + return (guest ? path+1 : path); +} + +int +main(argc, argv, envp) + int argc; + char *argv[]; + char **envp; +{ + int addrlen, ch, on = 1, tos; + char *cp, line[LINE_MAX]; + FILE *fd; + + /* + * LOG_NDELAY sets up the logging connection immediately, + * necessary for anonymous ftp's that chroot and can't do it later. + */ + openlog("ftpd", LOG_PID | LOG_NDELAY, LOG_FTP); + addrlen = sizeof(his_addr); + if (getpeername(0, (struct sockaddr *)&his_addr, &addrlen) < 0) { + syslog(LOG_ERR, "getpeername (%s): %m",argv[0]); + exit(1); + } + addrlen = sizeof(ctrl_addr); + if (getsockname(0, (struct sockaddr *)&ctrl_addr, &addrlen) < 0) { + syslog(LOG_ERR, "getsockname (%s): %m",argv[0]); + exit(1); + } +#ifdef IP_TOS + tos = IPTOS_LOWDELAY; + if (setsockopt(0, IPPROTO_IP, IP_TOS, (char *)&tos, sizeof(int)) < 0) + syslog(LOG_WARNING, "setsockopt (IP_TOS): %m"); +#endif + data_source.sin_port = htons(ntohs(ctrl_addr.sin_port) - 1); + debug = 0; +#ifdef SETPROCTITLE + /* + * Save start and extent of argv for setproctitle. + */ + Argv = argv; + while (*envp) + envp++; + LastArgv = envp[-1] + strlen(envp[-1]); +#endif /* SETPROCTITLE */ + + while ((ch = getopt(argc, argv, "dlt:T:u:v")) != EOF) { + switch (ch) { + case 'd': + debug = 1; + break; + + case 'l': + logging++; /* > 1 == extra logging */ + break; + + case 't': + timeout = atoi(optarg); + if (maxtimeout < timeout) + maxtimeout = timeout; + break; + + case 'T': + maxtimeout = atoi(optarg); + if (timeout > maxtimeout) + timeout = maxtimeout; + break; + + case 'u': + { + long val = 0; + + val = strtol(optarg, &optarg, 8); + if (*optarg != '\0' || val < 0) + warnx("bad value for -u"); + else + defumask = val; + break; + } + + case 'v': + debug = 1; + break; + + default: + warnx("unknown flag -%c ignored", optopt); + break; + } + } + (void) freopen(_PATH_DEVNULL, "w", stderr); + (void) signal(SIGPIPE, lostconn); + (void) signal(SIGCHLD, SIG_IGN); + if ((int)signal(SIGURG, myoob) < 0) + syslog(LOG_ERR, "signal: %m"); + + /* Try to handle urgent data inline */ +#ifdef SO_OOBINLINE + if (setsockopt(0, SOL_SOCKET, SO_OOBINLINE, (char *)&on, sizeof(on)) < 0) + syslog(LOG_ERR, "setsockopt: %m"); +#endif + +#ifdef F_SETOWN + if (fcntl(fileno(stdin), F_SETOWN, getpid()) == -1) + syslog(LOG_ERR, "fcntl F_SETOWN: %m"); +#endif + dolog(&his_addr); + /* + * Set up default state + */ + data = -1; + type = TYPE_A; + form = FORM_N; + stru = STRU_F; + mode = MODE_S; + tmpline[0] = '\0'; + + /* If logins are disabled, print out the message. */ + if ((fd = fopen(_PATH_NOLOGIN,"r")) != NULL) { + while (fgets(line, sizeof(line), fd) != NULL) { + if ((cp = strchr(line, '\n')) != NULL) + *cp = '\0'; + lreply(530, "%s", line); + } + (void) fflush(stdout); + (void) fclose(fd); + reply(530, "System not available."); + exit(0); + } + if ((fd = fopen(_PATH_FTPWELCOME, "r")) != NULL) { + while (fgets(line, sizeof(line), fd) != NULL) { + if ((cp = strchr(line, '\n')) != NULL) + *cp = '\0'; + lreply(220, "%s", line); + } + (void) fflush(stdout); + (void) fclose(fd); + /* reply(220,) must follow */ + } + (void) gethostname(hostname, sizeof(hostname)); + reply(220, "%s FTP server (%s) ready.", hostname, version); + (void) setjmp(errcatch); + for (;;) + (void) yyparse(); + /* NOTREACHED */ +} + +static void +lostconn(signo) + int signo; +{ + + if (debug) + syslog(LOG_DEBUG, "lost connection"); + dologout(-1); +} + +static char ttyline[20]; + +/* + * Helper function for sgetpwnam(). + */ +static char * +sgetsave(s) + char *s; +{ + char *new = malloc((unsigned) strlen(s) + 1); + + if (new == NULL) { + perror_reply(421, "Local resource failure: malloc"); + dologout(1); + /* NOTREACHED */ + } + (void) strcpy(new, s); + return (new); +} + +/* + * Save the result of a getpwnam. Used for USER command, since + * the data returned must not be clobbered by any other command + * (e.g., globbing). + */ +static struct passwd * +sgetpwnam(name) + char *name; +{ + static struct passwd save; + struct passwd *p; + + if ((p = getpwnam(name)) == NULL) + return (p); + if (save.pw_name) { + free(save.pw_name); + free(save.pw_passwd); + free(save.pw_gecos); + free(save.pw_dir); + free(save.pw_shell); + } + save = *p; + save.pw_name = sgetsave(p->pw_name); + save.pw_passwd = sgetsave(p->pw_passwd); + save.pw_gecos = sgetsave(p->pw_gecos); + save.pw_dir = sgetsave(p->pw_dir); + save.pw_shell = sgetsave(p->pw_shell); + return (&save); +} + +static int login_attempts; /* number of failed login attempts */ +static int askpasswd; /* had user command, ask for passwd */ +static char curname[10]; /* current USER name */ + +/* + * USER command. + * Sets global passwd pointer pw if named account exists and is acceptable; + * sets askpasswd if a PASS command is expected. If logged in previously, + * need to reset state. If name is "ftp" or "anonymous", the name is not in + * _PATH_FTPUSERS, and ftp account exists, set guest and pw, then just return. + * If account doesn't exist, ask for passwd anyway. Otherwise, check user + * requesting login privileges. Disallow anyone who does not have a standard + * shell as returned by getusershell(). Disallow anyone mentioned in the file + * _PATH_FTPUSERS to allow people such as root and uucp to be avoided. + */ +void +user(name) + char *name; +{ + char *cp, *shell; + + if (logged_in) { + if (guest) { + reply(530, "Can't change user from guest login."); + return; + } + end_login(); + } + + guest = 0; + if (strcmp(name, "ftp") == 0 || strcmp(name, "anonymous") == 0) { + if (checkuser("ftp") || checkuser("anonymous")) + reply(530, "User %s access denied.", name); + else if ((pw = sgetpwnam("ftp")) != NULL) { + guest = 1; + askpasswd = 1; + reply(331, + "Guest login ok, type your name as password."); + } else + reply(530, "User %s unknown.", name); + if (!askpasswd && logging) + syslog(LOG_NOTICE, + "ANONYMOUS FTP LOGIN REFUSED FROM %s", remotehost); + return; + } + if (pw = sgetpwnam(name)) { + if ((shell = pw->pw_shell) == NULL || *shell == 0) + shell = _PATH_BSHELL; + while ((cp = getusershell()) != NULL) + if (strcmp(cp, shell) == 0) + break; + endusershell(); + + if (cp == NULL || checkuser(name)) { + reply(530, "User %s access denied.", name); + if (logging) + syslog(LOG_NOTICE, + "FTP LOGIN REFUSED FROM %s, %s", + remotehost, name); + pw = (struct passwd *) NULL; + return; + } + } + if (logging) + strncpy(curname, name, sizeof(curname)-1); + reply(331, "Password required for %s.", name); + askpasswd = 1; + /* + * Delay before reading passwd after first failed + * attempt to slow down passwd-guessing programs. + */ + if (login_attempts) + sleep((unsigned) login_attempts); +} + +/* + * Check if a user is in the file _PATH_FTPUSERS + */ +static int +checkuser(name) + char *name; +{ + FILE *fd; + int found = 0; + char *p, line[BUFSIZ]; + + if ((fd = fopen(_PATH_FTPUSERS, "r")) != NULL) { + while (fgets(line, sizeof(line), fd) != NULL) + if ((p = strchr(line, '\n')) != NULL) { + *p = '\0'; + if (line[0] == '#') + continue; + if (strcmp(p, name) == 0) { + found = 1; + break; + } + } + (void) fclose(fd); + } + return (found); +} + +/* + * Terminate login as previous user, if any, resetting state; + * used when USER command is given or login fails. + */ +static void +end_login() +{ + + (void) seteuid((uid_t)0); + if (logged_in) + logwtmp(ttyline, "", ""); + pw = NULL; + logged_in = 0; + guest = 0; +} + +void +pass(passwd) + char *passwd; +{ + char *salt, *xpasswd; + FILE *fd; + + if (logged_in || askpasswd == 0) { + reply(503, "Login with USER first."); + return; + } + askpasswd = 0; + if (!guest) { /* "ftp" is only account allowed no password */ + if (pw == NULL) + salt = "xx"; + else + salt = pw->pw_passwd; + xpasswd = crypt(passwd, salt); + /* The strcmp does not catch null passwords! */ + if (pw == NULL || *pw->pw_passwd == '\0' || + strcmp(xpasswd, pw->pw_passwd)) { + reply(530, "Login incorrect."); + if (logging) + syslog(LOG_NOTICE, + "FTP LOGIN FAILED FROM %s, %s", + remotehost, curname); + pw = NULL; + if (login_attempts++ >= 5) { + syslog(LOG_NOTICE, + "repeated login failures from %s", + remotehost); + exit(0); + } + return; + } + } + login_attempts = 0; /* this time successful */ + if (setegid((gid_t)pw->pw_gid) < 0) { + reply(550, "Can't set gid."); + return; + } + (void) initgroups(pw->pw_name, pw->pw_gid); + + /* open wtmp before chroot */ + (void)sprintf(ttyline, "ftp%d", getpid()); + logwtmp(ttyline, pw->pw_name, remotehost); + logged_in = 1; + + if (guest) { + /* + * We MUST do a chdir() after the chroot. Otherwise + * the old current directory will be accessible as "." + * outside the new root! + */ + if (chroot(pw->pw_dir) < 0 || chdir("/") < 0) { + reply(550, "Can't set guest privileges."); + goto bad; + } + } else if (chdir(pw->pw_dir) < 0) { + if (chdir("/") < 0) { + reply(530, "User %s: can't change directory to %s.", + pw->pw_name, pw->pw_dir); + goto bad; + } else + lreply(230, "No directory! Logging in with home=/"); + } + if (seteuid((uid_t)pw->pw_uid) < 0) { + reply(550, "Can't set uid."); + goto bad; + } + /* + * Display a login message, if it exists. + * N.B. reply(230,) must follow the message. + */ + if ((fd = fopen(_PATH_FTPLOGINMESG, "r")) != NULL) { + char *cp, line[LINE_MAX]; + + while (fgets(line, sizeof(line), fd) != NULL) { + if ((cp = strchr(line, '\n')) != NULL) + *cp = '\0'; + lreply(230, "%s", line); + } + (void) fflush(stdout); + (void) fclose(fd); + } + if (guest) { + reply(230, "Guest login ok, access restrictions apply."); +#ifdef SETPROCTITLE + snprintf(proctitle, sizeof(proctitle), + "%s: anonymous/%.*s", remotehost, + sizeof(proctitle) - sizeof(remotehost) - + sizeof(": anonymous/"), passwd); + setproctitle(proctitle); +#endif /* SETPROCTITLE */ + if (logging) + syslog(LOG_INFO, "ANONYMOUS FTP LOGIN FROM %s, %s", + remotehost, passwd); + } else { + reply(230, "User %s logged in.", pw->pw_name); +#ifdef SETPROCTITLE + snprintf(proctitle, sizeof(proctitle), + "%s: %s", remotehost, pw->pw_name); + setproctitle(proctitle); +#endif /* SETPROCTITLE */ + if (logging) + syslog(LOG_INFO, "FTP LOGIN FROM %s as %s", + remotehost, pw->pw_name); + } + (void) umask(defumask); + return; +bad: + /* Forget all about it... */ + end_login(); +} + +void +retrieve(cmd, name) + char *cmd, *name; +{ + FILE *fin, *dout; + struct stat st; + int (*closefunc) __P((FILE *)); + + if (cmd == 0) { + fin = fopen(name, "r"), closefunc = fclose; + st.st_size = 0; + } else { + char line[BUFSIZ]; + + (void) sprintf(line, cmd, name), name = line; + fin = ftpd_popen(line, "r"), closefunc = ftpd_pclose; + st.st_size = -1; + st.st_blksize = BUFSIZ; + } + if (fin == NULL) { + if (errno != 0) { + perror_reply(550, name); + if (cmd == 0) { + LOGCMD("get", name); + } + } + return; + } + byte_count = -1; + if (cmd == 0 && (fstat(fileno(fin), &st) < 0 || !S_ISREG(st.st_mode))) { + reply(550, "%s: not a plain file.", name); + goto done; + } + if (restart_point) { + if (type == TYPE_A) { + off_t i, n; + int c; + + n = restart_point; + i = 0; + while (i++ < n) { + if ((c=getc(fin)) == EOF) { + perror_reply(550, name); + goto done; + } + if (c == '\n') + i++; + } + } else if (lseek(fileno(fin), restart_point, L_SET) < 0) { + perror_reply(550, name); + goto done; + } + } + dout = dataconn(name, st.st_size, "w"); + if (dout == NULL) + goto done; + send_data(fin, dout, st.st_blksize); + (void) fclose(dout); + data = -1; + pdata = -1; +done: + if (cmd == 0) + LOGBYTES("get", name, byte_count); + (*closefunc)(fin); +} + +void +store(name, mode, unique) + char *name, *mode; + int unique; +{ + FILE *fout, *din; + struct stat st; + int (*closefunc) __P((FILE *)); + + if (unique && stat(name, &st) == 0 && + (name = gunique(name)) == NULL) { + LOGCMD(*mode == 'w' ? "put" : "append", name); + return; + } + + if (restart_point) + mode = "r+"; + fout = fopen(name, mode); + closefunc = fclose; + if (fout == NULL) { + perror_reply(553, name); + LOGCMD(*mode == 'w' ? "put" : "append", name); + return; + } + byte_count = -1; + if (restart_point) { + if (type == TYPE_A) { + off_t i, n; + int c; + + n = restart_point; + i = 0; + while (i++ < n) { + if ((c=getc(fout)) == EOF) { + perror_reply(550, name); + goto done; + } + if (c == '\n') + i++; + } + /* + * We must do this seek to "current" position + * because we are changing from reading to + * writing. + */ + if (fseek(fout, 0L, L_INCR) < 0) { + perror_reply(550, name); + goto done; + } + } else if (lseek(fileno(fout), restart_point, L_SET) < 0) { + perror_reply(550, name); + goto done; + } + } + din = dataconn(name, (off_t)-1, "r"); + if (din == NULL) + goto done; + if (receive_data(din, fout) == 0) { + if (unique) + reply(226, "Transfer complete (unique file name:%s).", + name); + else + reply(226, "Transfer complete."); + } + (void) fclose(din); + data = -1; + pdata = -1; +done: + LOGBYTES(*mode == 'w' ? "put" : "append", name, byte_count); + (*closefunc)(fout); +} + +static FILE * +getdatasock(mode) + char *mode; +{ + int on = 1, s, t, tries; + + if (data >= 0) + return (fdopen(data, mode)); + (void) seteuid((uid_t)0); + s = socket(AF_INET, SOCK_STREAM, 0); + if (s < 0) + goto bad; + if (setsockopt(s, SOL_SOCKET, SO_REUSEADDR, + (char *) &on, sizeof(on)) < 0) + goto bad; + /* anchor socket to avoid multi-homing problems */ + data_source.sin_family = AF_INET; + data_source.sin_addr = ctrl_addr.sin_addr; + for (tries = 1; ; tries++) { + if (bind(s, (struct sockaddr *)&data_source, + sizeof(data_source)) >= 0) + break; + if (errno != EADDRINUSE || tries > 10) + goto bad; + sleep(tries); + } + (void) seteuid((uid_t)pw->pw_uid); +#ifdef IP_TOS + on = IPTOS_THROUGHPUT; + if (setsockopt(s, IPPROTO_IP, IP_TOS, (char *)&on, sizeof(int)) < 0) + syslog(LOG_WARNING, "setsockopt (IP_TOS): %m"); +#endif + return (fdopen(s, mode)); +bad: + /* Return the real value of errno (close may change it) */ + t = errno; + (void) seteuid((uid_t)pw->pw_uid); + (void) close(s); + errno = t; + return (NULL); +} + +static FILE * +dataconn(name, size, mode) + char *name; + off_t size; + char *mode; +{ + char sizebuf[32]; + FILE *file; + int retry = 0, tos; + + file_size = size; + byte_count = 0; + if (size != (off_t) -1) + (void) sprintf(sizebuf, " (%qd bytes)", size); + else + (void) strcpy(sizebuf, ""); + if (pdata >= 0) { + struct sockaddr_in from; + int s, fromlen = sizeof(from); + + s = accept(pdata, (struct sockaddr *)&from, &fromlen); + if (s < 0) { + reply(425, "Can't open data connection."); + (void) close(pdata); + pdata = -1; + return (NULL); + } + (void) close(pdata); + pdata = s; +#ifdef IP_TOS + tos = IPTOS_LOWDELAY; + (void) setsockopt(s, IPPROTO_IP, IP_TOS, (char *)&tos, + sizeof(int)); +#endif + reply(150, "Opening %s mode data connection for '%s'%s.", + type == TYPE_A ? "ASCII" : "BINARY", name, sizebuf); + return (fdopen(pdata, mode)); + } + if (data >= 0) { + reply(125, "Using existing data connection for '%s'%s.", + name, sizebuf); + usedefault = 1; + return (fdopen(data, mode)); + } + if (usedefault) + data_dest = his_addr; + usedefault = 1; + file = getdatasock(mode); + if (file == NULL) { + reply(425, "Can't create data socket (%s,%d): %s.", + inet_ntoa(data_source.sin_addr), + ntohs(data_source.sin_port), strerror(errno)); + return (NULL); + } + data = fileno(file); + while (connect(data, (struct sockaddr *)&data_dest, + sizeof(data_dest)) < 0) { + if (errno == EADDRINUSE && retry < swaitmax) { + sleep((unsigned) swaitint); + retry += swaitint; + continue; + } + perror_reply(425, "Can't build data connection"); + (void) fclose(file); + data = -1; + return (NULL); + } + reply(150, "Opening %s mode data connection for '%s'%s.", + type == TYPE_A ? "ASCII" : "BINARY", name, sizebuf); + return (file); +} + +/* + * Tranfer the contents of "instr" to "outstr" peer using the appropriate + * encapsulation of the data subject * to Mode, Structure, and Type. + * + * NB: Form isn't handled. + */ +static void +send_data(instr, outstr, blksize) + FILE *instr, *outstr; + off_t blksize; +{ + int c, cnt, filefd, netfd; + char *buf; + + transflag++; + if (setjmp(urgcatch)) { + transflag = 0; + return; + } + switch (type) { + + case TYPE_A: + while ((c = getc(instr)) != EOF) { + byte_count++; + if (c == '\n') { + if (ferror(outstr)) + goto data_err; + (void) putc('\r', outstr); + } + (void) putc(c, outstr); + } + fflush(outstr); + transflag = 0; + if (ferror(instr)) + goto file_err; + if (ferror(outstr)) + goto data_err; + reply(226, "Transfer complete."); + return; + + case TYPE_I: + case TYPE_L: + if ((buf = malloc((u_int)blksize)) == NULL) { + transflag = 0; + perror_reply(451, "Local resource failure: malloc"); + return; + } + netfd = fileno(outstr); + filefd = fileno(instr); + while ((cnt = read(filefd, buf, (u_int)blksize)) > 0 && + write(netfd, buf, cnt) == cnt) + byte_count += cnt; + transflag = 0; + (void)free(buf); + if (cnt != 0) { + if (cnt < 0) + goto file_err; + goto data_err; + } + reply(226, "Transfer complete."); + return; + default: + transflag = 0; + reply(550, "Unimplemented TYPE %d in send_data", type); + return; + } + +data_err: + transflag = 0; + perror_reply(426, "Data connection"); + return; + +file_err: + transflag = 0; + perror_reply(551, "Error on input file"); +} + +/* + * Transfer data from peer to "outstr" using the appropriate encapulation of + * the data subject to Mode, Structure, and Type. + * + * N.B.: Form isn't handled. + */ +static int +receive_data(instr, outstr) + FILE *instr, *outstr; +{ + int c; + int cnt, bare_lfs = 0; + char buf[BUFSIZ]; + + transflag++; + if (setjmp(urgcatch)) { + transflag = 0; + return (-1); + } + switch (type) { + + case TYPE_I: + case TYPE_L: + while ((cnt = read(fileno(instr), buf, sizeof(buf))) > 0) { + if (write(fileno(outstr), buf, cnt) != cnt) + goto file_err; + byte_count += cnt; + } + if (cnt < 0) + goto data_err; + transflag = 0; + return (0); + + case TYPE_E: + reply(553, "TYPE E not implemented."); + transflag = 0; + return (-1); + + case TYPE_A: + while ((c = getc(instr)) != EOF) { + byte_count++; + if (c == '\n') + bare_lfs++; + while (c == '\r') { + if (ferror(outstr)) + goto data_err; + if ((c = getc(instr)) != '\n') { + (void) putc ('\r', outstr); + if (c == '\0' || c == EOF) + goto contin2; + } + } + (void) putc(c, outstr); + contin2: ; + } + fflush(outstr); + if (ferror(instr)) + goto data_err; + if (ferror(outstr)) + goto file_err; + transflag = 0; + if (bare_lfs) { + lreply(226, + "WARNING! %d bare linefeeds received in ASCII mode", + bare_lfs); + (void)printf(" File may not have transferred correctly.\r\n"); + } + return (0); + default: + reply(550, "Unimplemented TYPE %d in receive_data", type); + transflag = 0; + return (-1); + } + +data_err: + transflag = 0; + perror_reply(426, "Data Connection"); + return (-1); + +file_err: + transflag = 0; + perror_reply(452, "Error writing file"); + return (-1); +} + +void +statfilecmd(filename) + char *filename; +{ + FILE *fin; + int c; + char line[LINE_MAX]; + + (void)snprintf(line, sizeof(line), "/bin/ls -lgA %s", filename); + fin = ftpd_popen(line, "r"); + lreply(211, "status of %s:", filename); + while ((c = getc(fin)) != EOF) { + if (c == '\n') { + if (ferror(stdout)){ + perror_reply(421, "control connection"); + (void) ftpd_pclose(fin); + dologout(1); + /* NOTREACHED */ + } + if (ferror(fin)) { + perror_reply(551, filename); + (void) ftpd_pclose(fin); + return; + } + (void) putc('\r', stdout); + } + (void) putc(c, stdout); + } + (void) ftpd_pclose(fin); + reply(211, "End of Status"); +} + +void +statcmd() +{ + struct sockaddr_in *sin; + u_char *a, *p; + + lreply(211, "%s FTP server status:", hostname, version); + printf(" %s\r\n", version); + printf(" Connected to %s", remotehost); + if (!isdigit(remotehost[0])) + printf(" (%s)", inet_ntoa(his_addr.sin_addr)); + printf("\r\n"); + if (logged_in) { + if (guest) + printf(" Logged in anonymously\r\n"); + else + printf(" Logged in as %s\r\n", pw->pw_name); + } else if (askpasswd) + printf(" Waiting for password\r\n"); + else + printf(" Waiting for user name\r\n"); + printf(" TYPE: %s", typenames[type]); + if (type == TYPE_A || type == TYPE_E) + printf(", FORM: %s", formnames[form]); + if (type == TYPE_L) +#if NBBY == 8 + printf(" %d", NBBY); +#else + printf(" %d", bytesize); /* need definition! */ +#endif + printf("; STRUcture: %s; transfer MODE: %s\r\n", + strunames[stru], modenames[mode]); + if (data != -1) + printf(" Data connection open\r\n"); + else if (pdata != -1) { + printf(" in Passive mode"); + sin = &pasv_addr; + goto printaddr; + } else if (usedefault == 0) { + printf(" PORT"); + sin = &data_dest; +printaddr: + a = (u_char *) &sin->sin_addr; + p = (u_char *) &sin->sin_port; +#define UC(b) (((int) b) & 0xff) + printf(" (%d,%d,%d,%d,%d,%d)\r\n", UC(a[0]), + UC(a[1]), UC(a[2]), UC(a[3]), UC(p[0]), UC(p[1])); +#undef UC + } else + printf(" No data connection\r\n"); + reply(211, "End of status"); +} + +void +fatal(s) + char *s; +{ + + reply(451, "Error in server: %s\n", s); + reply(221, "Closing connection due to server error."); + dologout(0); + /* NOTREACHED */ +} + +void +#if __STDC__ +reply(int n, const char *fmt, ...) +#else +reply(n, fmt, va_alist) + int n; + char *fmt; + va_dcl +#endif +{ + va_list ap; +#if __STDC__ + va_start(ap, fmt); +#else + va_start(ap); +#endif + (void)printf("%d ", n); + (void)vprintf(fmt, ap); + (void)printf("\r\n"); + (void)fflush(stdout); + if (debug) { + syslog(LOG_DEBUG, "<--- %d ", n); + vsyslog(LOG_DEBUG, fmt, ap); + } +} + +void +#if __STDC__ +lreply(int n, const char *fmt, ...) +#else +lreply(n, fmt, va_alist) + int n; + char *fmt; + va_dcl +#endif +{ + va_list ap; +#if __STDC__ + va_start(ap, fmt); +#else + va_start(ap); +#endif + (void)printf("%d- ", n); + (void)vprintf(fmt, ap); + (void)printf("\r\n"); + (void)fflush(stdout); + if (debug) { + syslog(LOG_DEBUG, "<--- %d- ", n); + vsyslog(LOG_DEBUG, fmt, ap); + } +} + +static void +ack(s) + char *s; +{ + + reply(250, "%s command successful.", s); +} + +void +nack(s) + char *s; +{ + + reply(502, "%s command not implemented.", s); +} + +/* ARGSUSED */ +void +yyerror(s) + char *s; +{ + char *cp; + + if (cp = strchr(cbuf,'\n')) + *cp = '\0'; + reply(500, "'%s': command not understood.", cbuf); +} + +void +delete(name) + char *name; +{ + struct stat st; + + LOGCMD("delete", name); + if (stat(name, &st) < 0) { + perror_reply(550, name); + return; + } + if ((st.st_mode&S_IFMT) == S_IFDIR) { + if (rmdir(name) < 0) { + perror_reply(550, name); + return; + } + goto done; + } + if (unlink(name) < 0) { + perror_reply(550, name); + return; + } +done: + ack("DELE"); +} + +void +cwd(path) + char *path; +{ + + if (chdir(path) < 0) + perror_reply(550, path); + else + ack("CWD"); +} + +void +makedir(name) + char *name; +{ + + LOGCMD("mkdir", name); + if (mkdir(name, 0777) < 0) + perror_reply(550, name); + else + reply(257, "MKD command successful."); +} + +void +removedir(name) + char *name; +{ + + LOGCMD("rmdir", name); + if (rmdir(name) < 0) + perror_reply(550, name); + else + ack("RMD"); +} + +void +pwd() +{ + char path[MAXPATHLEN + 1]; + + if (getwd(path) == (char *)NULL) + reply(550, "%s.", path); + else + reply(257, "\"%s\" is current directory.", path); +} + +char * +renamefrom(name) + char *name; +{ + struct stat st; + + if (stat(name, &st) < 0) { + perror_reply(550, name); + return ((char *)0); + } + reply(350, "File exists, ready for destination name"); + return (name); +} + +void +renamecmd(from, to) + char *from, *to; +{ + + LOGCMD2("rename", from, to); + if (rename(from, to) < 0) + perror_reply(550, "rename"); + else + ack("RNTO"); +} + +static void +dolog(sin) + struct sockaddr_in *sin; +{ + struct hostent *hp = gethostbyaddr((char *)&sin->sin_addr, + sizeof(struct in_addr), AF_INET); + + if (hp) + (void) strncpy(remotehost, hp->h_name, sizeof(remotehost)); + else + (void) strncpy(remotehost, inet_ntoa(sin->sin_addr), + sizeof(remotehost)); +#ifdef SETPROCTITLE + snprintf(proctitle, sizeof(proctitle), "%s: connected", remotehost); + setproctitle(proctitle); +#endif /* SETPROCTITLE */ + + if (logging) + syslog(LOG_INFO, "connection from %s", remotehost); +} + +/* + * Record logout in wtmp file + * and exit with supplied status. + */ +void +dologout(status) + int status; +{ + + if (logged_in) { + (void) seteuid((uid_t)0); + logwtmp(ttyline, "", ""); + } + /* beware of flushing buffers after a SIGPIPE */ + _exit(status); +} + +static void +myoob(signo) + int signo; +{ + char *cp; + + /* only process if transfer occurring */ + if (!transflag) + return; + cp = tmpline; + if (getline(cp, 7, stdin) == NULL) { + reply(221, "You could at least say goodbye."); + dologout(0); + } + upper(cp); + if (strcmp(cp, "ABOR\r\n") == 0) { + tmpline[0] = '\0'; + reply(426, "Transfer aborted. Data connection closed."); + reply(226, "Abort successful"); + longjmp(urgcatch, 1); + } + if (strcmp(cp, "STAT\r\n") == 0) { + if (file_size != (off_t) -1) + reply(213, "Status: %qd of %qd bytes transferred", + byte_count, file_size); + else + reply(213, "Status: %qd bytes transferred", byte_count); + } +} + +/* + * Note: a response of 425 is not mentioned as a possible response to + * the PASV command in RFC959. However, it has been blessed as + * a legitimate response by Jon Postel in a telephone conversation + * with Rick Adams on 25 Jan 89. + */ +void +passive() +{ + int len; + char *p, *a; + + pdata = socket(AF_INET, SOCK_STREAM, 0); + if (pdata < 0) { + perror_reply(425, "Can't open passive connection"); + return; + } + pasv_addr = ctrl_addr; + pasv_addr.sin_port = 0; + (void) seteuid((uid_t)0); + if (bind(pdata, (struct sockaddr *)&pasv_addr, sizeof(pasv_addr)) < 0) { + (void) seteuid((uid_t)pw->pw_uid); + goto pasv_error; + } + (void) seteuid((uid_t)pw->pw_uid); + len = sizeof(pasv_addr); + if (getsockname(pdata, (struct sockaddr *) &pasv_addr, &len) < 0) + goto pasv_error; + if (listen(pdata, 1) < 0) + goto pasv_error; + a = (char *) &pasv_addr.sin_addr; + p = (char *) &pasv_addr.sin_port; + +#define UC(b) (((int) b) & 0xff) + + reply(227, "Entering Passive Mode (%d,%d,%d,%d,%d,%d)", UC(a[0]), + UC(a[1]), UC(a[2]), UC(a[3]), UC(p[0]), UC(p[1])); + return; + +pasv_error: + (void) close(pdata); + pdata = -1; + perror_reply(425, "Can't open passive connection"); + return; +} + +/* + * Generate unique name for file with basename "local". + * The file named "local" is already known to exist. + * Generates failure reply on error. + */ +static char * +gunique(local) + char *local; +{ + static char new[MAXPATHLEN]; + struct stat st; + int count; + char *cp; + + cp = strrchr(local, '/'); + if (cp) + *cp = '\0'; + if (stat(cp ? local : ".", &st) < 0) { + perror_reply(553, cp ? local : "."); + return ((char *) 0); + } + if (cp) + *cp = '/'; + (void) strcpy(new, local); + cp = new + strlen(new); + *cp++ = '.'; + for (count = 1; count < 100; count++) { + (void)sprintf(cp, "%d", count); + if (stat(new, &st) < 0) + return (new); + } + reply(452, "Unique file name cannot be created."); + return (NULL); +} + +/* + * Format and send reply containing system error number. + */ +void +perror_reply(code, string) + int code; + char *string; +{ + + reply(code, "%s: %s.", string, strerror(errno)); +} + +static char *onefile[] = { + "", + 0 +}; + +void +send_file_list(whichf) + char *whichf; +{ + struct stat st; + DIR *dirp = NULL; + struct dirent *dir; + FILE *dout = NULL; + char **dirlist, *dirname; + int simple = 0; + int freeglob = 0; + glob_t gl; + + if (strpbrk(whichf, "~{[*?") != NULL) { + int flags = GLOB_BRACE|GLOB_NOCHECK|GLOB_QUOTE|GLOB_TILDE; + + memset(&gl, 0, sizeof(gl)); + freeglob = 1; + if (glob(whichf, flags, 0, &gl)) { + reply(550, "not found"); + goto out; + } else if (gl.gl_pathc == 0) { + errno = ENOENT; + perror_reply(550, whichf); + goto out; + } + dirlist = gl.gl_pathv; + } else { + onefile[0] = whichf; + dirlist = onefile; + simple = 1; + } + + if (setjmp(urgcatch)) { + transflag = 0; + goto out; + } + while (dirname = *dirlist++) { + if (stat(dirname, &st) < 0) { + /* + * If user typed "ls -l", etc, and the client + * used NLST, do what the user meant. + */ + if (dirname[0] == '-' && *dirlist == NULL && + transflag == 0) { + retrieve("/bin/ls %s", dirname); + goto out; + } + perror_reply(550, whichf); + if (dout != NULL) { + (void) fclose(dout); + transflag = 0; + data = -1; + pdata = -1; + } + goto out; + } + + if (S_ISREG(st.st_mode)) { + if (dout == NULL) { + dout = dataconn("file list", (off_t)-1, "w"); + if (dout == NULL) + goto out; + transflag++; + } + fprintf(dout, "%s%s\n", dirname, + type == TYPE_A ? "\r" : ""); + byte_count += strlen(dirname) + 1; + continue; + } else if (!S_ISDIR(st.st_mode)) + continue; + + if ((dirp = opendir(dirname)) == NULL) + continue; + + while ((dir = readdir(dirp)) != NULL) { + char nbuf[MAXPATHLEN]; + + if (dir->d_name[0] == '.' && dir->d_namlen == 1) + continue; + if (dir->d_name[0] == '.' && dir->d_name[1] == '.' && + dir->d_namlen == 2) + continue; + + sprintf(nbuf, "%s/%s", dirname, dir->d_name); + + /* + * We have to do a stat to insure it's + * not a directory or special file. + */ + if (simple || (stat(nbuf, &st) == 0 && + S_ISREG(st.st_mode))) { + if (dout == NULL) { + dout = dataconn("file list", (off_t)-1, + "w"); + if (dout == NULL) + goto out; + transflag++; + } + if (nbuf[0] == '.' && nbuf[1] == '/') + fprintf(dout, "%s%s\n", &nbuf[2], + type == TYPE_A ? "\r" : ""); + else + fprintf(dout, "%s%s\n", nbuf, + type == TYPE_A ? "\r" : ""); + byte_count += strlen(nbuf) + 1; + } + } + (void) closedir(dirp); + } + + if (dout == NULL) + reply(550, "No files found."); + else if (ferror(dout) != 0) + perror_reply(550, "Data connection"); + else + reply(226, "Transfer complete."); + + transflag = 0; + if (dout != NULL) + (void) fclose(dout); + data = -1; + pdata = -1; +out: + if (freeglob) { + freeglob = 0; + globfree(&gl); + } +} + +#ifdef SETPROCTITLE +/* + * Clobber argv so ps will show what we're doing. (Stolen from sendmail.) + * Warning, since this is usually started from inetd.conf, it often doesn't + * have much of an environment or arglist to overwrite. + */ +void +#if __STDC__ +setproctitle(const char *fmt, ...) +#else +setproctitle(fmt, va_alist) + char *fmt; + va_dcl +#endif +{ + int i; + va_list ap; + char *p, *bp, ch; + char buf[LINE_MAX]; + +#if __STDC__ + va_start(ap, fmt); +#else + va_start(ap); +#endif + (void)vsnprintf(buf, sizeof(buf), fmt, ap); + + /* make ps print our process name */ + p = Argv[0]; + *p++ = '-'; + + i = strlen(buf); + if (i > LastArgv - p - 2) { + i = LastArgv - p - 2; + buf[i] = '\0'; + } + bp = buf; + while (ch = *bp++) + if (ch != '\n' && ch != '\r') + *p++ = ch; + while (p < LastArgv) + *p++ = ' '; +} +#endif /* SETPROCTITLE */ diff --git a/libexec/ftpd/logwtmp.c b/libexec/ftpd/logwtmp.c new file mode 100644 index 0000000..d40840c --- /dev/null +++ b/libexec/ftpd/logwtmp.c @@ -0,0 +1,75 @@ +/* + * Copyright (c) 1988, 1993 + * The Regents of the University of California. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by the University of + * California, Berkeley and its contributors. + * 4. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + */ + +#ifndef lint +static char sccsid[] = "@(#)logwtmp.c 8.1 (Berkeley) 6/4/93"; +#endif /* not lint */ + +#include <sys/types.h> +#include <sys/time.h> +#include <sys/stat.h> + +#include <fcntl.h> +#include <utmp.h> +#include <unistd.h> +#include <stdio.h> +#include <string.h> +#include "extern.h" + +static int fd = -1; + +/* + * Modified version of logwtmp that holds wtmp file open + * after first call, for use with ftp (which may chroot + * after login, but before logout). + */ +void +logwtmp(line, name, host) + char *line, *name, *host; +{ + struct utmp ut; + struct stat buf; + + if (fd < 0 && (fd = open(_PATH_WTMP, O_WRONLY|O_APPEND, 0)) < 0) + return; + if (fstat(fd, &buf) == 0) { + (void)strncpy(ut.ut_line, line, sizeof(ut.ut_line)); + (void)strncpy(ut.ut_name, name, sizeof(ut.ut_name)); + (void)strncpy(ut.ut_host, host, sizeof(ut.ut_host)); + (void)time(&ut.ut_time); + if (write(fd, (char *)&ut, sizeof(struct utmp)) != + sizeof(struct utmp)) + (void)ftruncate(fd, buf.st_size); + } +} diff --git a/libexec/ftpd/pathnames.h b/libexec/ftpd/pathnames.h new file mode 100644 index 0000000..2a50063 --- /dev/null +++ b/libexec/ftpd/pathnames.h @@ -0,0 +1,40 @@ +/* + * Copyright (c) 1989, 1993 + * The Regents of the University of California. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by the University of + * California, Berkeley and its contributors. + * 4. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * @(#)pathnames.h 8.1 (Berkeley) 6/4/93 + */ + +#include <paths.h> + +#define _PATH_FTPUSERS "/etc/ftpusers" +#define _PATH_FTPWELCOME "/etc/ftpwelcome" +#define _PATH_FTPLOGINMESG "/etc/motd" diff --git a/libexec/ftpd/popen.c b/libexec/ftpd/popen.c new file mode 100644 index 0000000..b26732e --- /dev/null +++ b/libexec/ftpd/popen.c @@ -0,0 +1,171 @@ +/* + * Copyright (c) 1988, 1993, 1994 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software written by Ken Arnold and + * published in UNIX Review, Vol. 6, No. 8. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by the University of + * California, Berkeley and its contributors. + * 4. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + */ + +#ifndef lint +static char sccsid[] = "@(#)popen.c 8.3 (Berkeley) 4/6/94"; +#endif /* not lint */ + +#include <sys/types.h> +#include <sys/wait.h> + +#include <errno.h> +#include <glob.h> +#include <signal.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include "extern.h" + +/* + * Special version of popen which avoids call to shell. This ensures noone + * may create a pipe to a hidden program as a side effect of a list or dir + * command. + */ +static int *pids; +static int fds; + +FILE * +ftpd_popen(program, type) + char *program, *type; +{ + char *cp; + FILE *iop; + int argc, gargc, pdes[2], pid; + char **pop, *argv[100], *gargv[1000]; + + if (*type != 'r' && *type != 'w' || type[1]) + return (NULL); + + if (!pids) { + if ((fds = getdtablesize()) <= 0) + return (NULL); + if ((pids = (int *)malloc((u_int)(fds * sizeof(int)))) == NULL) + return (NULL); + memset(pids, 0, fds * sizeof(int)); + } + if (pipe(pdes) < 0) + return (NULL); + + /* break up string into pieces */ + for (argc = 0, cp = program;; cp = NULL) + if (!(argv[argc++] = strtok(cp, " \t\n"))) + break; + + /* glob each piece */ + gargv[0] = argv[0]; + for (gargc = argc = 1; argv[argc]; argc++) { + glob_t gl; + int flags = GLOB_BRACE|GLOB_NOCHECK|GLOB_QUOTE|GLOB_TILDE; + + memset(&gl, 0, sizeof(gl)); + if (glob(argv[argc], flags, NULL, &gl)) + gargv[gargc++] = strdup(argv[argc]); + else + for (pop = gl.gl_pathv; *pop; pop++) + gargv[gargc++] = strdup(*pop); + globfree(&gl); + } + gargv[gargc] = NULL; + + iop = NULL; + switch(pid = vfork()) { + case -1: /* error */ + (void)close(pdes[0]); + (void)close(pdes[1]); + goto pfree; + /* NOTREACHED */ + case 0: /* child */ + if (*type == 'r') { + if (pdes[1] != STDOUT_FILENO) { + dup2(pdes[1], STDOUT_FILENO); + (void)close(pdes[1]); + } + dup2(STDOUT_FILENO, STDERR_FILENO); /* stderr too! */ + (void)close(pdes[0]); + } else { + if (pdes[0] != STDIN_FILENO) { + dup2(pdes[0], STDIN_FILENO); + (void)close(pdes[0]); + } + (void)close(pdes[1]); + } + execv(gargv[0], gargv); + _exit(1); + } + /* parent; assume fdopen can't fail... */ + if (*type == 'r') { + iop = fdopen(pdes[0], type); + (void)close(pdes[1]); + } else { + iop = fdopen(pdes[1], type); + (void)close(pdes[0]); + } + pids[fileno(iop)] = pid; + +pfree: for (argc = 1; gargv[argc] != NULL; argc++) + free(gargv[argc]); + + return (iop); +} + +int +ftpd_pclose(iop) + FILE *iop; +{ + int fdes, omask, status; + pid_t pid; + + /* + * pclose returns -1 if stream is not associated with a + * `popened' command, or, if already `pclosed'. + */ + if (pids == 0 || pids[fdes = fileno(iop)] == 0) + return (-1); + (void)fclose(iop); + omask = sigblock(sigmask(SIGINT)|sigmask(SIGQUIT)|sigmask(SIGHUP)); + while ((pid = waitpid(pids[fdes], &status, 0)) < 0 && errno == EINTR) + continue; + (void)sigsetmask(omask); + pids[fdes] = 0; + if (pid < 0) + return (pid); + if (WIFEXITED(status)) + return (WEXITSTATUS(status)); + return (1); +} |