/* * dsi.c * * Copyright (C) 2006 Alex deVries * */ #include #include #include #include #include #include #include #include #include #include #include #include "afpfs-ng/utils.h" #include "afpfs-ng/dsi.h" #include "afpfs-ng/afp.h" #include "afpfs-ng/uams_def.h" #include "dsi_protocol.h" #include "afpfs-ng/libafpclient.h" #include "afp_internal.h" #include "afp_replies.h" /* define this in order to get reams of DSI debugging information */ #undef DEBUG_DSI static int dsi_remove_from_request_queue(struct afp_server *server, struct dsi_request *toremove); int convert_utf8dec_to_utf8pre(const char *src, int src_len, char * dest, int dest_len); int convert_utf8pre_to_utf8dec(const char * src, int src_len, char * dest, int dest_len); /* This sets up a DSI header. */ void dsi_setup_header(struct afp_server * server, struct dsi_header * header, char command) { memset(header,0, sizeof(struct dsi_header)); pthread_mutex_lock(&server->requestid_mutex); if (server->lastrequestid == 65535) server->lastrequestid = 0; else server->lastrequestid++; server->expectedrequestid = server->lastrequestid; pthread_mutex_unlock(&server->requestid_mutex); header->requestid = htons(server->lastrequestid); header->command = command; } int dsi_getstatus(struct afp_server * server) { #define GETSTATUS_BUF_SIZE 1024 struct dsi_header header; struct afp_rx_buffer rx; int ret; rx.data=malloc(GETSTATUS_BUF_SIZE); if(rx.data == NULL) return -1; rx.maxsize=GETSTATUS_BUF_SIZE; rx.size=0; dsi_setup_header(server,&header,DSI_DSIGetStatus); /* We're intentionally ignoring the results */ ret=dsi_send(server,(char *) &header,sizeof(struct dsi_header),60, 0,(void *) &rx); free(rx.data); return ret; } int dsi_sendtickle(struct afp_server *server) { struct dsi_header header; dsi_setup_header(server,&header,DSI_DSITickle); dsi_send(server,(char *) &header,sizeof(struct dsi_header),1, DSI_DONT_WAIT,NULL); return 0; } int dsi_opensession(struct afp_server *server) { struct { struct dsi_header dsi_header __attribute__((__packed__)); uint8_t flags; uint8_t length; uint32_t rx_quantum ; } __attribute__((__packed__)) dsi_opensession_header; dsi_setup_header(server,&dsi_opensession_header.dsi_header,DSI_DSIOpenSession); /* Advertize our rx quantum */ dsi_opensession_header.flags=1; dsi_opensession_header.length=sizeof(unsigned int); dsi_opensession_header.rx_quantum=htonl(server->attention_quantum); dsi_send(server,(char *) &dsi_opensession_header, sizeof(dsi_opensession_header),1,DSI_BLOCK_TIMEOUT,NULL); return 0; } /* static int check_incoming_dsi_message(struct afp_server * server, void * data, int size) { struct dsi_header * header = (struct dsi_header *) data; if (size > sizeof(struct dsi_header)) { log_for_client(NULL,AFPFSD,LOG_WARNING, "DSI packet too small"); return -1; } if (header->flags != DSI_REPLY) { log_for_client(NULL,AFPFSD,LOG_WARNING, "Got a non-DSI reply"); return -1; } if (header->requestid < server->lastrequestid ) { log_for_client(NULL,AFPFSD,LOG_WARNING, "Got a requestid that was too low"); return -1; } if (header->requestid > server->lastrequestid ) { log_for_client(NULL,AFPFSD,LOG_WARNING, "Got a requestid that was too high"); return -1; } return 0; } */ static int dsi_remove_from_request_queue(struct afp_server *server, struct dsi_request *toremove) { struct dsi_request *p, *prev=NULL; #ifdef DEBUG_DSI printf("*** removing %d, %s\n",toremove->requestid, afp_get_command_name(toremove->subcommand)); #endif if (!server_still_valid(server)) return -1; pthread_mutex_lock(&server->request_queue_mutex); for (p=server->command_requests;p;p=p->next) { if (p==toremove) { if (prev==NULL) server->command_requests=p->next; else prev->next = p->next; server->stats.requests_pending--; free(p); pthread_mutex_unlock(&server->request_queue_mutex); return 0; } prev=p; } pthread_mutex_unlock(&server->request_queue_mutex); #ifdef DEBUG_DSI printf("*** Never removed anything for %d, %s\n",toremove->requestid, afp_get_command_name(toremove->subcommand)); #endif log_for_client(NULL,AFPFSD,LOG_WARNING, "Got an unknown reply for requestid %i\n",ntohs(toremove->requestid)); return -1; } int dsi_send(struct afp_server *server, char * msg, int size,int wait,unsigned char subcommand, void ** other) { /* For wait: * -1: wait forever * 0: don't wait * x>n: wait for N seconds */ struct dsi_header *header = (struct dsi_header *) msg; struct dsi_request * new_request, *p; int rc=0; struct timespec ts; struct timeval tv; header->length=htonl(size-sizeof(struct dsi_header)); if (!server_still_valid(server) || server->fd==0) return -1; afp_wait_for_started_loop(); /* Add request to the queue */ if ((new_request=malloc(sizeof(struct dsi_request))) == NULL) { log_for_client(NULL,AFPFSD,LOG_ERR, "Could not allocate for new request\n"); return -1; } memset(new_request,0,sizeof(struct dsi_request)); new_request->requestid=server->lastrequestid; new_request->subcommand=subcommand; new_request->other=other; new_request->wait=wait; new_request->next=NULL; new_request->done_waiting=0; pthread_mutex_lock(&server->request_queue_mutex); if (server->command_requests==NULL) { server->command_requests=new_request; } else { for (p=server->command_requests;p->next;p=p->next); p->next=new_request; } server->stats.requests_pending++; pthread_mutex_unlock(&server->request_queue_mutex); pthread_cond_init(&new_request->waiting_cond,NULL); pthread_mutex_init(&new_request->waiting_mutex,NULL); if (server->connect_state==SERVER_STATE_DISCONNECTED) { char mesg[1024]; unsigned int l=0; /* Try and reconnect */ afp_server_reconnect(server,mesg,&l,1024); } pthread_mutex_lock(&server->send_mutex); #ifdef DEBUG_DSI printf("*** Sending %d, %s\n",ntohs(header->requestid), afp_get_command_name(new_request->subcommand)); #endif if (write(server->fd,msg,size)<0) { if ((errno==EPIPE) || (errno==EBADF)) { /* The server has closed the connection */ server->connect_state=SERVER_STATE_DISCONNECTED; return -1; } perror("writing to server"); rc=-1; pthread_mutex_unlock(&server->send_mutex); goto out; } server->stats.tx_bytes+=size; pthread_mutex_unlock(&server->send_mutex); #ifdef DEBUG_DSI printf("=== Waiting for response for %d %s\n", new_request->requestid, afp_get_command_name(new_request->subcommand)); #endif if (new_request->wait<0) { /* Wait forever */ #ifdef DEBUG_DSI printf("=== Waiting forever for %d, %s\n", new_request->requestid, afp_get_command_name(new_request->subcommand)); #endif pthread_mutex_lock(&new_request->waiting_mutex); if (new_request->done_waiting==0) rc=pthread_cond_wait( &new_request->waiting_cond, &new_request->waiting_mutex ); pthread_mutex_unlock(&new_request->waiting_mutex); } else if (new_request->wait>0) { /* wait for new_request->wait seconds */ #ifdef DEBUG_DSI printf("=== Waiting for %d %s, for %ds\n", new_request->requestid, afp_get_command_name(new_request->subcommand), new_request->wait); #endif gettimeofday(&tv,NULL); ts.tv_sec=tv.tv_sec; ts.tv_sec+=new_request->wait; ts.tv_nsec=tv.tv_usec *1000; if (new_request->wait==0) { #ifdef DEBUG_DSI printf("=== Changing my mind, no longer waiting for %d\n", new_request->requestid); #endif goto skip; } pthread_mutex_lock(&new_request->waiting_mutex); if (new_request->done_waiting==0) rc=pthread_cond_timedwait( &new_request->waiting_cond, &new_request->waiting_mutex,&ts); pthread_mutex_unlock(&new_request->waiting_mutex); if (rc==ETIMEDOUT) { /* FIXME: should handle this case properly */ #ifdef DEBUG_DSI printf("=== Timedout for %d\n", new_request->requestid); #endif goto out; } } else { /* Don't wait */ #ifdef DEBUG_DSI printf("=== Skipping wait altogether for %d\n",new_request->requestid); #endif } #ifdef DEBUG_DSI printf("=== Done waiting for %d %s, waiting for %ds," " return %d, DSI return %d\n", new_request->requestid, afp_get_command_name(new_request->subcommand), new_request->wait, rc,new_request->return_code); #endif skip: rc=new_request->return_code; out: dsi_remove_from_request_queue(server,new_request); return rc; } int dsi_command_reply(struct afp_server* server,unsigned short subcommand, void * other) { int ret = 0; if (server->data_readdata_read); return -1; } if (subcommand==0) { log_for_client(NULL,AFPFSD,LOG_WARNING, "Broken subcommand: %d\n",subcommand); return -1; } if ((subcommand==afpRead) || ( subcommand==afpReadExt)) { struct afp_rx_buffer * buf = other; #ifdef DEBUG_DSI printf("=== read() for afpRead, %d bytes\n",buf->maxsize-buf->size); #endif if ((ret=read(server->fd,buf->data+buf->size, buf->maxsize-buf->size))<0) { return -1; } server->stats.rx_bytes+=ret; if (ret==0) { return -1; } server->data_read+=ret; } ret = afp_reply(subcommand,server,other); return ret; } void dsi_opensession_reply(struct afp_server * server) { struct { uint8_t flags ; uint8_t length ; uint32_t tx_quantum; } __attribute__((__packed__)) * dsi_opensession_header = (void *) server->incoming_buffer + sizeof(struct dsi_header); server->tx_quantum = ntohl(dsi_opensession_header->tx_quantum); } static int dsi_parse_versions(struct afp_server * server, char * msg) { unsigned char num_versions = msg[0]; int i,j=0; char * p; unsigned char len; char tmpversionname[33]; struct afp_versions * tmpversion; memset(server->versions,0, SERVER_MAX_VERSIONS); if (num_versions > SERVER_MAX_VERSIONS) num_versions = SERVER_MAX_VERSIONS; p=msg+1; for (i=0;iav_name;tmpversion++) { if (strcmp(tmpversion->av_name,tmpversionname)==0) { server->versions[j]=tmpversion->av_number; j++; break; } } p+=len; } return 0; } static int dsi_parse_uams(struct afp_server * server, char * msg) { unsigned char num_uams = msg[0]; unsigned char len; int i; char * p; char ua_name[AFP_UAM_LENGTH+1]; server->supported_uams= 0; memset(ua_name,0,AFP_UAM_LENGTH+1); if (num_uams > SERVER_MAX_UAMS) num_uams = SERVER_MAX_UAMS; p=msg+1; for (i=0;isupported_uams|=uam_string_to_bitmap(ua_name); p+=len; } return 0; } /* The parsing of the return for DSI GetStatus is the same as for * AFP GetSrvrInfo (which we don't yet support) */ void dsi_getstatus_reply(struct afp_server * server) { /* Todo: check for buffer overruns */ char * data, *p, *p2; int len; uint16_t * offset; /* This is the fixed portion */ struct dsi_getstatus_header { struct dsi_header dsi __attribute__((__packed__)); uint16_t machine_offset; uint16_t version_offset; uint16_t uams_offset; uint16_t icon_offset; uint16_t flags ; } __attribute__((__packed__)) * reply1 = (void *) server->incoming_buffer; struct reply2 { uint16_t signature_offset; uint16_t networkaddress_offset; uint16_t directoryservices_offset; uint16_t utf8servername_offset; } __attribute__((__packed__)) * reply2; if (server->data_read < (sizeof(*reply1) + sizeof(*reply2))) { log_for_client(NULL,AFPFSD,LOG_ERR, "Got incomplete data for getstatus\n"); return ; } data = (char * ) server->incoming_buffer + sizeof(struct dsi_header); /* First, get the fixed portion */ p=data + ntohs(reply1->machine_offset); copy_from_pascal(server->machine_type,p,AFP_MACHINETYPE_LEN); p=data + ntohs(reply1->version_offset); dsi_parse_versions(server, p); p=data + ntohs(reply1->uams_offset); dsi_parse_uams(server, p); if (ntohs(reply1->icon_offset)>0) { /* The icon and mask are optional */ p=data + ntohs(reply1->icon_offset); memcpy(server->icon,p,256); } server->flags=ntohs(reply1->flags); p=(void *)((unsigned long) server->incoming_buffer + sizeof(*reply1)); p+=copy_from_pascal(server->server_name,p,AFP_SERVER_NAME_LEN)+1; /* Now work our way through the variable bits */ /* First, make sure we're on an even boundary */ if (((uintptr_t) p) & 0x1) p++; /* Get the signature */ offset = (uint16_t *) p; memcpy(server->signature, ((void *) data)+ntohs(*offset), AFP_SIGNATURE_LEN); p+=2; /* The network addresses */ if (server->flags & kSupportsTCP) { offset = (uint16_t *) p; /* We don't actually do anything with the network addresses, * but if we did, it'd go here */ p+=2; } /* The directory names */ if (server->flags & kSupportsDirServices) { offset = (uint16_t *) p; /* We don't actually do anything with the directory names, * but if we did, it'd go here */ p+=2; } if (server->flags & kSupportsUTF8SrvrName) { /* And now the UTF8 server name */ offset = (uint16_t *) p; p2=((void *) data)+ntohs(*offset); /* Skip the hint character */ p2+=1; len=copy_from_pascal(server->server_name_utf8,p2, AFP_SERVER_NAME_UTF8_LEN); /* This is a workaround. There's a bug in netatalk that in some * circumstances puts the UTF8 servername off by one character */ if (len==0) { p2++; len=copy_from_pascal(server->server_name_utf8,p2, AFP_SERVER_NAME_UTF8_LEN); } convert_utf8dec_to_utf8pre(server->server_name_utf8, strlen(server->server_name_utf8), server->server_name_printable, AFP_SERVER_NAME_UTF8_LEN); } else { /* We don't have a UTF8 servername, so let's make one */ iconv_t cd; size_t inbytesleft = strlen(server->server_name); size_t outbytesleft = AFP_SERVER_NAME_UTF8_LEN; char * inbuf = server->server_name; char * outbuf = server->server_name_printable; if ((cd = iconv_open("MACINTOSH","UTF-8")) == (iconv_t) -1) return; iconv(cd,&inbuf,&inbytesleft, &outbuf, &outbytesleft); iconv_close(cd); } } void dsi_incoming_closesession(struct afp_server *server) { afp_unmount_all_volumes(server); loop_disconnect(server); } void dsi_incoming_tickle(struct afp_server * server) { struct dsi_header header; dsi_setup_header(server,&header,DSI_DSITickle); dsi_send(server,(char *) &header,sizeof(struct dsi_header),0, DSI_DONT_WAIT,NULL); } void * dsi_incoming_attention(void * other) { struct afp_server * server = other; struct { struct dsi_header header __attribute__((__packed__)); uint16_t flags ; } __attribute__((__packed__)) *packet = (void *) server->attention_buffer; unsigned short flags; char mesg[AFP_LOGINMESG_LEN]; unsigned char shutdown=0; unsigned char mins=0; unsigned char checkmessage=0; memset(mesg,0,AFP_LOGINMESG_LEN); /* The logic here's undocumented. If we get an attention packet and there's no flag, then go check the message. Also, go check the the message if there is a flag and we have the AFPATTN_MESG flag. Checked on netatalk 2.0.3. */ /* It's a bit tough to find docs on this, but I found it at: http://web.archive.org/web/20010806173437/developer.apple.com/techpubs/macosx/Networking/AFPClient/AFPClient-15.html */ if (ntohl(packet->header.length)>=2) { flags=ntohs(packet->flags); if (flags&AFPATTN_MESG) checkmessage=1; if (flags&(AFPATTN_CRASH|AFPATTN_SHUTDOWN)) shutdown=1; mins=flags & 0xff; } else { checkmessage=1; } if (checkmessage) { afp_getsrvrmsg(server,AFPMESG_SERVER, ((server->using_version->av_number>=30)?1:0), DSI_DEFAULT_TIMEOUT,mesg); if(bcmp(mesg,"The server is going down for maintenance.",41)==0) shutdown=1; } if (shutdown) { log_for_client(NULL,AFPFSD,LOG_ERR, "Got a shutdown notice in packet %d, going down in %d mins\n",ntohs(packet->header.requestid),mins); loop_disconnect(server); server->connect_state=SERVER_STATE_DISCONNECTED; return NULL; } return NULL; } struct dsi_request * dsi_find_request(struct afp_server *server, unsigned short request_id) { struct dsi_request *p, *prev=NULL; pthread_mutex_lock(&server->request_queue_mutex); for (p=server->command_requests;p;p=p->next) { if (request_id==p->requestid) { pthread_mutex_unlock(&server->request_queue_mutex); return p; } prev=p; } pthread_mutex_unlock(&server->request_queue_mutex); return NULL; } int dsi_recv(struct afp_server * server) { struct dsi_header * header = (void *) server->incoming_buffer; struct dsi_request * request=NULL; int rc; int amount_to_read=0; int ret; unsigned char runt_packet=0; /* Make sure we have at least one header */ if ((amount_to_read=sizeof(struct dsi_header)-server->data_read)>0) { #ifdef DEBUG_DSI printf("<<< read() for dsi, %d bytes\n",amount_to_read); #endif ret = read(server->fd,server->incoming_buffer+server->data_read, amount_to_read); if (ret<0) { perror("dsi_recv"); return -1; } if (ret==0) { return -1; } server->stats.rx_bytes+=ret; server->data_read+=ret; if ((server->data_read==sizeof(struct dsi_header)) && (ntohl(header->length)==0)) { goto gotenough; } return 0; /* We'll get the rest of the packet next time */ } gotenough: /* At this point, we have just the header */ /* Figure out what it is a reply to */ request = dsi_find_request(server,ntohs(header->requestid)); if (!request && (header->flags==DSI_REPLY)) { log_for_client(NULL,AFPFSD,LOG_ERR, "I have no idea what this is a reply to, id %d.\n", ntohs(header->requestid)); runt_packet=1; server->stats.runt_packets++; } if (request) request->return_code=ntohl(header->return_code.error_code); /* If it is a read, read in as much as we can */ if ((request) && ((request->subcommand==afpRead) || (request->subcommand==afpReadExt))) { struct afp_rx_buffer * buf = request->other; int newmax=buf->maxsize-buf->size; if (((server->data_read==sizeof(struct dsi_header)) && (ntohl(header->length)==0))) { server->data_read=0; goto out; } if ((!buf) || (!buf->maxsize)) { log_for_client(NULL,AFPFSD,LOG_ERR, "No buffer allocated for incoming data\n"); return -1; } if (newmax>ntohl(header->length)-buf->size) newmax=ntohl(header->length)-buf->size; #ifdef DEBUG_DSI printf("<<< read() in response to a request, %d bytes\n",newmax); #endif ret = read(server->fd,buf->data+buf->size, newmax); if (ret<0) { return -1; } if (ret==0) { return -1; } server->stats.rx_bytes+=ret; buf->size+=ret; /* Check to see if we've read enough */ if (ntohl(header->length)==buf->size) { char * tmpbuf; int size_to_copy=server->data_read -sizeof(struct dsi_header); if (size_to_copy==0) { server->data_read=0; goto out; } else if (size_to_copy<0) goto error; /* Okay, so there is a buffer we have to shift */ if ((tmpbuf=malloc(size_to_copy))==NULL) { log_for_client(NULL,AFPFSD,LOG_ERR, "Problem allocating memory for dsi_recv of size %d",size_to_copy); goto error; } memcpy(tmpbuf, server->incoming_buffer+ sizeof(struct dsi_header), size_to_copy); memcpy(server->incoming_buffer,tmpbuf, size_to_copy); server->data_read=size_to_copy; free(tmpbuf); } else return 0; } else { /* Okay, so it isn't a response to an afpRead or afpReadExt */ if (((server->data_read==sizeof(struct dsi_header)) && (ntohl(header->length)==0))) goto process_packet; amount_to_read=min(ntohl(header->length),server->bufsize); #ifdef DEBUG_DSI printf("<<< read() of rest of AFP, %d bytes\n",amount_to_read); #endif ret = read(server->fd, (void *) (((unsigned long) server->incoming_buffer)+server->data_read), amount_to_read); if (ret<0) return -1; if (ret==0) { return -1; } server->stats.rx_bytes+=ret; server->data_read+=ret; if (server->data_read<(ntohl(header->length)+sizeof(*header))) return 0; } if (runt_packet) goto after_processing; process_packet: /* At this point, we have a full DSI packet (or, for an afpRead, just the header and the data's been dumped in the preallocated buffer */ #ifdef DEBUG_DSI printf("<<< Handling %d\n",ntohs(header->requestid)); #endif switch (header->command) { case DSI_DSICloseSession: dsi_incoming_closesession(server); break; case DSI_DSIGetStatus: dsi_getstatus_reply(server); break; case DSI_DSIOpenSession: dsi_opensession_reply(server); break; case DSI_DSITickle: dsi_incoming_tickle(server); break; case DSI_DSIWrite: case DSI_DSICommand: if (!runt_packet) dsi_command_reply(server, request->subcommand,request->other); break; case DSI_DSIAttention: { pthread_t thread; memcpy( server->attention_buffer, server->incoming_buffer, server->data_read); server->attention_len=server->data_read; pthread_create(&thread,NULL, dsi_incoming_attention,server); } break; default: log_for_client(NULL,AFPFSD,LOG_ERR, "Unknown DSI command %i\n",header->command); goto error; } after_processing: if (server->data_read==ntohl(header->length)+sizeof(*header)) { /* The most common case */ server->data_read=0; } else { unsigned int size_to_copy= server->data_read- (ntohl(header->length)+sizeof(*header)); char * tmpbuf; if (size_to_copylength)) { /* This could be replaced with memmove, as it handles * overlaps */ memcpy( server->incoming_buffer, server->incoming_buffer+ntohl(header->length), size_to_copy); } else { /* This is more complicated, we need an tmp buf */ if ((tmpbuf=malloc(size_to_copy))==NULL) { log_for_client(NULL,AFPFSD,LOG_ERR, "Problem allocating memory for dsi_recv of size %d",size_to_copy); goto error; } memcpy(tmpbuf, server->incoming_buffer+ntohl(header->length), size_to_copy); memcpy(server->incoming_buffer,tmpbuf,size_to_copy); free(tmpbuf); } server->data_read-=size_to_copy; } out: rc=ntohl(header->return_code.error_code); if (request) { #ifdef DEBUG_DSI printf("<<< Found request %d, %s\n",request->requestid, afp_get_command_name(request->subcommand)); #endif if (request->wait) { #ifdef DEBUG_DSI printf("<<< Signalling %d, returning %d or %d\n",request->requestid,request->return_code,rc); #endif pthread_mutex_lock(&request->waiting_mutex); request->wait=0; request->done_waiting=1; pthread_cond_signal(&request->waiting_cond); pthread_mutex_unlock(&request->waiting_mutex); } else { dsi_remove_from_request_queue(server,request); } } return 0; error: #ifdef DEBUG_DSI printf("returning from dsi_recv with an error\n"); #endif return -1; }