]> arthur.barton.de Git - netatalk.git/blob - etc/afpd/extattrs.c
2cbcec9e44f64ceea3bf8eeea147e55079b2ab74
[netatalk.git] / etc / afpd / extattrs.c
1 /*
2   $Id: extattrs.c,v 1.2 2009-03-03 13:51:25 franklahm Exp $
3   Copyright (c) 2009 Frank Lahm <franklahm@gmail.com>
4
5   This program is free software; you can redistribute it and/or modify
6   it under the terms of the GNU General Public License as published by
7   the Free Software Foundation; either version 2 of the License, or
8   (at your option) any later version.
9
10   This program is distributed in the hope that it will be useful,
11   but WITHOUT ANY WARRANTY; without even the implied warranty of
12   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13   GNU General Public License for more details.
14 */
15
16 /* According to man fsattr.5 we must define _ATFILE_SOURCE */
17 #define _ATFILE_SOURCE
18
19 #ifdef HAVE_CONFIG_H
20 #include "config.h"
21 #endif /* HAVE_CONFIG_H */
22
23 #ifdef HAVE_EXT_ATTRS
24
25 #include <errno.h>
26 #include <unistd.h>
27 #include <stdlib.h>
28 #include <string.h>
29 #include <sys/types.h>
30 #include <sys/stat.h>
31 #include <fcntl.h>
32 #include <dirent.h>
33
34 #include <atalk/adouble.h>
35 #include <atalk/afp.h>
36 #include <atalk/logger.h>
37
38 #include "globals.h"
39 #include "volume.h"
40 #include "desktop.h"
41 #include "directory.h"
42 #include "fork.h"
43 #include "extattrs.h"
44
45 static char *ea_finderinfo = "com.apple.FinderInfo";
46 static char *ea_resourcefork = "com.apple.ResourceFork";
47
48 /* This should be big enough to consecutively store the names of all attributes */
49 #define ATTRNAMEBUFSIZ 4096
50 static char attrnamebuf[ATTRNAMEBUFSIZ];
51
52 static void hexdump(void *m, size_t l) {
53     char *p = m;
54     int count = 0, len;
55     char buf[100];
56     char *bufp = buf;
57
58     while (l--) {
59         len = sprintf(bufp, "%02x ", *p++);
60         bufp += len;
61         count++;
62
63         if ((count % 16) == 0) {
64             LOG(log_debug9, logtype_afpd, "%s", buf);
65             bufp = buf;
66         }
67     }
68 }
69
70 static int getextattr_size(char *rbuf, int *rbuflen, char *uname, int oflag, char *attruname)
71 {
72     int                 ret, attrdirfd;
73     uint32_t            attrsize;
74     struct stat         st;
75
76     LOG(log_debug7, logtype_afpd, "getextattr_size(%s): attribute: \"%s\"", uname, attruname);
77
78     if ( -1 == (attrdirfd = attropen(uname, ".", O_RDONLY | oflag))) {
79         if (errno == ELOOP) {
80             /* its a symlink and client requested O_NOFOLLOW  */
81             LOG(log_debug, logtype_afpd, "getextattr_size(%s): encountered symlink with kXAttrNoFollow", uname);
82
83             memset(rbuf, 0, 4);
84             *rbuflen += 4;
85
86             return AFP_OK;
87         }
88         LOG(log_error, logtype_afpd, "getextattr_size: attropen error: %s", strerror(errno));
89         return AFPERR_MISC;
90     }
91
92     if ( -1 == (fstatat(attrdirfd, attruname, &st, 0))) {
93         LOG(log_error, logtype_afpd, "getextattr_size: fstatat error: %s", strerror(errno));
94         ret = AFPERR_MISC;
95         goto exit;
96     }
97     attrsize = (st.st_size > MAX_EA_SIZE) ? MAX_EA_SIZE : st.st_size;
98     
99     /* Start building reply packet */
100
101     LOG(log_debug7, logtype_afpd, "getextattr_size(%s): attribute: \"%s\", size: %u", uname, attruname, attrsize);
102
103     /* length of attribute data */
104     attrsize = htonl(attrsize);
105     memcpy(rbuf, &attrsize, 4);
106     *rbuflen += 4;
107
108     ret = AFP_OK;
109
110 exit:
111     close(attrdirfd);
112     return ret;
113 }
114
115 static int getextattr_content(char *rbuf, int *rbuflen, char *uname, int oflag, char *attruname, int maxreply)
116 {
117     int                 ret, attrdirfd;
118     size_t              toread, okread = 0, len;
119     char                *datalength;
120     struct stat         st;
121
122     if ( -1 == (attrdirfd = attropen(uname, attruname, O_RDONLY | oflag))) {
123         if (errno == ELOOP) {
124             /* its a symlink and client requested O_NOFOLLOW  */
125             LOG(log_debug, logtype_afpd, "getextattr_content(%s): encountered symlink with kXAttrNoFollow", uname);
126
127             memset(rbuf, 0, 4);
128             *rbuflen += 4;
129
130             return AFP_OK;
131         }
132         LOG(log_error, logtype_afpd, "getextattr_content(%s): attropen error: %s", attruname, strerror(errno));
133         return AFPERR_MISC;
134     }
135
136     if ( -1 == (fstat(attrdirfd, &st))) {
137         LOG(log_error, logtype_afpd, "getextattr_content(%s): fstatat error: %s", attruname,strerror(errno));
138         ret = AFPERR_MISC;
139         goto exit;
140     }
141
142     /* Start building reply packet */
143
144     maxreply -= MAX_REPLY_EXTRA_BYTES;
145     if (maxreply > MAX_EA_SIZE)
146         maxreply = MAX_EA_SIZE;
147
148     /* But never send more than the client requested */
149     toread = (maxreply < st.st_size) ? maxreply : st.st_size;
150
151     LOG(log_debug7, logtype_afpd, "getextattr_content(%s): attribute: \"%s\", size: %u", uname, attruname, maxreply);
152
153     /* remember where we must store length of attribute data in rbuf */
154     datalength = rbuf;
155     rbuf += 4;
156     *rbuflen += 4;
157
158     while (1) {
159         len = read(attrdirfd, rbuf, toread);
160         if (len == -1) {
161             LOG(log_error, logtype_afpd, "getextattr_content(%s): read error: %s", attruname, strerror(errno));
162             ret = AFPERR_MISC;
163             goto exit;
164         }
165         okread += len;
166         rbuf += len;
167         *rbuflen += len;
168         if ((len == 0) || (okread == toread))
169             break;
170     }
171
172     okread = htonl((uint32_t)okread);
173     memcpy(datalength, &okread, 4);
174
175     ret = AFP_OK;
176
177 exit:
178     close(attrdirfd);
179     return ret;
180 }
181
182 int list_extattr(AFPObj *obj, char *attrnamebuf, int *buflen, char *uname, int oflag)
183 {
184     int ret, attrbuflen = *buflen, len, attrdirfd = 0;
185     struct dirent *dp;
186     DIR *dirp = NULL;
187
188     /* Now list file attribute dir */
189     if ( -1 == (attrdirfd = attropen( uname, ".", O_RDONLY | oflag))) {
190         if (errno == ELOOP) {
191             /* its a symlink and client requested O_NOFOLLOW */
192             ret = AFPERR_BADTYPE;
193             goto exit;
194         }       
195         LOG(log_error, logtype_afpd, "list_extattr(%s): error opening atttribute dir: %s", uname, strerror(errno));
196         ret = AFPERR_MISC;
197         goto exit;
198     }
199
200     if (NULL == (dirp = fdopendir(attrdirfd))) {
201         LOG(log_error, logtype_afpd, "list_extattr(%s): error opening atttribute dir: %s", uname, strerror(errno));
202         ret = AFPERR_MISC;
203         goto exit;
204     }
205     
206     while ((dp = readdir(dirp)))  {
207         /* check if its "." or ".." */
208         if ((strcmp(dp->d_name, ".") == 0) || (strcmp(dp->d_name, "..") == 0) ||
209             (strcmp(dp->d_name, "SUNWattr_ro") == 0) || (strcmp(dp->d_name, "SUNWattr_rw") == 0))
210             continue;
211         
212         len = strlen(dp->d_name);
213         
214         if ( 0 >= ( len = convert_string(obj->options.unixcharset, CH_UTF8_MAC, dp->d_name, len, attrnamebuf + attrbuflen, 255)) ) {
215             ret = AFPERR_MISC;
216             goto exit;
217         }
218         if (len == 255)
219             /* convert_string didn't 0-terminate */
220             attrnamebuf[attrbuflen + 255] = 0;
221         
222         LOG(log_debug7, logtype_afpd, "list_extattr(%s): attribute: %s", uname, dp->d_name);
223         
224         attrbuflen += len + 1;
225         if (attrbuflen > (ATTRNAMEBUFSIZ - 256)) {
226             /* Next EA name could overflow, so bail out with error.
227                FIXME: evantually malloc/memcpy/realloc whatever.
228                Is it worth it ? */
229             LOG(log_warning, logtype_afpd, "list_extattr(%s): running out of buffer for EA names", uname);
230             ret = AFPERR_MISC;
231             goto exit;
232         }
233     }
234
235     ret = AFP_OK;
236
237 exit:
238     if (dirp)
239         closedir(dirp);
240
241     if (attrdirfd > 0)
242         close(attrdirfd);
243
244     *buflen = attrbuflen;
245     return ret;
246 }
247
248 /***************************************
249  * Interface
250  ****************************************/
251
252 /* 
253    Note: we're being called twice. Firstly the client only want the size of all
254    EA names, secondly it wants these names. In order to avoid scanning EAs twice
255    we cache them in a static buffer.
256  */
257 int afp_listextattr(AFPObj *obj, char *ibuf, int ibuflen _U_, char *rbuf, int *rbuflen)
258 {
259     int                 count, ret, oflag = 0;
260     uint16_t            vid, bitmap;
261     uint32_t            did, maxreply;
262     struct vol          *vol;
263     struct dir          *dir;
264     struct path         *s_path;
265     struct adouble      ad, *adp = NULL;
266     struct ofork        *of;
267     char                *uname, *FinderInfo;
268     static int          buf_valid = 0, attrbuflen = 0;
269
270 #ifdef DEBUG
271     LOG(log_debug9, logtype_afpd, "afp_listextattr: BEGIN");
272 #endif
273
274     *rbuflen = 0;
275     ibuf += 2;
276
277     /* Get MaxReplySize first */
278     memcpy( &maxreply, ibuf + 14, 4);
279     maxreply = ntohl( maxreply );
280
281     /*
282       If its the first request with maxreply=0 or if we didn't mark our buffers valid for
283       whatever reason (just a safety check, it should be valid), then scan for attributes
284     */
285     if ((maxreply == 0) || (buf_valid == 0)) {
286
287         attrbuflen = 0;
288
289         memcpy( &vid, ibuf, 2);
290         ibuf += 2;
291         if (NULL == ( vol = getvolbyvid( vid )) ) {
292             LOG(log_error, logtype_afpd, "afp_listextattr: getvolbyvid error: %s", strerror(errno));
293             return AFPERR_ACCESS;
294         }
295
296         memcpy( &did, ibuf, 4);
297         ibuf += 4;
298         if (NULL == ( dir = dirlookup( vol, did )) ) {
299             LOG(log_error, logtype_afpd, "afp_listextattr: dirlookup error: %s", strerror(errno));
300             return afp_errno;
301         }
302
303         memcpy( &bitmap, ibuf, 2);
304         bitmap = ntohs( bitmap );
305         ibuf += 2;
306
307         if (bitmap & kXAttrNoFollow)
308             oflag = O_NOFOLLOW;
309
310         /* Skip ReqCount, StartIndex and maxreply*/
311         ibuf += 10;
312
313         /* get name */
314         if (NULL == ( s_path = cname( vol, dir, &ibuf )) ) {
315             LOG(log_error, logtype_afpd, "afp_listextattr: cname error: %s", strerror(errno));
316             return AFPERR_NOOBJ;
317         }
318         uname = s_path->u_name;
319
320         /*
321           We have to check the FinderInfo for the file, because if they aren't all 0
322           we must return the synthetic attribute "com.apple.FinderInfo".
323           Note: the client will never (never seen in traces) request that attribute
324           via FPGetExtAttr !
325         */
326         if ((of = of_findname(s_path))) {
327             adp = of->of_ad;
328         } else {
329             ad_init(&ad, vol->v_adouble, vol->v_ad_options);
330             adp = &ad;
331         }
332
333         if ( ad_metadata( uname, 0, adp) < 0 ) {
334             switch (errno) {
335             case EACCES:
336                 LOG(log_error, logtype_afpd, "afp_listextattr(%s): %s: check resource fork permission?",
337                     uname, strerror(errno));
338                 return AFPERR_ACCESS;
339             default:
340                 LOG(log_error, logtype_afpd, "afp_listextattr(%s): error getting metadata: %s", uname, strerror(errno));
341                 return AFPERR_MISC;
342             }
343         }
344
345         FinderInfo = ad_entry(adp, ADEID_FINDERI);
346 #ifdef DEBUG
347         LOG(log_debug9, logtype_afpd, "afp_listextattr(%s): FinderInfo:", uname);
348         hexdump( FinderInfo, 32);
349 #endif
350
351         /* Now scan FinderInfo if its all 0 */
352         count = 32;
353         while (count--) {
354             if (*FinderInfo++) {
355                 /* FinderInfo contains some non 0 bytes -> include "com.apple.FinderInfo" */
356                 strcpy(attrnamebuf, ea_finderinfo);
357                 attrbuflen += strlen(ea_finderinfo) + 1;
358                 LOG(log_debug7, logtype_afpd, "afp_listextattr(%s): sending com.apple.FinderInfo", uname);
359                 break;
360             }
361         }
362
363         /* Now check for Ressource fork and add virtual EA "com.apple.ResourceFork" if size > 0 */
364         LOG(log_debug7, logtype_afpd, "afp_listextattr(%s): Ressourcefork size: %u", uname, adp->ad_eid[ADEID_RFORK].ade_len);
365         if (adp->ad_eid[ADEID_RFORK].ade_len > 0) {
366             LOG(log_debug7, logtype_afpd, "afp_listextattr(%s): sending com.apple.RessourceFork.", uname);
367             strcpy(attrnamebuf + attrbuflen, ea_resourcefork);
368             attrbuflen += strlen(ea_resourcefork) + 1;      
369         }
370
371         ret = list_extattr(obj, attrnamebuf, &attrbuflen, uname, oflag);
372         switch (ret) {
373         case AFPERR_BADTYPE:
374             /* its a symlink and client requested O_NOFOLLOW */
375             LOG(log_debug, logtype_afpd, "afp_listextattr(%s): encountered symlink with kXAttrNoFollow", uname);
376             attrbuflen = 0;
377             buf_valid = 0;
378             ret = AFP_OK;
379             goto exit;
380         case AFPERR_MISC:
381             attrbuflen = 0;
382             goto exit;
383         default:
384             buf_valid = 1;
385         }
386     }
387
388     /* Start building reply packet */
389     bitmap = htons(bitmap);
390     memcpy( rbuf, &bitmap, 2);
391     rbuf += 2;
392     *rbuflen += 2;
393
394     attrbuflen = htonl(attrbuflen);
395     memcpy( rbuf, &attrbuflen, 4);
396     rbuf += 4;
397     *rbuflen += 4;
398
399     /* Only copy buffer if the client asked for it (2nd request, maxreply>0)
400        and we didnt have an error (buf_valid) */
401     if (maxreply && buf_valid) {
402         memcpy( rbuf, attrnamebuf, attrbuflen);
403         *rbuflen += attrbuflen;
404         buf_valid = 0;
405     }
406
407     ret = AFP_OK;
408
409 exit:
410     if (ret != AFP_OK)
411         buf_valid = 0;
412     if (adp)
413         ad_close_metadata( adp);
414
415     LOG(log_debug9, logtype_afpd, "afp_listextattr: END");
416     return ret;
417 }
418
419 int afp_getextattr(AFPObj *obj _U_, char *ibuf, int ibuflen _U_, char *rbuf, int *rbuflen)
420 {
421     int                 ret, oflag = 0;
422     uint16_t            vid, bitmap;
423     uint32_t            did, maxreply, attrnamelen;
424     char                attrmname[256], attruname[256];
425     struct vol          *vol;
426     struct dir          *dir;
427     struct path         *s_path;
428
429
430 #ifdef DEBUG
431     LOG(log_debug9, logtype_afpd, "afp_getextattr: BEGIN");
432 #endif
433
434     *rbuflen = 0;
435     ibuf += 2;
436
437     memcpy( &vid, ibuf, 2);
438     ibuf += 2;
439     if (NULL == ( vol = getvolbyvid( vid )) ) {
440         LOG(log_error, logtype_afpd, "afp_getextattr: getvolbyvid error: %s", strerror(errno));
441         return AFPERR_ACCESS;
442     }
443
444     memcpy( &did, ibuf, 4);
445     ibuf += 4;
446     if (NULL == ( dir = dirlookup( vol, did )) ) {
447         LOG(log_error, logtype_afpd, "afp_getextattr: dirlookup error: %s", strerror(errno));
448         return afp_errno;
449     }
450
451     memcpy( &bitmap, ibuf, 2);
452     bitmap = ntohs( bitmap );
453     ibuf += 2;
454     if (bitmap & kXAttrNoFollow)
455         oflag = AT_SYMLINK_NOFOLLOW;
456
457     /* Skip Offset and ReqCount */
458     ibuf += 16;
459
460     /* Get MaxReply */
461     memcpy(&maxreply, ibuf, 4);
462     maxreply = ntohl(maxreply);
463     ibuf += 4;
464
465     /* get name */
466     if (NULL == ( s_path = cname( vol, dir, &ibuf )) ) {
467         LOG(log_error, logtype_afpd, "afp_getextattr: cname error: %s", strerror(errno));
468         return AFPERR_NOOBJ;
469     }
470
471     if ((unsigned long)ibuf & 1)
472         ibuf++;
473
474     /* get length of EA name */
475     memcpy(&attrnamelen, ibuf, 2);
476     attrnamelen = ntohs(attrnamelen);
477     ibuf += 2;
478     if (attrnamelen > 255)
479         /* dont fool with us */
480         attrnamelen = 255;
481
482     /* we must copy the name as its not 0-terminated and I DONT WANT TO WRITE to ibuf */
483     strncpy(attrmname, ibuf, attrnamelen);
484     attrmname[attrnamelen] = 0;
485
486     LOG(log_debug, logtype_afpd, "afp_getextattr(%s): EA: %s", s_path->u_name, attrmname);
487
488     /* Convert EA name in utf8 to unix charset */
489     if ( 0 >= ( attrnamelen = convert_string(CH_UTF8_MAC, obj->options.unixcharset,attrmname, attrnamelen, attruname, 255)) )
490         return AFPERR_MISC;
491
492     if (attrnamelen == 255)
493         /* convert_string didn't 0-terminate */
494         attruname[255] = 0;
495     
496     /* write bitmap now */
497     bitmap = htons(bitmap);
498     memcpy(rbuf, &bitmap, 2);
499     rbuf += 2;
500     *rbuflen += 2;
501     
502     /*
503       Switch on maxreply:
504       if its 0 we must return the size of the requested attribute,
505       if its non 0 we must return the attribute.
506     */
507     if (maxreply == 0)
508         ret = getextattr_size(rbuf, rbuflen, s_path->u_name, oflag, attruname);
509     else
510         ret = getextattr_content(rbuf, rbuflen, s_path->u_name, oflag, attruname, maxreply);
511
512 #ifdef DEBUG
513     LOG(log_debug9, logtype_afpd, "afp_getextattr: END");
514 #endif
515
516     return ret;
517 }
518
519 int afp_setextattr(AFPObj *obj _U_, char *ibuf, int ibuflen _U_, char *rbuf, int *rbuflen)
520 {
521     int                 len, oflag = O_CREAT | O_WRONLY, attrdirfd;
522     uint16_t            vid, bitmap;
523     uint32_t            did, attrnamelen, attrsize;
524     char                attrmname[256], attruname[256];
525     struct vol          *vol;
526     struct dir          *dir;
527     struct path         *s_path;
528
529 #ifdef DEBUG
530     LOG(log_debug9, logtype_afpd, "afp_setextattr: BEGIN");
531 #endif
532
533     *rbuflen = 0;
534     ibuf += 2;
535
536     memcpy( &vid, ibuf, 2);
537     ibuf += 2;
538     if (NULL == ( vol = getvolbyvid( vid )) ) {
539         LOG(log_error, logtype_afpd, "afp_setextattr: getvolbyvid error: %s", strerror(errno));
540         return AFPERR_ACCESS;
541     }
542
543     memcpy( &did, ibuf, 4);
544     ibuf += 4;
545     if (NULL == ( dir = dirlookup( vol, did )) ) {
546         LOG(log_error, logtype_afpd, "afp_setextattr: dirlookup error: %s", strerror(errno));
547         return afp_errno;
548     }
549
550     memcpy( &bitmap, ibuf, 2);
551     bitmap = ntohs( bitmap );
552     ibuf += 2;
553     if (bitmap & kXAttrNoFollow)
554         oflag |= AT_SYMLINK_NOFOLLOW;
555     if (bitmap & kXAttrCreate)
556         oflag |= O_EXCL;
557     else if (bitmap & kXAttrReplace)
558         oflag |= O_TRUNC;
559
560     /* Skip Offset */
561     ibuf += 8;
562
563     /* get name */
564     if (NULL == ( s_path = cname( vol, dir, &ibuf )) ) {
565         LOG(log_error, logtype_afpd, "afp_setextattr: cname error: %s", strerror(errno));
566         return AFPERR_NOOBJ;
567     }
568
569     if ((unsigned long)ibuf & 1)
570         ibuf++;
571
572     /* get length of EA name */
573     memcpy(&attrnamelen, ibuf, 2);
574     attrnamelen = ntohs(attrnamelen);
575     ibuf += 2;
576     if (attrnamelen > 255)
577         return AFPERR_PARAM;
578
579     /* we must copy the name as its not 0-terminated and we cant write to ibuf */
580     strncpy(attrmname, ibuf, attrnamelen);
581     attrmname[attrnamelen] = 0;
582     ibuf += attrnamelen;
583
584     /* Convert EA name in utf8 to unix charset */
585     if ( 0 >= ( attrnamelen = convert_string(CH_UTF8_MAC, obj->options.unixcharset,attrmname, attrnamelen, attruname, 255)) )
586         return AFPERR_MISC;
587
588     if (attrnamelen == 255)
589         /* convert_string didn't 0-terminate */
590         attruname[255] = 0;
591
592     /* get EA size */
593     memcpy(&attrsize, ibuf, 4);
594     attrsize = ntohl(attrsize);
595     ibuf += 4;
596     if (attrsize > MAX_EA_SIZE)
597         /* we arbitrarily make this fatal */
598         return AFPERR_PARAM;
599     
600     LOG(log_debug, logtype_afpd, "afp_setextattr(%s): EA: %s, size: %u", s_path->u_name, attrmname, attrsize);
601
602     if ( -1 == (attrdirfd = attropen(s_path->u_name, attruname, oflag, 0666))) {
603         if (errno == ELOOP) {
604             /* its a symlink and client requested O_NOFOLLOW  */
605             LOG(log_debug, logtype_afpd, "afp_setextattr(%s): encountered symlink with kXAttrNoFollow", s_path->u_name);
606             return AFP_OK;
607         }
608         LOG(log_error, logtype_afpd, "afp_setextattr(%s): attropen error: %s", s_path->u_name, strerror(errno));
609         return AFPERR_MISC;
610     }
611     
612     while (1) {
613         len = write(attrdirfd, ibuf, attrsize);
614         if (len == -1) {
615             LOG(log_error, logtype_afpd, "afp_setextattr(%s): read error: %s", attruname, strerror(errno));
616             return AFPERR_MISC;
617         }
618         attrsize -= len;
619         ibuf += len;
620         if (attrsize == 0)
621             break;
622     }
623
624 #ifdef DEBUG
625     LOG(log_debug9, logtype_afpd, "afp_setextattr: END");
626 #endif
627
628     return AFP_OK;
629 }
630
631 int afp_remextattr(AFPObj *obj _U_, char *ibuf, int ibuflen _U_, char *rbuf, int *rbuflen)
632 {
633     int                 oflag = O_RDONLY, attrdirfd;
634     uint16_t            vid, bitmap;
635     uint32_t            did, attrnamelen;
636     char                attrmname[256], attruname[256];
637     struct vol          *vol;
638     struct dir          *dir;
639     struct path         *s_path;
640
641 #ifdef DEBUG
642     LOG(log_debug9, logtype_afpd, "afp_remextattr: BEGIN");
643 #endif
644
645     *rbuflen = 0;
646     ibuf += 2;
647
648     memcpy( &vid, ibuf, 2);
649     ibuf += 2;
650     if (NULL == ( vol = getvolbyvid( vid )) ) {
651         LOG(log_error, logtype_afpd, "afp_remextattr: getvolbyvid error: %s", strerror(errno));
652         return AFPERR_ACCESS;
653     }
654
655     memcpy( &did, ibuf, 4);
656     ibuf += 4;
657     if (NULL == ( dir = dirlookup( vol, did )) ) {
658         LOG(log_error, logtype_afpd, "afp_remextattr: dirlookup error: %s", strerror(errno));
659         return afp_errno;
660     }
661
662     memcpy( &bitmap, ibuf, 2);
663     bitmap = ntohs( bitmap );
664     ibuf += 2;
665     if (bitmap & kXAttrNoFollow)
666         oflag |= AT_SYMLINK_NOFOLLOW;
667
668     /* get name */
669     if (NULL == ( s_path = cname( vol, dir, &ibuf )) ) {
670         LOG(log_error, logtype_afpd, "afp_setextattr: cname error: %s", strerror(errno));
671         return AFPERR_NOOBJ;
672     }
673
674     if ((unsigned long)ibuf & 1)
675         ibuf++;
676
677     /* get length of EA name */
678     memcpy(&attrnamelen, ibuf, 2);
679     attrnamelen = ntohs(attrnamelen);
680     ibuf += 2;
681     if (attrnamelen > 255)
682         return AFPERR_PARAM;
683
684     /* we must copy the name as its not 0-terminated and we cant write to ibuf */
685     strncpy(attrmname, ibuf, attrnamelen);
686     attrmname[attrnamelen] = 0;
687     ibuf += attrnamelen;
688
689     /* Convert EA name in utf8 to unix charset */
690     if ( 0 >= ( attrnamelen = convert_string(CH_UTF8_MAC, obj->options.unixcharset,attrmname, attrnamelen, attruname, 255)) )
691         return AFPERR_MISC;
692
693     if (attrnamelen == 255)
694         /* convert_string didn't 0-terminate */
695         attruname[255] = 0;
696
697     LOG(log_debug, logtype_afpd, "afp_remextattr(%s): EA: %s", s_path->u_name, attrmname);
698
699     if ( -1 == (attrdirfd = attropen(s_path->u_name, ".", oflag))) {
700         switch (errno) {
701         case ELOOP:
702             /* its a symlink and client requested O_NOFOLLOW  */
703             LOG(log_debug, logtype_afpd, "afp_remextattr(%s): encountered symlink with kXAttrNoFollow", s_path->u_name);
704             return AFP_OK;
705         case EACCES:
706             LOG(log_debug, logtype_afpd, "afp_remextattr(%s): unlinkat error: %s", s_path->u_name, strerror(errno));
707             return AFPERR_ACCESS;
708         default:
709             LOG(log_error, logtype_afpd, "afp_remextattr(%s): attropen error: %s", s_path->u_name, strerror(errno));
710             return AFPERR_MISC;
711         }
712     }
713     
714     if ( -1 == (unlinkat(attrdirfd, attruname, 0)) ) {
715         if (errno == EACCES) {
716             LOG(log_debug, logtype_afpd, "afp_remextattr(%s): unlinkat error: %s", s_path->u_name, strerror(errno));
717             return AFPERR_ACCESS;
718         }
719         LOG(log_error, logtype_afpd, "afp_remextattr(%s): unlinkat error: %s", s_path->u_name, strerror(errno));
720         return AFPERR_MISC;
721     }
722
723 #ifdef DEBUG
724     LOG(log_debug9, logtype_afpd, "afp_remextattr: END");
725 #endif
726
727     return AFP_OK;
728 }
729
730
731
732 #endif /* HAVE_EXT_ATTRS */
733