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