mirror of
https://github.com/samba-team/samba.git
synced 2025-02-07 01:58:28 +03:00
the real source code for the smbfs utilities. Forgot to do
cvs add first. Volker
This commit is contained in:
parent
ccbbc4e647
commit
65406a546e
297
source/client/smbmnt.c
Normal file
297
source/client/smbmnt.c
Normal file
@ -0,0 +1,297 @@
|
||||
/*
|
||||
* smbmount.c
|
||||
*
|
||||
* Copyright (C) 1995, 1996 by Paal-Kr. Engstad and Volker Lendecke
|
||||
*
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <signal.h>
|
||||
#include <pwd.h>
|
||||
#include <grp.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/param.h>
|
||||
#include <netinet/in.h>
|
||||
#include <netdb.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/types.h>
|
||||
/* #include <sys/wait.h> */ /* generates a warning here */
|
||||
extern pid_t waitpid(pid_t, int *, int);
|
||||
#include <sys/errno.h>
|
||||
#include <unistd.h>
|
||||
#include <fcntl.h>
|
||||
#include <errno.h>
|
||||
#include <ctype.h>
|
||||
#include <stdlib.h>
|
||||
#include <sys/mount.h>
|
||||
#include <mntent.h>
|
||||
|
||||
#include <linux/fs.h>
|
||||
#include <linux/smb.h>
|
||||
#include <linux/smb_mount.h>
|
||||
|
||||
#include <asm/unistd.h>
|
||||
|
||||
static char *progname;
|
||||
|
||||
|
||||
static void
|
||||
usage(void)
|
||||
{
|
||||
printf("usage: %s mount-point [options]\n", progname);
|
||||
printf("Try `%s -h' for more information\n", progname);
|
||||
}
|
||||
|
||||
static void
|
||||
help(void)
|
||||
{
|
||||
printf("\n");
|
||||
printf("usage: %s mount-point [options]\n", progname);
|
||||
printf("-u uid uid the mounted files get\n"
|
||||
"-g gid gid the mounted files get\n"
|
||||
"-f mode permission the files get (octal notation)\n"
|
||||
"-d mode permission the dirs get (octal notation)\n"
|
||||
"-P pid connection handler's pid\n\n"
|
||||
"-h print this help text\n");
|
||||
}
|
||||
|
||||
static int
|
||||
parse_args(int argc, char *argv[], struct smb_mount_data *data)
|
||||
{
|
||||
int opt;
|
||||
struct passwd *pwd;
|
||||
struct group *grp;
|
||||
|
||||
while ((opt = getopt (argc, argv, "u:g:f:d:"))
|
||||
!= EOF)
|
||||
{
|
||||
switch (opt)
|
||||
{
|
||||
case 'u':
|
||||
if (isdigit(optarg[0]))
|
||||
{
|
||||
data->uid = atoi(optarg);
|
||||
}
|
||||
else
|
||||
{
|
||||
pwd = getpwnam(optarg);
|
||||
if (pwd == NULL)
|
||||
{
|
||||
fprintf(stderr, "Unknown user: %s\n",
|
||||
optarg);
|
||||
return 1;
|
||||
}
|
||||
data->uid = pwd->pw_uid;
|
||||
}
|
||||
break;
|
||||
case 'g':
|
||||
if (isdigit(optarg[0]))
|
||||
{
|
||||
data->gid = atoi(optarg);
|
||||
}
|
||||
else
|
||||
{
|
||||
grp = getgrnam(optarg);
|
||||
if (grp == NULL)
|
||||
{
|
||||
fprintf(stderr, "Unknown group: %s\n",
|
||||
optarg);
|
||||
return 1;
|
||||
}
|
||||
data->gid = grp->gr_gid;
|
||||
}
|
||||
break;
|
||||
case 'f':
|
||||
data->file_mode = strtol(optarg, NULL, 8);
|
||||
break;
|
||||
case 'd':
|
||||
data->dir_mode = strtol(optarg, NULL, 8);
|
||||
break;
|
||||
default:
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
|
||||
}
|
||||
|
||||
static char *
|
||||
fullpath(const char *p)
|
||||
{
|
||||
char path[MAXPATHLEN];
|
||||
|
||||
if (strlen(p) > MAXPATHLEN-1)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (realpath(p, path) == NULL)
|
||||
{
|
||||
return strdup(p);
|
||||
}
|
||||
return strdup(path);
|
||||
}
|
||||
|
||||
/* Check whether user is allowed to mount on the specified mount point */
|
||||
static int
|
||||
mount_ok(struct stat *st)
|
||||
{
|
||||
if (!S_ISDIR(st->st_mode))
|
||||
{
|
||||
errno = ENOTDIR;
|
||||
return -1;
|
||||
}
|
||||
|
||||
if ( (getuid() != 0)
|
||||
&& ( (getuid() != st->st_uid)
|
||||
|| ((st->st_mode & S_IRWXU) != S_IRWXU)))
|
||||
{
|
||||
errno = EPERM;
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int
|
||||
main(int argc, char *argv[])
|
||||
{
|
||||
struct smb_mount_data data;
|
||||
struct stat st;
|
||||
|
||||
int fd;
|
||||
int um;
|
||||
unsigned int flags;
|
||||
|
||||
char *mount_point;
|
||||
|
||||
struct mntent ment;
|
||||
FILE *mtab;
|
||||
|
||||
progname = argv[0];
|
||||
|
||||
memset(&data, 0, sizeof(struct smb_mount_data));
|
||||
|
||||
if ( (argc == 2)
|
||||
&& (argv[1][0] == '-')
|
||||
&& (argv[1][1] == 'h')
|
||||
&& (argv[1][2] == '\0'))
|
||||
{
|
||||
help();
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (geteuid() != 0) {
|
||||
fprintf(stderr, "%s must be installed suid root\n", progname);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
if (argc < 2)
|
||||
{
|
||||
usage();
|
||||
return 1;
|
||||
}
|
||||
|
||||
mount_point = argv[1];
|
||||
|
||||
argv += 1;
|
||||
argc -= 1;
|
||||
|
||||
if (stat(mount_point, &st) == -1) {
|
||||
fprintf(stderr, "could not find mount point %s: %s\n",
|
||||
mount_point, strerror(errno));
|
||||
exit(1);
|
||||
}
|
||||
|
||||
if (mount_ok(&st) != 0) {
|
||||
fprintf(stderr, "cannot mount on %s: %s\n",
|
||||
mount_point, strerror(errno));
|
||||
exit(1);
|
||||
}
|
||||
|
||||
data.version = SMB_MOUNT_VERSION;
|
||||
|
||||
/* getuid() gives us the real uid, who may umount the fs */
|
||||
data.mounted_uid = getuid();
|
||||
|
||||
data.uid = getuid();
|
||||
data.gid = getgid();
|
||||
um = umask(0);
|
||||
umask(um);
|
||||
data.file_mode = (S_IRWXU|S_IRWXG|S_IRWXO) & ~um;
|
||||
data.dir_mode = 0;
|
||||
|
||||
if (parse_args(argc, argv, &data) != 0) {
|
||||
usage();
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (data.dir_mode == 0) {
|
||||
data.dir_mode = data.file_mode;
|
||||
if ((data.dir_mode & S_IRUSR) != 0)
|
||||
data.dir_mode |= S_IXUSR;
|
||||
if ((data.dir_mode & S_IRGRP) != 0)
|
||||
data.dir_mode |= S_IXGRP;
|
||||
if ((data.dir_mode & S_IROTH) != 0)
|
||||
data.dir_mode |= S_IXOTH;
|
||||
}
|
||||
|
||||
flags = MS_MGC_VAL;
|
||||
|
||||
if (mount(NULL, mount_point, "smbfs",
|
||||
flags, (char *)&data) < 0) {
|
||||
perror("mount error");
|
||||
printf("Please look at smbmount's manual page for "
|
||||
"possible reasons\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
ment.mnt_fsname = "none";
|
||||
ment.mnt_dir = fullpath(mount_point);
|
||||
ment.mnt_type = "smbfs";
|
||||
ment.mnt_opts = "";
|
||||
ment.mnt_freq = 0;
|
||||
ment.mnt_passno= 0;
|
||||
|
||||
mount_point = ment.mnt_dir;
|
||||
|
||||
if (mount_point == NULL)
|
||||
{
|
||||
fprintf(stderr, "Mount point too long\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
if ((fd = open(MOUNTED"~", O_RDWR|O_CREAT|O_EXCL, 0600)) == -1)
|
||||
{
|
||||
fprintf(stderr, "Can't get "MOUNTED"~ lock file");
|
||||
return 1;
|
||||
}
|
||||
close(fd);
|
||||
|
||||
if ((mtab = setmntent(MOUNTED, "a+")) == NULL)
|
||||
{
|
||||
fprintf(stderr, "Can't open " MOUNTED);
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (addmntent(mtab, &ment) == 1)
|
||||
{
|
||||
fprintf(stderr, "Can't write mount entry");
|
||||
return 1;
|
||||
}
|
||||
if (fchmod(fileno(mtab), 0644) == -1)
|
||||
{
|
||||
fprintf(stderr, "Can't set perms on "MOUNTED);
|
||||
return 1;
|
||||
}
|
||||
endmntent(mtab);
|
||||
|
||||
if (unlink(MOUNTED"~") == -1)
|
||||
{
|
||||
fprintf(stderr, "Can't remove "MOUNTED"~");
|
||||
return 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
941
source/client/smbmount.c
Normal file
941
source/client/smbmount.c
Normal file
@ -0,0 +1,941 @@
|
||||
/*
|
||||
Unix SMB/Netbios implementation.
|
||||
Version 1.9.
|
||||
SMB client
|
||||
Copyright (C) Andrew Tridgell 1994-1997
|
||||
|
||||
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., 675 Mass Ave, Cambridge, MA 02139, USA.
|
||||
*/
|
||||
|
||||
#ifdef SYSLOG
|
||||
#undef SYSLOG
|
||||
#endif
|
||||
|
||||
#include "includes.h"
|
||||
#include <linux/smb_fs.h>
|
||||
static struct smb_conn_opt conn_options;
|
||||
|
||||
#ifndef REGISTER
|
||||
#define REGISTER 0
|
||||
#endif
|
||||
|
||||
pstring cur_dir = "\\";
|
||||
pstring cd_path = "";
|
||||
extern pstring service;
|
||||
extern pstring desthost;
|
||||
extern pstring myname;
|
||||
extern pstring myhostname;
|
||||
extern pstring password;
|
||||
extern pstring username;
|
||||
extern pstring workgroup;
|
||||
char *cmdstr="";
|
||||
extern BOOL got_pass;
|
||||
extern BOOL connect_as_printer;
|
||||
extern BOOL connect_as_ipc;
|
||||
extern struct in_addr ipzero;
|
||||
|
||||
extern BOOL doencrypt;
|
||||
|
||||
extern pstring user_socket_options;
|
||||
|
||||
/* 30 second timeout on most commands */
|
||||
#define CLIENT_TIMEOUT (30*1000)
|
||||
#define SHORT_TIMEOUT (5*1000)
|
||||
|
||||
/* value for unused fid field in trans2 secondary request */
|
||||
#define FID_UNUSED (0xFFFF)
|
||||
|
||||
extern int name_type;
|
||||
|
||||
extern int max_protocol;
|
||||
|
||||
|
||||
time_t newer_than = 0;
|
||||
int archive_level = 0;
|
||||
|
||||
extern pstring debugf;
|
||||
extern int DEBUGLEVEL;
|
||||
|
||||
BOOL translation = False;
|
||||
|
||||
extern int cnum;
|
||||
extern int mid;
|
||||
extern int pid;
|
||||
extern int tid;
|
||||
extern int gid;
|
||||
extern int uid;
|
||||
|
||||
extern BOOL have_ip;
|
||||
extern int max_xmit;
|
||||
|
||||
/* clitar bits insert */
|
||||
extern int blocksize;
|
||||
extern BOOL tar_inc;
|
||||
extern BOOL tar_reset;
|
||||
/* clitar bits end */
|
||||
|
||||
|
||||
int myumask = 0755;
|
||||
|
||||
extern pstring scope;
|
||||
|
||||
BOOL prompt = True;
|
||||
|
||||
int printmode = 1;
|
||||
|
||||
BOOL recurse = False;
|
||||
BOOL lowercase = False;
|
||||
|
||||
struct in_addr dest_ip;
|
||||
|
||||
#define SEPARATORS " \t\n\r"
|
||||
|
||||
BOOL abort_mget = True;
|
||||
|
||||
extern int Protocol;
|
||||
|
||||
extern BOOL readbraw_supported ;
|
||||
extern BOOL writebraw_supported;
|
||||
|
||||
pstring fileselection = "";
|
||||
|
||||
extern file_info def_finfo;
|
||||
|
||||
/* timing globals */
|
||||
int get_total_size = 0;
|
||||
int get_total_time_ms = 0;
|
||||
int put_total_size = 0;
|
||||
int put_total_time_ms = 0;
|
||||
|
||||
/* totals globals */
|
||||
int dir_total = 0;
|
||||
|
||||
extern int Client;
|
||||
|
||||
#define USENMB
|
||||
|
||||
static BOOL setup_term_code(char *code)
|
||||
{
|
||||
interpret_coding_system(code);
|
||||
return True;
|
||||
}
|
||||
#define CNV_LANG(s) dos2unix_format(s,False)
|
||||
#define CNV_INPUT(s) unix2dos_format(s,True)
|
||||
|
||||
/****************************************************************************
|
||||
check for existance of a dir
|
||||
****************************************************************************/
|
||||
static BOOL chkpath(char *path,BOOL report)
|
||||
{
|
||||
fstring path2;
|
||||
pstring inbuf,outbuf;
|
||||
char *p;
|
||||
|
||||
strcpy(path2,path);
|
||||
trim_string(path2,NULL,"\\");
|
||||
if (!*path2) *path2 = '\\';
|
||||
|
||||
bzero(outbuf,smb_size);
|
||||
set_message(outbuf,0,4 + strlen(path2),True);
|
||||
SCVAL(outbuf,smb_com,SMBchkpth);
|
||||
SSVAL(outbuf,smb_tid,cnum);
|
||||
cli_setup_pkt(outbuf);
|
||||
|
||||
p = smb_buf(outbuf);
|
||||
*p++ = 4;
|
||||
strcpy(p,path2);
|
||||
|
||||
#if 0
|
||||
{
|
||||
/* this little bit of code can be used to extract NT error codes.
|
||||
Just feed a bunch of "cd foo" commands to smbclient then watch
|
||||
in netmon (tridge) */
|
||||
static int code=0;
|
||||
SIVAL(outbuf, smb_rcls, code | 0xC0000000);
|
||||
SSVAL(outbuf, smb_flg2, SVAL(outbuf, smb_flg2) | (1<<14));
|
||||
code++;
|
||||
}
|
||||
#endif
|
||||
|
||||
send_smb(Client,outbuf);
|
||||
client_receive_smb(Client,inbuf,CLIENT_TIMEOUT);
|
||||
|
||||
if (report && CVAL(inbuf,smb_rcls) != 0)
|
||||
DEBUG(2,("chkpath: %s\n",smb_errstr(inbuf)));
|
||||
|
||||
return(CVAL(inbuf,smb_rcls) == 0);
|
||||
}
|
||||
|
||||
|
||||
/* #define SMBFS_DEBUG 1 */
|
||||
|
||||
static void
|
||||
daemonize(void)
|
||||
{
|
||||
int i;
|
||||
if ((i = fork()) < 0)
|
||||
{
|
||||
DEBUG(0, ("could not fork\n"));
|
||||
}
|
||||
if (i > 0)
|
||||
{
|
||||
/* parent simply exits */
|
||||
exit(0);
|
||||
}
|
||||
setsid();
|
||||
chdir("/");
|
||||
}
|
||||
|
||||
static void
|
||||
close_our_files(void)
|
||||
{
|
||||
int i;
|
||||
for (i = 0; i < NR_OPEN; i++) {
|
||||
if (i == Client) {
|
||||
continue;
|
||||
}
|
||||
close(i);
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
usr1_handler(int x)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
/*
|
||||
* Send a login and store the connection options. This is a separate
|
||||
* function to keep clientutil.c independent of linux kernel changes.
|
||||
*/
|
||||
static BOOL mount_send_login(char *inbuf, char *outbuf)
|
||||
{
|
||||
struct connection_options opt;
|
||||
int res = cli_send_login(inbuf, outbuf, True, True, &opt);
|
||||
|
||||
if (!res)
|
||||
return res;
|
||||
|
||||
conn_options.protocol = opt.protocol;
|
||||
conn_options.case_handling = CASE_LOWER;
|
||||
conn_options.max_xmit = opt.max_xmit;
|
||||
conn_options.server_uid = opt.server_uid;
|
||||
conn_options.tid = opt.tid;
|
||||
conn_options.secmode = opt.sec_mode;
|
||||
conn_options.maxmux = opt.max_mux;
|
||||
conn_options.maxvcs = opt.max_vcs;
|
||||
conn_options.rawmode = opt.rawmode;
|
||||
conn_options.sesskey = opt.sesskey;
|
||||
conn_options.maxraw = opt.maxraw;
|
||||
conn_options.capabilities = opt.capabilities;
|
||||
conn_options.serverzone = opt.serverzone;
|
||||
|
||||
return True;
|
||||
}
|
||||
|
||||
/*
|
||||
* Call the smbfs ioctl to install a connection socket,
|
||||
* then wait for a signal to reconnect. Note that we do
|
||||
* not exit after open_sockets() or send_login() errors,
|
||||
* as the smbfs mount would then have no way to recover.
|
||||
*/
|
||||
static void
|
||||
send_fs_socket(char *mount_point, char *inbuf, char *outbuf)
|
||||
{
|
||||
int fd, closed = 0, res = 1;
|
||||
|
||||
while (1)
|
||||
{
|
||||
if ((fd = open(mount_point, O_RDONLY)) < 0)
|
||||
{
|
||||
#ifdef SMBFS_DEBUG
|
||||
printf("smbclient: can't open %s\n", mount_point);
|
||||
#endif
|
||||
break;
|
||||
}
|
||||
|
||||
/*
|
||||
* Call the ioctl even if we couldn't get a socket ...
|
||||
* there's no point in making smbfs wait for a timeout.
|
||||
*/
|
||||
conn_options.fd = -1;
|
||||
if (res)
|
||||
conn_options.fd = Client;
|
||||
res = ioctl(fd, SMB_IOC_NEWCONN, &conn_options);
|
||||
if (res != 0)
|
||||
{
|
||||
#ifdef SMBFS_DEBUG
|
||||
printf("smbclient: ioctl failed, res=%d\n",res);
|
||||
#endif
|
||||
}
|
||||
|
||||
close_sockets();
|
||||
close(fd);
|
||||
/*
|
||||
* Close all open files if we haven't done so yet.
|
||||
*/
|
||||
#ifndef SMBFS_DEBUG
|
||||
if (!closed)
|
||||
{
|
||||
closed = 1;
|
||||
close_our_files();
|
||||
}
|
||||
#endif
|
||||
|
||||
/*
|
||||
* Wait for a signal from smbfs ...
|
||||
*/
|
||||
signal(SIGUSR1, &usr1_handler);
|
||||
pause();
|
||||
#ifdef SMBFS_DEBUG
|
||||
printf("smbclient: got signal, getting new socket\n");
|
||||
#endif
|
||||
|
||||
res = mount_send_login(inbuf,outbuf);
|
||||
if (!res)
|
||||
{
|
||||
#ifdef SMBFS_DEBUG
|
||||
printf("smbclient: login failed\n");
|
||||
#endif
|
||||
}
|
||||
}
|
||||
#ifdef SMBFS_DEBUG
|
||||
printf("smbclient: exit\n");
|
||||
#endif
|
||||
exit(1);
|
||||
}
|
||||
|
||||
/****************************************************************************
|
||||
mount smbfs
|
||||
****************************************************************************/
|
||||
static void cmd_mount(char *inbuf,char *outbuf)
|
||||
{
|
||||
pstring mpoint;
|
||||
char mount_point[MAXPATHLEN+1];
|
||||
pstring mount_command;
|
||||
fstring buf;
|
||||
int retval;
|
||||
|
||||
if (!next_token(NULL, mpoint, NULL))
|
||||
{
|
||||
DEBUG(0,("You must supply a mount point\n"));
|
||||
return;
|
||||
}
|
||||
|
||||
memset(mount_point, 0, sizeof(mount_point));
|
||||
|
||||
if (realpath(mpoint, mount_point) == NULL)
|
||||
{
|
||||
DEBUG(0, ("Could not resolve mount point\n"));
|
||||
return;
|
||||
}
|
||||
|
||||
sprintf(mount_command, "smbmnt %s", mount_point);
|
||||
|
||||
while(next_token(NULL, buf, NULL))
|
||||
{
|
||||
strcat(mount_command, " ");
|
||||
strcat(mount_command, buf);
|
||||
}
|
||||
|
||||
DEBUG(3,("mount command: %s\n", mount_command));
|
||||
|
||||
/*
|
||||
* Create the background process before trying the mount.
|
||||
* (We delay closing files to allow diagnostic messages.)
|
||||
*/
|
||||
daemonize();
|
||||
|
||||
/* The parent has exited here, the child handles the connection: */
|
||||
if ((retval = system(mount_command)) != 0)
|
||||
{
|
||||
DEBUG(0,("mount failed\n"));
|
||||
exit(1);
|
||||
}
|
||||
send_fs_socket(mount_point, inbuf, outbuf);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/* This defines the commands supported by this client */
|
||||
struct
|
||||
{
|
||||
char *name;
|
||||
void (*fn)();
|
||||
char *description;
|
||||
} commands[] =
|
||||
{
|
||||
{"mount", cmd_mount, "<mount-point options> mount an smbfs file system"},
|
||||
{"",NULL,NULL}
|
||||
};
|
||||
|
||||
|
||||
/*******************************************************************
|
||||
lookup a command string in the list of commands, including
|
||||
abbreviations
|
||||
******************************************************************/
|
||||
static int process_tok(fstring tok)
|
||||
{
|
||||
int i = 0, matches = 0;
|
||||
int cmd=0;
|
||||
int tok_len = strlen(tok);
|
||||
|
||||
while (commands[i].fn != NULL)
|
||||
{
|
||||
if (strequal(commands[i].name,tok))
|
||||
{
|
||||
matches = 1;
|
||||
cmd = i;
|
||||
break;
|
||||
}
|
||||
else if (strnequal(commands[i].name, tok, tok_len))
|
||||
{
|
||||
matches++;
|
||||
cmd = i;
|
||||
}
|
||||
i++;
|
||||
}
|
||||
|
||||
if (matches == 0)
|
||||
return(-1);
|
||||
else if (matches == 1)
|
||||
return(cmd);
|
||||
else
|
||||
return(-2);
|
||||
}
|
||||
|
||||
/****************************************************************************
|
||||
help
|
||||
****************************************************************************/
|
||||
void cmd_help(void)
|
||||
{
|
||||
int i=0,j;
|
||||
fstring buf;
|
||||
|
||||
if (next_token(NULL,buf,NULL))
|
||||
{
|
||||
if ((i = process_tok(buf)) >= 0)
|
||||
DEBUG(0,("HELP %s:\n\t%s\n\n",commands[i].name,commands[i].description));
|
||||
}
|
||||
else
|
||||
while (commands[i].description)
|
||||
{
|
||||
for (j=0; commands[i].description && (j<5); j++) {
|
||||
DEBUG(0,("%-15s",commands[i].name));
|
||||
i++;
|
||||
}
|
||||
DEBUG(0,("\n"));
|
||||
}
|
||||
}
|
||||
|
||||
/****************************************************************************
|
||||
wait for keyboard activity, swallowing network packets
|
||||
****************************************************************************/
|
||||
#ifdef CLIX
|
||||
static char wait_keyboard(char *buffer)
|
||||
#else
|
||||
static void wait_keyboard(char *buffer)
|
||||
#endif
|
||||
{
|
||||
fd_set fds;
|
||||
int selrtn;
|
||||
struct timeval timeout;
|
||||
|
||||
#ifdef CLIX
|
||||
int delay = 0;
|
||||
#endif
|
||||
|
||||
while (1)
|
||||
{
|
||||
extern int Client;
|
||||
FD_ZERO(&fds);
|
||||
FD_SET(Client,&fds);
|
||||
#ifndef CLIX
|
||||
FD_SET(fileno(stdin),&fds);
|
||||
#endif
|
||||
|
||||
timeout.tv_sec = 20;
|
||||
timeout.tv_usec = 0;
|
||||
#ifdef CLIX
|
||||
timeout.tv_sec = 0;
|
||||
#endif
|
||||
selrtn = sys_select(&fds,&timeout);
|
||||
|
||||
#ifndef CLIX
|
||||
if (FD_ISSET(fileno(stdin),&fds))
|
||||
return;
|
||||
#else
|
||||
{
|
||||
char ch;
|
||||
int readret;
|
||||
|
||||
set_blocking(fileno(stdin), False);
|
||||
readret = read_data( fileno(stdin), &ch, 1);
|
||||
set_blocking(fileno(stdin), True);
|
||||
if (readret == -1)
|
||||
{
|
||||
if (errno != EAGAIN)
|
||||
{
|
||||
/* should crash here */
|
||||
DEBUG(1,("readchar stdin failed\n"));
|
||||
}
|
||||
}
|
||||
else if (readret != 0)
|
||||
{
|
||||
return ch;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
/* We deliberately use receive_smb instead of
|
||||
client_receive_smb as we want to receive
|
||||
session keepalives and then drop them here.
|
||||
*/
|
||||
if (FD_ISSET(Client,&fds))
|
||||
receive_smb(Client,buffer,0);
|
||||
|
||||
#ifdef CLIX
|
||||
delay++;
|
||||
if (delay > 100000)
|
||||
{
|
||||
delay = 0;
|
||||
chkpath("\\",False);
|
||||
}
|
||||
#else
|
||||
chkpath("\\",False);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/****************************************************************************
|
||||
process commands from the client
|
||||
****************************************************************************/
|
||||
static BOOL process(char *base_directory)
|
||||
{
|
||||
extern FILE *dbf;
|
||||
pstring line;
|
||||
char *cmd;
|
||||
|
||||
char *InBuffer = (char *)malloc(BUFFER_SIZE + SAFETY_MARGIN);
|
||||
char *OutBuffer = (char *)malloc(BUFFER_SIZE + SAFETY_MARGIN);
|
||||
|
||||
if ((InBuffer == NULL) || (OutBuffer == NULL))
|
||||
return(False);
|
||||
|
||||
bzero(OutBuffer,smb_size);
|
||||
|
||||
if (!mount_send_login(InBuffer,OutBuffer))
|
||||
return(False);
|
||||
|
||||
cmd = cmdstr;
|
||||
if (cmd[0] != '\0') while (cmd[0] != '\0')
|
||||
{
|
||||
char *p;
|
||||
fstring tok;
|
||||
int i;
|
||||
|
||||
if ((p = strchr(cmd, ';')) == 0)
|
||||
{
|
||||
strncpy(line, cmd, 999);
|
||||
line[1000] = '\0';
|
||||
cmd += strlen(cmd);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (p - cmd > 999) p = cmd + 999;
|
||||
strncpy(line, cmd, p - cmd);
|
||||
line[p - cmd] = '\0';
|
||||
cmd = p + 1;
|
||||
}
|
||||
|
||||
/* input language code to internal one */
|
||||
CNV_INPUT (line);
|
||||
|
||||
/* and get the first part of the command */
|
||||
{
|
||||
char *ptr = line;
|
||||
if (!next_token(&ptr,tok,NULL)) continue;
|
||||
}
|
||||
|
||||
if ((i = process_tok(tok)) >= 0)
|
||||
commands[i].fn(InBuffer,OutBuffer);
|
||||
else if (i == -2)
|
||||
DEBUG(0,("%s: command abbreviation ambiguous\n",CNV_LANG(tok)));
|
||||
else
|
||||
DEBUG(0,("%s: command not found\n",CNV_LANG(tok)));
|
||||
}
|
||||
else while (!feof(stdin))
|
||||
{
|
||||
fstring tok;
|
||||
int i;
|
||||
|
||||
bzero(OutBuffer,smb_size);
|
||||
|
||||
/* display a prompt */
|
||||
DEBUG(0,("smb: %s> ", CNV_LANG(cur_dir)));
|
||||
fflush(dbf);
|
||||
|
||||
#ifdef CLIX
|
||||
line[0] = wait_keyboard(InBuffer);
|
||||
/* this might not be such a good idea... */
|
||||
if ( line[0] == EOF)
|
||||
break;
|
||||
#else
|
||||
wait_keyboard(InBuffer);
|
||||
#endif
|
||||
|
||||
/* and get a response */
|
||||
#ifdef CLIX
|
||||
fgets( &line[1],999, stdin);
|
||||
#else
|
||||
if (!fgets(line,1000,stdin))
|
||||
break;
|
||||
#endif
|
||||
|
||||
/* input language code to internal one */
|
||||
CNV_INPUT (line);
|
||||
|
||||
/* special case - first char is ! */
|
||||
if (*line == '!')
|
||||
{
|
||||
system(line + 1);
|
||||
continue;
|
||||
}
|
||||
|
||||
/* and get the first part of the command */
|
||||
{
|
||||
char *ptr = line;
|
||||
if (!next_token(&ptr,tok,NULL)) continue;
|
||||
}
|
||||
|
||||
if ((i = process_tok(tok)) >= 0)
|
||||
commands[i].fn(InBuffer,OutBuffer);
|
||||
else if (i == -2)
|
||||
DEBUG(0,("%s: command abbreviation ambiguous\n",CNV_LANG(tok)));
|
||||
else
|
||||
DEBUG(0,("%s: command not found\n",CNV_LANG(tok)));
|
||||
}
|
||||
|
||||
cli_send_logout();
|
||||
return(True);
|
||||
}
|
||||
|
||||
/****************************************************************************
|
||||
usage on the program
|
||||
****************************************************************************/
|
||||
static void usage(char *pname)
|
||||
{
|
||||
DEBUG(0,("Usage: %s service <password> [-p port] [-d debuglevel] [-l log] ",
|
||||
pname));
|
||||
|
||||
DEBUG(0,("\nVersion %s\n",VERSION));
|
||||
DEBUG(0,("\t-p port connect to the specified port\n"));
|
||||
DEBUG(0,("\t-d debuglevel set the debuglevel\n"));
|
||||
DEBUG(0,("\t-l log basename. Basename for log/debug files\n"));
|
||||
DEBUG(0,("\t-n netbios name. Use this name as my netbios name\n"));
|
||||
DEBUG(0,("\t-N don't ask for a password\n"));
|
||||
DEBUG(0,("\t-P connect to service as a printer\n"));
|
||||
DEBUG(0,("\t-M host send a winpopup message to the host\n"));
|
||||
DEBUG(0,("\t-m max protocol set the max protocol level\n"));
|
||||
DEBUG(0,("\t-L host get a list of shares available on a host\n"));
|
||||
DEBUG(0,("\t-I dest IP use this IP to connect to\n"));
|
||||
DEBUG(0,("\t-E write messages to stderr instead of stdout\n"));
|
||||
DEBUG(0,("\t-U username set the network username\n"));
|
||||
DEBUG(0,("\t-W workgroup set the workgroup name\n"));
|
||||
DEBUG(0,("\t-c command string execute semicolon separated commands\n"));
|
||||
DEBUG(0,("\t-t terminal code terminal i/o code {sjis|euc|jis7|jis8|junet|hex}\n"));
|
||||
DEBUG(0,("\t-T<c|x>IXgbNa command line tar\n"));
|
||||
DEBUG(0,("\t-D directory start from directory\n"));
|
||||
DEBUG(0,("\n"));
|
||||
}
|
||||
|
||||
/****************************************************************************
|
||||
main program
|
||||
****************************************************************************/
|
||||
int main(int argc,char *argv[])
|
||||
{
|
||||
fstring base_directory;
|
||||
char *pname = argv[0];
|
||||
int port = SMB_PORT;
|
||||
int opt;
|
||||
extern FILE *dbf;
|
||||
extern char *optarg;
|
||||
extern int optind;
|
||||
pstring query_host;
|
||||
BOOL nt_domain_logon = False;
|
||||
static pstring servicesf = CONFIGFILE;
|
||||
pstring term_code;
|
||||
char *p;
|
||||
|
||||
#ifdef KANJI
|
||||
strcpy(term_code, KANJI);
|
||||
#else /* KANJI */
|
||||
*term_code = 0;
|
||||
#endif /* KANJI */
|
||||
|
||||
*query_host = 0;
|
||||
*base_directory = 0;
|
||||
|
||||
DEBUGLEVEL = 2;
|
||||
|
||||
setup_logging(pname,True);
|
||||
|
||||
TimeInit();
|
||||
charset_initialise();
|
||||
|
||||
pid = getpid();
|
||||
uid = getuid();
|
||||
gid = getgid();
|
||||
mid = pid + 100;
|
||||
myumask = umask(0);
|
||||
umask(myumask);
|
||||
|
||||
if (getenv("USER"))
|
||||
{
|
||||
strcpy(username,getenv("USER"));
|
||||
|
||||
/* modification to support userid%passwd syntax in the USER var
|
||||
25.Aug.97, jdblair@uab.edu */
|
||||
|
||||
if ((p=strchr(username,'%')))
|
||||
{
|
||||
*p = 0;
|
||||
strcpy(password,p+1);
|
||||
got_pass = True;
|
||||
memset(strchr(getenv("USER"),'%')+1,'X',strlen(password));
|
||||
}
|
||||
strupper(username);
|
||||
}
|
||||
|
||||
/* modification to support PASSWD environmental var
|
||||
25.Aug.97, jdblair@uab.edu */
|
||||
|
||||
if (getenv("PASSWD"))
|
||||
strcpy(password,getenv("PASSWD"));
|
||||
|
||||
if (*username == 0 && getenv("LOGNAME"))
|
||||
{
|
||||
strcpy(username,getenv("LOGNAME"));
|
||||
strupper(username);
|
||||
}
|
||||
|
||||
if (argc < 2)
|
||||
{
|
||||
usage(pname);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
if (*argv[1] != '-')
|
||||
{
|
||||
|
||||
strcpy(service,argv[1]);
|
||||
/* Convert any '/' characters in the service name to '\' characters */
|
||||
string_replace( service, '/','\\');
|
||||
argc--;
|
||||
argv++;
|
||||
|
||||
if (count_chars(service,'\\') < 3)
|
||||
{
|
||||
usage(pname);
|
||||
printf("\n%s: Not enough '\\' characters in service\n",service);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
/*
|
||||
if (count_chars(service,'\\') > 3)
|
||||
{
|
||||
usage(pname);
|
||||
printf("\n%s: Too many '\\' characters in service\n",service);
|
||||
exit(1);
|
||||
}
|
||||
*/
|
||||
|
||||
if (argc > 1 && (*argv[1] != '-'))
|
||||
{
|
||||
got_pass = True;
|
||||
strcpy(password,argv[1]);
|
||||
memset(argv[1],'X',strlen(argv[1]));
|
||||
argc--;
|
||||
argv++;
|
||||
}
|
||||
}
|
||||
|
||||
while ((opt =
|
||||
getopt(argc, argv,"s:B:O:M:S:i:Nn:d:Pp:l:hI:EB:U:L:t:m:W:T:D:c:")) != EOF)
|
||||
switch (opt)
|
||||
{
|
||||
case 'm':
|
||||
max_protocol = interpret_protocol(optarg,max_protocol);
|
||||
break;
|
||||
case 'O':
|
||||
strcpy(user_socket_options,optarg);
|
||||
break;
|
||||
case 'S':
|
||||
strcpy(desthost,optarg);
|
||||
strupper(desthost);
|
||||
nt_domain_logon = True;
|
||||
break;
|
||||
case 'B':
|
||||
iface_set_default(NULL,optarg,NULL);
|
||||
break;
|
||||
case 'D':
|
||||
strcpy(base_directory,optarg);
|
||||
break;
|
||||
case 'i':
|
||||
strcpy(scope,optarg);
|
||||
break;
|
||||
case 'U':
|
||||
{
|
||||
char *lp;
|
||||
strcpy(username,optarg);
|
||||
if ((lp=strchr(username,'%')))
|
||||
{
|
||||
*lp = 0;
|
||||
strcpy(password,lp+1);
|
||||
got_pass = True;
|
||||
memset(strchr(optarg,'%')+1,'X',strlen(password));
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
case 'W':
|
||||
strcpy(workgroup,optarg);
|
||||
break;
|
||||
case 'E':
|
||||
dbf = stderr;
|
||||
break;
|
||||
case 'I':
|
||||
{
|
||||
dest_ip = *interpret_addr2(optarg);
|
||||
if (zero_ip(dest_ip)) exit(1);
|
||||
have_ip = True;
|
||||
}
|
||||
break;
|
||||
case 'n':
|
||||
strcpy(myname,optarg);
|
||||
break;
|
||||
case 'N':
|
||||
got_pass = True;
|
||||
break;
|
||||
case 'd':
|
||||
if (*optarg == 'A')
|
||||
DEBUGLEVEL = 10000;
|
||||
else
|
||||
DEBUGLEVEL = atoi(optarg);
|
||||
break;
|
||||
case 'l':
|
||||
sprintf(debugf,"%s.client",optarg);
|
||||
break;
|
||||
case 'p':
|
||||
port = atoi(optarg);
|
||||
break;
|
||||
case 'c':
|
||||
cmdstr = optarg;
|
||||
got_pass = True;
|
||||
break;
|
||||
case 'h':
|
||||
usage(pname);
|
||||
exit(0);
|
||||
break;
|
||||
case 's':
|
||||
strcpy(servicesf, optarg);
|
||||
break;
|
||||
case 't':
|
||||
strcpy(term_code, optarg);
|
||||
break;
|
||||
default:
|
||||
usage(pname);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
if (!*query_host && !*service)
|
||||
{
|
||||
usage(pname);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
|
||||
DEBUG(3,("%s client started (version %s)\n",timestring(),VERSION));
|
||||
|
||||
if(!get_myname(myhostname,NULL))
|
||||
{
|
||||
DEBUG(0,("Failed to get my hostname.\n"));
|
||||
}
|
||||
|
||||
if (!lp_load(servicesf,True)) {
|
||||
fprintf(stderr, "Can't load %s - run testparm to debug it\n", servicesf);
|
||||
}
|
||||
|
||||
codepage_initialise(lp_client_code_page());
|
||||
|
||||
if(lp_client_code_page() == KANJI_CODEPAGE)
|
||||
{
|
||||
if (!setup_term_code (term_code))
|
||||
{
|
||||
DEBUG(0, ("%s: unknown terminal code name\n", optarg));
|
||||
usage (pname);
|
||||
exit (1);
|
||||
}
|
||||
}
|
||||
|
||||
if (*workgroup == 0)
|
||||
strcpy(workgroup,lp_workgroup());
|
||||
|
||||
load_interfaces();
|
||||
get_myname((*myname)?NULL:myname,NULL);
|
||||
strupper(myname);
|
||||
|
||||
#ifdef NTDOMAIN
|
||||
|
||||
if (nt_domain_logon)
|
||||
{
|
||||
int ret = 0;
|
||||
sprintf(service,"\\\\%s\\IPC$",query_host);
|
||||
strupper(service);
|
||||
connect_as_ipc = True;
|
||||
|
||||
DEBUG(5,("NT Domain Logon. Service: %s\n", service));
|
||||
|
||||
if (cli_open_sockets(port))
|
||||
{
|
||||
if (!cli_send_login(NULL,NULL,True,True,NULL)) return(1);
|
||||
|
||||
do_nt_login(desthost, myhostname, Client, cnum);
|
||||
|
||||
cli_send_logout();
|
||||
close_sockets();
|
||||
}
|
||||
|
||||
return(ret);
|
||||
}
|
||||
#endif
|
||||
|
||||
if (cli_open_sockets(port))
|
||||
{
|
||||
if (!process(base_directory))
|
||||
{
|
||||
close_sockets();
|
||||
return(1);
|
||||
}
|
||||
close_sockets();
|
||||
}
|
||||
else
|
||||
return(1);
|
||||
|
||||
return(0);
|
||||
}
|
191
source/client/smbumount.c
Normal file
191
source/client/smbumount.c
Normal file
@ -0,0 +1,191 @@
|
||||
/*
|
||||
* smbumount.c
|
||||
*
|
||||
* Copyright (C) 1995 by Volker Lendecke
|
||||
*
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <signal.h>
|
||||
#include <pwd.h>
|
||||
#include <grp.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/param.h>
|
||||
#include <netinet/in.h>
|
||||
#include <netdb.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/types.h>
|
||||
/* #include <sys/wait.h> */ /* generates a warning here */
|
||||
extern pid_t waitpid(pid_t, int *, int);
|
||||
#include <sys/errno.h>
|
||||
#include <unistd.h>
|
||||
#include <fcntl.h>
|
||||
#include <errno.h>
|
||||
#include <ctype.h>
|
||||
#include <stdlib.h>
|
||||
#include <sys/mount.h>
|
||||
#include <mntent.h>
|
||||
|
||||
#include <sys/ioctl.h>
|
||||
#include <linux/fs.h>
|
||||
#include <linux/smb.h>
|
||||
#include <linux/smb_mount.h>
|
||||
#include <linux/smb_fs.h>
|
||||
|
||||
static char *progname;
|
||||
|
||||
static void
|
||||
usage(void)
|
||||
{
|
||||
printf("usage: %s mount-point\n", progname);
|
||||
}
|
||||
|
||||
static int
|
||||
umount_ok(const char *mount_point)
|
||||
{
|
||||
int fid = open(mount_point, O_RDONLY, 0);
|
||||
uid_t mount_uid;
|
||||
|
||||
if (fid == -1) {
|
||||
fprintf(stderr, "Could not open %s: %s\n",
|
||||
mount_point, strerror(errno));
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (ioctl(fid, SMB_IOC_GETMOUNTUID, &mount_uid) != 0) {
|
||||
fprintf(stderr, "%s probably not smb-filesystem\n",
|
||||
mount_point);
|
||||
return -1;
|
||||
}
|
||||
|
||||
if ( (getuid() != 0)
|
||||
&& (mount_uid != getuid())) {
|
||||
fprintf(stderr, "You are not allowed to umount %s\n",
|
||||
mount_point);
|
||||
return -1;
|
||||
}
|
||||
|
||||
close(fid);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Make a canonical pathname from PATH. Returns a freshly malloced string.
|
||||
It is up the *caller* to ensure that the PATH is sensible. i.e.
|
||||
canonicalize ("/dev/fd0/.") returns "/dev/fd0" even though ``/dev/fd0/.''
|
||||
is not a legal pathname for ``/dev/fd0.'' Anything we cannot parse
|
||||
we return unmodified. */
|
||||
char *
|
||||
canonicalize (const char *path)
|
||||
{
|
||||
char *canonical = malloc (PATH_MAX + 1);
|
||||
|
||||
if (strlen(path) > PATH_MAX)
|
||||
{
|
||||
fprintf(stderr, "Mount point string too long\n");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (path == NULL)
|
||||
return NULL;
|
||||
|
||||
if (realpath (path, canonical))
|
||||
return canonical;
|
||||
|
||||
strcpy (canonical, path);
|
||||
return canonical;
|
||||
}
|
||||
|
||||
|
||||
int
|
||||
main(int argc, char *argv[])
|
||||
{
|
||||
int fd;
|
||||
|
||||
char* mount_point;
|
||||
|
||||
struct mntent *mnt;
|
||||
FILE* mtab;
|
||||
FILE* new_mtab;
|
||||
|
||||
progname = argv[0];
|
||||
|
||||
if (argc != 2) {
|
||||
usage();
|
||||
exit(1);
|
||||
}
|
||||
|
||||
if (geteuid() != 0) {
|
||||
fprintf(stderr, "%s must be installed suid root\n", progname);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
mount_point = canonicalize(argv[1]);
|
||||
|
||||
if (mount_point == NULL)
|
||||
{
|
||||
exit(1);
|
||||
}
|
||||
|
||||
if (umount_ok(mount_point) != 0) {
|
||||
exit(1);
|
||||
}
|
||||
|
||||
if (umount(mount_point) != 0) {
|
||||
fprintf(stderr, "Could not umount %s: %s\n",
|
||||
mount_point, strerror(errno));
|
||||
exit(1);
|
||||
}
|
||||
|
||||
if ((fd = open(MOUNTED"~", O_RDWR|O_CREAT|O_EXCL, 0600)) == -1)
|
||||
{
|
||||
fprintf(stderr, "Can't get "MOUNTED"~ lock file");
|
||||
return 1;
|
||||
}
|
||||
close(fd);
|
||||
|
||||
if ((mtab = setmntent(MOUNTED, "r")) == NULL) {
|
||||
fprintf(stderr, "Can't open " MOUNTED ": %s\n",
|
||||
strerror(errno));
|
||||
return 1;
|
||||
}
|
||||
|
||||
#define MOUNTED_TMP MOUNTED".tmp"
|
||||
|
||||
if ((new_mtab = setmntent(MOUNTED_TMP, "w")) == NULL) {
|
||||
fprintf(stderr, "Can't open " MOUNTED_TMP ": %s\n",
|
||||
strerror(errno));
|
||||
endmntent(mtab);
|
||||
return 1;
|
||||
}
|
||||
|
||||
while ((mnt = getmntent(mtab)) != NULL) {
|
||||
if (strcmp(mnt->mnt_dir, mount_point) != 0) {
|
||||
addmntent(new_mtab, mnt);
|
||||
}
|
||||
}
|
||||
|
||||
endmntent(mtab);
|
||||
|
||||
if (fchmod (fileno (new_mtab), S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH) < 0) {
|
||||
fprintf(stderr, "Error changing mode of %s: %s\n",
|
||||
MOUNTED_TMP, strerror(errno));
|
||||
exit(1);
|
||||
}
|
||||
|
||||
endmntent(new_mtab);
|
||||
|
||||
if (rename(MOUNTED_TMP, MOUNTED) < 0) {
|
||||
fprintf(stderr, "Cannot rename %s to %s: %s\n",
|
||||
MOUNTED, MOUNTED_TMP, strerror(errno));
|
||||
exit(1);
|
||||
}
|
||||
|
||||
if (unlink(MOUNTED"~") == -1)
|
||||
{
|
||||
fprintf(stderr, "Can't remove "MOUNTED"~");
|
||||
return 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user