The first a bug in pax and should be commited to FBSD, too.
[dragonfly.git] / contrib / lukemftpd / src / conf.c
1 /*      $NetBSD: conf.c,v 1.46 2001/12/04 13:54:12 lukem Exp $  */
2
3 /*-
4  * Copyright (c) 1997-2001 The NetBSD Foundation, Inc.
5  * All rights reserved.
6  *
7  * This code is derived from software contributed to The NetBSD Foundation
8  * by Simon Burge and Luke Mewburn.
9  *
10  * Redistribution and use in source and binary forms, with or without
11  * modification, are permitted provided that the following conditions
12  * are met:
13  * 1. Redistributions of source code must retain the above copyright
14  *    notice, this list of conditions and the following disclaimer.
15  * 2. Redistributions in binary form must reproduce the above copyright
16  *    notice, this list of conditions and the following disclaimer in the
17  *    documentation and/or other materials provided with the distribution.
18  * 3. All advertising materials mentioning features or use of this software
19  *    must display the following acknowledgement:
20  *        This product includes software developed by the NetBSD
21  *        Foundation, Inc. and its contributors.
22  * 4. Neither the name of The NetBSD Foundation nor the names of its
23  *    contributors may be used to endorse or promote products derived
24  *    from this software without specific prior written permission.
25  *
26  * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
27  * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
28  * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
29  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
30  * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
31  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
32  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
33  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
34  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
35  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
36  * POSSIBILITY OF SUCH DAMAGE.
37  */
38
39 #include "lukemftpd.h"
40
41 #include "extern.h"
42 #include "pathnames.h"
43
44 static char *strend(const char *, char *);
45 static int filetypematch(char *, int);
46
47
48                 /* class defaults */
49 #define DEFAULT_LIMIT           -1              /* unlimited connections */
50 #define DEFAULT_MAXFILESIZE     -1              /* unlimited file size */
51 #define DEFAULT_MAXTIMEOUT      7200            /* 2 hours */
52 #define DEFAULT_TIMEOUT         900             /* 15 minutes */
53 #define DEFAULT_UMASK           027             /* 15 minutes */
54
55 /*
56  * Initialise curclass to an `empty' state
57  */
58 void
59 init_curclass(void)
60 {
61         struct ftpconv  *conv, *cnext;
62
63         for (conv = curclass.conversions; conv != NULL; conv = cnext) {
64                 REASSIGN(conv->suffix, NULL);
65                 REASSIGN(conv->types, NULL);
66                 REASSIGN(conv->disable, NULL);
67                 REASSIGN(conv->command, NULL);
68                 cnext = conv->next;
69                 free(conv);
70         }
71
72         memset((char *)&curclass.advertise, 0, sizeof(curclass.advertise));
73         curclass.advertise.su_len = 0;          /* `not used' */
74         REASSIGN(curclass.chroot, NULL);
75         REASSIGN(curclass.classname, NULL);
76         curclass.conversions =  NULL;
77         REASSIGN(curclass.display, NULL);
78         REASSIGN(curclass.homedir, NULL);
79         curclass.limit =        DEFAULT_LIMIT;  
80         REASSIGN(curclass.limitfile, NULL);
81         curclass.maxfilesize =  DEFAULT_MAXFILESIZE;
82         curclass.maxrateget =   0;
83         curclass.maxrateput =   0;
84         curclass.maxtimeout =   DEFAULT_MAXTIMEOUT;
85         REASSIGN(curclass.motd, xstrdup(_PATH_FTPLOGINMESG));
86         REASSIGN(curclass.notify, NULL);
87         curclass.portmin =      0;
88         curclass.portmax =      0;
89         curclass.rateget =      0;
90         curclass.rateput =      0;
91         curclass.timeout =      DEFAULT_TIMEOUT;
92             /* curclass.type is set elsewhere */
93         curclass.umask =        DEFAULT_UMASK;
94
95         CURCLASS_FLAGS_SET(checkportcmd);
96         CURCLASS_FLAGS_CLR(denyquick);
97         CURCLASS_FLAGS_SET(modify);
98         CURCLASS_FLAGS_SET(passive);
99         CURCLASS_FLAGS_CLR(private);
100         CURCLASS_FLAGS_CLR(sanenames);
101         CURCLASS_FLAGS_SET(upload);
102 }
103
104 /*
105  * Parse the configuration file, looking for the named class, and
106  * define curclass to contain the appropriate settings.
107  */
108 void
109 parse_conf(const char *findclass)
110 {
111         FILE            *f;
112         char            *buf, *p;
113         size_t           len;
114         LLT              llval;
115         int              none, match;
116         char            *endp;
117         char            *class, *word, *arg, *template;
118         const char      *infile;
119         size_t           line;
120         unsigned int     timeout;
121         struct ftpconv  *conv, *cnext;
122
123         init_curclass();
124         REASSIGN(curclass.classname, xstrdup(findclass));
125                         /* set more guest defaults */
126         if (strcasecmp(findclass, "guest") == 0) {
127                 CURCLASS_FLAGS_CLR(modify);
128                 curclass.umask = 0707;
129         }
130
131         infile = conffilename(_PATH_FTPDCONF);
132         if ((f = fopen(infile, "r")) == NULL)
133                 return;
134
135         line = 0;
136         template = NULL;
137         for (;
138             (buf = fparseln(f, &len, &line, NULL, FPARSELN_UNESCCOMM |
139                         FPARSELN_UNESCCONT | FPARSELN_UNESCESC)) != NULL;
140             free(buf)) {
141                 none = match = 0;
142                 p = buf;
143                 if (len < 1)
144                         continue;
145                 if (p[len - 1] == '\n')
146                         p[--len] = '\0';
147                 if (EMPTYSTR(p))
148                         continue;
149                 
150                 NEXTWORD(p, word);
151                 NEXTWORD(p, class);
152                 NEXTWORD(p, arg);
153                 if (EMPTYSTR(word) || EMPTYSTR(class))
154                         continue;
155                 if (strcasecmp(class, "none") == 0)
156                         none = 1;
157                 if (! (strcasecmp(class, findclass) == 0 ||
158                        (template != NULL && strcasecmp(class, template) == 0) ||
159                        none ||
160                        strcasecmp(class, "all") == 0) )
161                         continue;
162
163 #define CONF_FLAG(x) \
164         do { \
165                 if (none || \
166                     (!EMPTYSTR(arg) && strcasecmp(arg, "off") == 0)) \
167                         CURCLASS_FLAGS_CLR(x); \
168                 else \
169                         CURCLASS_FLAGS_SET(x); \
170         } while (0)
171
172 #define CONF_STRING(x) \
173         do { \
174                 if (none || EMPTYSTR(arg)) \
175                         arg = NULL; \
176                 else \
177                         arg = xstrdup(arg); \
178                 REASSIGN(curclass.x, arg); \
179         } while (0)
180
181
182                 if (0)  {
183                         /* no-op */
184
185                 } else if ((strcasecmp(word, "advertise") == 0)
186                         || (strcasecmp(word, "advertize") == 0)) {
187                         struct addrinfo hints, *res;
188                         int             error;
189
190                         memset((char *)&curclass.advertise, 0,
191                             sizeof(curclass.advertise));
192                         curclass.advertise.su_len = 0;
193                         if (none || EMPTYSTR(arg))
194                                 continue;
195                         res = NULL;
196                         memset(&hints, 0, sizeof(hints));
197                                         /*
198                                          * only get addresses of the family
199                                          * that we're listening on
200                                          */
201                         hints.ai_family = ctrl_addr.su_family;
202                         hints.ai_socktype = SOCK_STREAM;
203                         error = getaddrinfo(arg, "0", &hints, &res);
204                         if (error) {
205                                 syslog(LOG_WARNING, "%s line %d: %s",
206                                     infile, (int)line, gai_strerror(error));
207  advertiseparsefail:
208                                 if (res)
209                                         freeaddrinfo(res);
210                                 continue;
211                         }
212                         if (res->ai_next) {
213                                 syslog(LOG_WARNING,
214     "%s line %d: multiple addresses returned for `%s'; please be more specific",
215                                     infile, (int)line, arg);
216                                 goto advertiseparsefail;
217                         }
218                         if (sizeof(curclass.advertise) < res->ai_addrlen || (
219 #ifdef INET6
220                             res->ai_family != AF_INET6 &&
221 #endif
222                             res->ai_family != AF_INET)) {
223                                 syslog(LOG_WARNING,
224     "%s line %d: unsupported protocol %d for `%s'",
225                                     infile, (int)line, res->ai_family, arg);
226                                 goto advertiseparsefail;
227                         }
228                         memcpy(&curclass.advertise, res->ai_addr,
229                             res->ai_addrlen);
230                         curclass.advertise.su_len = res->ai_addrlen;
231                         freeaddrinfo(res);
232
233                 } else if (strcasecmp(word, "checkportcmd") == 0) {
234                         CONF_FLAG(checkportcmd);
235
236                 } else if (strcasecmp(word, "chroot") == 0) {
237                         CONF_STRING(chroot);
238
239                 } else if (strcasecmp(word, "classtype") == 0) {
240                         if (!none && !EMPTYSTR(arg)) {
241                                 if (strcasecmp(arg, "GUEST") == 0)
242                                         curclass.type = CLASS_GUEST;
243                                 else if (strcasecmp(arg, "CHROOT") == 0)
244                                         curclass.type = CLASS_CHROOT;
245                                 else if (strcasecmp(arg, "REAL") == 0)
246                                         curclass.type = CLASS_REAL;
247                                 else {
248                                         syslog(LOG_WARNING,
249                                     "%s line %d: unknown class type `%s'",
250                                             infile, (int)line, arg);
251                                         continue;
252                                 }
253                         }
254
255                 } else if (strcasecmp(word, "conversion") == 0) {
256                         char *suffix, *types, *disable, *convcmd;
257
258                         if (EMPTYSTR(arg)) {
259                                 syslog(LOG_WARNING,
260                                     "%s line %d: %s requires a suffix",
261                                     infile, (int)line, word);
262                                 continue;       /* need a suffix */
263                         }
264                         NEXTWORD(p, types);
265                         NEXTWORD(p, disable);
266                         convcmd = p;
267                         if (convcmd)
268                                 convcmd += strspn(convcmd, " \t");
269                         suffix = xstrdup(arg);
270                         if (none || EMPTYSTR(types) ||
271                             EMPTYSTR(disable) || EMPTYSTR(convcmd)) {
272                                 types = NULL;
273                                 disable = NULL;
274                                 convcmd = NULL;
275                         } else {
276                                 types = xstrdup(types);
277                                 disable = xstrdup(disable);
278                                 convcmd = xstrdup(convcmd);
279                         }
280                         for (conv = curclass.conversions; conv != NULL;
281                             conv = conv->next) {
282                                 if (strcmp(conv->suffix, suffix) == 0)
283                                         break;
284                         }
285                         if (conv == NULL) {
286                                 conv = (struct ftpconv *)
287                                     calloc(1, sizeof(struct ftpconv));
288                                 if (conv == NULL) {
289                                         syslog(LOG_WARNING, "can't malloc");
290                                         continue;
291                                 }
292                                 conv->next = NULL;
293                                 for (cnext = curclass.conversions;
294                                     cnext != NULL; cnext = cnext->next)
295                                         if (cnext->next == NULL)
296                                                 break;
297                                 if (cnext != NULL)
298                                         cnext->next = conv;
299                                 else
300                                         curclass.conversions = conv;
301                         }
302                         REASSIGN(conv->suffix, suffix);
303                         REASSIGN(conv->types, types);
304                         REASSIGN(conv->disable, disable);
305                         REASSIGN(conv->command, convcmd);
306
307                 } else if (strcasecmp(word, "denyquick") == 0) {
308                         CONF_FLAG(denyquick);
309
310                 } else if (strcasecmp(word, "display") == 0) {
311                         CONF_STRING(display);
312
313                 } else if (strcasecmp(word, "homedir") == 0) {
314                         CONF_STRING(homedir);
315
316                 } else if (strcasecmp(word, "limit") == 0) {
317                         int limit;
318
319                         curclass.limit = DEFAULT_LIMIT;
320                         REASSIGN(curclass.limitfile, NULL);
321                         if (none || EMPTYSTR(arg))
322                                 continue;
323                         limit = (int)strtol(arg, &endp, 10);
324                         if (*endp != 0) {
325                                 syslog(LOG_WARNING,
326                                     "%s line %d: invalid limit %s",
327                                     infile, (int)line, arg);
328                                 continue;
329                         }
330                         curclass.limit = limit;
331                         REASSIGN(curclass.limitfile,
332                             EMPTYSTR(p) ? NULL : xstrdup(p));
333
334                 } else if (strcasecmp(word, "maxfilesize") == 0) {
335                         curclass.maxfilesize = DEFAULT_MAXFILESIZE;
336                         if (none || EMPTYSTR(arg))
337                                 continue;
338                         llval = strsuftoll(arg);
339                         if (llval == -1) {
340                                 syslog(LOG_WARNING,
341                                     "%s line %d: invalid maxfilesize %s",
342                                     infile, (int)line, arg);
343                                 continue;
344                         }
345                         curclass.maxfilesize = llval;
346
347                 } else if (strcasecmp(word, "maxtimeout") == 0) {
348                         curclass.maxtimeout = DEFAULT_MAXTIMEOUT;
349                         if (none || EMPTYSTR(arg))
350                                 continue;
351                         timeout = (unsigned int)strtoul(arg, &endp, 10);
352                         if (*endp != 0) {
353                                 syslog(LOG_WARNING,
354                                     "%s line %d: invalid maxtimeout %s",
355                                     infile, (int)line, arg);
356                                 continue;
357                         }
358                         if (timeout < 30) {
359                                 syslog(LOG_WARNING,
360                                     "%s line %d: maxtimeout %d < 30 seconds",
361                                     infile, (int)line, timeout);
362                                 continue;
363                         }
364                         if (timeout < curclass.timeout) {
365                                 syslog(LOG_WARNING,
366                                     "%s line %d: maxtimeout %d < timeout (%d)",
367                                     infile, (int)line, timeout,
368                                     curclass.timeout);
369                                 continue;
370                         }
371                         curclass.maxtimeout = timeout;
372
373                 } else if (strcasecmp(word, "modify") == 0) {
374                         CONF_FLAG(modify);
375
376                 } else if (strcasecmp(word, "motd") == 0) {
377                         CONF_STRING(motd);
378
379                 } else if (strcasecmp(word, "notify") == 0) {
380                         CONF_STRING(notify);
381
382                 } else if (strcasecmp(word, "passive") == 0) {
383                         CONF_FLAG(passive);
384
385                 } else if (strcasecmp(word, "portrange") == 0) {
386                         int minport, maxport;
387                         char *min, *max;
388
389                         curclass.portmin = 0;
390                         curclass.portmax = 0;
391                         if (none || EMPTYSTR(arg))
392                                 continue;
393                         min = arg;
394                         NEXTWORD(p, max);
395                         if (EMPTYSTR(max)) {
396                                 syslog(LOG_WARNING,
397                                    "%s line %d: missing maxport argument",
398                                    infile, (int)line);
399                                 continue;
400                         }
401                         minport = (int)strtol(min, &endp, 10);
402                         if (*endp != 0 || minport < IPPORT_RESERVED ||
403                             minport > IPPORT_ANONMAX) {
404                                 syslog(LOG_WARNING,
405                                     "%s line %d: invalid minport %s",
406                                     infile, (int)line, min);
407                                 continue;
408                         }
409                         maxport = (int)strtol(max, &endp, 10);
410                         if (*endp != 0 || maxport < IPPORT_RESERVED ||
411                             maxport > IPPORT_ANONMAX) {
412                                 syslog(LOG_WARNING,
413                                     "%s line %d: invalid maxport %s",
414                                     infile, (int)line, max);
415                                 continue;
416                         }
417                         if (minport >= maxport) {
418                                 syslog(LOG_WARNING,
419                                     "%s line %d: minport %d >= maxport %d",
420                                     infile, (int)line, minport, maxport);
421                                 continue;
422                         }
423                         curclass.portmin = minport;
424                         curclass.portmax = maxport;
425
426                 } else if (strcasecmp(word, "private") == 0) {
427                         CONF_FLAG(private);
428
429                 } else if (strcasecmp(word, "rateget") == 0) {
430                         curclass.maxrateget = 0;
431                         curclass.rateget = 0;
432                         if (none || EMPTYSTR(arg))
433                                 continue;
434                         llval = strsuftoll(arg);
435                         if (llval == -1) {
436                                 syslog(LOG_WARNING,
437                                     "%s line %d: invalid rateget %s",
438                                     infile, (int)line, arg);
439                                 continue;
440                         }
441                         curclass.maxrateget = llval;
442                         curclass.rateget = llval;
443
444                 } else if (strcasecmp(word, "rateput") == 0) {
445                         curclass.maxrateput = 0;
446                         curclass.rateput = 0;
447                         if (none || EMPTYSTR(arg))
448                                 continue;
449                         llval = strsuftoll(arg);
450                         if (llval == -1) {
451                                 syslog(LOG_WARNING,
452                                     "%s line %d: invalid rateput %s",
453                                     infile, (int)line, arg);
454                                 continue;
455                         }
456                         curclass.maxrateput = llval;
457                         curclass.rateput = llval;
458
459                 } else if (strcasecmp(word, "sanenames") == 0) {
460                         CONF_FLAG(sanenames);
461
462                 } else if (strcasecmp(word, "timeout") == 0) {
463                         curclass.timeout = DEFAULT_TIMEOUT;
464                         if (none || EMPTYSTR(arg))
465                                 continue;
466                         timeout = (unsigned int)strtoul(arg, &endp, 10);
467                         if (*endp != 0) {
468                                 syslog(LOG_WARNING,
469                                     "%s line %d: invalid timeout %s",
470                                     infile, (int)line, arg);
471                                 continue;
472                         }
473                         if (timeout < 30) {
474                                 syslog(LOG_WARNING,
475                                     "%s line %d: timeout %d < 30 seconds",
476                                     infile, (int)line, timeout);
477                                 continue;
478                         }
479                         if (timeout > curclass.maxtimeout) {
480                                 syslog(LOG_WARNING,
481                                     "%s line %d: timeout %d > maxtimeout (%d)",
482                                     infile, (int)line, timeout,
483                                     curclass.maxtimeout);
484                                 continue;
485                         }
486                         curclass.timeout = timeout;
487
488                 } else if (strcasecmp(word, "template") == 0) {
489                         if (none)
490                                 continue;
491                         REASSIGN(template, EMPTYSTR(arg) ? NULL : xstrdup(arg));
492
493                 } else if (strcasecmp(word, "umask") == 0) {
494                         mode_t fumask;
495
496                         curclass.umask = DEFAULT_UMASK;
497                         if (none || EMPTYSTR(arg))
498                                 continue;
499                         fumask = (mode_t)strtoul(arg, &endp, 8);
500                         if (*endp != 0 || fumask > 0777) {
501                                 syslog(LOG_WARNING,
502                                     "%s line %d: invalid umask %s",
503                                     infile, (int)line, arg);
504                                 continue;
505                         }
506                         curclass.umask = fumask;
507
508                 } else if (strcasecmp(word, "upload") == 0) {
509                         CONF_FLAG(upload);
510                         if (! CURCLASS_FLAGS_ISSET(upload))
511                                 CURCLASS_FLAGS_CLR(modify);
512
513                 } else {
514                         syslog(LOG_WARNING,
515                             "%s line %d: unknown directive '%s'",
516                             infile, (int)line, word);
517                         continue;
518                 }
519         }
520         REASSIGN(template, NULL);
521         fclose(f);
522 }
523
524 /*
525  * Show file listed in curclass.display first time in, and list all the
526  * files named in curclass.notify in the current directory.
527  * Send back responses with the prefix `code' + "-".
528  * If code == -1, flush the internal cache of directory names and return.
529  */
530 void
531 show_chdir_messages(int code)
532 {
533         static StringList *slist = NULL;
534
535         struct stat st;
536         struct tm *t;
537         glob_t   gl;
538         time_t   now, then;
539         int      age;
540         char     curwd[MAXPATHLEN];
541         char    *cp, **rlist;
542
543         if (code == -1) {
544                 if (slist != NULL)
545                         sl_free(slist, 1);
546                 slist = NULL;
547                 return;
548         }
549                 
550         if (quietmessages)
551                 return;
552
553                 /* Setup list for directory cache */
554         if (slist == NULL)
555                 slist = sl_init();
556         if (slist == NULL) {
557                 syslog(LOG_WARNING, "can't allocate memory for stringlist");
558                 return;
559         }
560
561                 /* Check if this directory has already been visited */
562         if (getcwd(curwd, sizeof(curwd) - 1) == NULL) {
563                 syslog(LOG_WARNING, "can't getcwd: %s", strerror(errno));
564                 return;
565         }
566         if (sl_find(slist, curwd) != NULL)
567                 return; 
568
569         cp = xstrdup(curwd);
570         if (sl_add(slist, cp) == -1)
571                 syslog(LOG_WARNING, "can't add `%s' to stringlist", cp);
572
573                 /* First check for a display file */
574         (void)display_file(curclass.display, code);
575
576                 /* Now see if there are any notify files */
577         if (EMPTYSTR(curclass.notify))
578                 return;
579
580         memset(&gl, 0, sizeof(gl));
581         if (glob(curclass.notify, GLOB_LIMIT, NULL, &gl) != 0
582             || gl.gl_matchc == 0) {
583                 globfree(&gl);
584                 return;
585         }
586         time(&now);
587         for (rlist = gl.gl_pathv; *rlist != NULL; rlist++) {
588                 if (stat(*rlist, &st) != 0)
589                         continue;
590                 if (!S_ISREG(st.st_mode))
591                         continue;
592                 then = st.st_mtime;
593                 if (code != 0) {
594                         reply(-code, "%s", "");
595                         code = 0;
596                 }
597                 reply(-code, "Please read the file %s", *rlist);
598                 t = localtime(&now);
599                 age = 365 * t->tm_year + t->tm_yday;
600                 t = localtime(&then);
601                 age -= 365 * t->tm_year + t->tm_yday;
602                 reply(-code, "  it was last modified on %.24s - %d day%s ago",
603                     ctime(&then), age, PLURAL(age));
604         }
605         globfree(&gl);
606 }
607
608 int
609 display_file(const char *file, int code)
610 {
611         FILE   *f;
612         char   *buf, *p;
613         char    curwd[MAXPATHLEN];
614         size_t  len;
615         off_t   lastnum;
616         time_t  now;
617
618         lastnum = 0;
619         if (quietmessages)
620                 return (0);
621
622         if (EMPTYSTR(file))
623                 return(0);
624         if ((f = fopen(file, "r")) == NULL)
625                 return (0);
626         reply(-code, "%s", "");
627
628         for (;
629             (buf = fparseln(f, &len, NULL, "\0\0\0", 0)) != NULL; free(buf)) {
630                 if (len > 0)
631                         if (buf[len - 1] == '\n')
632                                 buf[--len] = '\0';
633                 cprintf(stdout, "    ");
634
635                 for (p = buf; *p; p++) {
636                         if (*p == '%') {
637                                 p++;
638                                 switch (*p) {
639
640                                 case 'c':
641                                         cprintf(stdout, "%s",
642                                             curclass.classname ?
643                                             curclass.classname : "<unknown>");
644                                         break;
645
646                                 case 'C':
647                                         if (getcwd(curwd, sizeof(curwd)-1)
648                                             == NULL){
649                                                 syslog(LOG_WARNING,
650                                                     "can't getcwd: %s",
651                                                     strerror(errno));
652                                                 continue;
653                                         }
654                                         cprintf(stdout, "%s", curwd);
655                                         break;
656
657                                 case 'E':
658                                         if (! EMPTYSTR(emailaddr))
659                                                 cprintf(stdout, "%s",
660                                                     emailaddr);
661                                         break;
662
663                                 case 'L':
664                                         cprintf(stdout, "%s", hostname);
665                                         break;
666
667                                 case 'M':
668                                         if (curclass.limit == -1) {
669                                                 cprintf(stdout, "unlimited");
670                                                 lastnum = 0;
671                                         } else {
672                                                 cprintf(stdout, "%d",
673                                                     curclass.limit);
674                                                 lastnum = curclass.limit;
675                                         }
676                                         break;
677
678                                 case 'N':
679                                         cprintf(stdout, "%d", connections);
680                                         lastnum = connections;
681                                         break;
682
683                                 case 'R':
684                                         cprintf(stdout, "%s", remotehost);
685                                         break;
686
687                                 case 's':
688                                         if (lastnum != 1)
689                                                 cprintf(stdout, "s");
690                                         break;
691
692                                 case 'S':
693                                         if (lastnum != 1)
694                                                 cprintf(stdout, "S");
695                                         break;
696
697                                 case 'T':
698                                         now = time(NULL);
699                                         cprintf(stdout, "%.24s", ctime(&now));
700                                         break;
701
702                                 case 'U':
703                                         cprintf(stdout, "%s",
704                                             pw ? pw->pw_name : "<unknown>");
705                                         break;
706
707                                 case '%':
708                                         CPUTC('%', stdout);
709                                         break;
710
711                                 }
712                         } else
713                                 CPUTC(*p, stdout);
714                 }
715                 cprintf(stdout, "\r\n");
716         }
717
718         (void)fflush(stdout);
719         (void)fclose(f);
720         return (1);
721 }
722
723 /*
724  * Parse src, expanding '%' escapes, into dst (which must be at least
725  * MAXPATHLEN long).
726  */
727 void
728 format_path(char *dst, const char *src)
729 {
730         size_t len;
731         const char *p;
732
733         dst[0] = '\0';
734         len = 0;
735         if (src == NULL)
736                 return;
737         for (p = src; *p && len < MAXPATHLEN; p++) {
738                 if (*p == '%') {
739                         p++;
740                         switch (*p) {
741
742                         case 'c':
743                                 len += strlcpy(dst + len, curclass.classname,
744                                     MAXPATHLEN - len);
745                                 break;
746
747                         case 'd':
748                                 len += strlcpy(dst + len, pw->pw_dir,
749                                     MAXPATHLEN - len);
750                                 break;
751
752                         case 'u':
753                                 len += strlcpy(dst + len, pw->pw_name,
754                                     MAXPATHLEN - len);
755                                 break;
756
757                         case '%':
758                                 dst[len++] = '%';
759                                 break;
760
761                         }
762                 } else
763                         dst[len++] = *p;
764         }
765         if (len < MAXPATHLEN)
766                 dst[len] = '\0';
767         dst[MAXPATHLEN - 1] = '\0';
768 }
769
770 /*
771  * Find s2 at the end of s1.  If found, return a string up to (but
772  * not including) s2, otherwise returns NULL.
773  */
774 static char *
775 strend(const char *s1, char *s2)
776 {
777         static  char buf[MAXPATHLEN];
778
779         char    *start;
780         size_t  l1, l2;
781
782         l1 = strlen(s1);
783         l2 = strlen(s2);
784
785         if (l2 >= l1 || l1 >= sizeof(buf))
786                 return(NULL);
787         
788         strlcpy(buf, s1, sizeof(buf));
789         start = buf + (l1 - l2);
790
791         if (strcmp(start, s2) == 0) {
792                 *start = '\0';
793                 return(buf);
794         } else
795                 return(NULL);
796 }
797
798 static int
799 filetypematch(char *types, int mode)
800 {
801         for ( ; types[0] != '\0'; types++)
802                 switch (*types) {
803                   case 'd':
804                         if (S_ISDIR(mode))
805                                 return(1);
806                         break;
807                   case 'f':
808                         if (S_ISREG(mode))
809                                 return(1);
810                         break;
811                 }
812         return(0);
813 }
814
815 /*
816  * Look for a conversion.  If we succeed, return a pointer to the
817  * command to execute for the conversion.
818  *
819  * The command is stored in a static array so there's no memory
820  * leak problems, and not too much to change in ftpd.c.  This
821  * routine doesn't need to be re-entrant unless we start using a
822  * multi-threaded ftpd, and that's not likely for a while...
823  */
824 char **
825 do_conversion(const char *fname)
826 {
827         struct ftpconv  *cp;
828         struct stat      st;
829         int              o_errno;
830         char            *base = NULL;
831         char            *cmd, *p, *lp, **argv;
832         StringList      *sl;
833
834         o_errno = errno;
835         sl = NULL;
836         cmd = NULL;
837         for (cp = curclass.conversions; cp != NULL; cp = cp->next) {
838                 if (cp->suffix == NULL) {
839                         syslog(LOG_WARNING,
840                             "cp->suffix==NULL in conv list; SHOULDN'T HAPPEN!");
841                         continue;
842                 }
843                 if ((base = strend(fname, cp->suffix)) == NULL)
844                         continue;
845                 if (cp->types == NULL || cp->disable == NULL ||
846                     cp->command == NULL)
847                         continue;
848                                         /* Is it enabled? */
849                 if (strcmp(cp->disable, ".") != 0 &&
850                     stat(cp->disable, &st) == 0)
851                                 continue;
852                                         /* Does the base exist? */
853                 if (stat(base, &st) < 0)
854                         continue;
855                                         /* Is the file type ok */
856                 if (!filetypematch(cp->types, st.st_mode))
857                         continue;
858                 break;                  /* "We have a winner!" */
859         }
860
861         /* If we got through the list, no conversion */
862         if (cp == NULL)
863                 goto cleanup_do_conv;
864
865         /* Split up command into an argv */
866         if ((sl = sl_init()) == NULL)
867                 goto cleanup_do_conv;
868         cmd = xstrdup(cp->command);
869         p = cmd;
870         while (p) {
871                 NEXTWORD(p, lp);
872                 if (strcmp(lp, "%s") == 0)
873                         lp = base;
874                 if (sl_add(sl, xstrdup(lp)) == -1)
875                         goto cleanup_do_conv;
876         }
877
878         if (sl_add(sl, NULL) == -1)
879                 goto cleanup_do_conv;
880         argv = sl->sl_str;
881         free(cmd);
882         free(sl);
883         return(argv);
884
885  cleanup_do_conv:
886         if (sl)
887                 sl_free(sl, 1);
888         free(cmd);
889         errno = o_errno;
890         return(NULL);
891 }
892
893 /*
894  * Convert the string `arg' to a long long, which may have an optional SI suffix
895  * (`b', `k', `m', `g', `t'). Returns the number for success, -1 otherwise.
896  */
897 LLT
898 strsuftoll(const char *arg)
899 {
900         char *cp;
901         LLT val;
902
903         if (!isdigit((unsigned char)arg[0]))
904                 return (-1);
905
906         val = STRTOLL(arg, &cp, 10);
907         if (cp != NULL) {
908                 if (cp[0] != '\0' && cp[1] != '\0')
909                          return (-1);
910                 switch (tolower((unsigned char)cp[0])) {
911                 case '\0':
912                 case 'b':
913                         break;
914                 case 'k':
915                         val <<= 10;
916                         break;
917                 case 'm':
918                         val <<= 20;
919                         break;
920                 case 'g':
921                         val <<= 30;
922                         break;
923 #ifndef NO_LONG_LONG
924                 case 't':
925                         val <<= 40;
926                         break;
927 #endif
928                 default:
929                         return (-1);
930                 }
931         }
932         if (val < 0)
933                 return (-1);
934
935         return (val);
936 }
937
938 /*
939  * Count the number of current connections, reading from
940  *      /var/run/ftpd.pids-<class>
941  * Does a kill -0 on each pid in that file, and only counts
942  * processes that exist (or frees the slot if it doesn't).
943  * Adds getpid() to the first free slot. Truncates the file
944  * if possible.
945  */ 
946 void
947 count_users(void)
948 {
949         char    fn[MAXPATHLEN];
950         int     fd, i, last;
951         size_t  count;
952         pid_t  *pids, mypid;
953         struct stat sb;
954
955         (void)strlcpy(fn, _PATH_CLASSPIDS, sizeof(fn));
956         (void)strlcat(fn, curclass.classname, sizeof(fn));
957         pids = NULL;
958         connections = 1;
959
960         if ((fd = open(fn, O_RDWR | O_CREAT, 0600)) == -1)
961                 return;
962 #if HAVE_LOCKF
963         if (lockf(fd, F_TLOCK, 0) == -1)
964                 goto cleanup_count;
965 #elif HAVE_FLOCK
966         if (flock(fd, LOCK_EX | LOCK_NB) != 0)
967                 goto cleanup_count;
968 #else
969     /* XXX: use fcntl ? */
970 #endif
971         if (fstat(fd, &sb) == -1)
972                 goto cleanup_count;
973         if ((pids = malloc(sb.st_size + sizeof(pid_t))) == NULL)
974                 goto cleanup_count;
975         count = read(fd, pids, sb.st_size);
976         if (count < 0 || count != sb.st_size)
977                 goto cleanup_count;
978         count /= sizeof(pid_t);
979         mypid = getpid();
980         last = 0;
981         for (i = 0; i < count; i++) {
982                 if (pids[i] == 0)
983                         continue;
984                 if (kill(pids[i], 0) == -1 && errno != EPERM) {
985                         if (mypid != 0) {
986                                 pids[i] = mypid;
987                                 mypid = 0;
988                                 last = i;
989                         }
990                 } else {
991                         connections++;
992                         last = i;
993                 }
994         }
995         if (mypid != 0) {
996                 if (pids[last] != 0)
997                         last++;
998                 pids[last] = mypid;
999         }
1000         count = (last + 1) * sizeof(pid_t);
1001         if (lseek(fd, 0, SEEK_SET) == -1)
1002                 goto cleanup_count;
1003         if (write(fd, pids, count) == -1)
1004                 goto cleanup_count;
1005         (void)ftruncate(fd, count);
1006
1007  cleanup_count:
1008 #if HAVE_LOCKF
1009         if (lseek(fd, 0, SEEK_SET) != -1)
1010                 (void)lockf(fd, F_ULOCK, 0);
1011 #elif HAVE_FLOCK
1012         (void)flock(fd, LOCK_UN);
1013 #else
1014     /* XXX: use fcntl ? */
1015 #endif
1016         close(fd);
1017         REASSIGN(pids, NULL);
1018 }