1 /* vi:ai:et:ts=8 sw=2
2  */
3 /*
4  * wzdftpd - a modular and cool ftp server
5  * Copyright (C) 2002-2004  Pierre Chifflier
6  *
7  * This program is free software; you can redistribute it and/or
8  * modify it under the terms of the GNU General Public License
9  * as published by the Free Software Foundation; either version 2
10  * of the License, or (at your option) any later version.
11  *
12  * This program is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15  * GNU General Public License for more details.
16  *
17  * You should have received a copy of the GNU General Public License
18  * along with this program; if not, write to the Free Software
19  * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
20  *
21  * As a special exemption, Pierre Chifflier
22  * and other respective copyright holders give permission to link this program
23  * with OpenSSL, and distribute the resulting executable, without including
24  * the source code for OpenSSL in the source distribution.
25  */
26 
27 #include "wzd_all.h"
28 
29 #ifndef WZD_USE_PCH
30 
31 #include <stdio.h>
32 #include <stdlib.h>
33 #include <string.h>
34 
35 #include "wzd_structs.h"
36 
37 #include "wzd_commands.h"
38 #include "wzd_log.h"
39 #include "wzd_misc.h" /* ascii_lower */
40 #include "wzd_protocol.h"
41 
42 #include "wzd_debug.h"
43 
44 #endif /* WZD_USE_PCH */
45 
46 #define TELNET_SYNCH    242
47 #define TELNET_IP       244
48 
49 
50 static int _basic_check_ftp(const char * command);
51 
52 /** \brief Convert a 4-character string to an integer */
53 #define STRTOINT(a,b,c,d) (((a)<<24) + ((b)<<16) + ((c)<<8) + (d))
54 
55 
56 /** \brief Allocate memory for a ftp_command_t structure */
alloc_ftp_command(void)57 struct ftp_command_t * alloc_ftp_command(void)
58 {
59   struct ftp_command_t * command;
60 
61   command = wzd_malloc(sizeof(struct ftp_command_t));
62   memset(command,0,sizeof(struct ftp_command_t));
63 
64   return command;
65 }
66 
67 /** \brief Free memory used by a \a ftp_command_t structure */
free_ftp_command(struct ftp_command_t * command)68 void free_ftp_command(struct ftp_command_t * command)
69 {
70   if (command == NULL) return;
71 
72   str_deallocate(command->command_name);
73   str_deallocate(command->args);
74 
75   wzd_free(command);
76 }
77 
78 
79 /** \brief Fast token identification function.
80  *
81  * Converts the string into an integer and return the corresponding
82  * identifier. Luckily, all FTP commands are no more than 4 characters.
83  */
identify_token(const char * token)84 int identify_token(const char *token)
85 {
86   unsigned int length;
87   char buf[4];
88   if (!token || (length=strlen(token))==0)
89     return TOK_UNKNOWN;
90   memcpy(buf,token,sizeof(buf));
91   ascii_lower(buf,sizeof(buf));
92 
93   /* TODO order the following by probability order */
94   if (length <= 4) {
95     switch ( STRTOINT(buf[0],buf[1],buf[2],buf[3]) ) {
96       case STRTOINT('h','e','l','p'): return TOK_HELP;
97       case STRTOINT('u','s','e','r'): return TOK_USER;
98       case STRTOINT('p','a','s','s'): return TOK_PASS;
99       case STRTOINT('a','u','t','h'): return TOK_AUTH;
100       case STRTOINT('q','u','i','t'): return TOK_QUIT;
101       case STRTOINT('t','y','p','e'): return TOK_TYPE;
102       case STRTOINT('m','o','d','e'): return TOK_MODE;
103       case STRTOINT('p','o','r','t'): return TOK_PORT;
104       case STRTOINT('p','a','s','v'): return TOK_PASV;
105       case STRTOINT('p','w','d','\0'): return TOK_PWD;
106       case STRTOINT('n','o','o','p'): return TOK_NOOP;
107       case STRTOINT('s','y','s','t'): return TOK_SYST;
108       case STRTOINT('c','w','d','\0'): return TOK_CWD;
109       case STRTOINT('c','d','u','p'): return TOK_CDUP;
110       case STRTOINT('l','i','s','t'): return TOK_LIST;
111       case STRTOINT('n','l','s','t'): return TOK_NLST;
112       case STRTOINT('m','l','s','t'): return TOK_MLST;
113       case STRTOINT('m','l','s','d'): return TOK_MLSD;
114       case STRTOINT('m','k','d','\0'): return TOK_MKD;
115       case STRTOINT('r','m','d','\0'): return TOK_RMD;
116       case STRTOINT('r','e','t','r'): return TOK_RETR;
117       case STRTOINT('s','t','o','r'): return TOK_STOR;
118       case STRTOINT('a','p','p','e'): return TOK_APPE;
119       case STRTOINT('r','e','s','t'): return TOK_REST;
120       case STRTOINT('m','d','t','m'): return TOK_MDTM;
121       case STRTOINT('s','i','z','e'): return TOK_SIZE;
122       case STRTOINT('d','e','l','e'): return TOK_DELE;
123       case STRTOINT('a','b','o','r'): return TOK_ABOR;
124       case STRTOINT('p','b','s','z'): return TOK_PBSZ;
125       case STRTOINT('p','r','o','t'): return TOK_PROT;
126       case STRTOINT('c','p','s','v'): return TOK_CPSV;
127       case STRTOINT('s','s','c','n'): return TOK_SSCN;
128       case STRTOINT('s','i','t','e'): return TOK_SITE;
129       case STRTOINT('f','e','a','t'): return TOK_FEAT;
130       case STRTOINT('a','l','l','o'): return TOK_ALLO;
131       case STRTOINT('r','n','f','r'): return TOK_RNFR;
132       case STRTOINT('r','n','t','o'): return TOK_RNTO;
133       case STRTOINT('i','d','n','t'): return TOK_IDNT;
134       /* IPv6 */
135       case STRTOINT('e','p','s','v'): return TOK_EPSV;
136       case STRTOINT('e','p','r','t'): return TOK_EPRT;
137       /* extensions */
138       case STRTOINT('p','r','e','t'): return TOK_PRET;
139       case STRTOINT('x','c','r','c'): return TOK_XCRC;
140       case STRTOINT('x','m','d','5'): return TOK_XMD5;
141       case STRTOINT('o','p','t','s'): return TOK_OPTS;
142       case STRTOINT('m','o','d','a'): return TOK_MODA;
143       case STRTOINT('a','d','a','t'): return TOK_ADAT;
144       case STRTOINT('m','i','c','\0'): return TOK_MIC;
145 /*      default:
146         return TOK_UNKNOWN;*/
147     }
148   }
149 
150   /* XXX FIXME TODO the following sequence can be divided into parts, and MUST be followed by either
151    * STAT or ABOR or QUIT
152    * we should return TOK_PREPARE_SPECIAL_CMD or smthing like this
153    * and wait the next command
154    */
155   if (strcmp("\xff\xf2",buf)==0)
156     return TOK_NOTHING;
157   if (strcmp("\xff\xf4\xff\xf2",buf)==0)
158     return TOK_NOTHING;
159   if (strcmp("\xff\xf4",buf)==0) /* telnet IP */
160     return TOK_NOTHING;
161   if (strcmp("\xff",buf)==0) /* telnet SYNCH */
162     return TOK_NOTHING;
163   return TOK_UNKNOWN;
164 }
165 
166 /** \brief Remove extra characters from FTP command line
167  */
cleanup_ftp_command(char * buffer,size_t length)168 void cleanup_ftp_command(char * buffer, size_t length)
169 {
170   char * ptr;
171 
172   /* Force buffer to end with a 0, to ensure we won't have problems later with unterminated strings */
173   buffer[length-1] = '\0';
174 
175   if (buffer[0]=='\xff') {
176     char * ptr2;
177 
178     ptr = buffer;
179     /* skip telnet characters */
180     while (*ptr != '\0' &&
181         ((unsigned char)*ptr == 255 || (unsigned char)*ptr == TELNET_IP || (unsigned char)*ptr == TELNET_SYNCH))
182       ptr++;
183     /* TODO replace this by a working memmove or copy characters directly */
184     ptr2 = strdup(ptr);
185     wzd_strncpy(buffer,ptr2,WZD_BUFFER_LEN-1);
186     free(ptr2);
187   }
188 
189   ptr = strpbrk(buffer,"\r\n");
190   if (ptr != NULL) *ptr = '\0';
191 }
192 
193 /** \brief Parse and identify FTP command
194  *
195  * \note Input string is modified.
196  */
parse_ftp_command(wzd_string_t * s)197 struct ftp_command_t * parse_ftp_command(wzd_string_t * s)
198 {
199   struct ftp_command_t * ftp_command = NULL;
200   wzd_string_t * token;
201   wzd_command_t * command;
202 
203 out_log(LEVEL_FLOOD,"DEBUG parse_ftp_command(\"%s\")\n",str_tochar(s));
204 
205   if (_basic_check_ftp(str_tochar(s)) != 0) {
206     out_log(LEVEL_NORMAL,"FTP Error while decoding \"%s\"\n",str_tochar(s));
207     return NULL;
208   }
209 
210   token = str_tok(s," ");
211   if (token == NULL) {
212     out_log(LEVEL_NORMAL,"FTP Error empty command received, ignoring\n");
213     return NULL;
214   }
215 
216   command = commands_find(mainConfig->commands_list,token);
217 
218   if (command == NULL) {
219     if (str_length(s) > 0)
220       out_log(LEVEL_NORMAL,"WARNING unknown command received \"%s %s\"\n",str_tochar(token),str_tochar(s));
221     else
222       out_log(LEVEL_NORMAL,"WARNING unknown command received \"%s\"\n",str_tochar(token));
223     str_deallocate(token);
224     return NULL;
225   }
226 
227   if (command->id == TOK_SITE) {
228     wzd_string_t * site_command;
229     wzd_command_t * command_real;
230 
231     site_command = str_tok(s," \t");
232     if (site_command == NULL) {
233       /** \todo command is "site" without arguments. Return site help ? */
234       out_log(LEVEL_NORMAL,"WARNING received site command without arguments\n");
235       str_deallocate(token);
236       return NULL;
237     }
238     str_append(str_append(token,"_"),str_tochar(site_command));
239     str_tolower(token);
240     command_real = commands_find(mainConfig->commands_list,token);
241     if (command_real) command = command_real;
242     str_deallocate(site_command);
243   }
244 
245   if (command == NULL) {
246     if (str_length(s) > 0)
247       out_log(LEVEL_NORMAL,"WARNING could not parse command \"%s %s\"\n",str_tochar(token),str_tochar(s));
248     else
249       out_log(LEVEL_NORMAL,"WARNING could not parse command \"%s\"\n",str_tochar(token));
250     str_deallocate(token);
251     return NULL;
252   }
253 
254   ftp_command = alloc_ftp_command();
255 
256   ftp_command->command_name = token;
257   ftp_command->args = s;
258   ftp_command->command = command;
259 
260   return ftp_command;
261 }
262 
263 /** \brief Run basic tests on RFC compliance on input string
264  *
265  * \return 0 if ok
266  */
_basic_check_ftp(const char * command)267 static int _basic_check_ftp(const char * command)
268 {
269   const char *p = command;
270 
271   if (command == NULL) return -1;
272 
273   /* find first space position */
274   while (*p && *p != ' ')
275     p++;
276 
277   if ( (p - command) > 4 ) {
278     out_log(LEVEL_INFO,"FTP warning: first token is more than 4 characters\n");
279     return 1;
280   }
281 
282   if (*p == '\0') /* only one token */
283     return 0;
284 
285   if (*(p+1) == ' ') {
286     out_log(LEVEL_INFO,"FTP Warning: only one space allowed after first token\n");
287     return 1;
288   }
289 
290   return 0;
291 }
292