a1dd36f4b8c3e20c94658c7e33c5e5763c1cf6ba
[openafs.git] / src / WINNT / afsd / symlink.c
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 #include <afs/param.h>
11 #include <afs/stds.h>
12
13 #include <windows.h>
14 #include <stdlib.h>
15 #include <malloc.h>
16 #include <string.h>
17 #include <stdio.h>
18 #include <time.h>
19 #include <winsock2.h>
20 #include <errno.h>
21 #include <assert.h>
22
23 #include <osi.h>
24 #include <afsint.h>
25
26 #include "fs_utils.h"
27 #include "cmd.h"
28
29 #define MAXNAME 100
30 #define MAXSIZE 2048
31 #define MAXINSIZE 1300    /* pioctl complains if data is larger than this */
32
33 static char space[MAXSIZE];
34 static char tspace[1024];
35
36 #ifndef WIN32
37 static struct ubik_client *uclient;
38 #endif /* not WIN32 */
39
40
41 static char pn[] = "symlink";
42 static int rxInitDone = 0;
43
44 void Die();
45
46 foldcmp (a, b)
47     register char *a;
48     register char *b; {
49     register char t, u;
50     while (1) {
51         t = *a++;
52         u = *b++;
53         if (t >= 'A' && t <= 'Z') t += 0x20;
54         if (u >= 'A' && u <= 'Z') u += 0x20;
55         if (t != u) return 1;
56         if (t == 0) return 0;
57     }
58 }
59
60 /* this function returns TRUE (1) if the file is in AFS, otherwise false (0) */
61 static int InAFS(register char *apath)
62 {
63     struct ViceIoctl blob;
64     register afs_int32 code;
65
66     blob.in_size = 0;
67     blob.out_size = MAXSIZE;
68     blob.out = space;
69
70     code = pioctl(apath, VIOC_FILE_CELL_NAME, &blob, 1);
71     if (code) {
72         if ((errno == EINVAL) || (errno == ENOENT)) 
73             return 0;
74     }
75     return 1;
76 }
77
78 static int 
79 IsFreelanceRoot(char *apath)
80 {
81     struct ViceIoctl blob;
82     afs_int32 code;
83
84     blob.in_size = 0;
85     blob.out_size = MAXSIZE;
86     blob.out = space;
87
88     code = pioctl(apath, VIOC_FILE_CELL_NAME, &blob, 1);
89     if (code == 0)
90         return !strcmp("Freelance.Local.Root",space);
91     return 1;   /* assume it is because it is more restrictive that way */
92 }
93
94 #define AFSCLIENT_ADMIN_GROUPNAME "AFS Client Admins"
95
96 static BOOL IsAdmin (void)
97 {
98     static BOOL fAdmin = FALSE;
99     static BOOL fTested = FALSE;
100
101     if (!fTested)
102     {
103         /* Obtain the SID for the AFS client admin group.  If the group does
104          * not exist, then assume we have AFS client admin privileges.
105          */
106         PSID psidAdmin = NULL;
107         DWORD dwSize, dwSize2;
108         char pszAdminGroup[ MAX_COMPUTERNAME_LENGTH + sizeof(AFSCLIENT_ADMIN_GROUPNAME) + 2 ];
109         char *pszRefDomain = NULL;
110         SID_NAME_USE snu = SidTypeGroup;
111
112         dwSize = sizeof(pszAdminGroup);
113
114         if (!GetComputerName(pszAdminGroup, &dwSize)) {
115             /* Can't get computer name.  We return false in this case.
116                Retain fAdmin and fTested. This shouldn't happen.*/
117             return FALSE;
118         }
119
120         dwSize = 0;
121         dwSize2 = 0;
122
123         strcat(pszAdminGroup,"\\");
124         strcat(pszAdminGroup, AFSCLIENT_ADMIN_GROUPNAME);
125
126         LookupAccountName(NULL, pszAdminGroup, NULL, &dwSize, NULL, &dwSize2, &snu);
127         /* that should always fail. */
128
129         if (GetLastError() != ERROR_INSUFFICIENT_BUFFER) {
130             /* if we can't find the group, then we allow the operation */
131             fAdmin = TRUE;
132             return TRUE;
133         }
134
135         if (dwSize == 0 || dwSize2 == 0) {
136             /* Paranoia */
137             fAdmin = TRUE;
138             return TRUE;
139         }
140
141         psidAdmin = (PSID)malloc(dwSize); memset(psidAdmin,0,dwSize);
142         assert(psidAdmin);
143         pszRefDomain = (char *)malloc(dwSize2);
144         assert(pszRefDomain);
145
146         if (!LookupAccountName(NULL, pszAdminGroup, psidAdmin, &dwSize, pszRefDomain, &dwSize2, &snu)) {
147             /* We can't lookup the group now even though we looked it up earlier.  
148                Could this happen? */
149             fAdmin = TRUE;
150         } else {
151             /* Then open our current ProcessToken */
152             HANDLE hToken;
153
154             if (OpenProcessToken (GetCurrentProcess(), TOKEN_QUERY, &hToken))
155             {
156
157                 if (!CheckTokenMembership(hToken, psidAdmin, &fAdmin)) {
158                     /* We'll have to allocate a chunk of memory to store the list of
159                      * groups to which this user belongs; find out how much memory
160                      * we'll need.
161                      */
162                     DWORD dwSize = 0;
163                     PTOKEN_GROUPS pGroups;
164
165                     GetTokenInformation (hToken, TokenGroups, NULL, dwSize, &dwSize);
166
167                     pGroups = (PTOKEN_GROUPS)malloc(dwSize);
168                     assert(pGroups);
169
170                     /* Allocate that buffer, and read in the list of groups. */
171                     if (GetTokenInformation (hToken, TokenGroups, pGroups, dwSize, &dwSize))
172                     {
173                         /* Look through the list of group SIDs and see if any of them
174                          * matches the AFS Client Admin group SID.
175                          */
176                         size_t iGroup = 0;
177                         for (; (!fAdmin) && (iGroup < pGroups->GroupCount); ++iGroup)
178                         {
179                             if (EqualSid (psidAdmin, pGroups->Groups[ iGroup ].Sid)) {
180                                 fAdmin = TRUE;
181                             }
182                         }
183                     }
184
185                     if (pGroups)
186                         free(pGroups);
187                 }
188
189                 /* if do not have permission because we were not explicitly listed
190                  * in the Admin Client Group let's see if we are the SYSTEM account
191                  */
192                 if (!fAdmin) {
193                     PTOKEN_USER pTokenUser;
194                     SID_IDENTIFIER_AUTHORITY SIDAuth = SECURITY_NT_AUTHORITY;
195                     PSID pSidLocalSystem = 0;
196                     DWORD gle;
197
198                     GetTokenInformation(hToken, TokenUser, NULL, 0, &dwSize);
199
200                     pTokenUser = (PTOKEN_USER)malloc(dwSize);
201                     assert(pTokenUser);
202
203                     if (!GetTokenInformation(hToken, TokenUser, pTokenUser, dwSize, &dwSize))
204                         gle = GetLastError();
205
206                     if (AllocateAndInitializeSid( &SIDAuth, 1,
207                                                   SECURITY_LOCAL_SYSTEM_RID,
208                                                   0, 0, 0, 0, 0, 0, 0,
209                                                   &pSidLocalSystem))
210                     {
211                         if (EqualSid(pTokenUser->User.Sid, pSidLocalSystem)) {
212                             fAdmin = TRUE;
213                         }
214
215                         FreeSid(pSidLocalSystem);
216                     }
217
218                     if ( pTokenUser )
219                         free(pTokenUser);
220                 }
221             }
222         }
223
224         free(psidAdmin);
225         free(pszRefDomain);
226
227         fTested = TRUE;
228     }
229
230     return fAdmin;
231 }
232
233 /* return a static pointer to a buffer */
234 static char *Parent(apath)
235 char *apath; {
236     register char *tp;
237     strcpy(tspace, apath);
238     tp = strrchr(tspace, '\\');
239     if (tp) {
240         *(tp+1) = 0;    /* lv trailing slash so Parent("k:\foo") is "k:\" not "k:" */
241     }
242     else {
243         fs_ExtractDriveLetter(apath, tspace);
244         strcat(tspace, ".");
245     }
246     return tspace;
247 }
248
249
250 static ListLinkCmd(as)
251 register struct cmd_syndesc *as; {
252     register afs_int32 code;
253     struct ViceIoctl blob;
254     int error;
255     register struct cmd_item *ti;
256     char orig_name[1024];               /*Original name, may be modified*/
257     char true_name[1024];               /*``True'' dirname (e.g., symlink target)*/
258     char parent_dir[1024];              /*Parent directory of true name*/
259     register char *last_component;      /*Last component of true name*/
260 #ifndef WIN32
261     struct stat statbuff;               /*Buffer for status info*/
262 #endif /* not WIN32 */
263 #ifndef WIN32
264     int link_chars_read;                /*Num chars read in readlink()*/
265 #endif /* not WIN32 */
266     int thru_symlink;                   /*Did we get to a mount point via a symlink?*/
267     
268     error = 0;
269     for(ti=as->parms[0].items; ti; ti=ti->next) {
270         /* once per file */
271         thru_symlink = 0;
272 #ifdef WIN32
273         strcpy(orig_name, ti->data);
274 #else /* not WIN32 */
275         sprintf(orig_name, "%s%s",
276                 (ti->data[0] == '/') ? "" : "./",
277                 ti->data);
278 #endif /* not WIN32 */
279
280 #ifndef WIN32
281         if (lstat(orig_name, &statbuff) < 0) {
282             /* if lstat fails, we should still try the pioctl, since it
283                 may work (for example, lstat will fail, but pioctl will
284                     work if the volume of offline (returning ENODEV). */
285             statbuff.st_mode = S_IFDIR; /* lie like pros */
286         }
287
288         /*
289          * The lstat succeeded.  If the given file is a symlink, substitute
290          * the file name with the link name.
291          */
292         if ((statbuff.st_mode & S_IFMT) == S_IFLNK) {
293             thru_symlink = 1;
294             /*
295              * Read name of resolved file.
296              */
297             link_chars_read = readlink(orig_name, true_name, 1024);
298             if (link_chars_read <= 0) {
299                 fprintf(stderr,"%s: Can't read target name for '%s' symbolic link!\n",
300                        pn, orig_name);
301                 exit(1);
302             }
303
304             /*
305              * Add a trailing null to what was read, bump the length.
306              */
307             true_name[link_chars_read++] = 0;
308
309             /*
310              * If the symlink is an absolute pathname, we're fine.  Otherwise, we
311              * have to create a full pathname using the original name and the
312              * relative symlink name.  Find the rightmost slash in the original
313              * name (we know there is one) and splice in the symlink value.
314              */
315             if (true_name[0] != '\\') {
316                 last_component = (char *) strrchr(orig_name, '\\');
317                 strcpy(++last_component, true_name);
318                 strcpy(true_name, orig_name);
319             }
320         }
321         else
322             strcpy(true_name, orig_name);
323 #else   /* WIN32 */
324         strcpy(true_name, orig_name);
325 #endif /* WIN32 */
326
327         /*
328          * Find rightmost slash, if any.
329          */
330         last_component = (char *) strrchr(true_name, '\\');
331         if (!last_component)
332             last_component = (char *) strrchr(true_name, '/');
333         if (last_component) {
334             /*
335              * Found it.  Designate everything before it as the parent directory,
336              * everything after it as the final component.
337              */
338             strncpy(parent_dir, true_name, last_component - true_name + 1);
339             parent_dir[last_component - true_name + 1] = 0;
340             last_component++;   /*Skip the slash*/
341         }
342         else {
343             /*
344              * No slash appears in the given file name.  Set parent_dir to the current
345              * directory, and the last component as the given name.
346              */
347             fs_ExtractDriveLetter(true_name, parent_dir);
348             strcat(parent_dir, ".");
349             last_component = true_name;
350             fs_StripDriveLetter(true_name, true_name, sizeof(true_name));
351         }
352
353         if (strcmp(last_component, ".") == 0 || strcmp(last_component, "..") == 0) {
354             fprintf(stderr,"%s: you may not use '.' or '..' as the last component\n", pn);
355             fprintf(stderr,"%s: of a name in the 'symlink list' command.\n", pn);
356             error = 1;
357             continue;
358         }
359         blob.in = last_component;
360         blob.in_size = strlen(last_component)+1;
361         blob.out_size = MAXSIZE;
362         blob.out = space;
363         memset(space, 0, MAXSIZE);
364
365         code = pioctl(parent_dir, VIOC_LISTSYMLINK, &blob, 1);
366
367         if (code == 0)
368             printf("'%s' is a %ssymlink to '%s'\n",
369                    ti->data,
370                    (thru_symlink ? "symbolic link, leading to a " : ""),
371                    space);
372
373         else {
374             error = 1;
375             if (errno == EINVAL)
376                 fprintf(stderr,"'%s' is not a symlink.\n",
377                        ti->data);
378             else {
379                 Die(errno, (ti->data ? ti->data : parent_dir));
380             }
381         }
382     }
383     return error;
384 }
385
386 static MakeLinkCmd(as)
387 register struct cmd_syndesc *as; {
388     register afs_int32 code;
389     struct ViceIoctl blob;
390     char * parent;
391
392     parent = Parent(as->parms[0].items->data);
393
394     if (!InAFS(parent)) {
395         fprintf(stderr,"%s: symlinks must be created within the AFS file system\n", pn);
396         return 1;
397     }
398
399     if ( IsFreelanceRoot(parent) && !IsAdmin() ) {
400         fprintf(stderr,"%s: Only AFS Client Administrators may alter the root.afs volume\n", pn);
401         return 1;
402     }
403
404     strcpy(space, as->parms[1].items->data);
405 #ifdef WIN32
406     /* create symlink with a special pioctl for Windows NT, since it doesn't
407      * have a symlink system call.
408      */
409
410     /* TODO: Code needs to go here to prevent the creation of symlinks
411      * in \\AFS\all when not in the "AFS Client Admins" group.
412      */
413
414     blob.out_size = 0;
415     blob.in_size = 1 + strlen(space);
416     blob.in = space;
417     blob.out = NULL;
418     code = pioctl(as->parms[0].items->data, VIOC_SYMLINK, &blob, 0);
419 #else /* not WIN32 */
420     code = symlink(space, as->parms[0].items->data);
421 #endif /* not WIN32 */
422     if (code) {
423         Die(errno, as->parms[0].items->data);
424         return 1;
425     }
426     return 0;
427 }
428
429 /*
430  * Delete AFS symlinks.  Variables are used as follows:
431  *       tbuffer: Set to point to the null-terminated directory name of the
432  *          symlink (or ``.'' if none is provided)
433  *      tp: Set to point to the actual name of the symlink to nuke.
434  */
435 static RemoveLinkCmd(as)
436 register struct cmd_syndesc *as; {
437     register afs_int32 code=0;
438     struct ViceIoctl blob;
439     register struct cmd_item *ti;
440     char tbuffer[1024];
441     char lsbuffer[1024];
442     register char *tp;
443     
444     for(ti=as->parms[0].items; ti; ti=ti->next) {
445         /* once per file */
446         tp = (char *) strrchr(ti->data, '\\');
447         if (!tp)
448             tp = (char *) strrchr(ti->data, '/');
449         if (tp) {
450             strncpy(tbuffer, ti->data, code=tp-ti->data+1);  /* the dir name */
451             tbuffer[code] = 0;
452             tp++;   /* skip the slash */
453         }
454         else {
455             fs_ExtractDriveLetter(ti->data, tbuffer);
456             strcat(tbuffer, ".");
457             tp = ti->data;
458             fs_StripDriveLetter(tp, tp, 0);
459         }
460         blob.in = tp;
461         blob.in_size = strlen(tp)+1;
462         blob.out = lsbuffer;
463         blob.out_size = sizeof(lsbuffer);
464         code = pioctl(tbuffer, VIOC_LISTSYMLINK, &blob, 0);
465         if (code) {
466             if (errno == EINVAL)
467                 fprintf(stderr,"symlink: '%s' is not a symlink.\n", ti->data);
468             else {
469                 Die(errno, ti->data);
470             }
471             continue;   /* don't bother trying */
472         }
473
474         if ( IsFreelanceRoot(Parent(ti->data)) && !IsAdmin() ) {
475             fprintf(stderr,"symlink: Only AFS Client Administrators may alter the root.afs volume\n");
476             code = 1;
477             continue;   /* skip */
478         }
479
480         blob.out_size = 0;
481         blob.in = tp;
482         blob.in_size = strlen(tp)+1;
483         code = pioctl(tbuffer, VIOC_DELSYMLINK, &blob, 0);
484         if (code) {
485             Die(errno, ti->data);
486         }
487     }
488     return code;
489 }
490
491 static    struct ViceIoctl gblob;
492 static int debug = 0;
493
494 main(argc, argv)
495 int argc;
496 char **argv; {
497     register afs_int32 code;
498     register struct cmd_syndesc *ts;
499     
500 #ifdef  AFS_AIX32_ENV
501     /*
502      * The following signal action for AIX is necessary so that in case of a 
503      * crash (i.e. core is generated) we can include the user's data section 
504      * in the core dump. Unfortunately, by default, only a partial core is
505      * generated which, in many cases, isn't too useful.
506      */
507     struct sigaction nsa;
508     
509     sigemptyset(&nsa.sa_mask);
510     nsa.sa_handler = SIG_DFL;
511     nsa.sa_flags = SA_FULLDUMP;
512     sigaction(SIGSEGV, &nsa, NULL);
513 #endif
514
515 #ifdef WIN32
516     WSADATA WSAjunk;
517     WSAStartup(0x0101, &WSAjunk);
518 #endif /* WIN32 */
519
520     /* try to find volume location information */
521     
522
523     osi_Init();
524
525     ts = cmd_CreateSyntax("list", ListLinkCmd, 0, "list symlink");    
526     cmd_AddParm(ts, "-name", CMD_LIST, 0, "name");
527     
528     ts = cmd_CreateSyntax("make", MakeLinkCmd, 0, "make symlink");
529     cmd_AddParm(ts, "-name", CMD_SINGLE, 0, "name");
530     cmd_AddParm(ts, "-to", CMD_SINGLE, 0, "target");
531
532     ts = cmd_CreateSyntax("rm", RemoveLinkCmd, 0, "remove symlink");
533     cmd_AddParm(ts, "-name", CMD_LIST, 0, "name");
534
535     code = cmd_Dispatch(argc, argv);
536
537 #ifndef WIN32
538     if (rxInitDone) rx_Finalize();
539 #endif /* not WIN32 */
540     
541     return code;
542 }
543
544 void Die(code, filename)
545     int   code;
546     char *filename;
547 { /*Die*/
548
549     if (code == EINVAL) {
550         if (filename)
551             fprintf(stderr,"%s: Invalid argument; it is possible that %s is not in AFS.\n", pn, filename);
552         else fprintf(stderr,"%s: Invalid argument.\n", pn);
553     }
554     else if (code == ENOENT) {
555         if (filename) fprintf(stderr,"%s: File '%s' doesn't exist\n", pn, filename);
556         else fprintf(stderr,"%s: no such file returned\n", pn);
557     }
558     else if (code == EROFS)  fprintf(stderr,"%s: You can not change a backup or readonly volume\n", pn);
559     else if (code == EACCES || code == EPERM) {
560         if (filename) fprintf(stderr,"%s: You don't have the required access rights on '%s'\n", pn, filename);
561         else fprintf(stderr,"%s: You do not have the required rights to do this operation\n", pn);
562     }
563     else if (code == ENODEV) {
564         fprintf(stderr,"%s: AFS service may not have started.\n", pn);
565     }
566     else if (code == ESRCH) {
567         fprintf(stderr,"%s: Cell name not recognized.\n", pn);
568     }
569     else if (code == EPIPE) {
570         fprintf(stderr,"%s: Volume name or ID not recognized.\n", pn);
571     }
572     else if (code == EFBIG) {
573         fprintf(stderr,"%s: Cache size too large.\n", pn);
574     }
575     else if (code == ETIMEDOUT) {
576         if (filename)
577             fprintf(stderr,"%s:'%s': Connection timed out", pn, filename);
578         else
579             fprintf(stderr,"%s: Connection timed out", pn);
580     }
581     else {
582         if (filename) fprintf(stderr,"%s:'%s'", pn, filename);
583         else fprintf(stderr,"%s", pn);
584 #ifdef WIN32
585         fprintf(stderr, ": code 0x%x\n", code);
586 #else /* not WIN32 */
587         fprintf(stderr,": %s\n", error_message(code));
588 #endif /* not WIN32 */
589     }
590 } /*Die*/