1/*
2  $Id: extattrs.c,v 1.29 2010-01-05 12:06:33 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#ifdef HAVE_CONFIG_H
17#include "config.h"
18#endif /* HAVE_CONFIG_H */
19
20#include <unistd.h>
21#include <errno.h>
22#include <stdlib.h>
23#include <string.h>
24
25#include <atalk/adouble.h>
26#include <atalk/util.h>
27#include <atalk/vfs.h>
28#include <atalk/afp.h>
29#include <atalk/logger.h>
30#include <atalk/ea.h>
31#include <atalk/globals.h>
32
33#include "volume.h"
34#include "desktop.h"
35#include "directory.h"
36#include "fork.h"
37#include "extattrs.h"
38
39static const char *ea_finderinfo = "com.apple.FinderInfo";
40static const char *ea_resourcefork = "com.apple.ResourceFork";
41
42/* This should be big enough to consecutively store the names of all attributes */
43static char attrnamebuf[ATTRNAMEBUFSIZ];
44
45#ifdef DEBUG
46static void hexdump(void *m, size_t l) {
47    char *p = m;
48    int count = 0, len;
49    char buf[100];
50    char *bufp = buf;
51
52    while (l--) {
53        len = sprintf(bufp, "%02x ", *p++);
54        bufp += len;
55        count++;
56
57        if ((count % 16) == 0) {
58            LOG(log_debug9, logtype_afpd, "%s", buf);
59            bufp = buf;
60        }
61    }
62}
63#endif
64
65/***************************************
66 * AFP funcs
67 ****************************************/
68
69/*
70  Note: we're being called twice. Firstly the client only want the size of all
71  EA names, secondly it wants these names. In order to avoid scanning EAs twice
72  we cache them in a static buffer.
73*/
74int afp_listextattr(AFPObj *obj _U_, char *ibuf, size_t ibuflen _U_, char *rbuf, size_t *rbuflen)
75{
76    int                 ret, oflag = 0, adflags = 0;
77    uint16_t            vid, bitmap, uint16;
78    uint32_t            did, maxreply, tmpattr;
79    struct vol          *vol;
80    struct dir          *dir;
81    struct path         *s_path;
82    struct stat         *st;
83    struct adouble      ad, *adp = NULL;
84    char                *uname, *FinderInfo;
85    char                emptyFinderInfo[32] = { 0 };
86
87    static int          buf_valid = 0;
88    static size_t       attrbuflen = 0;
89
90    *rbuflen = 0;
91    ibuf += 2;
92
93    /* Get Bitmap and MaxReplySize first */
94    memcpy( &bitmap, ibuf +6, sizeof(bitmap));
95    bitmap = ntohs( bitmap );
96
97    memcpy( &maxreply, ibuf + 14, sizeof (maxreply));
98    maxreply = ntohl( maxreply );
99
100    /*
101      If its the first request with maxreply=0 or if we didn't mark our buffers valid for
102      whatever reason (just a safety check, it should be valid), then scan for attributes
103    */
104    if ((maxreply == 0) || (buf_valid == 0)) {
105
106        attrbuflen = 0;
107
108        memcpy( &vid, ibuf, sizeof(vid));
109        ibuf += sizeof(vid);
110        if (NULL == ( vol = getvolbyvid( vid )) ) {
111            LOG(log_debug, logtype_afpd, "afp_listextattr: getvolbyvid error: %s", strerror(errno));
112            return AFPERR_ACCESS;
113        }
114
115        memcpy( &did, ibuf, sizeof(did));
116        ibuf += sizeof(did);
117        if (NULL == ( dir = dirlookup( vol, did )) ) {
118            LOG(log_debug, logtype_afpd, "afp_listextattr: dirlookup error: %s", strerror(errno));
119            return afp_errno;
120        }
121
122        if (bitmap & kXAttrNoFollow)
123            oflag = O_NOFOLLOW;
124        /* Skip Bitmap, ReqCount, StartIndex and maxreply*/
125        ibuf += 12;
126
127        /* get name */
128        if (NULL == ( s_path = cname( vol, dir, &ibuf )) ) {
129            LOG(log_debug, logtype_afpd, "afp_listextattr: cname error: %s", strerror(errno));
130            return AFPERR_NOOBJ;
131        }
132
133        st   = &s_path->st;
134        if (!s_path->st_valid) {
135            /* it's a dir in our cache, we didn't stat it, do it now */
136            of_statdir(vol, s_path);
137        }
138        if ( s_path->st_errno != 0 ) {
139            return( AFPERR_NOOBJ );
140        }
141
142        adp = of_ad(vol, s_path, &ad);
143        uname = s_path->u_name;
144        /*
145          We have to check the FinderInfo for the file, because if they aren't all 0
146          we must return the synthetic attribute "com.apple.FinderInfo".
147          Note: the client will never (never seen in traces) request that attribute
148          via FPGetExtAttr !
149        */
150
151        if (S_ISDIR(st->st_mode))
152            adflags = ADFLAGS_DIR;
153
154        if ( ad_metadata( uname, adflags, adp) < 0 ) {
155            switch (errno) {
156            case ENOENT:
157                adp = NULL;
158                break;
159            case EACCES:
160                LOG(log_error, logtype_afpd, "afp_listextattr(%s): %s: check resource fork permission?",
161                    uname, strerror(errno));
162                return AFPERR_ACCESS;
163            default:
164                LOG(log_error, logtype_afpd, "afp_listextattr(%s): error getting metadata: %s", uname, strerror(errno));
165                return AFPERR_MISC;
166            }
167        }
168
169        if (adp) {
170            FinderInfo = ad_entry(adp, ADEID_FINDERI);
171            /* Check if FinderInfo equals default and empty FinderInfo*/
172            if (memcmp(FinderInfo, emptyFinderInfo, 32) != 0) {
173                /* FinderInfo contains some non 0 bytes -> include "com.apple.FinderInfo" */
174                strcpy(attrnamebuf, ea_finderinfo);
175                attrbuflen += strlen(ea_finderinfo) + 1;
176                LOG(log_debug7, logtype_afpd, "afp_listextattr(%s): sending com.apple.FinderInfo", uname);
177            }
178
179            /* Now check for Ressource fork and add virtual EA "com.apple.ResourceFork" if size > 0 */
180            LOG(log_debug7, logtype_afpd, "afp_listextattr(%s): Ressourcefork size: %llu", uname, adp->ad_rlen);
181
182            if (adp->ad_rlen > 0) {
183                LOG(log_debug7, logtype_afpd, "afp_listextattr(%s): sending com.apple.RessourceFork.", uname);
184                strcpy(attrnamebuf + attrbuflen, ea_resourcefork);
185                attrbuflen += strlen(ea_resourcefork) + 1;
186            }
187        }
188
189        ret = vol->vfs->vfs_ea_list(vol, attrnamebuf, &attrbuflen, uname, oflag);
190
191        switch (ret) {
192        case AFPERR_BADTYPE:
193            /* its a symlink and client requested O_NOFOLLOW */
194            LOG(log_debug, logtype_afpd, "afp_listextattr(%s): encountered symlink with kXAttrNoFollow", uname);
195            attrbuflen = 0;
196            buf_valid = 0;
197            ret = AFP_OK;
198            goto exit;
199        case AFPERR_MISC:
200            attrbuflen = 0;
201            goto exit;
202        default:
203            buf_valid = 1;
204        }
205    }
206
207    /* Start building reply packet */
208    bitmap = htons(bitmap);
209    memcpy( rbuf, &bitmap, sizeof(bitmap));
210    rbuf += sizeof(bitmap);
211    *rbuflen += sizeof(bitmap);
212
213    tmpattr = htonl(attrbuflen);
214    memcpy( rbuf, &tmpattr, sizeof(tmpattr));
215    rbuf += sizeof(tmpattr);
216    *rbuflen += sizeof(tmpattr);
217
218    /* Only copy buffer if the client asked for it (2nd request, maxreply>0)
219       and we didnt have an error (buf_valid) */
220    if (maxreply && buf_valid) {
221        memcpy( rbuf, attrnamebuf, attrbuflen);
222        *rbuflen += attrbuflen;
223        buf_valid = 0;
224    }
225
226    ret = AFP_OK;
227
228exit:
229    if (ret != AFP_OK)
230        buf_valid = 0;
231    if (adp)
232        ad_close_metadata( adp);
233
234    return ret;
235}
236
237static char *to_stringz(char *ibuf, uint16_t len)
238{
239static char attrmname[256];
240
241    if (len > 255)
242        /* dont fool with us */
243        len = 255;
244
245    /* we must copy the name as its not 0-terminated and I DONT WANT TO WRITE to ibuf */
246    strlcpy(attrmname, ibuf, len + 1);
247    return attrmname;
248}
249
250int afp_getextattr(AFPObj *obj _U_, char *ibuf, size_t ibuflen _U_, char *rbuf, size_t *rbuflen)
251{
252    int                 ret, oflag = 0;
253    uint16_t            vid, bitmap, attrnamelen;
254    uint32_t            did, maxreply;
255    char                attruname[256];
256    struct vol          *vol;
257    struct dir          *dir;
258    struct path         *s_path;
259
260
261    *rbuflen = 0;
262    ibuf += 2;
263
264    memcpy( &vid, ibuf, sizeof(vid));
265    ibuf += sizeof(vid);
266    if (NULL == ( vol = getvolbyvid( vid )) ) {
267        LOG(log_debug, logtype_afpd, "afp_getextattr: getvolbyvid error: %s", strerror(errno));
268        return AFPERR_ACCESS;
269    }
270
271    memcpy( &did, ibuf, sizeof(did));
272    ibuf += sizeof(did);
273    if (NULL == ( dir = dirlookup( vol, did )) ) {
274        LOG(log_debug, logtype_afpd, "afp_getextattr: dirlookup error: %s", strerror(errno));
275        return afp_errno;
276    }
277
278    memcpy( &bitmap, ibuf, sizeof(bitmap));
279    bitmap = ntohs( bitmap );
280    ibuf += sizeof(bitmap);
281
282    if (bitmap & kXAttrNoFollow)
283        oflag = O_NOFOLLOW;
284
285    /* Skip Offset and ReqCount */
286    ibuf += 16;
287
288    /* Get MaxReply */
289    memcpy(&maxreply, ibuf, sizeof(maxreply));
290    maxreply = ntohl(maxreply);
291    ibuf += sizeof(maxreply);
292
293    /* get name */
294    if (NULL == ( s_path = cname( vol, dir, &ibuf )) ) {
295        LOG(log_debug, logtype_afpd, "afp_getextattr: cname error: %s", strerror(errno));
296        return AFPERR_NOOBJ;
297    }
298
299    if ((unsigned long)ibuf & 1)
300        ibuf++;
301
302    /* get length of EA name */
303    memcpy(&attrnamelen, ibuf, sizeof(attrnamelen));
304    attrnamelen = ntohs(attrnamelen);
305    ibuf += sizeof(attrnamelen);
306
307    LOG(log_debug, logtype_afpd, "afp_getextattr(%s): EA: %s", s_path->u_name, to_stringz(ibuf, attrnamelen));
308
309    /* Convert EA name in utf8 to unix charset */
310    if ( 0 >= convert_string(CH_UTF8_MAC, obj->options.unixcharset, ibuf, attrnamelen, attruname, 256) )
311        return AFPERR_MISC;
312
313    /* write bitmap now */
314    bitmap = htons(bitmap);
315    memcpy(rbuf, &bitmap, sizeof(bitmap));
316    rbuf += sizeof(bitmap);
317    *rbuflen += sizeof(bitmap);
318
319    /*
320      Switch on maxreply:
321      if its 0 we must return the size of the requested attribute,
322      if its non 0 we must return the attribute.
323    */
324    if (maxreply == 0)
325        ret = vol->vfs->vfs_ea_getsize(vol, rbuf, rbuflen, s_path->u_name, oflag, attruname);
326    else
327        ret = vol->vfs->vfs_ea_getcontent(vol, rbuf, rbuflen, s_path->u_name, oflag, attruname, maxreply);
328
329    return ret;
330}
331
332int afp_setextattr(AFPObj *obj _U_, char *ibuf, size_t ibuflen _U_, char *rbuf _U_, size_t *rbuflen)
333{
334    int                 oflag = 0, ret;
335    uint16_t            vid, bitmap, attrnamelen;
336    uint32_t            did, attrsize;
337    char                attruname[256];
338    char		*attrmname;
339    struct vol          *vol;
340    struct dir          *dir;
341    struct path         *s_path;
342
343    *rbuflen = 0;
344    ibuf += 2;
345
346    memcpy( &vid, ibuf, sizeof(vid));
347    ibuf += sizeof(vid);
348    if (NULL == ( vol = getvolbyvid( vid )) ) {
349        LOG(log_debug, logtype_afpd, "afp_setextattr: getvolbyvid error: %s", strerror(errno));
350        return AFPERR_ACCESS;
351    }
352
353    memcpy( &did, ibuf, sizeof(did));
354    ibuf += sizeof(did);
355    if (NULL == ( dir = dirlookup( vol, did )) ) {
356        LOG(log_debug, logtype_afpd, "afp_setextattr: dirlookup error: %s", strerror(errno));
357        return afp_errno;
358    }
359
360    memcpy( &bitmap, ibuf, sizeof(bitmap));
361    bitmap = ntohs( bitmap );
362    ibuf += sizeof(bitmap);
363
364    if (bitmap & kXAttrNoFollow)
365        oflag |= O_NOFOLLOW;
366
367    if (bitmap & kXAttrCreate)
368        oflag |= O_CREAT;
369    else if (bitmap & kXAttrReplace)
370        oflag |= O_TRUNC;
371
372    /* Skip Offset */
373    ibuf += 8;
374
375    /* get name */
376    if (NULL == ( s_path = cname( vol, dir, &ibuf )) ) {
377        LOG(log_debug, logtype_afpd, "afp_setextattr: cname error: %s", strerror(errno));
378        return AFPERR_NOOBJ;
379    }
380
381    if ((unsigned long)ibuf & 1)
382        ibuf++;
383
384    /* get length of EA name */
385    memcpy(&attrnamelen, ibuf, sizeof(attrnamelen));
386    attrnamelen = ntohs(attrnamelen);
387    ibuf += sizeof(attrnamelen);
388    if (attrnamelen > 255)
389        return AFPERR_PARAM;
390
391    attrmname = ibuf;
392    /* Convert EA name in utf8 to unix charset */
393    if ( 0 >= convert_string(CH_UTF8_MAC, obj->options.unixcharset, attrmname, attrnamelen, attruname, 256))
394        return AFPERR_MISC;
395
396    ibuf += attrnamelen;
397    /* get EA size */
398    memcpy(&attrsize, ibuf, sizeof(attrsize));
399    attrsize = ntohl(attrsize);
400    ibuf += sizeof(attrsize);
401    if (attrsize > MAX_EA_SIZE)
402        /* we arbitrarily make this fatal */
403        return AFPERR_PARAM;
404
405    LOG(log_debug, logtype_afpd, "afp_setextattr(%s): EA: %s, size: %u", s_path->u_name, to_stringz(attrmname, attrnamelen), attrsize);
406
407    ret = vol->vfs->vfs_ea_set(vol, s_path->u_name, attruname, ibuf, attrsize, oflag);
408
409    return ret;
410}
411
412int afp_remextattr(AFPObj *obj _U_, char *ibuf, size_t ibuflen _U_, char *rbuf _U_, size_t *rbuflen)
413{
414    int                 oflag = 0, ret;
415    uint16_t            vid, bitmap, attrnamelen;
416    uint32_t            did;
417    char                attruname[256];
418    struct vol          *vol;
419    struct dir          *dir;
420    struct path         *s_path;
421
422    *rbuflen = 0;
423    ibuf += 2;
424
425    memcpy( &vid, ibuf, sizeof(vid));
426    ibuf += sizeof(vid);
427    if (NULL == ( vol = getvolbyvid( vid )) ) {
428        LOG(log_debug, logtype_afpd, "afp_remextattr: getvolbyvid error: %s", strerror(errno));
429        return AFPERR_ACCESS;
430    }
431
432    memcpy( &did, ibuf, sizeof(did));
433    ibuf += sizeof(did);
434    if (NULL == ( dir = dirlookup( vol, did )) ) {
435        LOG(log_debug, logtype_afpd, "afp_remextattr: dirlookup error: %s", strerror(errno));
436        return afp_errno;
437    }
438
439    memcpy( &bitmap, ibuf, sizeof(bitmap));
440    bitmap = ntohs( bitmap );
441    ibuf += sizeof(bitmap);
442
443    if (bitmap & kXAttrNoFollow)
444        oflag |= O_NOFOLLOW;
445
446    /* get name */
447    if (NULL == ( s_path = cname( vol, dir, &ibuf )) ) {
448        LOG(log_debug, logtype_afpd, "afp_setextattr: cname error: %s", strerror(errno));
449        return AFPERR_NOOBJ;
450    }
451
452    if ((unsigned long)ibuf & 1)
453        ibuf++;
454
455    /* get length of EA name */
456    memcpy(&attrnamelen, ibuf, sizeof(attrnamelen));
457    attrnamelen = ntohs(attrnamelen);
458    ibuf += sizeof(attrnamelen);
459    if (attrnamelen > 255)
460        return AFPERR_PARAM;
461
462    /* Convert EA name in utf8 to unix charset */
463    if ( 0 >= (convert_string(CH_UTF8_MAC, obj->options.unixcharset,ibuf, attrnamelen, attruname, 256)) )
464        return AFPERR_MISC;
465
466    LOG(log_debug, logtype_afpd, "afp_remextattr(%s): EA: %s", s_path->u_name, to_stringz(ibuf, attrnamelen));
467
468    ret = vol->vfs->vfs_ea_remove(vol, s_path->u_name, attruname, oflag);
469
470    return ret;
471}
472
473