3 * Copyright (c) 1992-2001 by Mike Gleason.
14 * The ~/.ncftp/bookmarks file contains a list of sites
15 * the user wants to remember.
17 * Unlike previous versions of the program, we now open/close
18 * the file every time we need it; That way we can have
19 * multiple ncftp processes changing the file. There is still
20 * a possibility that two different processes could be modifying
21 * the file at the same time.
26 int gBookmarkMatchMode
= 0;
27 int gNumBookmarks
= 0;
28 BookmarkPtr gBookmarkTable
= NULL
;
30 extern char gOurDirectoryPath
[];
32 /* Converts a pre-loaded Bookmark structure into a RFC 1738
33 * Uniform Resource Locator.
36 BookmarkToURL(BookmarkPtr bmp
, char *url
, size_t urlsize
)
40 /* //<user>:<password>@<host>:<port>/<url-path> */
41 /* Note that if an absolute path is given,
42 * you need to escape the first entry, i.e. /pub -> %2Fpub
44 (void) Strncpy(url
, "ftp://", urlsize
);
45 if (bmp
->user
[0] != '\0') {
46 (void) Strncat(url
, bmp
->user
, urlsize
);
47 if (bmp
->pass
[0] != '\0') {
48 (void) Strncat(url
, ":", urlsize
);
49 (void) Strncat(url
, "PASSWORD", urlsize
);
51 (void) Strncat(url
, "@", urlsize
);
53 (void) Strncat(url
, bmp
->name
, urlsize
);
54 if (bmp
->port
!= 21) {
55 (void) sprintf(pbuf
, ":%u", (unsigned int) bmp
->port
);
56 (void) Strncat(url
, pbuf
, urlsize
);
58 if (bmp
->dir
[0] == '/') {
59 /* Absolute URL path, must escape first slash. */
60 (void) Strncat(url
, "/%2F", urlsize
);
61 (void) Strncat(url
, bmp
->dir
+ 1, urlsize
);
62 (void) Strncat(url
, "/", urlsize
);
63 } else if (bmp
->dir
[0] != '\0') {
64 (void) Strncat(url
, "/", urlsize
);
65 (void) Strncat(url
, bmp
->dir
, urlsize
);
66 (void) Strncat(url
, "/", urlsize
);
74 SetBookmarkDefaults(BookmarkPtr bmp
)
76 (void) memset(bmp
, 0, sizeof(Bookmark
));
79 bmp
->xferMode
= 'S'; /* Use FTP protocol default as ours too. */
80 bmp
->hasSIZE
= kCommandAvailabilityUnknown
;
81 bmp
->hasMDTM
= kCommandAvailabilityUnknown
;
82 bmp
->hasUTIME
= kCommandAvailabilityUnknown
;
83 bmp
->hasPASV
= kCommandAvailabilityUnknown
;
85 bmp
->lastCall
= (time_t) 0;
87 } /* SetBookmarkDefaults */
92 /* Used when converting hex strings to integral types. */
94 HexCharToNibble(int c
)
114 return (c
- 'a' + 10);
121 return (c
- 'A' + 10);
124 return (-1); /* Error. */
125 } /* HexCharToNibble */
131 /* Fills in a Bookmark structure based off of a line from the NcFTP
135 ParseHostLine(char *line
, BookmarkPtr bmp
)
146 SetBookmarkDefaults(bmp
);
148 tokenend
= token
+ sizeof(token
) - 1;
153 /* Some tokens may need to have a comma in them. Since this is a
154 * field delimiter, these fields use \, to represent a comma, and
155 * \\ for a backslash. This chunk gets the next token, paying
156 * attention to the escaped stuff.
158 for (d
= token
; *s
!= '\0'; ) {
159 if ((*s
== '\\') && (s
[1] != '\0')) {
163 } else if (*s
== ',') {
166 } else if ((*s
== '$') && (s
[1] != '\0') && (s
[2] != '\0')) {
167 n1
= HexCharToNibble(s
[1]);
168 n2
= HexCharToNibble(s
[2]);
169 if ((n1
>= 0) && (n2
>= 0)) {
172 *(unsigned char *)d
++ = (unsigned int) n
;
183 case 1: (void) STRNCPY(bmp
->bookmarkName
, token
); break;
184 case 2: (void) STRNCPY(bmp
->name
, token
); break;
185 case 3: (void) STRNCPY(bmp
->user
, token
); break;
186 case 4: (void) STRNCPY(bmp
->pass
, token
); break;
187 case 5: (void) STRNCPY(bmp
->acct
, token
); break;
188 case 6: (void) STRNCPY(bmp
->dir
, token
);
189 result
= 0; /* Good enough to have these fields. */
192 if (token
[0] != '\0')
193 bmp
->xferType
= (int) token
[0];
196 /* Most of the time, we won't have a port. */
197 if (token
[0] == '\0')
198 bmp
->port
= (unsigned int) kDefaultFTPPort
;
200 bmp
->port
= (unsigned int) atoi(token
);
203 (void) sscanf(token
, "%lx", &L
);
204 bmp
->lastCall
= (time_t) L
;
206 case 10: bmp
->hasSIZE
= atoi(token
); break;
207 case 11: bmp
->hasMDTM
= atoi(token
); break;
208 case 12: bmp
->hasPASV
= atoi(token
); break;
209 case 13: bmp
->isUnix
= atoi(token
);
210 result
= 3; /* Version 3 had all fields to here. */
212 case 14: (void) STRNCPY(bmp
->lastIP
, token
); break;
213 case 15: (void) STRNCPY(bmp
->comment
, token
); break;
219 case 20: bmp
->xferMode
= token
[0];
220 result
= 7; /* Version 7 has all fields to here. */
222 case 21: bmp
->hasUTIME
= atoi(token
);
224 case 22: (void) STRNCPY(bmp
->ldir
, token
);
225 result
= 8; /* Version 8 has all fields to here. */
228 result
= 99; /* Version >8 ? */
234 /* Decode password, if it was base-64 encoded. */
235 if (strncmp(bmp
->pass
, kPasswordMagic
, kPasswordMagicLen
) == 0) {
236 FromBase64(pass
, bmp
->pass
+ kPasswordMagicLen
, strlen(bmp
->pass
+ kPasswordMagicLen
), 1);
237 (void) STRNCPY(bmp
->pass
, pass
);
240 } /* ParseHostLine */
246 CloseBookmarkFile(FILE *fp
)
250 } /* CloseBookmarkFile */
257 GetNextBookmark(FILE *fp
, Bookmark
*bmp
)
261 while (FGets(line
, sizeof(line
), fp
) != NULL
) {
262 if (ParseHostLine(line
, bmp
) >= 0)
266 } /* GetNextBookmark */
271 /* Opens a NcFTP 2.x or 3.x style bookmarks file, and sets the file pointer
272 * so that it is ready to read the first data line.
275 OpenBookmarkFile(int *numBookmarks0
)
277 char pathName
[256], path2
[256];
284 if (gOurDirectoryPath
[0] == '\0')
285 return NULL
; /* Don't create in root directory. */
286 (void) OurDirectoryPath(pathName
, sizeof(pathName
), kBookmarkFileName
);
287 fp
= fopen(pathName
, FOPEN_READ_TEXT
);
289 /* See if it exists under the old name. */
290 (void) OurDirectoryPath(path2
, sizeof(path2
), kOldBookmarkFileName
);
291 if (rename(path2
, pathName
) == 0) {
292 /* Rename succeeded, now open it. */
293 fp
= fopen(pathName
, FOPEN_READ_TEXT
);
297 return NULL
; /* Okay to not have one yet. */
300 (void) chmod(pathName
, 00600);
301 if (FGets(line
, sizeof(line
), fp
) == NULL
) {
302 (void) fprintf(stderr
, "%s: invalid format.\n", pathName
);
307 /* Sample line we're looking for:
308 * "NcFTP bookmark-file version: 8"
311 (void) sscanf(line
, "%*s %*s %*s %d", &version
);
312 if (version
< kBookmarkMinVersion
) {
314 (void) fprintf(stderr
, "%s: invalid format, or bad version.\n", pathName
);
318 (void) STRNCPY(path2
, pathName
);
319 (void) sprintf(line
, ".v%d", version
);
320 (void) STRNCAT(path2
, line
);
321 (void) rename(pathName
, path2
);
322 (void) fprintf(stderr
, "%s: old version.\n", pathName
);
327 /* Sample line we're looking for:
328 * "Number of entries: 28" or "# # # 1"
332 /* At the moment, we can't trust the number stored in the
333 * file. It's there for future use.
335 if (FGets(line
, sizeof(line
), fp
) == NULL
) {
336 (void) fprintf(stderr
, "%s: invalid format.\n", pathName
);
341 if (numBookmarks0
== (int *) 0) {
342 /* If the caller doesn't care how many bookmarks are *really*
343 * in the file, then we can return now.
348 /* Otherwise, we have to read through the whole file because
349 * unfortunately the header line can't be trusted.
351 for (numBookmarks
= 0; ; numBookmarks
++) {
352 if (GetNextBookmark(fp
, &junkbm
) < 0)
356 /* Now we have to re-open and re-position the file.
357 * We don't use rewind() because it doesn't always work.
358 * This introduces a race condition, but the bookmark
359 * functionality wasn't designed to be air-tight.
361 CloseBookmarkFile(fp
);
362 fp
= fopen(pathName
, FOPEN_READ_TEXT
);
365 if (FGets(line
, sizeof(line
), fp
) == NULL
) {
366 (void) fprintf(stderr
, "%s: invalid format.\n", pathName
);
371 if (FGets(line
, sizeof(line
), fp
) == NULL
) {
372 (void) fprintf(stderr
, "%s: invalid format.\n", pathName
);
377 /* NOW we're done. */
378 *numBookmarks0
= numBookmarks
;
380 } /* OpenBookmarkFile */
385 /* Looks for a saved bookmark by the abbreviation given. */
387 GetBookmark(const char *const bmabbr
, Bookmark
*bmp
)
394 size_t byBmNameFlag
= 0;
395 size_t byBmAbbrFlag
= 0;
396 size_t byHostNameFlag
= 0;
397 size_t byHostAbbrFlag
= 0;
403 fp
= OpenBookmarkFile(NULL
);
407 bmabbrLen
= strlen(bmabbr
);
408 while (FGets(line
, sizeof(line
), fp
) != NULL
) {
409 if (ParseHostLine(line
, bmp
) < 0)
411 if (ISTREQ(bmp
->bookmarkName
, bmabbr
)) {
412 /* Exact match, done. */
413 byBmNameFlag
= bmabbrLen
;
416 } else if (ISTRNEQ(bmp
->bookmarkName
, bmabbr
, bmabbrLen
)) {
417 /* Remember this one, it matched an abbreviated
421 byBmAbbrFlag
= bmabbrLen
;
422 } else if (ISTREQ(bmp
->name
, bmabbr
)) {
423 /* Remember this one, it matched a full
427 byHostNameFlag
= bmabbrLen
;
428 } else if ((cp
= strchr(bmp
->name
, '.')) != NULL
) {
429 /* See if it matched part of the hostname. */
430 if (ISTRNEQ(bmp
->name
, "ftp", 3)) {
432 } else if (ISTRNEQ(bmp
->name
, "www", 3)) {
437 if (ISTRNEQ(cp
, bmabbr
, bmabbrLen
)) {
438 /* Remember this one, it matched a full
442 byHostAbbrFlag
= bmabbrLen
;
447 if (gBookmarkMatchMode
== 0) {
448 /* Only use a bookmark when the exact
449 * bookmark name was used.
451 if (exactMatch
!= 0) {
455 /* Pick the best match, if any. */
456 if (byBmNameFlag
!= 0) {
457 /* *bmp is already set. */
459 } else if (byBmAbbrFlag
!= 0) {
462 } else if (byHostNameFlag
!= 0) {
465 } else if (byHostAbbrFlag
!= 0) {
472 memset(bmp
, 0, sizeof(Bookmark
));
474 CloseBookmarkFile(fp
);
482 BookmarkSortProc(const void *a
, const void *b
)
484 return (ISTRCMP((*(Bookmark
*)a
).bookmarkName
, (*(Bookmark
*)b
).bookmarkName
));
485 } /* BookmarkSortProc */
490 BookmarkSearchProc(const void *key
, const void *b
)
492 return (ISTRCMP((char *) key
, (*(Bookmark
*)b
).bookmarkName
));
493 } /* BookmarkSearchProc */
498 SearchBookmarkTable(const char *key
)
500 return ((BookmarkPtr
) bsearch(key
, gBookmarkTable
, (size_t) gNumBookmarks
, sizeof(Bookmark
), BookmarkSearchProc
));
501 } /* SearchBookmarkTable */
509 if ((gBookmarkTable
== NULL
) || (gNumBookmarks
< 2))
512 /* Sorting involves swapping entire Bookmark structures.
513 * Normally the proper thing to do is to use an array
514 * of pointers to Bookmarks and sort them, but even
515 * these days a large bookmark list can be sorted in
516 * the blink of an eye.
518 qsort(gBookmarkTable
, (size_t) gNumBookmarks
, sizeof(Bookmark
), BookmarkSortProc
);
519 } /* SortBookmarks */
524 LoadBookmarkTable(void)
529 infp
= OpenBookmarkFile(&nb
);
533 if ((nb
!= gNumBookmarks
) && (gBookmarkTable
!= NULL
)) {
534 /* Re-loading the table from disk. */
535 gBookmarkTable
= (Bookmark
*) realloc(gBookmarkTable
, (size_t) (nb
+ 1) * sizeof(Bookmark
));
536 memset(gBookmarkTable
, 0, (nb
+ 1) * sizeof(Bookmark
));
538 gBookmarkTable
= calloc((size_t) (nb
+ 1), (size_t) sizeof(Bookmark
));
541 if (gBookmarkTable
== NULL
) {
542 CloseBookmarkFile(infp
);
546 for (i
=0; i
<nb
; i
++) {
547 if (GetNextBookmark(infp
, gBookmarkTable
+ i
) < 0) {
553 CloseBookmarkFile(infp
);
556 } /* LoadBookmarkTable */
561 /* Some characters need to be escaped so the file is editable and can
562 * be parsed correctly the next time it is read.
565 BmEscapeTok(char *dst
, size_t dsize
, char *src
)
567 char *dlim
= dst
+ dsize
- 1;
571 while ((c
= *src
) != '\0') {
573 if ((c
== '\\') || (c
== ',') || (c
== '$')) {
574 /* These need to be escaped. */
575 if ((dst
+ 1) < dlim
) {
579 } else if (!isprint(c
)) {
580 /* Escape non-printing characters. */
581 if ((dst
+ 2) < dlim
) {
582 (void) sprintf(dst
, "$%02x", c
);
597 /* Converts a Bookmark into a text string, and writes it to the saved
601 WriteBmLine(Bookmark
*bmp
, FILE *outfp
, int savePassword
)
606 if (fprintf(outfp
, "%s", bmp
->bookmarkName
) < 0) return (-1) ;/*1*/
607 if (fprintf(outfp
, ",%s", BmEscapeTok(tok
, sizeof(tok
), bmp
->name
)) < 0) return (-1) ;/*2*/
608 if (fprintf(outfp
, ",%s", BmEscapeTok(tok
, sizeof(tok
), bmp
->user
)) < 0) return (-1) ;/*3*/
609 if ((bmp
->pass
[0] != '\0') && (savePassword
== 1)) {
610 (void) memcpy(pass
, kPasswordMagic
, kPasswordMagicLen
);
611 ToBase64(pass
+ kPasswordMagicLen
, bmp
->pass
, strlen(bmp
->pass
), 1);
612 if (fprintf(outfp
, ",%s", pass
) < 0) return (-1) ;/*4*/
614 if (fprintf(outfp
, ",%s", "") < 0) return (-1) ;/*4*/
616 if (fprintf(outfp
, ",%s", BmEscapeTok(tok
, sizeof(tok
), bmp
->acct
)) < 0) return (-1) ;/*5*/
617 if (fprintf(outfp
, ",%s", BmEscapeTok(tok
, sizeof(tok
), bmp
->dir
)) < 0) return (-1) ;/*6*/
618 if (fprintf(outfp
, ",%c", bmp
->xferType
) < 0) return (-1) ;/*7*/
619 if (fprintf(outfp
, ",%u", (unsigned int) bmp
->port
) < 0) return (-1) ;/*8*/
620 if (fprintf(outfp
, ",%lu", (unsigned long) bmp
->lastCall
) < 0) return (-1) ;/*9*/
621 if (fprintf(outfp
, ",%d", bmp
->hasSIZE
) < 0) return (-1) ;/*10*/
622 if (fprintf(outfp
, ",%d", bmp
->hasMDTM
) < 0) return (-1) ;/*11*/
623 if (fprintf(outfp
, ",%d", bmp
->hasPASV
) < 0) return (-1) ;/*12*/
624 if (fprintf(outfp
, ",%d", bmp
->isUnix
) < 0) return (-1) ;/*13*/
625 if (fprintf(outfp
, ",%s", bmp
->lastIP
) < 0) return (-1) ;/*14*/
626 if (fprintf(outfp
, ",%s", BmEscapeTok(tok
, sizeof(tok
), bmp
->comment
)) < 0) return (-1) ;/*15*/
627 if (fprintf(outfp
, ",%s", "") < 0) return (-1) ;/*16*/
628 if (fprintf(outfp
, ",%s", "") < 0) return (-1) ;/*17*/
629 if (fprintf(outfp
, ",%s", "") < 0) return (-1) ;/*18*/
630 if (fprintf(outfp
, ",%s", "") < 0) return (-1) ;/*19*/
631 if (fprintf(outfp
, ",%c", bmp
->xferMode
) < 0) return (-1) ;/*20*/
632 if (fprintf(outfp
, ",%d", bmp
->hasUTIME
) < 0) return (-1) ;/*21*/
633 if (fprintf(outfp
, ",%s", BmEscapeTok(tok
, sizeof(tok
), bmp
->ldir
)) < 0) return (-1) ;/*22*/
634 if (fprintf(outfp
, "\n") < 0) return (-1) ;
635 if (fflush(outfp
) < 0) return (-1);
642 SwapBookmarkFiles(void)
645 char pathName
[256], path2
[256];
647 (void) OurDirectoryPath(path2
, sizeof(path2
), kBookmarkFileName
);
648 (void) OurDirectoryPath(pathName
, sizeof(pathName
), kTmpBookmarkFileName
);
649 (void) sprintf(pidStr
, "-%u.txt", (unsigned int) getpid());
650 (void) STRNCAT(pathName
, pidStr
);
652 (void) remove(path2
);
653 if (rename(pathName
, path2
) < 0) {
657 } /* SwapBookmarkFiles */
664 /* Saves a Bookmark structure into the bookmarks file. */
666 OpenTmpBookmarkFile(int nb
)
670 char pathName
[256], path2
[256];
672 if (gOurDirectoryPath
[0] == '\0')
673 return (NULL
); /* Don't create in root directory. */
675 (void) OurDirectoryPath(path2
, sizeof(path2
), kBookmarkFileName
);
676 (void) OurDirectoryPath(pathName
, sizeof(pathName
), kTmpBookmarkFileName
);
677 (void) sprintf(pidStr
, "-%u.txt", (unsigned int) getpid());
678 (void) STRNCAT(pathName
, pidStr
);
680 outfp
= fopen(pathName
, FOPEN_WRITE_TEXT
);
682 (void) fprintf(stderr
, "Could not save bookmark.\n");
686 (void) chmod(pathName
, 00600);
688 if (fprintf(outfp
, "NcFTP bookmark-file version: %d\nNumber of bookmarks: %d\n", kBookmarkVersion
, nb
) < 0) {
689 (void) fprintf(stderr
, "Could not save bookmark.\n");
691 (void) fclose(outfp
);
695 if (fprintf(outfp
, "NcFTP bookmark-file version: %d\nNumber of bookmarks: ??\n", kBookmarkVersion
) < 0) {
696 (void) fprintf(stderr
, "Could not save bookmark.\n");
698 (void) fclose(outfp
);
704 } /* OpenTmpBookmarkFile */
710 SaveBookmarkTable(void)
716 if ((gNumBookmarks
< 1) || (gBookmarkTable
== NULL
))
717 return (0); /* Nothing to save. */
719 /* Get a count of live bookmarks. */
720 for (i
=0, nb
=0; i
<gNumBookmarks
; i
++) {
721 if (gBookmarkTable
[i
].deleted
== 0)
724 outfp
= OpenTmpBookmarkFile(nb
);
729 for (i
=0; i
<gNumBookmarks
; i
++) {
730 if (gBookmarkTable
[i
].deleted
== 0) {
731 if (WriteBmLine(gBookmarkTable
+ i
, outfp
, 1) < 0) {
732 CloseBookmarkFile(outfp
);
737 CloseBookmarkFile(outfp
);
738 if (SwapBookmarkFiles() < 0) {
742 } /* SaveBookmarkTable */
746 /* Saves a Bookmark structure into the bookmarks file. */
748 PutBookmark(Bookmark
*bmp
, int savePassword
)
756 outfp
= OpenTmpBookmarkFile(0);
760 (void) STRNCPY(bmAbbr
, bmp
->bookmarkName
);
761 (void) STRNCAT(bmAbbr
, ",");
762 len
= strlen(bmAbbr
);
764 /* This may fail the first time we ever save a bookmark. */
765 infp
= OpenBookmarkFile(NULL
);
767 while (FGets(line
, sizeof(line
), infp
) != NULL
) {
768 if (strncmp(line
, bmAbbr
, len
) == 0) {
769 /* Replace previous entry. */
770 if (WriteBmLine(bmp
, outfp
, savePassword
) < 0) {
771 (void) fprintf(stderr
, "Could not save bookmark.\n");
773 (void) fclose(outfp
);
777 if (fprintf(outfp
, "%s\n", line
) < 0) {
778 (void) fprintf(stderr
, "Could not save bookmark.\n");
780 (void) fclose(outfp
);
785 CloseBookmarkFile(infp
);
789 /* Add it as a new bookmark. */
790 if (WriteBmLine(bmp
, outfp
, savePassword
) < 0) {
791 (void) fprintf(stderr
, "Could not save bookmark.\n");
793 (void) fclose(outfp
);
798 if (fclose(outfp
) < 0) {
799 (void) fprintf(stderr
, "Could not save bookmark.\n");
804 if (SwapBookmarkFiles() < 0) {
805 (void) fprintf(stderr
, "Could not rename bookmark file.\n");
815 /* Tries to generate a bookmark abbreviation based off of the hostname. */
817 DefaultBookmarkName(char *dst
, size_t siz
, char *src
)
823 (void) STRNCPY(str
, src
);
825 /* Pick the first "significant" part of the hostname. Usually
826 * this is the first word in the name, but if it's something like
827 * ftp.unl.edu, we would want to choose "unl" and not "ftp."
830 if ((token
= strtok(str
, ".")) == NULL
)
832 else if ((ISTRNEQ(token
, "ftp", 3)) || (ISTRNEQ(token
, "www", 3))) {
833 if ((token
= strtok(NULL
, ".")) == NULL
)
836 for (cp
= token
; ; cp
++) {
838 /* Token was all digits, like an IP address perhaps. */
841 if (!isdigit((int) *cp
))
844 (void) Strncpy(dst
, token
, siz
);
845 } /* DefaultBookmarkName */