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
172#ifdef DEBUG
173            LOG(log_maxdebug, logtype_afpd, "afp_listextattr(%s): FinderInfo:", uname);
174            hexdump( FinderInfo, 32);
175#endif
176
177            if ((adflags & ADFLAGS_DIR)) {
178                /* set default view */
179                uint16 = htons(FINDERINFO_CLOSEDVIEW);
180                memcpy(emptyFinderInfo + FINDERINFO_FRVIEWOFF, &uint16, 2);
181            }
182
183            /* Check if FinderInfo equals default and empty FinderInfo*/
184            if (memcmp(FinderInfo, emptyFinderInfo, 32) != 0) {
185                /* FinderInfo contains some non 0 bytes -> include "com.apple.FinderInfo" */
186                strcpy(attrnamebuf, ea_finderinfo);
187                attrbuflen += strlen(ea_finderinfo) + 1;
188                LOG(log_debug7, logtype_afpd, "afp_listextattr(%s): sending com.apple.FinderInfo", uname);
189            }
190
191            /* Now check for Ressource fork and add virtual EA "com.apple.ResourceFork" if size > 0 */
192            LOG(log_debug7, logtype_afpd, "afp_listextattr(%s): Ressourcefork size: %llu", uname, adp->ad_rlen);
193
194            if (adp->ad_rlen > 0) {
195                LOG(log_debug7, logtype_afpd, "afp_listextattr(%s): sending com.apple.RessourceFork.", uname);
196                strcpy(attrnamebuf + attrbuflen, ea_resourcefork);
197                attrbuflen += strlen(ea_resourcefork) + 1;
198            }
199        }
200
201        ret = vol->vfs->vfs_ea_list(vol, attrnamebuf, &attrbuflen, uname, oflag);
202
203        switch (ret) {
204        case AFPERR_BADTYPE:
205            /* its a symlink and client requested O_NOFOLLOW */
206            LOG(log_debug, logtype_afpd, "afp_listextattr(%s): encountered symlink with kXAttrNoFollow", uname);
207            attrbuflen = 0;
208            buf_valid = 0;
209            ret = AFP_OK;
210            goto exit;
211        case AFPERR_MISC:
212            attrbuflen = 0;
213            goto exit;
214        default:
215            buf_valid = 1;
216        }
217    }
218
219    /* Start building reply packet */
220    bitmap = htons(bitmap);
221    memcpy( rbuf, &bitmap, sizeof(bitmap));
222    rbuf += sizeof(bitmap);
223    *rbuflen += sizeof(bitmap);
224
225    tmpattr = htonl(attrbuflen);
226    memcpy( rbuf, &tmpattr, sizeof(tmpattr));
227    rbuf += sizeof(tmpattr);
228    *rbuflen += sizeof(tmpattr);
229
230    /* Only copy buffer if the client asked for it (2nd request, maxreply>0)
231       and we didnt have an error (buf_valid) */
232    if (maxreply && buf_valid) {
233        memcpy( rbuf, attrnamebuf, attrbuflen);
234        *rbuflen += attrbuflen;
235        buf_valid = 0;
236    }
237
238    ret = AFP_OK;
239
240exit:
241    if (ret != AFP_OK)
242        buf_valid = 0;
243    if (adp)
244        ad_close_metadata( adp);
245
246    return ret;
247}
248
249static char *to_stringz(char *ibuf, uint16_t len)
250{
251static char attrmname[256];
252
253    if (len > 255)
254        /* dont fool with us */
255        len = 255;
256
257    /* we must copy the name as its not 0-terminated and I DONT WANT TO WRITE to ibuf */
258    strlcpy(attrmname, ibuf, len + 1);
259    return attrmname;
260}
261
262int afp_getextattr(AFPObj *obj _U_, char *ibuf, size_t ibuflen _U_, char *rbuf, size_t *rbuflen)
263{
264    int                 ret, oflag = 0;
265    uint16_t            vid, bitmap, attrnamelen;
266    uint32_t            did, maxreply;
267    char                attruname[256];
268    struct vol          *vol;
269    struct dir          *dir;
270    struct path         *s_path;
271
272
273    *rbuflen = 0;
274    ibuf += 2;
275
276    memcpy( &vid, ibuf, sizeof(vid));
277    ibuf += sizeof(vid);
278    if (NULL == ( vol = getvolbyvid( vid )) ) {
279        LOG(log_debug, logtype_afpd, "afp_getextattr: getvolbyvid error: %s", strerror(errno));
280        return AFPERR_ACCESS;
281    }
282
283    memcpy( &did, ibuf, sizeof(did));
284    ibuf += sizeof(did);
285    if (NULL == ( dir = dirlookup( vol, did )) ) {
286        LOG(log_debug, logtype_afpd, "afp_getextattr: dirlookup error: %s", strerror(errno));
287        return afp_errno;
288    }
289
290    memcpy( &bitmap, ibuf, sizeof(bitmap));
291    bitmap = ntohs( bitmap );
292    ibuf += sizeof(bitmap);
293
294    if (bitmap & kXAttrNoFollow)
295        oflag = O_NOFOLLOW;
296
297    /* Skip Offset and ReqCount */
298    ibuf += 16;
299
300    /* Get MaxReply */
301    memcpy(&maxreply, ibuf, sizeof(maxreply));
302    maxreply = ntohl(maxreply);
303    ibuf += sizeof(maxreply);
304
305    /* get name */
306    if (NULL == ( s_path = cname( vol, dir, &ibuf )) ) {
307        LOG(log_debug, logtype_afpd, "afp_getextattr: cname error: %s", strerror(errno));
308        return AFPERR_NOOBJ;
309    }
310
311    if ((unsigned long)ibuf & 1)
312        ibuf++;
313
314    /* get length of EA name */
315    memcpy(&attrnamelen, ibuf, sizeof(attrnamelen));
316    attrnamelen = ntohs(attrnamelen);
317    ibuf += sizeof(attrnamelen);
318
319    LOG(log_debug, logtype_afpd, "afp_getextattr(%s): EA: %s", s_path->u_name, to_stringz(ibuf, attrnamelen));
320
321    /* Convert EA name in utf8 to unix charset */
322    if ( 0 >= convert_string(CH_UTF8_MAC, obj->options.unixcharset, ibuf, attrnamelen, attruname, 256) )
323        return AFPERR_MISC;
324
325    /* write bitmap now */
326    bitmap = htons(bitmap);
327    memcpy(rbuf, &bitmap, sizeof(bitmap));
328    rbuf += sizeof(bitmap);
329    *rbuflen += sizeof(bitmap);
330
331    /*
332      Switch on maxreply:
333      if its 0 we must return the size of the requested attribute,
334      if its non 0 we must return the attribute.
335    */
336    if (maxreply == 0)
337        ret = vol->vfs->vfs_ea_getsize(vol, rbuf, rbuflen, s_path->u_name, oflag, attruname);
338    else
339        ret = vol->vfs->vfs_ea_getcontent(vol, rbuf, rbuflen, s_path->u_name, oflag, attruname, maxreply);
340
341    return ret;
342}
343
344int afp_setextattr(AFPObj *obj _U_, char *ibuf, size_t ibuflen _U_, char *rbuf _U_, size_t *rbuflen)
345{
346    int                 oflag = 0, ret;
347    uint16_t            vid, bitmap, attrnamelen;
348    uint32_t            did, attrsize;
349    char                attruname[256];
350    char		*attrmname;
351    struct vol          *vol;
352    struct dir          *dir;
353    struct path         *s_path;
354
355    *rbuflen = 0;
356    ibuf += 2;
357
358    memcpy( &vid, ibuf, sizeof(vid));
359    ibuf += sizeof(vid);
360    if (NULL == ( vol = getvolbyvid( vid )) ) {
361        LOG(log_debug, logtype_afpd, "afp_setextattr: getvolbyvid error: %s", strerror(errno));
362        return AFPERR_ACCESS;
363    }
364
365    memcpy( &did, ibuf, sizeof(did));
366    ibuf += sizeof(did);
367    if (NULL == ( dir = dirlookup( vol, did )) ) {
368        LOG(log_debug, logtype_afpd, "afp_setextattr: dirlookup error: %s", strerror(errno));
369        return afp_errno;
370    }
371
372    memcpy( &bitmap, ibuf, sizeof(bitmap));
373    bitmap = ntohs( bitmap );
374    ibuf += sizeof(bitmap);
375
376    if (bitmap & kXAttrNoFollow)
377        oflag |= O_NOFOLLOW;
378
379    if (bitmap & kXAttrCreate)
380        oflag |= O_CREAT;
381    else if (bitmap & kXAttrReplace)
382        oflag |= O_TRUNC;
383
384    /* Skip Offset */
385    ibuf += 8;
386
387    /* get name */
388    if (NULL == ( s_path = cname( vol, dir, &ibuf )) ) {
389        LOG(log_debug, logtype_afpd, "afp_setextattr: cname error: %s", strerror(errno));
390        return AFPERR_NOOBJ;
391    }
392
393    if ((unsigned long)ibuf & 1)
394        ibuf++;
395
396    /* get length of EA name */
397    memcpy(&attrnamelen, ibuf, sizeof(attrnamelen));
398    attrnamelen = ntohs(attrnamelen);
399    ibuf += sizeof(attrnamelen);
400    if (attrnamelen > 255)
401        return AFPERR_PARAM;
402
403    attrmname = ibuf;
404    /* Convert EA name in utf8 to unix charset */
405    if ( 0 >= convert_string(CH_UTF8_MAC, obj->options.unixcharset, attrmname, attrnamelen, attruname, 256))
406        return AFPERR_MISC;
407
408    ibuf += attrnamelen;
409    /* get EA size */
410    memcpy(&attrsize, ibuf, sizeof(attrsize));
411    attrsize = ntohl(attrsize);
412    ibuf += sizeof(attrsize);
413    if (attrsize > MAX_EA_SIZE)
414        /* we arbitrarily make this fatal */
415        return AFPERR_PARAM;
416
417    LOG(log_debug, logtype_afpd, "afp_setextattr(%s): EA: %s, size: %u", s_path->u_name, to_stringz(attrmname, attrnamelen), attrsize);
418
419    ret = vol->vfs->vfs_ea_set(vol, s_path->u_name, attruname, ibuf, attrsize, oflag);
420
421    return ret;
422}
423
424int afp_remextattr(AFPObj *obj _U_, char *ibuf, size_t ibuflen _U_, char *rbuf _U_, size_t *rbuflen)
425{
426    int                 oflag = 0, ret;
427    uint16_t            vid, bitmap, attrnamelen;
428    uint32_t            did;
429    char                attruname[256];
430    struct vol          *vol;
431    struct dir          *dir;
432    struct path         *s_path;
433
434    *rbuflen = 0;
435    ibuf += 2;
436
437    memcpy( &vid, ibuf, sizeof(vid));
438    ibuf += sizeof(vid);
439    if (NULL == ( vol = getvolbyvid( vid )) ) {
440        LOG(log_debug, logtype_afpd, "afp_remextattr: getvolbyvid error: %s", strerror(errno));
441        return AFPERR_ACCESS;
442    }
443
444    memcpy( &did, ibuf, sizeof(did));
445    ibuf += sizeof(did);
446    if (NULL == ( dir = dirlookup( vol, did )) ) {
447        LOG(log_debug, logtype_afpd, "afp_remextattr: dirlookup error: %s", strerror(errno));
448        return afp_errno;
449    }
450
451    memcpy( &bitmap, ibuf, sizeof(bitmap));
452    bitmap = ntohs( bitmap );
453    ibuf += sizeof(bitmap);
454
455    if (bitmap & kXAttrNoFollow)
456        oflag |= O_NOFOLLOW;
457
458    /* get name */
459    if (NULL == ( s_path = cname( vol, dir, &ibuf )) ) {
460        LOG(log_debug, logtype_afpd, "afp_setextattr: cname error: %s", strerror(errno));
461        return AFPERR_NOOBJ;
462    }
463
464    if ((unsigned long)ibuf & 1)
465        ibuf++;
466
467    /* get length of EA name */
468    memcpy(&attrnamelen, ibuf, sizeof(attrnamelen));
469    attrnamelen = ntohs(attrnamelen);
470    ibuf += sizeof(attrnamelen);
471    if (attrnamelen > 255)
472        return AFPERR_PARAM;
473
474    /* Convert EA name in utf8 to unix charset */
475    if ( 0 >= (convert_string(CH_UTF8_MAC, obj->options.unixcharset,ibuf, attrnamelen, attruname, 256)) )
476        return AFPERR_MISC;
477
478    LOG(log_debug, logtype_afpd, "afp_remextattr(%s): EA: %s", s_path->u_name, to_stringz(ibuf, attrnamelen));
479
480    ret = vol->vfs->vfs_ea_remove(vol, s_path->u_name, attruname, oflag);
481
482    return ret;
483}
484
485