1 /* utftpd_sccs: sccs support functions */
2 
3 /*
4  * Copyright (C) 1999 Uwe Ohse
5  *
6  * This program is free software; you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation; either version 2 of the License, or
9  * (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with this program; if not, write to the Free Software
18  * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
19  */
20 
21 #include "config.h"
22 #include <sys/types.h>
23 #include <sys/ioctl.h>
24 #include <sys/stat.h>
25 #include <signal.h>
26 #include <fcntl.h>
27 
28 #include <sys/socket.h>
29 #include <netinet/in.h>
30 #include "no_tftp.h"
31 #include <netdb.h>
32 
33 #include <setjmp.h>
34 #include <syslog.h>
35 #include <errno.h>
36 #include <ctype.h>
37 #include <string.h>
38 #include <stdlib.h>
39 #include <unistd.h>
40 #include "uogetopt.h"
41 #include "timselsysdep.h"
42 #include "nonblock.h"
43 #include "str2num.h"
44 #include "uostr.h"
45 #include "uoio.h"
46 #include "cdb.h"
47 #include "sys/wait.h"
48 #include "wildmat.h"
49 #include "utftpd.h"
50 #include "str_ulong.h"
51 
52 static ssize_t
53 my_read (int fd, char *buf, size_t bytes)
54 {
55 	ssize_t got = 0;
56 	while (bytes) {
57 		ssize_t t = read (fd, buf, bytes);
58 		if (t == -1 && errno != EINTR) return -1;
59 		if (t == -1) continue;
60 		if (t==0) break;
61 		buf += t;
62 		bytes -= t;
63 		got += t;
64 	}
65 	return got;
66 }
67 
68 static int
69 utftpd_sccs_delta(const char *comment, struct utftpd_ctrl *flags)
70 {
71 	const char *slash;
72 	static uostr_t s=UOSTR_INIT;
73 	static uostr_t t=UOSTR_INIT; /* tmp file */
74 	size_t pos;
75 	int target_fd;
76 	char numbuf[STR_ULONG];
77 	const char *er;
78 	pid_t pid;
79 	int is_identical=1;
80 
81 	slash=strrchr(flags->filename,'/');
82 	if (!uostr_dup_mem(&s,flags->filename,slash-flags->filename+1)) goto oom;
83 	pos=s.len;
84 	/* try to get an sccs file */
85 	if (!uostr_add_cstr(&s,"SCCS/s.")) goto oom;
86 	if (!uostr_add_cstr(&s,slash+1)) goto oom;
87 	if (!uostr_add_mem(&s,"",1)) goto oom;
88 
89 	if (!flags->revision) {
90 		/* first: get a copy of the last revision, to make sure we don't checkin the same stuff twice */
91 		/* but since we have a checked out copy here we need to use -p to put the output of sccs into a file, uah ... */
92 		/* so we create a temp file now */
93 		pid=getpid();
94 		if (!uostr_dup_mem(&t,flags->filename,slash-flags->filename+1)) goto oom;
95 		str_ulong(numbuf,(unsigned long)pid);
96 		if (!uostr_add_cstr(&t,"tmp.")) goto oom;
97 		if (!uostr_add_cstr(&t,numbuf)) goto oom;
98 		if (!uostr_add_cstr(&t,".")) goto oom;
99 		do {
100 			static int count=0;
101 			size_t pos_target=t.len;
102 			if (count==1000) {
103 				er=strerror(errno);
104 				syslog(LOG_ERR,"can't create temporary file: %s",er);
105 				do_nak(flags->remotefd,EUNDEF,"can't create temporary file");
106 				_exit(1);
107 			}
108 			str_ulong(numbuf,++count);
109 			if (!uostr_add_mem(&t,"",1)) goto oom;
110 			target_fd=open(t.data,O_RDWR|O_CREAT|O_EXCL,0600);
111 			if (target_fd==-1) uostr_cut(&t,pos_target);
112 		} while (target_fd==-1);
113 		unlink(t.data);
114 
115 		pid=fork();
116 		if (pid==0) {
117 			union {const char **a1; char *const *a2;} d;
118 			const char *argv[4];
119 			int oc=0;
120 			argv[oc++]="get";
121 			argv[oc++]="-p";
122 			argv[oc++]=strdup(s.data+pos); /* SCCS/s.something */
123 			if (!argv[oc-1]) _exit(1);
124 			argv[oc]=0;
125 
126 			uostr_cut(&s,pos); uostr_add_mem(&s,"",1);
127 			if (-1==chdir(s.data)) _exit(1);
128 			if (-1==dup2(target_fd,1)) _exit(1);
129 			if (-1==dup2(nullfd,2)) _exit(1);
130 
131 			d.a1=argv;
132 			execv(opt_sccs_get,d.a2);
133 			_exit(1);
134 		} else if (pid==-1) {
135 			/* we will then not be able to fork for the checkin, too */
136 			er=strerror(errno);
137 			syslog(LOG_ERR,"can't fork: %s",er);
138 			do_nak(flags->remotefd,EUNDEF,er);
139 			_exit(1);
140 		} else {
141 			pid=waitpid(pid,0,0);
142 		}
143 		if (opt_sccs_clean || opt_sccs_unget) { /* that might tell us about NFS casualities. */
144 			char *buf1;
145 			char *buf2;
146 #define MYBUF 4096
147 			/* we got that file */
148 			int fd1;
149 			fd1=open(flags->filename,O_RDONLY);
150 			if (fd1==-1) {
151 				er=strerror(errno);
152 				syslog(LOG_ERR,"can't open %s: %s",flags->filename,er);
153 				do_nak(flags->remotefd,EUNDEF,er);
154 				_exit(1);
155 			}
156 			buf1=(char *)malloc(MYBUF); if (!buf1) goto oom;
157 			buf2=(char *)malloc(MYBUF); if (!buf2) goto oom;
158 
159 			if (-1==lseek(target_fd,0,SEEK_SET)) {
160 				er=strerror(errno);
161 				syslog(LOG_ERR,"can't lseek in %s: %s",t.data,er);
162 				do_nak(flags->remotefd,EUNDEF,er);
163 				_exit(1);
164 			}
165 
166 			while (1) {
167 				ssize_t got1;
168 				ssize_t got2;
169 				got1=my_read(fd1,buf1,MYBUF);
170 				if (got1==-1) {
171 					er=strerror(errno);
172 					syslog(LOG_ERR,"can't read %s: %s",flags->filename,er);
173 					do_nak(flags->remotefd,EUNDEF,er);
174 					_exit(1);
175 				}
176 				got2=my_read(target_fd,buf2,MYBUF);
177 				if (got2==-1) {
178 					er=strerror(errno);
179 					syslog(LOG_ERR,"can't read %s: %s",t.data,er);
180 					do_nak(flags->remotefd,EUNDEF,er);
181 					_exit(1);
182 				}
183 				if (got1!=got2 || 0!=memcmp(buf1,buf2,got1)) { is_identical=0; break; }
184 				if (got1==0) break; /* EOF */
185 			}
186 			close(fd1);
187 			close(target_fd);
188 			if (is_identical) {
189 				syslog(LOG_ERR,"received file %s is identical to repository version",flags->filename);
190 			}
191 		} else {
192 			/* no clean, no unget: treat it as different */
193 			is_identical=0;
194 		}
195 	} else {
196 		/* commit to a revision, so we check it in regardless of whether it is identical or not */
197 		is_identical=0;
198 	}
199 
200 	/* second: check it in */
201 	pid=fork();
202 	if (pid==0) {
203 		static uostr_t co=UOSTR_INIT;
204 		union {const char **a1; char *const *a2;} d;
205 		int oc=0;
206 		const char *argv[5];
207 		char *s_file;
208 		s_file=strdup(s.data+pos); /* SCCS/s.something */
209 		if (!s_file) _exit(1);
210 		uostr_xdup_cstr(&co,"-yby utftpd for ");
211 		uostr_xadd_cstr(&co,remoteip);
212 		uostr_xadd_cstr(&co," ");
213 		if (comment) {
214 			uostr_xadd_cstr(&co," ");
215 			uostr_xadd_cstr(&co,comment);
216 		}
217 		uostr_xadd_mem(&co,"",1);
218 		uostr_cut(&s,pos); uostr_add_mem(&s,"",1);
219 		if (-1==chdir(s.data)) _exit(1);
220 		if (-1==dup2(nullfd,2)) _exit(1);
221 		if (is_identical && opt_sccs_clean) {
222 			argv[oc++]="clean";
223 			argv[oc++]="-u";
224 			argv[oc++]=s_file;
225 			argv[oc]=0;
226 			d.a1=argv;
227 			execv(opt_sccs_clean,d.a2);
228 		} else if (is_identical && opt_sccs_unget) {
229 			argv[oc++]="unget";
230 			argv[oc++]="-s";
231 			argv[oc++]=s_file;
232 			argv[oc++]=0;
233 			d.a1=argv;
234 			execv(opt_sccs_unget,d.a2);
235 		} else {
236 			argv[oc++]="delta";
237 			argv[oc++]="-s";
238 			if (flags->revision) {
239 				static uostr_t rev=UOSTR_INIT;
240 				if (!uostr_add_cstr(&rev,"-R")) _exit(1);
241 				if (!uostr_add_cstr(&rev,flags->revision)) _exit(1);
242 				if (!uostr_add_mem(&rev,"",1)) _exit(1);
243 				argv[oc++]=rev.data;
244 			}
245 			argv[oc++]=co.data;
246 			argv[oc++]=s_file;
247 			argv[oc]=0;
248 			d.a1=argv;
249 			execv(opt_sccs_delta,d.a2);
250 		}
251 		_exit(1);
252 	} else if (pid==-1) {
253 		er=strerror(errno);
254 		syslog(LOG_ERR,"can't fork: %s",er);
255 		do_nak(flags->remotefd,EUNDEF,er);
256 		_exit(1);
257 	} else {
258 		int fail=wait_check_and_log(pid);
259 		if (fail) {
260 			syslog(LOG_ERR,"version control system failed");
261 			do_nak(flags->remotefd,EUNDEF,"version control system failed");
262 			_exit(1);
263 		}
264 	}
265 	return 0;
266   oom:
267   	syslog(LOG_ERR,"out of memory");
268 	do_nak(flags->remotefd,EUNDEF,"out of memory");
269 	exit(1);
270 }
271 
272 static int
273 utftpd_sccs_get(int mode, struct utftpd_ctrl *flags)
274 {
275 	const char *slash;
276 	static uostr_t s=UOSTR_INIT;
277 	size_t pos;
278 	int fds[2];
279 	const char *er;
280 	pid_t pid;
281 
282 	slash=strrchr(flags->filename,'/');
283 	if (!uostr_dup_mem(&s,flags->filename,slash-flags->filename+1)) goto oom;
284 	pos=s.len;
285 	/* try to get an sccs file */
286 	if (!uostr_add_cstr(&s,"SCCS/s.")) goto oom;
287 	if (!uostr_add_cstr(&s,slash+1)) goto oom;
288 	if (!uostr_add_mem(&s,"",1)) goto oom;
289 
290 	if (mode==TSG_READ && -1==pipe(fds)) {
291 		er=strerror(errno);
292 		syslog(LOG_ERR,"pipe: %s",er);
293 		do_nak(flags->remotefd,EUNDEF,er);
294 		_exit(1);
295 	}
296 
297 	pid=fork();
298 	if (pid==0) {
299 		union {const char **a1; char *const *a2;} d;
300 		const char *argv[10]; /* 6 used */
301 		int oc=0;
302 		char *s_file=strdup(s.data+pos); /* SCCS/s.something */
303 		if (!s_file) _exit(1);
304 		uostr_cut(&s,pos); uostr_add_mem(&s,"",1);
305 		if (-1==chdir(s.data)) _exit(1);
306 		if (mode==TSG_READ && -1==dup2(fds[1],1)) _exit(1);
307 		if (-1==dup2(nullfd,2)) _exit(1);
308 		argv[oc++]="get";
309 		/* in case we want to edit something we check out the latest revision. try_sccs_delta will commit
310 		 * to the right rev.
311 		 */
312 		if (mode==TSG_WRITE) argv[oc++]="-e";
313 		else if (flags->revision) {
314 			static uostr_t rev=UOSTR_INIT;
315 			if (!uostr_add_cstr(&rev,"-r")) _exit(1);
316 			if (!uostr_add_cstr(&rev,flags->revision)) _exit(1);
317 			if (!uostr_add_mem(&rev,"",1)) _exit(1);
318 			argv[oc++]=rev.data;
319 		}
320 		if (mode==TSG_READ) {
321 			argv[oc++]="-p"; /* print to stdout */
322 			close(fds[0]);
323 		}
324 		argv[oc++]=s_file;
325 		argv[oc]=0;
326 		d.a1=argv;
327 		execv(opt_sccs_get,d.a2);
328 		_exit(1); /* just in case */
329 	} else if (pid==-1) {
330 		er=strerror(errno);
331 		if (mode==TSG_READ) { close(fds[0]); close(fds[1]); }
332 		syslog(LOG_ERR,"can't fork: %s",er);
333 		do_nak(flags->remotefd,EUNDEF,er);
334 		_exit(1);
335 	} else {
336 		if (mode==TSG_READ) {
337 			close(fds[1]);
338 			flags->pid=pid;
339 			flags->filefd=fds[0];
340 		} else {
341 			pid=waitpid(pid,0,0);
342 #define ADD_ON 0
343 #ifndef HAVE_FSYNC
344 # ifdef (O_SYNC)
345 #  undef ADD_ON
346 #  define ADD_ON O_SYNC
347 # endif
348 #endif
349 			flags->filefd=open(flags->filename,O_WRONLY|O_TRUNC|ADD_ON,0644);
350 			if (flags->filefd==-1) {
351 				syslog(LOG_ERR,"version control system failed, cannot open %s: %s",flags->filename,strerror(errno));
352 				do_nak(flags->remotefd,EUNDEF,"version control system failed");
353 				_exit(1);
354 			}
355 		}
356 	}
357 	return 0;
358   oom:
359   	syslog(LOG_ERR,"out of memory");
360 	do_nak(flags->remotefd,EUNDEF,"out of memory");
361 	exit(1);
362 }
363 
364 static int
365 utftpd_sccs_test(const char *filename, struct utftpd_ctrl *flags)
366 {
367 	const char *slash;
368 	static uostr_t s=UOSTR_INIT;
369 	size_t pos;
370 	struct stat st;
371 	if (!opt_sccs_delta || !opt_sccs_get) return -1;
372 
373 	slash=strrchr(filename,'/');
374 	if (!uostr_dup_mem(&s,filename,slash-filename+1)) goto oom;
375 	pos=s.len;
376 	/* try to get an sccs file */
377 	if (!uostr_add_cstr(&s,"SCCS/s.")) goto oom;
378 	if (!uostr_add_cstr(&s,slash+1)) goto oom;
379 	if (!uostr_add_mem(&s,"",1)) goto oom;
380 	if (0==stat(s.data,&st))
381 		return 0;
382 	return -1;
383   oom:
384   	syslog(LOG_ERR,"out of memory");
385 	do_nak(flags->remotefd,EUNDEF,"out of memory");
386 	exit(1);
387 }
388 
389 static int
390 utftpd_sccs_unget(struct utftpd_ctrl *flags)
391 {
392 	const char *slash;
393 	static uostr_t s=UOSTR_INIT;
394 	size_t pos;
395 	pid_t pid;
396 
397 	slash=strrchr(flags->filename,'/');
398 	if (!uostr_dup_mem(&s,flags->filename,slash-flags->filename+1)) goto oom;
399 	pos=s.len;
400 	if (!uostr_add_cstr(&s,"SCCS/s.")) goto oom;
401 	if (!uostr_add_cstr(&s,slash+1)) goto oom;
402 	if (!uostr_add_mem(&s,"",1)) goto oom;
403 
404 	pid=fork();
405 	if (pid==0) {
406 		union {const char **a1; char *const *a2;} d;
407 		const char *argv[10]; /* 4 used */
408 		int oc=0;
409 		char *s_file;
410 		s_file=strdup(s.data+pos); /* SCCS/s.something */
411 		if (!s_file) _exit(1);
412 		uostr_cut(&s,pos); uostr_add_mem(&s,"",1);
413 		if (-1==chdir(s.data)) _exit(1);
414 		if (-1==dup2(nullfd,2)) _exit(1);
415 		if (opt_sccs_clean) {
416 			argv[oc++]="clean";
417 			argv[oc++]="-u";
418 			argv[oc++]=s_file;
419 			argv[oc]=0;
420 			d.a1=argv;
421 			execv(opt_sccs_clean,d.a2);
422 		} else if (opt_sccs_unget) {
423 			argv[oc++]="unget";
424 			argv[oc++]="-s";
425 			argv[oc++]=s_file;
426 			argv[oc++]=0;
427 			d.a1=argv;
428 			execv(opt_sccs_unget,d.a2);
429 		}
430 		_exit(1);
431 	}
432 	else {
433 		int fail=wait_check_and_log(pid);
434 		if (fail) {
435 			syslog(LOG_ERR,"version control system failed to unget");
436 			_exit(1);
437 		}
438 	}
439 	return 0;
440   oom:
441   	syslog(LOG_ERR,"out of memory");
442 	exit(1);
443 }
444 
445 struct utftpd_vc utftpd_sccs={
446 	utftpd_sccs_test,
447 	utftpd_sccs_delta,
448 	utftpd_sccs_get,
449 	utftpd_sccs_unget
450 };
451