-
Notifications
You must be signed in to change notification settings - Fork 18
/
stat.c
553 lines (522 loc) · 17.8 KB
/
stat.c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
/*
* This file is part of the Green End SFTP Server.
* Copyright (C) 2007, 2011, 2014 Richard Kettlewell
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
* USA
*/
/** @file stat.c @brief SFTP attribute support implementation */
#include "sftpserver.h"
#include "types.h"
#include "users.h"
#include "sftp.h"
#include "alloc.h"
#include "debug.h"
#include "stat.h"
#include "utils.h"
#include <sys/stat.h>
#include <string.h>
#include <stdio.h>
#include <time.h>
#include <sys/time.h>
#include <unistd.h>
#include <fcntl.h>
#include "replaced.h"
void sftp_stat_to_attrs(struct allocator *a, const struct stat *sb,
struct sftpattr *attrs, uint32_t flags,
const char *path) {
sftp_memset(attrs, 0, sizeof *attrs);
attrs->valid = (SSH_FILEXFER_ATTR_SIZE | SSH_FILEXFER_ATTR_PERMISSIONS |
SSH_FILEXFER_ATTR_ACCESSTIME | SSH_FILEXFER_ATTR_MODIFYTIME |
SSH_FILEXFER_ATTR_UIDGID | SSH_FILEXFER_ATTR_ALLOCATION_SIZE |
SSH_FILEXFER_ATTR_LINK_COUNT | SSH_FILEXFER_ATTR_CTIME |
SSH_FILEXFER_ATTR_BITS);
switch(sb->st_mode & S_IFMT) {
case S_IFIFO:
attrs->type = SSH_FILEXFER_TYPE_FIFO;
break;
case S_IFCHR:
attrs->type = SSH_FILEXFER_TYPE_CHAR_DEVICE;
break;
case S_IFDIR:
attrs->type = SSH_FILEXFER_TYPE_DIRECTORY;
break;
case S_IFBLK:
attrs->type = SSH_FILEXFER_TYPE_BLOCK_DEVICE;
break;
case S_IFREG:
attrs->type = SSH_FILEXFER_TYPE_REGULAR;
break;
case S_IFLNK:
attrs->type = SSH_FILEXFER_TYPE_SYMLINK;
break;
case S_IFSOCK:
attrs->type = SSH_FILEXFER_TYPE_SOCKET;
break;
default:
attrs->type = SSH_FILEXFER_TYPE_SPECIAL;
break;
}
attrs->size = sb->st_size;
/* The cast ensures we don't do the multiply in a small word and overflow */
attrs->allocation_size = (uint64_t)sb->st_blksize * sb->st_blocks;
/* Only look up owner/group info if wanted */
if((flags & SSH_FILEXFER_ATTR_OWNERGROUP) &&
(attrs->owner = sftp_uid2name(a, sb->st_uid)) &&
(attrs->group = sftp_gid2name(a, sb->st_gid)))
attrs->valid |= SSH_FILEXFER_ATTR_OWNERGROUP;
attrs->uid = sb->st_uid;
attrs->gid = sb->st_gid;
attrs->permissions = sb->st_mode;
attrs->atime.seconds = sb->st_atime;
attrs->mtime.seconds = sb->st_mtime;
attrs->ctime.seconds = sb->st_ctime;
#ifdef ST_ATIM
if(sb->ST_ATIM.tv_nsec >= 0 && sb->ST_ATIM.tv_nsec < 1000000000 &&
sb->ST_MTIM.tv_nsec >= 0 && sb->ST_MTIM.tv_nsec < 1000000000 &&
sb->ST_CTIM.tv_nsec >= 0 && sb->ST_CTIM.tv_nsec < 1000000000) {
/* Only send subsecond times if they are in range */
attrs->atime.nanoseconds = sb->ST_ATIM.tv_nsec;
attrs->mtime.nanoseconds = sb->ST_MTIM.tv_nsec;
attrs->ctime.nanoseconds = sb->ST_CTIM.tv_nsec;
attrs->valid |= SSH_FILEXFER_ATTR_SUBSECOND_TIMES;
}
#endif
attrs->link_count = sb->st_nlink;
/* If we know the path we can determine whether the file is hidden or not */
if(path) {
const char *s = path + strlen(path);
/* Ignore trailing slashes */
while(s > path && s[-1] == '/')
--s;
while(s > path && s[-1] != '/')
--s;
if(*s == '.')
attrs->attrib_bits |= SSH_FILEXFER_ATTR_FLAGS_HIDDEN;
attrs->attrib_bits_valid |= SSH_FILEXFER_ATTR_FLAGS_HIDDEN;
}
/* TODO: SSH_FILEXFER_ATTR_FLAGS_CASE_INSENSITIVE for directories on
* case-independent filesystems */
/* TODO: SSH_FILEXFER_ATTR_FLAGS_IMMUTABLE for immutable files. Perhaps when
* I can find some documentation on the subject. */
attrs->name = path;
}
/** @brief Table of attribute bit names */
static const struct {
uint32_t bit;
const char *description;
} attr_bits[] = {{SSH_FILEXFER_ATTR_FLAGS_READONLY, "ro"},
{SSH_FILEXFER_ATTR_FLAGS_SYSTEM, "sys"},
{SSH_FILEXFER_ATTR_FLAGS_HIDDEN, "hide"},
{SSH_FILEXFER_ATTR_FLAGS_CASE_INSENSITIVE, "ci"},
{SSH_FILEXFER_ATTR_FLAGS_ARCHIVE, "arc"},
{SSH_FILEXFER_ATTR_FLAGS_ENCRYPTED, "enc"},
{SSH_FILEXFER_ATTR_FLAGS_COMPRESSED, "comp"},
{SSH_FILEXFER_ATTR_FLAGS_SPARSE, "sp"},
{SSH_FILEXFER_ATTR_FLAGS_APPEND_ONLY, "app"},
{SSH_FILEXFER_ATTR_FLAGS_IMMUTABLE, "imm"},
{SSH_FILEXFER_ATTR_FLAGS_SYNC, "sync"},
{SSH_FILEXFER_ATTR_FLAGS_TRANSLATION_ERR, "trans"}};
const char *sftp_format_attr(struct allocator *a, const struct sftpattr *attrs,
int thisyear, unsigned long flags) {
char perms[64], linkcount[64], size[64], date[64], nowner[64], ngroup[64];
char *formatted, *p, *bits;
size_t nbits;
const char *owner, *group;
static const char typedetails[] = "?-dl??scbp";
size_t n;
/* permissions */
p = perms;
*p++ = typedetails[attrs->type];
if(attrs->valid & SSH_FILEXFER_ATTR_PERMISSIONS) {
*p++ = (attrs->permissions & 00400) ? 'r' : '-';
*p++ = (attrs->permissions & 00200) ? 'w' : '-';
switch(attrs->permissions & 04100) {
case 00000:
*p++ = '-';
break;
case 00100:
*p++ = 'x';
break;
case 04000:
*p++ = 'S';
break;
case 04100:
*p++ = 's';
break;
}
*p++ = (attrs->permissions & 00040) ? 'r' : '-';
*p++ = (attrs->permissions & 00020) ? 'w' : '-';
switch(attrs->permissions & 02010) {
case 00000:
*p++ = '-';
break;
case 00010:
*p++ = 'x';
break;
case 02000:
*p++ = 'S';
break;
case 02010:
*p++ = 's';
break;
}
*p++ = (attrs->permissions & 00004) ? 'r' : '-';
*p++ = (attrs->permissions & 00002) ? 'w' : '-';
switch(attrs->permissions & 01001) {
case 00000:
*p++ = '-';
break;
case 00001:
*p++ = 'x';
break;
case 01000:
*p++ = 'T';
break;
case 01001:
*p++ = 't';
break;
}
*p = 0;
} else
strcpy(p, "?????????");
/* link count */
if(attrs->valid & SSH_FILEXFER_ATTR_LINK_COUNT)
sprintf(linkcount, "%" PRIu32, attrs->link_count);
else
strcpy(linkcount, "?");
/* size */
if(attrs->valid & SSH_FILEXFER_ATTR_SIZE)
sprintf(size, "%" PRIu64, attrs->size);
else
strcpy(size, "?");
/* ownership */
if(attrs->valid & SSH_FILEXFER_ATTR_UIDGID) {
sprintf(nowner, "%" PRIu32, attrs->uid);
sprintf(ngroup, "%" PRIu32, attrs->gid);
}
owner = group = "?";
if(flags & FORMAT_PREFER_NUMERIC_UID) {
if(attrs->valid & SSH_FILEXFER_ATTR_UIDGID) {
owner = nowner;
group = ngroup;
} else if(attrs->valid & SSH_FILEXFER_ATTR_OWNERGROUP) {
owner = attrs->owner;
group = attrs->group;
}
} else {
if(attrs->valid & SSH_FILEXFER_ATTR_OWNERGROUP) {
owner = attrs->owner;
group = attrs->group;
} else if(attrs->valid & SSH_FILEXFER_ATTR_UIDGID) {
owner = nowner;
group = ngroup;
}
}
/* timestamp */
if(attrs->valid & SSH_FILEXFER_ATTR_MODIFYTIME) {
struct tm mtime;
const time_t m = attrs->mtime.seconds;
(flags & FORMAT_PREFER_LOCALTIME ? localtime_r : gmtime_r)(&m, &mtime);
/* Timestamps in the current year we give down to seconds. For
* timestamps in other years we give the year. */
if(mtime.tm_year == thisyear)
strftime(date, sizeof date, "%b %d %H:%M", &mtime);
else
strftime(date, sizeof date, "%b %d %Y", &mtime);
} else
strcpy(date, "?");
/* attribute bits */
bits = NULL;
nbits = 0;
if(flags & FORMAT_ATTRS && (attrs->valid & SSH_FILEXFER_ATTR_BITS) &&
attrs->attrib_bits) {
bits = sftp_str_append(a, bits, &nbits, "[");
for(n = 0; n < sizeof attr_bits / sizeof *attr_bits; ++n) {
if(attrs->attrib_bits & attr_bits[n].bit) {
if(bits[1])
bits = sftp_str_append(a, bits, &nbits, ",");
bits = sftp_str_append(a, bits, &nbits, attr_bits[n].description);
}
}
bits = sftp_str_append(a, bits, &nbits, "]");
}
/* Format the result */
formatted = sftp_alloc(a, 80 + strlen(attrs->name));
/* The draft is pretty specific about field widths */
sprintf(formatted, "%10.10s %3s %-8s %-8s %8s %12s %s%s%s%s%s", perms,
linkcount, owner, group, size, date, attrs->name,
attrs->target ? " -> " : "", attrs->target ? attrs->target : "",
bits ? " " : "", bits ? bits : "");
return formatted;
}
uint32_t sftp_normalize_ownergroup(struct allocator *a,
struct sftpattr *attrs) {
uint32_t rc = SSH_FX_OK;
switch(attrs->valid &
(SSH_FILEXFER_ATTR_UIDGID | SSH_FILEXFER_ATTR_OWNERGROUP)) {
case SSH_FILEXFER_ATTR_UIDGID:
if((attrs->owner = sftp_uid2name(a, attrs->uid)) &&
(attrs->group = sftp_gid2name(a, attrs->gid)))
attrs->valid |= SSH_FILEXFER_ATTR_OWNERGROUP;
break;
case SSH_FILEXFER_ATTR_OWNERGROUP:
if((attrs->uid = sftp_name2uid(attrs->owner)) == (uid_t)-1)
rc = SSH_FX_OWNER_INVALID;
if((attrs->gid = sftp_name2gid(attrs->group)) == (gid_t)-1)
rc = SSH_FX_GROUP_INVALID;
if(rc == SSH_FX_OK)
attrs->valid |= SSH_FILEXFER_ATTR_UIDGID;
break;
}
return rc;
}
/** @brief Callbacks for do_sftp_set_status() */
struct sftp_set_status_callbacks {
/** @brief Truncate callback
* @param what Object to truncate
* @param size New size
* @return 0 on success, -ve on error
*/
int (*do_truncate)(const void *what, off_t size);
/** @brief Change ownership callback
* @param what Object to modify
* @param uid New UID
* @param gid New GID
* @return 0 on success, -ve on error
*/
int (*do_chown)(const void *what, uid_t uid, gid_t gid);
/** @brief Change mode callback
* @param what Object to modify
* @param mode New mode
* @return 0 on success, -ve on error
*/
int (*do_chmod)(const void *what, mode_t mode);
/** @brief Get attributes callback
* @param what Object to inspect
* @param sb Where to store information
* @return 0 on success, -ve on error
*/
int (*do_stat)(const void *what, struct stat *sb);
/** @brief Set file times
* @param what Object to modify
* @param tv New times
* @return 0 on success, -ve on error
*/
int (*do_utimens)(const void *what, struct timespec *tv);
};
/** @brief Implementation of sftp_set_status() and sftp_set_fstatus()
* @param a Allocator
* @param what Object to modify
* @param attrsp Attributes to set
* @param cb Callbacks for object type
* @param whyp Where to store error string, or a null pointer
* @return 0 on success or an error code on failure
*/
static uint32_t do_sftp_set_status(struct allocator *a, const void *what,
const struct sftpattr *attrsp,
const struct sftp_set_status_callbacks *cb,
const char **whyp) {
struct timespec times[2];
struct stat current;
struct sftpattr attrs = *attrsp;
const char *why;
if(!whyp)
whyp = &why;
*whyp = 0;
if((attrs.valid &
(SSH_FILEXFER_ATTR_SIZE | SSH_FILEXFER_ATTR_ALLOCATION_SIZE)) ==
(SSH_FILEXFER_ATTR_SIZE | SSH_FILEXFER_ATTR_ALLOCATION_SIZE) &&
attrs.allocation_size < attrs.size) {
/* This is a MUST, e.g. draft -13 s7.4. We honor it even though we don't
* honor allocation-size. */
*whyp = "size exceeds allocation-size";
return SSH_FX_INVALID_PARAMETER;
}
if(attrs.valid & (SSH_FILEXFER_ATTR_LINK_COUNT | SSH_FILEXFER_ATTR_TEXT_HINT))
/* Client has violated a MUST NOT */
return SSH_FX_INVALID_PARAMETER;
if((attrs.valid &
(SSH_FILEXFER_ATTR_SIZE | SSH_FILEXFER_ATTR_PERMISSIONS |
SSH_FILEXFER_ATTR_ACCESSTIME | SSH_FILEXFER_ATTR_MODIFYTIME |
SSH_FILEXFER_ATTR_UIDGID | SSH_FILEXFER_ATTR_OWNERGROUP |
SSH_FILEXFER_ATTR_SUBSECOND_TIMES |
SSH_FILEXFER_ATTR_ALLOCATION_SIZE)) != attrs.valid) {
/* SHOULD not change any attributes if we cannot perform as required. We
* have a SHOULD which allows us to ignore allocation-size. */
*whyp = "unsupported flags";
return SSH_FX_OP_UNSUPPORTED;
}
if(attrs.valid & SSH_FILEXFER_ATTR_SIZE) {
D(("...truncate to %" PRIu64, attrs.size));
if(cb->do_truncate(what, attrs.size) < 0) {
*whyp = "truncate";
return HANDLER_ERRNO;
}
}
sftp_normalize_ownergroup(a, &attrs);
if(attrs.valid & SSH_FILEXFER_ATTR_UIDGID) {
D(("...chown to %" PRId32 "/%" PRId32, attrs.uid, attrs.gid));
if(cb->do_chown(what, attrs.uid, attrs.gid) < 0) {
*whyp = "chown";
return HANDLER_ERRNO;
}
}
if(attrs.valid & SSH_FILEXFER_ATTR_PERMISSIONS) {
const mode_t mode = attrs.permissions & 07777;
D(("...chmod to %#o", (unsigned)mode));
if(cb->do_chmod(what, mode) < 0) {
*whyp = "chmod";
return HANDLER_ERRNO;
}
}
if(attrs.valid &
(SSH_FILEXFER_ATTR_ACCESSTIME | SSH_FILEXFER_ATTR_MODIFYTIME)) {
if(cb->do_stat(what, ¤t) < 0) {
D(("cannot stat"));
*whyp = "stat";
return HANDLER_ERRNO;
}
sftp_memset(times, 0, sizeof times);
times[0].tv_sec = ((attrs.valid & SSH_FILEXFER_ATTR_ACCESSTIME)
? (time_t)attrs.atime.seconds
: current.st_atime);
times[1].tv_sec = ((attrs.valid & SSH_FILEXFER_ATTR_MODIFYTIME)
? (time_t)attrs.mtime.seconds
: current.st_mtime);
#ifdef ST_ATIM
if(attrs.valid & SSH_FILEXFER_ATTR_SUBSECOND_TIMES) {
times[0].tv_nsec = ((attrs.valid & SSH_FILEXFER_ATTR_ACCESSTIME)
? (long)attrs.atime.nanoseconds
: current.ST_ATIM.tv_nsec);
times[1].tv_nsec = ((attrs.valid & SSH_FILEXFER_ATTR_MODIFYTIME)
? (long)attrs.mtime.nanoseconds
: current.ST_MTIM.tv_nsec);
}
#endif
D(("...utimes to atime %lu.%09lu mtime %lu.%09lu",
(unsigned long)times[0].tv_sec, (unsigned long)times[0].tv_nsec,
(unsigned long)times[1].tv_sec, (unsigned long)times[1].tv_nsec));
if(cb->do_utimens(what, times) < 0) {
*whyp = "utimens";
return HANDLER_ERRNO;
}
}
return SSH_FX_OK;
}
/** @brief Truncate a file by name
* @param what Object to truncate
* @param size New size
* @return 0 on success, -ve on error
*/
static int name_truncate(const void *what, off_t size) {
return truncate(what, size);
}
/** @brief Change ownership by name
* @param what Object to modify
* @param uid New UID
* @param gid New GID
* @return 0 on success, -ve on error
*/
static int name_chown(const void *what, uid_t uid, gid_t gid) {
return chown(what, uid, gid);
}
/** @brief Change mode by name
* @param what Object to modify
* @param mode New mode
* @return 0 on success, -ve on error
*/
static int name_chmod(const void *what, mode_t mode) {
return chmod(what, mode);
}
/** @brief Get attributes by name
* @param what Object to inspect
* @param sb Where to store information
* @return 0 on success, -ve on error
*/
static int name_lstat(const void *what, struct stat *sb) {
return lstat(what, sb);
}
/** @brief Set file times by name
* @param what Object to modify
* @param tv New times
* @return 0 on success, -ve on error
*/
static int name_utimens(const void *what, struct timespec *tv) {
return utimensat(AT_FDCWD, what, tv, 0);
}
/** @brief Table of callbacks for sftp_set_status() */
static const struct sftp_set_status_callbacks name_callbacks = {
name_truncate, name_chown, name_chmod, name_lstat, name_utimens};
uint32_t sftp_set_status(struct allocator *a, const char *path,
const struct sftpattr *attrsp, const char **whyp) {
return do_sftp_set_status(a, path, attrsp, &name_callbacks, whyp);
}
/** @brief Truncate a file by fd
* @param what Object to truncate
* @param size New size
* @return 0 on success, -ve on error
*/
static int fd_truncate(const void *what, off_t size) {
return ftruncate(*(const int *)what, size);
}
/** @brief Change ownership by fd
* @param what Object to modify
* @param uid New UID
* @param gid New GID
* @return 0 on success, -ve on error
*/
static int fd_chown(const void *what, uid_t uid, gid_t gid) {
return fchown(*(const int *)what, uid, gid);
}
/** @brief Change mode by fd
* @param what Object to modify
* @param mode New mode
* @return 0 on success, -ve on error
*/
static int fd_chmod(const void *what, mode_t mode) {
return fchmod(*(const int *)what, mode);
}
/** @brief Get attributes by fd
* @param what Object to inspect
* @param sb Where to store information
* @return 0 on success, -ve on error
*/
static int fd_stat(const void *what, struct stat *sb) {
return fstat(*(const int *)what, sb);
}
/** @brief Set file times by fd
* @param what Object to modify
* @param tv New times
* @return 0 on success, -ve on error
*/
static int fd_utimens(const void *what, struct timespec *tv) {
return futimens(*(const int *)what, tv);
}
/** @brief Table of callbacks for sftp_set_fstatus() */
static const struct sftp_set_status_callbacks fd_callbacks = {
fd_truncate, fd_chown, fd_chmod, fd_stat, fd_utimens};
uint32_t sftp_set_fstatus(struct allocator *a, int fd,
const struct sftpattr *attrsp, const char **whyp) {
return do_sftp_set_status(a, &fd, attrsp, &fd_callbacks, whyp);
}
/*
Local Variables:
c-basic-offset:2
comment-column:40
fill-column:79
indent-tabs-mode:nil
End:
*/