xref: /onnv-gate/usr/src/cmd/cmd-inet/usr.sbin/in.ftpd/extensions.c (revision 12333:f835be4545bb)
1 /*
2  * Copyright (c) 2001, 2010, Oracle and/or its affiliates. All rights reserved.
3  */
4 
5 /****************************************************************************
6 
7   Copyright (c) 1999,2000 WU-FTPD Development Group.
8   All rights reserved.
9 
10   Portions Copyright (c) 1980, 1985, 1988, 1989, 1990, 1991, 1993, 1994
11     The Regents of the University of California.
12   Portions Copyright (c) 1993, 1994 Washington University in Saint Louis.
13   Portions Copyright (c) 1996, 1998 Berkeley Software Design, Inc.
14   Portions Copyright (c) 1989 Massachusetts Institute of Technology.
15   Portions Copyright (c) 1998 Sendmail, Inc.
16   Portions Copyright (c) 1983, 1995, 1996, 1997 Eric P.  Allman.
17   Portions Copyright (c) 1997 by Stan Barber.
18   Portions Copyright (c) 1997 by Kent Landfield.
19   Portions Copyright (c) 1991, 1992, 1993, 1994, 1995, 1996, 1997
20     Free Software Foundation, Inc.
21 
22   Use and distribution of this software and its source code are governed
23   by the terms and conditions of the WU-FTPD Software License ("LICENSE").
24 
25   If you did not receive a copy of the license, it may be obtained online
26   at http://www.wu-ftpd.org/license.html.
27 
28   $Id: extensions.c,v 1.48 2000/07/01 18:17:38 wuftpd Exp $
29 
30 ****************************************************************************/
31 #include "config.h"
32 
33 #include <stdio.h>
34 #include <errno.h>
35 #include <string.h>
36 
37 #ifdef HAVE_SYS_SYSLOG_H
38 #include <sys/syslog.h>
39 #endif
40 #if defined(HAVE_SYSLOG_H) || (!defined(AUTOCONF) && !defined(HAVE_SYS_SYSLOG_H))
41 #include <syslog.h>
42 #endif
43 
44 #ifdef TIME_WITH_SYS_TIME
45 #include <time.h>
46 #include <sys/time.h>
47 #else
48 #ifdef HAVE_SYS_TIME_H
49 #include <sys/time.h>
50 #else
51 #include <time.h>
52 #endif
53 #endif
54 #include <pwd.h>
55 #include <setjmp.h>
56 #include <grp.h>
57 
58 #include <sys/types.h>
59 #include <sys/stat.h>
60 #include <sys/file.h>
61 #include <sys/param.h>
62 
63 #ifdef HAVE_SYS_FS_UFS_QUOTA_H
64 #include <sys/fs/ufs_quota.h>
65 #elif defined(HAVE_UFS_UFS_QUOTA_H)
66 #include <ufs/ufs/quota.h>
67 #elif defined(HAVE_UFS_QUOTA_H)
68 #include <ufs/quota.h>
69 #elif defined(HAVE_SYS_MNTENT_H)
70 #include <sys/mntent.h>
71 #elif defined(HAVE_SYS_MNTTAB_H)
72 #include <sys/mnttab.h>
73 #endif
74 
75 #if defined(HAVE_STATVFS)
76 #include <sys/statvfs.h>
77 #elif defined(HAVE_SYS_VFS)
78 #include <sys/vfs.h>
79 #elif defined(HAVE_SYS_MOUNT)
80 #include <sys/mount.h>
81 #endif
82 
83 #include <arpa/ftp.h>
84 
85 #ifdef HAVE_PATHS_H
86 #include <paths.h>
87 #endif
88 #include "pathnames.h"
89 #include "extensions.h"
90 #include "wu_fnmatch.h"
91 #include "proto.h"
92 
93 #if defined(HAVE_FTW)
94 #include <ftw.h>
95 #else
96 #include "support/ftw.h"
97 #endif
98 
99 #ifdef QUOTA
100 struct dqblk quota;
101 char *time_quota(long curstate, long softlimit, long timelimit, char *timeleft);
102 #endif
103 
104 #ifdef HAVE_REGEX_H
105 #include <regex.h>
106 #endif
107 
108 #if defined(HAVE_REGEX) && defined(SVR4) && ! (defined(NO_LIBGEN))
109 #include <libgen.h>
110 #endif
111 
112 extern int type, transflag, ftwflag, authenticated, autospout_free, data,
113     pdata, anonymous, guest;
114 extern char chroot_path[], guestpw[];
115 
116 #ifdef TRANSFER_COUNT
117 extern off_t data_count_in;
118 extern off_t data_count_out;
119 #ifdef TRANSFER_LIMIT
120 extern off_t data_limit_raw_in;
121 extern off_t data_limit_raw_out;
122 extern off_t data_limit_raw_total;
123 extern off_t data_limit_data_in;
124 extern off_t data_limit_data_out;
125 extern off_t data_limit_data_total;
126 #ifdef RATIO /* 1998/08/06 K.Wakui */
127 #define TRUNC_KB(n)   ((n)/1024+(((n)%1024)?1:0))
128 extern time_t	login_time;
129 extern time_t	limit_time;
130 extern off_t    total_free_dl;
131 extern int      upload_download_rate;
132 #endif /* RATIO */
133 #endif
134 #endif
135 
136 #ifdef OTHER_PASSWD
137 #include "getpwnam.h"
138 extern char _path_passwd[];
139 #endif
140 
141 #ifdef LOG_FAILED
142 extern char the_user[];
143 #endif
144 
145 extern char *globerr, remotehost[];
146 #ifdef THROUGHPUT
147 extern char remoteaddr[];
148 #endif
149 
150 #ifndef HAVE_REGEX
151 char *re_comp(const char *regex);
152 int re_exec(const char *p1);
153 #endif
154 
155 char shuttime[30], denytime[30], disctime[30];
156 
157 FILE *dout;
158 
159 time_t newer_time;
160 
161 int show_fullinfo;
162 
163 /* This always was a bug, because neither st_size nor time_t were required to
164    be compatible with int, but needs fixing properly for C9X. */
165 
166 /* Some systems use one format, some another.  This takes care of the garbage */
167 /* Do the system specific stuff only if we aren't autoconfed */
168 #if !defined(L_FORMAT)
169 #if (defined(BSD) && (BSD >= 199103)) && !defined(LONGOFF_T)
170 #define L_FORMAT "qd"
171 #else
172 #define L_FORMAT "d"
173 #endif
174 #endif
175 #if !defined(T_FORMAT)
176 #define T_FORMAT "d"
177 #endif
178 #if !defined(PW_UID_FORMAT)
179 #define PW_UID_FORMAT "d"
180 #endif
181 #if !defined(GR_GID_FORMAT)
182 #define GR_GID_FORMAT "d"
183 #endif
184 
185 int snprintf(char *str, size_t count, const char *fmt,...);
186 
187 #ifdef SITE_NEWER
check_newer(const char * path,const struct stat * st,int flag)188 int check_newer(const char *path, const struct stat *st, int flag)
189 {
190     if (st->st_mtime > newer_time) {
191 	if (show_fullinfo != 0) {
192 	    if (flag == FTW_F || flag == FTW_D) {
193 		fprintf(dout, "%s %" L_FORMAT " %" T_FORMAT " %s\n",
194 			flag == FTW_F ? "F" : "D",
195 			st->st_size, st->st_mtime, path);
196 	    }
197 	}
198 	else if (flag == FTW_F)
199 	    fprintf(dout, "%s\n", path);
200     }
201 
202     /* When an ABOR has been received (which sets ftwflag > 1) return a
203      * non-zero value which causes ftw to stop tree traversal and return.
204      */
205 
206     return (ftwflag > 1 ? 1 : 0);
207 }
208 #endif
209 
210 #if defined(HAVE_STATVFS)
getSize(char * s)211 long getSize(char *s)
212 {
213     struct statvfs buf;
214 
215     if (statvfs(s, &buf) != 0)
216 	return (0);
217 
218     return (buf.f_bavail * buf.f_frsize / 1024);
219 }
220 #elif defined(HAVE_SYS_VFS) || defined (HAVE_SYS_MOUNT)
getSize(char * s)221 long getSize(char *s)
222 {
223     struct statfs buf;
224 
225     if (statfs(s, &buf) != 0)
226 	return (0);
227 
228     return (buf.f_bavail * buf.f_bsize / 1024);
229 }
230 #endif
231 
232 /*************************************************************************/
233 /* FUNCTION  : msg_massage                                               */
234 /* PURPOSE   : Scan a message line for magic cookies, replacing them as  */
235 /*             needed.                                                   */
236 /* ARGUMENTS : pointer input and output buffers                          */
237 /*************************************************************************/
238 
msg_massage(const char * inbuf,char * outbuf,size_t outlen)239 void msg_massage(const char *inbuf, char *outbuf, size_t outlen)
240 {
241     const char *inptr = inbuf;
242     char *outptr = outbuf;
243 #ifdef QUOTA
244     char timeleft[80];
245 #endif
246     char buffer[MAXPATHLEN];
247     time_t curtime;
248     int limit;
249 #ifndef LOG_FAILED
250     extern struct passwd *pw;
251 #endif
252     struct aclmember *entry;
253 
254 #ifdef VIRTUAL
255     extern int virtual_mode;
256     extern int virtual_ftpaccess;
257     extern char virtual_email[];
258 #endif
259     extern char hostname[];
260     extern char authuser[];
261 
262     (void) acl_getclass(buffer);
263     limit = acl_getlimit(buffer, NULL);
264 
265     while ((outlen > 1) && (*inptr != '\0')) {
266 	if (*inptr != '%') {
267 	    *outptr++ = *inptr;
268 	    outlen -= 1;
269 	}
270 	else {
271 	    entry = NULL;
272 	    switch (*++inptr) {
273 	    case 'E':
274 #ifdef VIRTUAL
275 		if (virtual_mode && !virtual_ftpaccess && virtual_email[0] != '\0')
276 		    snprintf(outptr, outlen, "%s", virtual_email);
277 		else
278 #endif
279 		if ((getaclentry("email", &entry)) && ARG0)
280 		    snprintf(outptr, outlen, "%s", ARG0);
281 		else
282 		    *outptr = '\0';
283 		break;
284 
285 	    case 'N':
286 		snprintf(outptr, outlen, "%d", acl_countusers(buffer));
287 		break;
288 
289 	    case 'M':
290 		if (limit == -1)
291 		    strncpy(outptr, "unlimited", outlen);
292 		else
293 		    snprintf(outptr, outlen, "%d", limit);
294 		break;
295 
296 	    case 'T':
297 		(void) time(&curtime);
298 		strncpy(outptr, ctime(&curtime), outlen);
299 		if (outlen > 24)
300 		    *(outptr + 24) = '\0';
301 		break;
302 
303 	    case 'F':
304 #if defined(HAVE_STATVFS) || defined(HAVE_SYS_VFS) || defined(HAVE_SYS_MOUNT)
305 		snprintf(outptr, outlen, "%lu", (long) getSize("."));
306 #else
307 		*outptr = '\0';
308 #endif
309 		break;
310 
311 	    case 'C':
312 #ifdef HAVE_GETCWD
313 		(void) getcwd(outptr, outlen);
314 #else
315 #error	wu-ftpd on this platform has security deficiencies!!!
316 		(void) getwd(outptr);
317 #endif
318 		break;
319 
320 	    case 'R':
321 		strncpy(outptr, remotehost, outlen);
322 		break;
323 
324 	    case 'L':
325 		strncpy(outptr, hostname, outlen);
326 		break;
327 
328 	    case 'U':
329 		if (xferdone && anonymous)
330 		    strncpy(outptr, guestpw, outlen);
331 		else
332 #ifdef LOG_FAILED
333 		    strncpy(outptr, the_user, outlen);
334 #else /* LOG_FAILED */
335 		    strncpy(outptr,
336 			    (pw == NULL) ? "[unknown]" : pw->pw_name, outlen);
337 #endif /* LOG_FAILED */
338 		break;
339 
340 	    case 's':
341 		strncpy(outptr, shuttime, outlen);
342 		if (outlen > 24)
343 		    *(outptr + 24) = '\0';
344 		break;
345 
346 	    case 'd':
347 		strncpy(outptr, disctime, outlen);
348 		if (outlen > 24)
349 		    *(outptr + 24) = '\0';
350 		break;
351 
352 	    case 'r':
353 		strncpy(outptr, denytime, outlen);
354 		if (outlen > 24)
355 		    *(outptr + 24) = '\0';
356 		break;
357 
358 /* KH : cookie %u for RFC931 name */
359 	    case 'u':
360 		if (authenticated)
361 		    strncpy(outptr, authuser, outlen);
362 		else {
363 		    if (xferdone)
364 			snprintf(outptr, outlen, "%c", '*');
365 		    else
366 			strncpy(outptr, "[unknown]", outlen);
367 		}
368 		break;
369 
370 #ifdef QUOTA
371 	    case 'B':
372 #ifdef QUOTA_BLOCKS		/* 1024-blocks instead of 512-blocks */
373 		snprintf(outptr, outlen, "%ld", quota.dqb_bhardlimit % 2 ?
374 			 (long) (quota.dqb_bhardlimit / 2 + 1) : (long) (quota.dqb_bhardlimit / 2));
375 #else
376 		snprintf(outptr, outlen, "%ld", (long) quota.dqb_bhardlimit);
377 #endif
378 		break;
379 
380 	    case 'b':
381 #ifdef QUOTA_BLOCKS		/* 1024-blocks instead of 512-blocks */
382 		snprintf(outptr, outlen, "%ld", quota.dqb_bsoftlimit % 2 ?
383 			 (long) (quota.dqb_bsoftlimit / 2 + 1) : (long) (quota.dqb_bsoftlimit / 2));
384 #else
385 		snprintf(outptr, outlen, "%ld", (long) quota.dqb_bsoftlimit);
386 #endif
387 		break;
388 
389 	    case 'Q':
390 #ifdef QUOTA_BLOCKS		/* 1024-blocks instead of 512-blocks */
391 		snprintf(outptr, outlen, "%ld", quota.dqb_curblocks % 2 ?
392 			 (long) (quota.dqb_curblocks / 2 + 1) : (long) (quota.dqb_curblocks / 2));
393 #else
394 		snprintf(outptr, outlen, "%ld", quota.dqb_curblocks);
395 #endif
396 		break;
397 
398 	    case 'I':
399 #if defined(QUOTA_INODE)
400 		snprintf(outptr, outlen, "%d", quota.dqb_ihardlimit);
401 #else
402 		snprintf(outptr, outlen, "%ld", (long) quota.dqb_fhardlimit);
403 #endif
404 		break;
405 
406 	    case 'i':
407 #if defined(QUOTA_INODE)
408 		snprintf(outptr, outlen, "%d", quota.dqb_isoftlimit);
409 #else
410 		snprintf(outptr, outlen, "%ld", (long) quota.dqb_fsoftlimit);
411 #endif
412 		break;
413 
414 	    case 'q':
415 #if defined(QUOTA_INODE)
416 		snprintf(outptr, outlen, "%d", quota.dqb_curinodes);
417 #else
418 		snprintf(outptr, outlen, "%ld", (long) quota.dqb_curfiles);
419 #endif
420 		break;
421 
422 	    case 'H':
423 		time_quota(quota.dqb_curblocks, quota.dqb_bsoftlimit,
424 #if defined(QUOTA_INODE)
425 			   quota.dqb_btime, timeleft);
426 #else
427 			   quota.dqb_btimelimit, timeleft);
428 #endif
429 		strncpy(outptr, timeleft, outlen);
430 		break;
431 
432 	    case 'h':
433 #if defined(QUOTA_INODE)
434 		time_quota(quota.dqb_curinodes, quota.dqb_isoftlimit,
435 			   quota.dqb_itime, timeleft);
436 #else
437 		time_quota(quota.dqb_curfiles, quota.dqb_fsoftlimit,
438 			   quota.dqb_ftimelimit, timeleft);
439 #endif
440 		strncpy(outptr, timeleft, outlen);
441 		break;
442 #endif /* QUOTA */
443 
444 	    case '%':
445 		*outptr++ = '%';
446 		outlen -= 1;
447 		*outptr = '\0';
448 		break;
449 
450 #ifdef TRANSFER_COUNT
451 #ifdef TRANSFER_LIMIT
452 #ifdef RATIO
453             case 'x':
454                 switch (*++inptr) {
455                 case 'u':       /* upload bytes */
456                     sprintf(outptr,"%" L_FORMAT, TRUNC_KB(data_count_in) );
457                     break;
458                 case 'd':       /* download bytes */
459                     sprintf(outptr,"%" L_FORMAT, TRUNC_KB(data_count_out) );
460                     break;
461                 case 'R':       /* rate 1:n */
462                     if( upload_download_rate > 0 ) {
463                         sprintf(outptr,"%d", upload_download_rate );
464                     }
465                     else {
466                         strcpy(outptr,"free");
467                     }
468                     break;
469                 case 'c':       /* credit bytes */
470                     if( upload_download_rate > 0 ) {
471                         off_t credit=( data_count_in * upload_download_rate) - (data_count_out - total_free_dl);
472                         sprintf(outptr,"%" L_FORMAT, TRUNC_KB(credit) );
473                     }
474                     else {
475                         strcpy(outptr,"unlimited");
476                     }
477                     break;
478                 case 'T':       /* time limit (minutes) */
479                     if( limit_time > 0 ) {
480                         sprintf(outptr,"%d", limit_time );
481                     }
482                     else {
483                         strcpy(outptr,"unlimited");
484                     }
485                     break;
486                 case 'E':       /* elapsed time from loggedin (minutes) */
487                     sprintf(outptr,"%d", (time(NULL)-login_time)/60 );
488                     break;
489                 case 'L':       /* times left until force logout (minutes) */
490                     if( limit_time > 0 ) {
491                         sprintf(outptr,"%d", limit_time-(time(NULL)-login_time)/60 );
492                     }
493                     else {
494                         strcpy(outptr,"unlimited");
495                     }
496                     break;
497                 case 'U':       /* upload limit */
498 		    if( data_limit_raw_in > 0 ) {
499 			sprintf(outptr,"%" L_FORMAT, TRUNC_KB(data_limit_raw_in));
500 		    }
501 		    else if( data_limit_data_in > 0 ) {
502 			sprintf(outptr,"%" L_FORMAT, TRUNC_KB(data_limit_data_in));
503 		    }
504 		    else if( data_limit_raw_total > 0 ) {
505 			sprintf(outptr,"%" L_FORMAT, TRUNC_KB(data_limit_raw_total));
506 		    }
507 		    else if( data_limit_data_total > 0 ) {
508 			sprintf(outptr,"%" L_FORMAT, TRUNC_KB(data_limit_data_total));
509 		    }
510 		    else {
511 			strcpy(outptr, "unlimited");
512 		    }
513                     break;
514                 case 'D':       /* download limit */
515 		    if( data_limit_raw_out > 0 ) {
516 			sprintf(outptr,"%" L_FORMAT, TRUNC_KB(data_limit_raw_out));
517 		    }
518 		    else if( data_limit_data_out > 0 ) {
519 			sprintf(outptr,"%" L_FORMAT, TRUNC_KB(data_limit_data_out));
520 		    }
521 		    else if( data_limit_raw_total > 0 ) {
522 			sprintf(outptr,"%" L_FORMAT, TRUNC_KB(data_limit_raw_total));
523 		    }
524 		    else if( data_limit_data_total > 0 ) {
525 			sprintf(outptr,"%" L_FORMAT, TRUNC_KB(data_limit_data_total));
526 		    }
527 		    else {
528 			strcpy(outptr, "unlimited");
529 		    }
530                     break;
531                 default:
532                     strcpy(outptr,"%??");
533                     break;
534                 }
535                 break;
536 #endif /* RATIO */
537 #endif
538 #endif
539 		/* File transfer logging (xferlog) */
540 		case 'X':
541 		    if (xferdone) {  /* only if a transfer has just occurred */
542 			switch (*++inptr) {
543 			case 't':
544 			    snprintf(outptr, outlen, "%d", xfervalues.transfer_time);
545 			    break;
546 			case 's':
547 			    snprintf(outptr, outlen, "%" L_FORMAT, xfervalues.filesize);
548 			    break;
549 			case 'n':
550 			    snprintf(outptr, outlen, "%" L_FORMAT, xfervalues.transfer_bytes);
551 			    break;
552 			case 'P': /* absolute pathname */
553 			    /* FALLTHROUGH */
554 			case 'p': /* chroot-relative pathname */
555 			{
556 			    char namebuf[MAXPATHLEN];
557 			    int loop;
558 
559 			    if (*inptr == 'P')
560 				wu_realpath(xfervalues.filename, namebuf, chroot_path);
561 			    else
562 				fb_realpath(xfervalues.filename, namebuf);
563 			    for (loop = 0; namebuf[loop]; loop++) {
564 				if (isspace(namebuf[loop]) || iscntrl(namebuf[loop]))
565 				    namebuf[loop] = '_';
566 			    }
567 			    snprintf(outptr, outlen, "%s", namebuf);
568 			    break;
569 			}
570 			case 'y':
571 			    snprintf(outptr, outlen, "%c", xfervalues.transfer_type);
572 			    break;
573 			case 'f':
574 			    snprintf(outptr, outlen, "%s", xfervalues.special_action);
575 			    break;
576 			case 'd':
577 			    snprintf(outptr, outlen, "%c", xfervalues.transfer_direction);
578 			    break;
579 			case 'm':
580 			    snprintf(outptr, outlen, "%c", xfervalues.access_mode);
581 			    break;
582 			case 'a':
583 			    snprintf(outptr, outlen, "%d", xfervalues.auth);
584 			    break;
585 			case 'r':
586 			    snprintf(outptr, outlen, "%" L_FORMAT, xfervalues.restart_offset);
587 			    break;
588 			case 'c':
589 			    snprintf(outptr, outlen, "%c", xfervalues.completion);
590 			    break;
591 			default:
592 			    snprintf(outptr, outlen, "%%X%c", *inptr);
593 			    break;
594 			}
595 		    }
596 		    else
597 			snprintf(outptr, outlen, "%%%c", *inptr);
598 		    break;
599 
600 	    default:
601 		*outptr++ = '%';
602 		outlen -= 1;
603 		if (outlen > 1) {
604 		    *outptr++ = *inptr;
605 		    outlen -= 1;
606 		}
607 		*outptr = '\0';
608 		break;
609 	    }
610 	    outptr[outlen - 1] = '\0';
611 	    while (*outptr) {
612 		outptr++;
613 		outlen -= 1;
614 	    }
615 	}
616 	inptr++;
617     }
618     if (outlen > 0)
619 	*outptr = '\0';
620 }
621 
622 /*************************************************************************/
623 /* FUNCTION  : cwd_beenhere                                              */
624 /* PURPOSE   : Return 1 if the user has already visited this directory   */
625 /*             via C_WD.                                                 */
626 /* ARGUMENTS : a power-of-two directory function code (README, MESSAGE)  */
627 /*************************************************************************/
628 
cwd_beenhere(int dircode)629 int cwd_beenhere(int dircode)
630 {
631     struct dirlist {
632 	struct dirlist *next;
633 	int dircode;
634 	char dirname[1];
635     };
636 
637     static struct dirlist *head = NULL;
638     struct dirlist *curptr;
639     char cwd[MAXPATHLEN];
640 
641     (void) fb_realpath(".", cwd);
642 
643     for (curptr = head; curptr != NULL; curptr = curptr->next)
644 	if (strcmp(curptr->dirname, cwd) == 0) {
645 	    if (!(curptr->dircode & dircode)) {
646 		curptr->dircode |= dircode;
647 		return (0);
648 	    }
649 	    return (1);
650 	}
651     curptr = (struct dirlist *) malloc(strlen(cwd) + 1 + sizeof(struct dirlist));
652 
653     if (curptr != NULL) {
654 	curptr->next = head;
655 	head = curptr;
656 	curptr->dircode = dircode;
657 	strcpy(curptr->dirname, cwd);
658     }
659     return (0);
660 }
661 
662 /*************************************************************************/
663 /* FUNCTION  : show_banner                                               */
664 /* PURPOSE   : Display a banner on the user's terminal before login      */
665 /* ARGUMENTS : reply code to use                                         */
666 /*************************************************************************/
667 
show_banner(int msgcode)668 void show_banner(int msgcode)
669 {
670     char *crptr, linebuf[1024], outbuf[1024];
671     struct aclmember *entry = NULL;
672     FILE *infile;
673 
674 #ifdef VIRTUAL
675     extern int virtual_mode;
676     extern int virtual_ftpaccess;
677     extern char virtual_banner[];
678 
679     if (virtual_mode && !virtual_ftpaccess) {
680 	infile = fopen(virtual_banner, "r");
681 	if (infile) {
682 	    while (fgets(linebuf, sizeof(linebuf), infile) != NULL) {
683 		if ((crptr = strchr(linebuf, '\n')) != NULL)
684 		    *crptr = '\0';
685 		msg_massage(linebuf, outbuf, sizeof(outbuf));
686 		lreply(msgcode, "%s", outbuf);
687 	    }
688 	    fclose(infile);
689 #ifndef NO_SUCKING_NEWLINES
690 	    lreply(msgcode, "");
691 #endif
692 	}
693     }
694     else {
695 #endif
696 	/* banner <path> */
697 	while (getaclentry("banner", &entry)) {
698 	    if (!ARG0)
699 		continue;
700 	    infile = fopen(ARG0, "r");
701 	    if (infile) {
702 		while (fgets(linebuf, sizeof(linebuf), infile) != NULL) {
703 		    if ((crptr = strchr(linebuf, '\n')) != NULL)
704 			*crptr = '\0';
705 		    msg_massage(linebuf, outbuf, sizeof(outbuf));
706 		    lreply(msgcode, "%s", outbuf);
707 		}
708 		fclose(infile);
709 #ifndef NO_SUCKING_NEWLINES
710 		lreply(msgcode, "");
711 #endif
712 	    }
713 	}
714 #ifdef VIRTUAL
715     }
716 #endif
717 }
718 /*************************************************************************/
719 /* FUNCTION  : show_message                                              */
720 /* PURPOSE   : Display a message on the user's terminal if the current   */
721 /*             conditions are right                                      */
722 /* ARGUMENTS : reply code to use, LOG_IN|CMD                             */
723 /*************************************************************************/
724 
show_message(int msgcode,int mode)725 void show_message(int msgcode, int mode)
726 {
727     char *crptr, linebuf[1024], outbuf[1024], class[MAXPATHLEN], cwd[MAXPATHLEN];
728     int show, which;
729     struct aclmember *entry = NULL;
730     FILE *infile;
731 
732     if (mode == C_WD && cwd_beenhere(1) != 0)
733 	return;
734 
735 #ifdef HAVE_GETCWD
736     (void) getcwd(cwd, MAXPATHLEN - 1);
737 #else
738     (void) getwd(cwd);
739 #endif
740     (void) acl_getclass(class);
741 
742     /* message <path> [<when> [<class>]] */
743     while (getaclentry("message", &entry)) {
744 	if (!ARG0)
745 	    continue;
746 	show = 0;
747 
748 	if (mode == LOG_IN && (!ARG1 || !strcasecmp(ARG1, "login")))
749 	    if (!ARG2)
750 		show++;
751 	    else {
752 		for (which = 2; (which < MAXARGS) && ARG[which]; which++)
753 		    if (strcasecmp(class, ARG[which]) == 0)
754 			show++;
755 	    }
756 	if (mode == C_WD && ARG1 && !strncasecmp(ARG1, "cwd=", 4) &&
757 	    (!strcmp((ARG1) + 4, cwd) || *(ARG1 + 4) == '*' ||
758 	     !wu_fnmatch((ARG1) + 4, cwd, FNM_PATHNAME)))
759 	    if (!ARG2)
760 		show++;
761 	    else {
762 		for (which = 2; (which < MAXARGS) && ARG[which]; which++)
763 		    if (strcasecmp(class, ARG[which]) == 0)
764 			show++;
765 	    }
766 	if (show && (int) strlen(ARG0) > 0) {
767 	    infile = fopen(ARG0, "r");
768 	    if (infile) {
769 		while (fgets(linebuf, sizeof(linebuf), infile) != NULL) {
770 		    if ((crptr = strchr(linebuf, '\n')) != NULL)
771 			*crptr = '\0';
772 		    msg_massage(linebuf, outbuf, sizeof(outbuf));
773 		    lreply(msgcode, "%s", outbuf);
774 		}
775 		fclose(infile);
776 #ifndef NO_SUCKING_NEWLINES
777 		lreply(msgcode, "");
778 #endif
779 	    }
780 	}
781     }
782 }
783 
784 /*************************************************************************/
785 /* FUNCTION  : show_readme                                               */
786 /* PURPOSE   : Display a message about a README file to the user if the  */
787 /*             current conditions are right                              */
788 /* ARGUMENTS : pointer to ACL buffer, reply code, LOG_IN|C_WD            */
789 /*************************************************************************/
790 
show_readme(int code,int mode)791 void show_readme(int code, int mode)
792 {
793     char **filelist, **sfilelist, class[MAXPATHLEN], cwd[MAXPATHLEN];
794     int show, which, days;
795     time_t clock;
796 
797     struct stat buf;
798     struct tm *tp;
799     struct aclmember *entry = NULL;
800 
801     if (cwd_beenhere(2) != 0)
802 	return;
803 
804 #ifdef HAVE_GETCWD
805     (void) getcwd(cwd, MAXPATHLEN - 1);
806 #else
807     (void) getwd(cwd);
808 #endif
809     (void) acl_getclass(class);
810 
811     /* readme  <path> {<when>} */
812     while (getaclentry("readme", &entry)) {
813 	if (!ARG0)
814 	    continue;
815 	show = 0;
816 
817 	if (mode == LOG_IN && (!ARG1 || !strcasecmp(ARG1, "login")))
818 	    if (!ARG2)
819 		show++;
820 	    else {
821 		for (which = 2; (which < MAXARGS) && ARG[which]; which++)
822 		    if (strcasecmp(class, ARG[which]) == 0)
823 			show++;
824 	    }
825 	if (mode == C_WD && ARG1 && !strncasecmp(ARG1, "cwd=", 4)
826 	    && (!strcmp((ARG1) + 4, cwd) || *(ARG1 + 4) == '*' ||
827 		!wu_fnmatch((ARG1) + 4, cwd, FNM_PATHNAME)))
828 	    if (!ARG2)
829 		show++;
830 	    else {
831 		for (which = 2; (which < MAXARGS) && ARG[which]; which++)
832 		    if (strcasecmp(class, ARG[which]) == 0)
833 			show++;
834 	    }
835 	if (show) {
836 	    globerr = NULL;
837 	    filelist = ftpglob(ARG0, B_TRUE);
838 	    sfilelist = filelist;	/* save to free later */
839 	    if (!globerr) {
840 		while (filelist && *filelist) {
841 		    errno = 0;
842 		    if (!stat(*filelist, &buf) &&
843 			(buf.st_mode & S_IFMT) == S_IFREG) {
844 			lreply(code, "Please read the file %s", *filelist);
845 			(void) time(&clock);
846 			tp = localtime(&clock);
847 			days = 365 * tp->tm_year + tp->tm_yday;
848 			tp = localtime((time_t *) & buf.st_mtime);
849 			days -= 365 * tp->tm_year + tp->tm_yday;
850 /*
851    if (days == 0) {
852    lreply(code, "  it was last modified on %.24s - Today",
853    ctime((time_t *)&buf.st_mtime));
854    } else {
855  */
856 			lreply(code,
857 			   "  it was last modified on %.24s - %d day%s ago",
858 			       ctime((time_t *) & buf.st_mtime), days, days == 1 ? "" : "s");
859 /*
860    }
861  */
862 		    }
863 		    filelist++;
864 		}
865 	    }
866 	    if (sfilelist) {
867 		blkfree(sfilelist);
868 		free((char *) sfilelist);
869 	    }
870 	}
871     }
872 }
873 
874 /*************************************************************************/
875 /* FUNCTION  : deny_badxfertype                                          */
876 /* PURPOSE   : If user is in ASCII transfer mode and tries to retrieve a */
877 /*             binary file, abort transfer and display appropriate error */
878 /* ARGUMENTS : message code to use for denial, path of file to check for */
879 /*             binary contents or NULL to assume binary file             */
880 /*************************************************************************/
881 
deny_badasciixfer(int msgcode,char * filepath)882 int deny_badasciixfer(int msgcode, char *filepath)
883 {
884 
885     if (type == TYPE_A && !*filepath) {
886 	reply(msgcode, "This is a BINARY file, using ASCII mode to transfer will corrupt it.");
887 	return (1);
888     }
889     /* The hooks are here to prevent transfers of actual binary files, not
890      * just TAR or COMPRESS mode files... */
891     return (0);
892 }
893 
894 /*************************************************************************/
895 /* FUNCTION  : is_shutdown                                               */
896 /* PURPOSE   : Check to see if the server is shutting down, if it is     */
897 /*             arrange for the shutdown message to be sent in the next   */
898 /*             reply to the user                                         */
899 /* ARGUMENTS : whether to arrange for a shutdown message to be sent, new */
900 /*             or existing connection                                    */
901 /* RETURNS   : 1 if shutting down, 0 if not                              */
902 /*************************************************************************/
903 
is_shutdown(int quiet,int new)904 int is_shutdown(int quiet, int new)
905 {
906     static struct tm tmbuf;
907     static struct stat s_last;
908     static time_t last = 0, shut, deny, disc;
909     static int valid;
910     static char text[2048];
911     struct stat s_cur;
912 
913     extern char *autospout, Shutdown[];
914 
915     FILE *fp;
916 
917     int deny_off, disc_off;
918 
919     time_t curtime = time(NULL);
920 
921     char buf[1024], linebuf[1024];
922 
923     if (Shutdown[0] == '\0' || stat(Shutdown, &s_cur))
924 	return (0);
925 
926     if (s_last.st_mtime != s_cur.st_mtime) {
927 	valid = 0;
928 
929 	fp = fopen(Shutdown, "r");
930 	if (fp == NULL)
931 	    return (0);
932 	s_last = s_cur;
933 	fgets(buf, sizeof(buf), fp);
934 	if (sscanf(buf, "%d %d %d %d %d %ld %ld", &tmbuf.tm_year, &tmbuf.tm_mon,
935 	&tmbuf.tm_mday, &tmbuf.tm_hour, &tmbuf.tm_min, &deny, &disc) != 7) {
936 	    (void) fclose(fp);
937 	    return (0);
938 	}
939 	valid = 1;
940 	deny_off = 3600 * (deny / 100) + 60 * (deny % 100);
941 	disc_off = 3600 * (disc / 100) + 60 * (disc % 100);
942 
943 	tmbuf.tm_year -= 1900;
944 	tmbuf.tm_isdst = -1;
945 	shut = mktime(&tmbuf);
946 	strcpy(shuttime, ctime(&shut));
947 
948 	disc = shut - disc_off;
949 	strcpy(disctime, ctime(&disc));
950 
951 	deny = shut - deny_off;
952 	strcpy(denytime, ctime(&deny));
953 
954 	text[0] = '\0';
955 
956 	while (fgets(buf, sizeof(buf), fp) != NULL) {
957 	    msg_massage(buf, linebuf, sizeof(linebuf));
958 	    if ((strlen(text) + strlen(linebuf)) < sizeof(text))
959 		strcat(text, linebuf);
960 	}
961 
962 	(void) fclose(fp);
963     }
964     if (!valid)
965 	return (0);
966 
967     /* if last == 0, then is_shutdown() only called with quiet == 1 so far */
968     if (last == 0 && !quiet) {
969 	autospout = text;	/* warn them for the first time */
970 	autospout_free = 0;
971 	last = curtime;
972     }
973     /* if a new connection and past deny time, tell caller to drop 'em */
974     if (new && curtime > deny)
975 	return (1);
976 
977     /* if past disconnect time, tell caller to drop 'em */
978     if (curtime > disc)
979 	return (1);
980 
981     /* if less than 60 seconds to disconnection, warn 'em continuously */
982     if (curtime > (disc - 60) && !quiet) {
983 	autospout = text;
984 	autospout_free = 0;
985 	last = curtime;
986     }
987     /* if less than 15 minutes to disconnection, warn 'em every 5 mins */
988     if (curtime > (disc - 60 * 15)) {
989 	if ((curtime - last) > (60 * 5) && !quiet) {
990 	    autospout = text;
991 	    autospout_free = 0;
992 	    last = curtime;
993 	}
994     }
995     /* if less than 24 hours to disconnection, warn 'em every 30 mins */
996     if (curtime < (disc - 24 * 60 * 60) && !quiet) {
997 	if ((curtime - last) > (60 * 30)) {
998 	    autospout = text;
999 	    autospout_free = 0;
1000 	    last = curtime;
1001 	}
1002     }
1003     /* if more than 24 hours to disconnection, warn 'em every 60 mins */
1004     if (curtime > (disc - 24 * 60 * 60) && !quiet) {
1005 	if ((curtime - last) >= (24 * 60 * 60)) {
1006 	    autospout = text;
1007 	    autospout_free = 0;
1008 	    last = curtime;
1009 	}
1010     }
1011     return (0);
1012 }
1013 
1014 #ifdef SITE_NEWER
newer(char * date,char * path,int showlots)1015 void newer(char *date, char *path, int showlots)
1016 {
1017     struct tm tm;
1018 
1019     if (sscanf(date, "%04d%02d%02d%02d%02d%02d",
1020 	       &tm.tm_year, &tm.tm_mon, &tm.tm_mday,
1021 	       &tm.tm_hour, &tm.tm_min, &tm.tm_sec) == 6) {
1022 
1023 	tm.tm_year -= 1900;
1024 	tm.tm_mon--;
1025 	tm.tm_isdst = -1;
1026 	newer_time = mktime(&tm);
1027 	dout = dataconn("file list", (off_t) - 1, "w");
1028 
1029 	if (dout != NULL) {
1030 	    /* As ftw allocates storage it needs a chance to cleanup, setting
1031 	     * ftwflag prevents myoob from calling longjmp, incrementing
1032 	     * ftwflag instead which causes check_newer to return non-zero
1033 	     * which makes ftw return. */
1034 	    ftwflag = 1;
1035 	    transflag++;
1036 	    show_fullinfo = showlots;
1037 #if defined(HAVE_FTW)
1038 	    ftw(path, check_newer, -1);
1039 #else
1040 	    treewalk(path, check_newer, -1, NULL);
1041 #endif
1042 
1043 	    /* don't send a reply if myoob has already replied */
1044 	    if (ftwflag == 1) {
1045 		if (ferror(dout) != 0)
1046 		    perror_reply(550, "Data connection");
1047 		else
1048 		    reply(226, "Transfer complete.");
1049 	    }
1050 
1051 	    (void) fclose(dout);
1052 	    data = -1;
1053 	    pdata = -1;
1054 	    transflag = 0;
1055 	    ftwflag = 0;
1056 	}
1057     }
1058     else
1059 	reply(501, "Bad DATE format");
1060 }
1061 #endif
1062 
type_match(char * typelist)1063 int type_match(char *typelist)
1064 {
1065     char *start, *p;
1066     int len;
1067 
1068     if (typelist == NULL)
1069 	return (0);
1070 
1071     for (p = start = typelist; *start != '\0'; start = p) {
1072 	while (*p != '\0' && *p != ',')
1073 	    p++;
1074 	len = p - start;
1075 	if (*p != '\0')
1076 	    p++;
1077 	if (len == 9 && anonymous && strncasecmp(start, "anonymous", 9) == 0)
1078 	    return (1);
1079 	if (len == 5 && guest && strncasecmp(start, "guest", 5) == 0)
1080 	    return (1);
1081 	if (len == 4 && !guest && !anonymous &&
1082 	    strncasecmp(start, "real", 4) == 0)
1083 	    return (1);
1084 
1085 	if (len > 6 && strncasecmp(start, "class=", 6) == 0) {
1086 	    char class[1024];
1087 
1088 	    if ((acl_getclass(class) == 1) && (strlen(class) == len - 6) &&
1089 		(strncasecmp(start + 6, class, len - 6) == 0))
1090 		return (1);
1091 	}
1092     }
1093     return (0);
1094 }
1095 
path_compare(char * p1,char * p2)1096 int path_compare(char *p1, char *p2)
1097 {
1098     if ((strcmp(p1, "*") == 0) || (wu_fnmatch(p1, p2, FNM_PATHNAME) == 0))	/* 0 means they matched */
1099 	return (strlen(p1));
1100     else
1101 	return (-2);
1102 }
1103 
expand_id(void)1104 void expand_id(void)
1105 {
1106     char class[1024];
1107     struct aclmember *entry = NULL;
1108     (void) acl_getclass(class);
1109     while (getaclentry("upload", &entry)) {
1110 	char *q;
1111 	int i = 0;
1112 	int options = 1;
1113 	int classfound = 0;
1114 	int classmatched = 0;
1115 	while (options
1116 	       && (i < MAXARGS)
1117 	       && ((q = entry->arg[i]) != (char *) NULL)
1118 	       && (q[0] != '\0')) {
1119 	    if (strcasecmp(q, "absolute") == 0)
1120 		i++;
1121 	    else if (strcasecmp(q, "relative") == 0)
1122 		i++;
1123 	    else if (strncasecmp(q, "class=", 6) == 0) {
1124 		i++;
1125 		classfound = 1;
1126 		if (strcasecmp(q + 6, class) == 0)
1127 		    classmatched = 1;
1128 	    }
1129 	    else if (strcmp(q, "-") == 0) {
1130 		i++;
1131 		options = 0;
1132 	    }
1133 	    else
1134 		options = 0;
1135 	}
1136 	if (!classfound || classmatched) {
1137 	    char buf[BUFSIZ];
1138 	    /*
1139 	       *  UID
1140 	     */
1141 	    if (((i + 3) < MAXARGS)
1142 		&& ((q = entry->arg[i + 3]) != (char *) NULL)
1143 		&& (q[0] != '\0')
1144 		&& (strcmp(q, "*") != 0)) {
1145 		if (q[0] == '%')
1146 		    sprintf(buf, "%s", q + 1);
1147 		else {
1148 		    struct passwd *pwent = getpwnam(q);
1149 		    if (pwent)
1150 			sprintf(buf, "%" PW_UID_FORMAT, pwent->pw_uid);
1151 		    else
1152 			sprintf(buf, "%d", 0);
1153 		}
1154 		entry->arg[i + 3] = (char *) malloc(strlen(buf) + 1);
1155 		if (entry->arg[i + 3] == NULL) {
1156 		    syslog(LOG_ERR, "calloc error in expand_id");
1157 		    dologout(1);
1158 		}
1159 		strcpy(entry->arg[i + 3], buf);
1160 	    }
1161 	    /*
1162 	       *  GID
1163 	     */
1164 	    if (((i + 4) < MAXARGS)
1165 		&& ((q = entry->arg[i + 4]) != (char *) NULL)
1166 		&& (q[0] != '\0')
1167 		&& (strcmp(q, "*") != 0)) {
1168 		if (q[0] == '%')
1169 		    sprintf(buf, "%s", q + 1);
1170 		else {
1171 		    struct group *grent = getgrnam(q);
1172 		    if (grent)
1173 			sprintf(buf, "%" GR_GID_FORMAT, grent->gr_gid);
1174 		    else
1175 			sprintf(buf, "%d", 0);
1176 		    endgrent();
1177 		}
1178 		entry->arg[i + 4] = (char *) malloc(strlen(buf) + 1);
1179 		if (entry->arg[i + 4] == NULL) {
1180 		    syslog(LOG_ERR, "calloc error in expand_id");
1181 		    dologout(1);
1182 		}
1183 		strcpy(entry->arg[i + 4], buf);
1184 	    }
1185 	}
1186     }
1187 }
1188 
fn_check(char * name)1189 int fn_check(char *name)
1190 {
1191     /* check to see if this is a valid file name... path-filter <type>
1192      * <message_file> <allowed_charset> <disallowed> */
1193 
1194     struct aclmember *entry = NULL;
1195     int j;
1196     char *path;
1197 #if ! defined(HAVE_REGEXEC)
1198     char *sp;
1199 #endif
1200 
1201 #ifdef M_UNIX
1202 #ifdef HAVE_REGEX
1203     char *regp;
1204 #endif
1205 #endif
1206 
1207 #ifdef HAVE_REGEXEC
1208     regex_t regexbuf;
1209     regmatch_t regmatchbuf;
1210     int rval;
1211 #endif
1212 
1213 #ifdef LINUX
1214     re_syntax_options = RE_SYNTAX_POSIX_EXTENDED;
1215 #endif
1216 
1217     while (getaclentry("path-filter", &entry) && ARG0 != NULL) {
1218 	if (type_match(ARG0) && ARG1 && ARG2) {
1219 
1220 	    /*
1221 	     * check *only* the basename
1222 	     */
1223 
1224 	    if ((path = strrchr(name, '/')))
1225 		++path;
1226 	    else
1227 		path = name;
1228 
1229 	    /* is it in the allowed character set? */
1230 #if defined(HAVE_REGEXEC)
1231 	    if (regcomp(&regexbuf, ARG2, REG_EXTENDED) != 0) {
1232 		reply(550, "HAVE_REGEX error");
1233 #elif defined(HAVE_REGEX)
1234 		if ((sp = regcmp(ARG2, (char *) 0)) == NULL) {
1235 		    reply(550, "HAVE_REGEX error");
1236 #else
1237 	    if ((sp = re_comp(ARG2)) != 0) {
1238 		perror_reply(550, sp);
1239 #endif
1240 		return (0);
1241 	    }
1242 #if defined(HAVE_REGEXEC)
1243 	    rval = regexec(&regexbuf, path, 1, &regmatchbuf, 0);
1244 	    regfree(&regexbuf);
1245 	    if (rval != 0) {
1246 #elif defined(HAVE_REGEX)
1247 #ifdef M_UNIX
1248 		regp = regex(sp, path);
1249 		free(sp);
1250 		if (regp == NULL) {
1251 #else
1252 		if ((regex(sp, path)) == NULL) {
1253 #endif
1254 #else
1255 	    if ((re_exec(path)) != 1) {
1256 #endif
1257 		pr_mesg(550, ARG1);
1258 		reply(550, "%s: Permission denied on server. (Filename (accept))", name);
1259 		return (0);
1260 	    }
1261 	    /* is it in any of the disallowed regexps */
1262 
1263 	    for (j = 3; j < MAXARGS; ++j) {
1264 		/* ARGj == entry->arg[j] */
1265 		if (entry->arg[j]) {
1266 #if defined(HAVE_REGEXEC)
1267 		    if (regcomp(&regexbuf, entry->arg[j], REG_EXTENDED) != 0) {
1268 			reply(550, "HAVE_REGEX error");
1269 #elif defined(HAVE_REGEX)
1270 			if ((sp = regcmp(entry->arg[j], (char *) 0)) == NULL) {
1271 			    reply(550, "HAVE_REGEX error");
1272 #else
1273 		    if ((sp = re_comp(entry->arg[j])) != 0) {
1274 			perror_reply(550, sp);
1275 #endif
1276 			return (0);
1277 		    }
1278 #if defined(HAVE_REGEXEC)
1279 		    rval = regexec(&regexbuf, path, 1, &regmatchbuf, 0);
1280 		    regfree(&regexbuf);
1281 		    if (rval == 0) {
1282 #elif defined(HAVE_REGEX)
1283 #ifdef M_UNIX
1284 			regp = regex(sp, path);
1285 			free(sp);
1286 			if (regp != NULL) {
1287 #else
1288 			if ((regex(sp, path)) != NULL) {
1289 #endif
1290 #else
1291 		    if ((re_exec(path)) == 1) {
1292 #endif
1293 			pr_mesg(550, ARG1);
1294 			reply(550, "%s: Permission denied on server. (Filename (deny))", name);
1295 			return (0);
1296 		    }
1297 		}
1298 	    }
1299 	}
1300     }
1301     return (1);
1302 }
1303 
1304 int dir_check(char *name, uid_t * uid, gid_t * gid, int *d_mode, int *valid)
1305 {
1306     struct aclmember *entry = NULL;
1307     int match_value = -1;
1308     char *ap2 = NULL;
1309     char *ap3 = NULL;
1310     char *ap4 = NULL;
1311     char *ap5 = NULL;
1312     char *ap6 = NULL;
1313     char *ap7 = NULL;
1314     char cwdir[MAXPATHLEN];
1315     char *pwdir;
1316     char abspwdir[MAXPATHLEN];
1317     char relpwdir[MAXPATHLEN];
1318     char path[MAXPATHLEN];
1319     char *sp;
1320     struct stat stbuf;
1321     int stat_result = -1;
1322     char class[1024];
1323     extern char *home;
1324 
1325     (void) acl_getclass(class);
1326 
1327     *valid = 0;
1328     /* what's our current directory? */
1329 
1330     /* XXX We could use dynamic RAM to store this path, but I'd rather just bail
1331        out with an error. The rest of wu is so crufy that a long path might
1332        just blow up later */
1333 
1334     if ((strlen(name) + 1) > sizeof(path)) {
1335 	perror_reply(550, "Path too long");
1336 	return (-1);
1337     }
1338 
1339     strcpy(path, name);
1340     sp = strrchr(path, '/');
1341     if (sp)
1342 	*sp = '\0';
1343     else
1344 	strcpy(path, ".");
1345 
1346     if ((fb_realpath(path, cwdir)) == NULL) {
1347 	perror_reply(550, "Could not determine cwdir");
1348 	return (-1);
1349     }
1350 
1351     if ((fb_realpath(home, relpwdir)) == NULL) {
1352 	perror_reply(550, "Could not determine pwdir");
1353 	return (-1);
1354     }
1355 
1356     if ((wu_realpath(home, abspwdir, chroot_path)) == NULL) {
1357 	perror_reply(550, "Could not determine pwdir");
1358 	return (-1);
1359     }
1360 
1361     while (getaclentry("upload", &entry)) {
1362 	char *q;
1363 	int i = 0;
1364 	int options = 1;
1365 	int classfound = 0;
1366 	int classmatched = 0;
1367 	pwdir = abspwdir;
1368 	while (options
1369 	       && (i < MAXARGS)
1370 	       && ((q = entry->arg[i]) != (char *) NULL)
1371 	       && (q[0] != '\0')) {
1372 	    if (strcasecmp(q, "absolute") == 0) {
1373 		i++;
1374 		pwdir = abspwdir;
1375 	    }
1376 	    else if (strcasecmp(q, "relative") == 0) {
1377 		i++;
1378 		pwdir = relpwdir;
1379 	    }
1380 	    else if (strncasecmp(q, "class=", 6) == 0) {
1381 		i++;
1382 		classfound = 1;
1383 		if (strcasecmp(q + 6, class) == 0)
1384 		    classmatched = 1;
1385 	    }
1386 	    else if (strcmp(q, "-") == 0) {
1387 		i++;
1388 		options = 0;
1389 	    }
1390 	    else
1391 		options = 0;
1392 	}
1393 	if (!classfound || classmatched) {
1394 	    int j;
1395 	    if (((i + 1) < MAXARGS)
1396 		&& ((q = entry->arg[i]) != (char *) NULL)
1397 		&& (q[0] != '\0')
1398 		&& (0 < path_compare(q, pwdir))
1399 		&& ((j = path_compare(entry->arg[i + 1], cwdir)) >= match_value)) {
1400 		match_value = j;
1401 
1402 		ap2 = NULL;
1403 		if (((i + 2) < MAXARGS)
1404 		    && ((q = entry->arg[i + 2]) != (char *) NULL)
1405 		    && (q[0] != '\0'))
1406 		    ap2 = q;
1407 
1408 		ap3 = NULL;
1409 		if (((i + 3) < MAXARGS)
1410 		    && ((q = entry->arg[i + 3]) != (char *) NULL)
1411 		    && (q[0] != '\0'))
1412 		    ap3 = q;
1413 
1414 		ap4 = NULL;
1415 		if (((i + 4) < MAXARGS)
1416 		    && ((q = entry->arg[i + 4]) != (char *) NULL)
1417 		    && (q[0] != '\0'))
1418 		    ap4 = q;
1419 
1420 		ap5 = NULL;
1421 		if (((i + 5) < MAXARGS)
1422 		    && ((q = entry->arg[i + 5]) != (char *) NULL)
1423 		    && (q[0] != '\0'))
1424 		    ap5 = q;
1425 
1426 		ap6 = NULL;
1427 		if (((i + 6) < MAXARGS)
1428 		    && ((q = entry->arg[i + 6]) != (char *) NULL)
1429 		    && (q[0] != '\0'))
1430 		    ap6 = q;
1431 
1432 		ap7 = NULL;
1433 		if (((i + 7) < MAXARGS)
1434 		    && ((q = entry->arg[i + 7]) != (char *) NULL)
1435 		    && (q[0] != '\0'))
1436 		    ap7 = q;
1437 	    }
1438 	}
1439     }
1440 
1441     if (anonymous && (match_value < 0)) {
1442 	reply(550, "%s: Permission denied on server. (Upload dirs)", name);
1443 	return (0);
1444     }
1445     if ((ap2 && !strcasecmp(ap2, "no"))
1446 	|| (ap3 && !strcasecmp(ap3, "nodirs"))
1447 	|| (ap6 && !strcasecmp(ap6, "nodirs"))) {
1448 	reply(550, "%s: Permission denied on server. (Upload dirs)", name);
1449 	return (0);
1450     }
1451     if ((ap3 && *ap3 == '*') || (ap4 && *ap4 == '*'))
1452 	stat_result = stat(path, &stbuf);
1453     if (ap3) {
1454 	if ((ap3[0] != '*') || (ap3[1] != '\0'))
1455 	    *uid = atoi(ap3);	/* the uid  */
1456 	else if (stat_result == 0)
1457 	    *uid = stbuf.st_uid;
1458     }
1459     if (ap4) {
1460 	if ((ap4[0] != '*') || (ap4[1] != '\0'))
1461 	    *gid = atoi(ap4);	/* the gid */
1462 	else if (stat_result == 0)
1463 	    *gid = stbuf.st_gid;
1464     }
1465     if (ap7) {
1466 	sscanf(ap7, "%o", d_mode);
1467 	*valid = 1;
1468     }
1469     else if (ap5) {
1470 	sscanf(ap5, "%o", d_mode);
1471 	if (*d_mode & 0600)
1472 	    *d_mode |= 0100;
1473 	if (*d_mode & 0060)
1474 	    *d_mode |= 0010;
1475 	if (*d_mode & 0006)
1476 	    *d_mode |= 0001;
1477 	*valid = 1;
1478     }
1479     return (1);
1480 }
1481 
1482 int upl_check(char *name, uid_t * uid, gid_t * gid, int *f_mode, int *valid)
1483 {
1484     int match_value = -1;
1485     char cwdir[MAXPATHLEN];
1486     char *pwdir;
1487     char abspwdir[MAXPATHLEN];
1488     char relpwdir[MAXPATHLEN];
1489     char path[MAXPATHLEN];
1490     char *sp;
1491     struct stat stbuf;
1492     int stat_result = -1;
1493     char *ap2 = NULL;
1494     char *ap3 = NULL;
1495     char *ap4 = NULL;
1496     char *ap5 = NULL;
1497     struct aclmember *entry = NULL;
1498     char class[1024];
1499     extern char *home;
1500 
1501     *valid = 0;
1502     (void) acl_getclass(class);
1503 
1504     /* what's our current directory? */
1505 
1506     /* XXX We could use dynamic RAM to store this path, but I'd rather just bail
1507        out with an error. The rest of wu is so crufy that a long path might
1508        just blow up later */
1509 
1510     if ((strlen(name) + 1) > sizeof(path)) {
1511 	perror_reply(553, "Path too long");
1512 	return (-1);
1513     }
1514 
1515     strcpy(path, name);
1516     sp = strrchr(path, '/');
1517     if (sp)
1518 	*sp = '\0';
1519     else
1520 	strcpy(path, ".");
1521 
1522     if ((fb_realpath(path, cwdir)) == NULL) {
1523 	perror_reply(553, "Could not determine cwdir");
1524 	return (-1);
1525     }
1526 
1527     if ((wu_realpath(home, abspwdir, chroot_path)) == NULL) {
1528 	perror_reply(553, "Could not determine pwdir");
1529 	return (-1);
1530     }
1531 
1532     if ((fb_realpath(home, relpwdir)) == NULL) {
1533 	perror_reply(553, "Could not determine pwdir");
1534 	return (-1);
1535     }
1536 
1537     /*
1538        *  we are doing a "best match"... ..so we keep track of what "match
1539        *  value" we have received so far...
1540      */
1541     while (getaclentry("upload", &entry)) {
1542 	char *q;
1543 	int i = 0;
1544 	int options = 1;
1545 	int classfound = 0;
1546 	int classmatched = 0;
1547 	pwdir = abspwdir;
1548 	while (options
1549 	       && (i < MAXARGS)
1550 	       && ((q = entry->arg[i]) != (char *) NULL)
1551 	       && (q[0] != '\0')) {
1552 	    if (strcasecmp(q, "absolute") == 0) {
1553 		i++;
1554 		pwdir = abspwdir;
1555 	    }
1556 	    else if (strcasecmp(q, "relative") == 0) {
1557 		i++;
1558 		pwdir = relpwdir;
1559 	    }
1560 	    else if (strncasecmp(q, "class=", 6) == 0) {
1561 		i++;
1562 		classfound = 1;
1563 		if (strcasecmp(q + 6, class) == 0)
1564 		    classmatched = 1;
1565 	    }
1566 	    else if (strcmp(q, "-") == 0) {
1567 		i++;
1568 		options = 0;
1569 	    }
1570 	    else
1571 		options = 0;
1572 	}
1573 	if (!classfound || classmatched) {
1574 	    int j;
1575 	    if (((i + 1) < MAXARGS)
1576 		&& ((q = entry->arg[i]) != (char *) NULL)
1577 		&& (q[0] != '\0')
1578 		&& (0 < path_compare(q, pwdir))
1579 		&& ((j = path_compare(entry->arg[i + 1], cwdir)) >= match_value)) {
1580 		match_value = j;
1581 
1582 		ap2 = NULL;
1583 		if (((i + 2) < MAXARGS)
1584 		    && ((q = entry->arg[i + 2]) != (char *) NULL)
1585 		    && (q[0] != '\0'))
1586 		    ap2 = q;
1587 
1588 		ap3 = NULL;
1589 		if (((i + 3) < MAXARGS)
1590 		    && ((q = entry->arg[i + 3]) != (char *) NULL)
1591 		    && (q[0] != '\0'))
1592 		    ap3 = q;
1593 
1594 		ap4 = NULL;
1595 		if (((i + 4) < MAXARGS)
1596 		    && ((q = entry->arg[i + 4]) != (char *) NULL)
1597 		    && (q[0] != '\0'))
1598 		    ap4 = q;
1599 
1600 		ap5 = NULL;
1601 		if (((i + 5) < MAXARGS)
1602 		    && ((q = entry->arg[i + 5]) != (char *) NULL)
1603 		    && (q[0] != '\0'))
1604 		    ap5 = q;
1605 	    }
1606 	}
1607     }
1608 
1609     if (ap3
1610 	&& ((!strcasecmp("dirs", ap3))
1611 	    || (!strcasecmp("nodirs", ap3))))
1612 	ap3 = NULL;
1613 
1614     /*
1615        *  if we did get matches ... else don't do any of this stuff
1616      */
1617     if (match_value >= 0) {
1618 	if (!strcasecmp(ap2, "yes")) {
1619 	    if ((ap3 && *ap3 == '*') || (ap4 && *ap4 == '*'))
1620 		stat_result = stat(path, &stbuf);
1621 	    if (ap3) {
1622 		if ((ap3[0] != '*') || (ap3[1] != '\0'))
1623 		    *uid = atoi(ap3);	/* the uid  */
1624 		else if (stat_result == 0)
1625 		    *uid = stbuf.st_uid;
1626 	    }
1627 	    if (ap4) {
1628 		if ((ap4[0] != '*') || (ap4[1] != '\0'))
1629 		    *gid = atoi(ap4);	/* the gid  */
1630 		else if (stat_result == 0)
1631 		    *gid = stbuf.st_gid;
1632 		*valid = 1;
1633 	    }
1634 	    if (ap5)
1635 		sscanf(ap5, "%o", f_mode);	/* the mode */
1636 	}
1637 	else {
1638 	    reply(553, "%s: Permission denied on server. (Upload)", name);
1639 	    return (-1);
1640 	}
1641     }
1642     else {
1643 	/*
1644 	   *  upload defaults to "permitted"
1645 	 */
1646 	/* Not if anonymous */
1647 	if (anonymous) {
1648 	    reply(553, "%s: Permission denied on server. (Upload)", name);
1649 	    return (-1);
1650 	}
1651 	return (1);
1652     }
1653 
1654     return (match_value);
1655 }
1656 
1657 int del_check(char *name)
1658 {
1659     int pdelete = (anonymous ? 0 : 1);
1660     struct aclmember *entry = NULL;
1661 
1662     while (getaclentry("delete", &entry) && ARG0 && ARG1 != NULL) {
1663 	if (type_match(ARG1))
1664 	    if (anonymous) {
1665 		if (*ARG0 == 'y')
1666 		    pdelete = 1;
1667 	    }
1668 	    else if (*ARG0 == 'n')
1669 		pdelete = 0;
1670     }
1671 
1672 /* H* fix: no deletion, period. You put a file here, I get to look at it. */
1673 #ifdef PARANOID
1674     pdelete = 0;
1675 #endif
1676 
1677     if (!pdelete) {
1678 	reply(553, "%s: Permission denied on server. (Delete)", name);
1679 	return (0);
1680     }
1681     else {
1682 	return (1);
1683     }
1684 }
1685 
1686 /* The following is from the Debian add-ons. */
1687 
1688 #define lbasename(x) (strrchr(x,'/')?1+strrchr(x,'/'):x)
1689 
1690 int regexmatch(char *name, char *rgexp)
1691 {
1692 
1693 #ifdef M_UNIX
1694 #ifdef HAVE_REGEX
1695     char *regp;
1696 #endif
1697 #endif
1698 
1699 #ifdef HAVE_REGEXEC
1700     regex_t regexbuf;
1701     regmatch_t regmatchbuf;
1702     int rval;
1703 #else
1704     char *sp;
1705 #endif
1706 
1707 #if defined(HAVE_REGEXEC)
1708     if (regcomp(&regexbuf, rgexp, REG_EXTENDED) != 0) {
1709 	reply(553, "HAVE_REGEX error");
1710 #elif defined(HAVE_REGEX)
1711 	if ((sp = regcmp(rgexp, (char *) 0)) == NULL) {
1712 	    reply(553, "HAVE_REGEX error");
1713 #else
1714     if ((sp = re_comp(rgexp)) != 0) {
1715 	perror_reply(553, sp);
1716 #endif
1717 	return (0);
1718     }
1719 
1720 #if defined(HAVE_REGEXEC)
1721     rval = regexec(&regexbuf, name, 1, &regmatchbuf, 0);
1722     regfree(&regexbuf);
1723     if (rval != 0) {
1724 #elif defined(HAVE_REGEX)
1725 #ifdef M_UNIX
1726 	regp = regex(sp, name);
1727 	free(sp);
1728 	if (regp == NULL) {
1729 #else
1730 	if ((regex(sp, name)) == NULL) {
1731 #endif
1732 #else
1733     if ((re_exec(name)) != 1) {
1734 #endif
1735 	return (0);
1736     }
1737     return (1);
1738 }
1739 
1740 static int allow_retrieve(char *name)
1741 {
1742     char realname[MAXPATHLEN + 1];
1743     char localname[MAXPATHLEN + 1];
1744     char *whichname;
1745     int i;
1746     struct aclmember *entry = NULL;
1747     char *p, *q;
1748     int options;
1749     int classfound;
1750     int classmatched;
1751     char class[1024];
1752 
1753     (void) acl_getclass(class);
1754     if ((name == (char *) NULL)
1755 	|| (*name == '\0'))
1756 	return 0;
1757     fb_realpath(name, localname);
1758     wu_realpath(name, realname, chroot_path);
1759     while (getaclentry("allow-retrieve", &entry)) {
1760 	whichname = realname;
1761 	i = 0;
1762 	options = 1;
1763 	classfound = 0;
1764 	classmatched = 0;
1765 	while (options
1766 	       && (i < MAXARGS)
1767 	       && ((q = entry->arg[i]) != (char *) NULL)
1768 	       && (q[0] != '\0')) {
1769 	    if (strcasecmp(q, "absolute") == 0) {
1770 		i++;
1771 		whichname = realname;
1772 	    }
1773 	    else if (strcasecmp(q, "relative") == 0) {
1774 		i++;
1775 		whichname = localname;
1776 	    }
1777 	    else if (strncasecmp(q, "class=", 6) == 0) {
1778 		i++;
1779 		classfound = 1;
1780 		if (strcasecmp(q + 6, class) == 0)
1781 		    classmatched = 1;
1782 	    }
1783 	    else if (strcmp(q, "-") == 0) {
1784 		i++;
1785 		options = 0;
1786 	    }
1787 	    else
1788 		options = 0;
1789 	}
1790 	if (!classfound || classmatched) {
1791 	    for (; (i < MAXARGS) && ((q = entry->arg[i]) != (char *) NULL) && (q[0] != '\0'); i++) {
1792 		p = (q[0] == '/') ? whichname : lbasename(whichname);
1793 		if (!wu_fnmatch(q, p, FNM_PATHNAME | FNM_LEADING_DIR)) {
1794 		    return 1;
1795 		}
1796 	    }
1797 	}
1798     }
1799     return 0;
1800 }
1801 
1802 int checknoretrieve(char *name)
1803 {
1804     char realname[MAXPATHLEN + 1];
1805     char localname[MAXPATHLEN + 1];
1806     char *whichname;
1807     int i;
1808     struct aclmember *entry = NULL;
1809     char *p, *q;
1810     int options;
1811     int classfound;
1812     int classmatched;
1813     char class[1024];
1814 
1815     extern struct passwd *pw;
1816     extern char *remoteident;
1817 
1818     (void) acl_getclass(class);
1819     if ((name == (char *) NULL)
1820 	|| (*name == '\0'))
1821 	return 0;
1822     fb_realpath(name, localname);
1823     wu_realpath(name, realname, chroot_path);
1824     while (getaclentry("noretrieve", &entry)) {
1825 	whichname = realname;
1826 	i = 0;
1827 	options = 1;
1828 	classfound = 0;
1829 	classmatched = 0;
1830 	while (options
1831 	       && (i < MAXARGS)
1832 	       && ((q = entry->arg[i]) != (char *) NULL)
1833 	       && (q[0] != '\0')) {
1834 	    if (strcasecmp(q, "absolute") == 0) {
1835 		i++;
1836 		whichname = realname;
1837 	    }
1838 	    else if (strcasecmp(q, "relative") == 0) {
1839 		i++;
1840 		whichname = localname;
1841 	    }
1842 	    else if (strncasecmp(q, "class=", 6) == 0) {
1843 		i++;
1844 		classfound = 1;
1845 		if (strcasecmp(q + 6, class) == 0)
1846 		    classmatched = 1;
1847 	    }
1848 	    else if (strcmp(q, "-") == 0) {
1849 		i++;
1850 		options = 0;
1851 	    }
1852 	    else
1853 		options = 0;
1854 	}
1855 	if (!classfound || classmatched) {
1856 	    for (; (i < MAXARGS) && ((q = entry->arg[i]) != (char *) NULL) && (q[0] != '\0'); i++) {
1857 		p = (q[0] == '/') ? whichname : lbasename(whichname);
1858 		if (!wu_fnmatch(q, p, FNM_PATHNAME | FNM_LEADING_DIR)) {
1859 		    if (!allow_retrieve(name)) {
1860 			reply(550, "%s is marked unretrievable", localname);
1861 			return 1;
1862 		    }
1863 		}
1864 	    }
1865 	}
1866     }
1867     return 0;
1868 }
1869 
1870 #ifdef QUOTA
1871 
1872 #ifndef MNTMAXSTR
1873 #define MNTMAXSTR 2048		/* And hope it's enough */
1874 #endif
1875 
1876 #ifdef QUOTA_DEVICE
1877 
1878 int path_to_device(char *pathname, char *result)
1879 {
1880     FILE *fp;
1881 #ifdef HAS_OLDSTYLE_GETMNTENT
1882     struct mnttab static_mp;
1883     struct mnttab *mp = &static_mp;
1884 #else
1885     struct mntent *mp;
1886 #endif
1887     struct mount_ent {
1888 	char mnt_fsname[MNTMAXSTR], mnt_dir[MNTMAXSTR];
1889 	struct mount_ent *next;
1890     } mountent;
1891     struct mount_ent *current, *start, *new;
1892     char path[1024], mnt_dir[1024], *pos;
1893     int flag = 1;
1894 
1895     start = current = NULL;
1896 #ifdef HAS_OLDSTYLE_GETMNTENT
1897     fp = fopen(MNTTAB, "r");
1898 #else
1899     fp = setmntent(MNTTAB, "r");
1900 #endif
1901     if (fp == NULL)
1902 	return 0;
1903 #ifdef HAS_OLDSTYLE_GETMNTENT
1904     while (getmntent(fp, &static_mp) == 0)
1905 #else
1906     while (mp = getmntent(fp))
1907 #endif
1908     {
1909 	if (!(new = (struct mount_ent *) malloc(sizeof(mountent)))) {
1910 	    perror("malloc");
1911 	    flag = 0;
1912 	    break;
1913 	}
1914 
1915 	if (!start)
1916 	    start = current = new;
1917 	else
1918 	    current = current->next = new;
1919 
1920 #ifdef HAS_OLDSTYLE_GETMNTENT
1921 	strncpy(current->mnt_fsname, mp->mnt_special, strlen(mp->mnt_special) + 1);
1922 	strncpy(current->mnt_dir, mp->mnt_mountp, strlen(mp->mnt_mountp) + 1);
1923 #else
1924 	strncpy(current->mnt_fsname, mp->mnt_fsname, strlen(mp->mnt_fsname) + 1);
1925 	strncpy(current->mnt_dir, mp->mnt_dir, strlen(mp->mnt_dir) + 1);
1926 #endif
1927     }
1928 #ifdef HAS_OLDSTYLE_GETMNTENT
1929     fclose(fp);
1930 #else
1931     endmntent(fp);
1932 #endif
1933     current->next = NULL;
1934 
1935     wu_realpath(pathname, path, chroot_path);
1936 
1937     while (*path && flag) {
1938 	current = start;
1939 	while (current && flag) {
1940 	    if (strcmp(current->mnt_dir, "swap")) {
1941 		wu_realpath(current->mnt_dir, mnt_dir, chroot_path);
1942 		if (!strcmp(mnt_dir, path)) {
1943 		    flag = 0;
1944 		    /* no support for remote quota yet */
1945 		    if (!strchr(current->mnt_fsname, ':'))
1946 			strcpy(result, current->mnt_fsname);
1947 		}
1948 	    }
1949 	    current = current->next;
1950 	}
1951 	if (!((pos = strrchr(path, '/')) - path) && strlen(path) > 1)
1952 	    strcpy(path, "/");
1953 	else
1954 	    path[pos - path] = '\0';
1955     }
1956     while (current) {
1957 	new = current->next;
1958 	free(current);
1959 	current = new;
1960     }
1961     return 1;
1962 }
1963 #endif
1964 
1965 void get_quota(char *fs, int uid)
1966 {
1967     char mnt_fsname[MNTMAXSTR];
1968 #ifdef HAS_NO_QUOTACTL
1969     int dirfd;
1970     struct quotctl qp;
1971 #endif
1972 
1973     /*
1974      * Getting file system quota information can take a noticeable amount
1975      * of time, so only get quota information for specified users.
1976      * quota-info <uid-range> [<uid-range> ...]
1977      */
1978     if (!uid_match("quota-info", uid))
1979 	return;
1980 
1981 #ifdef HAS_NO_QUOTACTL
1982     if (path_to_device(fs, mnt_fsname)) {
1983 	dirfd = open(fs, O_RDONLY);
1984 	qp.op = Q_GETQUOTA;
1985 	qp.uid = uid;
1986 	qp.addr = (char *) &quota;
1987 	ioctl(dirfd, Q_QUOTACTL, &qp);
1988 	close(dirfd);
1989     }
1990 #else
1991 #ifdef QUOTA_DEVICE
1992 
1993     if (path_to_device(fs, mnt_fsname))
1994 #ifdef QCMD
1995 	quotactl(QCMD(Q_GETQUOTA, USRQUOTA), mnt_fsname, uid, (char *) &quota);
1996 #else
1997 	quotactl(Q_GETQUOTA, mnt_fsname, uid, (char *) &quota);
1998 #endif
1999 #else
2000     quotactl(fs, QCMD(Q_GETQUOTA, USRQUOTA), uid, (char *) &quota);
2001 #endif
2002 #endif /* HAS_NO_QUOTACTL */
2003 }
2004 
2005 char *time_quota(long curstate, long softlimit, long timelimit, char *timeleft)
2006 {
2007     struct timeval tv;
2008 
2009     gettimeofday(&tv, NULL);
2010     if (softlimit && curstate >= softlimit) {
2011 	if (timelimit == 0) {
2012 	    strcpy(timeleft, "NOT STARTED");
2013 	}
2014 	else if (timelimit > tv.tv_sec) {
2015 	    fmttime(timeleft, timelimit - tv.tv_sec);
2016 	}
2017 	else {
2018 	    strcpy(timeleft, "EXPIRED");
2019 	}
2020     }
2021     else {
2022 	timeleft[0] = '\0';
2023     }
2024     return (timeleft);
2025 }
2026 
2027 void fmttime(char *buf, register long time)
2028 {
2029     int i;
2030     static struct {
2031 	int c_secs;		/* conversion units in secs */
2032 	char *c_str;		/* unit string */
2033     } cunits[] = {
2034 	{
2035 	    60 *60 * 24 * 28, "months"
2036 	} ,
2037 	{
2038 	    60 *60 * 24 * 7, "weeks"
2039 	} ,
2040 	{
2041 	    60 *60 * 24, "days"
2042 	} ,
2043 	{
2044 	    60 *60, "hours"
2045 	} ,
2046 	{
2047 	    60, "mins"
2048 	} ,
2049 	{
2050 	    1, "secs"
2051 	}
2052     };
2053 
2054     if (time <= 0) {
2055 	strcpy(buf, "EXPIRED");
2056 	return;
2057     }
2058     for (i = 0; i < sizeof(cunits) / sizeof(cunits[0]); i++) {
2059 	if (time >= cunits[i].c_secs)
2060 	    break;
2061     }
2062     sprintf(buf, "%.1f %s", (double) time / cunits[i].c_secs, cunits[i].c_str);
2063 }
2064 
2065 #endif
2066 
2067 #ifdef THROUGHPUT
2068 
2069 int file_compare(char *patterns, char *file)
2070 {
2071     char buf[MAXPATHLEN+1];
2072     char *cp;
2073     char *cp2;
2074     int i;
2075     int matches = 0;
2076 
2077     strncpy(buf, patterns, sizeof(buf) - 1);
2078     buf[sizeof(buf) - 2] = '\0';
2079     i = strlen(buf);
2080     buf[i++] = ',';
2081     buf[i++] = '\0';
2082 
2083     cp = buf;
2084     while ((cp2 = strchr(cp, ',')) != NULL) {
2085 	*cp2++ = '\0';
2086 	if (wu_fnmatch(cp, file, FNM_PATHNAME) == 0) {
2087 	    matches = 1;
2088 	    break;
2089 	}
2090 	cp = cp2;
2091     }
2092     return matches;
2093 }
2094 
2095 int remote_compare(char *patterns)
2096 {
2097     char buf[MAXPATHLEN+1];
2098     char *cp;
2099     char *cp2;
2100     int i;
2101     int matches = 0;
2102 
2103     strncpy(buf, patterns, sizeof(buf) - 1);
2104     buf[sizeof(buf) - 2] = '\0';
2105     i = strlen(buf);
2106     buf[i++] = ',';
2107     buf[i++] = '\0';
2108 
2109     cp = buf;
2110     while ((cp2 = strchr(cp, ',')) != NULL) {
2111 	*cp2++ = '\0';
2112 	if (hostmatch(cp, remoteaddr, remotehost)) {
2113 	    matches = 1;
2114 	    break;
2115 	}
2116 	cp = cp2;
2117     }
2118     return matches;
2119 }
2120 
2121 void throughput_calc(char *name, int *bps, double *bpsmult)
2122 {
2123     int match_value = -1;
2124     char cwdir[MAXPATHLEN];
2125     char pwdir[MAXPATHLEN];
2126     char path[MAXPATHLEN];
2127     char file[MAXPATHLEN];
2128     char *ap3 = NULL, *ap4 = NULL;
2129     struct aclmember *entry = NULL;
2130     extern char *home;
2131     char *sp;
2132     int i;
2133 
2134     /* default is maximum throughput */
2135     *bps = -1;
2136     *bpsmult = 1.0;
2137 
2138     /* XXX We could use dynamic RAM to store this path, but I'd rather just bail
2139        out with an error. The rest of wu is so crufy that a long path might
2140        just blow up later */
2141 
2142     if ((strlen(name) + 1) > sizeof(path)) {
2143 	return;
2144     }
2145 
2146     /* what's our current directory? */
2147     strcpy(path, name);
2148     if ((sp = strrchr(path, '/')))
2149 	*sp = '\0';
2150     else
2151 	strcpy(path, ".");
2152     if ((sp = strrchr(name, '/')))
2153 	strcpy(file, sp + 1);
2154     else
2155 	strcpy(file, name);
2156     if ((fb_realpath(path, cwdir)) == NULL) {
2157 	return;
2158     }
2159 
2160     wu_realpath(home, pwdir, chroot_path);
2161 
2162     /* find best matching entry */
2163     while (getaclentry("throughput", &entry) && ARG0 && ARG1 && ARG2 && ARG3 && ARG4 && ARG5 != NULL) {
2164 	if ((0 < path_compare(ARG0, pwdir))
2165 	    && ((i = path_compare(ARG1, cwdir)) >= match_value)
2166 	    ) {
2167 	    if (file_compare(ARG2, file)) {
2168 		if (remote_compare(ARG5)) {
2169 		    match_value = i;
2170 		    ap3 = ARG3;
2171 		    ap4 = ARG4;
2172 		}
2173 	    }
2174 	}
2175     }
2176 
2177     /* if we did get matches */
2178     if (match_value >= 0) {
2179 	if (strcasecmp(ap3, "oo") == 0)
2180 	    *bps = -1;
2181 	else
2182 	    *bps = atoi(ap3);
2183 	if (strcmp(ap4, "-") == 0)
2184 	    *bpsmult = 1.0;
2185 	else
2186 	    *bpsmult = atof(ap4);
2187     }
2188     return;
2189 }
2190 
2191 void throughput_adjust(char *name)
2192 {
2193     int match_value = -1;
2194     char pwdir[MAXPATHLEN];
2195     char cwdir[MAXPATHLEN];
2196     char path[MAXPATHLEN];
2197     char file[MAXPATHLEN];
2198     char buf[MAXPATHLEN];
2199     char *ap3 = NULL, *ap4 = NULL;
2200     char **pap;
2201     struct aclmember *entry = NULL;
2202     extern char *home;
2203     char *sp;
2204     int i;
2205 
2206     /* XXX We could use dynamic RAM to store this path, but I'd rather just bail
2207        out with an error. The rest of wu is so crufy that a long path might
2208        just blow up later */
2209 
2210     if ((strlen(name) + 1) > sizeof(path)) {
2211 	return;
2212     }
2213 
2214     /* what's our current directory? */
2215     strcpy(path, name);
2216     if ((sp = strrchr(path, '/')))
2217 	*sp = '\0';
2218     else
2219 	strcpy(path, ".");
2220     if ((sp = strrchr(name, '/')))
2221 	strcpy(file, sp + 1);
2222     else
2223 	strcpy(file, name);
2224     if ((fb_realpath(path, cwdir)) == NULL) {
2225 	return;
2226     }
2227 
2228     wu_realpath(home, pwdir, chroot_path);
2229 
2230     /* find best matching entry */
2231     while (getaclentry("throughput", &entry) && ARG0 && ARG1 && ARG2 && ARG3 && ARG4 && ARG5 != NULL) {
2232 	if ((0 < path_compare(ARG0, pwdir))
2233 	    && ((i = path_compare(ARG1, cwdir)) >= match_value)
2234 	    ) {
2235 	    if (file_compare(ARG2, file)) {
2236 		if (remote_compare(ARG5)) {
2237 		    match_value = i;
2238 		    ap3 = ARG3;
2239 		    pap = ARG;
2240 		    ap4 = ARG4;
2241 		}
2242 	    }
2243 	}
2244     }
2245 
2246     /* if we did get matches */
2247     if (match_value >= 0) {
2248 	if (strcasecmp(ap3, "oo") != 0) {
2249 	    if (strcmp(ap4, "-") != 0) {
2250 		sprintf(buf, "%.0f", atoi(ap3) * atof(ap4));
2251 		pap[3] = (char *) malloc(strlen(buf) + 1);
2252 		if (pap[3] == NULL) {
2253 		    syslog(LOG_ERR, "malloc error in throughput_adjust");
2254 		    dologout(1);
2255 		}
2256 		/* Use ARG6 to keep track of malloced memory */
2257 		if (pap[6])
2258 		    free(pap[6]);
2259 		pap[6] = pap[3];
2260 		strcpy(pap[3], buf);
2261 	    }
2262 	}
2263     }
2264     return;
2265 }
2266 
2267 #endif
2268 
2269 #ifdef SOLARIS_2
2270 static int CheckMethod = 1;
2271 #else
2272 static int CheckMethod = 0;
2273 #endif
2274 
2275 void SetCheckMethod(const char *method)
2276 {
2277     if ((strcasecmp(method, "md5") == 0)
2278 	|| (strcasecmp(method, "rfc1321") == 0))
2279 	CheckMethod = 0;
2280     else if ((strcasecmp(method, "crc") == 0)
2281 	     || (strcasecmp(method, "posix") == 0))
2282 	CheckMethod = 1;
2283     else {
2284 	reply(500, "Unrecognized checksum method");
2285 	return;
2286     }
2287     switch (CheckMethod) {
2288     default:
2289 	reply(200, "Checksum method is now: MD5 (RFC1321)");
2290 	break;
2291     case 1:
2292 	reply(200, "Checksum method is now: CRC (POSIX)");
2293 	break;
2294     }
2295 }
2296 
2297 void ShowCheckMethod(void)
2298 {
2299     switch (CheckMethod) {
2300     default:
2301 	reply(200, "Current checksum method: MD5 (RFC1321)");
2302 	break;
2303     case 1:
2304 	reply(200, "Current checksum method: CRC (POSIX)");
2305 	break;
2306     }
2307 }
2308 
2309 void CheckSum(char *pathname)
2310 {
2311     char *cmd;
2312     char buf[MAXPATHLEN];
2313     FILE *cmdf;
2314     struct stat st;
2315 
2316     if (stat(pathname, &st) == 0) {
2317 	if ((st.st_mode & S_IFMT) != S_IFREG) {
2318 	    reply(500, "%s: not a plain file.", pathname);
2319 	    return;
2320 	}
2321     }
2322     else {
2323 	perror_reply(550, pathname);
2324 	return;
2325     }
2326 
2327     switch (CheckMethod) {
2328     default:
2329 	cmd = "/bin/md5sum";
2330 	break;
2331     case 1:
2332 	cmd = "/bin/cksum";
2333 	break;
2334     }
2335 
2336     if (strlen(cmd) + 1 + strlen(pathname) + 1 > sizeof(buf)) {
2337 	reply(500, "Pathname too long");
2338 	return;
2339     }
2340     sprintf(buf, "%s %s", cmd, pathname);
2341 
2342     cmdf = ftpd_popen(buf, "r", 0);
2343     if (!cmdf) {
2344 	perror_reply(550, cmd);
2345     }
2346     else {
2347 	if (fgets(buf, sizeof buf, cmdf)) {
2348 	    char *crptr = strchr(buf, '\n');
2349 	    if (crptr != NULL)
2350 		*crptr = '\0';
2351 	    reply(200, "%s", buf);
2352 	}
2353 	ftpd_pclose(cmdf);
2354     }
2355 }
2356 
2357 void CheckSumLastFile(void)
2358 {
2359     extern char LastFileTransferred[];
2360 
2361     if (LastFileTransferred[0] == '\0')
2362 	reply(500, "Nothing transferred yet");
2363     else
2364 	CheckSum(LastFileTransferred);
2365 }
2366