File: | src/usr.bin/rdistd/server.c |
Warning: | line 1151, column 34 Call to function 'mktemp' is insecure as it always creates or uses insecure temporary file. Use 'mkstemp' instead |
Press '?' to see keyboard shortcuts
Keyboard shortcuts:
1 | /* $OpenBSD: server.c,v 1.49 2022/12/26 19:16:02 jmc Exp $ */ |
2 | |
3 | /* |
4 | * Copyright (c) 1983 Regents of the University of California. |
5 | * All rights reserved. |
6 | * |
7 | * Redistribution and use in source and binary forms, with or without |
8 | * modification, are permitted provided that the following conditions |
9 | * are met: |
10 | * 1. Redistributions of source code must retain the above copyright |
11 | * notice, this list of conditions and the following disclaimer. |
12 | * 2. Redistributions in binary form must reproduce the above copyright |
13 | * notice, this list of conditions and the following disclaimer in the |
14 | * documentation and/or other materials provided with the distribution. |
15 | * 3. Neither the name of the University nor the names of its contributors |
16 | * may be used to endorse or promote products derived from this software |
17 | * without specific prior written permission. |
18 | * |
19 | * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND |
20 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE |
21 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE |
22 | * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE |
23 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL |
24 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS |
25 | * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) |
26 | * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT |
27 | * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY |
28 | * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF |
29 | * SUCH DAMAGE. |
30 | */ |
31 | |
32 | #include <ctype.h> |
33 | #include <dirent.h> |
34 | #include <errno(*__errno()).h> |
35 | #include <fcntl.h> |
36 | #include <grp.h> |
37 | #include <limits.h> |
38 | #include <stdio.h> |
39 | #include <stdlib.h> |
40 | #include <string.h> |
41 | #include <time.h> |
42 | #include <unistd.h> |
43 | |
44 | #include "server.h" |
45 | |
46 | /* |
47 | * Server routines |
48 | */ |
49 | |
50 | char tempname[sizeof _RDIST_TMP"rdistXXXXXXXX" + 1]; /* Tmp file name */ |
51 | char buf[BUFSIZ1024]; /* general purpose buffer */ |
52 | char target[PATH_MAX1024]; /* target/source directory name */ |
53 | char *ptarget; /* pointer to end of target name */ |
54 | int catname = 0; /* cat name to target name */ |
55 | char *sptarget[32]; /* stack of saved ptarget's for directories */ |
56 | char *fromhost = NULL((void *)0); /* Client hostname */ |
57 | static int64_t min_freespace = 0; /* Minimum free space on a filesystem */ |
58 | static int64_t min_freefiles = 0; /* Minimum free # files on a filesystem */ |
59 | int oumask; /* Old umask */ |
60 | |
61 | static int cattarget(char *); |
62 | static int setownership(char *, int, uid_t, gid_t, int); |
63 | static int setfilemode(char *, int, int, int); |
64 | static int fchog(int, char *, char *, char *, int); |
65 | static int removefile(struct stat *, int); |
66 | static void doclean(char *); |
67 | static void clean(char *); |
68 | static void dospecial(char *); |
69 | static void docmdspecial(void); |
70 | static void query(char *); |
71 | static int chkparent(char *, opt_t); |
72 | static char *savetarget(char *, opt_t); |
73 | static void recvfile(char *, opt_t, int, char *, char *, time_t, time_t, off_t); |
74 | static void recvdir(opt_t, int, char *, char *); |
75 | static void recvlink(char *, opt_t, int, off_t); |
76 | static void hardlink(char *); |
77 | static void setconfig(char *); |
78 | static void recvit(char *, int); |
79 | static void dochmog(char *); |
80 | static void settarget(char *, int); |
81 | |
82 | /* |
83 | * Cat "string" onto the target buffer with error checking. |
84 | */ |
85 | static int |
86 | cattarget(char *string) |
87 | { |
88 | if (strlen(string) + strlen(target) + 2 > sizeof(target)) { |
89 | message(MT_INFO0x0040, "target buffer is not large enough."); |
90 | return(-1); |
91 | } |
92 | if (!ptarget) { |
93 | message(MT_INFO0x0040, "NULL target pointer set."); |
94 | return(-10); |
95 | } |
96 | |
97 | (void) snprintf(ptarget, sizeof(target) - (ptarget - target), |
98 | "/%s", string); |
99 | |
100 | return(0); |
101 | } |
102 | |
103 | /* |
104 | * Set uid and gid ownership of a file. |
105 | */ |
106 | static int |
107 | setownership(char *file, int fd, uid_t uid, gid_t gid, int islink) |
108 | { |
109 | static int is_root = -1; |
110 | int status = -1; |
111 | |
112 | /* |
113 | * We assume only the Superuser can change uid ownership. |
114 | */ |
115 | switch (is_root) { |
116 | case -1: |
117 | is_root = getuid() == 0; |
118 | if (is_root) |
119 | break; |
120 | /* FALLTHROUGH */ |
121 | case 0: |
122 | uid = -1; |
123 | break; |
124 | case 1: |
125 | break; |
126 | } |
127 | |
128 | if (fd != -1 && !islink) |
129 | status = fchown(fd, uid, gid); |
130 | else |
131 | status = fchownat(AT_FDCWD-100, file, uid, gid, |
132 | AT_SYMLINK_NOFOLLOW0x02); |
133 | |
134 | if (status == -1) { |
135 | if (uid == (uid_t)-1) |
136 | message(MT_NOTICE0x0100, "%s: chgrp %d failed: %s", |
137 | target, gid, SYSERRstrerror((*__errno()))); |
138 | else |
139 | message(MT_NOTICE0x0100, "%s: chown %d:%d failed: %s", |
140 | target, uid, gid, SYSERRstrerror((*__errno()))); |
141 | return(-1); |
142 | } |
143 | |
144 | return(0); |
145 | } |
146 | |
147 | /* |
148 | * Set mode of a file |
149 | */ |
150 | static int |
151 | setfilemode(char *file, int fd, int mode, int islink) |
152 | { |
153 | int status = -1; |
154 | |
155 | if (mode == -1) |
156 | return(0); |
157 | |
158 | if (islink) |
159 | status = fchmodat(AT_FDCWD-100, file, mode, AT_SYMLINK_NOFOLLOW0x02); |
160 | |
161 | if (fd != -1 && !islink) |
162 | status = fchmod(fd, mode); |
163 | |
164 | if (status == -1 && !islink) |
165 | status = chmod(file, mode); |
166 | |
167 | if (status == -1) { |
168 | message(MT_NOTICE0x0100, "%s: chmod failed: %s", target, SYSERRstrerror((*__errno()))); |
169 | return(-1); |
170 | } |
171 | |
172 | return(0); |
173 | } |
174 | /* |
175 | * Change owner, group and mode of file. |
176 | */ |
177 | static int |
178 | fchog(int fd, char *file, char *owner, char *group, int mode) |
179 | { |
180 | int i; |
181 | struct stat st; |
182 | uid_t uid; |
183 | gid_t gid; |
184 | gid_t primegid = (gid_t)-2; |
185 | |
186 | uid = userid; |
187 | if (userid == 0) { /* running as root; take anything */ |
188 | if (*owner == ':') { |
189 | uid = (uid_t) atoi(owner + 1); |
190 | } else if (strcmp(owner, locuser) != 0) { |
191 | if (uid_from_user(owner, &uid) == -1) { |
192 | if (mode != -1 && IS_ON(mode, S_ISUID)(mode & 0004000)) { |
193 | message(MT_NOTICE0x0100, |
194 | "%s: unknown login name \"%s\", clearing setuid", |
195 | target, owner); |
196 | mode &= ~S_ISUID0004000; |
197 | uid = 0; |
198 | } else |
199 | message(MT_NOTICE0x0100, |
200 | "%s: unknown login name \"%s\"", |
201 | target, owner); |
202 | } |
203 | } else { |
204 | uid = userid; |
205 | primegid = groupid; |
206 | } |
207 | if (*group == ':') { |
208 | gid = (gid_t)atoi(group + 1); |
209 | goto ok; |
210 | } |
211 | } else { /* not root, setuid only if user==owner */ |
212 | if (mode != -1) { |
213 | if (IS_ON(mode, S_ISUID)(mode & 0004000) && |
214 | strcmp(locuser, owner) != 0) |
215 | mode &= ~S_ISUID0004000; |
216 | if (mode) |
217 | mode &= ~S_ISVTX0001000; /* and strip sticky too */ |
218 | } |
219 | primegid = groupid; |
220 | } |
221 | |
222 | gid = (gid_t)-1; |
223 | if (*group == ':') { |
224 | gid = (gid_t) atoi(group + 1); |
225 | } else if (gid_from_group(group, &gid) == -1) { |
226 | if (mode != -1 && IS_ON(mode, S_ISGID)(mode & 0002000)) { |
227 | message(MT_NOTICE0x0100, |
228 | "%s: unknown group \"%s\", clearing setgid", |
229 | target, group); |
230 | mode &= ~S_ISGID0002000; |
231 | } else |
232 | message(MT_NOTICE0x0100, |
233 | "%s: unknown group \"%s\"", |
234 | target, group); |
235 | } |
236 | |
237 | if (userid && gid != (gid_t)-1 && gid != primegid) { |
238 | for (i = 0; i < gidsetlen; i++) { |
239 | if (gid == gidset[i]) |
240 | goto ok; |
241 | } |
242 | if (mode != -1 && IS_ON(mode, S_ISGID)(mode & 0002000)) { |
243 | message(MT_NOTICE0x0100, |
244 | "%s: user %s not in group %s, clearing setgid", |
245 | target, locuser, group); |
246 | mode &= ~S_ISGID0002000; |
247 | } |
248 | gid = (gid_t)-1; |
249 | } |
250 | ok: |
251 | if (stat(file, &st) == -1) { |
252 | error("%s: Stat failed %s", file, SYSERRstrerror((*__errno()))); |
253 | return -1; |
254 | } |
255 | /* |
256 | * Set uid and gid ownership. If that fails, strip setuid and |
257 | * setgid bits from mode. Once ownership is set, successful |
258 | * or otherwise, set the new file mode. |
259 | */ |
260 | if (setownership(file, fd, uid, gid, S_ISLNK(st.st_mode)((st.st_mode & 0170000) == 0120000)) < 0) { |
261 | if (mode != -1 && IS_ON(mode, S_ISUID)(mode & 0004000)) { |
262 | message(MT_NOTICE0x0100, |
263 | "%s: chown failed, clearing setuid", target); |
264 | mode &= ~S_ISUID0004000; |
265 | } |
266 | if (mode != -1 && IS_ON(mode, S_ISGID)(mode & 0002000)) { |
267 | message(MT_NOTICE0x0100, |
268 | "%s: chown failed, clearing setgid", target); |
269 | mode &= ~S_ISGID0002000; |
270 | } |
271 | } |
272 | (void) setfilemode(file, fd, mode, S_ISLNK(st.st_mode)((st.st_mode & 0170000) == 0120000)); |
273 | |
274 | |
275 | return(0); |
276 | } |
277 | |
278 | /* |
279 | * Remove a file or directory (recursively) and send back an acknowledge |
280 | * or an error message. |
281 | */ |
282 | static int |
283 | removefile(struct stat *statb, int silent) |
284 | { |
285 | DIR *d; |
286 | static struct dirent *dp; |
287 | char *cp; |
288 | struct stat stb; |
289 | char *optarget; |
290 | int len, failures = 0; |
291 | |
292 | switch (statb->st_mode & S_IFMT0170000) { |
293 | case S_IFREG0100000: |
294 | case S_IFLNK0120000: |
295 | case S_IFCHR0020000: |
296 | case S_IFBLK0060000: |
297 | case S_IFSOCK0140000: |
298 | case S_IFIFO0010000: |
299 | if (unlink(target) == -1) { |
300 | if (errno(*__errno()) == ETXTBSY26) { |
301 | if (!silent) |
302 | message(MT_REMOTE0x0400|MT_NOTICE0x0100, |
303 | "%s: unlink failed: %s", |
304 | target, SYSERRstrerror((*__errno()))); |
305 | return(0); |
306 | } else { |
307 | error("%s: unlink failed: %s", target, SYSERRstrerror((*__errno()))); |
308 | return(-1); |
309 | } |
310 | } |
311 | goto removed; |
312 | |
313 | case S_IFDIR0040000: |
314 | break; |
315 | |
316 | default: |
317 | error("%s: not a plain file", target); |
318 | return(-1); |
319 | } |
320 | |
321 | errno(*__errno()) = 0; |
322 | if ((d = opendir(target)) == NULL((void *)0)) { |
323 | error("%s: opendir failed: %s", target, SYSERRstrerror((*__errno()))); |
324 | return(-1); |
325 | } |
326 | |
327 | optarget = ptarget; |
328 | len = ptarget - target; |
329 | while ((dp = readdir(d)) != NULL((void *)0)) { |
330 | if (dp->d_name[0] == '.' && (dp->d_name[1] == '\0' || |
331 | (dp->d_name[1] == '.' && dp->d_name[2] == '\0'))) |
332 | continue; |
333 | |
334 | if (len + 1 + (int)strlen(dp->d_name) >= PATH_MAX1024 - 1) { |
335 | if (!silent) |
336 | message(MT_REMOTE0x0400|MT_WARNING0x0010, |
337 | "%s/%s: Name too long", |
338 | target, dp->d_name); |
339 | continue; |
340 | } |
341 | ptarget = optarget; |
342 | *ptarget++ = '/'; |
343 | cp = dp->d_name; |
344 | while ((*ptarget++ = *cp++) != '\0') |
345 | continue; |
346 | ptarget--; |
347 | if (lstat(target, &stb) == -1) { |
348 | if (!silent) |
349 | message(MT_REMOTE0x0400|MT_WARNING0x0010, |
350 | "%s: lstat failed: %s", |
351 | target, SYSERRstrerror((*__errno()))); |
352 | continue; |
353 | } |
354 | if (removefile(&stb, 0) < 0) |
355 | ++failures; |
356 | } |
357 | (void) closedir(d); |
358 | ptarget = optarget; |
359 | *ptarget = CNULL'\0'; |
360 | |
361 | if (failures) |
362 | return(-1); |
363 | |
364 | if (rmdir(target) == -1) { |
365 | error("%s: rmdir failed: %s", target, SYSERRstrerror((*__errno()))); |
366 | return(-1); |
367 | } |
368 | removed: |
369 | #if NEWWAY |
370 | if (!silent) |
371 | message(MT_CHANGE0x0020|MT_REMOTE0x0400, "%s: removed", target); |
372 | #else |
373 | /* |
374 | * We use MT_NOTICE instead of MT_CHANGE because this function is |
375 | * sometimes called by other functions that are suppose to return a |
376 | * single ack() back to the client (rdist). This is a kludge until |
377 | * the Rdist protocol is re-done. Sigh. |
378 | */ |
379 | message(MT_NOTICE0x0100|MT_REMOTE0x0400, "%s: removed", target); |
380 | #endif |
381 | return(0); |
382 | } |
383 | |
384 | /* |
385 | * Check the current directory (initialized by the 'T' command to server()) |
386 | * for extraneous files and remove them. |
387 | */ |
388 | static void |
389 | doclean(char *cp) |
390 | { |
391 | DIR *d; |
392 | struct dirent *dp; |
393 | struct stat stb; |
394 | char *optarget, *ep; |
395 | int len; |
396 | opt_t opts; |
397 | char targ[PATH_MAX1024*4]; |
398 | |
399 | opts = strtol(cp, &ep, 8); |
400 | if (*ep != CNULL'\0') { |
401 | error("clean: options not delimited"); |
402 | return; |
403 | } |
404 | if ((d = opendir(target)) == NULL((void *)0)) { |
405 | error("%s: opendir failed: %s", target, SYSERRstrerror((*__errno()))); |
406 | return; |
407 | } |
408 | ack()(void) sendcmd('\5', ((void *)0)); |
409 | |
410 | optarget = ptarget; |
411 | len = ptarget - target; |
412 | while ((dp = readdir(d)) != NULL((void *)0)) { |
413 | if (dp->d_name[0] == '.' && (dp->d_name[1] == '\0' || |
414 | (dp->d_name[1] == '.' && dp->d_name[2] == '\0'))) |
415 | continue; |
416 | |
417 | if (len + 1 + (int)strlen(dp->d_name) >= PATH_MAX1024 - 1) { |
418 | message(MT_REMOTE0x0400|MT_WARNING0x0010, "%s/%s: Name too long", |
419 | target, dp->d_name); |
420 | continue; |
421 | } |
422 | ptarget = optarget; |
423 | *ptarget++ = '/'; |
424 | cp = dp->d_name; |
425 | while ((*ptarget++ = *cp++) != '\0') |
426 | continue; |
427 | ptarget--; |
428 | if (lstat(target, &stb) == -1) { |
429 | message(MT_REMOTE0x0400|MT_WARNING0x0010, "%s: lstat failed: %s", |
430 | target, SYSERRstrerror((*__errno()))); |
431 | continue; |
432 | } |
433 | |
434 | ENCODE(targ, dp->d_name)strvis(targ, dp->d_name, (0x04 | 0x08 | 0x10)); |
435 | (void) sendcmd(CC_QUERY'Q', "%s", targ); |
436 | (void) remline(cp = buf, sizeof(buf), TRUE1); |
437 | |
438 | if (*cp != CC_YES'Y') |
439 | continue; |
440 | |
441 | if (IS_ON(opts, DO_VERIFY)(opts & 0x0000001)) |
442 | message(MT_REMOTE0x0400|MT_INFO0x0040, "%s: need to remove", |
443 | target); |
444 | else |
445 | (void) removefile(&stb, 0); |
446 | } |
447 | (void) closedir(d); |
448 | |
449 | ptarget = optarget; |
450 | *ptarget = CNULL'\0'; |
451 | } |
452 | |
453 | /* |
454 | * Frontend to doclean(). |
455 | */ |
456 | static void |
457 | clean(char *cp) |
458 | { |
459 | doclean(cp); |
460 | (void) sendcmd(CC_END'E', NULL((void *)0)); |
461 | (void) response(); |
462 | } |
463 | |
464 | /* |
465 | * Execute a shell command to handle special cases. |
466 | * We can't really set an alarm timeout here since we |
467 | * have no idea how long the command should take. |
468 | */ |
469 | static void |
470 | dospecial(char *xcmd) |
471 | { |
472 | char cmd[BUFSIZ1024]; |
473 | if (DECODE(cmd, xcmd)strunvis(cmd, xcmd) == -1) { |
474 | error("dospecial: Cannot decode command."); |
475 | return; |
476 | } |
477 | runcommand(cmd); |
478 | } |
479 | |
480 | /* |
481 | * Do a special cmd command. This differs from normal special |
482 | * commands in that it's done after an entire command has been updated. |
483 | * The list of updated target files is sent one at a time with RC_FILE |
484 | * commands. Each one is added to an environment variable defined by |
485 | * E_FILES. When an RC_COMMAND is finally received, the E_FILES variable |
486 | * is stuffed into our environment and a normal dospecial() command is run. |
487 | */ |
488 | static void |
489 | docmdspecial(void) |
490 | { |
491 | char *cp; |
492 | char *cmd, *env = NULL((void *)0); |
493 | int n; |
494 | size_t len; |
495 | |
496 | /* We're ready */ |
497 | ack()(void) sendcmd('\5', ((void *)0)); |
498 | |
499 | for ( ; ; ) { |
500 | n = remline(cp = buf, sizeof(buf), FALSE0); |
501 | if (n <= 0) { |
502 | error("cmdspecial: premature end of input."); |
503 | return; |
504 | } |
505 | |
506 | switch (*cp++) { |
507 | case RC_FILE'F': |
508 | if (env == NULL((void *)0)) { |
509 | len = (2 * sizeof(E_FILES"FILES")) + strlen(cp) + 10; |
510 | env = xmalloc(len); |
511 | (void) snprintf(env, len, "export %s;%s=%s", |
512 | E_FILES"FILES", E_FILES"FILES", cp); |
513 | } else { |
514 | len = strlen(env) + 1 + strlen(cp) + 1; |
515 | env = xrealloc(env, len); |
516 | (void) strlcat(env, ":", len); |
517 | (void) strlcat(env, cp, len); |
518 | } |
519 | ack()(void) sendcmd('\5', ((void *)0)); |
520 | break; |
521 | |
522 | case RC_COMMAND'C': |
523 | if (env) { |
524 | len = strlen(env) + 1 + strlen(cp) + 1; |
525 | env = xrealloc(env, len); |
526 | (void) strlcat(env, ";", len); |
527 | (void) strlcat(env, cp, len); |
528 | cmd = env; |
529 | } else |
530 | cmd = cp; |
531 | |
532 | dospecial(cmd); |
533 | if (env) |
534 | (void) free(env); |
535 | return; |
536 | |
537 | default: |
538 | error("Unknown cmdspecial command '%s'.", cp); |
539 | return; |
540 | } |
541 | } |
542 | } |
543 | |
544 | /* |
545 | * Query. Check to see if file exists. Return one of the following: |
546 | * |
547 | * QC_ONNFS - resides on a NFS |
548 | * QC_ONRO - resides on a Read-Only filesystem |
549 | * QC_NO - doesn't exist |
550 | * QC_YESsize mtime - exists and its a regular file (size & mtime of file) |
551 | * QC_YES - exists and its a directory or symbolic link |
552 | * QC_ERRMSGmessage - error message |
553 | */ |
554 | static void |
555 | query(char *xname) |
556 | { |
557 | static struct stat stb; |
558 | int s = -1, stbvalid = 0; |
559 | char name[PATH_MAX1024]; |
560 | |
561 | if (DECODE(name, xname)strunvis(name, xname) == -1) { |
562 | error("query: Cannot decode filename"); |
563 | return; |
564 | } |
565 | |
566 | if (catname && cattarget(name) < 0) |
567 | return; |
568 | |
569 | if (IS_ON(options, DO_CHKNFS)(options & 0x0000200)) { |
570 | s = is_nfs_mounted(target, &stb, &stbvalid); |
571 | if (s > 0) |
572 | (void) sendcmd(QC_ONNFS'F', NULL((void *)0)); |
573 | |
574 | /* Either the above check was true or an error occurred */ |
575 | /* and is_nfs_mounted sent the error message */ |
576 | if (s != 0) { |
577 | *ptarget = CNULL'\0'; |
578 | return; |
579 | } |
580 | } |
581 | |
582 | if (IS_ON(options, DO_CHKREADONLY)(options & 0x0000400)) { |
583 | s = is_ro_mounted(target, &stb, &stbvalid); |
584 | if (s > 0) |
585 | (void) sendcmd(QC_ONRO'O', NULL((void *)0)); |
586 | |
587 | /* Either the above check was true or an error occurred */ |
588 | /* and is_ro_mounted sent the error message */ |
589 | if (s != 0) { |
590 | *ptarget = CNULL'\0'; |
591 | return; |
592 | } |
593 | } |
594 | |
595 | if (IS_ON(options, DO_CHKSYM)(options & 0x0020000)) { |
596 | if (is_symlinked(target, &stb, &stbvalid) > 0) { |
597 | (void) sendcmd(QC_SYM'l', NULL((void *)0)); |
598 | return; |
599 | } |
600 | } |
601 | |
602 | /* |
603 | * If stbvalid is false, "stb" is not valid because the stat() |
604 | * by is_*_mounted() either failed or does not match "target". |
605 | */ |
606 | if (!stbvalid && lstat(target, &stb) == -1) { |
607 | if (errno(*__errno()) == ENOENT2) |
608 | (void) sendcmd(QC_NO'N', NULL((void *)0)); |
609 | else |
610 | error("%s: lstat failed: %s", target, SYSERRstrerror((*__errno()))); |
611 | *ptarget = CNULL'\0'; |
612 | return; |
613 | } |
614 | |
615 | switch (stb.st_mode & S_IFMT0170000) { |
616 | case S_IFLNK0120000: |
617 | case S_IFDIR0040000: |
618 | case S_IFREG0100000: |
619 | (void) sendcmd(QC_YES'Y', "%lld %lld %o %s %s", |
620 | (long long) stb.st_size, |
621 | (long long) stb.st_mtimest_mtim.tv_sec, |
622 | stb.st_mode & 07777, |
623 | getusername(stb.st_uid, target, options), |
624 | getgroupname(stb.st_gid, target, options)); |
625 | break; |
626 | |
627 | default: |
628 | error("%s: not a file or directory", target); |
629 | break; |
630 | } |
631 | *ptarget = CNULL'\0'; |
632 | } |
633 | |
634 | /* |
635 | * Check to see if parent directory exists and create one if not. |
636 | */ |
637 | static int |
638 | chkparent(char *name, opt_t opts) |
639 | { |
640 | char *cp; |
641 | struct stat stb; |
642 | int r = -1; |
643 | |
644 | debugmsg(DM_CALL0x01, "chkparent(%s, %#x) start\n", name, opts); |
645 | |
646 | cp = strrchr(name, '/'); |
647 | if (cp == NULL((void *)0) || cp == name) |
648 | return(0); |
649 | |
650 | *cp = CNULL'\0'; |
651 | |
652 | if (lstat(name, &stb) == -1) { |
653 | if (errno(*__errno()) == ENOENT2 && chkparent(name, opts) >= 0) { |
654 | if (mkdir(name, 0777 & ~oumask) == 0) { |
655 | message(MT_NOTICE0x0100, "%s: mkdir", name); |
656 | r = 0; |
657 | } else |
658 | debugmsg(DM_MISC0x10, |
659 | "chkparent(%s, %#04o) mkdir fail: %s\n", |
660 | name, opts, SYSERRstrerror((*__errno()))); |
661 | } |
662 | } else /* It exists */ |
663 | r = 0; |
664 | |
665 | /* Put back what we took away */ |
666 | *cp = '/'; |
667 | |
668 | return(r); |
669 | } |
670 | |
671 | /* |
672 | * Save a copy of 'file' by renaming it. |
673 | */ |
674 | static char * |
675 | savetarget(char *file, opt_t opts) |
676 | { |
677 | static char savefile[PATH_MAX1024]; |
678 | |
679 | if (strlen(file) + sizeof(SAVE_SUFFIX".OLD") + 1 > PATH_MAX1024) { |
680 | error("%s: Cannot save: Save name too long", file); |
681 | return(NULL((void *)0)); |
682 | } |
683 | |
684 | if (IS_ON(opts, DO_HISTORY)(opts & 0x0100000)) { |
685 | int i; |
686 | struct stat st; |
687 | /* |
688 | * There is a race here, but the worst that can happen |
689 | * is to lose a version of the file |
690 | */ |
691 | for (i = 1; i < 1000; i++) { |
692 | (void) snprintf(savefile, sizeof(savefile), |
693 | "%s;%.3d", file, i); |
694 | if (lstat(savefile, &st) == -1 && errno(*__errno()) == ENOENT2) |
695 | break; |
696 | |
697 | } |
698 | if (i == 1000) { |
699 | message(MT_NOTICE0x0100, |
700 | "%s: More than 1000 versions for %s; reusing 1\n", |
701 | savefile, SYSERRstrerror((*__errno()))); |
702 | i = 1; |
703 | (void) snprintf(savefile, sizeof(savefile), |
704 | "%s;%.3d", file, i); |
705 | } |
706 | } |
707 | else { |
708 | (void) snprintf(savefile, sizeof(savefile), "%s%s", |
709 | file, SAVE_SUFFIX".OLD"); |
710 | |
711 | if (unlink(savefile) != 0 && errno(*__errno()) != ENOENT2) { |
712 | message(MT_NOTICE0x0100, "%s: remove failed: %s", |
713 | savefile, SYSERRstrerror((*__errno()))); |
714 | return(NULL((void *)0)); |
715 | } |
716 | } |
717 | |
718 | if (rename(file, savefile) != 0 && errno(*__errno()) != ENOENT2) { |
719 | error("%s -> %s: rename failed: %s", |
720 | file, savefile, SYSERRstrerror((*__errno()))); |
721 | return(NULL((void *)0)); |
722 | } |
723 | |
724 | return(savefile); |
725 | } |
726 | |
727 | /* |
728 | * Receive a file |
729 | */ |
730 | static void |
731 | recvfile(char *new, opt_t opts, int mode, char *owner, char *group, |
732 | time_t mtime, time_t atime, off_t size) |
733 | { |
734 | int f, wrerr, olderrno; |
735 | off_t i; |
736 | char *cp; |
737 | char *savefile = NULL((void *)0); |
738 | static struct stat statbuff; |
739 | |
740 | /* |
741 | * Create temporary file |
742 | */ |
743 | if (chkparent(new, opts) < 0 || (f = mkstemp(new)) == -1) { |
744 | error("%s: create failed: %s", new, SYSERRstrerror((*__errno()))); |
745 | return; |
746 | } |
747 | |
748 | /* |
749 | * Receive the file itself |
750 | */ |
751 | ack()(void) sendcmd('\5', ((void *)0)); |
752 | wrerr = 0; |
753 | olderrno = 0; |
754 | for (i = 0; i < size; i += BUFSIZ1024) { |
755 | off_t amt = BUFSIZ1024; |
756 | |
757 | cp = buf; |
758 | if (i + amt > size) |
759 | amt = size - i; |
760 | do { |
761 | ssize_t j; |
762 | |
763 | j = readrem(cp, amt); |
764 | if (j <= 0) { |
765 | (void) close(f); |
766 | (void) unlink(new); |
767 | fatalerr( |
768 | "Read error occurred while receiving file."); |
769 | finish(); |
770 | } |
771 | amt -= j; |
772 | cp += j; |
773 | } while (amt > 0); |
774 | amt = BUFSIZ1024; |
775 | if (i + amt > size) |
776 | amt = size - i; |
777 | if (wrerr == 0 && xwrite(f, buf, amt) != amt) { |
778 | olderrno = errno(*__errno()); |
779 | wrerr++; |
780 | } |
781 | } |
782 | |
783 | if (response() < 0) { |
784 | (void) close(f); |
785 | (void) unlink(new); |
786 | return; |
787 | } |
788 | |
789 | if (wrerr) { |
790 | error("%s: Write error: %s", new, strerror(olderrno)); |
791 | (void) close(f); |
792 | (void) unlink(new); |
793 | return; |
794 | } |
795 | |
796 | /* |
797 | * Do file comparison if enabled |
798 | */ |
799 | if (IS_ON(opts, DO_COMPARE)(opts & 0x0000008)) { |
800 | FILE *f1, *f2; |
801 | int c; |
802 | |
803 | errno(*__errno()) = 0; /* fopen is not a syscall */ |
804 | if ((f1 = fopen(target, "r")) == NULL((void *)0)) { |
805 | error("%s: open for read failed: %s", target, SYSERRstrerror((*__errno()))); |
806 | (void) close(f); |
807 | (void) unlink(new); |
808 | return; |
809 | } |
810 | errno(*__errno()) = 0; |
811 | if ((f2 = fopen(new, "r")) == NULL((void *)0)) { |
812 | error("%s: open for read failed: %s", new, SYSERRstrerror((*__errno()))); |
813 | (void) fclose(f1); |
814 | (void) close(f); |
815 | (void) unlink(new); |
816 | return; |
817 | } |
818 | while ((c = getc(f1)(!__isthreaded ? (--(f1)->_r < 0 ? __srget(f1) : (int)( *(f1)->_p++)) : (getc)(f1))) == getc(f2)(!__isthreaded ? (--(f2)->_r < 0 ? __srget(f2) : (int)( *(f2)->_p++)) : (getc)(f2))) |
819 | if (c == EOF(-1)) { |
820 | debugmsg(DM_MISC0x10, |
821 | "Files are the same '%s' '%s'.", |
822 | target, new); |
823 | (void) fclose(f1); |
824 | (void) fclose(f2); |
825 | (void) close(f); |
826 | (void) unlink(new); |
827 | /* |
828 | * This isn't an error per-se, but we |
829 | * need to indicate to the master that |
830 | * the file was not updated. |
831 | */ |
832 | error(NULL((void *)0)); |
833 | return; |
834 | } |
835 | debugmsg(DM_MISC0x10, "Files are different '%s' '%s'.", |
836 | target, new); |
837 | (void) fclose(f1); |
838 | (void) fclose(f2); |
839 | if (IS_ON(opts, DO_VERIFY)(opts & 0x0000001)) { |
840 | message(MT_REMOTE0x0400|MT_INFO0x0040, "%s: need to update", |
841 | target); |
842 | (void) close(f); |
843 | (void) unlink(new); |
844 | return; |
845 | } |
846 | } |
847 | |
848 | /* |
849 | * Set owner, group, and file mode |
850 | */ |
851 | if (fchog(f, new, owner, group, mode) < 0) { |
852 | (void) close(f); |
853 | (void) unlink(new); |
854 | return; |
855 | } |
856 | (void) close(f); |
857 | |
858 | /* |
859 | * Perform utimes() after file is closed to make |
860 | * certain OS's, such as NeXT 2.1, happy. |
861 | */ |
862 | if (setfiletime(new, time(NULL((void *)0)), mtime) < 0) |
863 | message(MT_NOTICE0x0100, "%s: utimes failed: %s", new, SYSERRstrerror((*__errno()))); |
864 | |
865 | /* |
866 | * Try to save target file from being over-written |
867 | */ |
868 | if (IS_ON(opts, DO_SAVETARGETS)(opts & 0x0001000)) |
869 | if ((savefile = savetarget(target, opts)) == NULL((void *)0)) { |
870 | (void) unlink(new); |
871 | return; |
872 | } |
873 | |
874 | /* |
875 | * If the target is a directory, we need to remove it first |
876 | * before we can rename the new file. |
877 | */ |
878 | if ((stat(target, &statbuff) == 0) && S_ISDIR(statbuff.st_mode)((statbuff.st_mode & 0170000) == 0040000)) { |
879 | char *saveptr = ptarget; |
880 | |
881 | ptarget = &target[strlen(target)]; |
882 | removefile(&statbuff, 0); |
883 | ptarget = saveptr; |
884 | } |
885 | |
886 | /* |
887 | * Install new (temporary) file as the actual target |
888 | */ |
889 | if (rename(new, target) == -1) { |
890 | static const char fmt[] = "%s -> %s: rename failed: %s"; |
891 | struct stat stb; |
892 | /* |
893 | * If the rename failed due to "Text file busy", then |
894 | * try to rename the target file and retry the rename. |
895 | */ |
896 | switch (errno(*__errno())) { |
897 | case ETXTBSY26: |
898 | /* Save the target */ |
899 | if ((savefile = savetarget(target, opts)) != NULL((void *)0)) { |
900 | /* Retry installing new file as target */ |
901 | if (rename(new, target) == -1) { |
902 | error(fmt, new, target, SYSERRstrerror((*__errno()))); |
903 | /* Try to put back save file */ |
904 | if (rename(savefile, target) == -1) |
905 | error(fmt, |
906 | savefile, target, SYSERRstrerror((*__errno()))); |
907 | (void) unlink(new); |
908 | } else |
909 | message(MT_NOTICE0x0100, "%s: renamed to %s", |
910 | target, savefile); |
911 | /* |
912 | * XXX: We should remove the savefile here. |
913 | * But we are nice to nfs clients and |
914 | * we keep it. |
915 | */ |
916 | } |
917 | break; |
918 | case EISDIR21: |
919 | /* |
920 | * See if target is a directory and remove it if it is |
921 | */ |
922 | if (lstat(target, &stb) == 0) { |
923 | if (S_ISDIR(stb.st_mode)((stb.st_mode & 0170000) == 0040000)) { |
924 | char *optarget = ptarget; |
925 | for (ptarget = target; *ptarget; |
926 | ptarget++); |
927 | /* If we failed to remove, we'll catch |
928 | it later */ |
929 | (void) removefile(&stb, 1); |
930 | ptarget = optarget; |
931 | } |
932 | } |
933 | if (rename(new, target) >= 0) |
934 | break; |
935 | /*FALLTHROUGH*/ |
936 | |
937 | default: |
938 | error(fmt, new, target, SYSERRstrerror((*__errno()))); |
939 | (void) unlink(new); |
940 | break; |
941 | } |
942 | } |
943 | |
944 | if (IS_ON(opts, DO_COMPARE)(opts & 0x0000008)) |
945 | message(MT_REMOTE0x0400|MT_CHANGE0x0020, "%s: updated", target); |
946 | else |
947 | ack()(void) sendcmd('\5', ((void *)0)); |
948 | } |
949 | |
950 | /* |
951 | * Receive a directory |
952 | */ |
953 | static void |
954 | recvdir(opt_t opts, int mode, char *owner, char *group) |
955 | { |
956 | static char lowner[100], lgroup[100]; |
957 | char *cp; |
958 | struct stat stb; |
959 | int s; |
960 | |
961 | s = lstat(target, &stb); |
962 | if (s == 0) { |
963 | /* |
964 | * If target is not a directory, remove it |
965 | */ |
966 | if (!S_ISDIR(stb.st_mode)((stb.st_mode & 0170000) == 0040000)) { |
967 | if (IS_ON(opts, DO_VERIFY)(opts & 0x0000001)) |
968 | message(MT_NOTICE0x0100, "%s: need to remove", |
969 | target); |
970 | else { |
971 | if (unlink(target) == -1) { |
972 | error("%s: remove failed: %s", |
973 | target, SYSERRstrerror((*__errno()))); |
974 | return; |
975 | } |
976 | } |
977 | s = -1; |
978 | errno(*__errno()) = ENOENT2; |
979 | } else { |
980 | if (!IS_ON(opts, DO_NOCHKMODE)(opts & 0x0008000) && |
981 | (stb.st_mode & 07777) != mode) { |
982 | if (IS_ON(opts, DO_VERIFY)(opts & 0x0000001)) |
983 | message(MT_NOTICE0x0100, |
984 | "%s: need to chmod to %#04o", |
985 | target, mode); |
986 | else if (chmod(target, mode) != 0) |
987 | message(MT_NOTICE0x0100, |
988 | "%s: chmod from %#04o to %#04o failed: %s", |
989 | target, |
990 | stb.st_mode & 07777, |
991 | mode, |
992 | SYSERRstrerror((*__errno()))); |
993 | else |
994 | message(MT_NOTICE0x0100, |
995 | "%s: chmod from %#04o to %#04o", |
996 | target, |
997 | stb.st_mode & 07777, |
998 | mode); |
999 | } |
1000 | |
1001 | /* |
1002 | * Check ownership and set if necessary |
1003 | */ |
1004 | lowner[0] = CNULL'\0'; |
1005 | lgroup[0] = CNULL'\0'; |
1006 | |
1007 | if (!IS_ON(opts, DO_NOCHKOWNER)(opts & 0x0004000) && owner) { |
1008 | int o; |
1009 | |
1010 | o = (owner[0] == ':') ? opts & DO_NUMCHKOWNER0x0080000 : |
1011 | opts; |
1012 | if ((cp = getusername(stb.st_uid, target, o)) |
1013 | != NULL((void *)0)) |
1014 | if (strcmp(owner, cp)) |
1015 | (void) strlcpy(lowner, cp, |
1016 | sizeof(lowner)); |
1017 | } |
1018 | if (!IS_ON(opts, DO_NOCHKGROUP)(opts & 0x0010000) && group) { |
1019 | int o; |
1020 | |
1021 | o = (group[0] == ':') ? opts & DO_NUMCHKGROUP0x0040000 : |
1022 | opts; |
1023 | if ((cp = getgroupname(stb.st_gid, target, o)) |
1024 | != NULL((void *)0)) |
1025 | if (strcmp(group, cp)) |
1026 | (void) strlcpy(lgroup, cp, |
1027 | sizeof(lgroup)); |
1028 | } |
1029 | |
1030 | /* |
1031 | * Need to set owner and/or group |
1032 | */ |
1033 | #define PRN(n) ((n[0] == ':') ? n+1 : n) |
1034 | if (lowner[0] != CNULL'\0' || lgroup[0] != CNULL'\0') { |
1035 | if (lowner[0] == CNULL'\0' && |
1036 | (cp = getusername(stb.st_uid, |
1037 | target, opts))) |
1038 | (void) strlcpy(lowner, cp, |
1039 | sizeof(lowner)); |
1040 | if (lgroup[0] == CNULL'\0' && |
1041 | (cp = getgroupname(stb.st_gid, |
1042 | target, opts))) |
1043 | (void) strlcpy(lgroup, cp, |
1044 | sizeof(lgroup)); |
1045 | |
1046 | if (IS_ON(opts, DO_VERIFY)(opts & 0x0000001)) |
1047 | message(MT_NOTICE0x0100, |
1048 | "%s: need to chown from %s:%s to %s:%s", |
1049 | target, |
1050 | PRN(lowner), PRN(lgroup), |
1051 | PRN(owner), PRN(group)); |
1052 | else { |
1053 | if (fchog(-1, target, owner, |
1054 | group, -1) == 0) |
1055 | message(MT_NOTICE0x0100, |
1056 | "%s: chown from %s:%s to %s:%s", |
1057 | target, |
1058 | PRN(lowner), |
1059 | PRN(lgroup), |
1060 | PRN(owner), |
1061 | PRN(group)); |
1062 | } |
1063 | } |
1064 | #undef PRN |
1065 | ack()(void) sendcmd('\5', ((void *)0)); |
1066 | return; |
1067 | } |
1068 | } |
1069 | |
1070 | if (IS_ON(opts, DO_VERIFY)(opts & 0x0000001)) { |
1071 | ack()(void) sendcmd('\5', ((void *)0)); |
1072 | return; |
1073 | } |
1074 | |
1075 | /* |
1076 | * Create the directory |
1077 | */ |
1078 | if (s < 0) { |
1079 | if (errno(*__errno()) == ENOENT2) { |
1080 | if (mkdir(target, mode) == 0 || |
1081 | (chkparent(target, opts) == 0 && |
1082 | mkdir(target, mode) == 0)) { |
1083 | message(MT_NOTICE0x0100, "%s: mkdir", target); |
1084 | (void) fchog(-1, target, owner, group, mode); |
1085 | ack()(void) sendcmd('\5', ((void *)0)); |
1086 | } else { |
1087 | error("%s: mkdir failed: %s", target, SYSERRstrerror((*__errno()))); |
1088 | ptarget = sptarget[--catname]; |
1089 | *ptarget = CNULL'\0'; |
1090 | } |
1091 | return; |
1092 | } |
1093 | } |
1094 | error("%s: lstat failed: %s", target, SYSERRstrerror((*__errno()))); |
1095 | ptarget = sptarget[--catname]; |
1096 | *ptarget = CNULL'\0'; |
1097 | } |
1098 | |
1099 | /* |
1100 | * Receive a link |
1101 | */ |
1102 | static void |
1103 | recvlink(char *new, opt_t opts, int mode, off_t size) |
1104 | { |
1105 | char tbuf[PATH_MAX1024], dbuf[BUFSIZ1024]; |
1106 | struct stat stb; |
1107 | char *optarget; |
1108 | int uptodate; |
1109 | off_t i; |
1110 | |
1111 | /* |
1112 | * Read basic link info |
1113 | */ |
1114 | ack()(void) sendcmd('\5', ((void *)0)); |
1115 | (void) remline(buf, sizeof(buf), TRUE1); |
1116 | |
1117 | if (response() < 0) { |
1118 | err()(void) sendcmd('\1', ((void *)0)); |
1119 | return; |
1120 | } |
1121 | |
1122 | if (DECODE(dbuf, buf)strunvis(dbuf, buf) == -1) { |
1123 | error("recvlink: cannot decode symlink target"); |
1124 | return; |
1125 | } |
1126 | |
1127 | uptodate = 0; |
1128 | if ((i = readlink(target, tbuf, sizeof(tbuf)-1)) != -1) { |
1129 | tbuf[i] = '\0'; |
1130 | if (i == size && strncmp(dbuf, tbuf, (int) size) == 0) |
1131 | uptodate = 1; |
1132 | } |
1133 | mode &= 0777; |
1134 | |
1135 | if (IS_ON(opts, DO_VERIFY)(opts & 0x0000001) || uptodate) { |
1136 | if (uptodate) |
1137 | message(MT_REMOTE0x0400|MT_INFO0x0040, NULL((void *)0)); |
1138 | else |
1139 | message(MT_REMOTE0x0400|MT_INFO0x0040, "%s: need to update", |
1140 | target); |
1141 | if (IS_ON(opts, DO_COMPARE)(opts & 0x0000008)) |
1142 | return; |
1143 | (void) sendcmd(C_END'E', NULL((void *)0)); |
1144 | (void) response(); |
1145 | return; |
1146 | } |
1147 | |
1148 | /* |
1149 | * Make new symlink using a temporary name |
1150 | */ |
1151 | if (chkparent(new, opts) < 0 || mktemp(new) == NULL((void *)0) || |
Call to function 'mktemp' is insecure as it always creates or uses insecure temporary file. Use 'mkstemp' instead | |
1152 | symlink(dbuf, new) == -1) { |
1153 | error("%s -> %s: symlink failed: %s", new, dbuf, SYSERRstrerror((*__errno()))); |
1154 | return; |
1155 | } |
1156 | |
1157 | /* |
1158 | * See if target is a directory and remove it if it is |
1159 | */ |
1160 | if (lstat(target, &stb) == 0) { |
1161 | if (S_ISDIR(stb.st_mode)((stb.st_mode & 0170000) == 0040000)) { |
1162 | optarget = ptarget; |
1163 | for (ptarget = target; *ptarget; ptarget++); |
1164 | if (removefile(&stb, 0) < 0) { |
1165 | ptarget = optarget; |
1166 | (void) unlink(new); |
1167 | (void) sendcmd(C_END'E', NULL((void *)0)); |
1168 | (void) response(); |
1169 | return; |
1170 | } |
1171 | ptarget = optarget; |
1172 | } |
1173 | } |
1174 | |
1175 | /* |
1176 | * Install link as the target |
1177 | */ |
1178 | if (rename(new, target) == -1) { |
1179 | error("%s -> %s: symlink rename failed: %s", |
1180 | new, target, SYSERRstrerror((*__errno()))); |
1181 | (void) unlink(new); |
1182 | (void) sendcmd(C_END'E', NULL((void *)0)); |
1183 | (void) response(); |
1184 | return; |
1185 | } |
1186 | |
1187 | message(MT_REMOTE0x0400|MT_CHANGE0x0020, "%s: updated", target); |
1188 | |
1189 | /* |
1190 | * Indicate end of receive operation |
1191 | */ |
1192 | (void) sendcmd(C_END'E', NULL((void *)0)); |
1193 | (void) response(); |
1194 | } |
1195 | |
1196 | /* |
1197 | * Creat a hard link to existing file. |
1198 | */ |
1199 | static void |
1200 | hardlink(char *cmd) |
1201 | { |
1202 | struct stat stb; |
1203 | int exists = 0; |
1204 | char *xoldname, *xnewname; |
1205 | char *cp = cmd; |
1206 | static char expbuf[BUFSIZ1024]; |
1207 | char oldname[BUFSIZ1024], newname[BUFSIZ1024]; |
1208 | |
1209 | /* Skip over opts */ |
1210 | (void) strtol(cp, &cp, 8); |
1211 | if (*cp++ != ' ') { |
1212 | error("hardlink: options not delimited"); |
1213 | return; |
1214 | } |
1215 | |
1216 | xoldname = strtok(cp, " "); |
1217 | if (xoldname == NULL((void *)0)) { |
1218 | error("hardlink: oldname name not delimited"); |
1219 | return; |
1220 | } |
1221 | |
1222 | if (DECODE(oldname, xoldname)strunvis(oldname, xoldname) == -1) { |
1223 | error("hardlink: Cannot decode oldname"); |
1224 | return; |
1225 | } |
1226 | |
1227 | xnewname = strtok(NULL((void *)0), " "); |
1228 | if (xnewname == NULL((void *)0)) { |
1229 | error("hardlink: new name not specified"); |
1230 | return; |
1231 | } |
1232 | |
1233 | if (DECODE(newname, xnewname)strunvis(newname, xnewname) == -1) { |
1234 | error("hardlink: Cannot decode newname"); |
1235 | return; |
1236 | } |
1237 | |
1238 | if (exptilde(expbuf, oldname, sizeof(expbuf)) == NULL((void *)0)) { |
1239 | error("hardlink: tilde expansion failed"); |
1240 | return; |
1241 | } |
1242 | |
1243 | if (catname && cattarget(newname) < 0) { |
1244 | error("Cannot set newname target."); |
1245 | return; |
1246 | } |
1247 | |
1248 | if (lstat(target, &stb) == 0) { |
1249 | int mode = stb.st_mode & S_IFMT0170000; |
1250 | |
1251 | if (mode != S_IFREG0100000 && mode != S_IFLNK0120000) { |
1252 | error("%s: not a regular file", target); |
1253 | return; |
1254 | } |
1255 | exists = 1; |
1256 | } |
1257 | |
1258 | if (chkparent(target, options) < 0 ) { |
1259 | error("%s: no parent: %s ", target, SYSERRstrerror((*__errno()))); |
1260 | return; |
1261 | } |
1262 | if (exists && (unlink(target) == -1)) { |
1263 | error("%s: unlink failed: %s", target, SYSERRstrerror((*__errno()))); |
1264 | return; |
1265 | } |
1266 | if (linkat(AT_FDCWD-100, expbuf, AT_FDCWD-100, target, 0) == -1) { |
1267 | error("%s: cannot link to %s: %s", target, oldname, SYSERRstrerror((*__errno()))); |
1268 | return; |
1269 | } |
1270 | ack()(void) sendcmd('\5', ((void *)0)); |
1271 | } |
1272 | |
1273 | /* |
1274 | * Set configuration information. |
1275 | * |
1276 | * A key letter is followed immediately by the value |
1277 | * to set. The keys are: |
1278 | * SC_FREESPACE - Set minimum free space of filesystem |
1279 | * SC_FREEFILES - Set minimum free number of files of filesystem |
1280 | */ |
1281 | static void |
1282 | setconfig(char *cmd) |
1283 | { |
1284 | char *cp = cmd; |
1285 | char *estr; |
1286 | const char *errstr; |
1287 | |
1288 | switch (*cp++) { |
1289 | case SC_HOSTNAME'H': /* Set hostname */ |
1290 | /* |
1291 | * Only use info if we don't know who this is. |
1292 | */ |
1293 | if (!fromhost) { |
1294 | fromhost = xstrdup(cp); |
1295 | message(MT_SYSLOG0x0200, "startup for %s", fromhost); |
1296 | setproctitle("serving %s", cp); |
1297 | } |
1298 | break; |
1299 | |
1300 | case SC_FREESPACE's': /* Minimum free space */ |
1301 | min_freespace = (int64_t)strtonum(cp, 0, LLONG_MAX0x7fffffffffffffffLL, &errstr); |
1302 | if (errstr) |
1303 | fatalerr("Minimum free space is %s: '%s'", errstr, |
1304 | optarg); |
1305 | break; |
1306 | |
1307 | case SC_FREEFILES'f': /* Minimum free files */ |
1308 | min_freefiles = (int64_t)strtonum(cp, 0, LLONG_MAX0x7fffffffffffffffLL, &errstr); |
1309 | if (errstr) |
1310 | fatalerr("Minimum free files is %s: '%s'", errstr, |
1311 | optarg); |
1312 | break; |
1313 | |
1314 | case SC_LOGGING'L': /* Logging options */ |
1315 | if ((estr = msgparseopts(cp, TRUE1)) != NULL((void *)0)) { |
1316 | fatalerr("Bad message option string (%s): %s", |
1317 | cp, estr); |
1318 | return; |
1319 | } |
1320 | break; |
1321 | |
1322 | case SC_DEFOWNER'o': |
1323 | (void) strlcpy(defowner, cp, sizeof(defowner)); |
1324 | break; |
1325 | |
1326 | case SC_DEFGROUP'g': |
1327 | (void) strlcpy(defgroup, cp, sizeof(defgroup)); |
1328 | break; |
1329 | |
1330 | default: |
1331 | message(MT_NOTICE0x0100, "Unknown config command \"%s\".", cp-1); |
1332 | return; |
1333 | } |
1334 | } |
1335 | |
1336 | /* |
1337 | * Receive something |
1338 | */ |
1339 | static void |
1340 | recvit(char *cmd, int type) |
1341 | { |
1342 | int mode; |
1343 | opt_t opts; |
1344 | off_t size; |
1345 | time_t mtime, atime; |
1346 | char *owner, *group, *file; |
1347 | char new[PATH_MAX1024]; |
1348 | char fileb[PATH_MAX1024]; |
1349 | int64_t freespace = -1, freefiles = -1; |
1350 | char *cp = cmd; |
1351 | |
1352 | /* |
1353 | * Get rdist option flags |
1354 | */ |
1355 | opts = strtol(cp, &cp, 8); |
1356 | if (*cp++ != ' ') { |
1357 | error("recvit: options not delimited"); |
1358 | return; |
1359 | } |
1360 | |
1361 | /* |
1362 | * Get file mode |
1363 | */ |
1364 | mode = strtol(cp, &cp, 8); |
1365 | if (*cp++ != ' ') { |
1366 | error("recvit: mode not delimited"); |
1367 | return; |
1368 | } |
1369 | |
1370 | /* |
1371 | * Get file size |
1372 | */ |
1373 | size = (off_t) strtoll(cp, &cp, 10); |
1374 | if (*cp++ != ' ') { |
1375 | error("recvit: size not delimited"); |
1376 | return; |
1377 | } |
1378 | |
1379 | /* |
1380 | * Get modification time |
1381 | */ |
1382 | mtime = (time_t) strtoll(cp, &cp, 10); |
1383 | if (*cp++ != ' ') { |
1384 | error("recvit: mtime not delimited"); |
1385 | return; |
1386 | } |
1387 | |
1388 | /* |
1389 | * Get access time |
1390 | */ |
1391 | atime = (time_t) strtoll(cp, &cp, 10); |
1392 | if (*cp++ != ' ') { |
1393 | error("recvit: atime not delimited"); |
1394 | return; |
1395 | } |
1396 | |
1397 | /* |
1398 | * Get file owner name |
1399 | */ |
1400 | owner = strtok(cp, " "); |
1401 | if (owner == NULL((void *)0)) { |
1402 | error("recvit: owner name not delimited"); |
1403 | return; |
1404 | } |
1405 | |
1406 | /* |
1407 | * Get file group name |
1408 | */ |
1409 | group = strtok(NULL((void *)0), " "); |
1410 | if (group == NULL((void *)0)) { |
1411 | error("recvit: group name not delimited"); |
1412 | return; |
1413 | } |
1414 | |
1415 | /* |
1416 | * Get file name. Can't use strtok() since there could |
1417 | * be white space in the file name. |
1418 | */ |
1419 | if (DECODE(fileb, group + strlen(group) + 1)strunvis(fileb, group + strlen(group) + 1) == -1) { |
1420 | error("recvit: Cannot decode file name"); |
1421 | return; |
1422 | } |
1423 | |
1424 | if (fileb[0] == '\0') { |
1425 | error("recvit: no file name"); |
1426 | return; |
1427 | } |
1428 | file = fileb; |
1429 | |
1430 | debugmsg(DM_MISC0x10, |
1431 | "recvit: opts = %#x mode = %#04o size = %lld mtime = %lld", |
1432 | opts, mode, (long long) size, (long long)mtime); |
1433 | debugmsg(DM_MISC0x10, |
1434 | "recvit: owner = '%s' group = '%s' file = '%s' catname = %d isdir = %d", |
1435 | owner, group, file, catname, (type == S_IFDIR0040000) ? 1 : 0); |
1436 | |
1437 | if (type == S_IFDIR0040000) { |
1438 | if ((size_t) catname >= sizeof(sptarget)) { |
1439 | error("%s: too many directory levels", target); |
1440 | return; |
1441 | } |
1442 | sptarget[catname] = ptarget; |
1443 | if (catname++) { |
1444 | *ptarget++ = '/'; |
1445 | while ((*ptarget++ = *file++) != '\0') |
1446 | continue; |
1447 | ptarget--; |
1448 | } |
1449 | } else { |
1450 | /* |
1451 | * Create name of temporary file |
1452 | */ |
1453 | if (catname && cattarget(file) < 0) { |
1454 | error("Cannot set file name."); |
1455 | return; |
1456 | } |
1457 | file = strrchr(target, '/'); |
1458 | if (file == NULL((void *)0)) |
1459 | (void) strlcpy(new, tempname, sizeof(new)); |
1460 | else if (file == target) |
1461 | (void) snprintf(new, sizeof(new), "/%s", tempname); |
1462 | else { |
1463 | *file = CNULL'\0'; |
1464 | (void) snprintf(new, sizeof(new), "%s/%s", target, |
1465 | tempname); |
1466 | *file = '/'; |
1467 | } |
1468 | } |
1469 | |
1470 | /* |
1471 | * Check to see if there is enough free space and inodes |
1472 | * to install this file. |
1473 | */ |
1474 | if (min_freespace || min_freefiles) { |
1475 | /* Convert file size to kilobytes */ |
1476 | int64_t fsize = (int64_t)size / 1024; |
1477 | |
1478 | if (getfilesysinfo(target, &freespace, &freefiles) != 0) |
1479 | return; |
1480 | |
1481 | /* |
1482 | * filesystem values < 0 indicate unsupported or unavailable |
1483 | * information. |
1484 | */ |
1485 | if (min_freespace && (freespace >= 0) && |
1486 | (freespace - fsize < min_freespace)) { |
1487 | error( |
1488 | "%s: Not enough free space on filesystem: min %lld " |
1489 | "free %lld", target, min_freespace, freespace); |
1490 | return; |
1491 | } |
1492 | if (min_freefiles && (freefiles >= 0) && |
1493 | (freefiles - 1 < min_freefiles)) { |
1494 | error( |
1495 | "%s: Not enough free files on filesystem: min %lld free " |
1496 | "%lld", target, min_freefiles, freefiles); |
1497 | return; |
1498 | } |
1499 | } |
1500 | |
1501 | /* |
1502 | * Call appropriate receive function to receive file |
1503 | */ |
1504 | switch (type) { |
1505 | case S_IFDIR0040000: |
1506 | recvdir(opts, mode, owner, group); |
1507 | break; |
1508 | |
1509 | case S_IFLNK0120000: |
1510 | recvlink(new, opts, mode, size); |
1511 | break; |
1512 | |
1513 | case S_IFREG0100000: |
1514 | recvfile(new, opts, mode, owner, group, mtime, atime, size); |
1515 | break; |
1516 | |
1517 | default: |
1518 | error("%d: unknown file type", type); |
1519 | break; |
1520 | } |
1521 | } |
1522 | |
1523 | /* |
1524 | * Chmog something |
1525 | */ |
1526 | static void |
1527 | dochmog(char *cmd) |
1528 | { |
1529 | int mode; |
1530 | opt_t opts; |
1531 | char *owner, *group, *file; |
1532 | char *cp = cmd; |
1533 | char fileb[PATH_MAX1024]; |
1534 | |
1535 | /* |
1536 | * Get rdist option flags |
1537 | */ |
1538 | opts = strtol(cp, &cp, 8); |
1539 | if (*cp++ != ' ') { |
1540 | error("dochmog: options not delimited"); |
1541 | return; |
1542 | } |
1543 | |
1544 | /* |
1545 | * Get file mode |
1546 | */ |
1547 | mode = strtol(cp, &cp, 8); |
1548 | if (*cp++ != ' ') { |
1549 | error("dochmog: mode not delimited"); |
1550 | return; |
1551 | } |
1552 | |
1553 | /* |
1554 | * Get file owner name |
1555 | */ |
1556 | owner = strtok(cp, " "); |
1557 | if (owner == NULL((void *)0)) { |
1558 | error("dochmog: owner name not delimited"); |
1559 | return; |
1560 | } |
1561 | |
1562 | /* |
1563 | * Get file group name |
1564 | */ |
1565 | group = strtok(NULL((void *)0), " "); |
1566 | if (group == NULL((void *)0)) { |
1567 | error("dochmog: group name not delimited"); |
1568 | return; |
1569 | } |
1570 | |
1571 | /* |
1572 | * Get file name. Can't use strtok() since there could |
1573 | * be white space in the file name. |
1574 | */ |
1575 | if (DECODE(fileb, group + strlen(group) + 1)strunvis(fileb, group + strlen(group) + 1) == -1) { |
1576 | error("dochmog: Cannot decode file name"); |
1577 | return; |
1578 | } |
1579 | |
1580 | if (fileb[0] == '\0') { |
1581 | error("dochmog: no file name"); |
1582 | return; |
1583 | } |
1584 | file = fileb; |
1585 | |
1586 | debugmsg(DM_MISC0x10, |
1587 | "dochmog: opts = %#x mode = %#04o", opts, mode); |
1588 | debugmsg(DM_MISC0x10, |
1589 | "dochmog: owner = '%s' group = '%s' file = '%s' catname = %d", |
1590 | owner, group, file, catname); |
1591 | |
1592 | if (catname && cattarget(file) < 0) { |
1593 | error("Cannot set newname target."); |
1594 | return; |
1595 | } |
1596 | |
1597 | (void) fchog(-1, target, owner, group, mode); |
1598 | |
1599 | ack()(void) sendcmd('\5', ((void *)0)); |
1600 | } |
1601 | |
1602 | /* |
1603 | * Set target information |
1604 | */ |
1605 | static void |
1606 | settarget(char *cmd, int isdir) |
1607 | { |
1608 | char *cp = cmd; |
1609 | opt_t opts; |
1610 | char file[BUFSIZ1024]; |
1611 | |
1612 | catname = isdir; |
1613 | |
1614 | /* |
1615 | * Parse options for this target |
1616 | */ |
1617 | opts = strtol(cp, &cp, 8); |
1618 | if (*cp++ != ' ') { |
1619 | error("settarget: options not delimited"); |
1620 | return; |
1621 | } |
1622 | options = opts; |
1623 | |
1624 | if (DECODE(file, cp)strunvis(file, cp) == -1) { |
1625 | error("settarget: Cannot decode target name"); |
1626 | return; |
1627 | } |
1628 | |
1629 | /* |
1630 | * Handle target |
1631 | */ |
1632 | if (exptilde(target, cp, sizeof(target)) == NULL((void *)0)) |
1633 | return; |
1634 | ptarget = target; |
1635 | while (*ptarget) |
1636 | ptarget++; |
1637 | |
1638 | ack()(void) sendcmd('\5', ((void *)0)); |
1639 | } |
1640 | |
1641 | /* |
1642 | * Cleanup in preparation for exiting. |
1643 | */ |
1644 | void |
1645 | cleanup(int dummy) |
1646 | { |
1647 | /* We don't need to do anything */ |
1648 | } |
1649 | |
1650 | /* |
1651 | * Server routine to read requests and process them. |
1652 | */ |
1653 | void |
1654 | server(void) |
1655 | { |
1656 | static char cmdbuf[BUFSIZ1024]; |
1657 | char *cp; |
1658 | int n, proto_version; |
1659 | |
1660 | if (setjmp(finish_jmpbuf)) |
1661 | return; |
1662 | (void) signal(SIGHUP1, sighandler); |
1663 | (void) signal(SIGINT2, sighandler); |
1664 | (void) signal(SIGQUIT3, sighandler); |
1665 | (void) signal(SIGTERM15, sighandler); |
1666 | (void) signal(SIGPIPE13, sighandler); |
1667 | (void) umask(oumask = umask(0)); |
1668 | (void) strlcpy(tempname, _RDIST_TMP"rdistXXXXXXXX", sizeof(tempname)); |
1669 | if (fromhost) { |
1670 | message(MT_SYSLOG0x0200, "Startup for %s", fromhost); |
1671 | #if defined(SETARGS) |
1672 | setproctitle("Serving %s", fromhost); |
1673 | #endif /* SETARGS */ |
1674 | } |
1675 | |
1676 | /* |
1677 | * Let client know we want it to send its version number |
1678 | */ |
1679 | (void) sendcmd(S_VERSION'V', NULL((void *)0)); |
1680 | |
1681 | if (remline(cmdbuf, sizeof(cmdbuf), TRUE1) < 0) { |
1682 | error("server: expected control record"); |
1683 | return; |
1684 | } |
1685 | |
1686 | if (cmdbuf[0] != S_VERSION'V' || !isdigit((unsigned char)cmdbuf[1])) { |
1687 | error("Expected version command, received: \"%s\".", cmdbuf); |
1688 | return; |
1689 | } |
1690 | |
1691 | proto_version = atoi(&cmdbuf[1]); |
1692 | if (proto_version != VERSION6) { |
1693 | error("Protocol version %d is not supported.", proto_version); |
1694 | return; |
1695 | } |
1696 | |
1697 | /* Version number is okay */ |
1698 | ack()(void) sendcmd('\5', ((void *)0)); |
1699 | |
1700 | /* |
1701 | * Main command loop |
1702 | */ |
1703 | for ( ; ; ) { |
1704 | n = remline(cp = cmdbuf, sizeof(cmdbuf), TRUE1); |
1705 | if (n == -1) /* EOF */ |
1706 | return; |
1707 | if (n == 0) { |
1708 | error("server: expected control record"); |
1709 | continue; |
1710 | } |
1711 | |
1712 | switch (*cp++) { |
1713 | case C_SETCONFIG'c': /* Configuration info */ |
1714 | setconfig(cp); |
1715 | ack()(void) sendcmd('\5', ((void *)0)); |
1716 | continue; |
1717 | |
1718 | case C_DIRTARGET'T': /* init target file/directory name */ |
1719 | settarget(cp, TRUE1); |
1720 | continue; |
1721 | |
1722 | case C_TARGET't': /* init target file/directory name */ |
1723 | settarget(cp, FALSE0); |
1724 | continue; |
1725 | |
1726 | case C_RECVREG'R': /* Transfer a regular file. */ |
1727 | recvit(cp, S_IFREG0100000); |
1728 | continue; |
1729 | |
1730 | case C_RECVDIR'D': /* Transfer a directory. */ |
1731 | recvit(cp, S_IFDIR0040000); |
1732 | continue; |
1733 | |
1734 | case C_RECVSYMLINK'K': /* Transfer symbolic link. */ |
1735 | recvit(cp, S_IFLNK0120000); |
1736 | continue; |
1737 | |
1738 | case C_RECVHARDLINK'k': /* Transfer hard link. */ |
1739 | hardlink(cp); |
1740 | continue; |
1741 | |
1742 | case C_END'E': /* End of transfer */ |
1743 | *ptarget = CNULL'\0'; |
1744 | if (catname <= 0) { |
1745 | error("server: too many '%c's", C_END'E'); |
1746 | continue; |
1747 | } |
1748 | ptarget = sptarget[--catname]; |
1749 | *ptarget = CNULL'\0'; |
1750 | ack()(void) sendcmd('\5', ((void *)0)); |
1751 | continue; |
1752 | |
1753 | case C_CLEAN'C': /* Clean. Cleanup a directory */ |
1754 | clean(cp); |
1755 | continue; |
1756 | |
1757 | case C_QUERY'Q': /* Query file/directory */ |
1758 | query(cp); |
1759 | continue; |
1760 | |
1761 | case C_SPECIAL'S': /* Special. Execute commands */ |
1762 | dospecial(cp); |
1763 | continue; |
1764 | |
1765 | case C_CMDSPECIAL's': /* Cmd Special. Execute commands */ |
1766 | docmdspecial(); |
1767 | continue; |
1768 | |
1769 | case C_CHMOG'M': /* Set owner, group, mode */ |
1770 | dochmog(cp); |
1771 | continue; |
1772 | |
1773 | case C_ERRMSG'\1': /* Normal error message */ |
1774 | if (cp && *cp) |
1775 | message(MT_NERROR0x0002|MT_NOREMOTE0x1000, "%s", cp); |
1776 | continue; |
1777 | |
1778 | case C_FERRMSG'\2': /* Fatal error message */ |
1779 | if (cp && *cp) |
1780 | message(MT_FERROR0x0004|MT_NOREMOTE0x1000, "%s", cp); |
1781 | return; |
1782 | |
1783 | default: |
1784 | error("server: unknown command '%s'", cp - 1); |
1785 | case CNULL'\0': |
1786 | continue; |
1787 | } |
1788 | } |
1789 | } |