]> arthur.barton.de Git - netatalk.git/blob - etc/afpd/status.c
e661538d815078000c961f562ca36db5c01c7358
[netatalk.git] / etc / afpd / status.c
1 /*
2  * $Id: status.c,v 1.26 2009-10-29 11:35:58 didg Exp $
3  *
4  * Copyright (c) 1990,1993 Regents of The University of Michigan.
5  * All Rights Reserved.  See COPYRIGHT.
6  */
7
8 #ifdef HAVE_CONFIG_H
9 #include "config.h"
10 #endif /* HAVE_CONFIG_H */
11
12 #include <stdio.h>
13 #include <stdlib.h>
14 #include <unistd.h>
15 #include <string.h>
16 #include <sys/types.h>
17 #include <atalk/logger.h>
18
19 #ifdef BSD4_4
20 #include <sys/param.h>
21 #ifndef HAVE_GETHOSTID
22 #include <sys/sysctl.h>
23 #endif /* HAVE_GETHOSTID */
24 #endif /* BSD4_4 */
25
26 #include <netatalk/at.h>
27 #include <netatalk/endian.h>
28 #include <atalk/dsi.h>
29 #include <atalk/atp.h>
30 #include <atalk/asp.h>
31 #include <atalk/nbp.h>
32 #include <atalk/unicode.h>
33
34 #include "globals.h"  /* includes <netdb.h> */
35 #include "status.h"
36 #include "afp_config.h"
37 #include "icon.h"
38
39 static   size_t maxstatuslen = 0;
40
41 static void status_flags(char *data, const int notif, const int ipok,
42                          const unsigned char passwdbits, const int dirsrvcs _U_, int flags)
43 {
44     u_int16_t           status;
45
46     status = AFPSRVRINFO_COPY;
47     if (passwdbits & PASSWD_SET) /* some uams may not allow this. */
48         status |= AFPSRVRINFO_PASSWD;
49     if (passwdbits & PASSWD_NOSAVE)
50         status |= AFPSRVRINFO_NOSAVEPASSWD;
51     status |= AFPSRVRINFO_SRVSIGNATURE;
52     /* only advertise tcp/ip if we have a valid address */
53     if (ipok)
54         status |= AFPSRVRINFO_TCPIP;
55     status |= AFPSRVRINFO_SRVMSGS;
56     /* Allow the user to decide if we should support server notifications.
57      * With this turned off, the clients will poll for directory changes every
58      * 10 seconds.  This might be too costly to network resources, so make
59      * this an optional thing.  Default will be to _not_ support server
60      * notifications. */
61     if (notif) {
62         status |= AFPSRVRINFO_SRVNOTIFY;
63     }
64     status |= AFPSRVRINFO_FASTBOZO;
65     status |= AFPSRVRINFO_SRVRDIR; /* AFP 3.1 specs says we need to specify this, but may set the count to 0 */
66     /* We don't set the UTF8 name flag here, we don't know whether we have enough space ... */
67
68     if (flags & OPTION_UUID)    /* 05122008 FIXME: can we set AFPSRVRINFO_UUID here ? see AFPSRVRINFO_SRVRDIR*/
69         status |= AFPSRVRINFO_UUID;
70
71     status = htons(status);
72     memcpy(data + AFPSTATUS_FLAGOFF, &status, sizeof(status));
73 }
74
75 static int status_server(char *data, const char *server, const struct afp_options *options)
76 {
77     char                *start = data;
78     char                *Obj, *Type, *Zone;
79     char                buf[32];
80     u_int16_t           status;
81     size_t              len;
82
83     /* make room for all offsets before server name */
84     data += AFPSTATUS_PRELEN;
85
86     /* extract the obj part of the server */
87     Obj = (char *) server;
88     nbp_name(server, &Obj, &Type, &Zone);
89     if ((size_t)-1 == (len = convert_string( 
90                         options->unixcharset, options->maccharset, 
91                         Obj, -1, buf, sizeof(buf))) ) {
92         len = MIN(strlen(Obj), 31);
93         *data++ = len;
94         memcpy( data, Obj, len );
95         LOG ( log_error, logtype_afpd, "Could not set servername, using fallback");
96     } else {
97         *data++ = len;
98         memcpy( data, buf, len );
99     }
100     if ((len + 1) & 1) /* pad server name and length byte to even boundary */
101         len++;
102     data += len;
103
104     /* make room for signature and net address offset. save location of
105      * signature offset. we're also making room for directory names offset
106      * and the utf-8 server name offset.
107      *
108      * NOTE: technically, we don't need to reserve space for the
109      * signature and net address offsets if they're not going to be
110      * used. as there are no offsets after them, it doesn't hurt to
111      * have them specified though. so, we just do that to simplify
112      * things.  
113      *
114      * NOTE2: AFP3.1 Documentation states that the directory names offset
115      * is a required feature, even though it can be set to zero.
116      */
117     len = data - start;
118     status = htons(len + AFPSTATUS_POSTLEN);
119     memcpy(start + AFPSTATUS_MACHOFF, &status, sizeof(status));
120     return len; /* return the offset to beginning of signature offset */
121 }
122
123 static void status_machine(char *data)
124 {
125     char                *start = data;
126     u_int16_t           status;
127     int                 len;
128 #ifdef AFS
129     const char          *machine = "afs";
130 #else /* !AFS */
131     const char          *machine = "Netatalk";
132 #endif /* AFS */
133
134     memcpy(&status, start + AFPSTATUS_MACHOFF, sizeof(status));
135     data += ntohs( status );
136     len = strlen( machine );
137     *data++ = len;
138     memcpy( data, machine, len );
139     data += len;
140     status = htons(data - start);
141     memcpy(start + AFPSTATUS_VERSOFF, &status, sizeof(status));
142 }
143
144 /* -------------------------------- 
145  * it turns out that a server signature screws up separate
146  * servers running on the same machine. to work around that, 
147  * i add in an increment.
148  * Not great, server signature are config dependent but well.
149  */
150  
151 static int           Id = 0;
152
153 /* server signature is a 16-byte quantity */
154 static u_int16_t status_signature(char *data, int *servoffset, DSI *dsi,
155                                   const struct afp_options *options)
156 {
157     char                 *status;
158     char                 *usersign;
159     int                  i;
160     u_int16_t            offset, sigoff;
161     long                 hostid;
162 #ifdef BSD4_4
163     int                  mib[2];
164     size_t               len;
165 #endif /* BSD4_4 */
166
167     status = data;
168
169     /* get server signature offset */
170     memcpy(&offset, data + *servoffset, sizeof(offset));
171     sigoff = offset = ntohs(offset);
172
173     /* jump to server signature offset */
174     data += offset;
175
176     /* Signature type is user string */
177     if (strncmp(options->signature, "user", 4) == 0) {
178         if (strlen(options->signature) <= 5) {
179             LOG(log_warning, logtype_afpd, "Signature %s id not valid. Switching back to hostid.",
180                             options->signature);
181             goto server_signature_hostid;
182     }
183     usersign = options->signature + 5;
184         if (strlen(usersign) < 3) 
185             LOG(log_warning, logtype_afpd, "Signature %s is very short !", 
186                             options->signature);
187     
188         memset(data, 0, 16);
189         strncpy(data, usersign, 16);
190         data += 16;
191         goto server_signature_done;
192     } /* signature = user */
193
194     /* If signature type is a standard hostid... */
195 server_signature_hostid:
196     /* 16-byte signature consists of copies of the hostid */
197 #if defined(BSD4_4) && !defined(HAVE_GETHOSTID)
198     mib[0] = CTL_KERN;
199     mib[1] = KERN_HOSTID;
200     len = sizeof(hostid);
201     sysctl(mib, 2, &hostid, &len, NULL, 0);
202 #else /* BSD4_4 && !HAVE_GETHOSTID */
203     hostid = gethostid();
204 #endif /* BSD4_4 && !HAVE_GETHOSTID */
205     if (!hostid) {
206         if (dsi)
207             hostid = dsi->server.sin_addr.s_addr;
208         else {
209             struct hostent *host;
210
211             if ((host = gethostbyname(options->hostname)))
212                 hostid = ((struct in_addr *) host->h_addr)->s_addr;
213         }
214     }
215
216     /* it turns out that a server signature screws up separate
217      * servers running on the same machine. to work around that, 
218      * i add in an increment */
219     hostid += Id;
220     Id++;
221     for (i = 0; i < 16; i += sizeof(hostid)) {
222         memcpy(data, &hostid, sizeof(hostid));
223         data += sizeof(hostid);
224      }
225
226 server_signature_done:
227     /* calculate net address offset */
228     *servoffset += sizeof(offset);
229     offset = htons(data - status);
230     memcpy(status + *servoffset, &offset, sizeof(offset));
231     return sigoff;
232 }
233
234 static size_t status_netaddress(char *data, int *servoffset,
235                              const ASP asp, const DSI *dsi,
236                              const struct afp_options *options)
237 {
238     char               *begin;
239     u_int16_t          offset;
240     size_t             addresses_len = 0;
241
242     begin = data;
243
244     /* get net address offset */
245     memcpy(&offset, data + *servoffset, sizeof(offset));
246     data += ntohs(offset);
247
248     /* format:
249        Address count (byte) 
250        len (byte = sizeof(length + address type + address) 
251        address type (byte, ip address = 0x01, ip + port = 0x02,
252                            ddp address = 0x03, fqdn = 0x04) 
253        address (up to 254 bytes, ip = 4, ip + port = 6, ddp = 4)
254        */
255
256     /* number of addresses. this currently screws up if we have a dsi
257        connection, but we don't have the ip address. to get around this,
258        we turn off the status flag for tcp/ip. */
259     *data++ = ((options->fqdn && dsi)? 1 : 0) + (dsi ? 1 : 0) + (asp ? 1 : 0) +
260               (((options->flags & OPTION_ANNOUNCESSH) && options->fqdn && dsi)? 1 : 0);
261
262     /* ip address */
263     if (dsi) {
264         const struct sockaddr_in *inaddr = &dsi->server;
265
266         if (inaddr->sin_port == htons(DSI_AFPOVERTCP_PORT)) {
267             *data++ = 6; /* length */
268             *data++ = 0x01; /* basic ip address */
269             memcpy(data, &inaddr->sin_addr.s_addr,
270                    sizeof(inaddr->sin_addr.s_addr));
271             data += sizeof(inaddr->sin_addr.s_addr);
272             addresses_len += 7;
273         } else {
274             /* ip address + port */
275             *data++ = 8;
276             *data++ = 0x02; /* ip address with port */
277             memcpy(data, &inaddr->sin_addr.s_addr,
278                    sizeof(inaddr->sin_addr.s_addr));
279             data += sizeof(inaddr->sin_addr.s_addr);
280             memcpy(data, &inaddr->sin_port, sizeof(inaddr->sin_port));
281             data += sizeof(inaddr->sin_port);
282             addresses_len += 9;
283         }
284     }
285
286     /* handle DNS names */
287     if (options->fqdn && dsi) {
288         size_t len = strlen(options->fqdn);
289         if ( len + 2 + addresses_len < maxstatuslen - offset) {
290             *data++ = len +2;
291             *data++ = 0x04;
292             memcpy(data, options->fqdn, len);
293             data += len;
294             addresses_len += len+2;
295         }
296
297         /* Annouce support for SSH tunneled AFP session, 
298          * this feature is available since 10.3.2.
299          * According to the specs (AFP 3.1 p.225) this should
300          * be an IP+Port style value, but it only works with 
301          * a FQDN. OSX Server uses FQDN as well.
302          */
303         if ( len + 2 + addresses_len < maxstatuslen - offset) {
304             if (options->flags & OPTION_ANNOUNCESSH) {
305                 *data++ = len +2;
306                 *data++ = 0x05;
307                 memcpy(data, options->fqdn, len);
308                 data += len;
309             }
310         }
311     }
312
313 #ifndef NO_DDP
314     if (asp) {
315         const struct sockaddr_at *ddpaddr = atp_sockaddr(asp->asp_atp);
316
317         /* ddp address */
318         *data++ = 6;
319         *data++ = 0x03; /* ddp address */
320         memcpy(data, &ddpaddr->sat_addr.s_net, sizeof(ddpaddr->sat_addr.s_net));
321         data += sizeof(ddpaddr->sat_addr.s_net);
322         memcpy(data, &ddpaddr->sat_addr.s_node,
323                sizeof(ddpaddr->sat_addr.s_node));
324         data += sizeof(ddpaddr->sat_addr.s_node);
325         memcpy(data, &ddpaddr->sat_port, sizeof(ddpaddr->sat_port));
326         data += sizeof(ddpaddr->sat_port);
327     }
328 #endif /* ! NO_DDP */
329
330     /* calculate/store Directory Services Names offset */
331     offset = htons(data - begin); 
332     *servoffset += sizeof(offset);
333     memcpy(begin + *servoffset, &offset, sizeof(offset));
334
335     /* return length of buffer */
336     return (data - begin);
337 }
338
339 static size_t status_directorynames(char *data, int *diroffset, 
340                                  const DSI *dsi _U_, 
341                                  const struct afp_options *options)
342 {
343     char *begin = data;
344     u_int16_t offset;
345     memcpy(&offset, data + *diroffset, sizeof(offset));
346     offset = ntohs(offset);
347     data += offset;
348
349     /* I can not find documentation of any other purpose for the
350      * DirectoryNames field.
351      */
352     /*
353      * Try to synthesize a principal:
354      * service '/' fqdn '@' realm
355      */
356     if (options->k5service && options->k5realm && options->fqdn) {
357         /* should k5princ be utf8 encoded? */
358         size_t len;
359         char *p = strchr( options->fqdn, ':' );
360         if (p) 
361             *p = '\0';
362         len = strlen( options->k5service ) 
363                         + strlen( options->fqdn )
364                         + strlen( options->k5realm );
365         len+=2; /* '/' and '@' */
366         if ( len > 255 || len+2 > maxstatuslen - offset) {
367             *data++ = 0;
368             LOG ( log_error, logtype_afpd, "status: could not set directory service list, no more room");
369         }        
370         else {
371             *data++ = 1; /* DirectoryNamesCount */
372             *data++ = len;
373             snprintf( data, len + 1, "%s/%s@%s", options->k5service,
374                                 options->fqdn, options->k5realm );
375             data += len;
376             if (p)
377                 *p = ':';
378        }
379     } else {
380         *data++ = 0;
381     }
382
383     /* Calculate and store offset for UTF8ServerName */
384     *diroffset += sizeof(u_int16_t);
385     offset = htons(data - begin);
386     memcpy(begin + *diroffset, &offset, sizeof(u_int16_t));
387
388     /* return length of buffer */
389     return (data - begin);
390 }
391
392 static size_t status_utf8servername(char *data, int *nameoffset,
393                                  const DSI *dsi _U_,
394                                  const struct afp_options *options)
395 {
396     char *Obj, *Type, *Zone;
397     u_int16_t namelen;
398     size_t len;
399     char *begin = data;
400     u_int16_t offset, status;
401
402     memcpy(&offset, data + *nameoffset, sizeof(offset));
403     offset = ntohs(offset);
404     data += offset;
405
406     /* FIXME:
407      * What is the valid character range for an nbpname?
408      *
409      * Apple's server likes to use the non-qualified hostname
410      * This obviously won't work very well if multiple servers are running
411      * on the box.
412      */
413
414     /* extract the obj part of the server */
415     Obj = (char *) (options->server ? options->server : options->hostname);
416     nbp_name(options->server ? options->server : options->hostname, &Obj, &Type, &Zone);
417
418     if ((size_t) -1 == (len = convert_string (
419                                         options->unixcharset, CH_UTF8_MAC, 
420                                         Obj, -1, data+sizeof(namelen), maxstatuslen-offset )) ) {
421         LOG ( log_error, logtype_afpd, "Could not set utf8 servername");
422
423         /* set offset to 0 */
424         memset(begin + *nameoffset, 0, sizeof(offset));
425         data = begin + offset;
426     }
427     else {
428         namelen = htons(len);
429         memcpy( data, &namelen, sizeof(namelen));
430         data += sizeof(namelen);
431         data += len;
432         offset = htons(offset);
433         memcpy(begin + *nameoffset, &offset, sizeof(u_int16_t));
434         
435         /* Now set the flag ... */
436         memcpy(&status, begin + AFPSTATUS_FLAGOFF, sizeof(status));
437         status = ntohs(status);
438         status |= AFPSRVRINFO_SRVUTF8;
439         status = htons(status);
440         memcpy(begin + AFPSTATUS_FLAGOFF, &status, sizeof(status));
441     }
442
443     /* return length of buffer */
444     return (data - begin);
445
446 }
447
448 /* returns actual offset to signature */
449 static void status_icon(char *data, const unsigned char *icondata,
450                         const int iconlen, const int sigoffset)
451 {
452     char                *start = data;
453     char                *sigdata = data + sigoffset;
454     u_int16_t           ret, status;
455
456     memcpy(&status, start + AFPSTATUS_ICONOFF, sizeof(status));
457     if ( icondata == NULL ) {
458         ret = status;
459         memset(start + AFPSTATUS_ICONOFF, 0, sizeof(status));
460     } else {
461         data += ntohs( status );
462         memcpy( data, icondata, iconlen);
463         data += iconlen;
464         ret = htons(data - start);
465     }
466
467     /* put in signature offset */
468     if (sigoffset)
469         memcpy(sigdata, &ret, sizeof(ret));
470 }
471
472 /* ---------------------
473 */
474 void status_reset(void)
475 {
476     Id = 0;
477 }
478
479
480 /* ---------------------
481  */
482 void status_init(AFPConfig *aspconfig, AFPConfig *dsiconfig,
483                  const struct afp_options *options)
484 {
485     ASP asp;
486     DSI *dsi;
487     char *status = NULL;
488     size_t statuslen;
489     int c, sigoff;
490
491     if (!(aspconfig || dsiconfig) || !options)
492         return;
493
494     if (aspconfig) {
495         status = aspconfig->status;
496         maxstatuslen=sizeof(aspconfig->status);
497         asp = aspconfig->obj.handle;
498     } else
499         asp = NULL;
500         
501     if (dsiconfig) {
502         status = dsiconfig->status;
503         maxstatuslen=sizeof(dsiconfig->status);
504         dsi = dsiconfig->obj.handle;
505     } else
506         dsi = NULL;
507
508     /*
509      * These routines must be called in order -- earlier calls
510      * set the offsets for later calls.
511      *
512      * using structs is a bad idea, but that's what the original code
513      * does. solaris, in fact, will segfault with them. so, now
514      * we just use the powers of #defines and memcpy.
515      *
516      * reply block layout (offsets are 16-bit quantities):
517      * machine type offset -> AFP version count offset ->
518      * UAM count offset -> vol icon/mask offset -> flags ->
519      *
520      * server name [padded to even boundary] -> signature offset ->
521      * network address offset ->
522      *
523      * at the appropriate offsets:
524      * machine type, afp versions, uams, server signature
525      * (16-bytes), network addresses, volume icon/mask 
526      */
527
528     status_flags(status, options->server_notif, options->fqdn ||
529                  (dsiconfig && dsi->server.sin_addr.s_addr),
530                  options->passwdbits, 
531                  (options->k5service && options->k5realm && options->fqdn), 
532                  options->flags);
533     /* returns offset to signature offset */
534     c = status_server(status, options->server ? options->server :
535                       options->hostname, options);
536     status_machine(status);
537     status_versions(status);
538     status_uams(status, options->uamlist);
539     if (options->flags & OPTION_CUSTOMICON)
540         status_icon(status, icon, sizeof(icon), c);
541     else
542         status_icon(status, apple_atalk_icon, sizeof(apple_atalk_icon), c);
543
544     sigoff = status_signature(status, &c, dsi, options);
545     /* c now contains the offset where the netaddress offset lives */
546
547     status_netaddress(status, &c, asp, dsi, options);
548     /* c now contains the offset where the Directory Names Count offset lives */
549
550     statuslen = status_directorynames(status, &c, dsi, options);
551     /* c now contains the offset where the UTF-8 ServerName offset lives */
552
553     if ( statuslen < maxstatuslen) 
554         statuslen = status_utf8servername(status, &c, dsi, options);
555
556 #ifndef NO_DDP
557     if (aspconfig) {
558         if (dsiconfig) /* status is dsiconfig->status */
559             memcpy(aspconfig->status, status, statuslen);
560         asp_setstatus(asp, status, statuslen);
561         aspconfig->signature = status + sigoff;
562         aspconfig->statuslen = statuslen;
563     }
564 #endif /* ! NO_DDP */
565
566     if (dsiconfig) {
567         if ((options->flags & OPTION_CUSTOMICON) == 0) {
568             status_icon(status, apple_tcp_icon, sizeof(apple_tcp_icon), 0);
569         }
570         dsi_setstatus(dsi, status, statuslen);
571         dsiconfig->signature = status + sigoff;
572         dsiconfig->statuslen = statuslen;
573     }
574 }
575
576 /* this is the same as asp/dsi_getstatus */
577 int afp_getsrvrinfo(AFPObj *obj, char *ibuf _U_, size_t ibuflen _U_, char *rbuf, size_t *rbuflen)
578 {
579     AFPConfig *config = obj->config;
580
581     memcpy(rbuf, config->status, config->statuslen);
582     *rbuflen = config->statuslen;
583     return AFP_OK;
584 }