#define DSI_BLOCKSIZ 16
structdsi_block{uint8_tdsi_flags;/* packet type: request or reply */uint8_tdsi_command;/* command */uint16_tdsi_requestID;/* request ID */union{uint32_tdsi_code;/* error code */uint32_tdsi_doff;/* data offset */}dsi_data;uint32_tdsi_len;/* total data length */uint32_tdsi_reserved;/* reserved field */};#define DSI_DATASIZ 65536
/* child and parent processes might interpret a couple of these
* differently. */typedefstructDSI{structDSI*next;/* multiple listening addresses */AFPObj*AFPobj;intstatuslen;charstatus[1400];char*signature;structdsi_blockheader;structsockaddr_storageserver,client;structitimervaltimer;inttickle;/* tickle count */intin_write;/* in the middle of writing multiple packets,
signal handlers can't write to the socket */intmsg_request;/* pending message to the client */intdown_request;/* pending SIGUSR1 down in 5 mn */uint32_tattn_quantum,datasize,server_quantum;uint16_tserverID,clientID;uint8_t*commands;/* DSI recieve buffer */uint8_tdata[DSI_DATASIZ];/* DSI reply buffer */size_tdatalen,cmdlen;off_tread_count,write_count;uint32_tflags;/* DSI flags like DSI_SLEEPING, DSI_DISCONNECTED */intsocket;/* AFP session socket */intserversock;/* listening socket *//* DSI readahead buffer used for buffered reads in dsi_peek */size_tdsireadbuf;/* size of the DSI readahead buffer used in dsi_peek() */char*buffer;/* buffer start */char*start;/* current buffer head */char*eof;/* end of currently used buffer */char*end;#ifdef USE_ZEROCONF
char*bonjourname;/* server name as UTF8 maxlen MAXINSTANCENAMELEN */intzeroconf_registered;#endif
/* protocol specific open/close, send/receive
* send/receive fill in the header and use dsi->commands.
* write/read just write/read data */pid_t(*proto_open)(structDSI*);void(*proto_close)(structDSI*);}DSI;
/*!
* Start a DSI session, fork an afpd process
*
* @param childp (w) after fork: parent return pointer to child, child returns NULL
* @returns 0 on sucess, any other value denotes failure
*/intdsi_getsession(DSI*dsi,server_child_t*serv_children,inttickleval,afp_child_t**childp){pid_tpid;intipc_fds[2];afp_child_t*child;if(socketpair(PF_UNIX,SOCK_STREAM,0,ipc_fds)<0){LOG(log_error,logtype_dsi,"dsi_getsess: %s",strerror(errno));return-1;}if(setnonblock(ipc_fds[0],1)!=0||setnonblock(ipc_fds[1],1)!=0){LOG(log_error,logtype_dsi,"dsi_getsess: setnonblock: %s",strerror(errno));return-1;}switch(pid=dsi->proto_open(dsi)){/* in libatalk/dsi/dsi_tcp.c */case-1:/* if we fail, just return. it might work later */LOG(log_error,logtype_dsi,"dsi_getsess: %s",strerror(errno));return-1;case0:/* child. mostly handled below. */break;default:/* parent *//* using SIGKILL is hokey, but the child might not have
* re-established its signal handler for SIGTERM yet. */close(ipc_fds[1]);if((child=server_child_add(serv_children,pid,ipc_fds[0]))==NULL){LOG(log_error,logtype_dsi,"dsi_getsess: %s",strerror(errno));close(ipc_fds[0]);dsi->header.dsi_flags=DSIFL_REPLY;dsi->header.dsi_data.dsi_code=htonl(DSIERR_SERVBUSY);dsi_send(dsi);dsi->header.dsi_data.dsi_code=DSIERR_OK;kill(pid,SIGKILL);}dsi->proto_close(dsi);*childp=child;return0;}/* Save number of existing and maximum connections */dsi->AFPobj->cnx_cnt=serv_children->servch_count;dsi->AFPobj->cnx_max=serv_children->servch_nsessions;/* get rid of some stuff */dsi->AFPobj->ipc_fd=ipc_fds[1];close(ipc_fds[0]);close(dsi->serversock);dsi->serversock=-1;server_child_free(serv_children);switch(dsi->header.dsi_command){caseDSIFUNC_STAT:/* send off status and return */{/* OpenTransport 1.1.2 bug workaround:
*
* OT code doesn't currently handle close sockets well. urk.
* the workaround: wait for the client to close its
* side. timeouts prevent indefinite resource use.
*/staticstructtimevaltimeout={120,0};fd_setreadfds;dsi_getstatus(dsi);FD_ZERO(&readfds);FD_SET(dsi->socket,&readfds);free(dsi);select(FD_SETSIZE,&readfds,NULL,NULL,&timeout);exit(0);}break;caseDSIFUNC_OPEN:/* setup session *//* set up the tickle timer */dsi->timer.it_interval.tv_sec=dsi->timer.it_value.tv_sec=tickleval;dsi->timer.it_interval.tv_usec=dsi->timer.it_value.tv_usec=0;dsi_opensession(dsi);*childp=NULL;return0;default:/* just close */LOG(log_info,logtype_dsi,"DSIUnknown %d",dsi->header.dsi_command);dsi->proto_close(dsi);exit(EXITERR_CLNT);}}
/* accept the socket and do a little sanity checking */staticpid_tdsi_tcp_open(DSI*dsi){pid_tpid;SOCKLEN_Tlen;len=sizeof(dsi->client);dsi->socket=accept(dsi->serversock,(structsockaddr*)&dsi->client,&len);#ifdef TCPWRAP
{structrequest_inforeq;request_init(&req,RQ_DAEMON,"afpd",RQ_FILE,dsi->socket,NULL);fromhost(&req);if(!hosts_access(&req)){LOG(deny_severity,logtype_dsi,"refused connect from %s",eval_client(&req));close(dsi->socket);errno=ECONNREFUSED;dsi->socket=-1;}}#endif /* TCPWRAP */if(dsi->socket<0)return-1;getitimer(ITIMER_PROF,&itimer);if(0==(pid=fork())){/* child */staticstructitimervaltimer={{0,0},{DSI_TCPTIMEOUT,0}};structsigactionnewact,oldact;uint8_tblock[DSI_BLOCKSIZ];size_tstored;/* reset signals */server_reset_signal();#ifndef DEBUGGING
/* install an alarm to deal with non-responsive connections */newact.sa_handler=timeout_handler;sigemptyset(&newact.sa_mask);newact.sa_flags=0;sigemptyset(&oldact.sa_mask);oldact.sa_flags=0;setitimer(ITIMER_PROF,&itimer,NULL);if((sigaction(SIGALRM,&newact,&oldact)<0)||(setitimer(ITIMER_REAL,&timer,NULL)<0)){LOG(log_error,logtype_dsi,"dsi_tcp_open: %s",strerror(errno));exit(EXITERR_SYS);}#endif
dsi_init_buffer(dsi);/* read in commands. this is similar to dsi_receive except
* for the fact that we do some sanity checking to prevent
* delinquent connections from causing mischief. *//* read in the first two bytes *//* dsi_stream_read实现从socket中读取内容到buf中 */len=dsi_stream_read(dsi,block,2);if(!len){/* connection already closed, don't log it (normal OSX 10.3 behaviour) */exit(EXITERR_CLOSED);}if(len<2||(block[0]>DSIFL_MAX)||(block[1]>DSIFUNC_MAX)){LOG(log_error,logtype_dsi,"dsi_tcp_open: invalid header");exit(EXITERR_CLNT);}/* read in the rest of the header */stored=2;while(stored<DSI_BLOCKSIZ){len=dsi_stream_read(dsi,block+stored,sizeof(block)-stored);if(len>0)stored+=len;else{LOG(log_error,logtype_dsi,"dsi_tcp_open: stream_read: %s",strerror(errno));exit(EXITERR_CLNT);}}dsi->header.dsi_flags=block[0];dsi->header.dsi_command=block[1];memcpy(&dsi->header.dsi_requestID,block+2,sizeof(dsi->header.dsi_requestID));memcpy(&dsi->header.dsi_data.dsi_code,block+4,sizeof(dsi->header.dsi_data.dsi_code));memcpy(&dsi->header.dsi_len,block+8,sizeof(dsi->header.dsi_len));memcpy(&dsi->header.dsi_reserved,block+12,sizeof(dsi->header.dsi_reserved));dsi->clientID=ntohs(dsi->header.dsi_requestID);/* make sure we don't over-write our buffers. */dsi->cmdlen=min(ntohl(dsi->header.dsi_len),dsi->server_quantum);stored=0;while(stored<dsi->cmdlen){len=dsi_stream_read(dsi,dsi->commands+stored,dsi->cmdlen-stored);if(len>0)stored+=len;else{LOG(log_error,logtype_dsi,"dsi_tcp_open: stream_read: %s",strerror(errno));exit(EXITERR_CLNT);}}/* stop timer and restore signal handler */#ifndef DEBUGGING
memset(&timer,0,sizeof(timer));setitimer(ITIMER_REAL,&timer,NULL);sigaction(SIGALRM,&oldact,NULL);#endif
LOG(log_info,logtype_dsi,"AFP/TCP session from %s:%u",getip_string((structsockaddr*)&dsi->client),getip_port((structsockaddr*)&dsi->client));}/* send back our pid */returnpid;}
/* OpenSession. set up the connection */voiddsi_opensession(DSI*dsi){uint32_ti=0;/* this serves double duty. it must be 4-bytes long */intoffs;if(setnonblock(dsi->socket,1)<0){LOG(log_error,logtype_dsi,"dsi_opensession: setnonblock: %s",strerror(errno));AFP_PANIC("setnonblock error");}/* parse options */while(i<dsi->cmdlen){switch(dsi->commands[i++]){caseDSIOPT_ATTNQUANT:memcpy(&dsi->attn_quantum,dsi->commands+i+1,dsi->commands[i]);dsi->attn_quantum=ntohl(dsi->attn_quantum);caseDSIOPT_SERVQUANT:/* just ignore these */default:i+=dsi->commands[i]+1;/* forward past length tag + length */break;}}/* let the client know the server quantum. we don't use the
* max server quantum due to a bug in appleshare client 3.8.6. */dsi->header.dsi_flags=DSIFL_REPLY;dsi->header.dsi_data.dsi_code=0;/* dsi->header.dsi_command = DSIFUNC_OPEN;*/dsi->cmdlen=2*(2+sizeof(i));/* length of data. dsi_send uses it. *//* DSI Option Server Request Quantum */dsi->commands[0]=DSIOPT_SERVQUANT;dsi->commands[1]=sizeof(i);i=htonl((dsi->server_quantum<DSI_SERVQUANT_MIN||dsi->server_quantum>DSI_SERVQUANT_MAX)?DSI_SERVQUANT_DEF:dsi->server_quantum);memcpy(dsi->commands+2,&i,sizeof(i));/* AFP replaycache size option */offs=2+sizeof(i);dsi->commands[offs]=DSIOPT_REPLCSIZE;dsi->commands[offs+1]=sizeof(i);i=htonl(REPLAYCACHE_SIZE);memcpy(dsi->commands+offs+2,&i,sizeof(i));dsi_send(dsi);}
漏洞利用
注意到dsi_opensession()中的memcpy(&dsi->attn_quantum, dsi->commands + i + 1, dsi->commands[i]);