2 * Copyright 2000, International Business Machines Corporation and others.
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
10 #include <afs/param.h>
12 #include <afs/com_err.h>
26 #include <WINNT\afsreg.h>
33 #define MAXINSIZE 1300 /* pioctl complains if data is larger than this */
35 static char space[MAXSIZE];
36 static char tspace[1024];
39 static struct ubik_client *uclient;
40 #endif /* not WIN32 */
43 static char pn[] = "symlink";
44 static int rxInitDone = 0;
56 if (t >= 'A' && t <= 'Z') t += 0x20;
57 if (u >= 'A' && u <= 'Z') u += 0x20;
64 /* this function returns TRUE (1) if the file is in AFS, otherwise false (0) */
65 static int InAFS(register char *apath)
67 struct ViceIoctl blob;
68 register afs_int32 code;
71 blob.out_size = MAXSIZE;
74 code = pioctl_utf8(apath, VIOC_FILE_CELL_NAME, &blob, 1);
76 if ((errno == EINVAL) || (errno == ENOENT))
83 IsFreelanceRoot(char *apath)
85 struct ViceIoctl blob;
89 blob.out_size = MAXSIZE;
92 code = pioctl_utf8(apath, VIOC_FILE_CELL_NAME, &blob, 1);
94 return !stricmp("Freelance.Local.Root",space);
95 return 1; /* assume it is because it is more restrictive that way */
98 static const char * NetbiosName(void)
100 static char buffer[1024] = "AFS";
106 code = RegOpenKeyEx(HKEY_LOCAL_MACHINE, AFSREG_CLT_SVC_PARAM_SUBKEY,
107 0, (IsWow64()?KEY_WOW64_64KEY:0)|KEY_QUERY_VALUE, &parmKey);
108 if (code == ERROR_SUCCESS) {
109 dummyLen = sizeof(buffer);
110 code = RegQueryValueEx(parmKey, "NetbiosName", NULL, NULL,
112 RegCloseKey (parmKey);
114 strcpy(buffer, "AFS");
119 #define AFSCLIENT_ADMIN_GROUPNAME "AFS Client Admins"
121 static BOOL IsAdmin (void)
123 static BOOL fAdmin = FALSE;
124 static BOOL fTested = FALSE;
128 /* Obtain the SID for the AFS client admin group. If the group does
129 * not exist, then assume we have AFS client admin privileges.
131 PSID psidAdmin = NULL;
132 DWORD dwSize, dwSize2;
133 char pszAdminGroup[ MAX_COMPUTERNAME_LENGTH + sizeof(AFSCLIENT_ADMIN_GROUPNAME) + 2 ];
134 char *pszRefDomain = NULL;
135 SID_NAME_USE snu = SidTypeGroup;
137 dwSize = sizeof(pszAdminGroup);
139 if (!GetComputerName(pszAdminGroup, &dwSize)) {
140 /* Can't get computer name. We return false in this case.
141 Retain fAdmin and fTested. This shouldn't happen.*/
148 strcat(pszAdminGroup,"\\");
149 strcat(pszAdminGroup, AFSCLIENT_ADMIN_GROUPNAME);
151 LookupAccountName(NULL, pszAdminGroup, NULL, &dwSize, NULL, &dwSize2, &snu);
152 /* that should always fail. */
154 if (GetLastError() != ERROR_INSUFFICIENT_BUFFER) {
155 /* if we can't find the group, then we allow the operation */
160 if (dwSize == 0 || dwSize2 == 0) {
166 psidAdmin = (PSID)malloc(dwSize); memset(psidAdmin,0,dwSize);
168 pszRefDomain = (char *)malloc(dwSize2);
169 assert(pszRefDomain);
171 if (!LookupAccountName(NULL, pszAdminGroup, psidAdmin, &dwSize, pszRefDomain, &dwSize2, &snu)) {
172 /* We can't lookup the group now even though we looked it up earlier.
173 Could this happen? */
176 /* Then open our current ProcessToken */
179 if (OpenProcessToken (GetCurrentProcess(), TOKEN_QUERY, &hToken))
182 if (!CheckTokenMembership(hToken, psidAdmin, &fAdmin)) {
183 /* We'll have to allocate a chunk of memory to store the list of
184 * groups to which this user belongs; find out how much memory
188 PTOKEN_GROUPS pGroups;
190 GetTokenInformation (hToken, TokenGroups, NULL, dwSize, &dwSize);
192 pGroups = (PTOKEN_GROUPS)malloc(dwSize);
195 /* Allocate that buffer, and read in the list of groups. */
196 if (GetTokenInformation (hToken, TokenGroups, pGroups, dwSize, &dwSize))
198 /* Look through the list of group SIDs and see if any of them
199 * matches the AFS Client Admin group SID.
202 for (; (!fAdmin) && (iGroup < pGroups->GroupCount); ++iGroup)
204 if (EqualSid (psidAdmin, pGroups->Groups[ iGroup ].Sid)) {
214 /* if do not have permission because we were not explicitly listed
215 * in the Admin Client Group let's see if we are the SYSTEM account
218 PTOKEN_USER pTokenUser;
219 SID_IDENTIFIER_AUTHORITY SIDAuth = SECURITY_NT_AUTHORITY;
220 PSID pSidLocalSystem = 0;
223 GetTokenInformation(hToken, TokenUser, NULL, 0, &dwSize);
225 pTokenUser = (PTOKEN_USER)malloc(dwSize);
228 if (!GetTokenInformation(hToken, TokenUser, pTokenUser, dwSize, &dwSize))
229 gle = GetLastError();
231 if (AllocateAndInitializeSid( &SIDAuth, 1,
232 SECURITY_LOCAL_SYSTEM_RID,
236 if (EqualSid(pTokenUser->User.Sid, pSidLocalSystem)) {
240 FreeSid(pSidLocalSystem);
258 /* return a static pointer to a buffer */
259 static char *Parent(apath)
262 strcpy(tspace, apath);
263 tp = strrchr(tspace, '\\');
265 *(tp+1) = 0; /* lv trailing slash so Parent("k:\foo") is "k:\" not "k:" */
268 fs_ExtractDriveLetter(apath, tspace);
275 static ListLinkCmd(register struct cmd_syndesc *as, void *arock)
277 register afs_int32 code;
278 struct ViceIoctl blob;
280 register struct cmd_item *ti;
281 char orig_name[1024]; /*Original name, may be modified*/
282 char true_name[1024]; /*``True'' dirname (e.g., symlink target)*/
283 char parent_dir[1024]; /*Parent directory of true name*/
284 register char *last_component; /*Last component of true name*/
286 struct stat statbuff; /*Buffer for status info*/
287 #endif /* not WIN32 */
289 int link_chars_read; /*Num chars read in readlink()*/
290 #endif /* not WIN32 */
291 int thru_symlink; /*Did we get to a mount point via a symlink?*/
294 for(ti=as->parms[0].items; ti; ti=ti->next) {
298 strcpy(orig_name, ti->data);
299 #else /* not WIN32 */
300 sprintf(orig_name, "%s%s",
301 (ti->data[0] == '/') ? "" : "./",
303 #endif /* not WIN32 */
306 if (lstat(orig_name, &statbuff) < 0) {
307 /* if lstat fails, we should still try the pioctl, since it
308 may work (for example, lstat will fail, but pioctl will
309 work if the volume of offline (returning ENODEV). */
310 statbuff.st_mode = S_IFDIR; /* lie like pros */
314 * The lstat succeeded. If the given file is a symlink, substitute
315 * the file name with the link name.
317 if ((statbuff.st_mode & S_IFMT) == S_IFLNK) {
320 * Read name of resolved file.
322 link_chars_read = readlink(orig_name, true_name, 1024);
323 if (link_chars_read <= 0) {
324 fprintf(stderr,"%s: Can't read target name for '%s' symbolic link!\n",
330 * Add a trailing null to what was read, bump the length.
332 true_name[link_chars_read++] = 0;
335 * If the symlink is an absolute pathname, we're fine. Otherwise, we
336 * have to create a full pathname using the original name and the
337 * relative symlink name. Find the rightmost slash in the original
338 * name (we know there is one) and splice in the symlink value.
340 if (true_name[0] != '\\') {
341 last_component = (char *) strrchr(orig_name, '\\');
342 strcpy(++last_component, true_name);
343 strcpy(true_name, orig_name);
347 strcpy(true_name, orig_name);
349 strcpy(true_name, orig_name);
353 * Find rightmost slash, if any.
355 last_component = (char *) strrchr(true_name, '\\');
357 last_component = (char *) strrchr(true_name, '/');
358 if (last_component) {
360 * Found it. Designate everything before it as the parent directory,
361 * everything after it as the final component.
363 strncpy(parent_dir, true_name, last_component - true_name + 1);
364 parent_dir[last_component - true_name + 1] = 0;
365 last_component++; /*Skip the slash*/
368 if (!InAFS(parent_dir)) {
369 const char * nbname = NetbiosName();
370 int len = (int)strlen(nbname);
372 if (parent_dir[0] == '\\' && parent_dir[1] == '\\' &&
373 parent_dir[len+2] == '\\' &&
374 parent_dir[len+3] == '\0' &&
375 !strnicmp(nbname,&parent_dir[2],len))
377 sprintf(parent_dir,"\\\\%s\\all\\", nbname);
384 * No slash appears in the given file name. Set parent_dir to the current
385 * directory, and the last component as the given name.
387 fs_ExtractDriveLetter(true_name, parent_dir);
388 strcat(parent_dir, ".");
389 last_component = true_name;
390 fs_StripDriveLetter(true_name, true_name, sizeof(true_name));
393 if (strcmp(last_component, ".") == 0 || strcmp(last_component, "..") == 0) {
394 fprintf(stderr,"%s: you may not use '.' or '..' as the last component\n", pn);
395 fprintf(stderr,"%s: of a name in the 'symlink list' command.\n", pn);
399 blob.in = last_component;
400 blob.in_size = (long)strlen(last_component)+1;
401 blob.out_size = MAXSIZE;
403 memset(space, 0, MAXSIZE);
405 code = pioctl_utf8(parent_dir, VIOC_LISTSYMLINK, &blob, 1);
408 printf("'%s' is a %ssymlink to '%s'\n",
410 (thru_symlink ? "symbolic link, leading to a " : ""),
416 fprintf(stderr,"'%s' is not a symlink.\n",
419 Die(errno, (ti->data ? ti->data : parent_dir));
426 static MakeLinkCmd(register struct cmd_syndesc *as, void *arock)
428 register afs_int32 code;
429 struct ViceIoctl blob;
431 char path[1024] = "";
433 strcpy(path, as->parms[0].items->data);
434 parent = Parent(path);
436 if (!InAFS(parent)) {
438 const char * nbname = NetbiosName();
439 int len = (int)strlen(nbname);
441 if (parent[0] == '\\' && parent[1] == '\\' &&
442 parent[len+2] == '\\' &&
443 parent[len+3] == '\0' &&
444 !strnicmp(nbname,&parent[2],len))
446 sprintf(path,"%sall\\%s", parent, &as->parms[0].items->data[strlen(parent)]);
447 parent = Parent(path);
448 if (!InAFS(parent)) {
449 fprintf(stderr,"%s: symlinks must be created within the AFS file system\n", pn);
455 fprintf(stderr,"%s: symlinks must be created within the AFS file system\n", pn);
461 if ( IsFreelanceRoot(parent) && !IsAdmin() ) {
462 fprintf(stderr,"%s: Only AFS Client Administrators may alter the root.afs volume\n", pn);
466 fprintf(stderr, "Creating symlink [%s] to [%s]\n", path, as->parms[1].items->data);
468 /* create symlink with a special pioctl for Windows NT, since it doesn't
469 * have a symlink system call.
472 blob.in_size = 1 + (long)strlen(as->parms[1].items->data);
473 blob.in = as->parms[1].items->data;
475 code = pioctl_utf8(path, VIOC_SYMLINK, &blob, 0);
476 #else /* not WIN32 */
477 code = symlink(as->parms[1].items->data, path);
478 #endif /* not WIN32 */
480 Die(errno, as->parms[0].items->data);
487 * Delete AFS symlinks. Variables are used as follows:
488 * tbuffer: Set to point to the null-terminated directory name of the
489 * symlink (or ``.'' if none is provided)
490 * tp: Set to point to the actual name of the symlink to nuke.
492 static RemoveLinkCmd(register struct cmd_syndesc *as, void *arock)
494 register afs_int32 code=0;
495 struct ViceIoctl blob;
496 register struct cmd_item *ti;
501 for(ti=as->parms[0].items; ti; ti=ti->next) {
503 tp = (char *) strrchr(ti->data, '\\');
505 tp = (char *) strrchr(ti->data, '/');
507 strncpy(tbuffer, ti->data, code=(afs_int32)(tp-ti->data+1)); /* the dir name */
509 tp++; /* skip the slash */
512 if (!InAFS(tbuffer)) {
513 const char * nbname = NetbiosName();
514 int len = (int)strlen(nbname);
516 if (tbuffer[0] == '\\' && tbuffer[1] == '\\' &&
517 tbuffer[len+2] == '\\' &&
518 tbuffer[len+3] == '\0' &&
519 !strnicmp(nbname,&tbuffer[2],len))
521 sprintf(tbuffer,"\\\\%s\\all\\", nbname);
527 fs_ExtractDriveLetter(ti->data, tbuffer);
528 strcat(tbuffer, ".");
530 fs_StripDriveLetter(tp, tp, 0);
533 blob.in_size = (int)strlen(tp)+1;
535 blob.out_size = sizeof(lsbuffer);
536 code = pioctl_utf8(tbuffer, VIOC_LISTSYMLINK, &blob, 0);
539 fprintf(stderr,"symlink: '%s' is not a symlink.\n", ti->data);
541 Die(errno, ti->data);
543 continue; /* don't bother trying */
546 if ( IsFreelanceRoot(tbuffer) && !IsAdmin() ) {
547 fprintf(stderr,"symlink: Only AFS Client Administrators may alter the root.afs volume\n");
554 blob.in_size = (long)strlen(tp)+1;
555 code = pioctl_utf8(tbuffer, VIOC_DELSYMLINK, &blob, 0);
557 Die(errno, ti->data);
564 FreeUtf8CmdLine(int argc, char ** argv)
567 for (i=0; i < argc; i++) {
575 MakeUtf8Cmdline(int argc, const wchar_t **wargv)
580 argv = calloc(argc, sizeof(argv[0]));
584 for (i=0; i < argc; i++) {
587 s = WideCharToMultiByte(CP_UTF8, 0, wargv[i], -1, NULL, 0, NULL, FALSE);
589 (argv[i] = calloc(s+1, sizeof(char))) == NULL) {
593 s = WideCharToMultiByte(CP_UTF8, 0, wargv[i], -1, argv[i], s+1, NULL, FALSE);
600 FreeUtf8CmdLine(argc, argv);
607 static struct ViceIoctl gblob;
608 static int debug = 0;
610 int wmain(int argc, wchar_t **wargv)
612 register afs_int32 code;
613 register struct cmd_syndesc *ts;
618 * The following signal action for AIX is necessary so that in case of a
619 * crash (i.e. core is generated) we can include the user's data section
620 * in the core dump. Unfortunately, by default, only a partial core is
621 * generated which, in many cases, isn't too useful.
623 struct sigaction nsa;
625 sigemptyset(&nsa.sa_mask);
626 nsa.sa_handler = SIG_DFL;
627 nsa.sa_flags = SA_FULLDUMP;
628 sigaction(SIGSEGV, &nsa, NULL);
633 WSAStartup(0x0101, &WSAjunk);
636 /* try to find volume location information */
638 argv = MakeUtf8Cmdline(argc, wargv);
642 ts = cmd_CreateSyntax("list", ListLinkCmd, NULL, "list symlink");
643 cmd_AddParm(ts, "-name", CMD_LIST, 0, "name");
645 ts = cmd_CreateSyntax("make", MakeLinkCmd, NULL, "make symlink");
646 cmd_AddParm(ts, "-name", CMD_SINGLE, 0, "name");
647 cmd_AddParm(ts, "-to", CMD_SINGLE, 0, "target");
649 ts = cmd_CreateSyntax("remove", RemoveLinkCmd, NULL, "remove symlink");
650 cmd_AddParm(ts, "-name", CMD_LIST, 0, "name");
651 cmd_CreateAlias(ts, "rm");
653 code = cmd_Dispatch(argc, argv);
656 if (rxInitDone) rx_Finalize();
657 #endif /* not WIN32 */
659 FreeUtf8CmdLine(argc, argv);
664 void Die(code, filename)
669 if (code == EINVAL) {
671 fprintf(stderr,"%s: Invalid argument; it is possible that %s is not in AFS.\n", pn, filename);
672 else fprintf(stderr,"%s: Invalid argument.\n", pn);
674 else if (code == ENOENT) {
675 if (filename) fprintf(stderr,"%s: File '%s' doesn't exist.\n", pn, filename);
676 else fprintf(stderr,"%s: no such file returned.\n", pn);
678 else if (code == EEXIST) {
679 if (filename) fprintf(stderr,"%s: File '%s' already exists.\n", pn, filename);
680 else fprintf(stderr,"%s: the specified file already exists.\n", pn);
682 else if (code == EROFS) fprintf(stderr,"%s: You can not change a backup or readonly volume\n", pn);
683 else if (code == EACCES || code == EPERM) {
684 if (filename) fprintf(stderr,"%s: You don't have the required access rights on '%s'\n", pn, filename);
685 else fprintf(stderr,"%s: You do not have the required rights to do this operation\n", pn);
687 else if (code == ENODEV) {
688 fprintf(stderr,"%s: AFS service may not have started.\n", pn);
690 else if (code == ESRCH) {
691 fprintf(stderr,"%s: Cell name not recognized.\n", pn);
693 else if (code == EPIPE) {
694 fprintf(stderr,"%s: Volume name or ID not recognized.\n", pn);
696 else if (code == EFBIG) {
697 fprintf(stderr,"%s: Cache size too large.\n", pn);
699 else if (code == ETIMEDOUT) {
701 fprintf(stderr,"%s:'%s': Connection timed out", pn, filename);
703 fprintf(stderr,"%s: Connection timed out", pn);
706 if (filename) fprintf(stderr,"%s:'%s'", pn, filename);
707 else fprintf(stderr,"%s", pn);
709 fprintf(stderr, ": code 0x%x\n", code);
710 #else /* not WIN32 */
711 fprintf(stderr,": %s\n", afs_error_message(code));
712 #endif /* not WIN32 */