/* File: ftpproxy/ftp.c Copyright (C) 1999, 2000 Wolfgang Zekoll Copyright (C) 2000, 2003 Andreas Schoenberg This software is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "ftp.h" #include "ip-lib.h" #include "lib.h" typedef struct _ftpcmd { char name[20]; int par, ispath, useccp; int resp; int log; } ftpcmd_t; ftpcmd_t cmdtab[] = { /* * Einfache FTP Kommandos. */ { "ABOR", 0, 0, 0, 225, 1 }, /* oder 226 */ { "ACCT", 1, 0, 0, 230, 0 }, { "CDUP", 1, 1, 1, 200, 1 }, { "CWD", 1, 1, 1, 250, 1 }, { "DELE", 1, 1, 1, 250, 1 }, { "NOOP", 0, 0, 0, 200, 0 }, { "MDTM", 1, 1, 1, 257, 1 }, { "MKD", 1, 1, 1, 257, 1 }, { "MODE", 1, 0, 0, 200, 0 }, { "PWD", 0, 0, 0, 257, 0 }, { "QUIT", 0, 0, 0, 221, 0 }, { "REIN", 0, 0, 0, 0, /* 220, */ 0 }, /* wird nicht unterstuetzt */ { "REST", 1, 0, 0, 350, 0 }, { "RNFR", 1, 1, 1, 350, 1 }, { "RNTO", 1, 1, 1, 250, 1 }, { "RMD", 1, 1, 1, 250, 1 }, { "SITE", 1, 0, 1, 200, 0 }, { "SIZE", 1, 1, 1, 213, 1 }, { "SMNT", 1, 0, 0, 250, 0 }, { "STAT", 1, 1, 1, 211, 0 }, /* oder 212, 213 */ { "STRU", 1, 0, 0, 0, /* 200, */ 0 }, /* wird nicht unterstuetzt */ { "SYST", 0, 0, 0, 215, 0 }, { "TYPE", 1, 0, 0, 200, 0 }, { "XCUP", 1, 1, 1, 200, 1 }, { "XCWD", 1, 1, 1, 250, 1 }, { "XMKD", 1, 1, 1, 257, 1 }, { "XPWD", 0, 0, 0, 257, 0 }, { "XRMD", 1, 1, 1, 250, 1 }, /* * Nur der Vollstaendigkeit halber: FTP Kommandos die gesondert * behandelt werden. */ { "LIST", 1, 1, 1, 0, 0 }, { "NLST", 1, 1, 1, 0, 0 }, { "PORT", 1, 0, 0, 0, /* 200, */ 0 }, { "PASV", 0, 0, 0, 0, /* 200, */ 0 }, { "ALLO", 1, 0, 0, 0, /* 200, */ 0 }, { "RETR", 1, 1, 1, 0, 0 }, { "STOR", 1, 1, 1, 0, 0 }, { "STOU", 0, 0, 1, 0, 0 }, { "APPE", 1, 1, 1, 0, 0 }, { "HELP", 0, 0, 0, 0, 0 }, { "FEAT", 0, 0, 0, 0, 0 }, { "", 0, 0, 0, 0, 0 } }; unsigned get_interface_info(int pfd, char *ip, int max) { int size; unsigned int port; struct sockaddr_in saddr; size = sizeof(saddr); if (getsockname(pfd, (struct sockaddr *) &saddr, &size) < 0) { syslog(LOG_NOTICE, "-ERR: can't get interface info: %s", strerror(errno)); exit (-1); } copy_string(ip, (char *) inet_ntoa(saddr.sin_addr), max); port = ntohs(saddr.sin_port); return (port); } int get_client_info(ftp_t *x, int pfd) { int size; struct sockaddr_in saddr; struct in_addr *addr; struct hostent *hostp = NULL; *x->client = 0; size = sizeof(saddr); if (getpeername(pfd, (struct sockaddr *) &saddr, &size) < 0 ) return (-1); copy_string(x->client_ip, (char *) inet_ntoa(saddr.sin_addr), sizeof(x->client_ip)); if (x->config->numeric_only == 1) copy_string(x->client, x->client_ip, sizeof(x->client)); else { addr = &saddr.sin_addr, hostp = gethostbyaddr((char *) addr, sizeof (saddr.sin_addr.s_addr), AF_INET); copy_string(x->client, hostp == NULL? x->client_ip: hostp->h_name, sizeof(x->client)); } strlwr(x->client); return (0); } /* * Basic I/O functions */ int close_ch(ftp_t *x, dtc_t *ch) { if (ch->isock >= 0) close(ch->isock); if (ch->osock >= 0) close (ch->osock); ch->isock = -1; ch->osock = -1; ch->state = 0; ch->operation = 0; ch->seen150 = 0; return (0); } int getc_fd(ftp_t *x, int fd) { int c; bio_t *bio; if (fd == 0) bio = &x->cbuf; else if (fd == x->fd.server) bio = &x->sbuf; else { syslog(LOG_NOTICE, "-ERR: internal bio/fd error"); exit (1); } if (bio->here >= bio->len) { int rc, max, bytes, earlyreported; struct timeval tov; fd_set available, fdset; bio->len = bio->here = 0; earlyreported = 0; FD_ZERO(&fdset); FD_SET(fd, &fdset); /* x->fd.max = fd; */ max = fd; if (x->ch.operation == 0) /* nichts */ ; else if (x->ch.state == PORT_LISTEN) { if (x->ch.mode == MODE_PORT) { FD_SET(x->ch.osock, &fdset); if (x->ch.osock > max) max = x->ch.osock; x->ch.active = x->ch.osock; } else if (x->ch.mode == MODE_PASSIVE) { FD_SET(x->ch.isock, &fdset); if (x->ch.isock > max) max = x->ch.isock; x->ch.active = x->ch.isock; } else { syslog(LOG_NOTICE, "-ERR: internal mode error"); exit (-1); } } else if (x->ch.state == PORT_CONNECTED && x->ch.seen150 == 1) { FD_SET(x->ch.active, &fdset); if (x->ch.active > max) max = x->ch.active; } bytes = 0; while (1) { /* memmove(&available, &fdset, sizeof(fd_set)); */ available = fdset; tov.tv_sec = x->config->timeout; tov.tv_usec = 0; if (debug >= 2) fprintf (stderr, "select max= %d\n", max); rc = select(max + 1, &available, (fd_set *) NULL, (fd_set *) NULL, &tov); if (rc < 0) { syslog(LOG_NOTICE, "select() error: %s\n", strerror(errno)); break; } else if (rc == 0) { syslog(LOG_NOTICE, "connection timed out: client= %s, server= %s:%u", x->client, x->server.name, x->server.port); return (-1); } if (FD_ISSET(fd, &available)) { if ((bytes = read(fd, bio->buffer, sizeof(bio->buffer) - 2)) <= 0) { if (debug != 0) { if (bytes == 0) fprintf (stderr, "received zero bytes on fd %d\n", fd); else fprintf (stderr, "received %d bytes on fd %d, errno= %d, error= %s\n", bytes, fd, errno, strerror(errno)); } return (-1); } break; } else if (FD_ISSET(x->ch.active, &available)) { if (x->ch.state == PORT_LISTEN) { int sock, adrlen; struct sockaddr_in adr; earlyreported = 0; adrlen = sizeof(struct sockaddr); sock = accept(x->ch.active, (struct sockaddr *) &adr, &adrlen); if (debug != 0) fprintf (stderr, "accept() on socket\n"); if (sock < 0) { syslog(LOG_NOTICE, "-ERR: accept error: %s", strerror(errno)); exit (1); } else { char remote[80]; copy_string(remote, inet_ntoa(adr.sin_addr), sizeof(remote)); if (debug) fprintf (stderr, "connection from %s\n", remote); /* * Gegenstelle ueberpruefen. */ if (x->ch.mode == MODE_PORT) { if (strcmp(x->server.ipnum, remote) != 0) { if (x->config->allow_anyremote != 0) /* configuration tells us not to care -- 31JAN02asg */ ; else { syslog(LOG_NOTICE, "-ERR: unexpected connect: %s, expected= %s", remote, x->server.ipnum); exit (1); } } } else { if (strcmp(x->client_ip, remote) != 0) { if (x->config->allow_anyremote != 0) /* ok -- 31JAN02asg */ ; else { syslog(LOG_NOTICE, "-ERR: unexpected connect: %s, expected= %s", remote, x->client_ip); exit (1); } } } } /* * Datenkanal zur anderen Seite aufbauen. */ if (x->ch.mode == MODE_PORT) { dup2(sock, x->ch.osock); close (sock); x->ch.state = PORT_CONNECTED; if (debug) fprintf (stderr, "osock= %d\n", x->ch.osock); if ((x->ch.isock = openip(x->ch.client.ipnum, x->ch.client.port, x->interface, x->config->dataport)) < 0) { syslog(LOG_NOTICE, "-ERR: can't connect to client: %s", strerror(errno)); exit (1); } if (debug) fprintf (stderr, "isock= %d\n", x->ch.isock); } else if (x->ch.mode == MODE_PASSIVE) { dup2(sock, x->ch.isock); close (sock); x->ch.state = PORT_CONNECTED; if (debug) fprintf (stderr, "isock= %d\n", x->ch.isock); if ((x->ch.osock = openip(x->ch.server.ipnum, x->ch.server.port, x->config->sourceip, 0)) < 0) { syslog(LOG_NOTICE, "-ERR: can't connect to server: %s", strerror(errno)); exit (1); } if (debug) fprintf (stderr, "osock= %d\n", x->ch.osock); } /* * Setzen der Datenquelle (Server oder Client). */ if (x->ch.operation == OP_GET) { x->ch.active = x->ch.osock; x->ch.other = x->ch.isock; } else if (x->ch.operation == OP_PUT) { x->ch.active = x->ch.isock; x->ch.other = x->ch.osock; } else { syslog(LOG_NOTICE, "-ERR: transfer operation error"); exit (1); } if (x->ch.seen150 == 0) { /* * And finally ... another attempt to solve the short * data transmission timing problem: If we didn't receive * the 150 response yet from the server we deactivate the * data channel until we have the 150 -- 030406asg */ if (debug >= 2) fprintf (stderr, "150 not seen, deactivating data channel\n"); FD_ZERO(&fdset); FD_SET(fd, &fdset); max = fd; } else { if (debug >= 2) fprintf (stderr, "150 already seen, activating data channel\n"); FD_ZERO(&fdset); FD_SET(fd, &fdset); FD_SET(x->ch.active, &fdset); max = (fd > x->ch.active)? fd: x->ch.active; } if (debug) fprintf (stderr, "active= %d, other= %d\n", x->ch.active, x->ch.other); x->ch.bytes = 0; x->ch.started = time(NULL); } else if (x->ch.state == PORT_CONNECTED) { int wrote; char buffer[FTPMAXBSIZE + 10]; if (x->ch.operation == 0) { if (earlyreported == 0) { earlyreported = 1; syslog(LOG_NOTICE, "early write/read event, sleeping 2 seconds"); sleep(2); continue; } } bytes = read(x->ch.active, buffer, x->config->bsize /* sizeof(buffer) */ ); /* * Handling servers that close the data connection -- 24APR02asg */ wrote = 0; if ((bytes > 0) && ((wrote = write(x->ch.other, buffer, bytes)) == bytes)) x->ch.bytes += bytes; else { if (wrote < 0) syslog(LOG_NOTICE, "error writing data channel, error= %s", strerror(errno)); if (debug) fprintf (stderr, "closing data connection\n"); close_ch(x, &x->ch); FD_ZERO(&fdset); FD_SET(fd, &fdset); max = fd; return (1); } } } } bio->len = bytes; bio->here = 0; } if (bio->here >= bio->len) return (-1); c = (unsigned char) bio->buffer[bio->here++]; return (c); } char *readline_fd(ftp_t *x, int fd, char *line, int size) { int c, k; *line = 0; size = size - 2; c = getc_fd(x, fd); if (c < 0) return (NULL); else if (c == 1) { strcpy(line, "\001"); return (line); } k = 0; while (c > 0 && c != '\n' && c != 0) { if (k < size) line[k++] = c; c = getc_fd(x, fd); } line[k] = 0; noctrl(line); k = 0; while ((c = (unsigned char ) line[k]) != 0 && c > 126) k++; if (k > 0) copy_string(line, &line[k], size); return (line); } char *cfgets(ftp_t *x, char *line, int size) { char *p; *line = 0; if ((p = readline_fd(x, 0, line, size)) == NULL) return (NULL); else if (debug != 0) fprintf (stderr, "CLI >>>: %s\n", p); return (line); } int cfputs(ftp_t *x, char *line) { char buffer[310]; if (debug) fprintf (stderr, ">>> CLI: %s\n", line); snprintf (buffer, sizeof(buffer) - 2, "%s\r\n", line); write(1, buffer, strlen(buffer)); return (0); } char *sfgets(ftp_t *x, char *line, int size) { char *p; *line = 0; if ((p = readline_fd(x, x->fd.server, line, size)) == NULL) return (NULL); else if (debug != 0) fprintf (stderr, "SVR >>>: %s\n", p); return (line); } int sfputs(ftp_t *x, char *format, ...) { int len; char buffer[310]; va_list ap; va_start(ap, format); vsnprintf (buffer, sizeof(buffer) - 10, format, ap); va_end(ap); if (debug) fprintf (stderr, ">>> SVR: %s\n", buffer); /* * There are firewalls that don't like command to be split in * two packets. Notice: the `- 10' above is really important * to protect the proxy against buffer overflows. */ strcat(buffer, "\r\n"); len = strlen(buffer); /* * SIGPIPE is catched but then ignored, we have to handle it * one our own now -- 24APR02asg */ if (write(x->fd.server, buffer, len) != len) { syslog(LOG_NOTICE, "-ERR: error writing control connect, error= %s", strerror(errno)); exit (1); } /* * write(x->fd.server, buffer, strlen(buffer)); * write(x->fd.server, "\r\n", 2); */ return (0); } int sfputc(ftp_t *x, char *command, char *parameter, char *line, int size, char **here) { int rc; char *p, buffer[300]; if (command != NULL && *command != 0) { if (parameter != NULL && *parameter != 0) snprintf (buffer, sizeof(buffer) - 2, "%s %s", command, skip_ws(parameter)); else copy_string(buffer, command, sizeof(buffer)); sfputs(x, "%s", buffer); } if (sfgets(x, line, size) == NULL) { if (debug != 0) fprintf (stderr, "server disappered in sfputc(), pos #1\n"); return (-1); } else if (strlen(line) < 3) { if (debug != 0) fprintf (stderr, "short server reply in sfputc()\n"); return (-1); } rc = atoi(line); if (line[3] != ' ' && line[3] != 0) { while (1) { if (sfgets(x, line, size) == NULL) { syslog(LOG_NOTICE, "-ERR: lost server while reading client greeting: %s", x->server.name); exit (1); } if (strlen(line) < 3) /* line too short to be response's last line */ ; else if (line[3] != ' ' && line[3] != 0) /* neither white space nor EOL at position #4 */ ; else if (line[0] >= '0' && line[0] <= '9' && atoi(line) == rc) break; /* status code followed by EOL or blank detected */ } } if (here != NULL) { p = skip_ws(&line[3]); *here = p; } return (rc); } int doquit(ftp_t *x) { int rc; char resp[200]; if ((rc = sfputc(x, "QUIT", "", resp, sizeof(resp), NULL)) != 221) syslog(LOG_NOTICE, "unexpected resonse to QUIT: %s", resp); cfputs(x, "221 goodbye"); syslog(LOG_NOTICE, "%d QUIT", rc); return (0); } char *_getipnum(char *line, char **here, char *ip, int size) { int c, i, k; copy_string(ip, line, size); k = 0; for (i=0; (c = ip[i]) != 0; i++) { if (c == ',') { if (k < 3) { ip[i] = '.'; k++; } else { ip[i++] = 0; break; } } } if (here != NULL) *here = &line[i]; return (ip); } unsigned long _getport(char *line, char **here) { unsigned long port; char *p; p = line; port = strtoul(p, &p, 10); if (*p != ',') return (0); p++; port = (port << 8) + strtoul(p, &p, 10); if (here != NULL) *here = p; return (port); } int doport(ftp_t *x, char *command, char *par) { int c, rc; char *p, line[200]; dtc_t *ch; ch = &x->ch; _getipnum(par, &p, ch->client.ipnum, sizeof(ch->client.ipnum)); ch->client.port = _getport(p, &p); if (debug) fprintf (stderr, "client listens on %s:%u\n", ch->client.ipnum, ch->client.port); get_interface_info(x->fd.server, ch->outside.ipnum, sizeof(ch->outside.ipnum)); ch->osock = bind_to_port(ch->outside.ipnum, 0); ch->outside.port = get_interface_info(ch->osock, line, sizeof(line)); if (debug) fprintf (stderr, "listening on %s:%u\n", ch->outside.ipnum, ch->outside.port); copy_string(line, ch->outside.ipnum, sizeof(line)); for (p=line; (c = *p) != 0; p++) { if (c == '.') *p = ','; } *p++ = ','; snprintf (p, 20, "%u,%u", ch->outside.port >> 8, ch->outside.port & 0xFF); /* Open port first */ ch->isock = -1; ch->mode = MODE_PORT; ch->state = PORT_LISTEN; /* then send PORT cmd */ rc = sfputc(x, "PORT", line, line, sizeof(line), &p); /* check return code */ if (rc != 200){ cfputs(x, "500 not accepted"); close_ch(x, &x->ch); } else cfputs(x, "200 ok, port allocated"); /* if (rc != 200) cfputs(x, "500 not accepted"); else { cfputs(x, "200 ok, port allocated"); ch->isock = -1; ch->mode = MODE_PORT; ch->state = PORT_LISTEN; } */ *ch->command = 0; return (rc); } int dopasv(ftp_t *x, char *command, char *par) { int c, k, rc; char *p, line[200]; dtc_t *ch; ch = &x->ch; rc = sfputc(x, "PASV", "", line, sizeof(line), &p); if (rc != 227) { cfputs(x, "500 not accepted"); return (0); } /* * Ende der Port-Koordinaten im Server-Response suchen. */ k = strlen(line); while (k > 0 && isdigit(line[k-1]) == 0) k--; if (isdigit(line[k-1])) { line[k--] = 0; while (k > 0 && (isdigit(line[k-1]) || line[k-1] == ',')) k--; } /* * line[k] sollte jetzt auf die erste Ziffer des PASV Response * zeigen. */ if (isdigit(line[k]) == 0) { syslog(LOG_NOTICE, "can't locate passive response: %s", line); cfputs(x, "500 not accepted"); return (0); } /* * Auslesen der PASV IP-Nummer und des Ports. */ p = &line[k]; _getipnum(p, &p, ch->server.ipnum, sizeof(ch->server.ipnum)); ch->server.port = _getport(p, &p); if (debug) fprintf (stderr, "server listens on %s:%u\n", ch->server.ipnum, ch->server.port); get_interface_info(0, ch->inside.ipnum, sizeof(ch->inside.ipnum)); ch->isock = bind_to_port(ch->inside.ipnum, 0); ch->inside.port = get_interface_info(ch->isock, line, sizeof(line)); if (debug) fprintf (stderr, "listening on %s:%u\n", ch->inside.ipnum, ch->inside.port); snprintf (line, sizeof(line) - 2, "227 Entering Passive Mode (%s,%u,%u)", ch->inside.ipnum, ch->inside.port >> 8, ch->inside.port & 0xFF); for (p=line; (c = *p) != 0; p++) { if (c == '.') *p = ','; } cfputs(x, line); ch->osock = -1; ch->mode = MODE_PASSIVE; ch->state = PORT_LISTEN; *ch->command = 0; ch->operation = 0; return (rc); } int dofeat(ftp_t *x) { /* * Not so easy because we have to align with the server response. */ int rc; char *p, word[80], serverfeature[80], line[300]; static char *proxyfeatlist = "SIZE:MDTM"; sfputs(x, "%s", "FEAT"); if (sfgets(x, line, sizeof(line)) == NULL) { syslog(LOG_NOTICE, "monitor: server not responding"); exit (1); } rc = atoi(line); if (rc != 211) { /* kein FEAT Support */ ; cfputs(x, "502 command not implemented"); return (1); } cfputs(x, "211-feature list follows"); while (1) { if (sfgets(x, line, sizeof(line)) == NULL) { syslog(LOG_NOTICE, "lost server in FEAT response"); exit (1); } else if (*line != ' ') { /* * RFC2389 specifies exactly one space in this * multi-line response. Nothing else. */ break; } /* Get feature from server response ... */ copy_string(serverfeature, line, sizeof(serverfeature)); strupr(serverfeature); /* ... and compare it against our feature list */ p = proxyfeatlist; while (*get_quoted(&p, ':', word, sizeof(word)) != 0) { if (strcmp(word, serverfeature) == 0) { snprintf (line, sizeof(line) - 4, " %s", word); cfputs(x, line); break; } } } cfputs(x, "211 end"); return (0); } int setvar(ftp_t *x, char *var, char *value) { char varname[200]; #if defined SOLARIS snprintf (varname, sizeof(varname) - 2, "%s%s=%s", x->config->varname, var, value != NULL? value: ""); putenv(varname); #else snprintf (varname, sizeof(varname) - 2, "%s%s", x->config->varname, var); setenv(varname, value != NULL? value: "", 1); #endif return (0); } int set_variables(ftp_t *x) { char val[200]; setvar(x, "INTERFACE", x->interface); snprintf (val, sizeof(val) - 2, "%u", x->port); setvar(x, "PORT", val); setvar(x, "CLIENT", x->client_ip); setvar(x, "CLIENTNAME", x->client); setvar(x, "SERVER", x->server.ipnum); snprintf (val, sizeof(val) - 2, "%u", x->server.port); setvar(x, "SERVERPORT", val); setvar(x, "SERVERNAME", x->server.name); setvar(x, "SERVERLOGIN", x->username); setvar(x, "USERNAME", x->local.username); setvar(x, "PASSWD", x->local.password); return (0); } int run_acp(ftp_t *x) { int rc, pid, pfd[2]; char line[300]; if (*x->config->acp == 0) return (0); rc = 0; if (pipe(pfd) != 0) { syslog(LOG_NOTICE, "-ERR: can't pipe: %s", strerror(errno)); exit (1); } else if ((pid = fork()) < 0) { syslog(LOG_NOTICE, "-ERR: can't fork acp: %s", strerror(errno)); exit (1); } else if (pid == 0) { int argc; char *argv[32]; close(0); /* Das acp kann nicht vom client lesen. */ dup2(pfd[1], 2); /* stderr wird vom parent gelesen. */ close(pfd[0]); set_variables(x); copy_string(line, x->config->acp, sizeof(line)); argc = split(line, argv, ' ', 30); argv[argc] = NULL; execvp(argv[0], argv); syslog(LOG_NOTICE, "-ERR: can't exec acp %s: %s", argv[0], strerror(errno)); exit (1); } else { int len; char message[300]; close(pfd[1]); *message = 0; if ((len = read(pfd[0], message, sizeof(message) - 2)) < 0) len = 0; message[len] = 0; noctrl(message); close(pfd[0]); if (waitpid(pid, &rc, 0) < 0) { syslog(LOG_NOTICE, "-ERR: error while waiting for acp: %s", strerror(errno)); exit (1); } rc = WIFEXITED(rc) != 0? WEXITSTATUS(rc): 1; if (*message == 0) copy_string(message, rc == 0? "access granted": "access denied", sizeof(message)); if (*message != 0) syslog(LOG_NOTICE, "%s (rc= %d)", message, rc); } return (rc); } static char *getvarname(char **here, char *var, int size) { int c, k; size = size - 2; k = 0; while ((c = **here) != 0) { *here += 1; if (c == ' ' || c == '\t' || c == '=') break; if (k < size) var[k++] = c; } var[k] = 0; strupr(var); *here = skip_ws(*here); return (var); } int run_ctp(ftp_t *x) { int rc, pid, pfd[2]; char line[300]; FILE *fp; if (*x->config->ctp == 0) return (0); rc = 0; if (pipe(pfd) != 0) { syslog(LOG_NOTICE, "-ERR: can't pipe: %s", strerror(errno)); exit (1); } else if ((pid = fork()) < 0) { syslog(LOG_NOTICE, "-ERR: can't fork trp: %s", strerror(errno)); exit (1); } else if (pid == 0) { int argc; char *argv[32]; close(0); /* Das trp kann nicht vom client lesen. */ dup2(pfd[1], 1); /* stdout wird vom parent gelesen. */ close(pfd[0]); set_variables(x); copy_string(line, x->config->ctp, sizeof(line)); argc = split(line, argv, ' ', 30); argv[argc] = NULL; execvp(argv[0], argv); syslog(LOG_NOTICE, "-ERR: can't exec trp %s: %s", argv[0], strerror(errno)); exit (1); } else { char *p, var[80], line[300]; close(pfd[1]); fp = fdopen(pfd[0], "r"); while (fgets(line, sizeof(line), fp)) { p = skip_ws(noctrl(line)); getvarname(&p, var, sizeof(var)); if (strcmp(var, "SERVERNAME") == 0 || strcmp(var, "SERVER") == 0) copy_string(x->server.name, p, sizeof(x->server.name)); else if (strcmp(var, "SERVERLOGIN") == 0 || strcmp(var, "LOGIN") == 0) copy_string(x->username, p, sizeof(x->username)); else if (strcmp(var, "SERVERPASSWD") == 0 || strcmp(var, "PASSWD") == 0) copy_string(x->password, p, sizeof(x->password)); else if (strcmp(var, "SERVERPORT") == 0 || strcmp(var, "PORT") == 0) x->server.port = atoi(p); /* * Enable the trp to send error messages. */ else if (strcmp(var, "-ERR") == 0 || strcmp(var, "-ERR:") == 0) { syslog(LOG_NOTICE, "-ERR: %s", skip_ws(p)); exit (1); } } fclose(fp); /* * In standalone mode we do not receive the SIGCHLD because * we set it to SIG_IGN -- 030406asg */ if (x->config->standalone == 0 && waitpid(pid, &rc, 0) < 0) { syslog(LOG_NOTICE, "-ERR: error while waiting for trp: %s", strerror(errno)); exit (1); } rc = WIFEXITED(rc) != 0? WEXITSTATUS(rc): 1; if (rc != 0) { syslog(LOG_NOTICE, "-ERR: trp signals error condition, rc= %d", rc); exit (1); } } return (rc); } int get_ftpdir(ftp_t *x) { int rc, len; char *p, *start, line[300]; static char *quotes = "'\"'`"; sfputs(x, "%s", "PWD"); if (sfgets(x, line, sizeof(line)) == NULL) { syslog(LOG_NOTICE, "monitor: server not responding"); exit (1); } rc = strtol(line, &p, 10); if (rc != 257) { syslog(LOG_NOTICE, "monitor: PWD status: %d", rc); exit (1); } p = skip_ws(p); if (*p == 0) { syslog(LOG_NOTICE, "monitor: directory unset"); exit (1); } if ((start = strchr(p, '/')) == NULL) { syslog(LOG_NOTICE, "monitor: can't find directory in string: %s", p); exit (1); } get_word(&start, x->cwd, sizeof(x->cwd)); if ((len = strlen(x->cwd)) > 0 && strchr(quotes, x->cwd[len-1]) != NULL) x->cwd[len - 1] = 0; if (*x->cwd != '/') { syslog(LOG_NOTICE, "monitor: invalid path: %s", x->cwd); exit (1); } syslog(LOG_NOTICE, "cwd: %s", x->cwd); return (0); } int get_ftppath(ftp_t *x, char *path) { int i, k, n, m; char cwp[200], ftpdir[200], pbuf[200]; char *part[DIR_MAXDEPTH+5], *dir[DIR_MAXDEPTH+5]; /* * Zuerst wird das aktuelle Verzeichnis (der ftppath) in seine * Einzelteile zerlegt ... */ if (*path == '/') { /* * ... Ausnahme: die path-Angabe ist absolut ... */ dir[0] = ""; n = 1; } else { copy_string(ftpdir, x->cwd, sizeof(ftpdir)); if (*ftpdir != 0 && strcmp(ftpdir, "/") != 0) n = split(ftpdir, part, '/', DIR_MAXDEPTH); else { dir[0] = ""; n = 1; } } /* * ... danach der path. Die path Teile werden unter Beachtung * der ueblichen Regeln an die Teile des aktuellen Verzeichnisses * angehangen ... */ copy_string(pbuf, path, sizeof(pbuf)); m = split(pbuf, dir, '/', 15); for (i=0; i 1) n = n - 1; } else part[n++] = dir[i]; if (n < 1 || n >= DIR_MAXDEPTH) return (1); /* ungueltiges Verzeichnis */ } /* * ... und das Ergebnis wieder zusammengesetzt. */ if (n <= 1) { strcpy(cwp, "/"); } else { k = 0; for (i=1; i= sizeof(dir)) return (1); /* Name zu lang */ cwp[k++] = '/'; strcpy(&cwp[k], part[i]); k += strlen(&cwp[k]); } cwp[k] = 0; } /* * Der normalisierte path auf das Objekt (Datei oder Verzeichnis, * ist hier egal) ist fertig. */ copy_string(x->filepath, cwp, sizeof(x->filepath)); return (0); } int run_ccp(ftp_t *x, char *cmd, char *par) { int rc, pid, pfd[2], lfd[2]; char message[300], line[300]; /* * Wenn kein ccp angegeben ist ist alles erlaubt. */ if (*x->config->ccp == 0) return (CCP_OK); /* * Der Vorgang fuer ccp's ist fast gleich mit dem fuer acp's. */ rc = 0; if (pipe(pfd) != 0 || pipe(lfd)) { syslog(LOG_NOTICE, "-ERR: can't pipe: %s", strerror(errno)); exit (1); } else if ((pid = fork()) < 0) { syslog(LOG_NOTICE, "-ERR: can't fork ccp: %s", strerror(errno)); exit (1); } else if (pid == 0) { int argc; char *argv[32]; dup2(pfd[1], 2); /* stderr nach FTP Client */ close(pfd[0]); dup2(lfd[1], 1); /* stdout nach syslog */ close(lfd[0]); close(0); set_variables(x); setvar(x, "COMMAND", cmd); setvar(x, "PARAMETER", par); setvar(x, "SESSION", x->session); snprintf (line, sizeof(line) - 2, "%d", x->ccpcoll); setvar(x, "CCPCOLL", line); setvar(x, "FTPHOME", x->home); setvar(x, "FTPPATH", x->filepath); copy_string(line, x->config->ccp, sizeof(line)); argc = split(line, argv, ' ', 30); argv[argc] = NULL; execvp(argv[0], argv); syslog(LOG_NOTICE, "-ERR: can't exec ccp %s: %s", argv[0], strerror(errno)); exit (1); } else { int len; /* * Nicht gebrauchte fd's schliessen. */ close(pfd[1]); close(lfd[1]); /* * syslog Meldung lesen und entsprechende pipe schliessen. */ *message = 0; if ((len = read(lfd[0], message, sizeof(message) - 2)) < 0) len = 0; message[len] = 0; noctrl(message); close(lfd[0]); if (*message != 0) syslog(LOG_NOTICE, "%s", message); /* * Fehlermeldung lesen, pipe schliessen. */ *message = 0; if ((len = read(pfd[0], message, sizeof(message) - 2)) < 0) len = 0; message[len] = 0; noctrl(message); close(pfd[0]); /* * return code holen. */ if (waitpid(pid, &rc, 0) < 0) { syslog(LOG_NOTICE, "-ERR: error while waiting for ccp: %s", strerror(errno)); exit (1); } rc = WIFEXITED(rc) != 0? WEXITSTATUS(rc): 1; if (rc == 0) return (CCP_OK); if (*message == 0) copy_string(message, "permission denied", sizeof(message)); /* * snprintf (command, sizeof(command) - 2, "%s%s%s", cmd, (par != 0? " ": ""), par); * syslog(LOG_NOTICE, "ccp: -ERR: %s@%s: %s: %s: rc= %d", * x->username, x->server.name, * command, message, rc); */ } x->ccpcoll++; if (isdigit(*message)) cfputs(x, message); else { snprintf (line, sizeof(line) - 2, "553 %s", message); cfputs(x, line); } /* cfputs(x, "553 permission denied."); */ return (CCP_ERROR); } /* * dologin() accepts now blanks with in and at the end of * passwords - 22JAN02asg */ int dologin(ftp_t *x) { int c, i, rc; char *p, word[80], line[300]; struct hostent *hostp; struct sockaddr_in saddr; while (1) { if (readline_fd(x, 0, line, sizeof(line)) == NULL) return (1); if (x->config->allow_passwdblanks == 0) p = noctrl(line); else { p = line; for (i=strlen(line)-1; i>=0; i--) { if ((c = line[i]) != '\n' && c != '\r') { line[i+1] = 0; break; } } } get_word(&p, word, sizeof(word)); strupr(word); if (strcmp(word, "USER") == 0) { get_word(&p, x->username, sizeof(x->username)); cfputs(x, "331 password required"); } else if (strcmp(word, "PASS") == 0) { if (*x->username == 0) { cfputs(x, "503 give USER first"); continue; } if (x->config->allow_passwdblanks == 0) get_word(&p, x->password, sizeof(x->password)); else copy_string(x->password, p, sizeof(x->password)); break; } else if (strcmp(word, "QUIT") == 0) { cfputs(x, "221 goodbye"); return (2); } else { cfputs(x, "530 login first"); } } if (*x->config->ctp != 0) { /* * We are extremly liberate here with server selection * if we have a dynamic control program, we accept * anything here -- 030404asg */ if ((p = strchr(x->username, '@')) == NULL && (p = strchr(x->username, '%')) == NULL) *x->server.name = 0; else if (x->config->use_last_at == 0) { *p++ = 0; copy_string(x->server.name, p, sizeof(x->server.name)); } else { if ((p = strrchr(x->username, '@')) == NULL) p = strrchr(x->username, '%'); *p++ = 0; copy_string(x->server.name, p, sizeof(x->server.name)); } } else if (x->config->selectserver == 0) { if ((p = strchr(x->username, '@')) != NULL && (p = strchr(x->username, '%')) != NULL) { cfputs(x, "500 service unavailable"); syslog(LOG_NOTICE, "-ERR: hostname supplied: %s", p); exit (1); } copy_string(x->server.name, x->config->server, sizeof(x->server.name)); } else { /* * Normally we search for the first '@' so that the client can * not use "proxy hopping". The option "-u" can override * this behaviour. */ if (x->config->use_last_at == 0) { if ((p = strchr(x->username, '@')) == NULL && (p = strchr(x->username, '%')) == NULL) { cfputs(x, "500 service unavailable"); syslog(LOG_NOTICE, "-ERR: missing hostname"); exit (1); } } else { if ((p = strrchr(x->username, '@')) == NULL && (p = strrchr(x->username, '%')) == NULL) { cfputs(x, "500 service unavailable"); syslog(LOG_NOTICE, "-ERR: missing hostname"); exit (1); } } *p++ = 0; copy_string(x->server.name, p, sizeof(x->server.name)); /* * Den Server auf der Serverliste suchen, wenn eine Liste * vorhanden ist. */ /* * Checking the server against the given list is done later now, * see below. Code quoted -- 030404asg * * if ((p = x->config->serverlist) != NULL && *p != 0) { * int permitted; * char pattern[80]; * * permitted = 0; * while ((p = skip_ws(p)), *get_quoted(&p, ',', pattern, sizeof(pattern)) != 0) { * noctrl(pattern); * if (strpcmp(x->server.name, pattern) == 0) { * permitted = 1; * break; * } * } * * if (permitted == 0) { * cfputs(x, "500 service unavailable"); * syslog(LOG_NOTICE, "-ERR: hostname not permitted: %s", x->server.name); * exit (1); * } * } */ } /* * Wenn vorhanden Proxy Login und Passwort auslesen. */ if ((p = strchr(x->username, ':')) != NULL) { *p++ = 0; copy_string(x->local.username, x->username, sizeof(x->local.username)); copy_string(x->username, p, sizeof(x->username)); } if ((p = strchr(x->password, ':')) != NULL) { *p++ = 0; copy_string(x->local.password, x->password, sizeof(x->local.password)); copy_string(x->password, p, sizeof(x->password)); } /* * Call the dynamic configuration program. */ if (*x->config->ctp != 0) { x->server.port = get_port(x->server.name, 21); if (run_ctp(x) != 0) exit (0); /* Never happens, we exit in run_ctp() */ if (debug != 0) { fprintf (stderr, "trp debug: server= %s:%u, login= %s, passwd= %s", x->server.name, x->server.port, x->username, x->password); } } /* * Get port an IP number of server. Moved code here -- 030404asg */ x->server.port = get_port(x->server.name, 21); if ((hostp = gethostbyname(x->server.name)) == NULL) { cfputs(x, "500 service unavailable"); syslog(LOG_NOTICE, "-ERR: can't resolve hostname: %s", x->server.name); exit (1); } memcpy(&saddr.sin_addr, hostp->h_addr, hostp->h_length); copy_string(x->server.ipnum, inet_ntoa(saddr.sin_addr), sizeof(x->server.ipnum)); /* * Call the access control program to check if the proxy * request is allowed. Moved code here -- 030404asg */ if (*x->config->acp != 0) { if (run_acp(x) != 0) exit (0); } /* * Verification if the destination server is on the given list * is done now here. * * Notice: Prior to this change you could give a fixed desination * server as command line argument and a list of allowed server * too. Meaningless because the proxy didn't care when the `server * selection' option wasn't turned on. Now also the fixed server * is checked against the list. * * I don't expect that this breaks an already running configuration * because as said above this configuration was senseless in earlier * proxy versions -- 030404asg */ if ((p = x->config->serverlist) != NULL && *p != 0) { int permitted; char pattern[80]; permitted = 0; while ((p = skip_ws(p)), *get_quoted(&p, ',', pattern, sizeof(pattern)) != 0) { noctrl(pattern); if (strpcmp(x->server.name, pattern) == 0) { permitted = 1; break; } } if (permitted == 0) { cfputs(x, "500 service unavailable"); syslog(LOG_NOTICE, "-ERR: hostname not permitted: %s", x->server.name); exit (1); } } /* * Establish connection to the server */ if ((x->fd.server = openip(x->server.name, x->server.port, x->config->sourceip, 0)) < 0) { cfputs(x, "500 service unavailable"); syslog(LOG_NOTICE, "-ERR: can't connect to server: %s", x->server.name); exit (1); } syslog(LOG_NOTICE, "connected to server: %s", x->server.name); if (sfputc(x, NULL, NULL, line, sizeof(line), NULL) != 220) { cfputs(x, "500 service unavailable"); syslog(LOG_NOTICE, "-ERR: unexpected server greeting: %s", line); exit (1); } /* * Login auf FTP-Server. * * Complete rewrite because of servers wanting no password after * login of anonymous user. */ rc = sfputc(x, "USER", x->username, line, sizeof(line), NULL); if (rc == 230) { cfputs(x, "230 login accepted"); syslog(LOG_NOTICE, "login accepted: %s@%s, no password needed.", x->username, x->server.name); return (0); } else if (rc != 331) { cfputs(x, "500 service unavailable"); syslog(LOG_NOTICE, "-ERR: unexpected reply to USER: %s", line); exit (1); } else if (sfputc(x, "PASS", x->password, line, sizeof(line), NULL) != 230) { cfputs(x, "530 bad login"); syslog(LOG_NOTICE, "-ERR: reply to PASS: %s", line); exit (1); } cfputs(x, "230 login accepted"); syslog(LOG_NOTICE, "login accepted: %s@%s", x->username, x->server.name); return (0); /* if (sfputc(x, "USER", x->username, line, sizeof(line), NULL) != 331) { cfputs(x, "500 service unavailable"); syslog(LOG_NOTICE, "-ERR: unexpected reply to USER: %s", line); exit (1); } else if (sfputc(x, "PASS", x->password, line, sizeof(line), NULL) != 230) { cfputs(x, "530 bad login"); syslog(LOG_NOTICE, "-ERR: reply to PASS: %s", line); exit (1); } cfputs(x, "230 login accepted"); syslog(LOG_NOTICE, "login accepted: %s@%s", x->username, x->server.name); return (0); */ } void signal_handler(int sig) { /* * Changed the way we handle broken pipes (broken control or * data connection). We ignore it here but write() returns -1 * and errno is set to EPIPE which is checked. */ if (sig == SIGPIPE) { signal(SIGPIPE, signal_handler); return; } syslog(LOG_NOTICE, "-ERR: received signal #%d", sig); exit (1); } int set_signals(void) { signal(SIGHUP, signal_handler); signal(SIGINT, signal_handler); signal(SIGQUIT, signal_handler); signal(SIGSEGV, signal_handler); signal(SIGPIPE, signal_handler); signal(SIGALRM, signal_handler); signal(SIGTERM, signal_handler); signal(SIGUSR1, signal_handler); signal(SIGUSR2, signal_handler); return (0); } ftpcmd_t *getcmd(char *name) { int i; for (i=0; cmdtab[i].name[0] != 0; i++) { if (strcmp(cmdtab[i].name, name) == 0) return (&cmdtab[i]); } return (NULL); } int proxy_request(config_t *config) { int rc; char *p, command[200], parameter[200], line[300]; ftpcmd_t *cmd; ftp_t *x; set_signals(); /* * Set socket options to prevent us from the rare case that * we transfer data to/from the client before the client has * seen our "150 ..." message. * Seems so that is doesn't work on all systems. * So temporary only enable it on linux. */ #if defined(__linux__) rc = 1; if (setsockopt(1, SOL_TCP, TCP_NODELAY, &rc, sizeof(rc)) != 0) syslog(LOG_NOTICE, "can't set TCP_NODELAY, error= %s", strerror(errno)); #endif if (config->bsize <= 0) config->bsize = 1024; else if (config->bsize > FTPMAXBSIZE) config->bsize = FTPMAXBSIZE; x = allocate(sizeof(ftp_t)); x->config = config; snprintf (x->session, sizeof(x->session) - 2, "%lu-%u", time(NULL), getpid()); /* * Fix potential problems after immediate initial unseccsesful * up/downloads. Wasn't a problem since we all do a LIST * at first. */ x->ch.isock = -1; x->ch.osock = -1; if (get_client_info(x, 0) < 0) { syslog(LOG_NOTICE, "-ERR: can't get client info: %s", strerror(errno)); exit (1); } x->port = get_interface_info(0, x->interface, sizeof(x->interface)); syslog(LOG_NOTICE, "connected to client: %s, interface= %s:%u", x->client, x->interface, x->port); if (*x->config->configfile != 0) { if (readconfig(x->config, x->config->configfile, x->interface) == 0) { cfputs(x, "421 not available"); syslog(LOG_NOTICE, "-ERR: unconfigured interface: %s", x->interface); exit (1); } } syslog(LOG_NOTICE, "info: monitor mode: %s, ccp: %s", x->config->monitor == 0? "off": "on", *x->config->ccp == 0? "": x->config->ccp); cfputs(x, "220 server ready - login please"); if ((rc = dologin(x)) < 0) return (1); else if (rc == 2) return (0); /* * Open the xferlog if we have one. */ if (*x->config->xferlog != 0) { if (*x->server.name == 0) copy_string(x->logusername, x->username, sizeof(x->logusername)); else if (x->server.port != 21) snprintf (x->logusername, sizeof(x->logusername), "%s@%s:%u", x->username, x->server.name, x->server.port); else snprintf (x->logusername, sizeof(x->logusername), "%s@%s", x->username, x->server.name); x->xlfp = fopen(x->config->xferlog, "a"); if (x->xlfp == NULL) { syslog(LOG_NOTICE, "-WARN: can't open xferlog: %s, error= %s", x->config->xferlog, strerror(errno)); } } if (x->config->monitor) { get_ftpdir(x); copy_string(x->home, x->cwd, sizeof(x->home)); } while ((p = cfgets(x, line, sizeof(line))) != NULL) { if (*p == '\001') { if (*x->ch.command != 0) { syslog(LOG_NOTICE, "%s %s: %ld bytes", x->ch.command, x->ch.filename, x->ch.bytes); if (x->xlfp != NULL) { unsigned long now; char date[80]; /* * Write xferlog entry but notice that (1) session are never * flagged as anonymous and (2) the transfer type is always * binary (type flag was added to data channel but is * actually not used. 10MAY04wzk */ now = time(NULL); copy_string(date, ctime(&now), sizeof(date)); fprintf (x->xlfp, "%s %lu %s %lu %s %c %c %c %c %s %s %d %s %c\n", date, now - x->ch.started, x->client_ip, x->ch.bytes, x->ch.filename, 'b', /* x->ch.type == TYPE_ASC? 'a': 'b', */ '-', strcmp(x->ch.command, "RETR")? 'i': 'o', 'u', /* x->isanonymous == 1? 'a': 'u', */ x->logusername, "ftp", 1, x->logusername, 'c'); fflush(x->xlfp); } } /* * Handle multiline server responses after the * data transfer. */ sfputc(x, NULL, NULL, line, sizeof(line), NULL); cfputs(x, line); continue; } p = noctrl(line); get_word(&p, command, sizeof(command)); strupr(command); if ((cmd = getcmd(command)) == NULL || cmd->resp == -1) { cfputs(x, "502 command not implemented"); syslog(LOG_NOTICE, "command not implemented: %s", command); continue; } *x->filepath = 0; if (cmd->par == 0) *parameter = 0; else { if (strcmp(command, "CDUP") == 0) strcpy(parameter, ".."); else if (strcmp(command, "SITE") == 0) copy_string(parameter, p, sizeof(parameter)); else { if (x->config->allow_blanks != 0) copy_string(parameter, p, sizeof(parameter)); else get_word(&p, parameter, sizeof(parameter)); if (*parameter == 0) { if (strcmp(command, "LIST") == 0 || strcmp(command, "NLST") == 0) /* nichts, ist ok */ ; else { syslog(LOG_NOTICE, "parameter required: %s", command); exit (1); } } } if (cmd->ispath != 0) { if (x->config->monitor) { if ((strcmp(command, "LIST") == 0 || strcmp(command, "NLST") == 0) && *parameter == 0) { /* * Sonderfall: wir simulieren `*' als Parameter. */ get_ftppath(x, "*"); } else get_ftppath(x, parameter); } } } if (cmd->useccp != 0) { if (run_ccp(x, command, parameter) != CCP_OK) continue; } if (strcmp(command, "QUIT") == 0) { /* run_ccp(x, "QUIT", ""); */ doquit(x); break; } else if (strcmp(command, "PORT") == 0) doport(x, command, parameter); else if (strcmp(command, "FEAT") == 0) dofeat(x); else if (strcmp(command, "PASV") == 0) dopasv(x, command, parameter); else if (strcmp(command, "LIST") == 0 || strcmp(command, "NLST") == 0) { x->ch.operation = OP_GET; /* fuer PASV mode */ rc = sfputc(x, command, parameter, line, sizeof(line), NULL); if (rc == 125 || rc == 150) { x->ch.operation = OP_GET; x->ch.seen150 = 1; if (debug >= 2) fprintf (stderr, "received 150 response\n"); } else close_ch(x, &x->ch); cfputs(x, line); *x->ch.command = 0; } else if (strcmp(command, "RETR") == 0) { x->ch.operation = OP_GET; /* fuer PASV mode */ rc = sfputc(x, "RETR", parameter, line, sizeof(line), NULL); if (rc == 125 || rc == 150) { x->ch.operation = OP_GET; x->ch.seen150 = 1; if (debug >= 2) fprintf (stderr, "received 150 response\n"); } else close_ch(x, &x->ch); cfputs(x, line); copy_string(x->ch.command, "RETR", sizeof(x->ch.command)); copy_string(x->ch.filename, x->config->monitor != 0? x->filepath: parameter, sizeof(x->ch.filename)); if (extralog != 0) syslog(LOG_NOTICE, "%d RETR %s", rc, (x->config->monitor != 0)? parameter: x->filepath); } else if (strcmp(command, "STOR") == 0 || strcmp(command, "APPE") == 0 || strcmp(command, "STOU") == 0) { x->ch.operation = OP_PUT; /* fuer PASV mode */ rc = sfputc(x, command, parameter, line, sizeof(line), NULL); if (rc == 125 || rc == 150) { x->ch.operation = OP_PUT; x->ch.seen150 = 1; if (debug >= 2) fprintf (stderr, "received 150 response\n"); copy_string(x->ch.command, command, sizeof(x->ch.command)); } else close_ch(x, &x->ch); cfputs(x, line); copy_string(x->ch.filename, x->config->monitor != 0? x->filepath: parameter, sizeof(x->ch.filename)); if (extralog != 0) { if (strcmp(command, "STOU") == 0) syslog(LOG_NOTICE, "%d %s %s", rc, command, "-"); else syslog(LOG_NOTICE, "%d %s %s", rc, command, x->ch.filename); } } else { if (strcmp(command, "CDUP") == 0) *parameter = 0; rc = sfputc(x, command, parameter, line, sizeof(line), NULL); cfputs(x, line); if (extralog != 0 && cmd->log != 0) { if (x->config->monitor != 0 && cmd->ispath != 0) syslog(LOG_NOTICE, "%d %s %s", rc, command, x->filepath); else syslog(LOG_NOTICE, "%d %s%s%s", rc, command, *parameter != 0? " ": "", parameter); } if (strcmp(command, "CWD") == 0 || strcmp(command, "CDUP") == 0) { if (x->config->monitor) get_ftpdir(x); } } } if (*x->config->ccp != 0) run_ccp(x, "+EXIT", x->session); syslog(LOG_NOTICE, "+OK: proxy terminating"); return (0); }