diff --git a/tools/ftp-indexer.c b/tools/ftp-indexer.c index 5576e42..0ac98b4 100644 --- a/tools/ftp-indexer.c +++ b/tools/ftp-indexer.c @@ -1,1382 +1,1382 @@ /* ftp-indexer.c - Create an HTML index file for an FTP directory * Copyright (C) 2017 g10 Code GmbH * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, see . */ /* The following script is triggred by a cronjob at ftp.gnupg.org to * build the index pages. * --8<---------------cut here---------------start------------->8--- #!/bin/sh set -e top=/home/ftp scratch=/home/ftp/.scratch cd "$top" opt_force=no if [ "$1" = "--force" ]; then shift opt_force=yes fi INDEXER=/usr/local/bin/ftp-indexer if [ ! -x $INDEXER ]; then echo "mk-ftp-index.html.sh: Index tool $INDEXER not found - aborting" >&2 exit 1 fi INDEXER_OPTS="--reverse-ver --gpgweb --readme --index $scratch/ftp-index.new" INDEXER_OPTS="$INDEXER_OPTS --exclude README --exclude index.html" (find . -type d ! -name '\.*' ! -name dev ; echo .) |\ while read dir rest; do dir=${dir##./} if cd "$dir"; then if [ "$dir" = "." ]; then desc="/" extraopt="--exclude dev" else desc="$dir/" extraopt="" fi [ -f $scratch/index.html ] && rm $scratch/index.html [ -f index.html ] && cat index.html >$scratch/index.html $INDEXER $INDEXER_OPTS $extraopt . "$desc" >$scratch/index.html.new if [ $opt_force = no -a -f $scratch/index.html ]; then grep -v '^$scratch/index.html.x grep -v '^$scratch/index.html.new.x if ! cmp -s $scratch/index.html.x $scratch/index.html.new.x ; then mv $scratch/index.html.new index.html mv $scratch/ftp-index.new .ftp-index fi rm $scratch/index.html [ -f $scratch/index.html.new ] && rm $scratch/index.html.new [ -f $scratch/ftp-index.new ] && rm $scratch/ftp-index.new else mv $scratch/index.html.new index.html mv $scratch/ftp-index.new .ftp-index fi fi cd "$top" done --8<---------------cut here---------------end--------------->8--- * **/ #include #include #include #include #include #include #include #include #include #include #include #define PGMNAME "ftp-indexer" #define VERSION "0.1" #define DIM(v) (sizeof(v)/sizeof((v)[0])) #define DIMof(type,member) DIM(((type *)0)->member) #if __GNUC__ > 2 || (__GNUC__ == 2 && __GNUC_MINOR__ >= 5) #define ATTR_PRINTF(a,b) __attribute__ ((format (printf,a,b))) #define ATTR_NR_PRINTF(a,b) __attribute__ ((noreturn,format (printf,a,b))) #else #define ATTR_PRINTF(a,b) #define ATTR_NR_PRINTF(a,b) #endif #if __GNUC__ >= 4 # define ATTR_SENTINEL(a) __attribute__ ((sentinel(a))) #else # define ATTR_SENTINEL(a) #endif #define digitp(a) ((a) >= '0' && (a) <= '9') #define VALID_URI_CHARS "abcdefghijklmnopqrstuvwxyz" \ "ABCDEFGHIJKLMNOPQRSTUVWXYZ" \ "01234567890@" \ "!\"#$%&'()*+,-./:;<=>?[\\]^_{|}~" /* A simple object to keep strings in a list. */ struct strlist_s { struct strlist_s *next; char d[1]; }; typedef struct strlist_s *strlist_t; /* An object to collect information about files. */ struct finfo_s { struct finfo_s *next; unsigned int is_dir:1; unsigned int is_reg:1; time_t mtime; unsigned long long size; char name[1]; }; typedef struct finfo_s *finfo_t; static int opt_verbose; static int opt_debug; static int opt_reverse; static int opt_reverse_ver; static int opt_files_first; static int opt_html; static const char *opt_index; static int opt_gpgweb; static int opt_readme; static strlist_t opt_exclude; static void die (const char *format, ...) ATTR_NR_PRINTF(1,2); static void err (const char *format, ...) ATTR_PRINTF(1,2); static void inf (const char *format, ...) ATTR_PRINTF(1,2); static char *xstrconcat (const char *s1, ...) ATTR_SENTINEL(0); static void die (const char *fmt, ...) { va_list arg_ptr; va_start (arg_ptr, fmt); fputs (PGMNAME": ", stderr); vfprintf (stderr, fmt, arg_ptr); va_end (arg_ptr); exit (1); } static void err (const char *fmt, ...) { va_list arg_ptr; va_start (arg_ptr, fmt); fputs (PGMNAME": ", stderr); vfprintf (stderr, fmt, arg_ptr); va_end (arg_ptr); } static void inf (const char *fmt, ...) { va_list arg_ptr; if (!opt_verbose) return; va_start (arg_ptr, fmt); fputs (PGMNAME": ", stderr); vfprintf (stderr, fmt, arg_ptr); va_end (arg_ptr); } static void * xmalloc (size_t n) { void *p = malloc (n); if (!p) die ("out of core\n"); return p; } static void * xcalloc (size_t n, size_t k) { void *p = calloc (n, k); if (!p) die ("out of core\n"); return p; } static char * xstrdup (const char *string) { char *buf = xmalloc (strlen (string) + 1); strcpy (buf, string); return buf; } static inline char * my_stpcpy (char *a, const char *b) { while (*b) *a++ = *b++; *a = 0; return (char*)a; } /* Helper for xstrconcat. */ static char * do_strconcat (const char *s1, va_list arg_ptr) { const char *argv[48]; size_t argc; size_t needed; char *buffer, *p; argc = 0; argv[argc++] = s1; needed = strlen (s1); while (((argv[argc] = va_arg (arg_ptr, const char *)))) { needed += strlen (argv[argc]); if (argc >= DIM (argv)-1) die ("internal error: too may args for strconcat\n"); argc++; } needed++; buffer = xmalloc (needed); for (p = buffer, argc=0; argv[argc]; argc++) p = my_stpcpy (p, argv[argc]); return buffer; } /* Concatenate the string S1 with all the following strings up to a NULL. Returns a malloced buffer with the new string or NULL on a malloc error or if too many arguments are given. */ static char * xstrconcat (const char *s1, ...) { va_list arg_ptr; char *result; if (!s1) result = xstrdup (""); else { va_start (arg_ptr, s1); result = do_strconcat (s1, arg_ptr); va_end (arg_ptr); } return result; } /* If SPECIAL is NULL this function escapes in forms mode. */ static size_t escape_data (char *buffer, const void *data, size_t datalen, const char *special) { int forms = !special; const unsigned char *s; size_t n = 0; if (forms) special = "%;?&="; for (s = data; datalen; s++, datalen--) { if (forms && *s == ' ') { if (buffer) *buffer++ = '+'; n++; } else if (forms && *s == '\n') { if (buffer) memcpy (buffer, "%0D%0A", 6); n += 6; } else if (forms && *s == '\r' && datalen > 1 && s[1] == '\n') { if (buffer) memcpy (buffer, "%0D%0A", 6); n += 6; s++; datalen--; } else if (strchr (VALID_URI_CHARS, *s) && !strchr (special, *s)) { if (buffer) *(unsigned char*)buffer++ = *s; n++; } else { if (buffer) { snprintf (buffer, 4, "%%%02X", *s); buffer += 3; } n += 3; } } return n; } static int insert_escapes (char *buffer, const char *string, const char *special) { return escape_data (buffer, string, strlen (string), special); } /* Allocate a new string from STRING using standard HTTP escaping as * well as escaping of characters given in SPECIALS. A common pattern * for SPECIALS is "%;?&=". However it depends on the needs, for * example "+" and "/: often needs to be escaped too. Returns NULL on * failure and sets ERRNO. If SPECIAL is NULL a dedicated forms * encoding mode is used. */ static char * http_escape_string (const char *string, const char *specials) { int n; char *buf; n = insert_escapes (NULL, string, specials); buf = xmalloc (n+1); insert_escapes (buf, string, specials); buf[n] = 0; return buf; } /* Same as http_escape_string but with an explict length. */ static char * http_escape_buffer (const char *string, size_t length, const char *specials) { int n; char *buf; n = escape_data (NULL, string, length, specials); buf = xmalloc (n+1); escape_data (buf, string, length, specials); buf[n] = 0; return buf; } /* Percent-escape the string STR by replacing colons with '%3a'. */ static char * do_percent_escape (const char *str) { int i, j; char *ptr; for (i=j=0; str[i]; i++) if (str[i] == ':' || str[i] == '%' || str[i] == '\n') j++; ptr = xmalloc (i + 2 * j + 1); i = 0; while (*str) { if (*str == ':') { ptr[i++] = '%'; ptr[i++] = '3'; ptr[i++] = 'a'; } else if (*str == '%') { ptr[i++] = '%'; ptr[i++] = '2'; ptr[i++] = '5'; } else if (*str == '\n') { /* The newline is problematic in a line-based format. */ ptr[i++] = '%'; ptr[i++] = '0'; ptr[i++] = 'a'; } else ptr[i++] = *str; str++; } ptr[i] = '\0'; return ptr; } /* Simple percent escape for colon based listings. Returns a * statically allocated buffer. */ static char * percent_escape (const char *str) { static char *buffer; free (buffer); buffer = do_percent_escape (str); return buffer; } /* Escape STRING at a max length of N for use in HTML. Returns a * statically allocated buffer. */ static const char * html_escape_n (const char *string, size_t length) { static char *buffer; char *p; /* The escaped string may be up to 6 times of STRING due to the expansion of '\"' to """. */ free (buffer); p = buffer = xmalloc (6 * length + 1); for (; *string && length; string++, length--) { switch (*string) { case '\"': p = my_stpcpy (p, """); break; case '&': p = my_stpcpy (p, "&"); break; case '<': p = my_stpcpy (p, "<"); break; case '>': p = my_stpcpy (p, ">"); break; default: *p++ = *string; break; } } *p = 0; return buffer; } /* Escape STRING for use in HTML. Returns a statically allocated * buffer. noet that this buffer is shared with the buffer returned * by html_escape_n. */ static const char * html_escape (const char *string) { return html_escape_n (string, strlen (string)); } /* Escape STRING but insert for one https link. */ static const char * html_escape_detect_link (const char *string) { const char *start, *s; char *part1, *url, *part2, *part3; size_t urllen; char *buffer, *p; start = strstr (string, "https://"); if (!start || !start[8] || start[8] == ' ' || start[8] == '\t') return html_escape (string); if (!(start == string || start[-1] == ' ' || start[-1] == '\t' || start[-1] == '<')) return html_escape (string); urllen = 0; for (s = start; *s && *s != ' ' && *s != '\t' && *s != '>'; s++) urllen++; part1 = xstrdup (html_escape_n (string, start-string)); url = http_escape_buffer (start, urllen, "\""); part2 = xstrdup (html_escape_n (start, urllen)); part3 = xstrdup (html_escape (start + urllen)); buffer = xmalloc (strlen (part1) + strlen (url) + strlen (part2) + strlen (part3) + 100); p = my_stpcpy (buffer, part1); p = my_stpcpy (p, ""); p = my_stpcpy (p, part2); p = my_stpcpy (p, ""); my_stpcpy (p, part3); free (part1); free (url); free (part2); free (part3); return buffer; } /* Escape STRING for use as a HREF attribute. Returns a statically * allocated buffer. */ static const char * html_escape_href (const char *string) { static char *buffer; free (buffer); buffer = http_escape_string (string, "\""); return buffer; } /* Format T and return a statically allocated buffer. */ static const char * format_time_now (int human) { static char buffer[40]; struct tm *tp; time_t now; time (&now); tp = gmtime (&now); if (!tp) *buffer = 0; if (human) snprintf (buffer, sizeof buffer, "%04d-%02d-%02d", 1900 + tp->tm_year, tp->tm_mon+1, tp->tm_mday); else snprintf (buffer, sizeof buffer, "%04d%02d%02dT%02d%02d%02dZ", 1900 + tp->tm_year, tp->tm_mon+1, tp->tm_mday, tp->tm_hour, tp->tm_min, tp->tm_sec); return buffer; } /* Format T and return a statically allocated buffer. */ static const char * format_time (time_t t) { static char buffer[80]; struct tm *tp; tp = gmtime (&t); if (!tp) *buffer = 0; else if (opt_html) snprintf (buffer, sizeof buffer, "" "%04d-%02d-%02d", 1900 + tp->tm_year, tp->tm_mon+1, tp->tm_mday, tp->tm_hour, tp->tm_min, tp->tm_sec, 1900 + tp->tm_year, tp->tm_mon+1, tp->tm_mday); else snprintf (buffer, sizeof buffer, "%04d%02d%02dT%02d%02d%02dZ", 1900 + tp->tm_year, tp->tm_mon+1, tp->tm_mday, tp->tm_hour, tp->tm_min, tp->tm_sec); return buffer; } /* Format SIZE and return a statically allocated buffer. */ static const char * format_size (unsigned long long size) { static char buffer[80]; const char *suffix; unsigned long long val = size; if (size < 1024) { val = size; suffix = ""; } else if (size < 1024 * 1024) { val = size / 1024; suffix = "k"; } else if (size < 1024 * 1024 * 1024) { val = size / (1024 * 1024); suffix = "M"; } else { val = size / (1024 * 1024 * 1024); suffix = "G"; } if (opt_html) snprintf (buffer, sizeof buffer, "%llu%s", size, size == 1? "":"s", val, suffix); else snprintf (buffer, sizeof buffer, "%llu%s", val, suffix); return buffer; } /* This function parses the first portion of the version number S and * stores it at NUMBER. On success, this function returns a pointer * into S starting with the first character, which is not part of the * initial number portion; on failure, NULL is returned. */ static const char* parse_version_number (const char *s, int *number) { int val = 0; if (*s == '0' && digitp (s[1])) return NULL; /* Leading zeros are not allowed. */ for (; digitp (*s); s++) { val *= 10; val += *s - '0'; } *number = val; return val < 0 ? NULL : s; } /* This function breaks up the complete string-representation of the * version number S, which is of the following struture: ... The major, * minor and micro number components will be stored in *MAJOR, *MINOR * and *MICRO. * * On success, the last component, the patch level, will be returned; * in failure, NULL will be returned. */ static const char * parse_version_string (const char *s, int *major, int *minor, int *micro) { s = parse_version_number (s, major); if (!s || *s != '.') return NULL; s++; s = parse_version_number (s, minor); if (!s || *s != '.') return NULL; s++; s = parse_version_number (s, micro); if (!s) return NULL; return s; /* patchlevel */ } /* Compare function for version strings. */ static int compare_version_strings (const char *a, const char *b) { int a_major, a_minor, a_micro; int b_major, b_minor, b_micro; const char *a_plvl, *b_plvl; a_plvl = parse_version_string (a, &a_major, &a_minor, &a_micro); if (!a_plvl) a_major = a_minor = a_micro = 0; b_plvl = parse_version_string (b, &b_major, &b_minor, &b_micro); if (!b_plvl) b_major = b_minor = b_micro = 0; if (!a_plvl && !b_plvl) return -1; /* Put invalid strings at the end. */ if (a_plvl && !b_plvl) return 1; if (!a_plvl && b_plvl) return -1; if (a_major > b_major) return 1; if (a_major < b_major) return -1; if (a_minor > b_minor) return 1; if (a_minor < b_minor) return -1; if (a_micro > b_micro) return 1; if (a_micro < b_micro) return -1; if (opt_reverse_ver && !opt_reverse) { - /* We may only compare up to the next dot and the swicth back to + /* We may only compare up to the next dot and the switch back to * regular order. */ for (; *a_plvl && *b_plvl; a_plvl++, b_plvl++) { if (*a_plvl == '.' && *b_plvl == '.') return 0 - strcmp (a_plvl, b_plvl); else if (*a_plvl == '.') return 1; /* B is larger but we need to reverse. */ else if (*b_plvl == '.') return -1; /* A is larger but we need to reverse. */ else if (*a_plvl != *b_plvl) break; } if (*a_plvl == *b_plvl) return 0; else return (*(signed char *)b_plvl - *(signed char *)a_plvl); } else return strcmp (a_plvl, b_plvl); } /* If string looks like a file name with a version nuymber, return a * pointer to the version number part; else return NULL. */ static const char * find_version_string (const char *s) { do { s = strchr (s, '-'); if (!s) return NULL; /* Version string must be prefixed with a dash. */ s++; } while (!digitp (*s)); return s; } /* Sort function for the directory listing. */ static int sort_finfo (const void *arg_a, const void *arg_b) { const finfo_t *a = arg_a; const finfo_t *b = arg_b; const char *astr, *bstr; const char *aver, *bver; if (opt_reverse) { astr = (*b)->name; bstr = (*a)->name; } else { astr = (*a)->name; bstr = (*b)->name; } aver = find_version_string (astr); bver = aver? find_version_string (bstr) : NULL; if (aver && bver && (aver - astr) == (bver - bstr) && !memcmp (astr, bstr, (aver - astr))) { if (opt_reverse_ver) return 0 - compare_version_strings (aver, bver); else return compare_version_strings (aver, bver); } return strcmp(astr, bstr); } /* Note: This function assumes that the CWD is the listed directory. */ static void print_header (const char *title) { const char *esc_title; if (!opt_html) return; esc_title = html_escape (title); if (opt_gpgweb) { FILE *readme; char line[256]; char *p; int c; fputs ("\n" "\n" "\n", stdout); printf("\n" "ftp.gnupg.org:%s\n", esc_title); fputs ("\n", stdout); printf("\n", format_time_now (0)); fputs ("\n" "\n" "\n" "\n", stdout); fputs ("\n" "
\n" "
" " " "
\n", stdout); printf("
\n" "
\n" "

