1 /*------------------------------------------------------------------------------
2 // emTimeZonesProc.c
3 //
4 // Copyright (C) 2008-2009,2017-2018 Oliver Hamann.
5 //
6 // Homepage: http://eaglemode.sourceforge.net/
7 //
8 // This program is free software: you can redistribute it and/or modify it under
9 // the terms of the GNU General Public License version 3 as published by the
10 // Free Software Foundation.
11 //
12 // This program is distributed in the hope that it will be useful, but WITHOUT
13 // ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
14 // FOR A PARTICULAR PURPOSE. See the GNU General Public License version 3 for
15 // more details.
16 //
17 // You should have received a copy of the GNU General Public License version 3
18 // along with this program. If not, see <http://www.gnu.org/licenses/>.
19 //----------------------------------------------------------------------------*/
20 
21 #include <stdio.h>
22 #include <stdlib.h>
23 #include <string.h>
24 #include <errno.h>
25 #if defined(_WIN32)
26 #	include <fcntl.h>
27 #	include <io.h>
28 #	include <windows.h>
29 #else
30 #	include <time.h>
31 #	include <unistd.h>
32 #	include <sys/stat.h>
33 #endif
34 
35 
36 /* This is a child process because setenv("TZ",name) is not thread-safe. */
37 
38 
39 #if defined(_WIN32)
40 struct TzMapEntry {
41 	char * tz;
42 	char * winTz;
43 };
44 static struct TzMapEntry * tzMap=NULL;
45 static int tzMapElems=0;
46 #endif
47 
48 
49 #if defined(_WIN32)
compareTzMapEntries(const void * a,const void * b)50 static int compareTzMapEntries(const void * a, const void * b)
51 {
52 	return strcmp(
53 		((const struct TzMapEntry*)a)->tz,
54 		((const struct TzMapEntry*)b)->tz
55 	);
56 }
57 #endif
58 
59 
60 #if defined(_WIN32)
loadZone2WinTab(const char * zoneInfoDir,char * errBuf,int errBufSize)61 static int loadZone2WinTab(
62 	const char * zoneInfoDir, char * errBuf, int errBufSize
63 )
64 {
65 	char * path;
66 	char * p, * tz, * winTz;
67 	char buf[512];
68 	FILE * f;
69 	int tzMapSize;
70 
71 	path=malloc(strlen(zoneInfoDir)+256);
72 	sprintf(path,"%s\\zone2win.tab",zoneInfoDir);
73 	f=fopen(path,"r");
74 	if (!f) {
75 		snprintf(errBuf,errBufSize,"Failed to open %s: %s",path,strerror(errno));
76 		errBuf[errBufSize-1]=0;
77 		free(path);
78 		return 0;
79 	}
80 	free(path);
81 
82 	tzMapSize=256;
83 	tzMapElems=0;
84 	tzMap=malloc(sizeof(struct TzMapEntry)*tzMapSize);
85 
86 	while (fgets(buf,sizeof(buf),f)) {
87 		tz=NULL;
88 		winTz=NULL;
89 
90 		p=strchr(buf,'#');
91 		if (!p) p=buf+strlen(buf);
92 		while (p>buf && (unsigned char)p[-1]<=32) p--;
93 		*p=0;
94 		p=buf;
95 		while (*p && (unsigned char)*p<=32) p++;
96 		if (*p) {
97 			tz=p;
98 			do { p++; } while ((unsigned char)*p>32);
99 			if (*p) {
100 				*p=0;
101 				do { p++; } while (*p && (unsigned char)*p<=32);
102 				if (*p) winTz=p;
103 			}
104 		}
105 
106 		if (tz && winTz) {
107 			if (tzMapElems>=tzMapSize) {
108 				tzMapSize+=256;
109 				tzMap=realloc(tzMap,sizeof(struct TzMapEntry)*tzMapSize);
110 			}
111 			tzMap[tzMapElems].tz=strdup(tz);
112 			tzMap[tzMapElems].winTz=strdup(winTz);
113 			tzMapElems++;
114 		}
115 	}
116 
117 	fclose(f);
118 
119 	qsort(tzMap,tzMapElems,sizeof(struct TzMapEntry),compareTzMapEntries);
120 
121 	return 1;
122 }
123 #endif
124 
125 
126 #if defined(_WIN32)
getWinTz(const char * zoneInfoDir,const char * tz,char * errBuf,int errBufSize)127 static const char * getWinTz(
128 	const char * zoneInfoDir, const char * tz, char * errBuf, int errBufSize
129 )
130 {
131 	int i,j,k,l;
132 
133 	if (!tzMap) {
134 		if (!loadZone2WinTab(zoneInfoDir,errBuf,errBufSize)) {
135 			return 0;
136 		}
137 	}
138 
139 	i=0;
140 	j=tzMapElems;
141 	while (i<j) {
142 		k=(i+j)/2;
143 		l=strcmp(tzMap[k].tz,tz);
144 		if (l<0) i=k+1;
145 		else if (l>0) j=k;
146 		else return tzMap[k].winTz;
147 	}
148 
149 	snprintf(errBuf,errBufSize,"Windows time zone not configured");
150 	errBuf[errBufSize-1]=0;
151 	return NULL;
152 }
153 #endif
154 
155 
156 #if defined(_WIN32)
getWinTzInfo(const char * winTz,TIME_ZONE_INFORMATION * info,char * errBuf,int errBufSize)157 static int getWinTzInfo(
158 	const char * winTz, TIME_ZONE_INFORMATION * info,
159 	char * errBuf, int errBufSize
160 )
161 {
162 	struct Tzi {
163 		LONG Bias;
164 		LONG StandardBias;
165 		LONG DaylightBias;
166 		SYSTEMTIME StandardDate;
167 		SYSTEMTIME DaylightDate;
168 	};
169 
170 	char * key;
171 	HKEY hk;
172 	struct Tzi tzi;
173 	DWORD d,tziSize;
174 
175 	key=malloc(strlen(winTz)+256);
176 	sprintf(key,"SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Time Zones\\%s",winTz);
177 
178 	d=RegOpenKeyEx(HKEY_LOCAL_MACHINE,key,0,KEY_QUERY_VALUE,&hk);
179 	if (d != ERROR_SUCCESS) {
180 		snprintf(errBuf,errBufSize,"RegOpenKeyEx failed for \"%s\": 0x%lX",winTz,(long)d);
181 		errBuf[errBufSize-1]=0;
182 		free(key);
183 		return 0;
184 	}
185 
186 	tziSize=sizeof(tzi);
187 	d=RegQueryValueEx(hk,"TZI",NULL,NULL,(BYTE*)&tzi,&tziSize);
188 	if (d!=ERROR_SUCCESS) {
189 		snprintf(errBuf,errBufSize,"RegQueryValueEx failed for TZI in \"%s\": 0x%lX",winTz,(long)d);
190 		errBuf[errBufSize-1]=0;
191 		RegCloseKey(hk);
192 		free(key);
193 		return 0;
194 	}
195 	if (tziSize!=sizeof(tzi)) {
196 		snprintf(errBuf,errBufSize,"Reg TZI value too short for %s",winTz);
197 		errBuf[errBufSize-1]=0;
198 		RegCloseKey(hk);
199 		free(key);
200 		return 0;
201 	}
202 
203 	memset(info,0,sizeof(TIME_ZONE_INFORMATION));
204 	info->Bias=tzi.Bias;
205 	info->DaylightBias=tzi.DaylightBias;
206 	info->DaylightDate=tzi.DaylightDate;
207 	info->StandardBias=tzi.StandardBias;
208 	info->StandardDate=tzi.StandardDate;
209 
210 	RegCloseKey(hk);
211 	free(key);
212 
213 	return 1;
214 }
215 #endif
216 
217 
218 #if !defined(_WIN32)
checkTzFile(const char * zoneInfoDir,const char * tz)219 static int checkTzFile(const char * zoneInfoDir, const char * tz)
220 {
221 	char * path;
222 	struct stat st;
223 	int sr;
224 
225 	path=malloc(strlen(zoneInfoDir)+strlen(tz)+2);
226 	strcpy(path,zoneInfoDir);
227 	strcat(path,"/");
228 	strcat(path,tz);
229 	sr=stat(path,&st);
230 	free(path);
231 	return sr==0 && (st.st_mode&S_IFMT)==S_IFREG;
232 }
233 #endif
234 
235 
236 #if !defined(_WIN32)
checkTzFileCached(const char * zoneInfoDir,const char * tz)237 static int checkTzFileCached(const char * zoneInfoDir, const char * tz)
238 {
239 	struct cacheEntry {
240 		char * tz;
241 		int result;
242 	};
243 	static struct cacheEntry * * cache = NULL;
244 	static int cacheElems = 0;
245 	static int cacheSize = 0;
246 	int i,j,k,l;
247 
248 	i=0;
249 	j=cacheElems;
250 	while (i<j) {
251 		k=(i+j)/2;
252 		l=strcmp(cache[k]->tz,tz);
253 		if (l<0) i=k+1;
254 		else if (l>0) j=k;
255 		else return cache[k]->result;
256 	}
257 	if (cacheElems>=cacheSize) {
258 		cacheSize+=256;
259 		if (cache) cache=realloc(cache,sizeof(struct cacheEntry*)*cacheSize);
260 		else cache=malloc(sizeof(struct cacheEntry*)*cacheSize);
261 	}
262 	if (i<cacheElems) {
263 		memmove(cache+i+1,cache+i,sizeof(struct cacheEntry*)*(cacheElems-i));
264 	}
265 	cacheElems++;
266 	cache[i]=malloc(sizeof(struct cacheEntry));
267 	cache[i]->tz=strdup(tz);
268 	cache[i]->result=checkTzFile(zoneInfoDir,tz);
269 	return cache[i]->result;
270 }
271 #endif
272 
273 
274 #if defined(_WIN32)
275 static SYSTEMTIME tzBaseTime;
276 #else
277 static time_t tzBaseTime;
278 #endif
279 
280 
setTzBaseTimeNow()281 void setTzBaseTimeNow()
282 {
283 #if defined(_WIN32)
284 	GetSystemTime(&tzBaseTime);
285 #else
286 	tzBaseTime=time(NULL);
287 #endif
288 }
289 
290 
setTzBaseTime(int year,int month,int day,int hour,int minute,int second)291 void setTzBaseTime(int year, int month, int day, int hour, int minute, int second)
292 {
293 #if defined(_WIN32)
294 	memset(&tzBaseTime,0,sizeof(tzBaseTime));
295 	tzBaseTime.wYear=(WORD)year;
296 	tzBaseTime.wMonth=(WORD)month;
297 	tzBaseTime.wDay=(WORD)day;
298 	tzBaseTime.wHour=(WORD)hour;
299 	tzBaseTime.wMinute=(WORD)minute;
300 	tzBaseTime.wSecond=(WORD)second;
301 #else
302 	struct tm tm;
303 
304 	memset(&tm,0,sizeof(struct tm));
305 	tm.tm_year=year-1900;
306 	tm.tm_mon=month-1;
307 	tm.tm_mday=day;
308 	tm.tm_hour=hour;
309 	tm.tm_min=minute;
310 	tm.tm_sec=second;
311 
312 	if (setenv("TZ","",1)!=0) {
313 		fprintf(stderr,"setenv failed: %s\n",strerror(errno));
314 		exit(255);
315 	}
316 
317 	tzBaseTime=mktime(&tm);
318 #endif
319 }
320 
321 
getTzTime(const char * zoneInfoDir,const char * tz,int * pYear,int * pMonth,int * pDay,int * pWDay,int * pHour,int * pMinute,int * pSecond,char * errBuf,int errBufSize)322 int getTzTime(
323 	const char * zoneInfoDir, const char * tz,
324 	int * pYear, int * pMonth, int * pDay, int * pWDay,
325 	int * pHour, int * pMinute, int * pSecond,
326 	char * errBuf, int errBufSize
327 )
328 {
329 #if defined(_WIN32)
330 	TIME_ZONE_INFORMATION tzInfo;
331 	const char * winTz;
332 	SYSTEMTIME st;
333 
334 	winTz=getWinTz(zoneInfoDir,tz,errBuf,errBufSize);
335 	if (!winTz) {
336 		return 0;
337 	}
338 
339 	if (!getWinTzInfo(winTz,&tzInfo,errBuf,errBufSize)) {
340 		return 0;
341 	}
342 
343 	if (!SystemTimeToTzSpecificLocalTime(&tzInfo,&tzBaseTime,&st)) {
344 		snprintf(errBuf,errBufSize,"Time conversion failure (0x%lX)",(long)GetLastError());
345 		errBuf[errBufSize-1]=0;
346 		return 0;
347 	}
348 
349 	*pYear  =st.wYear;
350 	*pMonth =st.wMonth;
351 	*pDay   =st.wDay;
352 	*pWDay  =st.wDayOfWeek;
353 	*pHour  =st.wHour;
354 	*pMinute=st.wMinute;
355 	*pSecond=st.wSecond;
356 	return 1;
357 #else
358 	struct tm * ptm;
359 
360 	if (!checkTzFileCached(zoneInfoDir,tz)) {
361 		snprintf(errBuf,errBufSize,"Could not find %s/%s",zoneInfoDir,tz);
362 		errBuf[errBufSize-1]=0;
363 		return 0;
364 	}
365 	if (setenv("TZ",tz,1)!=0) {
366 		snprintf(errBuf,errBufSize,"setenv failed: %s",strerror(errno));
367 		errBuf[errBufSize-1]=0;
368 		return 0;
369 	}
370 	ptm=localtime(&tzBaseTime);
371 	if (!ptm) {
372 		snprintf(errBuf,errBufSize,"Time conversion failure");
373 		errBuf[errBufSize-1]=0;
374 		return 0;
375 	}
376 	*pYear  =ptm->tm_year+1900;
377 	*pMonth =ptm->tm_mon+1;
378 	*pDay   =ptm->tm_mday;
379 	*pWDay  =ptm->tm_wday;
380 	*pHour  =ptm->tm_hour;
381 	*pMinute=ptm->tm_min;
382 	*pSecond=ptm->tm_sec;
383 	return 1;
384 #endif
385 }
386 
387 
handleRequest(const char * zoneInfoDir,const char * request,char * replyBuf,int replyBufSize)388 void handleRequest(
389 	const char * zoneInfoDir, const char * request,
390 	char * replyBuf, int replyBufSize
391 )
392 {
393 	int year, month, day, wDay, hour, minute, second, n;
394 	const char * p;
395 	char errBuf[256];
396 
397 	if (strncmp(request,"test#",5)==0 && (p=strchr(request+5,'#'))!=NULL) {
398 		n=sscanf(request+5,"%d-%d-%d %d:%d:%d",&year,&month,&day,&hour,&minute,&second);
399 		if (n!=6) {
400 			snprintf(replyBuf,replyBufSize,"ERROR: Failed to parse test command\n");
401 			replyBuf[replyBufSize-1]=0;
402 			return;
403 		}
404 		setTzBaseTime(year,month,day,hour,minute,second);
405 		n=snprintf(replyBuf,replyBufSize,"%s#",request+5);
406 		if (n>replyBufSize) n=replyBufSize;
407 		replyBuf+=n;
408 		replyBufSize-=n;
409 		request=p+1;
410 	}
411 	else {
412 		setTzBaseTimeNow();
413 	}
414 
415 	if (
416 		getTzTime(
417 			zoneInfoDir,request,
418 			&year,&month,&day,&wDay,&hour,&minute,&second,
419 			errBuf,sizeof(errBuf)
420 		)
421 	) {
422 		snprintf(
423 			replyBuf,replyBufSize,
424 			"%d-%d-%d %d %d:%d:%d\n",
425 			year,month,day,wDay,hour,minute,second
426 		);
427 		replyBuf[replyBufSize-1]=0;
428 	}
429 	else {
430 		snprintf(replyBuf,replyBufSize,"ERROR: %s\n",errBuf);
431 		replyBuf[replyBufSize-1]=0;
432 	}
433 }
434 
435 
tzServe(int argc,char * argv[])436 int tzServe(int argc, char * argv[])
437 {
438 	static const int maxReplySize=256;
439 	const char * zoneInfoDir;
440 	char * rBuf, * wBuf, * request;
441 	int fdIn, fdOut, rBufSize, wBufSize, rBufFill, wBufFill, i, j;
442 
443 	if (argc!=2) {
444 		fprintf(stderr,"%s: Illegal arguments\n",argv[0]);
445 		return 1;
446 	}
447 	zoneInfoDir=argv[1];
448 
449 	fdIn=fileno(stdin);
450 	fdOut=fileno(stdout);
451 	rBufSize=65536;
452 	wBufSize=65536;
453 	rBuf=(char*)malloc(rBufSize);
454 	wBuf=(char*)malloc(wBufSize);
455 	rBufFill=0;
456 	wBufFill=0;
457 	for (;;) {
458 		if (rBufFill<rBufSize) {
459 			i=(int)read(fdIn,rBuf+rBufFill,rBufSize-rBufFill);
460 			if (i<=0) break;
461 			rBufFill+=i;
462 		}
463 		for (i=0, j=0; i<rBufFill; i++) {
464 			if (rBuf[i]!=0x0a && rBuf[i]!=0x0d) continue;
465 			if (i==j) { j++; continue; }
466 			rBuf[i]=0;
467 			request=rBuf+j;
468 			j=i+1;
469 			while (wBufSize-wBufFill<maxReplySize) {
470 				wBufSize*=2;
471 				wBuf=(char*)realloc(wBuf,wBufSize);
472 			}
473 			handleRequest(zoneInfoDir,request,wBuf+wBufFill,maxReplySize);
474 			wBufFill+=strlen(wBuf+wBufFill);
475 		}
476 		if (j==0 && rBufFill>=rBufSize) {
477 			rBufSize*=2;
478 			rBuf=(char*)realloc(rBuf,rBufSize);
479 			continue;
480 		}
481 		rBufFill-=j;
482 		if (rBufFill>0) memmove(rBuf,rBuf+j,rBufFill);
483 		if (wBufFill>0) {
484 			i=(int)write(fdOut,wBuf,wBufFill);
485 			if (i<=0) break;
486 			wBufFill-=i;
487 			if (wBufFill>0) memmove(wBuf,wBuf+i,wBufFill);
488 		}
489 	}
490 	free(rBuf);
491 	free(wBuf);
492 	return 0;
493 }
494 
495 
496 #ifdef _WIN32
tzServeThreadProc(LPVOID data)497 static DWORD WINAPI tzServeThreadProc(LPVOID data)
498 {
499 	return tzServe(__argc,__argv);
500 }
501 #endif
502 
503 
main(int argc,char * argv[])504 int main(int argc, char * argv[])
505 {
506 #ifdef _WIN32
507 	HANDLE hdl;
508 	DWORD d;
509 	MSG msg;
510 
511 	setmode(fileno(stdout),O_BINARY);
512 	setmode(fileno(stdin),O_BINARY);
513 	setbuf(stderr,NULL);
514 
515 	hdl=CreateThread(NULL,0,tzServeThreadProc,NULL,0,&d);
516 	do {
517 		while (PeekMessage(&msg,NULL,0,0,PM_REMOVE)) {
518 			if (msg.message==WM_QUIT) ExitProcess(143);
519 		}
520 		d=MsgWaitForMultipleObjects(1,&hdl,FALSE,INFINITE,QS_ALLPOSTMESSAGE);
521 	} while(d==WAIT_OBJECT_0+1);
522 	WaitForSingleObject(hdl,INFINITE);
523 	GetExitCodeThread(hdl,&d);
524 	return d;
525 #else
526 	return tzServe(argc,argv);
527 #endif
528 }
529