home *** CD-ROM | disk | FTP | other *** search
/ Geek Gadgets 1 / ADE-1.bin / ade-dist / ncftp-2.3.0-src.tgz / tar.out / contrib / ncftp / Bookmark.c < prev    next >
C/C++ Source or Header  |  1996-09-28  |  19KB  |  831 lines

  1. /* Bookmark.c */
  2.  
  3. #include "Sys.h"
  4.  
  5. #include <ctype.h>
  6.  
  7. /* TO-DO: If I decide to implement strstr matching, watch out for
  8.  * local domain hosts, with simple names.
  9.  */
  10.  
  11. #include "Util.h"
  12. #include "Bookmark.h"
  13. #include "FTP.h"
  14.  
  15. /* We keep an array of structures of all the sites we know about in
  16.  * a contigious block of memory.
  17.  */
  18. BookmarkPtr gHosts = (BookmarkPtr) 0;
  19.  
  20. /* But we also keep an array of pointers to the structures, so we can
  21.  * use that for sorting purposes.  We don't want to have to shuffle
  22.  * large structures around when sorting.  As the name hints, we have
  23.  * this array sorted by nickname, for quick searching.
  24.  */
  25. BookmarkPtrList gBookmarks = (BookmarkPtrList) 0;
  26.  
  27. /* This is the number of elements in both gHosts and gBookmarks. */
  28. int gNumBookmarks = 0;
  29.  
  30. /* We don't want the other portions of the program overwriting the
  31.  * current entry in the gHosts list, because we may want to save our
  32.  * changes under a new entry.  So if the host was in our list, we make
  33.  * a copy of the data, and let them write into that.  Then we can write
  34.  * the changed structure under a new entry, or overwrite the old one.
  35.  * This also works for entirely new entries.  We just give the caller
  36.  * this, initialized to the default values.
  37.  */
  38. Bookmark gRmtInfo = { NULL, NULL, 0, "", "" };
  39.  
  40. /* Used to tell if we got the information from the host information list,
  41.  * or we were using a new entry.
  42.  */
  43. int gRmtInfoIsNew;
  44.  
  45. /* We use this outside of this module to tell if we should actually
  46.  * save the information collected.  We don't want to save it if the
  47.  * stuff wasn't really valid, so we won't save unless you logged in
  48.  * successfully.
  49.  */
  50. int gWantRmtInfoSaved;
  51.  
  52. /* Used to tell if the host file needs to be written back out.
  53.  * If we haven't changed anything, then don't waste the time to
  54.  * write the file.
  55.  */
  56. int gModifiedBookmarks = 0;
  57.  
  58. /* These are the first and last nodes in the linked-list of remote
  59.  * site information structures.
  60.  */
  61. BookmarkPtr gFirstRsi = NULL, gLastRsi = NULL;
  62.  
  63. /* If greater than zero, we will only save the most recent sites, up
  64.  * to this number.
  65.  */
  66. int gMaxBookmarks = kNoBookmarkLimit;
  67.  
  68. extern string gEmailAddress, gAnonPassword;
  69. extern int gPreferredDataPortMode;
  70. extern string gOurDirectoryPath;
  71. extern longstring gRemoteCWD;
  72.  
  73. static
  74. int BookmarkSortProc(const BookmarkPtr *a, const BookmarkPtr *b)
  75. {
  76.     return (ISTRCMP((**a).bookmarkName, (**b).bookmarkName));    
  77. }    /* BookmarkSortProc */
  78.  
  79.  
  80.  
  81. static
  82. int BookmarkSortTimeProc(const BookmarkPtr *a, const BookmarkPtr *b)
  83. {
  84.     return ((**b).lastCall - (**a).lastCall);    
  85. }    /* BookmarkSortTimeProc */
  86.  
  87.  
  88.  
  89. static
  90. int BookmarkSearchProc(char *key, const BookmarkPtr *b)
  91. {
  92.     return (ISTRCMP(key, (**b).bookmarkName));    
  93. }    /* BookmarkSearchProc */
  94.  
  95.  
  96.  
  97.  
  98.  
  99. void SortBookmarks(void)
  100. {
  101.     int i;
  102.     BookmarkPtr p;
  103.  
  104.     if (gBookmarks != (BookmarkPtrList) 0)
  105.         free(gBookmarks);
  106.     gBookmarks = (BookmarkPtrList) malloc(
  107.         sizeof(BookmarkPtr) * (gNumBookmarks + 1)
  108.     );    
  109.     if (gBookmarks == (BookmarkPtrList) 0)
  110.         OutOfMemory();
  111.  
  112.     for (p = gFirstRsi, i=0; p != NULL; i++, p = p->next) {
  113.         gBookmarks[i] = p;
  114.     }
  115.     gBookmarks[gNumBookmarks] = NULL;
  116.  
  117.     QSORT(gBookmarks,
  118.         gNumBookmarks, sizeof(BookmarkPtr), BookmarkSortProc);
  119.     
  120.     for (i=0; i<gNumBookmarks; i++) {
  121.         p = gBookmarks[i];
  122.         p->index = i;
  123.     }
  124. }    /* SortBookmarks */
  125.  
  126.  
  127.  
  128.  
  129. void UpdateBookmarkPtr(BookmarkPtr dst, BookmarkPtr src)
  130. {
  131.     BookmarkPtr next, prev;
  132.     int idx;
  133.     
  134.     /* Need to preserve dst's links, but copy all of src's stuff. */
  135.     next = dst->next;
  136.     prev = dst->prev;
  137.     idx = dst->index;
  138.     *dst = *src;
  139.     dst->next = next;
  140.     dst->prev = prev;
  141.     dst->index = idx;
  142. }    /* UpdateBookmarkPtr */
  143.  
  144.  
  145.  
  146.  
  147.  
  148. BookmarkPtr AddBookmarkPtr(BookmarkPtr buf)
  149. {
  150.     BookmarkPtr newRsip;
  151.  
  152.     newRsip = (BookmarkPtr) malloc(sizeof(Bookmark));
  153.     if (newRsip != NULL) {
  154.         memcpy(newRsip, buf, sizeof(Bookmark));
  155.         newRsip->next = NULL;
  156.         if (gFirstRsi == NULL) {
  157.             gFirstRsi = gLastRsi = newRsip;
  158.             newRsip->prev = NULL;
  159.         } else {
  160.             newRsip->prev = gLastRsi;
  161.             gLastRsi->next = newRsip;
  162.             gLastRsi = newRsip;
  163.         }
  164.         ++gNumBookmarks;
  165.         /* Just need to know if we should write out the host file later. */
  166.         gModifiedBookmarks++;
  167.     } else {
  168.         OutOfMemory();
  169.     }
  170.     return newRsip;
  171. }    /* AddBookmarkPtr */
  172.  
  173.  
  174.  
  175.  
  176.  
  177. BookmarkPtr RemoveBookmarkPtr(BookmarkPtr killMe)
  178. {
  179.     BookmarkPtr nextRsi, prevRsi;
  180.     
  181.     nextRsi = killMe->next;    
  182.     prevRsi = killMe->prev;    
  183.     
  184.     if (gFirstRsi == killMe)
  185.         gFirstRsi = nextRsi;
  186.     if (gLastRsi == killMe)
  187.         gLastRsi = prevRsi;
  188.  
  189.     if (nextRsi != NULL)
  190.         nextRsi->prev = prevRsi;
  191.     if (prevRsi != NULL)
  192.         prevRsi->next = nextRsi;
  193.  
  194.     PTRZERO(killMe, sizeof(Bookmark));
  195.     free(killMe);
  196.     --gNumBookmarks;
  197.     ++gModifiedBookmarks;
  198.     return (nextRsi);
  199. }    /* RemoveBookmarkPtr */
  200.  
  201.  
  202.  
  203.  
  204.  
  205. void MakeBookmarkUnique(char *dst, size_t siz)
  206. {
  207.     int i;
  208.     string s2, s3;
  209.     BookmarkPtr *bmpp;
  210.     char *cp;
  211.  
  212.     /* Make sure we can concat 3 more characters if necessary. */
  213.     Strncpy(s2, dst, siz - 3);
  214.     for (cp = s2 + strlen(s2) - 1; cp > s2; ) {
  215.         if (isdigit(*cp))
  216.             *cp-- = '\0';
  217.         else
  218.             break;
  219.     }
  220.  
  221.     /* Make a copy of the original. */
  222.     STRNCPY(s3, dst);
  223.     
  224.     for (i=1; i<=999; i++) {
  225.         if (i > 1)
  226.             sprintf(dst, "%s%d", s2, i);
  227.         else
  228.             Strncpy(dst, s3, siz);
  229.     
  230.         /* See if there is already a nickname by this name. */
  231.         if (gNumBookmarks == 0)
  232.             break;
  233.         bmpp = (BookmarkPtr *) BSEARCH(dst, gBookmarks, gNumBookmarks,
  234.             sizeof(BookmarkPtr), BookmarkSearchProc);
  235.         if (bmpp == NULL)
  236.             break;
  237.     }
  238. }    /* MakeBookmarkUnique */
  239.  
  240.  
  241.  
  242.  
  243. void MakeUpABookmarkName(char *dst, size_t siz, char *src)
  244. {
  245.     string str;
  246.     char *token;
  247.     char *cp;
  248.  
  249.     STRNCPY(str, src);
  250.     
  251.     /* Pick the first "significant" part of the hostname.  Usually
  252.      * this is the first word in the name, but if it's something like
  253.      * ftp.unl.edu, we would want to choose "unl" and not "ftp."
  254.      */
  255.     token = str;
  256.     if ((token = strtok(token, ".")) == NULL)
  257.         token = "misc";
  258.     else if (ISTREQ(token, "ftp")) {
  259.         if ((token = strtok(NULL, ".")) == NULL)
  260.             token = "misc";
  261.     }
  262.     for (cp = token; ; cp++) {
  263.         if (*cp == '\0') {
  264.             /* Token was all digits, like an IP address perhaps. */
  265.             token = "misc";
  266.         }
  267.         if (!isdigit(*cp))
  268.             break;
  269.     }
  270.     Strncpy(dst, token, siz);
  271.     MakeBookmarkUnique(dst, siz);
  272. }    /* MakeUpABookmarkName */
  273.  
  274.  
  275.  
  276.  
  277. void SetBookmarkDefaults(BookmarkPtr bmp)
  278. {
  279.     PTRZERO(bmp, sizeof(Bookmark));
  280.  
  281.     bmp->xferType = 'I';
  282.     bmp->port = kPortUnset;
  283.     bmp->hasSIZE = 1;
  284.     bmp->hasMDTM = 1;
  285.     if (gPreferredDataPortMode >= kPassiveMode) {
  286.         /* Assume we have it until proven otherwise. */
  287.         bmp->hasPASV = 1;
  288.     } else {
  289.         /* If default is PORT, then make the user explicitly set this. */
  290.         bmp->hasPASV = 0;
  291.     }    
  292.     bmp->isUnix = 1;
  293.     bmp->lastCall = (time_t) 0;
  294. }    /* SetBookmarkDefaults */
  295.  
  296.  
  297.  
  298. void SetNewBookmarkDefaults(BookmarkPtr bmp)
  299. {
  300.     /* Return a pointer to a new entry, initialized to
  301.      * all the defaults, except for name and nickname.
  302.      */
  303.     SetBookmarkDefaults(bmp);
  304.     STRNCPY(bmp->name, "foobar.snafu.gov");
  305.     STRNCPY(bmp->bookmarkName, "NEW");
  306.  
  307.     /* That will make a unique "NEW" nickname. */
  308.     MakeBookmarkUnique(bmp->bookmarkName, sizeof(bmp->bookmarkName));
  309. }    /* SetNewBookmarkDefaults */
  310.  
  311.  
  312.  
  313.  
  314. int GetBookmark(char *host, size_t siz)
  315. {
  316.     BookmarkPtr *bmpp;
  317.     int i;
  318.     size_t len;
  319.     
  320.     if (gNumBookmarks == 0)
  321.         bmpp = NULL;
  322.     else {
  323.         bmpp = (BookmarkPtr *)
  324.             BSEARCH(host, gBookmarks, gNumBookmarks,
  325.                 sizeof(BookmarkPtr), BookmarkSearchProc);
  326.         if (bmpp == NULL) {
  327.             /* No exact match, but the user doesn't have to type the
  328.              * whole nickname, just the first few letters of it.
  329.              */
  330.             /* This could probably be done in a bsearch proc too... */
  331.             len = strlen(host);
  332.             for (i=0; i<gNumBookmarks; i++) {
  333.                 if (ISTRNEQ(gBookmarks[i]->bookmarkName, host, len)) {
  334.                     bmpp = &gBookmarks[i];
  335.                     break;
  336.                 }
  337.             }
  338.         }
  339.         if ((bmpp == NULL) && (strchr(host, '.') != NULL)) {
  340.             /* If thing we were given looks like a full hostname (has at
  341.              * least one period), see if we have an exact match on the
  342.              * hostname.
  343.              *
  344.              * This is actually not recommended -- you should try to use
  345.              * the nicknames only since they are unique.  We can have more
  346.              * than one entry for the same hostname!
  347.              */
  348.             for (i=0; i<gNumBookmarks; i++) {
  349.                 if (ISTREQ(gBookmarks[i]->name, host)) {
  350.                     bmpp = &gBookmarks[i];
  351.                     break;
  352.                 }
  353.             }
  354.         }
  355.     }
  356.  
  357.     gWantRmtInfoSaved = 0;
  358.     if (bmpp != NULL) {
  359.         gRmtInfo = **bmpp;
  360.         
  361.         /* So we know that this isn't in the list, but just a copy
  362.          * of someone else's data.
  363.          */
  364.         gRmtInfo.next = gRmtInfo.prev = NULL;
  365.         
  366.         gRmtInfoIsNew = 0;
  367.         /* gHost needs to be set here, since the caller wasn't using
  368.          * a real host name.
  369.          */
  370.         Strncpy(host, gRmtInfo.name, siz);
  371.         return (1);
  372.     }
  373.     
  374.     SetNewBookmarkDefaults(&gRmtInfo);    
  375.     STRNCPY(gRmtInfo.name, host);
  376.     MakeUpABookmarkName(gRmtInfo.bookmarkName, sizeof(gRmtInfo.bookmarkName), host);
  377.     
  378.     gRmtInfoIsNew = 1;
  379.     return (0);
  380. }    /* GetBookmark */
  381.  
  382.  
  383.  
  384.  
  385. int ParseHostLine(char *line, BookmarkPtr bmp)
  386. {
  387.     string token;
  388.     char *s, *d;
  389.     char *tokenend;
  390.     long L;
  391.     int i;
  392.     int result;
  393.  
  394.     SetBookmarkDefaults(bmp);
  395.     s = line;
  396.     tokenend = token + sizeof(token) - 1;
  397.     result = -1;
  398.     for (i=0; ; i++) {
  399.         if (*s == '\0')
  400.             break;
  401.         /* Some tokens may need to have a comma in them.  Since this is a
  402.          * field delimiter, these fields use \, to represent a comma, and
  403.          * \\ for a backslash.  This chunk gets the next token, paying
  404.          * attention to the escaped stuff.
  405.          */
  406.         for (d = token; *s != '\0'; ) {
  407.             if ((*s == '\\') && (s[1] != '\0')) {
  408.                 if (d < tokenend)
  409.                     *d++ = s[1];
  410.                 s += 2;
  411.             } else if (*s == ',') {
  412.                 ++s;
  413.                 break;
  414.             } else {
  415.                 if (d < tokenend)
  416.                     *d++ = *s;
  417.                 ++s;
  418.             }
  419.         }
  420.         *d = '\0';
  421.         switch(i) {
  422.             case 0: (void) STRNCPY(bmp->bookmarkName, token); break;
  423.             case 1: (void) STRNCPY(bmp->name, token); break;
  424.             case 2: (void) STRNCPY(bmp->user, token); break;
  425.             case 3: (void) STRNCPY(bmp->pass, token); break;
  426.             case 4: (void) STRNCPY(bmp->acct, token); break;
  427.             case 5: (void) STRNCPY(bmp->dir, token);
  428.                     result = 0;        /* Good enough to have these fields. */
  429.                     break;
  430.             case 6: bmp->xferType = token[0]; break;
  431.             case 7:
  432.                 /* Most of the time, we won't have a port. */
  433.                 if (token[0] == '\0')
  434.                     bmp->port = (unsigned int) kDefaultFTPPort;
  435.                 else
  436.                     bmp->port = (unsigned int) atoi(token);
  437.                 break;
  438.             case 8:
  439.                 sscanf(token, "%lx", &L);
  440.                 bmp->lastCall = (time_t) L;
  441.                 break;
  442.             case 9: bmp->hasSIZE = atoi(token); break;
  443.             case 10: bmp->hasMDTM = atoi(token); break;
  444.             case 11: bmp->hasPASV = atoi(token); break;
  445.             case 12: bmp->isUnix = atoi(token);
  446.                     result = 3;        /* Version 3 had all fields to here. */
  447.                     break;
  448.             case 13: (void) STRNCPY(bmp->lastIP, token); break;
  449.             case 14: (void) STRNCPY(bmp->comment, token); break;
  450.             case 15: sscanf(token, "%ld", &bmp->xferKbytes); break;
  451.             case 16: sscanf(token, "%ld", &bmp->xferHSeconds);
  452.                     result = 4;        /* Version 4 had all fields up to here. */
  453.                     break;
  454.             case 17: bmp->nCalls = atoi(token);
  455.                     result = 5;        /* Version 5 has all fields to here. */
  456.                     break;
  457.             case 18: bmp->noSaveDir = atoi(token);
  458.                     result = 6;        /* Version 5 has all fields to here. */
  459.                     break;
  460.             default:
  461.                     result = 99;    /* Version >4 ? */
  462.                     goto done;
  463.         }
  464.     }
  465. done:
  466.     return (result);
  467. }    /* ParseHostLine */
  468.  
  469.  
  470.  
  471.  
  472. void ReadBookmarkFile(void)
  473. {
  474.     string pathName;
  475.     string path2;
  476.     FILE *fp;
  477.     longstring line;
  478.     int version;
  479.     Bookmark newRsi;
  480.  
  481.     if (gOurDirectoryPath[0] == '\0')
  482.         return;        /* Don't create in root directory. */
  483.     OurDirectoryPath(pathName, sizeof(pathName), kBookmarkFileName);
  484.     fp = fopen(pathName, "r");
  485.     if (fp == NULL) {
  486.         OurDirectoryPath(path2, sizeof(path2), kOldBookmarkFileName);
  487.         if (rename(path2, pathName) == 0) {
  488.             /* Rename succeeded, now open it. */
  489.             fp = fopen(pathName, "r");
  490.             if (fp == NULL)
  491.                 return;
  492.         }
  493.         return;        /* Okay to not have one yet. */
  494.     }
  495.  
  496.     if (FGets(line, sizeof(line), fp) == NULL)
  497.         goto badFmt;
  498.     
  499.     /* Sample line we're looking for:
  500.      * "NcFTP bookmark-file version: 2"
  501.      */
  502.     version = -1;
  503.     (void) sscanf(line, "%*s %*s %*s %d", &version);
  504.     if (version < kBookmarkMinVersion) {
  505.         if (version < 0)
  506.             goto badFmt;
  507.         STRNCPY(path2, pathName);
  508.         sprintf(line, ".v%d", version);
  509.         STRNCAT(path2, line);
  510.         (void) rename(pathName, path2);
  511.         Error(kDontPerror, "%s: old version.\n", pathName);
  512.         fclose(fp);
  513.         return;
  514.     }
  515.         
  516.     if (FGets(line, sizeof(line), fp) == NULL)
  517.         goto badFmt;
  518.     
  519.     /* Sample line we're looking for:
  520.      * "Number of entries: 28"
  521.      */
  522.     gNumBookmarks = -1;
  523.     
  524.     /* At the moment, we don't really care about the number stored in the
  525.      * file.  It's there for future use.
  526.      */
  527.     (void) sscanf(line, "%*s %*s %*s %d", &gNumBookmarks);
  528.     if (gNumBookmarks < 0)
  529.         goto badFmt;
  530.     
  531.     gHosts = (BookmarkPtr) 0;
  532.     gBookmarks = (BookmarkPtrList) 0;
  533.     gNumBookmarks = 0;
  534.     
  535.     while (FGets(line, sizeof(line), fp) != NULL) {
  536.         if (ParseHostLine(line, &newRsi) >= 0) {
  537.             AddBookmarkPtr(&newRsi);
  538.         }
  539.     }
  540.     fclose(fp);
  541.  
  542.     SortBookmarks();
  543.     DebugMsg("Read %d entries from %s.\n", gNumBookmarks, pathName);
  544.     return;
  545.     
  546. badFmt:
  547.     Error(kDontPerror, "%s: invalid format.\n", pathName);
  548.     fclose(fp);
  549. }    /* ReadBookmarkFile */
  550.  
  551.  
  552.  
  553.  
  554. BookmarkPtr DuplicateBookmark(BookmarkPtr origbmp)
  555. {
  556.     Bookmark newRsi;
  557.     BookmarkPtr newRsip;
  558.     string str;
  559.  
  560.     STRNCPY(str, origbmp->bookmarkName);
  561.     MakeBookmarkUnique(str, sizeof(origbmp->bookmarkName));
  562.  
  563.     newRsi = *origbmp;
  564.     STRNCPY(newRsi.bookmarkName, str);
  565.     newRsip = AddBookmarkPtr(&newRsi);
  566.  
  567.     /* Have to re-sort now so our bsearches will work. */
  568.     SortBookmarks();
  569.     return (newRsip);
  570. }    /* DuplicateBookmark */
  571.  
  572.  
  573.  
  574.  
  575. void DeleteBookmark(BookmarkPtr bmp)
  576. {
  577.     if (gNumBookmarks < 1)
  578.         return;
  579.     
  580.     RemoveBookmarkPtr(bmp);
  581.     SortBookmarks();
  582. }    /* DuplicateBookmark */
  583.  
  584.  
  585.  
  586.  
  587. void SaveBookmark(char *asNick)
  588. {
  589.     BookmarkPtr *bmpp;
  590.     Bookmark rm;
  591.  
  592.     memcpy(&rm, &gRmtInfo, sizeof(rm));
  593.     STRNCPY(rm.bookmarkName, asNick);
  594.     STRNCPY(rm.dir, gRemoteCWD);
  595.     rm.xferKbytes = 0L;
  596.     rm.xferHSeconds = 0L;
  597.     rm.nCalls = 0;
  598.  
  599.     /* Don't update dir if you move around the next time you use it. */
  600.     rm.noSaveDir = 1;
  601.  
  602.     bmpp = (BookmarkPtr *) BSEARCH(asNick,
  603.         gBookmarks, gNumBookmarks,
  604.         sizeof(BookmarkPtr), BookmarkSearchProc);
  605.     if (bmpp == NULL) {
  606.         /* Add a new entry. */
  607.         rm.comment[0] = '\0';
  608.         (void) AddBookmarkPtr(&rm);
  609.  
  610.         /* Have to re-sort now so our bsearches will work. */
  611.         SortBookmarks();
  612.         PrintF("Saving new bookmark named \"%s\" in your host file, pointing\nto <URL:ftp://%s/%s/>.\n",
  613.             rm.bookmarkName,
  614.             rm.name,
  615.             rm.dir
  616.         );
  617.     } else {
  618.         /* Copy over an existing one. */
  619.         UpdateBookmarkPtr(*bmpp, &rm);
  620.         PrintF("Updated bookmark named \"%s\" in your host file, so it now points\nto <URL:ftp://%s/%s/>.\n",
  621.             rm.bookmarkName,
  622.             rm.name,
  623.             rm.dir
  624.         );
  625.     }
  626.  
  627.     /* Just need to know if we should write out the host file later. */
  628.     gModifiedBookmarks++;
  629. }    /* SaveBookmark */
  630.  
  631.  
  632.  
  633.  
  634. void SaveCurHostBookmark(char *asNick)
  635. {
  636.     BookmarkPtr *bmpp;
  637.  
  638.     if (gRmtInfoIsNew) {
  639.         (void) AddBookmarkPtr(&gRmtInfo);
  640.         PrintF("Saving new bookmark named \"%s\" in your host file, pointing\nto <URL:ftp://%s/%s/>.\n",
  641.             gRmtInfo.bookmarkName,
  642.             gRmtInfo.name,
  643.             gRmtInfo.dir
  644.         );
  645.  
  646.         /* Have to re-sort now so our bsearches will work. */
  647.         SortBookmarks();
  648.     } else {
  649.         /* We were working with an existing entry.
  650.          * If the nickname given to us as the parameter is different
  651.          * from the existing bookmarkName, then we're supposed to save
  652.          * this as a new entry.
  653.          */
  654.         if ((asNick == NULL) || ISTREQ(asNick, gRmtInfo.bookmarkName)) {
  655.             /* Save over old entry. */
  656.             bmpp = (BookmarkPtr *) BSEARCH(gRmtInfo.bookmarkName,
  657.                 gBookmarks, gNumBookmarks,
  658.                 sizeof(BookmarkPtr), BookmarkSearchProc);
  659.             /* This had better be in there, since we did this before
  660.              * and it was in there.
  661.              */
  662.             if (bmpp == NULL) {
  663.                 Error(kDontPerror,
  664.                 "Programmer's error: couldn't re-find host info entry.\n");
  665.                 return;
  666.             }
  667.             /* Copy over the old stuff. */
  668.             UpdateBookmarkPtr(*bmpp, &gRmtInfo);
  669.  
  670.             /* Just need to know if we should write out the host file later. */
  671.             gModifiedBookmarks++;
  672.         } else {
  673.             /* Add a new entry. */
  674.             STRNCPY(gRmtInfo.bookmarkName, asNick);
  675.             MakeBookmarkUnique(gRmtInfo.bookmarkName, sizeof(gRmtInfo.bookmarkName));
  676.             (void) AddBookmarkPtr(&gRmtInfo);
  677.  
  678.             /* Have to re-sort now so our bsearches will work. */
  679.             SortBookmarks();
  680.         }
  681.     }
  682.  
  683.     gRmtInfoIsNew = 0;
  684. }    /* SaveCurHostBookmark */
  685.  
  686.  
  687.  
  688. static
  689. void EscapeString(char *d, char *s)
  690. {
  691.     if (s != NULL) {
  692.         while (*s != '\0') {
  693.             if (*s == ',' || *s == '\\')
  694.                 *d++ = '\\';
  695.             *d++ = *s++;
  696.         }
  697.     }
  698.     *d = '\0';
  699. }    /* EscapeString */
  700.  
  701.  
  702.  
  703.  
  704. void WriteBookmarkFile(void)
  705. {
  706.     string pathName;
  707.     string bupPathName;
  708.     longstring escapedStr;
  709.     FILE *fp;
  710.     char portStr[16];
  711.     int i;
  712.     int nPasswds;
  713.     BookmarkPtr bmp;
  714.  
  715.     if (!gModifiedBookmarks)
  716.         return;
  717.  
  718.     OurDirectoryPath(pathName, sizeof(pathName), kBookmarkFileName);
  719.  
  720.     if ((gMaxBookmarks != kNoBookmarkLimit) && (gNumBookmarks > gMaxBookmarks)) {
  721.         DebugMsg("Purging %d old remote sites from %s.\n",
  722.             gNumBookmarks - gMaxBookmarks,
  723.             pathName
  724.         );
  725.  
  726.         /* Sort sites by last time we called.  We want the older sites to
  727.          * float to the bottom.
  728.          */
  729.         QSORT(gBookmarks,
  730.             gNumBookmarks, sizeof(BookmarkPtr), BookmarkSortTimeProc);
  731.  
  732.         gNumBookmarks = gMaxBookmarks;
  733.     }
  734.     
  735.     /* See if we can move the existing file to a new name, in case
  736.      * something happens while we write this out.  Host files are
  737.      * valuable enough that people would be pissed off if their
  738.      * host file got nuked.
  739.      */
  740.     OurDirectoryPath(bupPathName, sizeof(bupPathName), kBookmarkBupFileName);
  741.     (void) UNLINK(bupPathName);
  742.     (void) rename(pathName, bupPathName);
  743.     
  744.     fp = fopen(pathName, "w");
  745.     if (fp == NULL)
  746.         goto err;
  747.     
  748.     if (fprintf(fp, "NcFTP bookmark-file version: %d\nNumber of entries: %d\n",
  749.         kBookmarkVersion,
  750.         gNumBookmarks
  751.     ) < 0)
  752.         goto err;
  753.     if (fflush(fp) < 0)
  754.         goto err;
  755.  
  756.     for (i=0, nPasswds=0; i<gNumBookmarks; i++) {
  757.         *portStr = '\0';
  758.         bmp = gBookmarks[i];
  759.         if (bmp->port != kDefaultFTPPort)
  760.             sprintf(portStr, "%u", bmp->port);
  761.         if ((bmp->pass[0] != '\0')
  762.             && (!STREQ(bmp->pass, gEmailAddress))
  763.             && (!STREQ(bmp->pass, gAnonPassword)))
  764.             nPasswds++;
  765.         if (bmp->acct[0] != '\0')
  766.             nPasswds++;        /* Don't publicize accounts, either. */
  767.  
  768.         /* Insert the quote character '\' for strings that can have
  769.          * commas or backslashes in them.
  770.          */
  771.         EscapeString(escapedStr, bmp->pass);
  772.         if (fprintf(fp, "%s,%s,%s,%s,%s,",
  773.             bmp->bookmarkName,
  774.             bmp->name,
  775.             bmp->user,
  776.             escapedStr,
  777.             bmp->acct
  778.         ) < 0)
  779.             goto err;
  780.  
  781.         EscapeString(escapedStr, bmp->dir);
  782.         if (fprintf(fp, "%s,%c,%s,%lx,%d,%d,%d,%d,",
  783.             escapedStr,
  784.             bmp->xferType,
  785.             portStr,
  786.             (unsigned long) bmp->lastCall,
  787.             bmp->hasSIZE,
  788.             bmp->hasMDTM,
  789.             bmp->hasPASV,
  790.             bmp->isUnix
  791.         ) < 0)
  792.             goto err;
  793.  
  794.         EscapeString(escapedStr, bmp->comment);
  795.         if (fprintf(fp, "%s,%s,%ld,%ld,%d,%d\n",
  796.             bmp->lastIP,
  797.             escapedStr,
  798.             bmp->xferKbytes,
  799.             bmp->xferHSeconds,
  800.             bmp->nCalls,
  801.             bmp->noSaveDir
  802.         ) < 0)
  803.             goto err;
  804.     }
  805.     if (fclose(fp) < 0) {
  806.         fp = NULL;
  807.         goto err;
  808.     }
  809.     (void) UNLINK(bupPathName);
  810.     if (nPasswds > 0) {
  811.         /* Set permissions so other users can't see the passwords.
  812.          * Of course this isn't really secure, which is why the program
  813.          * won't save passwords entered at the password prompt.  You must
  814.          * explicitly set them from the host editor.
  815.          */
  816.         (void) chmod(pathName, 0600);    /* Set it to -rw------- */
  817.     }
  818.     return;
  819.  
  820. err:
  821.     if (access(bupPathName, F_OK) < 0) {
  822.         Error(kDoPerror, "Could not write to %s.\n", pathName);
  823.     } else {
  824.         /* Move backup file back to the original. */
  825.         rename(bupPathName, pathName);
  826.         Error(kDoPerror, "Could not update %s.\n", pathName);
  827.     }
  828.     if (fp != NULL)
  829.         fclose(fp);
  830. }    /* WriteBookmarkFile */
  831.