ftp.gnupg.org:%s

\n" "
\n", esc_title); readme = fopen ("README", "r"); if (opt_readme && (readme = fopen ("README", "r"))) { fputs ("
\n", stdout);
           while (fgets (line, sizeof line, readme))
             {
               int no_lf = 0;
               /* Eat up the rest of an incomplete line.  */
               if (!*line)
                 no_lf = 1;
               else if (line[strlen (line)-1] != '\n')
                 {
                   no_lf = 1;
                   while ((c = getc (readme)) != EOF && c != '\n')
                     ;
                 }
 
               /* Replace empty lines with a leading doc by an empty
                * line.  These lines are used on FTP servers to avoid
                * problems with broken FTP cleints.  */
               if (*line == '.')
                 {
                   for (p=line+1; (*p == ' ' || *p == '\t' || *p == '\n'); p++)
                     ;
                   if (!*p)
                     {
                       putchar ('\n');
                       *line = 0;
                     }
                 }
 
               if (*line)
                 fputs (html_escape_detect_link (line), stdout);
               if (no_lf)
                 putchar ('\n');
             }
           fputs ("
\n", stdout); fclose (readme); } fputs ("
\n", stdout); } else { printf ("\n" "\n" "Index of %s\n" "\n" "\n" "

Index of %s

\n" "\n", esc_title, esc_title); } } static void print_footer (void) { if (!opt_html) return; if (opt_gpgweb) { fputs ("\n" "\n" "
\n", stdout); fputs ("
\n" "\n" "
\n", stdout); fputs ("
\n" "\"Traueranzeige:\n" "

\n" "
\n", stdout); fputs ("
\n" "" "\"CC " "This web page is Copyright 2017 GnuPG e.V. and" " licensed under a " "Creative Commons Attribution-ShareAlike 4.0 International" " License. See " "copying for details.\n", stdout); printf("Page last updated on %s.\n", format_time_now (1)); fputs ("
\n" "
\n" "\n" "\n" "\n", stdout); } else { printf ("
\n" "\n" "\n"); } } /* Print COUNT directories from the array SORTED. * Note: This function assumes that the CWD is the listed directory. */ static void print_dirs (finfo_t *sorted, int count, int at_root) { int idx; finfo_t fi; int any = 0; char *title = NULL; for (idx=0; idx < count; idx++) { fi = sorted[idx]; if (!fi->is_dir) continue; if (!any && opt_html) { any = 1; if (opt_gpgweb) { fputs ("

Directories

\n" "
\n" "\n", stdout); if (!at_root) fputs ("" "\n", stdout); } else { fputs ("" "\n", stdout); if (!at_root) fputs ("\n", stdout); } } free (title); title = NULL; if (opt_gpgweb) { char *fname; FILE *fp; fname = xstrconcat (fi->name, "/", ".title", NULL); fp = fopen (fname, "r"); free (fname); if (fp) { char line[200]; if (fgets (line, sizeof line, fp) && *line) { if (line[strlen(line)-1] == '\n') line[strlen(line)-1] = 0; title = xstrdup (html_escape (line)); } fclose (fp); } } if (opt_html) { if (opt_gpgweb) printf ("" "", html_escape_href (fi->name), html_escape (fi->name)); else printf ("", html_escape_href (fi->name), html_escape (fi->name)); if (title) printf ("", title); fputs ("\n", stdout); } else printf ("D %s\n", fi->name); } if (any && opt_gpgweb) { fputs ("
" "Parent Directory
 

Directories

" "Parent Directory
%s
%s%s
\n" "
\n\n", stdout); } else if (opt_gpgweb && !at_root) { /* !any - need to print an UP link */ fputs ("
\n" "\n" "" "\n" "
" "Parent Directory
\n" "
\n", stdout); } free (title); } /* Print COUNT files from the array SORTED. */ static void print_files (finfo_t *sorted, int count) { int idx; finfo_t fi; int any = 0; for (idx=0; idx < count; idx++) { fi = sorted[idx]; if (!fi->is_reg) continue; if (!any && opt_html) { any = 1; if (opt_gpgweb) { fputs ("

Files

\n" "
\n" "\n", stdout); } else fputs ("\n", stdout); } if (opt_gpgweb) printf ("" "" "\n", strstr (fi->name, ".sig")? "document": strstr (fi->name, ".tar")? "tar" : "document", html_escape_href (fi->name), html_escape (fi->name), format_time (fi->mtime), format_size (fi->size)); else if (opt_html) printf ("" "\n", html_escape_href (fi->name), html_escape (fi->name), format_time (fi->mtime), format_size (fi->size)); else printf ("F %s\n", fi->name); } if (any && opt_gpgweb) { fputs ("

Files

%s%s%s
%s%s%s
\n" "
\n\n", stdout); } } /* Scan DIRECTORY and print an index. * FIXME: This does a chdir and does not preserve the old PWD. * The fix is to build the full filename beofre stat'ing. */ static void scan_directory (const char *directory, const char *title) { DIR *dir; struct dirent *dentry; finfo_t fi; finfo_t finfo = NULL; finfo_t *sorted; int count = 0; int idx; size_t len; strlist_t sl; int at_root = 0; if (opt_gpgweb) { if (!strcmp (title, "/")) at_root = 1; } else if (!strcmp (directory, "/")) at_root = 1; dir = opendir (directory); if (!dir) { err ("can't open directory '%s': %s\n", directory, strerror (errno)); return; } while (errno=0,(dentry = readdir (dir))) { if (*dentry->d_name == '.') continue; /* Skip self, parent, and hidden directories. */ len = strlen (dentry->d_name); if (!len) continue; /* Empty filenames should actually not exist. */ if (dentry->d_name[len-1] == '~') continue; /* Skip backup files. */ for (sl = opt_exclude; sl; sl = sl->next) if (!strcmp (sl->d, dentry->d_name)) break; if (sl) continue; /* Skip excluded names. */ fi = xcalloc (1, sizeof *fi + strlen (dentry->d_name)); strcpy (fi->name, dentry->d_name); fi->next = finfo; finfo = fi; count++; } if (errno) die ("error reading directory '%s': %s\n", directory, strerror (errno)); closedir (dir); sorted = xcalloc (count, sizeof *sorted); for (fi=finfo, idx=0; fi; fi = fi->next) sorted[idx++] = fi; inf ("directory '%s' has %d files\n", directory, count); qsort (sorted, count, sizeof *sorted, sort_finfo); if (chdir (directory)) die ("cannot chdir to '%s': %s\n", directory, strerror (errno)); for (idx=0; idx < count; idx++) { struct stat sb; fi = sorted[idx]; if (stat (fi->name, &sb)) { err ("cannot stat '%s': %s\n", fi->name, strerror (errno)); continue; } fi->is_dir = !!S_ISDIR(sb.st_mode); fi->is_reg = !!S_ISREG(sb.st_mode); fi->size = fi->is_reg? sb.st_size : 0; fi->mtime = sb.st_mtime; } print_header (title); if (opt_files_first) { print_files (sorted, count); print_dirs (sorted, count, at_root); } else { print_dirs (sorted, count, at_root); print_files (sorted, count); } print_footer (); /* We create the index file in the current directory. */ if (opt_index) { FILE *indexfp = fopen (opt_index, "w"); if (!indexfp) die ("error creating '%s' for '%s': %s\n", opt_index, directory, strerror (errno)); for (idx=0; idx < count; idx++) { fi = sorted[idx]; fprintf (indexfp, "%s:%c:%llu:%lu:\n", percent_escape (fi->name), fi->is_dir? 'd': fi->is_reg? 'r': '?', fi->size, (unsigned long)fi->mtime); } if (ferror (indexfp)) die ("error writing '%s' for '%s': %s\n", opt_index, directory, strerror (errno)); /* Fixme: Check for close errors. */ fclose (indexfp); } free (sorted); while ((fi = finfo)) { fi = finfo->next; free (finfo); finfo = fi; } } int main (int argc, char **argv) { int last_argc = -1; strlist_t sl; if (argc < 1) die ("Hey, read up on how to use exec(2)\n"); argv++; argc--; while (argc && last_argc != argc ) { last_argc = argc; if (!strcmp (*argv, "--")) { argc--; argv++; break; } else if (!strcmp (*argv, "--version")) { fputs (PGMNAME " " VERSION "\n" "Copyright (C) 2017 g10 Code GmbH\n" "License GPLv3+: GNU GPL version 3 or later" " \n" "This is free software: you are free to change" " and redistribute it.\n" "There is NO WARRANTY, to the extent permitted by law.\n", stdout); exit (0); } else if (!strcmp (*argv, "--help")) { fputs ("usage: " PGMNAME " [options] directory [title]\n" "Print an index for an FTP directory.\n\n" "Options:\n" " --version print program version\n" " --verbose verbose diagnostics\n" " --debug flyswatter\n" " --reverse reverse sort order\n" " --reverse-ver reverse only the version number order\n" " --files-first print files before directories\n" " --html output HTML\n" " --gpgweb output HTML as used at gnupg.org\n" " --readme include README file\n" " --index FILE create index FILE\n" " --exclude NAME ignore file NAME\n" , stdout); exit (0); } else if (!strcmp (*argv, "--verbose")) { opt_verbose++; argc--; argv++; } else if (!strcmp (*argv, "--debug")) { opt_debug++; argc--; argv++; } else if (!strcmp (*argv, "--reverse")) { opt_reverse = 1; argc--; argv++; } else if (!strcmp (*argv, "--reverse-ver")) { opt_reverse_ver = 1; argc--; argv++; } else if (!strcmp (*argv, "--files-first")) { opt_files_first = 1; argc--; argv++; } else if (!strcmp (*argv, "--readme")) { opt_readme = 1; argc--; argv++; } else if (!strcmp (*argv, "--html")) { opt_html = 1; argc--; argv++; } else if (!strcmp (*argv, "--index")) { argc--; argv++; if (!argc || !**argv) die ("argument missing for option '%s'\n", argv[-1]); opt_index = *argv; argc--; argv++; } else if (!strcmp (*argv, "--gpgweb")) { opt_gpgweb = opt_html = 1; argc--; argv++; } else if (!strcmp (*argv, "--exclude")) { argc--; argv++; if (!argc || !**argv) die ("argument missing for option '%s'\n", argv[-1]); sl = xmalloc (sizeof *sl + strlen (*argv)); strcpy (sl->d, *argv); sl->next = opt_exclude; opt_exclude = sl; argc--; argv++; } else if (!strncmp (*argv, "--", 2)) die ("unknown option '%s' (use --help)\n", *argv); } if (argc < 1 || argc > 2) die ("usage: " PGMNAME " [options] directory [title]\n"); scan_directory (argv[0], argv[1]? argv[1]:argv[0]); return 0; } /* Local Variables: compile-command: "cc -Wall -g -o ftp-indexer ftp-indexer.c" End: */ diff --git a/web/swdb.mac b/web/swdb.mac index b7ecd28..57f0874 100644 --- a/web/swdb.mac +++ b/web/swdb.mac @@ -1,218 +1,218 @@ # Version information # Please run make in this directory after changing this file. # # Primary FTP server base directory # #+macro: ftp_base ftp://ftp.gnupg.org/gcrypt #+macro: ftp_loc_base /ftp/gcrypt # # GnuPG-2.2 # #+macro: gnupg22_ver 2.2.11 #+macro: gnupg22_date 2018-11-06 #+macro: gnupg22_size 6496k #+macro: gnupg22_sha1 c762d300c6c5616c14abff1cfaa099baa5fcbd2c #+macro: gnupg22_sha2 496c3e123ef53f35436ddccca58e82acaa901ca4e21174e77386c0cea0c49cd9 #+macro: gnupg22_branch STABLE-BRANCH-2-2 #+macro: gnupg22_w32_ver 2.2.11_20181106 #+macro: gnupg22_w32_date 2018-11-06 #+macro: gnupg22_w32_size 3928k #+macro: gnupg22_w32_sha1 d1b1ba1bcf433cd1accf22772600f8a5186e156c #+macro: gnupg22_w32_sha2 1e3fe08215b34f1ba71e7fb7856151db9167478cb9e8f21140801d776799bdfe # # GnuPG 2.0 (end-of-life) # #+macro: gnupg20_ver 2.0.31 #+macro: gnupg20_date 2017-12-29 #+macro: gnupg20_size 4324k #+macro: gnupg20_sha1 a7d5021a6a39dd67942e00a1239e37063edb00f0 #+macro: gnupg20_sha2 095558cfbba52fba582963e97b0c016889570b4712d6b871abeef2cf93e62293 #+macro: gnupg20_branch STABLE-BRANCH-2-0 # # GnuPG-1 # #+macro: gnupg1_ver 1.4.23 #+macro: gnupg1_date 2018-06-11 #+macro: gnupg1_size 3661k #+macro: gnupg1_sha1 13747486ed5ff707f796f34f50f4c3085c3a6875 #+macro: gnupg1_sha2 c9462f17e651b6507848c08c430c791287cd75491f8b5a8b50c6ed46b12678ba # #+macro: gnupg1_w32cli_ver 1.4.23 #+macro: gnupg1_w32cli_size 2376k #+macro: gnupg1_w32cli_sha1 d4c9962179d36a140be72c34f34e557b56c975b5 # # Gpg4win # #+macro: gpg4win_ver 3.1.5 #+macro: gpg4win_date 2018-11-13 #+macro: gpg4win_src_size 5301k #+macro: gpg4win_src_sha1 7bd3343f76a1d7df2d1d565cf3d1ede959286a4d #+macro: gpg4win_src_sha2 456143b0542557701a5dbb96240e818573b00afadeb9e7eee194343ff4b839e4 #+macro: gpg4win_exe_size 27820k #+macro: gpg4win_exe_sha1 675b2261029ba54486eac08c9ac0fe09c7f1626c #+macro: gpg4win_exe_sha2 4749ab2d02d384abc2b0fd045c86380e6f840b540a2081e6c0f7d538a3397b23 #+macro: gpg4win_isrc_size 223311k #+macro: gpg4win_isrc_sha1 7ff481557428ebc37fc91518375d6239540a7c74 #+macro: gpg4win_isrc_sha2 5ee57522b0da38e3f4ed80e3e6262d7fc6a557b52e7cd42ec32a01db6d1889f1 # # GPA # #+macro: gpa_ver 0.10.0 #+macro: gpa_date 2018-10-16 #+macro: gpa_size 745k #+macro: gpa_sha1 61475989acd12de8b7daacd906200e8b4f519c5a #+macro: gpa_sha2 95dbabe75fa5c8dc47e3acf2df7a51cee096051e5a842b4c9b6d61e40a6177b1 # # PINENTRY # #+macro: pinentry_ver 1.1.0 #+macro: pinentry_date 2017-12-03 #+macro: pinentry_size 456k #+macro: pinentry_sha1 693bdf9f48dfb3e040d92f50b1bb464e268b9fb0 #+macro: pinentry_sha2 68076686fa724a290ea49cdf0d1c0c1500907d1b759a3bcbfbec0293e8f56570 # # GPGME # #+macro: gpgme_ver 1.12.0 #+macro: gpgme_date 2018-10-08 #+macro: gpgme_size 1619k #+macro: gpgme_sha1 6f1828fcd7de4366ca063e57f35e4ab24bc91baf #+macro: gpgme_sha2 b4dc951c3743a60e2e120a77892e9e864fb936b2e58e7c77e8581f4d050e8cd8 # # LIBGCRYPT # #+macro: libgcrypt_ver 1.8.4 #+macro: libgcrypt_date 2018-10-26 #+macro: libgcrypt_size 2920k #+macro: libgcrypt_sha1 4a8ef9db6922f3a31992aca5640b4198a69b58fc #+macro: libgcrypt_sha2 f638143a0672628fde0cad745e9b14deb85dffb175709cacc1f4fe24b93f2227 # # LIBKSBA # #+macro: libksba_ver 1.3.5 #+macro: libksba_date 2016-08-22 #+macro: libksba_size 606k #+macro: libksba_sha1 a98385734a0c3f5b713198e8d6e6e4aeb0b76fde #+macro: libksba_sha2 41444fd7a6ff73a79ad9728f985e71c9ba8cd3e5e53358e70d5f066d35c1a340 # # DirMngr (not any longer needed) # #+macro: dirmngr_ver 1.1.1 #+macro: dirmngr_date 2013-04-26 #+macro: dirmngr_size 554k #+macro: dirmngr_sha1 e708d4aa5ce852f4de3f4b58f4e4f221f5e5c690 # -# LIBGPG-ERROR -# -#+macro: libgpg_error_ver 1.32 -#+macro: libgpg_error_date 2018-07-12 -#+macro: libgpg_error_size 883k -#+macro: libgpg_error_sha1 e310718c7737c816cb1313a2f3baf60fd6a6d5d3 -#+macro: libgpg_error_sha2 c345c5e73cc2332f8d50db84a2280abfb1d8f6d4f1858b9daa30404db44540ca -#+macro: gpgrt_error_ver 1.32 -#+macro: gpgrt_error_date 2018-07-12 -#+macro: gpgrt_error_size 883k -#+macro: gpgrt_error_sha1 e310718c7737c816cb1313a2f3baf60fd6a6d5d3 -#+macro: gpgrt_error_sha2 c345c5e73cc2332f8d50db84a2280abfb1d8f6d4f1858b9daa30404db44540ca +# LIBGPG-ERROR (aka GPGRT) +# +#+macro: libgpg_error_ver 1.33 +#+macro: libgpg_error_date 2018-12-07 +#+macro: libgpg_error_size 896k +#+macro: libgpg_error_sha1 bd40bf4cb6a0b725f5ea91b68d6ae5aeb387a750 +#+macro: libgpg_error_sha2 5d38826656e746c936e7742d9cde072b50baa3c4c49daa168a56813612bf03ff +#+macro: gpgrt_ver 1.33 +#+macro: gpgrt_date 2018-12-07 +#+macro: gpgrt_size 896k +#+macro: gpgrt_sha1 bd40bf4cb6a0b725f5ea91b68d6ae5aeb387a750 +#+macro: gpgrt_sha2 5d38826656e746c936e7742d9cde072b50baa3c4c49daa168a56813612bf03ff # # LIBASSUAN # #+macro: libassuan_ver 2.5.1 #+macro: libassuan_date 2017-12-07 #+macro: libassuan_size 551k #+macro: libassuan_sha1 c8432695bf1daa914a92f51e911881ed93d50604 #+macro: libassuan_sha2 47f96c37b4f2aac289f0bc1bacfa8bd8b4b209a488d3d15e2229cb6cc9b26449 # # NTBTLS # #+macro: ntbtls_ver 0.1.2 #+macro: ntbtls_date 2017-09-19 #+macro: ntbtls_size 334k #+macro: ntbtls_sha1 05cf57cd8400b338b94bdce7287397c24315ac49 #+macro: ntbtls_sha2 8240db84e50c2351b19eb8064bdfd4d25e3c157d37875c62e335df237d7bdce7 # # nPth # #+macro: npth_ver 1.6 #+macro: npth_date 2018-07-16 #+macro: npth_size 293k #+macro: npth_sha1 f9d63e9747b027e4e404fe3c20c73c73719e1731 #+macro: npth_sha2 1393abd9adcf0762d34798dc34fdcf4d0d22a8410721e76f1e3afcd1daa4e2d1 # # GpgEX # #+macro: gpgex_ver 1.0.6 #+macro: gpgex_date 2018-04-10 #+macro: gpgex_size 297k #+macro: gpgex_sha1 443270219d542f97b6f124d2101ae8d803c6dbe2 #+macro: gpgex_sha2 d69898d1022400bd09bb684080edc95be554af736ae9c014df73543f34f7481b # # GpgOL # #+macro: gpgol_ver 2.3.2 #+macro: gpgol_date 2018-11-12 #+macro: gpgol_size 766k #+macro: gpgol_sha1 b2d48f17b2cf20f6c55b98dbad1f14ac6e3adfc4 #+macro: gpgol_sha2 6c298c7a8787e0796a1984d3c1e948b55b9fff685dff3fe7e9d3012001b5b85f # # ADNS # #+macro: adns_ver 1.4-g10-7 #+macro: adns_date 2015-11-20 #+macro: adns_sha1 d2e089d98d3faed6cad009477cc0bc030b35c646 # # zlib (mirrored at our server) # #+macro: zlib_ver 1.2.8 #+macro: zlib_sha1_gz a4d316c404ff54ca545ea71a27af7dbc29817088 # # bzip2 (mirrored and stripped down version) # #+macro: bzip2_ver 1.0.6-g10 #+macro: bzip2_sha1_gz 6e38be3377340a21a1f13ff84b5e6adce97cd1d4 # # SQLite (mirrored at our server) # #+macro: sqlite_ver 3120200 #+macro: sqlite_sha1_gz b43c2e7238e54c50b95fbbd85c48792f4f39af8c # --- end of swdb.mac ---