kprop.M and kpropd.M: Document -P (port) option
[krb5.git] / src / slave / kprop.c
1 /*
2  * slave/kprop.c
3  *
4  * Copyright 1990,1991 by the Massachusetts Institute of Technology.
5  * All Rights Reserved.
6  *
7  * Export of this software from the United States of America may
8  *   require a specific license from the United States Government.
9  *   It is the responsibility of any person or organization contemplating
10  *   export to obtain such a license before exporting.
11  * 
12  * WITHIN THAT CONSTRAINT, permission to use, copy, modify, and
13  * distribute this software and its documentation for any purpose and
14  * without fee is hereby granted, provided that the above copyright
15  * notice appear in all copies and that both that copyright notice and
16  * this permission notice appear in supporting documentation, and that
17  * the name of M.I.T. not be used in advertising or publicity pertaining
18  * to distribution of the software without specific, written prior
19  * permission.  M.I.T. makes no representations about the suitability of
20  * this software for any purpose.  It is provided "as is" without express
21  * or implied warranty.
22  * 
23  *
24  */
25
26
27 #include <errno.h>
28 #include <stdio.h>
29 #include <ctype.h>
30 #include <sys/file.h>
31 #include <signal.h>
32 #include <string.h>
33 #include <sys/types.h>
34 #include <sys/time.h>
35 #include <sys/stat.h>
36 #include <sys/socket.h>
37 #include <netinet/in.h>
38 #include <sys/param.h>
39 #include <netdb.h>
40
41 #include "k5-int.h"
42 #include "com_err.h"
43
44 #ifdef NEED_SYS_FCNTL_H
45 #include <sys/fcntl.h>
46 #endif
47
48 #include "kprop.h"
49
50 static char *kprop_version = KPROP_PROT_VERSION;
51
52 char    *progname = 0;
53 int     debug = 0;
54 char    *srvtab = 0;
55 char    *slave_host;
56 char    *realm = 0;
57 char    *file = KPROP_DEFAULT_FILE;
58 short   port = 0;
59
60 krb5_principal  my_principal;           /* The Kerberos principal we'll be */
61                                 /* running under, initialized in */
62                                 /* get_tickets() */
63 krb5_ccache     ccache;         /* Credentials cache which we'll be using */
64 /* krb5_creds   my_creds;       /* My credentials */
65 krb5_creds      creds;
66 krb5_address    sender_addr;
67 krb5_address    receiver_addr;
68
69 void    PRS
70         PROTOTYPE((krb5_context, char **));
71 void    get_tickets
72         PROTOTYPE((krb5_context));
73 static void usage 
74         PROTOTYPE((void));
75 krb5_error_code open_connection 
76         PROTOTYPE((char *, int *, char *));
77 void    kerberos_authenticate 
78         PROTOTYPE((krb5_context, krb5_auth_context **, 
79                    int, krb5_principal, krb5_creds **));
80 int     open_database 
81         PROTOTYPE((krb5_context, char *, int *));
82 void    close_database 
83         PROTOTYPE((krb5_context, int));
84 void    xmit_database 
85         PROTOTYPE((krb5_context, krb5_auth_context *, krb5_creds *, 
86                    int, int, int));
87 void    send_error 
88         PROTOTYPE((krb5_context, krb5_creds *, int, char *, krb5_error_code));
89 void    update_last_prop_file 
90         PROTOTYPE((char *, char *));
91
92 static void usage()
93 {
94         fprintf(stderr, "\nUsage: %s [-r realm] [-f file] [-d] [-P port] [-s srvtab] slave_host\n\n",
95                 progname);
96         exit(1);
97 }
98
99 void
100 main(argc, argv)
101         int     argc;
102         char    **argv;
103 {
104         int     fd, database_fd, database_size;
105         krb5_error_code retval;
106         krb5_context context;
107         krb5_creds *my_creds;
108         krb5_auth_context * auth_context;
109         char    Errmsg[256];
110         
111         PRS(context, argv);
112         krb5_init_context(&context);
113         get_tickets(context);
114
115         database_fd = open_database(context, file, &database_size);
116         if (retval = open_connection(slave_host, &fd, Errmsg)) {
117                 com_err(progname, retval, "%s while opening connection to %s",
118                         Errmsg, slave_host);
119                 exit(1);
120         }
121         if (fd < 0) {
122                 fprintf(stderr, "%s: %s while opening connection to %s\n",
123                         progname, Errmsg, slave_host);
124                 exit(1);
125         }
126         kerberos_authenticate(context, &auth_context, fd, my_principal, 
127                               &my_creds);
128         xmit_database(context, auth_context, my_creds, fd, database_fd, 
129                       database_size);
130         update_last_prop_file(slave_host, file);
131         printf("Database propagation to %s: SUCCEEDED\n", slave_host);
132         krb5_free_cred_contents(context, my_creds);
133         close_database(context, database_fd);
134         exit(0);
135 }
136
137 void PRS(context, argv)
138         krb5_context context;
139         char    **argv;
140 {
141         register char   *word, ch;
142         
143         krb5_init_context(&context);
144         krb5_init_ets(context);
145         progname = *argv++;
146         while (word = *argv++) {
147                 if (*word == '-') {
148                         word++;
149                         while (word && (ch = *word++)) {
150                                 switch(ch){
151                                 case 'r':
152                                         if (*word)
153                                                 realm = word;
154                                         else
155                                                 realm = *argv++;
156                                         if (!realm)
157                                                 usage();
158                                         word = 0;
159                                         break;
160                                 case 'f':
161                                         if (*word)
162                                                 file = word;
163                                         else
164                                                 file = *argv++;
165                                         if (!file)
166                                                 usage();
167                                         word = 0;
168                                         break;
169                                 case 'd':
170                                         debug++;
171                                         break;
172                                 case 'P':
173                                         if (*word)
174                                                 port = htons(atoi(word));
175                                         else
176                                                 port = htons(atoi(*argv++));
177                                         if (!port)
178                                                 usage();
179                                         word = 0;
180                                         break;
181                                 case 's':
182                                         if (*word)
183                                                 srvtab = word;
184                                         else
185                                                 srvtab = *argv++;
186                                         if (!srvtab)
187                                                 usage();
188                                         word = 0;
189                                         break;
190                                 default:
191                                         usage();
192                                 }
193                                 
194                         }
195                 } else {
196                         if (slave_host)
197                                 usage();
198                         else
199                                 slave_host = word;
200                 }
201         }
202         if (!slave_host)
203                 usage();
204 }
205
206 void get_tickets(context)
207     krb5_context context;
208 {
209         char   my_host_name[MAXHOSTNAMELEN];
210         char   buf[BUFSIZ];
211         char   *cp;
212         struct hostent *hp;
213         krb5_error_code retval;
214         static char tkstring[] = "/tmp/kproptktXXXXXX";
215         krb5_keytab keytab = NULL;
216
217         /*
218          * Figure out what tickets we'll be using to send stuff
219          */
220         if (gethostname (my_host_name, sizeof(my_host_name)) != 0) { 
221                 com_err(progname, errno, "while getting my hostname");
222                 exit(1);
223         }
224         /* get canonicalized  service instance name */
225         if (!(hp = gethostbyname(my_host_name))) {
226                 fprintf(stderr, "Couldn't get my cannonicalized host name!\n");
227                 exit(1);
228         }
229         for (cp=hp->h_name; *cp; cp++)
230                 if (isupper(*cp))
231                         *cp = tolower(*cp);
232         if (realm)
233                 sprintf(buf, "host/%s@%s", hp->h_name, realm);
234         else
235                 sprintf(buf, "host/%s", hp->h_name);
236         if (retval = krb5_parse_name(context, buf, &my_principal)) {
237                 com_err (progname, retval, "when parsing name %s",buf);
238                 exit(1);
239         }
240
241         /*
242          * Initialize cache file which we're going to be using
243          */
244         (void) mktemp(tkstring);
245         sprintf(buf, "FILE:%s", tkstring);
246         if (retval = krb5_cc_resolve(context, buf, &ccache)) {
247                 com_err(progname, retval, "while opening crednetials cache %s",
248                         buf);
249                 exit(1);
250         }
251         if (retval = krb5_cc_initialize(context, ccache, my_principal)) {
252                 com_err (progname, retval, "when initializing cache %s",
253                          buf);
254                 exit(1);
255         }
256
257         /*
258          * Get the tickets we'll need.
259          *
260          * Construct the principal name for the slave host.
261          */
262         memset((char *)&creds, 0, sizeof(creds));
263         if (!(hp = gethostbyname(slave_host))) {
264                 fprintf(stderr,
265                         "Couldn't get cannonicalized name for slave\n");
266                 exit(1);
267         }
268         for (cp=hp->h_name; *cp; cp++)
269                 if (isupper(*cp))
270                         *cp = tolower(*cp);
271         if (!(slave_host = malloc(strlen(hp->h_name) + 1))) {
272                 com_err(progname, ENOMEM,
273                         "while allocate space for canonicalized slave host");
274                 exit(1);
275         }
276         strcpy(slave_host, hp->h_name);
277         if (realm)
278                 sprintf(buf, "%s/%s@%s", KPROP_SERVICE_NAME, slave_host,
279                         realm);
280         else
281                 sprintf(buf, "%s/%s", KPROP_SERVICE_NAME, hp->h_name);
282         if (retval = krb5_parse_name(context, buf, &creds.server)) {
283                 com_err(progname, retval,
284                         "while parsing slave principal name");
285                 exit(1);
286         }
287         /*
288          * Now fill in the client....
289          */
290         if (retval = krb5_copy_principal(context, my_principal, &creds.client)) {
291                 com_err(progname, retval, "While copying client principal");
292                 exit(1);
293         }
294         if (srvtab) {
295                 if (retval = krb5_kt_resolve(context, srvtab, &keytab)) {
296                         com_err(progname, retval, "while resolving keytab");
297                         exit(1);
298                 }
299         }
300
301         retval = krb5_get_in_tkt_with_keytab(context, 0, 0, NULL,
302                                              NULL, keytab, ccache, &creds, 0);
303         if (retval) {
304                 com_err(progname, retval, "while getting initial ticket\n");
305                 exit(1);
306         }
307         /*
308          * Now destroy the cache right away --- the credentials we
309          * need will be in my_creds.
310          */
311         if (retval = krb5_cc_destroy(context, ccache)) {
312                 com_err(progname, retval, "while destroying ticket cache");
313                 exit(1);
314         }
315 }
316
317 krb5_error_code
318 open_connection(host, fd, Errmsg)
319         char    *host;
320         int     *fd;
321         char    *Errmsg;
322 {
323         int     s;
324         krb5_error_code retval;
325         
326         struct hostent  *hp;
327         register struct servent *sp;
328         struct sockaddr_in sin;
329         int             socket_length;
330
331         hp = gethostbyname(host);
332         if (hp == NULL) {
333                 (void) sprintf(Errmsg, "%s: unknown host", host);
334                 *fd = -1;
335                 return(0);
336         }
337         sin.sin_family = hp->h_addrtype;
338         memcpy((char *)&sin.sin_addr, hp->h_addr, hp->h_length);
339         if(!port) {
340                 sp = getservbyname(KPROP_SERVICE, "tcp");
341                 if (sp == 0) {
342                         (void) strcpy(Errmsg, KPROP_SERVICE);
343                         (void) strcat(Errmsg, "/tcp: unknown service");
344                         *fd = -1;
345                         return(0);
346                 }
347                 sin.sin_port = sp->s_port;
348         } else
349                 sin.sin_port = port;
350         s = socket(AF_INET, SOCK_STREAM, 0);
351         
352         if (s < 0) {
353                 (void) sprintf(Errmsg, "in call to socket");
354                 return(errno);
355         }
356         if (connect(s, (struct sockaddr *)&sin, sizeof sin) < 0) {
357                 retval = errno;
358                 close(s);
359                 (void) sprintf(Errmsg, "in call to connect");
360                 return(retval);
361         }
362         *fd = s;
363
364         /*
365          * Set receiver_addr and sender_addr.
366          */
367         receiver_addr.addrtype = ADDRTYPE_INET;
368         receiver_addr.length = sizeof(sin.sin_addr);
369         receiver_addr.contents = (krb5_octet *) malloc(sizeof(sin.sin_addr));
370         memcpy((char *) receiver_addr.contents, (char *) &sin.sin_addr,
371                sizeof(sin.sin_addr));
372
373         socket_length = sizeof(sin);
374         if (getsockname(s, (struct sockaddr *)&sin, &socket_length) < 0) {
375                 retval = errno;
376                 close(s);
377                 (void) sprintf(Errmsg, "in call to getsockname");
378                 return(retval);
379         }
380         sender_addr.addrtype = ADDRTYPE_INET;
381         sender_addr.length = sizeof(sin.sin_addr);
382         sender_addr.contents = (krb5_octet *) malloc(sizeof(sin.sin_addr));
383         memcpy((char *) sender_addr.contents, (char *) &sin.sin_addr,
384                sizeof(sin.sin_addr));
385
386         return(0);
387 }
388
389
390 void kerberos_authenticate(context, auth_context, fd, me, new_creds)
391     krb5_context context;
392     krb5_auth_context **auth_context;
393     int fd;
394     krb5_principal me;
395     krb5_creds ** new_creds;
396 {
397         krb5_error_code retval;
398         krb5_error      *error = NULL;
399         krb5_ap_rep_enc_part    *rep_result;
400
401     if (retval = krb5_auth_con_init(context, auth_context)) 
402         exit(1);
403
404     krb5_auth_con_setflags(context, *auth_context, 
405                            KRB5_AUTH_CONTEXT_DO_SEQUENCE);
406
407     if (retval = krb5_auth_con_setaddrs(context, *auth_context, &sender_addr,
408                                         &receiver_addr)) {
409         com_err(progname, retval, "in krb5_auth_con_setaddrs");
410         exit(1);
411     }
412
413         if (retval = krb5_sendauth(context, auth_context, (void *)&fd, 
414                                    kprop_version, me, creds.server,
415                                    AP_OPTS_MUTUAL_REQUIRED, NULL, &creds, NULL,
416                                    &error, &rep_result, new_creds)) {
417                 com_err(progname, retval, "while authenticating to server");
418                 if (error) {
419                         if (error->error == KRB_ERR_GENERIC) {
420                                 if (error->text.data)
421                                         fprintf(stderr,
422                                                 "Generic remote error: %s\n",
423                                                 error->text.data);
424                         } else if (error->error) {
425                                 com_err(progname,
426                                         error->error + ERROR_TABLE_BASE_krb5,
427                                         "signalled from server");
428                                 if (error->text.data)
429                                         fprintf(stderr,
430                                                 "Error text from server: %s\n",
431                                                 error->text.data);
432                         }
433                         krb5_free_error(context, error);
434                 }
435                 exit(1);
436         }
437         krb5_free_ap_rep_enc_part(context, rep_result);
438 }
439
440 FILE * dbfp;
441 char * dbpathname;
442 /*
443  * Open the Kerberos database dump file.  Takes care of locking it
444  * and making sure that the .ok file is more recent that the database
445  * dump file itself.
446  *
447  * Returns the file descriptor of the database dump file.  Also fills
448  * in the size of the database file.
449  */
450 int
451 open_database(context, data_fn, size)
452     krb5_context context;
453     char *data_fn;
454     int *size;
455 {
456         int             fd;
457         int             err;
458         struct stat     stbuf, stbuf_ok;
459         char            *data_ok_fn;
460         static char ok[] = ".dump_ok";
461
462         dbpathname = strdup(data_fn);
463         if (!dbpathname) {
464             com_err(progname, ENOMEM, "allocating database file name '%s'",
465                     data_fn);
466             exit(1);
467         }
468         if ((dbfp = fopen(dbpathname, "r")) == 0) {
469                 com_err(progname, errno, "while trying to open %s",
470                         dbpathname);
471                 exit(1);
472         }
473
474         err = krb5_lock_file(context, dbfp, dbpathname,
475                              KRB5_LOCKMODE_SHARED|KRB5_LOCKMODE_DONTBLOCK);
476         if (err == EAGAIN || err == EWOULDBLOCK || errno == EACCES) {
477             com_err(progname, 0, "database locked");
478             exit(1);
479         } else if (err) {
480             com_err(progname, err, "while trying to lock '%s'", dbpathname);
481             exit(1);
482         }           
483         fd = fileno(dbfp);
484         if (fstat(fd, &stbuf)) {
485                 com_err(progname, errno, "while trying to stat %s",
486                         data_fn);
487                 exit(1);
488         }
489         if ((data_ok_fn = (char *) malloc(strlen(data_fn)+strlen(ok)+1))
490             == NULL) {
491                 com_err(progname, ENOMEM, "while trying to malloc data_ok_fn");
492                 exit(1);
493         }
494         strcat(strcpy(data_ok_fn, data_fn), ok);
495         if (stat(data_ok_fn, &stbuf_ok)) {
496                 com_err(progname, errno, "while trying to stat %s",
497                         data_ok_fn);
498                 free(data_ok_fn);
499                 exit(1);
500         }
501         free(data_ok_fn);
502         if (stbuf.st_mtime > stbuf_ok.st_mtime) {
503                 com_err(progname, 0, "'%s' more recent than '%s'.",
504                         data_fn, data_ok_fn);
505                 exit(1);
506         }
507         *size = stbuf.st_size;
508         return(fd);
509 }
510
511 void
512 close_database(context, fd)
513     krb5_context context;
514     int fd;
515 {
516     int err;
517     if (fd != fileno(dbfp)) {
518         com_err(progname, 0, "bad fd passed to close_database");
519         exit(1);
520     }
521     err = krb5_lock_file(context, dbfp, dbpathname, KRB5_LOCKMODE_UNLOCK);
522     if (err)
523         com_err(progname, err, "while unlocking database '%s'", dbpathname);
524     free(dbpathname);
525     (void) fclose(dbfp);
526     return;
527 }
528   
529 /*
530  * Now we send over the database.  We use the following protocol:
531  * Send over a KRB_SAFE message with the size.  Then we send over the
532  * database in blocks of KPROP_BLKSIZE, encrypted using KRB_PRIV.
533  * Then we expect to see a KRB_SAFE message with the size sent back.
534  * 
535  * At any point in the protocol, we may send a KRB_ERROR message; this
536  * will abort the entire operation.
537  */
538 void
539 xmit_database(context, auth_context, my_creds, fd, database_fd, database_size)
540     krb5_context context;
541     krb5_auth_context * auth_context;
542     krb5_creds *my_creds;
543     int fd;
544     int database_fd;
545     int database_size;
546 {
547         krb5_int32      send_size, sent_size, n;
548         krb5_data       inbuf, outbuf;
549         char            buf[KPROP_BUFSIZ];
550         krb5_error_code retval;
551         krb5_error      *error;
552         
553         /*
554          * Send over the size
555          */
556         send_size = htonl(database_size);
557         inbuf.data = (char *) &send_size;
558         inbuf.length = sizeof(send_size); /* must be 4, really */
559         /* KPROP_CKSUMTYPE */
560         if (retval = krb5_mk_safe(context, auth_context, &inbuf, 
561                                   &outbuf, NULL)) {
562                 com_err(progname, retval, "while encoding database size");
563                 send_error(context, my_creds, fd, "while encoding database size", retval);
564                 exit(1);
565         }
566         if (retval = krb5_write_message(context, (void *) &fd, &outbuf)) {
567                 krb5_xfree(outbuf.data);
568                 com_err(progname, retval, "while sending database size");
569                 exit(1);
570         }
571         krb5_xfree(outbuf.data);
572     /*
573      * Initialize the initial vector.
574      */
575     if (retval = krb5_auth_con_initivector(context, auth_context)) {
576         send_error(context, my_creds, fd, 
577                    "failed while initializing i_vector", retval);
578         com_err(progname, retval, "while allocating i_vector");
579         exit(1);
580     }
581         /*
582          * Send over the file, block by block....
583          */
584         inbuf.data = buf;
585         sent_size = 0;
586         while (n = read(database_fd, buf, sizeof(buf))) {
587                 inbuf.length = n;
588                 if (retval = krb5_mk_priv(context, auth_context, &inbuf,
589                                           &outbuf, NULL)) {
590                         sprintf(buf,
591                                 "while encoding database block starting at %d",
592                                 sent_size);
593                         com_err(progname, retval, buf);
594                         send_error(context, my_creds, fd, buf, retval);
595                         exit(1);
596                 }
597                 if (retval = krb5_write_message(context, (void *)&fd,&outbuf)) {
598                         krb5_xfree(outbuf.data);
599                         com_err(progname, retval,
600                                 "while sending database block starting at %d",
601                                 sent_size);
602                         exit(1);
603                 }
604                 krb5_xfree(outbuf.data);
605                 sent_size += n;
606                 if (debug)
607                         printf("%d bytes sent.\n", sent_size);
608         }
609         if (sent_size != database_size) {
610                 com_err(progname, 0, "Premature EOF found for database file!");
611                 send_error(context, my_creds, fd,"Premature EOF found for database file!",
612                            KRB5KRB_ERR_GENERIC);
613                 exit(1);
614         }
615         /*
616          * OK, we've sent the database; now let's wait for a success
617          * indication from the remote end.
618          */
619         if (retval = krb5_read_message(context, (void *) &fd, &inbuf)) {
620                 com_err(progname, retval,
621                         "while reading response from server");
622                 exit(1);
623         }
624         /*
625          * If we got an error response back from the server, display
626          * the error message
627          */
628         if (krb5_is_krb_error(&inbuf)) {
629                 if (retval = krb5_rd_error(context, &inbuf, &error)) {
630                         com_err(progname, retval,
631                                 "while decoding error response from server");
632                         exit(1);
633                 }
634                 if (error->error == KRB_ERR_GENERIC) {
635                         if (error->text.data)
636                                 fprintf(stderr,
637                                         "Generic remote error: %s\n",
638                                         error->text.data);
639                 } else if (error->error) {
640                         com_err(progname, error->error + ERROR_TABLE_BASE_krb5,
641                                 "signalled from server");
642                         if (error->text.data)
643                                 fprintf(stderr,
644                                         "Error text from server: %s\n",
645                                         error->text.data);
646                 }
647                 krb5_free_error(context, error);
648                 exit(1);
649         }
650         if (retval = krb5_rd_safe(context,auth_context,&inbuf,&outbuf,NULL)) {
651                 com_err(progname, retval,
652                         "while decoding final size packet from server");
653                 exit(1);
654         }
655         memcpy((char *)&send_size, outbuf.data, sizeof(send_size));
656         send_size = ntohl(send_size);
657         if (send_size != database_size) {
658                 com_err(progname, 0,
659                         "Kpropd sent database size %d, expecting %d",
660                         send_size, database_size);
661                 exit(1);
662         }
663         free(outbuf.data);
664         free(inbuf.data);
665 }
666
667 void
668 send_error(context, my_creds, fd, err_text, err_code)
669     krb5_context context;
670     krb5_creds *my_creds;
671     int fd;
672     char        *err_text;
673     krb5_error_code     err_code;
674 {
675         krb5_error      error;
676         const char      *text;
677         krb5_data       outbuf;
678
679         memset((char *)&error, 0, sizeof(error));
680         krb5_us_timeofday(context, &error.ctime, &error.cusec);
681         error.server = my_creds->server;
682         error.client = my_principal;
683         error.error = err_code - ERROR_TABLE_BASE_krb5;
684         if (error.error > 127)
685                 error.error = KRB_ERR_GENERIC;
686         if (err_text)
687                 text = err_text;
688         else
689                 text = error_message(err_code);
690         error.text.length = strlen(text) + 1;
691         if (error.text.data = malloc(error.text.length)) {
692                 strcpy(error.text.data, text);
693                 if (!krb5_mk_error(context, &error, &outbuf)) {
694                         (void) krb5_write_message(context, (void *)&fd,&outbuf);
695                         krb5_xfree(outbuf.data);
696                 }
697                 free(error.text.data);
698         }
699 }
700
701 void update_last_prop_file(hostname, file_name)
702         char *hostname;
703         char *file_name;
704 {
705         /* handle slave locking/failure stuff */
706         char *file_last_prop;
707         int fd;
708         static char last_prop[]=".last_prop";
709
710         if ((file_last_prop = (char *)malloc(strlen(file_name) +
711                                              strlen(hostname) + 1 +
712                                              strlen(last_prop) + 1)) == NULL) {
713                 com_err(progname, ENOMEM,
714                         "while allocating filename for update_last_prop_file");
715                 return;
716         }
717         strcpy(file_last_prop, file_name);
718         strcat(file_last_prop, ".");
719         strcat(file_last_prop, hostname);
720         strcat(file_last_prop, last_prop);
721         if ((fd = open(file_last_prop, O_WRONLY|O_CREAT|O_TRUNC, 0600)) < 0) {
722                 com_err(progname, errno,
723                         "while creating 'last_prop' file, '%s'",
724                         file_last_prop);
725                 free(file_last_prop);
726                 return;
727         }
728         free(file_last_prop);
729         close(fd);
730         return;
731 }