1 /*
2    Virtual File System: FTP file system
3 
4    Copyright (C) 2015-2021
5    The Free Software Foundation, Inc.
6 
7    Written by: Andrew Borodin <aborodin@vmail.ru>, 2013
8 
9    This file is part of the Midnight Commander.
10 
11    The Midnight Commander is free software: you can redistribute it
12    and/or modify it under the terms of the GNU General Public License as
13    published by the Free Software Foundation, either version 3 of the License,
14    or (at your option) any later version.
15 
16    The Midnight Commander is distributed in the hope that it will be useful,
17    but WITHOUT ANY WARRANTY; without even the implied warranty of
18    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
19    GNU General Public License for more details.
20 
21    You should have received a copy of the GNU General Public License
22    along with this program.  If not, see <http://www.gnu.org/licenses/>.
23  */
24 
25 /** \file
26  *  \brief Source: Virtual File System: FTP file system
27  *  \author Andrew Borodin
28  *  \date 2015
29  *
30  *  Parser of ftp long file list (reply to "LIST -la" command).
31  *  Borrowed from lftp project (http://http://lftp.yar.ru/).
32  *  Author of original lftp code: Alexander V. Lukyanov (lav@yars.free.net)
33  */
34 
35 #include <config.h>
36 
37 #include <ctype.h>              /* isdigit() */
38 #include <stdio.h>              /* sscanf() */
39 #include <stdlib.h>
40 #include <string.h>
41 #include <sys/stat.h>           /* mode_t */
42 #include <time.h>
43 #include <unistd.h>
44 #include <sys/types.h>
45 
46 #include "lib/global.h"
47 
48 #include "lib/vfs/vfs.h"
49 #include "lib/vfs/utilvfs.h"
50 
51 #include "ftpfs.h"
52 
53 /*** global variables ****************************************************************************/
54 
55 /*** file scope macro definitions ****************************************************************/
56 
57 #define number_of_parsers 7
58 
59 #define MINUTE (60)
60 #define HOUR   (60 * MINUTE)
61 #define DAY    (24 * HOUR)
62 
63 #define NO_SIZE     ((off_t) (-1L))
64 #define NO_SIZE_YET ((off_t) (-2L))
65 #define NO_DATE     ((time_t) (-1L))
66 #define NO_DATE_YET ((time_t) (-2L))
67 
68 #define FIRST_TOKEN strtok (line, " \t")
69 #define NEXT_TOKEN  strtok (NULL, " \t")
70 #define FIRST_TOKEN_R strtok_r (line, " \t", &next)
71 #define NEXT_TOKEN_R  strtok_r (NULL, " \t", &next)
72 
73 #define ERR2 do { (*err)++; return FALSE; } while (FALSE)
74 
75 /*** file scope type declarations ****************************************************************/
76 
77 typedef enum
78 {
79     UNKNOWN = 0,
80     DIRECTORY,
81     SYMLINK,
82     NORMAL
83 } filetype;
84 
85 typedef gboolean (*ftpfs_line_parser) (char *line, struct stat * s, char **filename,
86                                        char **linkname, int *err);
87 
88 /* formard declarations */
89 static gboolean ftpfs_parse_long_list_UNIX (char *line, struct stat *s, char **filename,
90                                             char **linkname, int *err);
91 static gboolean ftpfs_parse_long_list_NT (char *line, struct stat *s, char **filename,
92                                           char **linkname, int *err);
93 static gboolean ftpfs_parse_long_list_EPLF (char *line, struct stat *s, char **filename,
94                                             char **linkname, int *err);
95 static gboolean ftpfs_parse_long_list_MLSD (char *line, struct stat *s, char **filename,
96                                             char **linkname, int *err);
97 static gboolean ftpfs_parse_long_list_AS400 (char *line, struct stat *s, char **filename,
98                                              char **linkname, int *err);
99 static gboolean ftpfs_parse_long_list_OS2 (char *line, struct stat *s, char **filename,
100                                            char **linkname, int *err);
101 static gboolean ftpfs_parse_long_list_MacWebStar (char *line, struct stat *s, char **filename,
102                                                   char **linkname, int *err);
103 
104 /*** file scope variables ************************************************************************/
105 
106 static time_t rawnow;
107 static struct tm now;
108 
109 static ftpfs_line_parser line_parsers[number_of_parsers] = {
110     ftpfs_parse_long_list_UNIX,
111     ftpfs_parse_long_list_NT,
112     ftpfs_parse_long_list_EPLF,
113     ftpfs_parse_long_list_MLSD,
114     ftpfs_parse_long_list_AS400,
115     ftpfs_parse_long_list_OS2,
116     ftpfs_parse_long_list_MacWebStar
117 };
118 
119 /* --------------------------------------------------------------------------------------------- */
120 /*** file scope functions ************************************************************************/
121 /* --------------------------------------------------------------------------------------------- */
122 
123 static inline uid_t
ftpfs_get_uid(const char * s)124 ftpfs_get_uid (const char *s)
125 {
126     uid_t u;
127 
128     if (*s < '0' || *s > '9')
129         u = vfs_finduid (s);
130     else
131         u = (uid_t) atol (s);
132 
133     return u;
134 }
135 
136 /* --------------------------------------------------------------------------------------------- */
137 
138 static inline gid_t
ftpfs_get_gid(const char * s)139 ftpfs_get_gid (const char *s)
140 {
141     gid_t g;
142 
143     if (*s < '0' || *s > '9')
144         g = vfs_findgid (s);
145     else
146         g = (gid_t) atol (s);
147 
148     return g;
149 }
150 
151 /* --------------------------------------------------------------------------------------------- */
152 
153 static void
ftpfs_init_time(void)154 ftpfs_init_time (void)
155 {
156     time (&rawnow);
157     now = *localtime (&rawnow);
158 }
159 
160 /* --------------------------------------------------------------------------------------------- */
161 
162 static int
guess_year(int month,int day,int hour,int minute)163 guess_year (int month, int day, int hour, int minute)
164 {
165     int year;
166 
167     (void) hour;
168     (void) minute;
169 
170     year = now.tm_year + 1900;
171 
172     if (month * 32 + day > now.tm_mon * 32 + now.tm_mday + 6)
173         year--;
174 
175     return year;
176 }
177 
178 /* --------------------------------------------------------------------------------------------- */
179 
180 static gboolean
parse_year_or_time(const char * year_or_time,int * year,int * hour,int * minute)181 parse_year_or_time (const char *year_or_time, int *year, int *hour, int *minute)
182 {
183     if (year_or_time[2] == ':')
184     {
185         if (sscanf (year_or_time, "%2d:%2d", hour, minute) != 2)
186             return FALSE;
187 
188         *year = -1;
189     }
190     else
191     {
192         if (sscanf (year_or_time, "%d", year) != 1)
193             return FALSE;
194 
195         *hour = *minute = 0;
196     }
197 
198     return TRUE;
199 }
200 
201 /* --------------------------------------------------------------------------------------------- */
202 
203 /* Converts struct tm to time_t, assuming the data in tm is UTC rather
204    than local timezone (mktime assumes the latter).
205 
206    Contributed by Roger Beeman <beeman@cisco.com>, with the help of
207    Mark Baushke <mdb@cisco.com> and the rest of the Gurus at CISCO.  */
208 static time_t
mktime_from_utc(const struct tm * t)209 mktime_from_utc (const struct tm *t)
210 {
211     struct tm tc;
212     time_t tl, tb;
213 
214     memcpy (&tc, t, sizeof (struct tm));
215 
216     /* UTC times are never DST; if we say -1, we'll introduce odd localtime-
217      * dependant errors. */
218 
219     tc.tm_isdst = 0;
220 
221     tl = mktime (&tc);
222     if (tl == -1)
223         return (-1);
224 
225     tb = mktime (gmtime (&tl));
226 
227     return (tl <= tb ? (tl + (tl - tb)) : (tl - (tb - tl)));
228 }
229 
230 /* --------------------------------------------------------------------------------------------- */
231 
232 static time_t
ftpfs_convert_date(const char * s)233 ftpfs_convert_date (const char *s)
234 {
235     struct tm tm;
236     int year, month, day, hour, minute, second;
237     int skip = 0;
238     int n;
239 
240     memset (&tm, 0, sizeof (tm));
241 
242     n = sscanf (s, "%4d%n", &year, &skip);
243 
244     /* try to workaround server's y2k bug *
245      * I hope in the next 300 years the y2k bug will be finally fixed :) */
246     if (n == 1 && year >= 1910 && year <= 1930)
247     {
248         n = sscanf (s, "%5d%n", &year, &skip);
249         year = year - 19100 + 2000;
250     }
251 
252     if (n != 1)
253         return NO_DATE;
254 
255     n = sscanf (s + skip, "%2d%2d%2d%2d%2d", &month, &day, &hour, &minute, &second);
256 
257     if (n != 5)
258         return NO_DATE;
259 
260     tm.tm_year = year - 1900;
261     tm.tm_mon = month - 1;
262     tm.tm_mday = day;
263     tm.tm_hour = hour;
264     tm.tm_min = minute;
265     tm.tm_sec = second;
266 
267     return mktime_from_utc (&tm);
268 }
269 
270 /* --------------------------------------------------------------------------------------------- */
271 
272 /*
273    -rwxr-xr-x   1 lav      root         4771 Sep 12  1996 install-sh
274    -rw-r--r--   1 lav      root         1349 Feb  2 14:10 lftp.lsm
275    drwxr-xr-x   4 lav      root         1024 Feb 22 15:32 lib
276    lrwxrwxrwx   1 lav      root           33 Feb 14 17:45 ltconfig -> /usr/share/libtool/ltconfig
277 
278    NOTE: group may be missing.
279  */
280 
281 static gboolean
parse_ls_line(char * line,struct stat * s,char ** filename,char ** linkname)282 parse_ls_line (char *line, struct stat *s, char **filename, char **linkname)
283 {
284     char *next = NULL;
285     char *t;
286     mode_t type, mode = 0;
287     char *group_or_size;
288     struct tm date;
289     const char *day_of_month;
290     gboolean year_anomaly = FALSE;
291     char *name;
292 
293     /* parse perms */
294     t = FIRST_TOKEN_R;
295     if (t == NULL)
296         return FALSE;
297 
298     if (!vfs_parse_filetype (t, NULL, &type))
299         return FALSE;
300 
301     if (vfs_parse_fileperms (t + 1, NULL, &mode))
302         mode |= type;
303 
304     s->st_mode = mode;
305 
306     /* link count */
307     t = NEXT_TOKEN_R;
308     if (t == NULL)
309         return FALSE;
310     s->st_nlink = atol (t);
311 
312     /* user */
313     t = NEXT_TOKEN_R;
314     if (t == NULL)
315         return FALSE;
316 
317     s->st_uid = ftpfs_get_uid (t);
318 
319     /* group or size */
320     group_or_size = NEXT_TOKEN_R;
321 
322     /* size or month */
323     t = NEXT_TOKEN_R;
324     if (t == NULL)
325         return FALSE;
326     if (isdigit ((unsigned char) *t))
327     {
328         /* it's size, so the previous was group: */
329         long long size;
330         int n;
331 
332         s->st_gid = ftpfs_get_gid (t);
333 
334         if (sscanf (t, "%lld%n", &size, &n) == 1 && t[n] == '\0')
335             s->st_size = (off_t) size;
336         t = NEXT_TOKEN_R;
337         if (t == NULL)
338             return FALSE;
339     }
340     else
341     {
342         /*  it was month, so the previous was size: */
343         long long size;
344         int n;
345 
346         if (sscanf (group_or_size, "%lld%n", &size, &n) == 1 && group_or_size[n] == '\0')
347             s->st_size = (off_t) size;
348     }
349 
350 #ifdef HAVE_STRUCT_STAT_ST_BLKSIZE
351     s->st_blksize = 512;
352 #endif
353 #ifdef HAVE_STRUCT_STAT_ST_BLOCKS
354     s->st_blocks = (s->st_size + 511) / 512;
355 #endif
356 
357     memset (&date, 0, sizeof (date));
358 
359     if (!vfs_parse_month (t, &date))
360         date.tm_mon = 0;
361 
362     day_of_month = NEXT_TOKEN_R;
363     if (day_of_month == NULL)
364         return FALSE;
365     date.tm_mday = atoi (day_of_month);
366 
367     /* time or year */
368     t = NEXT_TOKEN_R;
369     if (t == NULL)
370         return FALSE;
371     date.tm_isdst = -1;
372     date.tm_hour = date.tm_min = 0;
373     date.tm_sec = 30;
374 
375     if (sscanf (t, "%2d:%2d", &date.tm_hour, &date.tm_min) == 2)
376         date.tm_year = guess_year (date.tm_mon, date.tm_mday, date.tm_hour, date.tm_min) - 1900;
377     else
378     {
379         if (day_of_month + strlen (day_of_month) + 1 == t)
380             year_anomaly = TRUE;
381         date.tm_year = atoi (t) - 1900;
382         /* We don't know the hour.  Set it to something other than 0, or
383          * DST -1 will end up changing the date. */
384         date.tm_hour = 12;
385         date.tm_min = 0;
386         date.tm_sec = 0;
387     }
388 
389     s->st_mtime = mktime (&date);
390     /* Use resulting time value */
391     s->st_atime = s->st_ctime = s->st_mtime;
392 
393     name = strtok_r (NULL, "", &next);
394     if (name == NULL)
395         return FALSE;
396 
397     /* there are ls which output extra space after year. */
398     if (year_anomaly && *name == ' ')
399         name++;
400 
401     if (!S_ISLNK (s->st_mode))
402         *linkname = NULL;
403     else
404     {
405         char *arrow;
406 
407         for (arrow = name; (arrow = strstr (arrow, " -> ")) != NULL; arrow++)
408             if (arrow != name && arrow[4] != '\0')
409             {
410                 *arrow = '\0';
411                 *linkname = g_strdup (arrow + 4);
412                 break;
413             }
414     }
415 
416     *filename = g_strdup (name);
417 
418     return TRUE;
419 }
420 
421 /* --------------------------------------------------------------------------------------------- */
422 
423 static gboolean
ftpfs_parse_long_list_UNIX(char * line,struct stat * s,char ** filename,char ** linkname,int * err)424 ftpfs_parse_long_list_UNIX (char *line, struct stat *s, char **filename, char **linkname, int *err)
425 {
426     int tmp;
427     gboolean ret;
428 
429     if (sscanf (line, "total %d", &tmp) == 1)
430         return FALSE;
431 
432     if (strncasecmp (line, "Status of ", 10) == 0)
433         return FALSE;           /* STAT output. */
434     if (strchr ("bcpsD", line[0]) != NULL)      /* block, char, pipe, socket, Door. */
435         return FALSE;
436 
437     ret = parse_ls_line (line, s, filename, linkname);
438     if (!ret)
439         (*err)++;
440 
441     return ret;
442 }
443 
444 /* --------------------------------------------------------------------------------------------- */
445 
446 /*
447    07-13-98  09:06PM       <DIR>          aix
448    07-13-98  09:06PM       <DIR>          hpux
449    07-13-98  09:06PM       <DIR>          linux
450    07-13-98  09:06PM       <DIR>          ncr
451    07-13-98  09:06PM       <DIR>          solaris
452    03-18-98  06:01AM              2109440 nlxb318e.tar
453    07-02-98  11:17AM                13844 Whatsnew.txt
454  */
455 
456 static gboolean
ftpfs_parse_long_list_NT(char * line,struct stat * s,char ** filename,char ** linkname,int * err)457 ftpfs_parse_long_list_NT (char *line, struct stat *s, char **filename, char **linkname, int *err)
458 {
459     char *t;
460     int month, day, year, hour, minute;
461     char am;
462     struct tm tms;
463     long long size;
464 
465     t = FIRST_TOKEN;
466     if (t == NULL)
467         ERR2;
468     if (sscanf (t, "%2d-%2d-%2d", &month, &day, &year) != 3)
469         ERR2;
470     if (year >= 70)
471         year += 1900;
472     else
473         year += 2000;
474 
475     t = NEXT_TOKEN;
476     if (t == NULL)
477         ERR2;
478     am = 'A';                   /* AM/PM is optional */
479     if (sscanf (t, "%2d:%2d%c", &hour, &minute, &am) < 2)
480         ERR2;
481 
482     t = NEXT_TOKEN;
483     if (t == NULL)
484         ERR2;
485 
486     if (am == 'P')              /* PM - after noon */
487     {
488         hour += 12;
489         if (hour == 24)
490             hour = 0;
491     }
492 
493     tms.tm_sec = 30;            /* seconds after the minute [0, 61]  */
494     tms.tm_min = minute;        /* minutes after the hour [0, 59] */
495     tms.tm_hour = hour;         /* hour since midnight [0, 23] */
496     tms.tm_mday = day;          /* day of the month [1, 31] */
497     tms.tm_mon = month - 1;     /* months since January [0, 11] */
498     tms.tm_year = year - 1900;  /* years since 1900 */
499     tms.tm_isdst = -1;
500 
501 
502     s->st_mtime = mktime (&tms);
503     /* Use resulting time value */
504     s->st_atime = s->st_ctime = s->st_mtime;
505 
506     if (strcmp (t, "<DIR>") == 0)
507         s->st_mode = S_IFDIR;
508     else
509     {
510         s->st_mode = S_IFREG;
511         if (sscanf (t, "%lld", &size) != 1)
512             ERR2;
513         s->st_size = (off_t) size;
514     }
515 
516     t = strtok (NULL, "");
517     if (t == NULL)
518         ERR2;
519     while (*t == ' ')
520         t++;
521     if (*t == '\0')
522         ERR2;
523 
524     *filename = g_strdup (t);
525     *linkname = NULL;
526 
527     return TRUE;
528 }
529 
530 /* --------------------------------------------------------------------------------------------- */
531 
532 /*
533    +i774.71425,m951188401,/,       users
534    +i774.49602,m917883130,r,s79126,        jgr_www2.exe
535 
536    starts with +
537    comma separated
538    first character of field is type:
539    i - ?
540    m - modification time
541    / - means directory
542    r - means plain file
543    s - size
544    up - permissions in octal
545    \t - file name follows.
546  */
547 
548 static gboolean
ftpfs_parse_long_list_EPLF(char * line,struct stat * s,char ** filename,char ** linkname,int * err)549 ftpfs_parse_long_list_EPLF (char *line, struct stat *s, char **filename, char **linkname, int *err)
550 {
551     size_t len;
552     const char *b;
553     const char *name = NULL;
554     size_t name_len = 0;
555     off_t size = NO_SIZE;
556     time_t date = NO_DATE;
557     long date_l;
558     long long size_ll;
559     gboolean dir = FALSE;
560     gboolean type_known = FALSE;
561     int perms = -1;
562     const char *scan;
563     ssize_t scan_len;
564 
565     len = strlen (line);
566     b = line;
567 
568     if (len < 2 || b[0] != '+')
569         ERR2;
570 
571     scan = b + 1;
572     scan_len = len - 1;
573 
574     while (scan != NULL && scan_len > 0)
575     {
576         const char *comma;
577 
578         switch (*scan)
579         {
580         case '\t':             /* the rest is file name. */
581             name = scan + 1;
582             name_len = scan_len - 1;
583             scan = NULL;
584             break;
585         case 's':
586             if (sscanf (scan + 1, "%lld", &size_ll) != 1)
587                 break;
588             size = size_ll;
589             break;
590         case 'm':
591             if (sscanf (scan + 1, "%ld", &date_l) != 1)
592                 break;
593             date = date_l;
594             break;
595         case '/':
596             dir = TRUE;
597             type_known = TRUE;
598             break;
599         case 'r':
600             dir = FALSE;
601             type_known = TRUE;
602             break;
603         case 'i':
604             break;
605         case 'u':
606             if (scan[1] == 'p') /* permissions. */
607                 if (sscanf (scan + 2, "%o", (unsigned int *) &perms) != 1)
608                     perms = -1;
609             break;
610         default:
611             name = NULL;
612             scan = NULL;
613             break;
614         }
615         if (scan == NULL || scan_len == 0)
616             break;
617 
618         comma = (const char *) memchr (scan, ',', scan_len);
619         if (comma == NULL)
620             break;
621 
622         scan_len -= comma + 1 - scan;
623         scan = comma + 1;
624     }
625 
626     if (name == NULL || !type_known)
627         ERR2;
628 
629     *filename = g_strndup (name, name_len);
630     *linkname = NULL;
631 
632     if (size != NO_SIZE)
633         s->st_size = size;
634     if (date != NO_DATE)
635     {
636         s->st_mtime = date;
637         /* Use resulting time value */
638         s->st_atime = s->st_ctime = s->st_mtime;
639     }
640     if (type_known)
641         s->st_mode = dir ? S_IFDIR : S_IFREG;
642     if (perms != -1)
643         s->st_mode |= perms;    /* FIXME */
644 
645     return TRUE;
646 }
647 
648 /* --------------------------------------------------------------------------------------------- */
649 /*
650    Type=cdir;Modify=20021029173810;Perm=el;Unique=BP8AAjJufAA; /
651    Type=pdir;Modify=20021029173810;Perm=el;Unique=BP8AAjJufAA; ..
652    Type=dir;Modify=20010118144705;Perm=e;Unique=BP8AAjNufAA; bin
653    Type=dir;Modify=19981021003019;Perm=el;Unique=BP8AAlhufAA; pub
654    Type=file;Size=12303;Modify=19970124132601;Perm=r;Unique=BP8AAo9ufAA; mailserv.FAQ
655    modify=20161215062118;perm=flcdmpe;type=dir;UNIX.group=503;UNIX.mode=0700; directory-name
656    modify=20161213121618;perm=adfrw;size=6369064;type=file;UNIX.group=503;UNIX.mode=0644; file-name
657    modify=20120103123744;perm=adfrw;size=11;type=OS.unix=symlink;UNIX.group=0;UNIX.mode=0777; www
658  */
659 
660 static gboolean
ftpfs_parse_long_list_MLSD(char * line,struct stat * s,char ** filename,char ** linkname,int * err)661 ftpfs_parse_long_list_MLSD (char *line, struct stat *s, char **filename, char **linkname, int *err)
662 {
663     const char *name = NULL;
664     off_t size = NO_SIZE;
665     time_t date = NO_DATE;
666     const char *owner = NULL;
667     const char *group = NULL;
668     filetype type = UNKNOWN;
669     int perms = -1;
670     char *space;
671     char *tok;
672 
673     space = strstr (line, "; ");
674     if (space != NULL)
675     {
676         name = space + 2;
677         *space = '\0';
678     }
679     else
680     {
681         /* NcFTPd does not put a semicolon after last fact, workaround it. */
682         space = strchr (line, ' ');
683         if (space == NULL)
684             ERR2;
685         name = space + 1;
686         *space = '\0';
687     }
688 
689     for (tok = strtok (line, ";"); tok != NULL; tok = strtok (NULL, ";"))
690     {
691         if (strcasecmp (tok, "Type=cdir") == 0
692             || strcasecmp (tok, "Type=pdir") == 0 || strcasecmp (tok, "Type=dir") == 0)
693         {
694             type = DIRECTORY;
695             continue;
696         }
697         if (strcasecmp (tok, "Type=file") == 0)
698         {
699             type = NORMAL;
700             continue;
701         }
702         if (strcasecmp (tok, "Type=OS.unix=symlink") == 0)
703         {
704             type = SYMLINK;
705             continue;
706         }
707         if (strncasecmp (tok, "Modify=", 7) == 0)
708         {
709             date = ftpfs_convert_date (tok + 7);
710             continue;
711         }
712         if (strncasecmp (tok, "Size=", 5) == 0)
713         {
714             long long size_ll;
715 
716             if (sscanf (tok + 5, "%lld", &size_ll) == 1)
717                 size = size_ll;
718             continue;
719         }
720         if (strncasecmp (tok, "Perm=", 5) == 0)
721         {
722             perms = 0;
723             for (tok += 5; *tok != '\0'; tok++)
724             {
725                 switch (g_ascii_tolower (*tok))
726                 {
727                 case 'e':
728                     perms |= 0111;
729                     break;
730                 case 'l':
731                     perms |= 0444;
732                     break;
733                 case 'r':
734                     perms |= 0444;
735                     break;
736                 case 'c':
737                     perms |= 0200;
738                     break;
739                 case 'w':
740                     perms |= 0200;
741                     break;
742                 default:
743                     break;
744                 }
745             }
746             continue;
747         }
748         if (strncasecmp (tok, "UNIX.mode=", 10) == 0)
749         {
750             if (sscanf (tok + 10, "%o", (unsigned int *) &perms) != 1)
751                 perms = -1;
752             continue;
753         }
754         if (strncasecmp (tok, "UNIX.owner=", 11) == 0)
755         {
756             owner = tok + 11;
757             continue;
758         }
759         if (strncasecmp (tok, "UNIX.group=", 11) == 0)
760         {
761             group = tok + 11;
762             continue;
763         }
764         if (strncasecmp (tok, "UNIX.uid=", 9) == 0)
765         {
766             if (owner == NULL)
767                 owner = tok + 9;
768             continue;
769         }
770         if (strncasecmp (tok, "UNIX.gid=", 9) == 0)
771         {
772             if (group == NULL)
773                 group = tok + 9;
774             continue;
775         }
776     }
777     if (name == NULL || name[0] == '\0' || type == UNKNOWN)
778         ERR2;
779 
780     *filename = g_strdup (name);
781     *linkname = NULL;
782 
783     if (size != NO_SIZE)
784         s->st_size = size;
785     if (date != NO_DATE)
786     {
787         s->st_mtime = date;
788         /* Use resulting time value */
789         s->st_atime = s->st_ctime = s->st_mtime;
790     }
791     switch (type)
792     {
793     case DIRECTORY:
794         s->st_mode = S_IFDIR;
795         break;
796     case SYMLINK:
797         s->st_mode = S_IFLNK;
798         break;
799     case NORMAL:
800         s->st_mode = S_IFREG;
801         break;
802     default:
803         g_assert_not_reached ();
804     }
805     if (perms != -1)
806         s->st_mode |= perms;    /* FIXME */
807     if (owner != NULL)
808         s->st_uid = ftpfs_get_uid (owner);
809     if (group != NULL)
810         s->st_uid = ftpfs_get_gid (group);
811 
812     return TRUE;
813 }
814 
815 /* --------------------------------------------------------------------------------------------- */
816 
817 /*
818    ASUSER          8192 04/26/05 13:54:16 *DIR       dir/
819    ASUSER          8192 04/26/05 13:57:34 *DIR       dir1/
820    ASUSER        365255 02/28/01 15:41:40 *STMF      readme.txt
821    ASUSER       8489625 03/18/03 09:37:00 *STMF      saved.zip
822    ASUSER        365255 02/28/01 15:41:40 *STMF      unist.old
823  */
824 
825 static gboolean
ftpfs_parse_long_list_AS400(char * line,struct stat * s,char ** filename,char ** linkname,int * err)826 ftpfs_parse_long_list_AS400 (char *line, struct stat *s, char **filename, char **linkname, int *err)
827 {
828     char *t;
829     char *user;
830     long long size;
831     int month, day, year, hour, minute, second;
832     struct tm tms;
833     time_t mtime;
834     mode_t type;
835     char *slash;
836 
837     t = FIRST_TOKEN;
838     if (t == NULL)
839         ERR2;
840     user = t;
841 
842     t = NEXT_TOKEN;
843     if (t == NULL)
844         ERR2;
845     if (sscanf (t, "%lld", &size) != 1)
846         ERR2;
847 
848     t = NEXT_TOKEN;
849     if (t == NULL)
850         ERR2;
851     if (sscanf (t, "%2d/%2d/%2d", &month, &day, &year) != 3)
852         ERR2;
853     if (year >= 70)
854         year += 1900;
855     else
856         year += 2000;
857 
858     t = NEXT_TOKEN;
859     if (t == NULL)
860         ERR2;
861     if (sscanf (t, "%2d:%2d:%2d", &hour, &minute, &second) != 3)
862         ERR2;
863 
864     t = NEXT_TOKEN;
865     if (t == NULL)
866         ERR2;
867 
868     tms.tm_sec = second;        /* seconds after the minute [0, 61]  */
869     tms.tm_min = minute;        /* minutes after the hour [0, 59] */
870     tms.tm_hour = hour;         /* hour since midnight [0, 23] */
871     tms.tm_mday = day;          /* day of the month [1, 31] */
872     tms.tm_mon = month - 1;     /* months since January [0, 11] */
873     tms.tm_year = year - 1900;  /* years since 1900 */
874     tms.tm_isdst = -1;
875     mtime = mktime (&tms);
876 
877     t = NEXT_TOKEN;
878     if (t == NULL)
879         ERR2;
880     if (strcmp (t, "*DIR") == 0)
881         type = S_IFDIR;
882     else
883         type = S_IFREG;
884 
885     t = strtok (NULL, "");
886     if (t == NULL)
887         ERR2;
888     while (*t == ' ')
889         t++;
890     if (*t == '\0')
891         ERR2;
892 
893     *linkname = NULL;
894 
895     slash = strchr (t, '/');
896     if (slash != NULL)
897     {
898         if (slash == t)
899             return FALSE;
900 
901         *slash = '\0';
902         type = S_IFDIR;
903         if (slash[1] != '\0')
904         {
905             *filename = g_strdup (t);
906             s->st_mode = type;  /* FIXME */
907             return TRUE;
908         }
909     }
910 
911     *filename = g_strdup (t);
912     s->st_mode = type;
913     s->st_size = (off_t) size;
914     s->st_mtime = mtime;
915     /* Use resulting time value */
916     s->st_atime = s->st_ctime = s->st_mtime;
917     s->st_uid = ftpfs_get_uid (user);
918 
919     return TRUE;
920 }
921 
922 /* --------------------------------------------------------------------------------------------- */
923 
924 /*
925    0          DIR  06-27-96  11:57  PROTOCOL
926    169               11-29-94  09:20  SYSLEVEL.MPT
927  */
928 
929 static gboolean
ftpfs_parse_long_list_OS2(char * line,struct stat * s,char ** filename,char ** linkname,int * err)930 ftpfs_parse_long_list_OS2 (char *line, struct stat *s, char **filename, char **linkname, int *err)
931 {
932     char *t;
933     long long size;
934     int month, day, year, hour, minute;
935     struct tm tms;
936 
937     t = FIRST_TOKEN;
938     if (t == NULL)
939         ERR2;
940 
941     if (sscanf (t, "%lld", &size) != 1)
942         ERR2;
943     s->st_size = (off_t) size;
944 
945     t = NEXT_TOKEN;
946     if (t == NULL)
947         ERR2;
948     s->st_mode = S_IFREG;
949     if (strcmp (t, "DIR") == 0)
950     {
951         s->st_mode = S_IFDIR;
952         t = NEXT_TOKEN;
953 
954         if (t == NULL)
955             ERR2;
956     }
957 
958     if (sscanf (t, "%2d-%2d-%2d", &month, &day, &year) != 3)
959         ERR2;
960     if (year >= 70)
961         year += 1900;
962     else
963         year += 2000;
964 
965     t = NEXT_TOKEN;
966     if (t == NULL)
967         ERR2;
968     if (sscanf (t, "%2d:%2d", &hour, &minute) != 3)
969         ERR2;
970 
971     tms.tm_sec = 30;            /* seconds after the minute [0, 61]  */
972     tms.tm_min = minute;        /* minutes after the hour [0, 59] */
973     tms.tm_hour = hour;         /* hour since midnight [0, 23] */
974     tms.tm_mday = day;          /* day of the month [1, 31] */
975     tms.tm_mon = month - 1;     /* months since January [0, 11] */
976     tms.tm_year = year - 1900;  /* years since 1900 */
977     tms.tm_isdst = -1;
978     s->st_mtime = mktime (&tms);
979     /* Use resulting time value */
980     s->st_atime = s->st_ctime = s->st_mtime;
981 
982     t = strtok (NULL, "");
983     if (t == NULL)
984         ERR2;
985     while (*t == ' ')
986         t++;
987     if (*t == '\0')
988         ERR2;
989     *filename = g_strdup (t);
990     *linkname = NULL;
991 
992     return TRUE;
993 }
994 
995 /* --------------------------------------------------------------------------------------------- */
996 
997 static gboolean
ftpfs_parse_long_list_MacWebStar(char * line,struct stat * s,char ** filename,char ** linkname,int * err)998 ftpfs_parse_long_list_MacWebStar (char *line, struct stat *s, char **filename,
999                                   char **linkname, int *err)
1000 {
1001     char *t;
1002     mode_t type, mode;
1003     struct tm date;
1004     const char *day_of_month;
1005     char *name;
1006 
1007     t = FIRST_TOKEN;
1008     if (t == NULL)
1009         ERR2;
1010 
1011     if (!vfs_parse_filetype (t, NULL, &type))
1012         ERR2;
1013 
1014     s->st_mode = type;
1015 
1016     if (!vfs_parse_fileperms (t + 1, NULL, &mode))
1017         ERR2;
1018     /* permissions are meaningless here. */
1019 
1020     /* "folder" or 0 */
1021     t = NEXT_TOKEN;
1022     if (t == NULL)
1023         ERR2;
1024 
1025     if (strcmp (t, "folder") != 0)
1026     {
1027         long long size;
1028 
1029         /* size? */
1030         t = NEXT_TOKEN;
1031         if (t == NULL)
1032             ERR2;
1033         /* size */
1034         t = NEXT_TOKEN;
1035         if (t == NULL)
1036             ERR2;
1037         if (!isdigit ((unsigned char) *t))
1038             ERR2;
1039 
1040         if (sscanf (t, "%lld", &size) == 1)
1041             s->st_size = (off_t) size;
1042     }
1043     else
1044     {
1045         /* ?? */
1046         t = NEXT_TOKEN;
1047         if (t == NULL)
1048             ERR2;
1049     }
1050 
1051     /* month */
1052     t = NEXT_TOKEN;
1053     if (t == NULL)
1054         ERR2;
1055 
1056     memset (&date, 0, sizeof (date));
1057 
1058     if (!vfs_parse_month (t, &date))
1059         ERR2;
1060 
1061     day_of_month = NEXT_TOKEN;
1062     if (day_of_month == NULL)
1063         ERR2;
1064 
1065     date.tm_mday = atoi (day_of_month);
1066 
1067     /* time or year */
1068     t = NEXT_TOKEN;
1069     if (t == NULL)
1070         ERR2;
1071     if (!parse_year_or_time (t, &date.tm_year, &date.tm_hour, &date.tm_min))
1072         ERR2;
1073 
1074     date.tm_isdst = -1;
1075     date.tm_sec = 30;
1076     if (date.tm_year == -1)
1077         date.tm_year = guess_year (date.tm_mon, date.tm_mday, date.tm_hour, date.tm_min) - 1900;
1078     else
1079         date.tm_hour = 12;
1080 
1081     s->st_mtime = mktime (&date);
1082     /* Use resulting time value */
1083     s->st_atime = s->st_ctime = s->st_mtime;
1084 
1085     name = strtok (NULL, "");
1086     if (name == NULL)
1087         ERR2;
1088 
1089     /* no symlinks on Mac, but anyway. */
1090     if (!S_ISLNK (s->st_mode))
1091         *linkname = NULL;
1092     else
1093     {
1094         char *arrow;
1095 
1096         for (arrow = name; (arrow = strstr (arrow, " -> ")) != NULL; arrow++)
1097             if (arrow != name && arrow[4] != '\0')
1098             {
1099                 *arrow = '\0';
1100                 *linkname = g_strdup (arrow + 4);
1101                 break;
1102             }
1103     }
1104 
1105     *filename = g_strdup (name);
1106 
1107     return TRUE;
1108 }
1109 
1110 /* --------------------------------------------------------------------------------------------- */
1111 /*** public functions ****************************************************************************/
1112 /* --------------------------------------------------------------------------------------------- */
1113 
1114 GSList *
ftpfs_parse_long_list(struct vfs_class * me,struct vfs_s_inode * dir,GSList * buf,int * err_ret)1115 ftpfs_parse_long_list (struct vfs_class * me, struct vfs_s_inode * dir, GSList * buf, int *err_ret)
1116 {
1117     int err[number_of_parsers];
1118     GSList *set[number_of_parsers];     /* arrays of struct vfs_s_entry */
1119     size_t i;
1120     GSList *bufp;
1121     ftpfs_line_parser guessed_parser = NULL;
1122     GSList **the_set = NULL;
1123     int *the_err = NULL;
1124     int *best_err1 = &err[0];
1125     int *best_err2 = &err[1];
1126 
1127     ftpfs_init_time ();
1128 
1129     if (err_ret != NULL)
1130         *err_ret = 0;
1131 
1132     memset (&err, 0, sizeof (err));
1133     memset (&set, 0, sizeof (set));
1134 
1135     for (bufp = buf; bufp != NULL; bufp = g_slist_next (bufp))
1136     {
1137         char *b = (char *) bufp->data;
1138         size_t blen;
1139 
1140         blen = strlen (b);
1141 
1142         if (b[blen - 1] == '\r')
1143         {
1144             b[blen - 1] = '\0';
1145             blen--;
1146         }
1147 
1148         if (blen == 0)
1149             continue;
1150 
1151         if (guessed_parser == NULL)
1152         {
1153             for (i = 0; i < number_of_parsers; i++)
1154             {
1155                 struct vfs_s_entry *info;
1156                 gboolean ok;
1157                 char *tmp_line;
1158                 int nlink;
1159 
1160                 /* parser can clobber the line - work on a copy */
1161                 tmp_line = g_strndup (b, blen);
1162 
1163                 info = vfs_s_generate_entry (me, NULL, dir, 0);
1164                 nlink = info->ino->st.st_nlink;
1165                 ok = (*line_parsers[i]) (tmp_line, &info->ino->st, &info->name,
1166                                          &info->ino->linkname, &err[i]);
1167                 if (ok && strchr (info->name, '/') == NULL)
1168                 {
1169                     info->ino->st.st_nlink = nlink;     /* Ouch, we need to preserve our counts :-( */
1170                     set[i] = g_slist_prepend (set[i], info);
1171                 }
1172                 else
1173                     vfs_s_free_entry (me, info);
1174 
1175                 g_free (tmp_line);
1176 
1177                 if (*best_err1 > err[i])
1178                     best_err1 = &err[i];
1179                 if (*best_err2 > err[i] && best_err1 != &err[i])
1180                     best_err2 = &err[i];
1181 
1182                 if (*best_err1 > 16)
1183                     goto leave; /* too many errors with best parser. */
1184             }
1185 
1186             if (*best_err2 > (*best_err1 + 1) * 16)
1187             {
1188                 i = (size_t) (best_err1 - err);
1189                 guessed_parser = line_parsers[i];
1190                 the_set = &set[i];
1191                 the_err = &err[i];
1192             }
1193         }
1194         else
1195         {
1196             struct vfs_s_entry *info;
1197             gboolean ok;
1198             char *tmp_line;
1199             int nlink;
1200 
1201             /* parser can clobber the line - work on a copy */
1202             tmp_line = g_strndup (b, blen);
1203 
1204             info = vfs_s_generate_entry (me, NULL, dir, 0);
1205             nlink = info->ino->st.st_nlink;
1206             ok = guessed_parser (tmp_line, &info->ino->st, &info->name, &info->ino->linkname,
1207                                  the_err);
1208             if (ok && strchr (info->name, '/') == NULL)
1209             {
1210                 info->ino->st.st_nlink = nlink; /* Ouch, we need to preserve our counts :-( */
1211                 *the_set = g_slist_prepend (*the_set, info);
1212             }
1213             else
1214                 vfs_s_free_entry (me, info);
1215 
1216             g_free (tmp_line);
1217         }
1218     }
1219 
1220     if (the_set == NULL)
1221     {
1222         i = best_err1 - err;
1223         the_set = &set[i];
1224         the_err = &err[i];
1225     }
1226 
1227   leave:
1228     for (i = 0; i < number_of_parsers; i++)
1229         if (&set[i] != the_set)
1230         {
1231             for (bufp = set[i]; bufp != NULL; bufp = g_slist_next (bufp))
1232                 vfs_s_free_entry (me, VFS_ENTRY (bufp->data));
1233 
1234             g_slist_free (set[i]);
1235         }
1236 
1237     if (err_ret != NULL && the_err != NULL)
1238         *err_ret = *the_err;
1239 
1240     return the_set != NULL ? g_slist_reverse (*the_set) : NULL;
1241 }
1242 
1243 /* --------------------------------------------------------------------------------------------- */
1244