]>
Commit | Line | Data |
---|---|---|
b7080c8e A |
1 | /* |
2 | * Copyright (c) 1985, 1988, 1990, 1992, 1993, 1994 | |
3 | * The Regents of the University of California. All rights reserved. | |
4 | * | |
5 | * Redistribution and use in source and binary forms, with or without | |
6 | * modification, are permitted provided that the following conditions | |
7 | * are met: | |
8 | * 1. Redistributions of source code must retain the above copyright | |
9 | * notice, this list of conditions and the following disclaimer. | |
10 | * 2. Redistributions in binary form must reproduce the above copyright | |
11 | * notice, this list of conditions and the following disclaimer in the | |
12 | * documentation and/or other materials provided with the distribution. | |
13 | * 3. All advertising materials mentioning features or use of this software | |
14 | * must display the following acknowledgement: | |
15 | * This product includes software developed by the University of | |
16 | * California, Berkeley and its contributors. | |
17 | * 4. Neither the name of the University nor the names of its contributors | |
18 | * may be used to endorse or promote products derived from this software | |
19 | * without specific prior written permission. | |
20 | * | |
21 | * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND | |
22 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE | |
23 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE | |
24 | * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE | |
25 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL | |
26 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS | |
27 | * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) | |
28 | * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT | |
29 | * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY | |
30 | * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF | |
31 | * SUCH DAMAGE. | |
32 | */ | |
33 | ||
3e383549 | 34 | #if 0 |
b7080c8e A |
35 | #ifndef lint |
36 | static char copyright[] = | |
37 | "@(#) Copyright (c) 1985, 1988, 1990, 1992, 1993, 1994\n\ | |
38 | The Regents of the University of California. All rights reserved.\n"; | |
39 | #endif /* not lint */ | |
3e383549 | 40 | #endif |
b7080c8e A |
41 | |
42 | #ifndef lint | |
3e383549 A |
43 | #if 0 |
44 | static char sccsid[] = "@(#)ftpd.c 8.4 (Berkeley) 4/16/94"; | |
45 | #endif | |
46 | static const char rcsid[] = | |
47 | "$FreeBSD: src/libexec/ftpd/ftpd.c,v 1.75 2001/03/27 19:40:50 markm Exp $"; | |
b7080c8e A |
48 | #endif /* not lint */ |
49 | ||
b7080c8e A |
50 | /* |
51 | * FTP server. | |
52 | */ | |
53 | #include <sys/param.h> | |
b7080c8e | 54 | #include <sys/ioctl.h> |
3e383549 | 55 | #include <sys/mman.h> |
b7080c8e | 56 | #include <sys/socket.h> |
3e383549 A |
57 | #include <sys/stat.h> |
58 | #include <sys/time.h> | |
b7080c8e A |
59 | #include <sys/wait.h> |
60 | ||
61 | #include <netinet/in.h> | |
62 | #include <netinet/in_systm.h> | |
63 | #include <netinet/ip.h> | |
3e383549 | 64 | #include <netinet/tcp.h> |
b7080c8e A |
65 | |
66 | #define FTP_NAMES | |
67 | #include <arpa/ftp.h> | |
68 | #include <arpa/inet.h> | |
69 | #include <arpa/telnet.h> | |
70 | ||
71 | #include <ctype.h> | |
72 | #include <dirent.h> | |
73 | #include <err.h> | |
74 | #include <errno.h> | |
75 | #include <fcntl.h> | |
76 | #include <glob.h> | |
77 | #include <limits.h> | |
78 | #include <netdb.h> | |
79 | #include <pwd.h> | |
3e383549 | 80 | #include <grp.h> |
b7080c8e A |
81 | #include <setjmp.h> |
82 | #include <signal.h> | |
83 | #include <stdio.h> | |
84 | #include <stdlib.h> | |
85 | #include <string.h> | |
86 | #include <syslog.h> | |
87 | #include <time.h> | |
88 | #include <unistd.h> | |
3e383549 A |
89 | // #include <libutil.h> |
90 | #ifdef LOGIN_CAP | |
91 | #include <login_cap.h> | |
92 | #endif | |
93 | ||
94 | #ifdef SKEY | |
95 | #include <skey.h> | |
96 | #endif | |
97 | ||
98 | #ifdef USE_PAM | |
99 | #include <security/pam_appl.h> | |
100 | #endif | |
b7080c8e A |
101 | |
102 | #include "pathnames.h" | |
103 | #include "extern.h" | |
104 | ||
105 | #if __STDC__ | |
106 | #include <stdarg.h> | |
107 | #else | |
108 | #include <varargs.h> | |
109 | #endif | |
110 | ||
3e383549 A |
111 | static char version[] = "Version 6.00LS"; |
112 | #undef main | |
113 | ||
114 | /* wrapper for KAME-special getnameinfo() */ | |
115 | #ifndef NI_WITHSCOPEID | |
116 | #define NI_WITHSCOPEID 0 | |
117 | #endif | |
b7080c8e A |
118 | |
119 | extern off_t restart_point; | |
120 | extern char cbuf[]; | |
121 | ||
3e383549 A |
122 | union sockunion server_addr; |
123 | union sockunion ctrl_addr; | |
124 | union sockunion data_source; | |
125 | union sockunion data_dest; | |
126 | union sockunion his_addr; | |
127 | union sockunion pasv_addr; | |
b7080c8e | 128 | |
3e383549 | 129 | int daemon_mode; |
b7080c8e A |
130 | int data; |
131 | jmp_buf errcatch, urgcatch; | |
132 | int logged_in; | |
133 | struct passwd *pw; | |
134 | int debug; | |
135 | int timeout = 900; /* timeout after 15 minutes of inactivity */ | |
136 | int maxtimeout = 7200;/* don't allow idle time to be set beyond 2 hours */ | |
137 | int logging; | |
3e383549 A |
138 | int restricted_data_ports = 1; |
139 | int paranoid = 1; /* be extra careful about security */ | |
140 | int anon_only = 0; /* Only anonymous ftp allowed */ | |
b7080c8e | 141 | int guest; |
3e383549 A |
142 | int dochroot; |
143 | int stats; | |
144 | int statfd = -1; | |
b7080c8e A |
145 | int type; |
146 | int form; | |
147 | int stru; /* avoid C keyword */ | |
148 | int mode; | |
149 | int usedefault = 1; /* for data transfers */ | |
150 | int pdata = -1; /* for passive mode */ | |
3e383549 A |
151 | int readonly=0; /* Server is in readonly mode. */ |
152 | int noepsv=0; /* EPSV command is disabled. */ | |
b7080c8e A |
153 | sig_atomic_t transflag; |
154 | off_t file_size; | |
155 | off_t byte_count; | |
156 | #if !defined(CMASK) || CMASK == 0 | |
157 | #undef CMASK | |
158 | #define CMASK 027 | |
159 | #endif | |
160 | int defumask = CMASK; /* default umask value */ | |
161 | char tmpline[7]; | |
3e383549 A |
162 | char *hostname; |
163 | #ifdef VIRTUAL_HOSTING | |
164 | char *ftpuser; | |
165 | ||
166 | int epsvall = 0; | |
167 | ||
168 | static struct ftphost { | |
169 | struct ftphost *next; | |
170 | struct addrinfo *hostinfo; | |
171 | char *hostname; | |
172 | char *anonuser; | |
173 | char *statfile; | |
174 | char *welcome; | |
175 | char *loginmsg; | |
176 | } *thishost, *firsthost; | |
177 | ||
178 | #endif | |
b7080c8e | 179 | char remotehost[MAXHOSTNAMELEN]; |
3e383549 A |
180 | char *ident = NULL; |
181 | ||
182 | static char ttyline[20]; | |
183 | char *tty = ttyline; /* for klogin */ | |
184 | ||
185 | #ifdef USE_PAM | |
186 | static int auth_pam __P((struct passwd**, const char*)); | |
187 | pam_handle_t *pamh = NULL; | |
188 | #endif | |
189 | ||
190 | char *pid_file = NULL; | |
191 | ||
192 | /* | |
193 | * Limit number of pathnames that glob can return. | |
194 | * A limit of 0 indicates the number of pathnames is unlimited. | |
195 | */ | |
196 | #define MAXGLOBARGS 16384 | |
197 | # | |
b7080c8e A |
198 | |
199 | /* | |
200 | * Timeout intervals for retrying connections | |
201 | * to hosts that don't accept PORT cmds. This | |
202 | * is a kludge, but given the problems with TCP... | |
203 | */ | |
204 | #define SWAITMAX 90 /* wait at most 90 seconds */ | |
205 | #define SWAITINT 5 /* interval between retries */ | |
206 | ||
207 | int swaitmax = SWAITMAX; | |
208 | int swaitint = SWAITINT; | |
209 | ||
210 | #ifdef SETPROCTITLE | |
3e383549 | 211 | #ifdef OLD_SETPROCTITLE |
b7080c8e A |
212 | char **Argv = NULL; /* pointer to argument vector */ |
213 | char *LastArgv = NULL; /* end of argv */ | |
3e383549 | 214 | #endif /* OLD_SETPROCTITLE */ |
b7080c8e A |
215 | char proctitle[LINE_MAX]; /* initial part of title */ |
216 | #endif /* SETPROCTITLE */ | |
217 | ||
3e383549 A |
218 | #ifdef SKEY |
219 | int pwok = 0; | |
220 | #endif | |
221 | ||
b7080c8e A |
222 | #define LOGCMD(cmd, file) \ |
223 | if (logging > 1) \ | |
224 | syslog(LOG_INFO,"%s %s%s", cmd, \ | |
225 | *(file) == '/' ? "" : curdir(), file); | |
226 | #define LOGCMD2(cmd, file1, file2) \ | |
227 | if (logging > 1) \ | |
228 | syslog(LOG_INFO,"%s %s%s %s%s", cmd, \ | |
229 | *(file1) == '/' ? "" : curdir(), file1, \ | |
230 | *(file2) == '/' ? "" : curdir(), file2); | |
231 | #define LOGBYTES(cmd, file, cnt) \ | |
232 | if (logging > 1) { \ | |
233 | if (cnt == (off_t)-1) \ | |
234 | syslog(LOG_INFO,"%s %s%s", cmd, \ | |
235 | *(file) == '/' ? "" : curdir(), file); \ | |
236 | else \ | |
237 | syslog(LOG_INFO, "%s %s%s = %qd bytes", \ | |
238 | cmd, (*(file) == '/') ? "" : curdir(), file, cnt); \ | |
239 | } | |
240 | ||
3e383549 A |
241 | #ifdef VIRTUAL_HOSTING |
242 | static void inithosts __P((void)); | |
243 | static void selecthost __P((union sockunion *)); | |
244 | #endif | |
b7080c8e A |
245 | static void ack __P((char *)); |
246 | static void myoob __P((int)); | |
3e383549 | 247 | static int checkuser __P((char *, char *, int)); |
b7080c8e | 248 | static FILE *dataconn __P((char *, off_t, char *)); |
3e383549 | 249 | static void dolog __P((struct sockaddr *)); |
b7080c8e A |
250 | static char *curdir __P((void)); |
251 | static void end_login __P((void)); | |
252 | static FILE *getdatasock __P((char *)); | |
253 | static char *gunique __P((char *)); | |
254 | static void lostconn __P((int)); | |
255 | static int receive_data __P((FILE *, FILE *)); | |
3e383549 | 256 | static void send_data __P((FILE *, FILE *, off_t, off_t, int)); |
b7080c8e A |
257 | static struct passwd * |
258 | sgetpwnam __P((char *)); | |
259 | static char *sgetsave __P((char *)); | |
3e383549 A |
260 | static void reapchild __P((int)); |
261 | static void logxfer __P((char *, long, long)); | |
b7080c8e A |
262 | |
263 | static char * | |
264 | curdir() | |
265 | { | |
266 | static char path[MAXPATHLEN+1+1]; /* path + '/' + '\0' */ | |
267 | ||
268 | if (getcwd(path, sizeof(path)-2) == NULL) | |
269 | return (""); | |
270 | if (path[1] != '\0') /* special case for root dir. */ | |
271 | strcat(path, "/"); | |
272 | /* For guest account, skip / since it's chrooted */ | |
273 | return (guest ? path+1 : path); | |
274 | } | |
275 | ||
276 | int | |
277 | main(argc, argv, envp) | |
278 | int argc; | |
279 | char *argv[]; | |
280 | char **envp; | |
281 | { | |
282 | int addrlen, ch, on = 1, tos; | |
283 | char *cp, line[LINE_MAX]; | |
284 | FILE *fd; | |
3e383549 A |
285 | int error; |
286 | char *bindname = NULL; | |
287 | int family = AF_UNSPEC; | |
288 | int enable_v4 = 0; | |
b7080c8e | 289 | |
3e383549 A |
290 | tzset(); /* in case no timezone database in ~ftp */ |
291 | ||
292 | #ifdef OLD_SETPROCTITLE | |
b7080c8e A |
293 | /* |
294 | * Save start and extent of argv for setproctitle. | |
295 | */ | |
296 | Argv = argv; | |
297 | while (*envp) | |
298 | envp++; | |
299 | LastArgv = envp[-1] + strlen(envp[-1]); | |
3e383549 A |
300 | #endif /* OLD_SETPROCTITLE */ |
301 | ||
b7080c8e | 302 | |
3e383549 | 303 | while ((ch = getopt(argc, argv, "AdlDESURrt:T:u:va:p:46")) != -1) { |
b7080c8e | 304 | switch (ch) { |
3e383549 A |
305 | case 'D': |
306 | daemon_mode++; | |
307 | break; | |
308 | ||
b7080c8e | 309 | case 'd': |
3e383549 A |
310 | debug++; |
311 | break; | |
312 | ||
313 | case 'E': | |
314 | noepsv = 1; | |
b7080c8e A |
315 | break; |
316 | ||
317 | case 'l': | |
318 | logging++; /* > 1 == extra logging */ | |
319 | break; | |
320 | ||
3e383549 A |
321 | case 'r': |
322 | readonly = 1; | |
323 | break; | |
324 | ||
325 | case 'R': | |
326 | paranoid = 0; | |
327 | break; | |
328 | ||
329 | case 'S': | |
330 | stats++; | |
b7080c8e A |
331 | break; |
332 | ||
333 | case 'T': | |
334 | maxtimeout = atoi(optarg); | |
335 | if (timeout > maxtimeout) | |
336 | timeout = maxtimeout; | |
337 | break; | |
338 | ||
3e383549 A |
339 | case 't': |
340 | timeout = atoi(optarg); | |
341 | if (maxtimeout < timeout) | |
342 | maxtimeout = timeout; | |
343 | break; | |
344 | ||
345 | case 'U': | |
346 | restricted_data_ports = 0; | |
347 | break; | |
348 | ||
349 | case 'a': | |
350 | bindname = optarg; | |
351 | break; | |
352 | ||
353 | case 'p': | |
354 | pid_file = optarg; | |
355 | break; | |
356 | ||
b7080c8e A |
357 | case 'u': |
358 | { | |
359 | long val = 0; | |
360 | ||
361 | val = strtol(optarg, &optarg, 8); | |
362 | if (*optarg != '\0' || val < 0) | |
363 | warnx("bad value for -u"); | |
364 | else | |
365 | defumask = val; | |
366 | break; | |
367 | } | |
3e383549 A |
368 | case 'A': |
369 | anon_only = 1; | |
370 | break; | |
b7080c8e A |
371 | |
372 | case 'v': | |
373 | debug = 1; | |
374 | break; | |
375 | ||
3e383549 A |
376 | case '4': |
377 | enable_v4 = 1; | |
378 | if (family == AF_UNSPEC) | |
379 | family = AF_INET; | |
380 | break; | |
381 | ||
382 | case '6': | |
383 | family = AF_INET6; | |
384 | break; | |
385 | ||
b7080c8e A |
386 | default: |
387 | warnx("unknown flag -%c ignored", optopt); | |
388 | break; | |
389 | } | |
390 | } | |
3e383549 A |
391 | |
392 | #ifdef VIRTUAL_HOSTING | |
393 | inithosts(); | |
394 | #endif | |
b7080c8e | 395 | (void) freopen(_PATH_DEVNULL, "w", stderr); |
3e383549 A |
396 | |
397 | /* | |
398 | * LOG_NDELAY sets up the logging connection immediately, | |
399 | * necessary for anonymous ftp's that chroot and can't do it later. | |
400 | */ | |
401 | openlog("ftpd", LOG_PID | LOG_NDELAY, LOG_FTP); | |
402 | ||
403 | if (daemon_mode) { | |
404 | int ctl_sock, fd; | |
405 | struct addrinfo hints, *res; | |
406 | ||
407 | /* | |
408 | * Detach from parent. | |
409 | */ | |
410 | if (daemon(1, 1) < 0) { | |
411 | syslog(LOG_ERR, "failed to become a daemon"); | |
412 | exit(1); | |
413 | } | |
414 | (void) signal(SIGCHLD, reapchild); | |
415 | /* init bind_sa */ | |
416 | memset(&hints, 0, sizeof(hints)); | |
417 | ||
418 | hints.ai_family = family == AF_UNSPEC ? AF_INET : family; | |
419 | hints.ai_socktype = SOCK_STREAM; | |
420 | hints.ai_protocol = 0; | |
421 | hints.ai_flags = AI_PASSIVE; | |
422 | error = getaddrinfo(bindname, "ftp", &hints, &res); | |
423 | if (error) { | |
424 | if (family == AF_UNSPEC) { | |
425 | hints.ai_family = AF_UNSPEC; | |
426 | error = getaddrinfo(bindname, "ftp", &hints, | |
427 | &res); | |
428 | } | |
429 | } | |
430 | if (error) { | |
431 | syslog(LOG_ERR, "%s", gai_strerror(error)); | |
432 | if (error == EAI_SYSTEM) | |
433 | syslog(LOG_ERR, "%s", strerror(errno)); | |
434 | exit(1); | |
435 | } | |
436 | if (res->ai_addr == NULL) { | |
437 | syslog(LOG_ERR, "-a %s: getaddrinfo failed", hostname); | |
438 | exit(1); | |
439 | } else | |
440 | family = res->ai_addr->sa_family; | |
441 | /* | |
442 | * Open a socket, bind it to the FTP port, and start | |
443 | * listening. | |
444 | */ | |
445 | ctl_sock = socket(family, SOCK_STREAM, 0); | |
446 | if (ctl_sock < 0) { | |
447 | syslog(LOG_ERR, "control socket: %m"); | |
448 | exit(1); | |
449 | } | |
450 | if (setsockopt(ctl_sock, SOL_SOCKET, SO_REUSEADDR, | |
451 | (char *)&on, sizeof(on)) < 0) | |
452 | syslog(LOG_ERR, "control setsockopt: %m"); | |
453 | #ifdef IPV6_BINDV6ONLY | |
454 | if (family == AF_INET6 && enable_v4 == 0) { | |
455 | if (setsockopt(ctl_sock, IPPROTO_IPV6, IPV6_BINDV6ONLY, | |
456 | (char *)&on, sizeof (on)) < 0) | |
457 | syslog(LOG_ERR, | |
458 | "control setsockopt(IPV6_BINDV6ONLY): %m"); | |
459 | } | |
460 | #endif /* IPV6_BINDV6ONLY */ | |
461 | memcpy(&server_addr, res->ai_addr, res->ai_addr->sa_len); | |
462 | if (bind(ctl_sock, (struct sockaddr *)&server_addr, | |
463 | server_addr.su_len) < 0) { | |
464 | syslog(LOG_ERR, "control bind: %m"); | |
465 | exit(1); | |
466 | } | |
467 | if (listen(ctl_sock, 32) < 0) { | |
468 | syslog(LOG_ERR, "control listen: %m"); | |
469 | exit(1); | |
470 | } | |
471 | /* | |
472 | * Atomically write process ID | |
473 | */ | |
474 | if (pid_file) | |
475 | { | |
476 | int fd; | |
477 | char buf[20]; | |
478 | ||
479 | fd = open(pid_file, O_CREAT | O_WRONLY | O_TRUNC | |
480 | | O_NONBLOCK | O_EXLOCK, 0644); | |
481 | if (fd < 0) { | |
482 | if (errno == EAGAIN) | |
483 | errx(1, "%s: file locked", pid_file); | |
484 | else | |
485 | err(1, "%s", pid_file); | |
486 | } | |
487 | snprintf(buf, sizeof(buf), | |
488 | "%lu\n", (unsigned long) getpid()); | |
489 | if (write(fd, buf, strlen(buf)) < 0) | |
490 | err(1, "%s: write", pid_file); | |
491 | /* Leave the pid file open and locked */ | |
492 | } | |
493 | /* | |
494 | * Loop forever accepting connection requests and forking off | |
495 | * children to handle them. | |
496 | */ | |
497 | while (1) { | |
498 | addrlen = server_addr.su_len; | |
499 | fd = accept(ctl_sock, (struct sockaddr *)&his_addr, &addrlen); | |
500 | if (fork() == 0) { | |
501 | /* child */ | |
502 | (void) dup2(fd, 0); | |
503 | (void) dup2(fd, 1); | |
504 | close(ctl_sock); | |
505 | break; | |
506 | } | |
507 | close(fd); | |
508 | } | |
509 | } else { | |
510 | addrlen = sizeof(his_addr); | |
511 | if (getpeername(0, (struct sockaddr *)&his_addr, &addrlen) < 0) { | |
512 | syslog(LOG_ERR, "getpeername (%s): %m",argv[0]); | |
513 | exit(1); | |
514 | } | |
515 | } | |
516 | ||
b7080c8e | 517 | (void) signal(SIGCHLD, SIG_IGN); |
3e383549 A |
518 | (void) signal(SIGPIPE, lostconn); |
519 | if (signal(SIGURG, myoob) == SIG_ERR) | |
b7080c8e A |
520 | syslog(LOG_ERR, "signal: %m"); |
521 | ||
3e383549 A |
522 | addrlen = sizeof(ctrl_addr); |
523 | if (getsockname(0, (struct sockaddr *)&ctrl_addr, &addrlen) < 0) { | |
524 | syslog(LOG_ERR, "getsockname (%s): %m",argv[0]); | |
525 | exit(1); | |
526 | } | |
527 | #ifdef VIRTUAL_HOSTING | |
528 | /* select our identity from virtual host table */ | |
529 | selecthost(&ctrl_addr); | |
530 | #endif | |
531 | #ifdef IP_TOS | |
532 | if (ctrl_addr.su_family == AF_INET) | |
533 | { | |
534 | tos = IPTOS_LOWDELAY; | |
535 | if (setsockopt(0, IPPROTO_IP, IP_TOS, (char *)&tos, sizeof(int)) < 0) | |
536 | syslog(LOG_WARNING, "setsockopt (IP_TOS): %m"); | |
537 | } | |
538 | #endif | |
539 | /* | |
540 | * Disable Nagle on the control channel so that we don't have to wait | |
541 | * for peer's ACK before issuing our next reply. | |
542 | */ | |
543 | if (setsockopt(0, IPPROTO_TCP, TCP_NODELAY, &on, sizeof(on)) < 0) | |
544 | syslog(LOG_WARNING, "control setsockopt TCP_NODELAY: %m"); | |
545 | ||
546 | data_source.su_port = htons(ntohs(ctrl_addr.su_port) - 1); | |
547 | ||
548 | /* set this here so klogin can use it... */ | |
549 | (void)snprintf(ttyline, sizeof(ttyline), "ftp%d", getpid()); | |
550 | ||
b7080c8e A |
551 | /* Try to handle urgent data inline */ |
552 | #ifdef SO_OOBINLINE | |
553 | if (setsockopt(0, SOL_SOCKET, SO_OOBINLINE, (char *)&on, sizeof(on)) < 0) | |
554 | syslog(LOG_ERR, "setsockopt: %m"); | |
555 | #endif | |
556 | ||
557 | #ifdef F_SETOWN | |
558 | if (fcntl(fileno(stdin), F_SETOWN, getpid()) == -1) | |
559 | syslog(LOG_ERR, "fcntl F_SETOWN: %m"); | |
560 | #endif | |
3e383549 | 561 | dolog((struct sockaddr *)&his_addr); |
b7080c8e A |
562 | /* |
563 | * Set up default state | |
564 | */ | |
565 | data = -1; | |
566 | type = TYPE_A; | |
567 | form = FORM_N; | |
568 | stru = STRU_F; | |
569 | mode = MODE_S; | |
570 | tmpline[0] = '\0'; | |
571 | ||
572 | /* If logins are disabled, print out the message. */ | |
573 | if ((fd = fopen(_PATH_NOLOGIN,"r")) != NULL) { | |
574 | while (fgets(line, sizeof(line), fd) != NULL) { | |
575 | if ((cp = strchr(line, '\n')) != NULL) | |
576 | *cp = '\0'; | |
577 | lreply(530, "%s", line); | |
578 | } | |
579 | (void) fflush(stdout); | |
580 | (void) fclose(fd); | |
581 | reply(530, "System not available."); | |
582 | exit(0); | |
583 | } | |
3e383549 A |
584 | #ifdef VIRTUAL_HOSTING |
585 | if ((fd = fopen(thishost->welcome, "r")) != NULL) { | |
586 | #else | |
b7080c8e | 587 | if ((fd = fopen(_PATH_FTPWELCOME, "r")) != NULL) { |
3e383549 | 588 | #endif |
b7080c8e A |
589 | while (fgets(line, sizeof(line), fd) != NULL) { |
590 | if ((cp = strchr(line, '\n')) != NULL) | |
591 | *cp = '\0'; | |
592 | lreply(220, "%s", line); | |
593 | } | |
594 | (void) fflush(stdout); | |
595 | (void) fclose(fd); | |
596 | /* reply(220,) must follow */ | |
597 | } | |
3e383549 A |
598 | #ifndef VIRTUAL_HOSTING |
599 | if ((hostname = malloc(MAXHOSTNAMELEN)) == NULL) | |
600 | fatal("Ran out of memory."); | |
601 | (void) gethostname(hostname, MAXHOSTNAMELEN - 1); | |
602 | hostname[MAXHOSTNAMELEN - 1] = '\0'; | |
603 | #endif | |
b7080c8e A |
604 | reply(220, "%s FTP server (%s) ready.", hostname, version); |
605 | (void) setjmp(errcatch); | |
606 | for (;;) | |
607 | (void) yyparse(); | |
608 | /* NOTREACHED */ | |
609 | } | |
610 | ||
611 | static void | |
612 | lostconn(signo) | |
613 | int signo; | |
614 | { | |
615 | ||
616 | if (debug) | |
617 | syslog(LOG_DEBUG, "lost connection"); | |
3e383549 | 618 | dologout(1); |
b7080c8e A |
619 | } |
620 | ||
3e383549 A |
621 | #ifdef VIRTUAL_HOSTING |
622 | /* | |
623 | * read in virtual host tables (if they exist) | |
624 | */ | |
625 | ||
626 | static void | |
627 | inithosts() | |
628 | { | |
629 | FILE *fp; | |
630 | char *cp; | |
631 | struct ftphost *hrp, *lhrp; | |
632 | char line[1024]; | |
633 | struct addrinfo hints, *res, *ai; | |
634 | ||
635 | /* | |
636 | * Fill in the default host information | |
637 | */ | |
638 | if (gethostname(line, sizeof(line)) < 0) | |
639 | line[0] = '\0'; | |
640 | if ((hrp = malloc(sizeof(struct ftphost))) == NULL || | |
641 | (hrp->hostname = strdup(line)) == NULL) | |
642 | fatal("Ran out of memory."); | |
643 | hrp->hostinfo = NULL; | |
644 | ||
645 | memset(&hints, 0, sizeof(hints)); | |
646 | hints.ai_flags = AI_CANONNAME; | |
647 | hints.ai_family = AF_UNSPEC; | |
648 | getaddrinfo(hrp->hostname, NULL, &hints, &res); | |
649 | if (res) | |
650 | hrp->hostinfo = res; | |
651 | hrp->statfile = _PATH_FTPDSTATFILE; | |
652 | hrp->welcome = _PATH_FTPWELCOME; | |
653 | hrp->loginmsg = _PATH_FTPLOGINMESG; | |
654 | hrp->anonuser = "ftp"; | |
655 | hrp->next = NULL; | |
656 | thishost = firsthost = lhrp = hrp; | |
657 | if ((fp = fopen(_PATH_FTPHOSTS, "r")) != NULL) { | |
658 | int addrsize, error, gothost; | |
659 | void *addr; | |
660 | struct hostent *hp; | |
661 | ||
662 | while (fgets(line, sizeof(line), fp) != NULL) { | |
663 | int i, hp_error; | |
664 | ||
665 | if ((cp = strchr(line, '\n')) == NULL) { | |
666 | /* ignore long lines */ | |
667 | while (fgets(line, sizeof(line), fp) != NULL && | |
668 | strchr(line, '\n') == NULL) | |
669 | ; | |
670 | continue; | |
671 | } | |
672 | *cp = '\0'; | |
673 | cp = strtok(line, " \t"); | |
674 | /* skip comments and empty lines */ | |
675 | if (cp == NULL || line[0] == '#') | |
676 | continue; | |
677 | ||
678 | hints.ai_flags = 0; | |
679 | hints.ai_family = AF_UNSPEC; | |
680 | hints.ai_flags = AI_PASSIVE; | |
681 | error = getaddrinfo(cp, NULL, &hints, &res); | |
682 | if (error != NULL) | |
683 | continue; | |
684 | for (ai = res; ai != NULL && ai->ai_addr != NULL; | |
685 | ai = ai->ai_next) { | |
686 | ||
687 | gothost = 0; | |
688 | for (hrp = firsthost; hrp != NULL; hrp = hrp->next) { | |
689 | struct addrinfo *hi; | |
690 | ||
691 | for (hi = hrp->hostinfo; hi != NULL; | |
692 | hi = hi->ai_next) | |
693 | if (hi->ai_addrlen == ai->ai_addrlen && | |
694 | memcmp(hi->ai_addr, | |
695 | ai->ai_addr, | |
696 | ai->ai_addr->sa_len) == 0) { | |
697 | gothost++; | |
698 | break; | |
699 | } | |
700 | if (gothost) | |
701 | break; | |
702 | } | |
703 | if (hrp == NULL) { | |
704 | if ((hrp = malloc(sizeof(struct ftphost))) == NULL) | |
705 | continue; | |
706 | /* defaults */ | |
707 | hrp->statfile = _PATH_FTPDSTATFILE; | |
708 | hrp->welcome = _PATH_FTPWELCOME; | |
709 | hrp->loginmsg = _PATH_FTPLOGINMESG; | |
710 | hrp->anonuser = "ftp"; | |
711 | hrp->next = NULL; | |
712 | lhrp->next = hrp; | |
713 | lhrp = hrp; | |
714 | } | |
715 | hrp->hostinfo = res; | |
716 | ||
717 | /* | |
718 | * determine hostname to use. | |
719 | * force defined name if there is a valid alias | |
720 | * otherwise fallback to primary hostname | |
721 | */ | |
722 | /* XXX: getaddrinfo() can't do alias check */ | |
723 | switch(hrp->hostinfo->ai_family) { | |
724 | case AF_INET: | |
725 | addr = &((struct sockaddr_in *)&hrp->hostinfo->ai_addr)->sin_addr; | |
726 | addrsize = sizeof(struct sockaddr_in); | |
727 | break; | |
728 | case AF_INET6: | |
729 | addr = &((struct sockaddr_in6 *)&hrp->hostinfo->ai_addr)->sin6_addr; | |
730 | addrsize = sizeof(struct sockaddr_in6); | |
731 | break; | |
732 | default: | |
733 | /* should not reach here */ | |
734 | if (hrp->hostinfo != NULL) | |
735 | freeaddrinfo(hrp->hostinfo); | |
736 | free(hrp); | |
737 | continue; | |
738 | /* NOTREACHED */ | |
739 | } | |
740 | if ((hp = getipnodebyaddr((char*)addr, addrsize, | |
741 | hrp->hostinfo->ai_family, | |
742 | &hp_error)) != NULL) { | |
743 | if (strcmp(cp, hp->h_name) != 0) { | |
744 | if (hp->h_aliases == NULL) | |
745 | cp = hp->h_name; | |
746 | else { | |
747 | i = 0; | |
748 | while (hp->h_aliases[i] && | |
749 | strcmp(cp, hp->h_aliases[i]) != 0) | |
750 | ++i; | |
751 | if (hp->h_aliases[i] == NULL) | |
752 | cp = hp->h_name; | |
753 | } | |
754 | } | |
755 | } | |
756 | hrp->hostname = strdup(cp); | |
757 | freehostent(hp); | |
758 | /* ok, now we now peel off the rest */ | |
759 | i = 0; | |
760 | while (i < 4 && (cp = strtok(NULL, " \t")) != NULL) { | |
761 | if (*cp != '-' && (cp = strdup(cp)) != NULL) { | |
762 | switch (i) { | |
763 | case 0: /* anon user permissions */ | |
764 | hrp->anonuser = cp; | |
765 | break; | |
766 | case 1: /* statistics file */ | |
767 | hrp->statfile = cp; | |
768 | break; | |
769 | case 2: /* welcome message */ | |
770 | hrp->welcome = cp; | |
771 | break; | |
772 | case 3: /* login message */ | |
773 | hrp->loginmsg = cp; | |
774 | break; | |
775 | } | |
776 | } | |
777 | ++i; | |
778 | } | |
779 | /* XXX: re-initialization for getaddrinfo() loop */ | |
780 | cp = strtok(line, " \t"); | |
781 | } | |
782 | } | |
783 | (void) fclose(fp); | |
784 | } | |
785 | } | |
786 | ||
787 | static void | |
788 | selecthost(su) | |
789 | union sockunion *su; | |
790 | { | |
791 | struct ftphost *hrp; | |
792 | u_int16_t port; | |
793 | #ifdef INET6 | |
794 | struct in6_addr *mapped_in6 = NULL; | |
795 | #endif | |
796 | struct addrinfo *hi; | |
797 | ||
798 | #ifdef INET6 | |
799 | /* | |
800 | * XXX IPv4 mapped IPv6 addr consideraton, | |
801 | * specified in rfc2373. | |
802 | */ | |
803 | if (su->su_family == AF_INET6 && | |
804 | IN6_IS_ADDR_V4MAPPED(&su->su_sin6.sin6_addr)) | |
805 | mapped_in6 = &su->su_sin6.sin6_addr; | |
806 | #endif | |
807 | ||
808 | hrp = thishost = firsthost; /* default */ | |
809 | port = su->su_port; | |
810 | su->su_port = 0; | |
811 | while (hrp != NULL) { | |
812 | for (hi = hrp->hostinfo; hi != NULL; hi = hi->ai_next) { | |
813 | if (memcmp(su, hi->ai_addr, hi->ai_addrlen) == 0) { | |
814 | thishost = hrp; | |
815 | break; | |
816 | } | |
817 | #ifdef INET6 | |
818 | /* XXX IPv4 mapped IPv6 addr consideraton */ | |
819 | if (hi->ai_addr->sa_family == AF_INET && mapped_in6 != NULL && | |
820 | (memcmp(&mapped_in6->s6_addr[12], | |
821 | &((struct sockaddr_in *)hi->ai_addr)->sin_addr, | |
822 | sizeof(struct in_addr)) == 0)) { | |
823 | thishost = hrp; | |
824 | break; | |
825 | } | |
826 | #endif | |
827 | } | |
828 | hrp = hrp->next; | |
829 | } | |
830 | su->su_port = port; | |
831 | /* setup static variables as appropriate */ | |
832 | hostname = thishost->hostname; | |
833 | ftpuser = thishost->anonuser; | |
834 | } | |
835 | #endif | |
b7080c8e A |
836 | |
837 | /* | |
838 | * Helper function for sgetpwnam(). | |
839 | */ | |
840 | static char * | |
841 | sgetsave(s) | |
842 | char *s; | |
843 | { | |
844 | char *new = malloc((unsigned) strlen(s) + 1); | |
845 | ||
846 | if (new == NULL) { | |
847 | perror_reply(421, "Local resource failure: malloc"); | |
848 | dologout(1); | |
849 | /* NOTREACHED */ | |
850 | } | |
851 | (void) strcpy(new, s); | |
852 | return (new); | |
853 | } | |
854 | ||
855 | /* | |
856 | * Save the result of a getpwnam. Used for USER command, since | |
857 | * the data returned must not be clobbered by any other command | |
858 | * (e.g., globbing). | |
859 | */ | |
860 | static struct passwd * | |
861 | sgetpwnam(name) | |
862 | char *name; | |
863 | { | |
864 | static struct passwd save; | |
865 | struct passwd *p; | |
866 | ||
867 | if ((p = getpwnam(name)) == NULL) | |
868 | return (p); | |
869 | if (save.pw_name) { | |
870 | free(save.pw_name); | |
871 | free(save.pw_passwd); | |
872 | free(save.pw_gecos); | |
873 | free(save.pw_dir); | |
874 | free(save.pw_shell); | |
875 | } | |
876 | save = *p; | |
877 | save.pw_name = sgetsave(p->pw_name); | |
878 | save.pw_passwd = sgetsave(p->pw_passwd); | |
879 | save.pw_gecos = sgetsave(p->pw_gecos); | |
880 | save.pw_dir = sgetsave(p->pw_dir); | |
881 | save.pw_shell = sgetsave(p->pw_shell); | |
882 | return (&save); | |
883 | } | |
884 | ||
885 | static int login_attempts; /* number of failed login attempts */ | |
886 | static int askpasswd; /* had user command, ask for passwd */ | |
3e383549 | 887 | static char curname[MAXLOGNAME]; /* current USER name */ |
b7080c8e A |
888 | |
889 | /* | |
890 | * USER command. | |
891 | * Sets global passwd pointer pw if named account exists and is acceptable; | |
892 | * sets askpasswd if a PASS command is expected. If logged in previously, | |
893 | * need to reset state. If name is "ftp" or "anonymous", the name is not in | |
894 | * _PATH_FTPUSERS, and ftp account exists, set guest and pw, then just return. | |
895 | * If account doesn't exist, ask for passwd anyway. Otherwise, check user | |
896 | * requesting login privileges. Disallow anyone who does not have a standard | |
897 | * shell as returned by getusershell(). Disallow anyone mentioned in the file | |
898 | * _PATH_FTPUSERS to allow people such as root and uucp to be avoided. | |
899 | */ | |
900 | void | |
901 | user(name) | |
902 | char *name; | |
903 | { | |
904 | char *cp, *shell; | |
905 | ||
906 | if (logged_in) { | |
907 | if (guest) { | |
908 | reply(530, "Can't change user from guest login."); | |
909 | return; | |
3e383549 A |
910 | } else if (dochroot) { |
911 | reply(530, "Can't change user from chroot user."); | |
912 | return; | |
b7080c8e A |
913 | } |
914 | end_login(); | |
915 | } | |
916 | ||
917 | guest = 0; | |
918 | if (strcmp(name, "ftp") == 0 || strcmp(name, "anonymous") == 0) { | |
3e383549 A |
919 | #if !defined(_PATH_FTPUSERS) |
920 | #define _PATH_FTPUSERS "/etc/ftpusers" | |
921 | #endif | |
922 | if (checkuser(_PATH_FTPUSERS, "ftp", 0) || | |
923 | checkuser(_PATH_FTPUSERS, "anonymous", 0)) | |
b7080c8e | 924 | reply(530, "User %s access denied.", name); |
3e383549 A |
925 | #ifdef VIRTUAL_HOSTING |
926 | else if ((pw = sgetpwnam(thishost->anonuser)) != NULL) { | |
927 | #else | |
b7080c8e | 928 | else if ((pw = sgetpwnam("ftp")) != NULL) { |
3e383549 | 929 | #endif |
b7080c8e A |
930 | guest = 1; |
931 | askpasswd = 1; | |
932 | reply(331, | |
3e383549 | 933 | "Guest login ok, send your email address as password."); |
b7080c8e A |
934 | } else |
935 | reply(530, "User %s unknown.", name); | |
936 | if (!askpasswd && logging) | |
937 | syslog(LOG_NOTICE, | |
938 | "ANONYMOUS FTP LOGIN REFUSED FROM %s", remotehost); | |
939 | return; | |
940 | } | |
3e383549 A |
941 | if (anon_only != 0) { |
942 | reply(530, "Sorry, only anonymous ftp allowed."); | |
943 | return; | |
944 | } | |
945 | ||
946 | if ((pw = sgetpwnam(name))) { | |
b7080c8e A |
947 | if ((shell = pw->pw_shell) == NULL || *shell == 0) |
948 | shell = _PATH_BSHELL; | |
949 | while ((cp = getusershell()) != NULL) | |
950 | if (strcmp(cp, shell) == 0) | |
951 | break; | |
952 | endusershell(); | |
953 | ||
3e383549 | 954 | if (cp == NULL || checkuser(_PATH_FTPUSERS, name, 1)) { |
b7080c8e A |
955 | reply(530, "User %s access denied.", name); |
956 | if (logging) | |
957 | syslog(LOG_NOTICE, | |
958 | "FTP LOGIN REFUSED FROM %s, %s", | |
959 | remotehost, name); | |
960 | pw = (struct passwd *) NULL; | |
961 | return; | |
962 | } | |
963 | } | |
964 | if (logging) | |
965 | strncpy(curname, name, sizeof(curname)-1); | |
3e383549 A |
966 | #ifdef SKEY |
967 | pwok = skeyaccess(name, NULL, remotehost, remotehost); | |
968 | reply(331, "%s", skey_challenge(name, pw, pwok)); | |
969 | #else | |
b7080c8e | 970 | reply(331, "Password required for %s.", name); |
3e383549 | 971 | #endif |
b7080c8e A |
972 | askpasswd = 1; |
973 | /* | |
974 | * Delay before reading passwd after first failed | |
975 | * attempt to slow down passwd-guessing programs. | |
976 | */ | |
977 | if (login_attempts) | |
978 | sleep((unsigned) login_attempts); | |
979 | } | |
980 | ||
981 | /* | |
3e383549 | 982 | * Check if a user is in the file "fname" |
b7080c8e A |
983 | */ |
984 | static int | |
3e383549 A |
985 | checkuser(fname, name, pwset) |
986 | char *fname; | |
b7080c8e | 987 | char *name; |
3e383549 | 988 | int pwset; |
b7080c8e A |
989 | { |
990 | FILE *fd; | |
991 | int found = 0; | |
992 | char *p, line[BUFSIZ]; | |
993 | ||
3e383549 A |
994 | if ((fd = fopen(fname, "r")) != NULL) { |
995 | while (!found && fgets(line, sizeof(line), fd) != NULL) | |
b7080c8e A |
996 | if ((p = strchr(line, '\n')) != NULL) { |
997 | *p = '\0'; | |
998 | if (line[0] == '#') | |
999 | continue; | |
3e383549 A |
1000 | /* |
1001 | * if first chr is '@', check group membership | |
1002 | */ | |
1003 | if (line[0] == '@') { | |
1004 | int i = 0; | |
1005 | struct group *grp; | |
1006 | ||
1007 | if ((grp = getgrnam(line+1)) == NULL) | |
1008 | continue; | |
1009 | /* | |
1010 | * Check user's default group | |
1011 | */ | |
1012 | if (pwset && grp->gr_gid == pw->pw_gid) | |
1013 | found = 1; | |
1014 | /* | |
1015 | * Check supplementary groups | |
1016 | */ | |
1017 | while (!found && grp->gr_mem[i]) | |
1018 | found = strcmp(name, | |
1019 | grp->gr_mem[i++]) | |
1020 | == 0; | |
b7080c8e | 1021 | } |
3e383549 A |
1022 | /* |
1023 | * Otherwise, just check for username match | |
1024 | */ | |
1025 | else | |
1026 | found = strcmp(line, name) == 0; | |
b7080c8e A |
1027 | } |
1028 | (void) fclose(fd); | |
1029 | } | |
1030 | return (found); | |
1031 | } | |
1032 | ||
1033 | /* | |
1034 | * Terminate login as previous user, if any, resetting state; | |
1035 | * used when USER command is given or login fails. | |
1036 | */ | |
1037 | static void | |
1038 | end_login() | |
1039 | { | |
3e383549 A |
1040 | #ifdef USE_PAM |
1041 | int e; | |
1042 | #endif | |
b7080c8e A |
1043 | |
1044 | (void) seteuid((uid_t)0); | |
1045 | if (logged_in) | |
3e383549 | 1046 | ftpd_logwtmp(ttyline, "", ""); |
b7080c8e | 1047 | pw = NULL; |
3e383549 A |
1048 | #ifdef LOGIN_CAP |
1049 | setusercontext(NULL, getpwuid(0), (uid_t)0, | |
1050 | LOGIN_SETPRIORITY|LOGIN_SETRESOURCES|LOGIN_SETUMASK); | |
1051 | #endif | |
1052 | #ifdef USE_PAM | |
1053 | if ((e = pam_setcred(pamh, PAM_DELETE_CRED)) != PAM_SUCCESS) | |
1054 | syslog(LOG_ERR, "pam_setcred: %s", pam_strerror(pamh, e)); | |
1055 | if ((e = pam_close_session(pamh,0)) != PAM_SUCCESS) | |
1056 | syslog(LOG_ERR, "pam_close_session: %s", pam_strerror(pamh, e)); | |
1057 | if ((e = pam_end(pamh, e)) != PAM_SUCCESS) | |
1058 | syslog(LOG_ERR, "pam_end: %s", pam_strerror(pamh, e)); | |
1059 | pamh = NULL; | |
1060 | #endif | |
b7080c8e A |
1061 | logged_in = 0; |
1062 | guest = 0; | |
3e383549 A |
1063 | dochroot = 0; |
1064 | } | |
1065 | ||
1066 | #ifdef USE_PAM | |
1067 | ||
1068 | /* | |
1069 | * the following code is stolen from imap-uw PAM authentication module and | |
1070 | * login.c | |
1071 | */ | |
1072 | #define COPY_STRING(s) (s ? strdup(s) : NULL) | |
1073 | ||
1074 | struct cred_t { | |
1075 | const char *uname; /* user name */ | |
1076 | const char *pass; /* password */ | |
1077 | }; | |
1078 | typedef struct cred_t cred_t; | |
1079 | ||
1080 | static int | |
1081 | auth_conv(int num_msg, const struct pam_message **msg, | |
1082 | struct pam_response **resp, void *appdata) | |
1083 | { | |
1084 | int i; | |
1085 | cred_t *cred = (cred_t *) appdata; | |
1086 | struct pam_response *reply = | |
1087 | malloc(sizeof(struct pam_response) * num_msg); | |
1088 | ||
1089 | for (i = 0; i < num_msg; i++) { | |
1090 | switch (msg[i]->msg_style) { | |
1091 | case PAM_PROMPT_ECHO_ON: /* assume want user name */ | |
1092 | reply[i].resp_retcode = PAM_SUCCESS; | |
1093 | reply[i].resp = COPY_STRING(cred->uname); | |
1094 | /* PAM frees resp. */ | |
1095 | break; | |
1096 | case PAM_PROMPT_ECHO_OFF: /* assume want password */ | |
1097 | reply[i].resp_retcode = PAM_SUCCESS; | |
1098 | reply[i].resp = COPY_STRING(cred->pass); | |
1099 | /* PAM frees resp. */ | |
1100 | break; | |
1101 | case PAM_TEXT_INFO: | |
1102 | case PAM_ERROR_MSG: | |
1103 | reply[i].resp_retcode = PAM_SUCCESS; | |
1104 | reply[i].resp = NULL; | |
1105 | break; | |
1106 | default: /* unknown message style */ | |
1107 | free(reply); | |
1108 | return PAM_CONV_ERR; | |
1109 | } | |
1110 | } | |
1111 | ||
1112 | *resp = reply; | |
1113 | return PAM_SUCCESS; | |
1114 | } | |
1115 | ||
1116 | /* | |
1117 | * Attempt to authenticate the user using PAM. Returns 0 if the user is | |
1118 | * authenticated, or 1 if not authenticated. If some sort of PAM system | |
1119 | * error occurs (e.g., the "/etc/pam.conf" file is missing) then this | |
1120 | * function returns -1. This can be used as an indication that we should | |
1121 | * fall back to a different authentication mechanism. | |
1122 | */ | |
1123 | static int | |
1124 | auth_pam(struct passwd **ppw, const char *pass) | |
1125 | { | |
1126 | pam_handle_t *pamh = NULL; | |
1127 | const char *tmpl_user; | |
1128 | const void *item; | |
1129 | int rval; | |
1130 | int e; | |
1131 | cred_t auth_cred = { (*ppw)->pw_name, pass }; | |
1132 | struct pam_conv conv = { &auth_conv, &auth_cred }; | |
1133 | ||
1134 | e = pam_start("ftpd", (*ppw)->pw_name, &conv, &pamh); | |
1135 | if (e != PAM_SUCCESS) { | |
1136 | syslog(LOG_ERR, "pam_start: %s", pam_strerror(pamh, e)); | |
1137 | return -1; | |
1138 | } | |
1139 | ||
1140 | e = pam_set_item(pamh, PAM_RHOST, remotehost); | |
1141 | if (e != PAM_SUCCESS) { | |
1142 | syslog(LOG_ERR, "pam_set_item(PAM_RHOST): %s", | |
1143 | pam_strerror(pamh, e)); | |
1144 | return -1; | |
1145 | } | |
1146 | ||
1147 | e = pam_authenticate(pamh, 0); | |
1148 | switch (e) { | |
1149 | case PAM_SUCCESS: | |
1150 | /* | |
1151 | * With PAM we support the concept of a "template" | |
1152 | * user. The user enters a login name which is | |
1153 | * authenticated by PAM, usually via a remote service | |
1154 | * such as RADIUS or TACACS+. If authentication | |
1155 | * succeeds, a different but related "template" name | |
1156 | * is used for setting the credentials, shell, and | |
1157 | * home directory. The name the user enters need only | |
1158 | * exist on the remote authentication server, but the | |
1159 | * template name must be present in the local password | |
1160 | * database. | |
1161 | * | |
1162 | * This is supported by two various mechanisms in the | |
1163 | * individual modules. However, from the application's | |
1164 | * point of view, the template user is always passed | |
1165 | * back as a changed value of the PAM_USER item. | |
1166 | */ | |
1167 | if ((e = pam_get_item(pamh, PAM_USER, &item)) == | |
1168 | PAM_SUCCESS) { | |
1169 | tmpl_user = (const char *) item; | |
1170 | if (strcmp((*ppw)->pw_name, tmpl_user) != 0) | |
1171 | *ppw = getpwnam(tmpl_user); | |
1172 | } else | |
1173 | syslog(LOG_ERR, "Couldn't get PAM_USER: %s", | |
1174 | pam_strerror(pamh, e)); | |
1175 | rval = 0; | |
1176 | break; | |
1177 | ||
1178 | case PAM_AUTH_ERR: | |
1179 | case PAM_USER_UNKNOWN: | |
1180 | case PAM_MAXTRIES: | |
1181 | rval = 1; | |
1182 | break; | |
1183 | ||
1184 | default: | |
1185 | syslog(LOG_ERR, "pam_authenticate: %s", pam_strerror(pamh, e)); | |
1186 | rval = -1; | |
1187 | break; | |
1188 | } | |
1189 | ||
1190 | if (rval == 0) { | |
1191 | e = pam_acct_mgmt(pamh, 0); | |
1192 | if (e == PAM_NEW_AUTHTOK_REQD) { | |
1193 | e = pam_chauthtok(pamh, PAM_CHANGE_EXPIRED_AUTHTOK); | |
1194 | if (e != PAM_SUCCESS) { | |
1195 | syslog(LOG_ERR, "pam_chauthtok: %s", pam_strerror(pamh, e)); | |
1196 | rval = 1; | |
1197 | } | |
1198 | } else if (e != PAM_SUCCESS) { | |
1199 | rval = 1; | |
1200 | } | |
1201 | } | |
1202 | ||
1203 | if (rval != 0) { | |
1204 | if ((e = pam_end(pamh, e)) != PAM_SUCCESS) { | |
1205 | syslog(LOG_ERR, "pam_end: %s", pam_strerror(pamh, e)); | |
1206 | } | |
1207 | pamh = NULL; | |
1208 | } | |
1209 | return rval; | |
b7080c8e A |
1210 | } |
1211 | ||
3e383549 A |
1212 | #endif /* USE_PAM */ |
1213 | ||
b7080c8e A |
1214 | void |
1215 | pass(passwd) | |
1216 | char *passwd; | |
1217 | { | |
3e383549 | 1218 | int rval; |
b7080c8e | 1219 | FILE *fd; |
3e383549 A |
1220 | #ifdef LOGIN_CAP |
1221 | login_cap_t *lc = NULL; | |
1222 | #endif | |
1223 | #ifdef USE_PAM | |
1224 | int e; | |
1225 | #endif | |
b7080c8e A |
1226 | |
1227 | if (logged_in || askpasswd == 0) { | |
1228 | reply(503, "Login with USER first."); | |
1229 | return; | |
1230 | } | |
1231 | askpasswd = 0; | |
1232 | if (!guest) { /* "ftp" is only account allowed no password */ | |
3e383549 A |
1233 | if (pw == NULL) { |
1234 | rval = 1; /* failure below */ | |
1235 | goto skip; | |
1236 | } | |
1237 | #ifdef USE_PAM | |
1238 | rval = auth_pam(&pw, passwd); | |
1239 | if (rval >= 0) | |
1240 | goto skip; | |
1241 | #endif | |
1242 | #ifdef SKEY | |
1243 | if (pwok) | |
1244 | rval = strcmp(pw->pw_passwd, | |
1245 | crypt(passwd, pw->pw_passwd)); | |
1246 | if (rval) | |
1247 | rval = strcmp(pw->pw_passwd, | |
1248 | skey_crypt(passwd, pw->pw_passwd, pw, pwok)); | |
1249 | #else | |
1250 | rval = strcmp(pw->pw_passwd, crypt(passwd, pw->pw_passwd)); | |
1251 | #endif | |
b7080c8e | 1252 | /* The strcmp does not catch null passwords! */ |
3e383549 A |
1253 | if (*pw->pw_passwd == '\0' || |
1254 | (pw->pw_expire && time(NULL) >= pw->pw_expire)) | |
1255 | rval = 1; /* failure */ | |
1256 | skip: | |
1257 | /* | |
1258 | * If rval == 1, the user failed the authentication check | |
1259 | * above. If rval == 0, either PAM or local authentication | |
1260 | * succeeded. | |
1261 | */ | |
1262 | if (rval) { | |
b7080c8e A |
1263 | reply(530, "Login incorrect."); |
1264 | if (logging) | |
1265 | syslog(LOG_NOTICE, | |
1266 | "FTP LOGIN FAILED FROM %s, %s", | |
1267 | remotehost, curname); | |
1268 | pw = NULL; | |
1269 | if (login_attempts++ >= 5) { | |
1270 | syslog(LOG_NOTICE, | |
1271 | "repeated login failures from %s", | |
1272 | remotehost); | |
1273 | exit(0); | |
1274 | } | |
1275 | return; | |
1276 | } | |
1277 | } | |
3e383549 A |
1278 | #ifdef SKEY |
1279 | pwok = 0; | |
1280 | #endif | |
b7080c8e A |
1281 | login_attempts = 0; /* this time successful */ |
1282 | if (setegid((gid_t)pw->pw_gid) < 0) { | |
1283 | reply(550, "Can't set gid."); | |
1284 | return; | |
1285 | } | |
3e383549 A |
1286 | /* May be overridden by login.conf */ |
1287 | (void) umask(defumask); | |
1288 | #ifdef LOGIN_CAP | |
1289 | if ((lc = login_getpwclass(pw)) != NULL) { | |
1290 | char remote_ip[MAXHOSTNAMELEN]; | |
1291 | ||
1292 | getnameinfo((struct sockaddr *)&his_addr, his_addr.su_len, | |
1293 | remote_ip, sizeof(remote_ip) - 1, NULL, 0, | |
1294 | NI_NUMERICHOST|NI_WITHSCOPEID); | |
1295 | remote_ip[sizeof(remote_ip) - 1] = 0; | |
1296 | if (!auth_hostok(lc, remotehost, remote_ip)) { | |
1297 | syslog(LOG_INFO|LOG_AUTH, | |
1298 | "FTP LOGIN FAILED (HOST) as %s: permission denied.", | |
1299 | pw->pw_name); | |
1300 | reply(530, "Permission denied.\n"); | |
1301 | pw = NULL; | |
1302 | return; | |
1303 | } | |
1304 | if (!auth_timeok(lc, time(NULL))) { | |
1305 | reply(530, "Login not available right now.\n"); | |
1306 | pw = NULL; | |
1307 | return; | |
1308 | } | |
1309 | } | |
1310 | setusercontext(lc, pw, (uid_t)0, | |
1311 | LOGIN_SETLOGIN|LOGIN_SETGROUP|LOGIN_SETPRIORITY| | |
1312 | LOGIN_SETRESOURCES|LOGIN_SETUMASK); | |
1313 | #else | |
1314 | setlogin(pw->pw_name); | |
b7080c8e | 1315 | (void) initgroups(pw->pw_name, pw->pw_gid); |
3e383549 A |
1316 | #endif |
1317 | ||
1318 | #ifdef USE_PAM | |
1319 | if (pamh) { | |
1320 | if ((e = pam_open_session(pamh, 0)) != PAM_SUCCESS) { | |
1321 | syslog(LOG_ERR, "pam_open_session: %s", pam_strerror(pamh, e)); | |
1322 | } else if ((e = pam_setcred(pamh, PAM_ESTABLISH_CRED)) != PAM_SUCCESS) { | |
1323 | syslog(LOG_ERR, "pam_setcred: %s", pam_strerror(pamh, e)); | |
1324 | } | |
1325 | } | |
1326 | #endif | |
b7080c8e A |
1327 | |
1328 | /* open wtmp before chroot */ | |
3e383549 | 1329 | ftpd_logwtmp(ttyline, pw->pw_name, remotehost); |
b7080c8e A |
1330 | logged_in = 1; |
1331 | ||
3e383549 A |
1332 | if (guest && stats && statfd < 0) |
1333 | #ifdef VIRTUAL_HOSTING | |
1334 | if ((statfd = open(thishost->statfile, O_WRONLY|O_APPEND)) < 0) | |
1335 | #else | |
1336 | if ((statfd = open(_PATH_FTPDSTATFILE, O_WRONLY|O_APPEND)) < 0) | |
1337 | #endif | |
1338 | stats = 0; | |
1339 | ||
1340 | dochroot = | |
1341 | #ifdef LOGIN_CAP /* Allow login.conf configuration as well */ | |
1342 | login_getcapbool(lc, "ftp-chroot", 0) || | |
1343 | #endif | |
1344 | checkuser(_PATH_FTPCHROOT, pw->pw_name, 1); | |
b7080c8e A |
1345 | if (guest) { |
1346 | /* | |
1347 | * We MUST do a chdir() after the chroot. Otherwise | |
1348 | * the old current directory will be accessible as "." | |
1349 | * outside the new root! | |
1350 | */ | |
1351 | if (chroot(pw->pw_dir) < 0 || chdir("/") < 0) { | |
1352 | reply(550, "Can't set guest privileges."); | |
1353 | goto bad; | |
1354 | } | |
3e383549 A |
1355 | } else if (dochroot) { |
1356 | if (chroot(pw->pw_dir) < 0 || chdir("/") < 0) { | |
1357 | reply(550, "Can't change root."); | |
1358 | goto bad; | |
1359 | } | |
b7080c8e A |
1360 | } else if (chdir(pw->pw_dir) < 0) { |
1361 | if (chdir("/") < 0) { | |
1362 | reply(530, "User %s: can't change directory to %s.", | |
1363 | pw->pw_name, pw->pw_dir); | |
1364 | goto bad; | |
1365 | } else | |
1366 | lreply(230, "No directory! Logging in with home=/"); | |
1367 | } | |
1368 | if (seteuid((uid_t)pw->pw_uid) < 0) { | |
1369 | reply(550, "Can't set uid."); | |
1370 | goto bad; | |
1371 | } | |
3e383549 | 1372 | |
b7080c8e A |
1373 | /* |
1374 | * Display a login message, if it exists. | |
1375 | * N.B. reply(230,) must follow the message. | |
1376 | */ | |
3e383549 A |
1377 | #ifdef VIRTUAL_HOSTING |
1378 | if ((fd = fopen(thishost->loginmsg, "r")) != NULL) { | |
1379 | #else | |
b7080c8e | 1380 | if ((fd = fopen(_PATH_FTPLOGINMESG, "r")) != NULL) { |
3e383549 | 1381 | #endif |
b7080c8e A |
1382 | char *cp, line[LINE_MAX]; |
1383 | ||
1384 | while (fgets(line, sizeof(line), fd) != NULL) { | |
1385 | if ((cp = strchr(line, '\n')) != NULL) | |
1386 | *cp = '\0'; | |
1387 | lreply(230, "%s", line); | |
1388 | } | |
1389 | (void) fflush(stdout); | |
1390 | (void) fclose(fd); | |
1391 | } | |
1392 | if (guest) { | |
3e383549 A |
1393 | if (ident != NULL) |
1394 | free(ident); | |
1395 | ident = strdup(passwd); | |
1396 | if (ident == NULL) | |
1397 | fatal("Ran out of memory."); | |
1398 | ||
b7080c8e A |
1399 | reply(230, "Guest login ok, access restrictions apply."); |
1400 | #ifdef SETPROCTITLE | |
3e383549 A |
1401 | #ifdef VIRTUAL_HOSTING |
1402 | if (thishost != firsthost) | |
1403 | snprintf(proctitle, sizeof(proctitle), | |
1404 | "%s: anonymous(%s)/%.*s", remotehost, hostname, | |
1405 | (int)(sizeof(proctitle) - sizeof(remotehost) - | |
1406 | sizeof(": anonymous/")), passwd); | |
1407 | else | |
1408 | #endif | |
1409 | snprintf(proctitle, sizeof(proctitle), | |
1410 | "%s: anonymous/%.*s", remotehost, | |
1411 | (int)(sizeof(proctitle) - sizeof(remotehost) - | |
1412 | sizeof(": anonymous/")), passwd); | |
b7080c8e A |
1413 | setproctitle("%s", proctitle); |
1414 | #endif /* SETPROCTITLE */ | |
1415 | if (logging) | |
1416 | syslog(LOG_INFO, "ANONYMOUS FTP LOGIN FROM %s, %s", | |
1417 | remotehost, passwd); | |
1418 | } else { | |
3e383549 A |
1419 | if (dochroot) |
1420 | reply(230, "User %s logged in, access restrictions apply.", | |
1421 | pw->pw_name); | |
1422 | else | |
b7080c8e | 1423 | reply(230, "User %s logged in.", pw->pw_name); |
3e383549 | 1424 | |
b7080c8e A |
1425 | #ifdef SETPROCTITLE |
1426 | snprintf(proctitle, sizeof(proctitle), | |
3e383549 | 1427 | "%s: %s", remotehost, pw->pw_name); |
b7080c8e A |
1428 | setproctitle("%s", proctitle); |
1429 | #endif /* SETPROCTITLE */ | |
1430 | if (logging) | |
1431 | syslog(LOG_INFO, "FTP LOGIN FROM %s as %s", | |
1432 | remotehost, pw->pw_name); | |
1433 | } | |
3e383549 A |
1434 | #ifdef LOGIN_CAP |
1435 | login_close(lc); | |
1436 | #endif | |
b7080c8e A |
1437 | return; |
1438 | bad: | |
1439 | /* Forget all about it... */ | |
3e383549 A |
1440 | #ifdef LOGIN_CAP |
1441 | login_close(lc); | |
1442 | #endif | |
b7080c8e A |
1443 | end_login(); |
1444 | } | |
1445 | ||
1446 | void | |
1447 | retrieve(cmd, name) | |
1448 | char *cmd, *name; | |
1449 | { | |
1450 | FILE *fin, *dout; | |
1451 | struct stat st; | |
1452 | int (*closefunc) __P((FILE *)); | |
3e383549 | 1453 | time_t start; |
b7080c8e A |
1454 | |
1455 | if (cmd == 0) { | |
1456 | fin = fopen(name, "r"), closefunc = fclose; | |
1457 | st.st_size = 0; | |
1458 | } else { | |
1459 | char line[BUFSIZ]; | |
1460 | ||
3e383549 | 1461 | (void) snprintf(line, sizeof(line), cmd, name), name = line; |
b7080c8e A |
1462 | fin = ftpd_popen(line, "r"), closefunc = ftpd_pclose; |
1463 | st.st_size = -1; | |
1464 | st.st_blksize = BUFSIZ; | |
1465 | } | |
1466 | if (fin == NULL) { | |
1467 | if (errno != 0) { | |
1468 | perror_reply(550, name); | |
1469 | if (cmd == 0) { | |
1470 | LOGCMD("get", name); | |
1471 | } | |
1472 | } | |
1473 | return; | |
1474 | } | |
1475 | byte_count = -1; | |
1476 | if (cmd == 0 && (fstat(fileno(fin), &st) < 0 || !S_ISREG(st.st_mode))) { | |
1477 | reply(550, "%s: not a plain file.", name); | |
1478 | goto done; | |
1479 | } | |
1480 | if (restart_point) { | |
1481 | if (type == TYPE_A) { | |
1482 | off_t i, n; | |
1483 | int c; | |
1484 | ||
1485 | n = restart_point; | |
1486 | i = 0; | |
1487 | while (i++ < n) { | |
1488 | if ((c=getc(fin)) == EOF) { | |
1489 | perror_reply(550, name); | |
1490 | goto done; | |
1491 | } | |
1492 | if (c == '\n') | |
1493 | i++; | |
1494 | } | |
1495 | } else if (lseek(fileno(fin), restart_point, L_SET) < 0) { | |
1496 | perror_reply(550, name); | |
1497 | goto done; | |
1498 | } | |
1499 | } | |
1500 | dout = dataconn(name, st.st_size, "w"); | |
1501 | if (dout == NULL) | |
1502 | goto done; | |
3e383549 A |
1503 | time(&start); |
1504 | send_data(fin, dout, st.st_blksize, st.st_size, | |
1505 | restart_point == 0 && cmd == 0 && S_ISREG(st.st_mode)); | |
1506 | if (cmd == 0 && guest && stats) | |
1507 | logxfer(name, st.st_size, start); | |
b7080c8e A |
1508 | (void) fclose(dout); |
1509 | data = -1; | |
1510 | pdata = -1; | |
1511 | done: | |
1512 | if (cmd == 0) | |
1513 | LOGBYTES("get", name, byte_count); | |
1514 | (*closefunc)(fin); | |
1515 | } | |
1516 | ||
1517 | void | |
1518 | store(name, mode, unique) | |
1519 | char *name, *mode; | |
1520 | int unique; | |
1521 | { | |
1522 | FILE *fout, *din; | |
1523 | struct stat st; | |
1524 | int (*closefunc) __P((FILE *)); | |
1525 | ||
3e383549 | 1526 | if ((unique || guest) && stat(name, &st) == 0 && |
b7080c8e A |
1527 | (name = gunique(name)) == NULL) { |
1528 | LOGCMD(*mode == 'w' ? "put" : "append", name); | |
1529 | return; | |
1530 | } | |
1531 | ||
1532 | if (restart_point) | |
1533 | mode = "r+"; | |
1534 | fout = fopen(name, mode); | |
1535 | closefunc = fclose; | |
1536 | if (fout == NULL) { | |
1537 | perror_reply(553, name); | |
1538 | LOGCMD(*mode == 'w' ? "put" : "append", name); | |
1539 | return; | |
1540 | } | |
1541 | byte_count = -1; | |
1542 | if (restart_point) { | |
1543 | if (type == TYPE_A) { | |
1544 | off_t i, n; | |
1545 | int c; | |
1546 | ||
1547 | n = restart_point; | |
1548 | i = 0; | |
1549 | while (i++ < n) { | |
1550 | if ((c=getc(fout)) == EOF) { | |
1551 | perror_reply(550, name); | |
1552 | goto done; | |
1553 | } | |
1554 | if (c == '\n') | |
1555 | i++; | |
1556 | } | |
1557 | /* | |
1558 | * We must do this seek to "current" position | |
1559 | * because we are changing from reading to | |
1560 | * writing. | |
1561 | */ | |
1562 | if (fseek(fout, 0L, L_INCR) < 0) { | |
1563 | perror_reply(550, name); | |
1564 | goto done; | |
1565 | } | |
1566 | } else if (lseek(fileno(fout), restart_point, L_SET) < 0) { | |
1567 | perror_reply(550, name); | |
1568 | goto done; | |
1569 | } | |
1570 | } | |
1571 | din = dataconn(name, (off_t)-1, "r"); | |
1572 | if (din == NULL) | |
1573 | goto done; | |
1574 | if (receive_data(din, fout) == 0) { | |
1575 | if (unique) | |
1576 | reply(226, "Transfer complete (unique file name:%s).", | |
1577 | name); | |
1578 | else | |
1579 | reply(226, "Transfer complete."); | |
1580 | } | |
1581 | (void) fclose(din); | |
1582 | data = -1; | |
1583 | pdata = -1; | |
1584 | done: | |
1585 | LOGBYTES(*mode == 'w' ? "put" : "append", name, byte_count); | |
1586 | (*closefunc)(fout); | |
1587 | } | |
1588 | ||
1589 | static FILE * | |
1590 | getdatasock(mode) | |
1591 | char *mode; | |
1592 | { | |
1593 | int on = 1, s, t, tries; | |
1594 | ||
1595 | if (data >= 0) | |
1596 | return (fdopen(data, mode)); | |
1597 | (void) seteuid((uid_t)0); | |
3e383549 A |
1598 | |
1599 | s = socket(data_dest.su_family, SOCK_STREAM, 0); | |
b7080c8e A |
1600 | if (s < 0) |
1601 | goto bad; | |
1602 | if (setsockopt(s, SOL_SOCKET, SO_REUSEADDR, | |
1603 | (char *) &on, sizeof(on)) < 0) | |
1604 | goto bad; | |
1605 | /* anchor socket to avoid multi-homing problems */ | |
3e383549 A |
1606 | data_source = ctrl_addr; |
1607 | data_source.su_port = htons(20); /* ftp-data port */ | |
b7080c8e A |
1608 | for (tries = 1; ; tries++) { |
1609 | if (bind(s, (struct sockaddr *)&data_source, | |
3e383549 | 1610 | data_source.su_len) >= 0) |
b7080c8e A |
1611 | break; |
1612 | if (errno != EADDRINUSE || tries > 10) | |
1613 | goto bad; | |
1614 | sleep(tries); | |
1615 | } | |
1616 | (void) seteuid((uid_t)pw->pw_uid); | |
1617 | #ifdef IP_TOS | |
3e383549 A |
1618 | if (data_source.su_family == AF_INET) |
1619 | { | |
b7080c8e A |
1620 | on = IPTOS_THROUGHPUT; |
1621 | if (setsockopt(s, IPPROTO_IP, IP_TOS, (char *)&on, sizeof(int)) < 0) | |
1622 | syslog(LOG_WARNING, "setsockopt (IP_TOS): %m"); | |
3e383549 A |
1623 | } |
1624 | #endif | |
1625 | #ifdef TCP_NOPUSH | |
1626 | /* | |
1627 | * Turn off push flag to keep sender TCP from sending short packets | |
1628 | * at the boundaries of each write(). Should probably do a SO_SNDBUF | |
1629 | * to set the send buffer size as well, but that may not be desirable | |
1630 | * in heavy-load situations. | |
1631 | */ | |
1632 | on = 1; | |
1633 | if (setsockopt(s, IPPROTO_TCP, TCP_NOPUSH, (char *)&on, sizeof on) < 0) | |
1634 | syslog(LOG_WARNING, "setsockopt (TCP_NOPUSH): %m"); | |
b7080c8e | 1635 | #endif |
3e383549 A |
1636 | #ifdef SO_SNDBUF |
1637 | on = 65536; | |
1638 | if (setsockopt(s, SOL_SOCKET, SO_SNDBUF, (char *)&on, sizeof on) < 0) | |
1639 | syslog(LOG_WARNING, "setsockopt (SO_SNDBUF): %m"); | |
1640 | #endif | |
1641 | ||
b7080c8e A |
1642 | return (fdopen(s, mode)); |
1643 | bad: | |
1644 | /* Return the real value of errno (close may change it) */ | |
1645 | t = errno; | |
1646 | (void) seteuid((uid_t)pw->pw_uid); | |
1647 | (void) close(s); | |
1648 | errno = t; | |
1649 | return (NULL); | |
1650 | } | |
1651 | ||
1652 | static FILE * | |
1653 | dataconn(name, size, mode) | |
1654 | char *name; | |
1655 | off_t size; | |
1656 | char *mode; | |
1657 | { | |
1658 | char sizebuf[32]; | |
1659 | FILE *file; | |
1660 | int retry = 0, tos; | |
1661 | ||
1662 | file_size = size; | |
1663 | byte_count = 0; | |
1664 | if (size != (off_t) -1) | |
3e383549 | 1665 | (void) snprintf(sizebuf, sizeof(sizebuf), " (%qd bytes)", size); |
b7080c8e | 1666 | else |
3e383549 | 1667 | *sizebuf = '\0'; |
b7080c8e | 1668 | if (pdata >= 0) { |
3e383549 A |
1669 | union sockunion from; |
1670 | int s, fromlen = ctrl_addr.su_len; | |
1671 | struct timeval timeout; | |
1672 | fd_set set; | |
1673 | ||
1674 | FD_ZERO(&set); | |
1675 | FD_SET(pdata, &set); | |
b7080c8e | 1676 | |
3e383549 A |
1677 | timeout.tv_usec = 0; |
1678 | timeout.tv_sec = 120; | |
1679 | ||
1680 | if (select(pdata+1, &set, (fd_set *) 0, (fd_set *) 0, &timeout) == 0 || | |
1681 | (s = accept(pdata, (struct sockaddr *) &from, &fromlen)) < 0) { | |
b7080c8e A |
1682 | reply(425, "Can't open data connection."); |
1683 | (void) close(pdata); | |
1684 | pdata = -1; | |
1685 | return (NULL); | |
1686 | } | |
1687 | (void) close(pdata); | |
1688 | pdata = s; | |
1689 | #ifdef IP_TOS | |
3e383549 A |
1690 | if (from.su_family == AF_INET) |
1691 | { | |
1692 | tos = IPTOS_THROUGHPUT; | |
b7080c8e A |
1693 | (void) setsockopt(s, IPPROTO_IP, IP_TOS, (char *)&tos, |
1694 | sizeof(int)); | |
3e383549 | 1695 | } |
b7080c8e A |
1696 | #endif |
1697 | reply(150, "Opening %s mode data connection for '%s'%s.", | |
1698 | type == TYPE_A ? "ASCII" : "BINARY", name, sizebuf); | |
1699 | return (fdopen(pdata, mode)); | |
1700 | } | |
1701 | if (data >= 0) { | |
1702 | reply(125, "Using existing data connection for '%s'%s.", | |
1703 | name, sizebuf); | |
1704 | usedefault = 1; | |
1705 | return (fdopen(data, mode)); | |
1706 | } | |
1707 | if (usedefault) | |
1708 | data_dest = his_addr; | |
1709 | usedefault = 1; | |
1710 | file = getdatasock(mode); | |
1711 | if (file == NULL) { | |
3e383549 A |
1712 | #if defined(HAVE_GETNAMEINFO) |
1713 | char hostbuf[BUFSIZ], portbuf[BUFSIZ]; | |
1714 | getnameinfo((struct sockaddr *)&data_source, | |
1715 | data_source.su_len, hostbuf, sizeof(hostbuf) - 1, | |
1716 | portbuf, sizeof(portbuf), | |
1717 | NI_NUMERICHOST|NI_NUMERICSERV|NI_WITHSCOPEID); | |
1718 | reply(425, "Can't create data socket (%s,%s): %s.", | |
1719 | hostbuf, portbuf, strerror(errno)); | |
1720 | #else | |
1721 | reply(425, "Can't create data socket (%s,%d): %s.", | |
1722 | inet_ntoa(data_source.su_sin.sin_addr), | |
1723 | ntohs(data_source.su_sin.sin_port), strerror(errno)); | |
1724 | #endif | |
b7080c8e A |
1725 | return (NULL); |
1726 | } | |
1727 | data = fileno(file); | |
1728 | while (connect(data, (struct sockaddr *)&data_dest, | |
3e383549 | 1729 | data_dest.su_len) < 0) { |
b7080c8e A |
1730 | if (errno == EADDRINUSE && retry < swaitmax) { |
1731 | sleep((unsigned) swaitint); | |
1732 | retry += swaitint; | |
1733 | continue; | |
1734 | } | |
1735 | perror_reply(425, "Can't build data connection"); | |
1736 | (void) fclose(file); | |
1737 | data = -1; | |
1738 | return (NULL); | |
1739 | } | |
1740 | reply(150, "Opening %s mode data connection for '%s'%s.", | |
1741 | type == TYPE_A ? "ASCII" : "BINARY", name, sizebuf); | |
1742 | return (file); | |
1743 | } | |
1744 | ||
1745 | /* | |
1746 | * Tranfer the contents of "instr" to "outstr" peer using the appropriate | |
3e383549 | 1747 | * encapsulation of the data subject to Mode, Structure, and Type. |
b7080c8e A |
1748 | * |
1749 | * NB: Form isn't handled. | |
1750 | */ | |
1751 | static void | |
3e383549 | 1752 | send_data(instr, outstr, blksize, filesize, isreg) |
b7080c8e A |
1753 | FILE *instr, *outstr; |
1754 | off_t blksize; | |
3e383549 A |
1755 | off_t filesize; |
1756 | int isreg; | |
b7080c8e | 1757 | { |
3e383549 A |
1758 | int c, filefd, netfd; |
1759 | char *buf, *bp; | |
1760 | size_t len; | |
1761 | off_t cnt; | |
b7080c8e A |
1762 | |
1763 | transflag++; | |
1764 | if (setjmp(urgcatch)) { | |
1765 | transflag = 0; | |
1766 | return; | |
1767 | } | |
1768 | switch (type) { | |
1769 | ||
1770 | case TYPE_A: | |
1771 | while ((c = getc(instr)) != EOF) { | |
1772 | byte_count++; | |
1773 | if (c == '\n') { | |
1774 | if (ferror(outstr)) | |
1775 | goto data_err; | |
1776 | (void) putc('\r', outstr); | |
1777 | } | |
1778 | (void) putc(c, outstr); | |
1779 | } | |
1780 | fflush(outstr); | |
1781 | transflag = 0; | |
1782 | if (ferror(instr)) | |
1783 | goto file_err; | |
1784 | if (ferror(outstr)) | |
1785 | goto data_err; | |
1786 | reply(226, "Transfer complete."); | |
1787 | return; | |
1788 | ||
1789 | case TYPE_I: | |
1790 | case TYPE_L: | |
3e383549 A |
1791 | /* |
1792 | * isreg is only set if we are not doing restart and we | |
1793 | * are sending a regular file | |
1794 | */ | |
1795 | netfd = fileno(outstr); | |
1796 | filefd = fileno(instr); | |
1797 | ||
1798 | #if defined(HAVE_SENDFILE) | |
1799 | if (isreg) { | |
1800 | ||
1801 | off_t offset; | |
1802 | int err; | |
1803 | ||
1804 | len = filesize; | |
1805 | err = cnt = offset = 0; | |
1806 | ||
1807 | while (err != -1 && cnt < filesize) { | |
1808 | err = sendfile(filefd, netfd, offset, len, | |
1809 | (struct sf_hdtr *) NULL, &cnt, 0); | |
1810 | byte_count += cnt; | |
1811 | offset += cnt; | |
1812 | len -= cnt; | |
1813 | ||
1814 | if (err == -1) { | |
1815 | if (!cnt) | |
1816 | goto oldway; | |
1817 | ||
1818 | goto data_err; | |
1819 | } | |
1820 | } | |
1821 | ||
1822 | reply(226, "Transfer complete."); | |
1823 | return; | |
1824 | } | |
1825 | #else | |
1826 | if (isreg && filesize < (off_t)16 * 1024 * 1024) { | |
1827 | buf = mmap(0, filesize, PROT_READ, MAP_SHARED, filefd, | |
1828 | (off_t)0); | |
1829 | if (buf == MAP_FAILED) { | |
1830 | syslog(LOG_WARNING, "mmap(%lu): %m", | |
1831 | (unsigned long)filesize); | |
1832 | goto oldway; | |
1833 | } | |
1834 | bp = buf; | |
1835 | len = filesize; | |
1836 | do { | |
1837 | cnt = write(netfd, bp, len); | |
1838 | len -= cnt; | |
1839 | bp += cnt; | |
1840 | if (cnt > 0) byte_count += cnt; | |
1841 | } while(cnt > 0 && len > 0); | |
1842 | ||
1843 | transflag = 0; | |
1844 | munmap(buf, (size_t)filesize); | |
1845 | if (cnt < 0) | |
1846 | goto data_err; | |
1847 | reply(226, "Transfer complete."); | |
1848 | return; | |
1849 | } | |
1850 | #endif | |
1851 | oldway: | |
b7080c8e A |
1852 | if ((buf = malloc((u_int)blksize)) == NULL) { |
1853 | transflag = 0; | |
1854 | perror_reply(451, "Local resource failure: malloc"); | |
1855 | return; | |
1856 | } | |
3e383549 | 1857 | |
b7080c8e A |
1858 | while ((cnt = read(filefd, buf, (u_int)blksize)) > 0 && |
1859 | write(netfd, buf, cnt) == cnt) | |
1860 | byte_count += cnt; | |
1861 | transflag = 0; | |
1862 | (void)free(buf); | |
1863 | if (cnt != 0) { | |
1864 | if (cnt < 0) | |
1865 | goto file_err; | |
1866 | goto data_err; | |
1867 | } | |
1868 | reply(226, "Transfer complete."); | |
1869 | return; | |
1870 | default: | |
1871 | transflag = 0; | |
1872 | reply(550, "Unimplemented TYPE %d in send_data", type); | |
1873 | return; | |
1874 | } | |
1875 | ||
1876 | data_err: | |
1877 | transflag = 0; | |
1878 | perror_reply(426, "Data connection"); | |
1879 | return; | |
1880 | ||
1881 | file_err: | |
1882 | transflag = 0; | |
1883 | perror_reply(551, "Error on input file"); | |
1884 | } | |
1885 | ||
1886 | /* | |
1887 | * Transfer data from peer to "outstr" using the appropriate encapulation of | |
1888 | * the data subject to Mode, Structure, and Type. | |
1889 | * | |
1890 | * N.B.: Form isn't handled. | |
1891 | */ | |
1892 | static int | |
1893 | receive_data(instr, outstr) | |
1894 | FILE *instr, *outstr; | |
1895 | { | |
1896 | int c; | |
3e383549 | 1897 | int cnt, bare_lfs; |
b7080c8e A |
1898 | char buf[BUFSIZ]; |
1899 | ||
1900 | transflag++; | |
1901 | if (setjmp(urgcatch)) { | |
1902 | transflag = 0; | |
1903 | return (-1); | |
1904 | } | |
3e383549 A |
1905 | |
1906 | bare_lfs = 0; | |
1907 | ||
b7080c8e A |
1908 | switch (type) { |
1909 | ||
1910 | case TYPE_I: | |
1911 | case TYPE_L: | |
1912 | while ((cnt = read(fileno(instr), buf, sizeof(buf))) > 0) { | |
1913 | if (write(fileno(outstr), buf, cnt) != cnt) | |
1914 | goto file_err; | |
1915 | byte_count += cnt; | |
1916 | } | |
1917 | if (cnt < 0) | |
1918 | goto data_err; | |
1919 | transflag = 0; | |
1920 | return (0); | |
1921 | ||
1922 | case TYPE_E: | |
1923 | reply(553, "TYPE E not implemented."); | |
1924 | transflag = 0; | |
1925 | return (-1); | |
1926 | ||
1927 | case TYPE_A: | |
1928 | while ((c = getc(instr)) != EOF) { | |
1929 | byte_count++; | |
1930 | if (c == '\n') | |
1931 | bare_lfs++; | |
1932 | while (c == '\r') { | |
1933 | if (ferror(outstr)) | |
1934 | goto data_err; | |
1935 | if ((c = getc(instr)) != '\n') { | |
1936 | (void) putc ('\r', outstr); | |
1937 | if (c == '\0' || c == EOF) | |
1938 | goto contin2; | |
1939 | } | |
1940 | } | |
1941 | (void) putc(c, outstr); | |
1942 | contin2: ; | |
1943 | } | |
1944 | fflush(outstr); | |
1945 | if (ferror(instr)) | |
1946 | goto data_err; | |
1947 | if (ferror(outstr)) | |
1948 | goto file_err; | |
1949 | transflag = 0; | |
1950 | if (bare_lfs) { | |
1951 | lreply(226, | |
1952 | "WARNING! %d bare linefeeds received in ASCII mode", | |
1953 | bare_lfs); | |
1954 | (void)printf(" File may not have transferred correctly.\r\n"); | |
1955 | } | |
1956 | return (0); | |
1957 | default: | |
1958 | reply(550, "Unimplemented TYPE %d in receive_data", type); | |
1959 | transflag = 0; | |
1960 | return (-1); | |
1961 | } | |
1962 | ||
1963 | data_err: | |
1964 | transflag = 0; | |
1965 | perror_reply(426, "Data Connection"); | |
1966 | return (-1); | |
1967 | ||
1968 | file_err: | |
1969 | transflag = 0; | |
1970 | perror_reply(452, "Error writing file"); | |
1971 | return (-1); | |
1972 | } | |
1973 | ||
1974 | void | |
1975 | statfilecmd(filename) | |
1976 | char *filename; | |
1977 | { | |
1978 | FILE *fin; | |
1979 | int c; | |
1980 | char line[LINE_MAX]; | |
1981 | ||
3e383549 | 1982 | (void)snprintf(line, sizeof(line), _PATH_LS " -lgA %s", filename); |
b7080c8e A |
1983 | fin = ftpd_popen(line, "r"); |
1984 | lreply(211, "status of %s:", filename); | |
1985 | while ((c = getc(fin)) != EOF) { | |
1986 | if (c == '\n') { | |
1987 | if (ferror(stdout)){ | |
1988 | perror_reply(421, "control connection"); | |
1989 | (void) ftpd_pclose(fin); | |
1990 | dologout(1); | |
1991 | /* NOTREACHED */ | |
1992 | } | |
1993 | if (ferror(fin)) { | |
1994 | perror_reply(551, filename); | |
1995 | (void) ftpd_pclose(fin); | |
1996 | return; | |
1997 | } | |
1998 | (void) putc('\r', stdout); | |
1999 | } | |
2000 | (void) putc(c, stdout); | |
2001 | } | |
2002 | (void) ftpd_pclose(fin); | |
2003 | reply(211, "End of Status"); | |
2004 | } | |
2005 | ||
2006 | void | |
2007 | statcmd() | |
2008 | { | |
3e383549 | 2009 | union sockunion *su; |
b7080c8e | 2010 | u_char *a, *p; |
3e383549 A |
2011 | char hname[INET6_ADDRSTRLEN]; |
2012 | int ispassive; | |
b7080c8e A |
2013 | |
2014 | lreply(211, "%s FTP server status:", hostname, version); | |
2015 | printf(" %s\r\n", version); | |
2016 | printf(" Connected to %s", remotehost); | |
3e383549 A |
2017 | #if defined(HAVE_GETNAMEINFO) |
2018 | if (!getnameinfo((struct sockaddr *)&his_addr, his_addr.su_len, | |
2019 | hname, sizeof(hname) - 1, NULL, 0, | |
2020 | NI_NUMERICHOST|NI_WITHSCOPEID)) { | |
2021 | if (strcmp(hname, remotehost) != 0) | |
2022 | printf(" (%s)", hname); | |
2023 | #else | |
2024 | { | |
2025 | if (!isdigit(remotehost[0])) | |
2026 | printf(" (%s)", inet_ntoa(his_addr.su_sin.sin_addr)); | |
2027 | #endif | |
2028 | } | |
b7080c8e A |
2029 | printf("\r\n"); |
2030 | if (logged_in) { | |
2031 | if (guest) | |
2032 | printf(" Logged in anonymously\r\n"); | |
2033 | else | |
2034 | printf(" Logged in as %s\r\n", pw->pw_name); | |
2035 | } else if (askpasswd) | |
2036 | printf(" Waiting for password\r\n"); | |
2037 | else | |
2038 | printf(" Waiting for user name\r\n"); | |
2039 | printf(" TYPE: %s", typenames[type]); | |
2040 | if (type == TYPE_A || type == TYPE_E) | |
2041 | printf(", FORM: %s", formnames[form]); | |
2042 | if (type == TYPE_L) | |
2043 | #if NBBY == 8 | |
2044 | printf(" %d", NBBY); | |
2045 | #else | |
2046 | printf(" %d", bytesize); /* need definition! */ | |
2047 | #endif | |
2048 | printf("; STRUcture: %s; transfer MODE: %s\r\n", | |
2049 | strunames[stru], modenames[mode]); | |
2050 | if (data != -1) | |
2051 | printf(" Data connection open\r\n"); | |
2052 | else if (pdata != -1) { | |
3e383549 A |
2053 | ispassive = 1; |
2054 | su = &pasv_addr; | |
b7080c8e A |
2055 | goto printaddr; |
2056 | } else if (usedefault == 0) { | |
3e383549 A |
2057 | ispassive = 0; |
2058 | su = &data_dest; | |
b7080c8e | 2059 | printaddr: |
b7080c8e | 2060 | #define UC(b) (((int) b) & 0xff) |
3e383549 A |
2061 | #if defined(VIRTUAL_HOSTING) |
2062 | if (epsvall) { | |
2063 | printf(" EPSV only mode (EPSV ALL)\r\n"); | |
2064 | goto epsvonly; | |
2065 | } | |
2066 | #endif | |
2067 | ||
2068 | /* PORT/PASV */ | |
2069 | if (su->su_family == AF_INET) { | |
2070 | a = (u_char *) &su->su_sin.sin_addr; | |
2071 | p = (u_char *) &su->su_sin.sin_port; | |
2072 | printf(" %s (%d,%d,%d,%d,%d,%d)\r\n", | |
2073 | ispassive ? "PASV" : "PORT", | |
2074 | UC(a[0]), UC(a[1]), UC(a[2]), UC(a[3]), | |
2075 | UC(p[0]), UC(p[1])); | |
2076 | } | |
2077 | ||
2078 | /* LPRT/LPSV */ | |
2079 | { | |
2080 | int alen, af, i; | |
2081 | ||
2082 | switch (su->su_family) { | |
2083 | case AF_INET: | |
2084 | a = (u_char *) &su->su_sin.sin_addr; | |
2085 | p = (u_char *) &su->su_sin.sin_port; | |
2086 | alen = sizeof(su->su_sin.sin_addr); | |
2087 | af = 4; | |
2088 | break; | |
2089 | case AF_INET6: | |
2090 | a = (u_char *) &su->su_sin6.sin6_addr; | |
2091 | p = (u_char *) &su->su_sin6.sin6_port; | |
2092 | alen = sizeof(su->su_sin6.sin6_addr); | |
2093 | af = 6; | |
2094 | break; | |
2095 | default: | |
2096 | af = 0; | |
2097 | break; | |
2098 | } | |
2099 | if (af) { | |
2100 | printf(" %s (%d,%d,", ispassive ? "LPSV" : "LPRT", | |
2101 | af, alen); | |
2102 | for (i = 0; i < alen; i++) | |
2103 | printf("%d,", UC(a[i])); | |
2104 | printf("%d,%d,%d)\r\n", 2, UC(p[0]), UC(p[1])); | |
2105 | } | |
2106 | } | |
2107 | ||
2108 | #if defined(HAVE_GETNAMEINFO) | |
2109 | epsvonly:; | |
2110 | /* EPRT/EPSV */ | |
2111 | { | |
2112 | int af; | |
2113 | ||
2114 | switch (su->su_family) { | |
2115 | case AF_INET: | |
2116 | af = 1; | |
2117 | break; | |
2118 | case AF_INET6: | |
2119 | af = 2; | |
2120 | break; | |
2121 | default: | |
2122 | af = 0; | |
2123 | break; | |
2124 | } | |
2125 | if (af) { | |
2126 | if (!getnameinfo((struct sockaddr *)su, su->su_len, | |
2127 | hname, sizeof(hname) - 1, NULL, 0, | |
2128 | NI_NUMERICHOST)) { | |
2129 | printf(" %s |%d|%s|%d|\r\n", | |
2130 | ispassive ? "EPSV" : "EPRT", | |
2131 | af, hname, htons(su->su_port)); | |
2132 | } | |
2133 | } | |
2134 | } | |
2135 | #endif | |
b7080c8e A |
2136 | #undef UC |
2137 | } else | |
2138 | printf(" No data connection\r\n"); | |
2139 | reply(211, "End of status"); | |
2140 | } | |
2141 | ||
2142 | void | |
2143 | fatal(s) | |
2144 | char *s; | |
2145 | { | |
2146 | ||
2147 | reply(451, "Error in server: %s\n", s); | |
2148 | reply(221, "Closing connection due to server error."); | |
2149 | dologout(0); | |
2150 | /* NOTREACHED */ | |
2151 | } | |
2152 | ||
2153 | void | |
2154 | #if __STDC__ | |
2155 | reply(int n, const char *fmt, ...) | |
2156 | #else | |
2157 | reply(n, fmt, va_alist) | |
2158 | int n; | |
2159 | char *fmt; | |
2160 | va_dcl | |
2161 | #endif | |
2162 | { | |
2163 | va_list ap; | |
2164 | #if __STDC__ | |
2165 | va_start(ap, fmt); | |
2166 | #else | |
2167 | va_start(ap); | |
2168 | #endif | |
2169 | (void)printf("%d ", n); | |
2170 | (void)vprintf(fmt, ap); | |
2171 | (void)printf("\r\n"); | |
2172 | (void)fflush(stdout); | |
2173 | if (debug) { | |
2174 | syslog(LOG_DEBUG, "<--- %d ", n); | |
2175 | vsyslog(LOG_DEBUG, fmt, ap); | |
2176 | } | |
2177 | } | |
2178 | ||
2179 | void | |
2180 | #if __STDC__ | |
2181 | lreply(int n, const char *fmt, ...) | |
2182 | #else | |
2183 | lreply(n, fmt, va_alist) | |
2184 | int n; | |
2185 | char *fmt; | |
2186 | va_dcl | |
2187 | #endif | |
2188 | { | |
2189 | va_list ap; | |
2190 | #if __STDC__ | |
2191 | va_start(ap, fmt); | |
2192 | #else | |
2193 | va_start(ap); | |
2194 | #endif | |
2195 | (void)printf("%d- ", n); | |
2196 | (void)vprintf(fmt, ap); | |
2197 | (void)printf("\r\n"); | |
2198 | (void)fflush(stdout); | |
2199 | if (debug) { | |
2200 | syslog(LOG_DEBUG, "<--- %d- ", n); | |
2201 | vsyslog(LOG_DEBUG, fmt, ap); | |
2202 | } | |
2203 | } | |
2204 | ||
2205 | static void | |
2206 | ack(s) | |
2207 | char *s; | |
2208 | { | |
2209 | ||
2210 | reply(250, "%s command successful.", s); | |
2211 | } | |
2212 | ||
2213 | void | |
2214 | nack(s) | |
2215 | char *s; | |
2216 | { | |
2217 | ||
2218 | reply(502, "%s command not implemented.", s); | |
2219 | } | |
2220 | ||
2221 | /* ARGSUSED */ | |
2222 | void | |
2223 | yyerror(s) | |
2224 | char *s; | |
2225 | { | |
2226 | char *cp; | |
2227 | ||
3e383549 | 2228 | if ((cp = strchr(cbuf,'\n'))) |
b7080c8e A |
2229 | *cp = '\0'; |
2230 | reply(500, "'%s': command not understood.", cbuf); | |
2231 | } | |
2232 | ||
2233 | void | |
2234 | delete(name) | |
2235 | char *name; | |
2236 | { | |
2237 | struct stat st; | |
2238 | ||
2239 | LOGCMD("delete", name); | |
2240 | if (stat(name, &st) < 0) { | |
2241 | perror_reply(550, name); | |
2242 | return; | |
2243 | } | |
2244 | if ((st.st_mode&S_IFMT) == S_IFDIR) { | |
2245 | if (rmdir(name) < 0) { | |
2246 | perror_reply(550, name); | |
2247 | return; | |
2248 | } | |
2249 | goto done; | |
2250 | } | |
2251 | if (unlink(name) < 0) { | |
2252 | perror_reply(550, name); | |
2253 | return; | |
2254 | } | |
2255 | done: | |
2256 | ack("DELE"); | |
2257 | } | |
2258 | ||
2259 | void | |
2260 | cwd(path) | |
2261 | char *path; | |
2262 | { | |
2263 | ||
2264 | if (chdir(path) < 0) | |
2265 | perror_reply(550, path); | |
2266 | else | |
2267 | ack("CWD"); | |
2268 | } | |
2269 | ||
2270 | void | |
2271 | makedir(name) | |
2272 | char *name; | |
2273 | { | |
2274 | ||
2275 | LOGCMD("mkdir", name); | |
2276 | if (mkdir(name, 0777) < 0) | |
2277 | perror_reply(550, name); | |
2278 | else | |
2279 | reply(257, "MKD command successful."); | |
2280 | } | |
2281 | ||
2282 | void | |
2283 | removedir(name) | |
2284 | char *name; | |
2285 | { | |
2286 | ||
2287 | LOGCMD("rmdir", name); | |
2288 | if (rmdir(name) < 0) | |
2289 | perror_reply(550, name); | |
2290 | else | |
2291 | ack("RMD"); | |
2292 | } | |
2293 | ||
2294 | void | |
2295 | pwd() | |
2296 | { | |
3e383549 | 2297 | char path[MAXPATHLEN + 1]; |
b7080c8e | 2298 | |
3e383549 | 2299 | if (getwd(path) == (char *)NULL) |
b7080c8e A |
2300 | reply(550, "%s.", path); |
2301 | else | |
2302 | reply(257, "\"%s\" is current directory.", path); | |
2303 | } | |
2304 | ||
2305 | char * | |
2306 | renamefrom(name) | |
2307 | char *name; | |
2308 | { | |
2309 | struct stat st; | |
2310 | ||
2311 | if (stat(name, &st) < 0) { | |
2312 | perror_reply(550, name); | |
2313 | return ((char *)0); | |
2314 | } | |
2315 | reply(350, "File exists, ready for destination name"); | |
2316 | return (name); | |
2317 | } | |
2318 | ||
2319 | void | |
2320 | renamecmd(from, to) | |
2321 | char *from, *to; | |
2322 | { | |
3e383549 | 2323 | struct stat st; |
b7080c8e A |
2324 | |
2325 | LOGCMD2("rename", from, to); | |
3e383549 A |
2326 | |
2327 | if (guest && (stat(to, &st) == 0)) { | |
2328 | reply(550, "%s: permission denied", to); | |
2329 | return; | |
2330 | } | |
2331 | ||
b7080c8e A |
2332 | if (rename(from, to) < 0) |
2333 | perror_reply(550, "rename"); | |
2334 | else | |
2335 | ack("RNTO"); | |
2336 | } | |
2337 | ||
2338 | static void | |
3e383549 A |
2339 | dolog(who) |
2340 | struct sockaddr *who; | |
b7080c8e | 2341 | { |
3e383549 A |
2342 | #if defined(HAVE_GETNAMEINFO) |
2343 | int error; | |
2344 | #endif | |
2345 | ||
2346 | #if defined(HAVE_REALHOSTNAME_SA) | |
2347 | realhostname_sa(remotehost, sizeof(remotehost) - 1, who, who->sa_len); | |
2348 | #else | |
2349 | struct sockaddr_in *sin = (struct sockaddr_in *)who; | |
b7080c8e | 2350 | struct hostent *hp = gethostbyaddr((char *)&sin->sin_addr, |
3e383549 A |
2351 | sizeof(struct in_addr), AF_INET); |
2352 | ||
2353 | if (hp) | |
2354 | (void) strncpy(remotehost, hp->h_name, sizeof(remotehost)); | |
2355 | else | |
2356 | (void) strncpy(remotehost, inet_ntoa(sin->sin_addr), | |
2357 | sizeof(remotehost)); | |
2358 | #endif | |
b7080c8e | 2359 | |
b7080c8e | 2360 | #ifdef SETPROCTITLE |
3e383549 A |
2361 | #ifdef VIRTUAL_HOSTING |
2362 | if (thishost != firsthost) | |
2363 | snprintf(proctitle, sizeof(proctitle), "%s: connected (to %s)", | |
2364 | remotehost, hostname); | |
2365 | else | |
2366 | #endif | |
2367 | snprintf(proctitle, sizeof(proctitle), "%s: connected", | |
2368 | remotehost); | |
b7080c8e A |
2369 | setproctitle("%s", proctitle); |
2370 | #endif /* SETPROCTITLE */ | |
2371 | ||
3e383549 A |
2372 | if (logging) { |
2373 | #ifdef VIRTUAL_HOSTING | |
2374 | if (thishost != firsthost) | |
2375 | syslog(LOG_INFO, "connection from %s (to %s)", | |
2376 | remotehost, hostname); | |
2377 | else | |
2378 | #endif | |
2379 | { | |
2380 | #if defined(HAVE_GETNAMEINFO) | |
2381 | char who_name[MAXHOSTNAMELEN]; | |
2382 | ||
2383 | error = getnameinfo(who, who->sa_len, | |
2384 | who_name, sizeof(who_name) - 1, | |
2385 | NULL, 0, | |
2386 | NI_NUMERICHOST|NI_WITHSCOPEID); | |
2387 | syslog(LOG_INFO, "connection from %s (%s)", remotehost, | |
2388 | error == 0 ? who_name : ""); | |
2389 | #else | |
2390 | syslog(LOG_INFO, "connection from %s", remotehost); | |
2391 | #endif | |
2392 | } | |
2393 | } | |
b7080c8e A |
2394 | } |
2395 | ||
2396 | /* | |
2397 | * Record logout in wtmp file | |
2398 | * and exit with supplied status. | |
2399 | */ | |
2400 | void | |
2401 | dologout(status) | |
2402 | int status; | |
2403 | { | |
3e383549 A |
2404 | /* |
2405 | * Prevent reception of SIGURG from resulting in a resumption | |
2406 | * back to the main program loop. | |
2407 | */ | |
2408 | transflag = 0; | |
b7080c8e A |
2409 | |
2410 | if (logged_in) { | |
2411 | (void) seteuid((uid_t)0); | |
3e383549 | 2412 | ftpd_logwtmp(ttyline, "", ""); |
b7080c8e A |
2413 | } |
2414 | /* beware of flushing buffers after a SIGPIPE */ | |
2415 | _exit(status); | |
2416 | } | |
2417 | ||
2418 | static void | |
2419 | myoob(signo) | |
2420 | int signo; | |
2421 | { | |
2422 | char *cp; | |
2423 | ||
2424 | /* only process if transfer occurring */ | |
2425 | if (!transflag) | |
2426 | return; | |
2427 | cp = tmpline; | |
2428 | if (getline(cp, 7, stdin) == NULL) { | |
2429 | reply(221, "You could at least say goodbye."); | |
2430 | dologout(0); | |
2431 | } | |
2432 | upper(cp); | |
2433 | if (strcmp(cp, "ABOR\r\n") == 0) { | |
2434 | tmpline[0] = '\0'; | |
2435 | reply(426, "Transfer aborted. Data connection closed."); | |
2436 | reply(226, "Abort successful"); | |
2437 | longjmp(urgcatch, 1); | |
2438 | } | |
2439 | if (strcmp(cp, "STAT\r\n") == 0) { | |
3e383549 | 2440 | tmpline[0] = '\0'; |
b7080c8e A |
2441 | if (file_size != (off_t) -1) |
2442 | reply(213, "Status: %qd of %qd bytes transferred", | |
2443 | byte_count, file_size); | |
2444 | else | |
2445 | reply(213, "Status: %qd bytes transferred", byte_count); | |
2446 | } | |
2447 | } | |
2448 | ||
2449 | /* | |
2450 | * Note: a response of 425 is not mentioned as a possible response to | |
2451 | * the PASV command in RFC959. However, it has been blessed as | |
2452 | * a legitimate response by Jon Postel in a telephone conversation | |
2453 | * with Rick Adams on 25 Jan 89. | |
2454 | */ | |
2455 | void | |
2456 | passive() | |
2457 | { | |
2458 | int len; | |
2459 | char *p, *a; | |
2460 | ||
3e383549 A |
2461 | if (pdata >= 0) /* close old port if one set */ |
2462 | close(pdata); | |
2463 | ||
2464 | pdata = socket(ctrl_addr.su_family, SOCK_STREAM, 0); | |
b7080c8e A |
2465 | if (pdata < 0) { |
2466 | perror_reply(425, "Can't open passive connection"); | |
2467 | return; | |
2468 | } | |
3e383549 | 2469 | |
b7080c8e | 2470 | (void) seteuid((uid_t)0); |
3e383549 A |
2471 | |
2472 | #ifdef IP_PORTRANGE | |
2473 | if (ctrl_addr.su_family == AF_INET) { | |
2474 | int on = restricted_data_ports ? IP_PORTRANGE_HIGH | |
2475 | : IP_PORTRANGE_DEFAULT; | |
2476 | ||
2477 | if (setsockopt(pdata, IPPROTO_IP, IP_PORTRANGE, | |
2478 | (char *)&on, sizeof(on)) < 0) | |
2479 | goto pasv_error; | |
b7080c8e | 2480 | } |
3e383549 A |
2481 | #endif |
2482 | #ifdef IPV6_PORTRANGE | |
2483 | if (ctrl_addr.su_family == AF_INET6) { | |
2484 | int on = restricted_data_ports ? IPV6_PORTRANGE_HIGH | |
2485 | : IPV6_PORTRANGE_DEFAULT; | |
2486 | ||
2487 | if (setsockopt(pdata, IPPROTO_IPV6, IPV6_PORTRANGE, | |
2488 | (char *)&on, sizeof(on)) < 0) | |
2489 | goto pasv_error; | |
2490 | } | |
2491 | #endif | |
2492 | ||
2493 | pasv_addr = ctrl_addr; | |
2494 | pasv_addr.su_port = 0; | |
2495 | if (bind(pdata, (struct sockaddr *)&pasv_addr, pasv_addr.su_len) < 0) | |
2496 | goto pasv_error; | |
2497 | ||
b7080c8e | 2498 | (void) seteuid((uid_t)pw->pw_uid); |
3e383549 | 2499 | |
b7080c8e A |
2500 | len = sizeof(pasv_addr); |
2501 | if (getsockname(pdata, (struct sockaddr *) &pasv_addr, &len) < 0) | |
2502 | goto pasv_error; | |
2503 | if (listen(pdata, 1) < 0) | |
2504 | goto pasv_error; | |
3e383549 A |
2505 | if (pasv_addr.su_family == AF_INET) |
2506 | a = (char *) &pasv_addr.su_sin.sin_addr; | |
2507 | else if (pasv_addr.su_family == AF_INET6 && | |
2508 | IN6_IS_ADDR_V4MAPPED(&pasv_addr.su_sin6.sin6_addr)) | |
2509 | a = (char *) &pasv_addr.su_sin6.sin6_addr.s6_addr[12]; | |
2510 | else | |
2511 | goto pasv_error; | |
2512 | ||
2513 | p = (char *) &pasv_addr.su_port; | |
b7080c8e A |
2514 | |
2515 | #define UC(b) (((int) b) & 0xff) | |
2516 | ||
2517 | reply(227, "Entering Passive Mode (%d,%d,%d,%d,%d,%d)", UC(a[0]), | |
2518 | UC(a[1]), UC(a[2]), UC(a[3]), UC(p[0]), UC(p[1])); | |
2519 | return; | |
2520 | ||
2521 | pasv_error: | |
3e383549 A |
2522 | (void) seteuid((uid_t)pw->pw_uid); |
2523 | (void) close(pdata); | |
2524 | pdata = -1; | |
2525 | perror_reply(425, "Can't open passive connection"); | |
2526 | return; | |
2527 | } | |
2528 | ||
2529 | /* | |
2530 | * Long Passive defined in RFC 1639. | |
2531 | * 228 Entering Long Passive Mode | |
2532 | * (af, hal, h1, h2, h3,..., pal, p1, p2...) | |
2533 | */ | |
2534 | ||
2535 | void | |
2536 | long_passive(cmd, pf) | |
2537 | char *cmd; | |
2538 | int pf; | |
2539 | { | |
2540 | int len; | |
2541 | char *p, *a; | |
2542 | ||
2543 | if (pdata >= 0) /* close old port if one set */ | |
2544 | close(pdata); | |
2545 | ||
2546 | if (pf != PF_UNSPEC) { | |
2547 | if (ctrl_addr.su_family != pf) { | |
2548 | switch (ctrl_addr.su_family) { | |
2549 | case AF_INET: | |
2550 | pf = 1; | |
2551 | break; | |
2552 | case AF_INET6: | |
2553 | pf = 2; | |
2554 | break; | |
2555 | default: | |
2556 | pf = 0; | |
2557 | break; | |
2558 | } | |
2559 | /* | |
2560 | * XXX | |
2561 | * only EPRT/EPSV ready clients will understand this | |
2562 | */ | |
2563 | if (strcmp(cmd, "EPSV") == 0 && pf) { | |
2564 | reply(522, "Network protocol mismatch, " | |
2565 | "use (%d)", pf); | |
2566 | } else | |
2567 | reply(501, "Network protocol mismatch"); /*XXX*/ | |
2568 | ||
2569 | return; | |
2570 | } | |
2571 | } | |
2572 | ||
2573 | pdata = socket(ctrl_addr.su_family, SOCK_STREAM, 0); | |
2574 | if (pdata < 0) { | |
2575 | perror_reply(425, "Can't open passive connection"); | |
2576 | return; | |
2577 | } | |
2578 | ||
2579 | (void) seteuid((uid_t)0); | |
2580 | ||
2581 | pasv_addr = ctrl_addr; | |
2582 | pasv_addr.su_port = 0; | |
2583 | len = pasv_addr.su_len; | |
2584 | ||
2585 | #ifdef IP_PORTRANGE | |
2586 | if (ctrl_addr.su_family == AF_INET) { | |
2587 | int on = restricted_data_ports ? IP_PORTRANGE_HIGH | |
2588 | : IP_PORTRANGE_DEFAULT; | |
2589 | ||
2590 | if (setsockopt(pdata, IPPROTO_IP, IP_PORTRANGE, | |
2591 | (char *)&on, sizeof(on)) < 0) | |
2592 | goto pasv_error; | |
2593 | } | |
2594 | #endif | |
2595 | #ifdef IPV6_PORTRANGE | |
2596 | if (ctrl_addr.su_family == AF_INET6) { | |
2597 | int on = restricted_data_ports ? IPV6_PORTRANGE_HIGH | |
2598 | : IPV6_PORTRANGE_DEFAULT; | |
2599 | ||
2600 | if (setsockopt(pdata, IPPROTO_IPV6, IPV6_PORTRANGE, | |
2601 | (char *)&on, sizeof(on)) < 0) | |
2602 | goto pasv_error; | |
2603 | } | |
2604 | #endif | |
2605 | ||
2606 | if (bind(pdata, (struct sockaddr *)&pasv_addr, len) < 0) | |
2607 | goto pasv_error; | |
2608 | ||
2609 | (void) seteuid((uid_t)pw->pw_uid); | |
2610 | ||
2611 | if (getsockname(pdata, (struct sockaddr *) &pasv_addr, &len) < 0) | |
2612 | goto pasv_error; | |
2613 | if (listen(pdata, 1) < 0) | |
2614 | goto pasv_error; | |
2615 | ||
2616 | #define UC(b) (((int) b) & 0xff) | |
2617 | ||
2618 | if (strcmp(cmd, "LPSV") == 0) { | |
2619 | p = (char *)&pasv_addr.su_port; | |
2620 | switch (pasv_addr.su_family) { | |
2621 | case AF_INET: | |
2622 | a = (char *) &pasv_addr.su_sin.sin_addr; | |
2623 | v4_reply: | |
2624 | reply(228, | |
2625 | "Entering Long Passive Mode (%d,%d,%d,%d,%d,%d,%d,%d,%d)", | |
2626 | 4, 4, UC(a[0]), UC(a[1]), UC(a[2]), UC(a[3]), | |
2627 | 2, UC(p[0]), UC(p[1])); | |
2628 | return; | |
2629 | case AF_INET6: | |
2630 | if (IN6_IS_ADDR_V4MAPPED(&pasv_addr.su_sin6.sin6_addr)) { | |
2631 | a = (char *) &pasv_addr.su_sin6.sin6_addr.s6_addr[12]; | |
2632 | goto v4_reply; | |
2633 | } | |
2634 | a = (char *) &pasv_addr.su_sin6.sin6_addr; | |
2635 | reply(228, | |
2636 | "Entering Long Passive Mode " | |
2637 | "(%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d)", | |
2638 | 6, 16, UC(a[0]), UC(a[1]), UC(a[2]), UC(a[3]), | |
2639 | UC(a[4]), UC(a[5]), UC(a[6]), UC(a[7]), | |
2640 | UC(a[8]), UC(a[9]), UC(a[10]), UC(a[11]), | |
2641 | UC(a[12]), UC(a[13]), UC(a[14]), UC(a[15]), | |
2642 | 2, UC(p[0]), UC(p[1])); | |
2643 | return; | |
2644 | } | |
2645 | } else if (strcmp(cmd, "EPSV") == 0) { | |
2646 | switch (pasv_addr.su_family) { | |
2647 | case AF_INET: | |
2648 | case AF_INET6: | |
2649 | reply(229, "Entering Extended Passive Mode (|||%d|)", | |
2650 | ntohs(pasv_addr.su_port)); | |
2651 | return; | |
2652 | } | |
2653 | } else { | |
2654 | /* more proper error code? */ | |
2655 | } | |
2656 | ||
2657 | pasv_error: | |
2658 | (void) seteuid((uid_t)pw->pw_uid); | |
b7080c8e A |
2659 | (void) close(pdata); |
2660 | pdata = -1; | |
2661 | perror_reply(425, "Can't open passive connection"); | |
2662 | return; | |
2663 | } | |
2664 | ||
2665 | /* | |
2666 | * Generate unique name for file with basename "local". | |
2667 | * The file named "local" is already known to exist. | |
2668 | * Generates failure reply on error. | |
2669 | */ | |
2670 | static char * | |
2671 | gunique(local) | |
2672 | char *local; | |
2673 | { | |
2674 | static char new[MAXPATHLEN]; | |
2675 | struct stat st; | |
2676 | int count; | |
2677 | char *cp; | |
2678 | ||
2679 | cp = strrchr(local, '/'); | |
2680 | if (cp) | |
2681 | *cp = '\0'; | |
2682 | if (stat(cp ? local : ".", &st) < 0) { | |
2683 | perror_reply(553, cp ? local : "."); | |
2684 | return ((char *) 0); | |
2685 | } | |
2686 | if (cp) | |
2687 | *cp = '/'; | |
3e383549 A |
2688 | /* -4 is for the .nn<null> we put on the end below */ |
2689 | (void) snprintf(new, sizeof(new) - 4, "%s", local); | |
b7080c8e A |
2690 | cp = new + strlen(new); |
2691 | *cp++ = '.'; | |
2692 | for (count = 1; count < 100; count++) { | |
2693 | (void)sprintf(cp, "%d", count); | |
2694 | if (stat(new, &st) < 0) | |
2695 | return (new); | |
2696 | } | |
2697 | reply(452, "Unique file name cannot be created."); | |
2698 | return (NULL); | |
2699 | } | |
2700 | ||
2701 | /* | |
2702 | * Format and send reply containing system error number. | |
2703 | */ | |
2704 | void | |
2705 | perror_reply(code, string) | |
2706 | int code; | |
2707 | char *string; | |
2708 | { | |
2709 | ||
2710 | reply(code, "%s: %s.", string, strerror(errno)); | |
2711 | } | |
2712 | ||
2713 | static char *onefile[] = { | |
2714 | "", | |
2715 | 0 | |
2716 | }; | |
2717 | ||
2718 | void | |
2719 | send_file_list(whichf) | |
2720 | char *whichf; | |
2721 | { | |
2722 | struct stat st; | |
2723 | DIR *dirp = NULL; | |
2724 | struct dirent *dir; | |
2725 | FILE *dout = NULL; | |
2726 | char **dirlist, *dirname; | |
2727 | int simple = 0; | |
2728 | int freeglob = 0; | |
2729 | glob_t gl; | |
2730 | ||
2731 | if (strpbrk(whichf, "~{[*?") != NULL) { | |
2732 | int flags = GLOB_BRACE|GLOB_NOCHECK|GLOB_QUOTE|GLOB_TILDE; | |
2733 | ||
2734 | memset(&gl, 0, sizeof(gl)); | |
3e383549 A |
2735 | gl.gl_matchc = MAXGLOBARGS; |
2736 | #if !defined(GLOB_MAXPATH) | |
2737 | #define GLOB_MAXPATH 0x1000 | |
2738 | #endif | |
2739 | flags |= GLOB_MAXPATH; | |
b7080c8e A |
2740 | freeglob = 1; |
2741 | if (glob(whichf, flags, 0, &gl)) { | |
2742 | reply(550, "not found"); | |
2743 | goto out; | |
2744 | } else if (gl.gl_pathc == 0) { | |
2745 | errno = ENOENT; | |
2746 | perror_reply(550, whichf); | |
2747 | goto out; | |
2748 | } | |
2749 | dirlist = gl.gl_pathv; | |
2750 | } else { | |
2751 | onefile[0] = whichf; | |
2752 | dirlist = onefile; | |
2753 | simple = 1; | |
2754 | } | |
2755 | ||
2756 | if (setjmp(urgcatch)) { | |
2757 | transflag = 0; | |
2758 | goto out; | |
2759 | } | |
3e383549 | 2760 | while ((dirname = *dirlist++)) { |
b7080c8e A |
2761 | if (stat(dirname, &st) < 0) { |
2762 | /* | |
2763 | * If user typed "ls -l", etc, and the client | |
2764 | * used NLST, do what the user meant. | |
2765 | */ | |
2766 | if (dirname[0] == '-' && *dirlist == NULL && | |
2767 | transflag == 0) { | |
3e383549 | 2768 | retrieve(_PATH_LS " %s", dirname); |
b7080c8e A |
2769 | goto out; |
2770 | } | |
2771 | perror_reply(550, whichf); | |
2772 | if (dout != NULL) { | |
2773 | (void) fclose(dout); | |
2774 | transflag = 0; | |
2775 | data = -1; | |
2776 | pdata = -1; | |
2777 | } | |
2778 | goto out; | |
2779 | } | |
2780 | ||
2781 | if (S_ISREG(st.st_mode)) { | |
2782 | if (dout == NULL) { | |
2783 | dout = dataconn("file list", (off_t)-1, "w"); | |
2784 | if (dout == NULL) | |
2785 | goto out; | |
2786 | transflag++; | |
2787 | } | |
2788 | fprintf(dout, "%s%s\n", dirname, | |
2789 | type == TYPE_A ? "\r" : ""); | |
2790 | byte_count += strlen(dirname) + 1; | |
2791 | continue; | |
2792 | } else if (!S_ISDIR(st.st_mode)) | |
2793 | continue; | |
2794 | ||
2795 | if ((dirp = opendir(dirname)) == NULL) | |
2796 | continue; | |
2797 | ||
2798 | while ((dir = readdir(dirp)) != NULL) { | |
2799 | char nbuf[MAXPATHLEN]; | |
2800 | ||
2801 | if (dir->d_name[0] == '.' && dir->d_namlen == 1) | |
2802 | continue; | |
2803 | if (dir->d_name[0] == '.' && dir->d_name[1] == '.' && | |
2804 | dir->d_namlen == 2) | |
2805 | continue; | |
2806 | ||
3e383549 A |
2807 | snprintf(nbuf, sizeof(nbuf), |
2808 | "%s/%s", dirname, dir->d_name); | |
b7080c8e A |
2809 | |
2810 | /* | |
2811 | * We have to do a stat to insure it's | |
2812 | * not a directory or special file. | |
2813 | */ | |
2814 | if (simple || (stat(nbuf, &st) == 0 && | |
2815 | S_ISREG(st.st_mode))) { | |
2816 | if (dout == NULL) { | |
2817 | dout = dataconn("file list", (off_t)-1, | |
2818 | "w"); | |
2819 | if (dout == NULL) | |
2820 | goto out; | |
2821 | transflag++; | |
2822 | } | |
2823 | if (nbuf[0] == '.' && nbuf[1] == '/') | |
2824 | fprintf(dout, "%s%s\n", &nbuf[2], | |
2825 | type == TYPE_A ? "\r" : ""); | |
2826 | else | |
2827 | fprintf(dout, "%s%s\n", nbuf, | |
2828 | type == TYPE_A ? "\r" : ""); | |
2829 | byte_count += strlen(nbuf) + 1; | |
2830 | } | |
2831 | } | |
2832 | (void) closedir(dirp); | |
2833 | } | |
2834 | ||
2835 | if (dout == NULL) | |
2836 | reply(550, "No files found."); | |
2837 | else if (ferror(dout) != 0) | |
2838 | perror_reply(550, "Data connection"); | |
2839 | else | |
2840 | reply(226, "Transfer complete."); | |
2841 | ||
2842 | transflag = 0; | |
2843 | if (dout != NULL) | |
2844 | (void) fclose(dout); | |
2845 | data = -1; | |
2846 | pdata = -1; | |
2847 | out: | |
2848 | if (freeglob) { | |
2849 | freeglob = 0; | |
2850 | globfree(&gl); | |
2851 | } | |
2852 | } | |
2853 | ||
3e383549 A |
2854 | void |
2855 | reapchild(signo) | |
2856 | int signo; | |
2857 | { | |
2858 | while (wait3(NULL, WNOHANG, NULL) > 0); | |
2859 | } | |
2860 | ||
2861 | #ifdef OLD_SETPROCTITLE | |
b7080c8e A |
2862 | /* |
2863 | * Clobber argv so ps will show what we're doing. (Stolen from sendmail.) | |
2864 | * Warning, since this is usually started from inetd.conf, it often doesn't | |
2865 | * have much of an environment or arglist to overwrite. | |
2866 | */ | |
2867 | void | |
2868 | #if __STDC__ | |
2869 | setproctitle(const char *fmt, ...) | |
2870 | #else | |
2871 | setproctitle(fmt, va_alist) | |
2872 | char *fmt; | |
2873 | va_dcl | |
2874 | #endif | |
2875 | { | |
2876 | int i; | |
2877 | va_list ap; | |
2878 | char *p, *bp, ch; | |
2879 | char buf[LINE_MAX]; | |
2880 | ||
2881 | #if __STDC__ | |
2882 | va_start(ap, fmt); | |
2883 | #else | |
2884 | va_start(ap); | |
2885 | #endif | |
2886 | (void)vsnprintf(buf, sizeof(buf), fmt, ap); | |
2887 | ||
2888 | /* make ps print our process name */ | |
2889 | p = Argv[0]; | |
2890 | *p++ = '-'; | |
2891 | ||
2892 | i = strlen(buf); | |
2893 | if (i > LastArgv - p - 2) { | |
2894 | i = LastArgv - p - 2; | |
2895 | buf[i] = '\0'; | |
2896 | } | |
2897 | bp = buf; | |
2898 | while (ch = *bp++) | |
2899 | if (ch != '\n' && ch != '\r') | |
2900 | *p++ = ch; | |
2901 | while (p < LastArgv) | |
2902 | *p++ = ' '; | |
2903 | } | |
3e383549 A |
2904 | #endif /* OLD_SETPROCTITLE */ |
2905 | ||
2906 | static void | |
2907 | logxfer(name, size, start) | |
2908 | char *name; | |
2909 | long size; | |
2910 | long start; | |
2911 | { | |
2912 | char buf[1024]; | |
2913 | char path[MAXPATHLEN + 1]; | |
2914 | time_t now; | |
2915 | ||
2916 | if (statfd >= 0 && getwd(path) != NULL) { | |
2917 | time(&now); | |
2918 | snprintf(buf, sizeof(buf), "%.20s!%s!%s!%s/%s!%ld!%ld\n", | |
2919 | ctime(&now)+4, ident, remotehost, | |
2920 | path, name, size, now - start + (now == start)); | |
2921 | write(statfd, buf, strlen(buf)); | |
2922 | } | |
2923 | } |