0fadf3ad5e3574f293f057023fd8a2659077255c
[openafs.git] / src / WINNT / client_config / drivemap.cpp
1 /*
2  * Copyright 2000, International Business Machines Corporation and others.
3  * All Rights Reserved.
4  * 
5  * This software has been released under the terms of the IBM Public
6  * License.  For details, see the LICENSE file in the top-level source
7  * directory or online at http://www.openafs.org/dl/license10.html
8  */
9
10 extern "C" {
11 #include <afs/param.h>
12 #include <afs/stds.h>
13 }
14
15 #include <windows.h>
16 #include <stdlib.h>
17 #include <stdio.h>
18 #include <WINNT/TaLocale.h>
19 #include "drivemap.h"
20
21
22 /*
23  * REGISTRY ___________________________________________________________________
24  *
25  */
26
27 static const TCHAR AFSConfigKeyName[] = TEXT("SYSTEM\\CurrentControlSet\\Services\\TransarcAFSDaemon\\Parameters");
28
29
30 /*
31  * PROFILE SECTIONS ___________________________________________________________
32  *
33  */
34
35 #define cREALLOC_SUBMOUNTS   4
36
37 static TCHAR cszINIFILE[] = TEXT("afsdsbmt.ini");
38 static TCHAR cszSECTION_SUBMOUNTS[] = TEXT("AFS Submounts");
39 static TCHAR cszSECTION_MAPPINGS[] = TEXT("AFS Mappings");
40
41 static TCHAR cszAUTOSUBMOUNT[] = TEXT("Auto");
42 static TCHAR cszLANMANDEVICE[] = TEXT("\\Device\\LanmanRedirector\\");
43
44
45 /*
46  * STRINGS ____________________________________________________________________
47  *
48  */
49
50 static LPTSTR AllocateStringMemory (size_t cch)
51 {
52    LPTSTR psz = (LPTSTR)Allocate (sizeof(TCHAR) * (cch+1));
53    memset (psz, 0x00, sizeof(TCHAR) * (cch+1));
54    return psz;
55 }
56
57 static void FreeStringMemory (LPTSTR pszString)
58 {
59    Free (pszString);
60 }
61
62 static int lstrncmpi (LPCTSTR pszA, LPCTSTR pszB, size_t cch)
63 {
64    if (!pszA || !pszB)
65       {
66       return (!pszB) - (!pszA);   // A,!B:1, !A,B:-1, !A,!B:0
67       }
68
69    for ( ; cch > 0; cch--, pszA = CharNext(pszA), pszB = CharNext(pszB))
70       {
71       TCHAR chA = toupper( *pszA );
72       TCHAR chB = toupper( *pszB );
73
74       if (!chA || !chB)
75          return (!chB) - (!chA);    // A,!B:1, !A,B:-1, !A,!B:0
76
77       if (chA != chB)
78          return (int)(chA) - (int)(chB);   // -1:A<B, 0:A==B, 1:A>B
79       }
80
81    return 0;  // no differences before told to stop comparing, so A==B
82 }
83
84
85 /*
86  * REALLOC ____________________________________________________________________
87  *
88  */
89
90 #ifndef REALLOC
91 #define REALLOC(_a,_c,_r,_i) DriveMapReallocFunction ((LPVOID*)&_a,sizeof(*_a),&_c,_r,_i)
92 BOOL DriveMapReallocFunction (LPVOID *ppTarget, size_t cbElement, size_t *pcTarget, size_t cReq, size_t cInc)
93 {
94    LPVOID pNew;
95    size_t cNew;
96
97    if (cReq <= *pcTarget)
98       return TRUE;
99
100    if ((cNew = cInc * ((cReq + cInc-1) / cInc)) <= 0)
101       return FALSE;
102
103    if ((pNew = Allocate (cbElement * cNew)) == NULL)
104       return FALSE;
105    memset (pNew, 0x00, cbElement * cNew);
106
107    if (*pcTarget != 0)
108    {
109       memcpy (pNew, *ppTarget, cbElement * (*pcTarget));
110       Free (*ppTarget);
111    }
112
113    *ppTarget = pNew;
114    *pcTarget = cNew;
115    return TRUE;
116 }
117 #endif
118
119
120 /*
121  * WINDOWS NT STUFF ___________________________________________________________
122  *
123  */
124
125 static BOOL IsWindowsNT (void)
126 {
127    static BOOL fChecked = FALSE;
128    static BOOL fIsWinNT = FALSE;
129
130    if (!fChecked)
131       {
132       fChecked = TRUE;
133
134       OSVERSIONINFO Version;
135       memset (&Version, 0x00, sizeof(Version));
136       Version.dwOSVersionInfoSize = sizeof(Version);
137
138       if (GetVersionEx (&Version))
139          {
140          if (Version.dwPlatformId == VER_PLATFORM_WIN32_NT)
141             fIsWinNT = TRUE;
142          }
143       }
144
145    return fIsWinNT;
146 }
147
148
149 BOOL IsWindows2000 (void)
150 {
151    static BOOL fChecked = FALSE;
152    static BOOL fIsWin2K = FALSE;
153
154    if (!fChecked)
155       {
156       fChecked = TRUE;
157
158       OSVERSIONINFO Version;
159       memset (&Version, 0x00, sizeof(Version));
160       Version.dwOSVersionInfoSize = sizeof(Version);
161
162       if (GetVersionEx (&Version))
163          {
164          if (Version.dwPlatformId == VER_PLATFORM_WIN32_NT &&
165              Version.dwMajorVersion >= 5)
166              fIsWin2K = TRUE;
167          }
168       }
169
170    return fIsWin2K;
171 }
172
173 /*
174  * GENERAL ____________________________________________________________________
175  *
176  */
177
178 void GetClientNetbiosName (LPTSTR pszName)
179 {
180    *pszName = TEXT('\0');
181
182    if (IsWindowsNT())
183       {
184       HKEY hk;
185       if (RegOpenKey (HKEY_LOCAL_MACHINE, TEXT("System\\CurrentControlSet\\Control\\ComputerName\\ComputerName"), &hk) == 0)
186          {
187          DWORD dwSize = MAX_PATH;
188          DWORD dwType = REG_SZ;
189          RegQueryValueEx (hk, TEXT("ComputerName"), NULL, &dwType, (PBYTE)pszName, &dwSize);
190          }
191       }
192    else // (!IsWindowsNT())
193       {
194       HKEY hk;
195       if (RegOpenKey (HKEY_LOCAL_MACHINE, TEXT("System\\CurrentControlSet\\Services\\TransarcAFSDaemon\\Parameters"), &hk) == 0)
196          {
197          DWORD dwSize = MAX_PATH;
198          DWORD dwType = REG_SZ;
199          RegQueryValueEx (hk, TEXT("Gateway"), NULL, &dwType, (PBYTE)pszName, &dwSize);
200          }
201       }
202
203    // Shorten the server name from its FQDN
204    //
205    for (LPTSTR pch = pszName; *pch; ++pch)
206       {
207       if (*pch == TEXT('.'))
208          {
209          *(LPTSTR)pch = TEXT('\0');
210          break;
211          }
212       }
213
214    // Form NetBIOS name from client's (possibly truncated) simple host name.
215    if (*pszName != TEXT('\0')) {
216        pszName[11] = TEXT('\0');
217        lstrcat(pszName, TEXT("-AFS"));
218    }
219 }
220
221
222 BOOL SubmountToPath (PDRIVEMAPLIST pList, LPTSTR pszPath, LPTSTR pszSubmount, BOOL fMarkInUse)
223 {
224    // We can't do this translation unless we're under Windows NT.
225    //
226    if (!IsWindowsNT())
227       return FALSE;
228
229    // \\computer-afs\all always maps to "/afs"
230    //
231    if (!lstrcmpi (pszSubmount, TEXT("all")))
232       {
233       lstrcpy (pszPath, TEXT("/afs"));
234       return TRUE;
235       }
236
237    // Otherwise, look up our list of submounts.
238    //
239    for (size_t ii = 0; ii < pList->cSubmounts; ++ii)
240       {
241       if (!lstrcmpi (pList->aSubmounts[ii].szSubmount, pszSubmount))
242          {
243          if (fMarkInUse)
244             pList->aSubmounts[ii].fInUse = TRUE;
245          AdjustAfsPath (pszPath, pList->aSubmounts[ii].szMapping, TRUE, TRUE);
246          return TRUE;
247          }
248       }
249
250    return FALSE;
251 }
252
253
254 BOOL IsValidSubmountName (LPTSTR pszSubmount)
255 {
256    if (!*pszSubmount)
257       return FALSE;
258    if (lstrlen (pszSubmount) > 12)
259       return FALSE;
260
261    for ( ; *pszSubmount; ++pszSubmount)
262       {
263       if (!isprint(*pszSubmount))
264          return FALSE;
265       if (*pszSubmount == TEXT(' '))
266          return FALSE;
267       if (*pszSubmount == TEXT('\t'))
268          return FALSE;
269       }
270
271    return TRUE;
272 }
273
274
275 /*
276  * PIOCTL SUPPORT _____________________________________________________________
277  *
278  */
279
280 extern "C" {
281
282 #include "../afsd/fs_utils.h"
283
284 #define __CM_CONFIG_INTERFACES_ONLY__
285 #include "../afsd/cm_config.h"
286
287 #define __CM_IOCTL_INTERFACES_ONLY__
288 #include "../afsd/cm_ioctl.h"
289
290 } // extern "C"
291
292 #define PIOCTL_MAXSIZE     2048
293
294
295 BOOL fCanIssuePIOCTL (void)
296 {
297    if (!IsWindowsNT())
298       {
299       TCHAR szGateway[ 256 ] = TEXT("");
300       GetClientNetbiosName (szGateway);
301       return (szGateway[0]) ? TRUE : FALSE;
302       }
303
304    SERVICE_STATUS Status;
305    memset (&Status, 0x00, sizeof(Status));
306    Status.dwCurrentState = SERVICE_STOPPED;
307
308    SC_HANDLE hManager;
309    if ((hManager = OpenSCManager (NULL, NULL, GENERIC_READ)) != NULL)
310       {
311       SC_HANDLE hService;
312       if ((hService = OpenService (hManager, TEXT("TransarcAFSDaemon"), GENERIC_READ)) != NULL)
313          {
314          QueryServiceStatus (hService, &Status);
315          CloseServiceHandle (hService);
316          }
317
318       CloseServiceHandle (hManager);
319       }
320
321    return (Status.dwCurrentState == SERVICE_RUNNING) ? TRUE : FALSE;
322 }
323
324
325 /*
326  * QUERYDRIVEMAPLIST __________________________________________________________
327  *
328  */
329
330 void QueryDriveMapList_ReadSubmounts (PDRIVEMAPLIST pList)
331 {
332    if (IsWindowsNT())
333       {
334       size_t cchLHS = 1024;
335       LPTSTR mszLHS = AllocateStringMemory (cchLHS);
336
337       for (int iRetry = 0; iRetry < 5; ++iRetry)
338          {
339          DWORD rc = GetPrivateProfileString (cszSECTION_SUBMOUNTS, NULL, TEXT(""), mszLHS, cchLHS, cszINIFILE);
340          if ((rc != cchLHS-1) && (rc != cchLHS-2))
341             break;
342
343          FreeStringMemory (mszLHS);
344          cchLHS *= 2;
345          mszLHS = AllocateStringMemory (cchLHS);
346          }
347
348       for (LPTSTR psz = mszLHS; psz && *psz; psz += 1+lstrlen(psz))
349          {
350          SUBMOUNT Submount;
351          memset (&Submount, 0x00, sizeof(SUBMOUNT));
352          lstrcpy (Submount.szSubmount, psz);
353
354          TCHAR szMapping[ MAX_PATH ] = TEXT("");
355          GetPrivateProfileString (cszSECTION_SUBMOUNTS, Submount.szSubmount, TEXT(""), szMapping, MAX_PATH, cszINIFILE);
356          if (szMapping[0] != TEXT('\0'))
357             {
358             AdjustAfsPath (Submount.szMapping, szMapping, FALSE, TRUE);
359
360             for (size_t ii = 0; ii < pList->cSubmounts; ++ii)
361                {
362                if (!pList->aSubmounts[ii].szSubmount[0])
363                   break;
364                }
365             if (REALLOC (pList->aSubmounts, pList->cSubmounts, 1+ii, cREALLOC_SUBMOUNTS))
366                {
367                memcpy (&pList->aSubmounts[ii], &Submount, sizeof(SUBMOUNT));
368                }
369             }
370          }
371
372       FreeStringMemory (mszLHS);
373       }
374 }
375
376
377 void QueryDriveMapList_ReadMappings (PDRIVEMAPLIST pList)
378 {
379    size_t cchLHS = 1024;
380    LPTSTR mszLHS = AllocateStringMemory (cchLHS);
381
382    for (int iRetry = 0; iRetry < 5; ++iRetry)
383       {
384       DWORD rc = GetPrivateProfileString (cszSECTION_MAPPINGS, NULL, TEXT(""), mszLHS, cchLHS, cszINIFILE);
385       if ((rc != cchLHS-1) && (rc != cchLHS-2))
386          break;
387
388       FreeStringMemory (mszLHS);
389       cchLHS *= 2;
390       mszLHS = AllocateStringMemory (cchLHS);
391       }
392
393    for (LPTSTR psz = mszLHS; psz && *psz; psz += 1+lstrlen(psz))
394       {
395       DRIVEMAP DriveMap;
396       memset (&DriveMap, 0x00, sizeof(DRIVEMAP));
397       DriveMap.chDrive = toupper(*psz);
398       DriveMap.fPersistent = TRUE;
399       if ((DriveMap.chDrive < chDRIVE_A) || (DriveMap.chDrive > chDRIVE_Z))
400          continue;
401
402       TCHAR szMapping[ MAX_PATH ] = TEXT("");
403       GetPrivateProfileString (cszSECTION_MAPPINGS, psz, TEXT(""), szMapping, MAX_PATH, cszINIFILE);
404       if (szMapping[0] != TEXT('\0'))
405          {
406          AdjustAfsPath (DriveMap.szMapping, szMapping, TRUE, TRUE);
407          if (DriveMap.szMapping[ lstrlen(DriveMap.szMapping)-1 ] == TEXT('*'))
408             {
409             DriveMap.fPersistent = FALSE;
410             DriveMap.szMapping[ lstrlen(DriveMap.szMapping)-1 ] = TEXT('\0');
411             }
412          size_t iDrive = DriveMap.chDrive - chDRIVE_A;
413          memcpy (&pList->aDriveMap[ iDrive ], &DriveMap, sizeof(DRIVEMAP));
414          }
415       }
416
417    FreeStringMemory (mszLHS);
418 }
419
420
421 void QueryDriveMapList_WriteMappings (PDRIVEMAPLIST pList)
422 {
423    WriteDriveMappings (pList);
424 }
425
426
427 void WriteDriveMappings (PDRIVEMAPLIST pList)
428 {
429    WritePrivateProfileString (cszSECTION_MAPPINGS, NULL, NULL, cszINIFILE);
430
431    for (size_t iDrive = 0; iDrive < 26; ++iDrive)
432       {
433       if (pList->aDriveMap[iDrive].szMapping[0] != TEXT('\0'))
434          {
435          TCHAR szLHS[] = TEXT("*");
436          szLHS[0] = pList->aDriveMap[iDrive].chDrive;
437
438          TCHAR szRHS[MAX_PATH];
439          AdjustAfsPath (szRHS, pList->aDriveMap[iDrive].szMapping, TRUE, TRUE);
440          if (!pList->aDriveMap[iDrive].fPersistent)
441             lstrcat (szRHS, TEXT("*"));
442
443          WritePrivateProfileString (cszSECTION_MAPPINGS, szLHS, szRHS, cszINIFILE);
444          }
445       }
446 }
447
448 BOOL DriveIsGlobalAfsDrive(TCHAR chDrive)
449 {
450    TCHAR szKeyName[128];
451    TCHAR szValueName[128];
452    TCHAR szValue[128];
453    HKEY hKey;
454
455    _stprintf(szKeyName, TEXT("%s\\GlobalAutoMapper"), AFSConfigKeyName);
456
457    if (RegOpenKeyEx(HKEY_LOCAL_MACHINE, szKeyName, 0, KEY_QUERY_VALUE, &hKey) != ERROR_SUCCESS)
458       return FALSE;
459
460    _stprintf(szValueName, TEXT("%c:"), chDrive);
461
462    DWORD dwSize = sizeof(szValue);
463    BOOL bIsGlobal = (RegQueryValueEx (hKey, szValueName, NULL, NULL, (PBYTE)szValue, &dwSize) == ERROR_SUCCESS);
464
465    RegCloseKey (hKey);
466    
467    return bIsGlobal;
468 }
469
470
471 void QueryDriveMapList_FindNetworkDrives (PDRIVEMAPLIST pList, BOOL *pfFoundNew)
472 {
473    for (TCHAR chDrive = chDRIVE_A; chDrive <= chDRIVE_Z; ++chDrive)
474       {
475       TCHAR szSubmount[ MAX_PATH ];
476       if (!GetDriveSubmount (chDrive, szSubmount))
477          continue;
478
479       // We've got a mapping!  Drive {chDrive} is mapped to submount
480       // {szSubmount}. See if that submount makes sense.
481       //
482       if (!IsWindowsNT())
483          {
484          size_t iDrive = chDrive - chDRIVE_A;
485          if (pList->aDriveMap[ iDrive ].szMapping[0] != TEXT('\0'))
486             {
487             pList->aDriveMap[ iDrive ].fActive = TRUE;
488             lstrcpy (pList->aDriveMap[ iDrive ].szSubmount, szSubmount);
489             }
490          continue;
491          }
492       else // (IsWindowsNT())
493          {
494          TCHAR szAfsPath[ MAX_PATH ];
495          if (!SubmountToPath (pList, szAfsPath, szSubmount, TRUE))
496             continue;
497
498          // Okay, we know that drive {chDrive} is mapped to afs path {szAfsPath}.
499          // If this drive is a global afs drive, then reject it.  Otherwise, look 
500          // at pList->aDriveMap, to see if this drive mapping is already in our 
501          // list. If not, add it and set pfFoundNew.
502          //
503          if (DriveIsGlobalAfsDrive(chDrive))
504             continue;
505          
506          size_t iDrive = chDrive - chDRIVE_A;
507          if (lstrcmpi (pList->aDriveMap[ iDrive ].szMapping, szAfsPath))
508             {
509             *pfFoundNew = TRUE;
510             pList->aDriveMap[ iDrive ].fPersistent = TRUE;
511             }
512          pList->aDriveMap[ iDrive ].fActive = TRUE;
513          pList->aDriveMap[ iDrive ].chDrive = chDrive;
514          lstrcpy (pList->aDriveMap[ iDrive ].szSubmount, szSubmount);
515          AdjustAfsPath (pList->aDriveMap[ iDrive ].szMapping, szAfsPath, TRUE, TRUE);
516          }
517       }
518 }
519
520
521 void QueryDriveMapList (PDRIVEMAPLIST pList)
522 {
523    // Initialize the data structure
524    //
525    memset (pList, 0x00, sizeof(DRIVEMAPLIST));
526    for (size_t ii = 0; ii < 26; ++ii)
527       pList->aDriveMap[ii].chDrive = chDRIVE_A + ii;
528
529    // Read the current lists of submounts and drive letter mappings
530    //
531    QueryDriveMapList_ReadSubmounts (pList);
532    QueryDriveMapList_ReadMappings (pList);
533
534    // Look through the current list of network drives, and see if
535    // any are currently mapped to AFS. If we find any which are mapped
536    // into AFS unexpectedly, we'll have to rewrite the mappings list.
537    //
538    BOOL fFoundNew = FALSE;
539    QueryDriveMapList_FindNetworkDrives (pList, &fFoundNew);
540
541    if (fFoundNew)
542       {
543       QueryDriveMapList_WriteMappings (pList);
544       }
545 }
546
547
548 void FreeDriveMapList (PDRIVEMAPLIST pList)
549 {
550    if (pList->aSubmounts)
551       Free (pList->aSubmounts);
552    memset (pList, 0x00, sizeof(DRIVEMAPLIST));
553 }
554
555
556 BOOL PathToSubmount (LPTSTR pszSubmount, LPTSTR pszMapping, LPTSTR pszSubmountReq, ULONG *pStatus)
557 {
558    if (pszSubmountReq && !IsValidSubmountName (pszSubmountReq))
559       pszSubmountReq = NULL;
560
561    TCHAR szAfsPath[ MAX_PATH ];
562    AdjustAfsPath (szAfsPath, pszMapping, TRUE, TRUE);
563
564    // Try to ask AFSD for a new submount name.
565    //
566    if (!fCanIssuePIOCTL())
567       return FALSE;
568
569    BYTE InData[ PIOCTL_MAXSIZE ];
570    memset (InData, 0x00, sizeof(InData));
571
572    LPTSTR pszInData = (LPTSTR)InData;
573    lstrcpy (pszInData, pszMapping);
574    pszInData += 1+lstrlen(pszInData);
575    if (pszSubmountReq)
576       lstrcpy (pszInData, pszSubmountReq);
577
578    BYTE OutData[ PIOCTL_MAXSIZE ];
579    memset (OutData, 0x00, sizeof(OutData));
580
581    struct ViceIoctl IOInfo;
582    IOInfo.in = (char *)InData;
583    IOInfo.in_size = PIOCTL_MAXSIZE;
584    IOInfo.out = (char *)OutData;
585    IOInfo.out_size = PIOCTL_MAXSIZE;
586
587    ULONG status;
588    if ((status = pioctl (0, VIOC_MAKESUBMOUNT, &IOInfo, 1)) != 0)
589       return FALSE;
590
591    lstrcpy (pszSubmount, (LPCTSTR)OutData);
592    return (pszSubmount[0] != TEXT('\0')) ? TRUE : FALSE;
593 }
594
595
596 BOOL ActivateDriveMap (TCHAR chDrive, LPTSTR pszMapping, LPTSTR pszSubmountReq, BOOL fPersistent, DWORD *pdwStatus)
597 {
598    // We can only map drives to places in AFS using this function.
599    //
600    if ( (lstrncmpi (pszMapping, TEXT("/afs"), lstrlen(TEXT("/afs")))) &&
601         (lstrncmpi (pszMapping, TEXT("\\afs"), lstrlen(TEXT("\\afs")))) )
602       {
603       if (pdwStatus)
604          *pdwStatus = ERROR_BAD_NETPATH;
605       return FALSE;
606       }
607
608    // First we have to translate {pszMapping} into a submount, and if there is
609    // no current submount associated with this path, we'll have to make one.
610    //
611    ULONG status;
612    TCHAR szSubmount[ MAX_PATH ];
613    if (!PathToSubmount (szSubmount, pszMapping, pszSubmountReq, &status))
614       {
615       if (pdwStatus)
616          *pdwStatus = status;
617       return FALSE;
618       }
619
620    // We now have a submount name and drive letter--map the network drive.
621    //
622    TCHAR szClient[ MAX_PATH ];
623    GetClientNetbiosName (szClient);
624
625    TCHAR szLocal[ MAX_PATH ] = TEXT("*:");
626    szLocal[0] = chDrive;
627
628    TCHAR szRemote[ MAX_PATH ];
629    wsprintf (szRemote, TEXT("\\\\%s\\%s"), szClient, szSubmount);
630
631    NETRESOURCE Resource;
632    memset (&Resource, 0x00, sizeof(NETRESOURCE));
633    Resource.dwScope = RESOURCE_GLOBALNET;
634    Resource.dwType = RESOURCETYPE_DISK;
635    Resource.dwDisplayType = RESOURCEDISPLAYTYPE_SHARE;
636    Resource.dwUsage = RESOURCEUSAGE_CONNECTABLE;
637    Resource.lpLocalName = szLocal;
638    Resource.lpRemoteName = szRemote;
639
640    DWORD rc = WNetAddConnection2 (&Resource, NULL, NULL, ((fPersistent) ? CONNECT_UPDATE_PROFILE : 0));
641    if (rc == NO_ERROR)
642       return TRUE;
643
644    if (pdwStatus)
645       *pdwStatus = rc;
646    return FALSE;
647 }
648
649
650 BOOL InactivateDriveMap (TCHAR chDrive, DWORD *pdwStatus)
651 {
652    TCHAR szLocal[ MAX_PATH ] = TEXT("*:");
653    szLocal[0] = chDrive;
654
655    DWORD rc = WNetCancelConnection (szLocal, FALSE);
656    if (rc == NO_ERROR)
657       return TRUE;
658
659    if (pdwStatus)
660       *pdwStatus = rc;
661    return FALSE;
662 }
663
664
665 void AddSubMount (LPTSTR pszSubmount, LPTSTR pszMapping)
666 {
667    TCHAR szRHS[ MAX_PATH ];
668    AdjustAfsPath (szRHS, pszMapping, FALSE, TRUE);
669    if (!szRHS[0])
670       lstrcpy (szRHS, TEXT("/"));
671    WritePrivateProfileString (cszSECTION_SUBMOUNTS, pszSubmount, szRHS, cszINIFILE);
672 }
673
674
675 void RemoveSubMount (LPTSTR pszSubmount)
676 {
677    WritePrivateProfileString (cszSECTION_SUBMOUNTS, pszSubmount, NULL, cszINIFILE);
678 }
679
680
681 void AdjustAfsPath (LPTSTR pszTarget, LPCTSTR pszSource, BOOL fWantAFS, BOOL fWantForwardSlashes)
682 {
683    if (!*pszSource)
684       lstrcpy (pszTarget, (fWantAFS) ? TEXT("/afs") : TEXT(""));
685    else if ((*pszSource != TEXT('/')) && (*pszSource != TEXT('\\')))
686       wsprintf (pszTarget, TEXT("/afs/%s"), pszSource);
687    else if (fWantAFS && lstrncmpi (&pszSource[1], TEXT("afs"), 3))
688       wsprintf (pszTarget, TEXT("/afs%s"), pszSource);
689    else if (!fWantAFS && !lstrncmpi (&pszSource[1], TEXT("afs"), 3))
690       lstrcpy (pszTarget, &pszSource[4]);
691    else
692       lstrcpy (pszTarget, pszSource);
693
694    for (LPTSTR pch = pszTarget; *pch; ++pch)
695       {
696       if (fWantForwardSlashes)
697          {
698          *pch = (*pch == TEXT('\\')) ? TEXT('/') : (*pch);
699          }
700       else // (!fWantForwardSlashes)
701          {
702          *pch = (*pch == TEXT('/')) ? TEXT('\\') : (*pch);
703          }
704       }
705
706    if (lstrlen(pszTarget) &&
707        ((pszTarget[lstrlen(pszTarget)-1] == TEXT('/')) ||
708         (pszTarget[lstrlen(pszTarget)-1] == TEXT('\\'))))
709       {
710       pszTarget[lstrlen(pszTarget)-1] = TEXT('\0');
711       }
712 }
713
714
715 BOOL GetDriveSubmount (TCHAR chDrive, LPTSTR pszSubmountNow)
716 {
717    TCHAR szDrive[] = TEXT("*:");
718    szDrive[0] = chDrive;
719
720    TCHAR szMapping[ MAX_PATH ] = TEXT("");
721    LPTSTR pszSubmount = szMapping;
722
723    if (IsWindowsNT())
724       {
725       QueryDosDevice (szDrive, szMapping, MAX_PATH);
726
727       // Now if this is an AFS network drive mapping, {szMapping} will be:
728       //
729       //   \Device\LanmanRedirector\Q:\machine-afs\submount
730       //
731       // on Windows NT. On Windows 2000, it will be:
732       //
733       //   \Device\LanmanRedirector\;Q:0\machine-afs\submount
734       //
735       // (This is presumably to support multiple drive mappings with
736       // Terminal Server).
737       //
738       if (lstrncmpi (szMapping, cszLANMANDEVICE, lstrlen(cszLANMANDEVICE)))
739          return FALSE;
740       pszSubmount = &szMapping[ lstrlen(cszLANMANDEVICE) ];
741
742       if (IsWindows2000())
743           if (*(pszSubmount) != TEXT(';'))
744              return FALSE;
745
746       if (toupper(*(++pszSubmount)) != chDrive)
747          return FALSE;
748
749       if (*(++pszSubmount) != TEXT(':'))
750          return FALSE;
751
752       if (IsWindows2000())
753           if (*(++pszSubmount) != TEXT('0'))
754              return FALSE;
755
756       if (*(++pszSubmount) != TEXT('\\'))
757          return FALSE;
758       for (++pszSubmount; *pszSubmount && (*pszSubmount != TEXT('\\')); ++pszSubmount)
759          if (!lstrncmpi (pszSubmount, TEXT("-afs\\"), lstrlen(TEXT("-afs\\"))))
760             break;
761       if ((!*pszSubmount) || (*pszSubmount == TEXT('\\')))
762          return FALSE;
763       pszSubmount += lstrlen("-afs\\");
764       }
765    else // (!IsWindowsNT())
766       {
767       DWORD dwSize = MAX_PATH;
768       if (WNetGetConnection (szDrive, szMapping, &dwSize) != NO_ERROR)
769          return FALSE;
770       if (*(pszSubmount++) != TEXT('\\'))
771          return FALSE;
772       if (*(pszSubmount++) != TEXT('\\'))
773          return FALSE;
774       for ( ; *pszSubmount && (*pszSubmount != TEXT('\\')); ++pszSubmount)
775          if (!lstrncmpi (pszSubmount, TEXT("-afs\\"), lstrlen(TEXT("-afs\\"))))
776             break;
777       if ((!*pszSubmount) || (*pszSubmount == TEXT('\\')))
778          return FALSE;
779       pszSubmount += lstrlen("-afs\\");
780       }
781
782    if (!pszSubmount || !*pszSubmount)
783       return FALSE;
784
785    lstrcpy (pszSubmountNow, pszSubmount);
786    return TRUE;
787 }
788