1 /*
2  * Copyright (C) 1998,1999  Ross Combs (rocombs@cs.nmsu.edu)
3  * Copyright (C) 1999  Rob Crittenden (rcrit@greyoak.com)
4  *
5  * This program is free software; you can redistribute it and/or
6  * modify it under the terms of the GNU General Public License
7  * as published by the Free Software Foundation; either version 2
8  * of the License, or (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with this program; if not, write to the Free Software
17  * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
18  */
19 #include "common/setup_before.h"
20 #include <stdio.h>
21 #ifdef HAVE_STDDEF_H
22 # include <stddef.h>
23 #else
24 # ifndef NULL
25 #  define NULL ((void *)0)
26 # endif
27 #endif
28 #ifdef STDC_HEADERS
29 # include <stdlib.h>
30 #else
31 # ifdef HAVE_MALLOC_H
32 #  include <malloc.h>
33 # endif
34 #endif
35 #ifdef HAVE_STRING_H
36 # include <string.h>
37 #else
38 # ifdef HAVE_STRINGS_H
39 #  include <strings.h>
40 # endif
41 #endif
42 #include "compat/strchr.h"
43 #include <errno.h>
44 #include "compat/strerror.h"
45 #ifdef HAVE_SYS_TYPES_H
46 # include <sys/types.h>
47 #endif
48 #ifdef HAVE_SYS_STAT_H
49 # include <sys/stat.h>
50 #endif
51 #include "common/bn_type.h"
52 #include "common/queue.h"
53 #include "connection.h"
54 #include "common/packet.h"
55 #include "common/file_protocol.h"
56 #include "common/eventlog.h"
57 #include "prefs.h"
58 #include "common/bnettime.h"
59 #include "common/util.h"
60 #include "common/xalloc.h"
61 #include "file.h"
62 #include "common/setup_after.h"
63 
64 
65 static char const * file_get_info(char const * rawname, unsigned int * len, bn_long * modtime);
66 
file_find_default(const char * rawname)67 static char * file_find_default(const char *rawname)
68 {
69     /* Add new default files here */
70     const char * defaultfiles[]={"termsofservice-",".txt",
71 				 "newaccount-",".txt",
72 				 "chathelp-war3-",".txt",
73 				 "matchmaking-war3-",".dat",
74 				 "tos_",".txt",
75 				 "tos-unicode_", ".txt",
76 				 NULL,NULL};
77     const char ** pattern, **extension;
78     char *filename = NULL;
79 
80     for (pattern = defaultfiles, extension = defaultfiles + 1; *pattern; pattern+=2, extension+=2)
81     	if (!strncmp(rawname, *pattern,strlen(*pattern))) {	/* Check if there is a default file available for this kind of file */
82 	    filename = (char*)xmalloc(strlen(prefs_get_filedir()) + 1 + strlen(*pattern) + 7 + strlen(*extension) + 1);
83 
84 	    strcpy(filename, prefs_get_filedir());
85 	    strcat(filename, "/");
86 	    strcat(filename, *pattern);
87 	    strcat(filename, "default");
88 	    strcat(filename, *extension);
89 
90 	    break;
91 	}
92 
93     return filename;
94 }
95 
file_get_info(char const * rawname,unsigned int * len,bn_long * modtime)96 static char const * file_get_info(char const * rawname, unsigned int * len, bn_long * modtime)
97 {
98     char *filename;
99     struct stat  sfile;
100     t_bnettime   bt;
101 
102     if (!rawname) {
103 	eventlog(eventlog_level_error,__FUNCTION__,"got NULL rawname");
104 	return NULL;
105     }
106 
107     if (!len) {
108 	eventlog(eventlog_level_error,__FUNCTION__,"got NULL len");
109 	return NULL;
110     }
111 
112     if (!modtime) {
113 	eventlog(eventlog_level_error,__FUNCTION__,"got NULL modtime");
114 	return NULL;
115     }
116 
117     if (strchr(rawname,'/') || strchr(rawname,'\\')) {
118 	eventlog(eventlog_level_warn,__FUNCTION__,"got rawname containing '/' or '\\' \"%s\"",rawname);
119 	return NULL;
120     }
121 
122     filename = buildpath(prefs_get_filedir(), rawname);
123 
124     if (stat(filename,&sfile)<0) { /* if it doesn't exist, try to replace with default file */
125 	xfree((void*)filename);
126 	filename = file_find_default(rawname);
127 	if (!filename) return NULL; /* no default version */
128 
129 	if (stat(filename,&sfile)<0) { /* try again */
130 	    /* FIXME: check for lower-case version of filename */
131 	    xfree(filename);
132 	    return NULL;
133 	}
134     }
135 
136     *len = (unsigned int)sfile.st_size;
137     bt = time_to_bnettime(sfile.st_mtime,0);
138     bnettime_to_bn_long(bt,modtime);
139 
140     return filename;
141 }
142 
143 
file_to_mod_time(char const * rawname,bn_long * modtime)144 extern int file_to_mod_time(char const * rawname, bn_long * modtime)
145 {
146     char const * filename;
147     unsigned int len;
148 
149     if (!rawname)
150     {
151 	eventlog(eventlog_level_error,__FUNCTION__,"got NULL rawname");
152 	return -1;
153     }
154     if (!modtime)
155     {
156 	eventlog(eventlog_level_error,__FUNCTION__,"got NULL modtime");
157 	return -1;
158     }
159 
160     if (!(filename = file_get_info(rawname, &len, modtime)))
161 	return -1;
162 
163     xfree((void *)filename); /* avoid warning */
164 
165     return 0;
166 }
167 
168 
169 /* Send a file.  If the file doesn't exist we still need to respond
170  * to the file request.  This will set filelen to 0 and send the server
171  * reply message and the client will be happy and not hang.
172  */
file_send(t_connection * c,char const * rawname,unsigned int adid,unsigned int etag,unsigned int startoffset,int need_header)173 extern int file_send(t_connection * c, char const * rawname, unsigned int adid, unsigned int etag, unsigned int startoffset, int need_header)
174 {
175     char const * filename;
176     t_packet *   rpacket;
177     FILE *       fp;
178     unsigned int filelen;
179     int          nbytes;
180 
181     if (!c)
182     {
183 	eventlog(eventlog_level_error,__FUNCTION__,"got NULL connection");
184 	return -1;
185     }
186     if (!rawname)
187     {
188 	eventlog(eventlog_level_error,__FUNCTION__,"got NULL rawname");
189 	return -1;
190     }
191 
192     if (!(rpacket = packet_create(packet_class_file)))
193     {
194 	eventlog(eventlog_level_error,__FUNCTION__,"could not create file packet");
195 	return -1;
196     }
197     packet_set_size(rpacket,sizeof(t_server_file_reply));
198     packet_set_type(rpacket,SERVER_FILE_REPLY);
199 
200     if ((filename = file_get_info(rawname,&filelen,&rpacket->u.server_file_reply.timestamp)))
201     {
202 	if (!(fp = fopen(filename,"rb")))
203 	{
204 	    /* FIXME: check for lower-case version of filename */
205 	    eventlog(eventlog_level_error,__FUNCTION__,"stat() succeeded yet could not open file \"%s\" for reading (fclose: %s)",filename,pstrerror(errno));
206 	    filelen = 0;
207 	}
208 	xfree((void *)filename); /* avoid warning */
209     }
210     else
211     {
212 	fp = NULL;
213 	filelen = 0;
214 	bn_long_set_a_b(&rpacket->u.server_file_reply.timestamp,0,0);
215     }
216 
217     if (fp)
218     {
219 	if (startoffset<filelen) {
220 	    fseek(fp,startoffset,SEEK_SET);
221 	} else {
222 	    eventlog(eventlog_level_warn,__FUNCTION__,"[%d] startoffset is beyond end of file (%u>%u)",conn_get_socket(c),startoffset,filelen);
223 	    /* Keep the real filesize. Battle.net does it the same way ... */
224 	    fclose(fp);
225 	    fp = NULL;
226 	}
227     }
228 
229     if (need_header)
230     {
231 	/* send the header from the server with the rawname and length. */
232 	bn_int_set(&rpacket->u.server_file_reply.filelen,filelen);
233 	bn_int_set(&rpacket->u.server_file_reply.adid,adid);
234 	bn_int_set(&rpacket->u.server_file_reply.extensiontag,etag);
235 	/* rpacket->u.server_file_reply.timestamp is set above */
236 	packet_append_string(rpacket,rawname);
237 	conn_push_outqueue(c,rpacket);
238     }
239     packet_del_ref(rpacket);
240 
241     /* Now send the data. Since it may be longer than a packet; we use
242      * the raw packet class.
243      */
244     if (!fp)
245     {
246 	eventlog(eventlog_level_warn,__FUNCTION__,"[%d] sending no data for file \"%s\"",conn_get_socket(c),rawname);
247 	return -1;
248     }
249 
250     eventlog(eventlog_level_info,__FUNCTION__,"[%d] sending file \"%s\" of length %d",conn_get_socket(c),rawname,filelen);
251     for (;;)
252     {
253 	if (!(rpacket = packet_create(packet_class_raw)))
254 	{
255 	    eventlog(eventlog_level_error,__FUNCTION__,"could not create raw packet");
256 	    if (fclose(fp)<0)
257 		eventlog(eventlog_level_error,__FUNCTION__,"could not close file \"%s\" after reading (fclose: %s)",rawname,pstrerror(errno));
258 	    return -1;
259 	}
260 	if ((nbytes = fread(packet_get_raw_data_build(rpacket,0),1,MAX_PACKET_SIZE,fp))<(int)MAX_PACKET_SIZE)
261 	{
262 	    if (nbytes>0) /* send out last portion */
263 	    {
264 		packet_set_size(rpacket,nbytes);
265 		conn_push_outqueue(c,rpacket);
266 	    }
267 	    packet_del_ref(rpacket);
268 	    if (ferror(fp))
269 		eventlog(eventlog_level_error,__FUNCTION__,"read failed before EOF on file \"%s\" (fread: %s)",rawname,pstrerror(errno));
270 	    break;
271 	}
272 	packet_set_size(rpacket,nbytes);
273 	conn_push_outqueue(c,rpacket);
274 	packet_del_ref(rpacket);
275     }
276 
277     if (fclose(fp)<0)
278 	eventlog(eventlog_level_error,__FUNCTION__,"could not close file \"%s\" after reading (fclose: %s)",rawname,pstrerror(errno));
279     return 0;
280 }
281