/*
* GProFTPD - A GTK+ frontend for the ProFTPD standalone server.
* Copyright (C) 2001 - 2006 Magnus Loef (Magnus-swe) <magnus-swe@telia.com>
*
* 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.
*
*/
#include "../config.h"
#include <gtk/gtk.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "widgets.h"
#include "gettext.h"
#include "allocate.h"
#include "commands.h"
#include "show_info.h"
#include "add_user.h"
#include "functions.h"
#include "dir_treeview_funcs.h"
#include "system_defines.h"
#include "populate_users.h"
#include "populate_user_settings.h"
#include "populate_conf_tab.h"
#include "select_first_user.h"
#include "reread_conf.h"
#ifdef USE_DARWIN
#include "osx_functions.h"
#endif
extern char global_server_address[1024];
extern char global_server_port[1024];
extern char global_server_type[1024];
extern int use_ratio;
extern int use_quota;
extern long num_rows;
extern int row_pos;
/* Used globally with dir_treeview_funcs.c */
char *user_profile;
/* Set in dir_treeview_funcs.c */
extern gchar *homedir;
/* Declared in gproftpd.c and set in treeview_dir_funcs */
extern int global_dir_error;
/* Temporary, for option deprecations in proftpd */
extern char global_version[1024];
void add_user(struct w *widgets)
{
/* Adds a new user to the selected server */
FILE *fp;
long conf_size = 0;
long profile_size = 0;
char *old_buffer, *new_buffer;
char *user_check, *address_buffer, *port_buffer;
int length=0, limit_access=0, user_added=0;
int found_server=0, standard_server=0;
gchar *utf8=NULL;
gchar *info, *cmd, *restricted_dir=NULL;
G_CONST_RETURN gchar *br=NULL, *brc=NULL, *fr=NULL, *frc=NULL;
G_CONST_RETURN gchar *username;
G_CONST_RETURN gchar *password;
G_CONST_RETURN gchar *group;
G_CONST_RETURN gchar *comment;
G_CONST_RETURN gchar *shell;
username = gtk_entry_get_text(GTK_ENTRY(widgets->user_set_entry[0]));
password = gtk_entry_get_text(GTK_ENTRY(widgets->user_set_entry[1]));
group = gtk_entry_get_text(GTK_ENTRY(widgets->user_set_entry[2]));
comment = gtk_entry_get_text(GTK_ENTRY(widgets->user_set_entry[3]));
/* Shell is a gtk_combo_box_entry_new_text */
shell = gtk_entry_get_text(GTK_ENTRY(GTK_BIN(widgets->user_set_combo[0])->child));
/* If the shell is false it will add the users directory as /dev/null and the specified false shell.
* If the shell is real it will add the users directory as /USERSHOME/username and the specified real shell.
* The ftp directory will be located where specified in the application.
* This is done so that no user gets sshd/etc access unless the admin wants to.
*/
/* Ratios */
if( use_ratio )
{
br = gtk_entry_get_text(GTK_ENTRY(widgets->user_set_spinbutton[1]));
brc = gtk_entry_get_text(GTK_ENTRY(widgets->user_set_spinbutton[2]));
fr = gtk_entry_get_text(GTK_ENTRY(widgets->user_set_spinbutton[3]));
frc = gtk_entry_get_text(GTK_ENTRY(widgets->user_set_spinbutton[4]));
}
/* If the username field is empty inform that this cant be done. */
length = strlen(username);
if( length == 0 )
{
info = g_strdup_printf(_("You must specify a username.\n"));
show_info(info);
g_free(info);
return;
}
if( username[0]=='0'||username[0]=='1'||username[0]=='2'||username[0]=='3'||username[0]=='4'
|| username[0]=='5'||username[0]=='6'||username[0]=='7'||username[0]=='8'||username[0]=='9')
{
info = g_strdup_printf(_("Failed adding user: %s\nA user name can not begin with a number.\n"), username);
show_info(info);
g_free(info);
return;
}
if( username[0]=='r' && username[1]=='o' && username[2]=='o' && username[3]=='t' && strlen(username) == 4 )
{
info = g_strdup_printf(_("Failed adding user: %s\nThe user root can not be added for security reasons.\n"), username);
show_info(info);
g_free(info);
return;
}
if( strstr((char *)username, "<") || strstr((char *)username, ">") )
{
info = g_strdup_printf(_("Failed adding user: %s\nchars \"<\" and \">\" arent allowed.\n"), username);
show_info(info);
g_free(info);
return;
}
/* If the password field has less then 6 chars we inform that this cant be done */
length = strlen(password);
if( length < 6 )
{
info = g_strdup_printf(_("Failed adding user: %s\nA minimum password length of 6 chars is required.\n"), username);
show_info(info);
g_free(info);
return;
}
/* If the group filed has less then 3 chars we inform that this cant be done */
length = strlen(group);
if( length == 0 )
{
info = g_strdup_printf(_("Failed adding user: %s\nNo group specified.\n"), username);
show_info(info);
g_free(info);
return;
}
/* If the shell field has less then 3 chars we inform that this cant be done */
length = strlen(shell);
if( length < 3 )
{
info = g_strdup_printf(_("Failed adding user: %s\nNo shell specified.\n"), username);
show_info(info);
g_free(info);
return;
}
/* A comment is required */
length = strlen(comment);
if( length == 0 )
{
info = g_strdup_printf(_("A comment is required to add a user.\n"));
show_info(info);
g_free(info);
return;
}
/* Check if the user exists in the selected server */
if((fp=fopen(PROFTPD_CONF,"r"))==NULL)
{
info = g_strdup_printf(_("Failed adding user: %s\nCant open proftpd.conf.\n"), username);
show_info(info);
g_free(info);
return;
}
fseek(fp, 0, SEEK_END);
conf_size = ftell(fp);
rewind(fp);
old_buffer = allocate(conf_size);
user_check = allocate(4096);
snprintf(user_check, 4000, "User %s\n", username);
address_buffer = allocate(8192+15);
port_buffer = allocate(8192+3);
if( strstr((char *)global_server_type, "Virtualhost") )
sprintf(address_buffer, "<VirtualHost %s>\n", global_server_address);
else
standard_server = 1;
sprintf(port_buffer, "Port %s\n", global_server_port);
/* Scroll to the correct vhost */
if( ! standard_server && conf_size > 1 )
while(fgets(old_buffer, conf_size, fp)!=NULL)
{
/* The correct server address is found */
if( ! found_server && ! strcmp(old_buffer, address_buffer) )
{
/* Lets see if its the same port as the selected one */
while(fgets(old_buffer, conf_size, fp)!=NULL)
{
if( strstr(old_buffer, "Port")
&& ! strcmp(old_buffer, port_buffer) )
{
found_server = 1;
break;
}
/* End of vhost, break and check the next vhost */
if( strstr(old_buffer, "</VirtualHost>") )
break;
}
}
/* This vhost has the correct address and port */
if( found_server )
break;
}
/* The selected vhost was not found */
if( ! standard_server && ! found_server )
{
info = g_strdup_printf(_("Failed adding user: %s\nThe selected virtual host was not found.\n"), username);
show_info(info);
g_free(info);
free(old_buffer);
free(user_check);
free(address_buffer);
free(port_buffer);
fclose(fp);
return;
}
/* We have begun at the top for the standard server
* or scrolled to the selected vhost. */
/* Check if the user exists in this selected vhost or standard server */
if( conf_size > 1 )
while(fgets(old_buffer, conf_size, fp)!=NULL)
{
if( ! strcmp(old_buffer, user_check)
&& ! strstr(old_buffer, "AllowUser")
&& ! strstr(old_buffer, "FakeUser") )
{
info = g_strdup_printf(_("Failed adding user: %s\nThe user already exists in this server.\n"), username);
show_info(info);
g_free(info);
free(old_buffer);
free(user_check);
free(address_buffer);
free(port_buffer);
fclose(fp);
return;
}
/* End the search if we are looking for a standard user
and the end of the standard server is found */
if( standard_server && strstr(old_buffer, "<VirtualHost") )
break;
/* End the search if we are looking for a vhost user
and the end of the vhost is found */
if( ! standard_server && strstr(old_buffer, "</VirtualHost") )
break;
}
free(user_check);
free(old_buffer);
fclose(fp);
/* The selected shell is false, add the group and the user with a false shell and home /dev/null */
if( strstr(shell, "nologin") || strstr(shell, "false") || strstr(shell, "dev/null") )
{
/* All supported systems but darwin */
#ifndef USE_DARWIN
/* Add the group if it doesnt exist */
if( ! group_exists(group) )
{
cmd = g_strdup_printf("%s '%s'", ADDGROUP, group);
if( ! run_command(cmd) )
{
info = g_strdup_printf(_("Error adding group: %s\n"), group);
show_info(info);
g_free(info);
}
g_free(cmd);
}
/* Add the user to this group if it doesnt exist */
if( ! user_exists(username) )
{
cmd = g_strdup_printf("%s '%s' -g '%s' -d /dev/null -c '%s' -s %s", ADDUSER, username, group, comment, shell);
if( ! run_command(cmd) )
{
info = g_strdup_printf(_("Failed adding user: %s\n"), username);
show_info(info);
g_free(info);
}
else
user_added = 1;
g_free(cmd);
}
#elif USE_DARWIN
/* Add the false user using darwins niutil commands (Darwin is out of sync) */
if( ! niutil_user_exists(username) )
{
if( ! niutil_useradd(username, shell) )
{
info = g_strdup_printf(_("Failed adding user: %s\n"), username);
show_info(info);
g_free(info);
}
else
user_added = 1;
}
#endif
}
else
{
/* The selected shell is not false add a real user account */
#ifndef USE_DARWIN
/* Add the group if it doesnt exist */
if( ! group_exists(group) )
{
cmd = g_strdup_printf("%s '%s'", ADDGROUP, group);
if( ! run_command(cmd) )
{
info = g_strdup_printf(_("Failed adding group: %s\n"), group);
show_info(info);
g_free(info);
}
g_free(cmd);
}
/* Add the user to this group if it doesnt exist */
if( ! user_exists(username) )
{
/* Add the user with a real shell to /USERSHOME/ UserName (was: -m -s) */
cmd = g_strdup_printf("%s '%s' -g '%s' -d '%s%s' -c '%s' -s %s", ADDUSER, username, group, USERSHOME, username, comment, shell);
if( ! run_command(cmd) )
{
info = g_strdup_printf(_("Failed adding user: %s\n"), username);
show_info(info);
g_free(info);
}
else
user_added = 1;
g_free(cmd);
}
#elif USE_DARWIN
/* Add a real darwin user using the niutil commands (Darwin is out of sync) */
if( ! niutil_user_exists(username) )
{
if( ! niutil_useradd(username, shell) )
{
info = g_strdup_printf(_("Failed adding user: %s\n"), username);
show_info(info);
g_free(info);
}
else
user_added = 1;
}
#endif
}
/* Dont add anything if we couldnt add the system user */
#ifndef USE_DARWIN
if( ! user_exists(username) )
#elif USE_DARWIN
if( ! niutil_user_exists(username) )
#endif
{
info = g_strdup_printf(_("The system user was not added because uppercase\nor language specific letters are not allowed.\n"));
show_info(info);
g_free(info);
free(address_buffer);
free(port_buffer);
return;
}
/* Setup the users profile and create its directories */
/* Set the homedir globally from the first row in the treeview using row_pos=0 */
num_rows = 0;/* Set global num_rows */
gtk_tree_model_foreach(GTK_TREE_MODEL(widgets->directory_store), (GtkTreeModelForeachFunc) num_rows_func, widgets);
if( num_rows < 1 )
{
info = g_strdup_printf(_("Missing ftp home directory. Scroll down and add one first.\n"));
show_info(info);
g_free(info);
return;
}
/* (Global) Statics + entries + (number of rows * dirlen + APPE STOR STOU etc) */
profile_size = 16384 + 1400 + (num_rows * 16384);
/* Allocate the user profile */
user_profile = allocate(profile_size+1);
/* Set the users home dir and its access settings globally */
row_pos = 0; /* Only get the home directory */
gtk_tree_model_foreach(GTK_TREE_MODEL(widgets->directory_store), (GtkTreeModelForeachFunc) dirs_foreach, widgets);
/* Bad directory name or no directory at all */
if( global_dir_error )
{
/* Info is shown in the foreach function */
global_dir_error = 0;
free(user_profile);
if( homedir!=NULL )
g_free(homedir);
return;
}
/* If ratios is used it needs a restricted toplevel directory and
a block all .ftpaccess file in this toplevel directory */
if( use_ratio )
{
/* Make the restricted directory under each users chroot directory */
restricted_dir = g_strdup_printf("%s/%s", homedir, "restricted");
make_dir_chmod((gchar *)restricted_dir, "0777"); /* Must be like this */
/* Add a block all .ftpaccess file to this directory */
cmd = g_strdup_printf("echo \"DenyAll\n\" > %s/.ftpaccess", restricted_dir);
if( ! run_command(cmd) )
{
printf("Error creating .ftpaccess file here: %s/.ftpaccess\n", restricted_dir);
/* Fixme, popup */
}
g_free(cmd);
/* Add the ratio files */
cmd = g_strdup_printf("touch %s/proftpd_ratios %s/proftpd_ratios_temp", restricted_dir, restricted_dir);
if( ! run_command(cmd) )
{
printf("Error creating the ratio files here: %s\n", restricted_dir);
/* Fixme, popup */
}
g_free(cmd);
/* Chmod a+rw on the ratio files (must be a+rw) */
cmd = g_strdup_printf("chmod a+rw %s/proftpd_ratios %s/proftpd_ratios_temp", restricted_dir, restricted_dir);
if( ! run_command(cmd) )
{
printf("Error chmodding the ratio files here: %s\n", restricted_dir);
/* Fixme, popup */
}
g_free(cmd);
/* Chown ratio files to SERVERUSER:SERVERGROUP */
cmd = g_strdup_printf("chown %s:%s %s/proftpd_ratios %s/proftpd_ratios_temp", SERVER_USER, SERVER_GROUP, restricted_dir, restricted_dir);
if( ! run_command(cmd) )
{
printf("Error chmodding the ratio files here: %s\n", restricted_dir);
/* Fixme, popup */
}
g_free(cmd);
g_free(restricted_dir);
}
/* The users configuration profile */
strcpy(user_profile, "\n<Anonymous ");
strcat(user_profile, homedir);
strcat(user_profile, ">\n");
strcat(user_profile, "User ");
strcat(user_profile, username);
strcat(user_profile, "\nGroup ");
strcat(user_profile, group);
strcat(user_profile, "\n");
strcat(user_profile, "AnonRequirePassword on\n");
strcat(user_profile, "MaxClients 5 \"The server is full, hosting %m users\"\n");
strcat(user_profile, "DisplayLogin welcome.msg\n");
/* Fix for a changed directive, sorry Fedora backported... */
// if( strstr(global_version, "1.2.") || strstr(global_version, "1.3.0") )
// strcat(user_profile, "DisplayFirstChdir .msg\n");
// else
// strcat(user_profile, "DisplayChdir .msg\n");
/* Ratio Module (reversed br/fr order in the gui) */
if( use_ratio )
{
strcat(user_profile, "UserRatio ");
strcat(user_profile, username);
strcat(user_profile, " ");
strcat(user_profile, fr);
strcat(user_profile, " ");
strcat(user_profile, frc);
strcat(user_profile, " ");
strcat(user_profile, br);
strcat(user_profile, " ");
strcat(user_profile, brc);
strcat(user_profile, "\n");
}
strcat(user_profile, "<Limit LOGIN>\n");
strcat(user_profile, " Allow from all\n");
strcat(user_profile, " Deny from all\n");
strcat(user_profile, "</Limit>\n");
strcat(user_profile, "AllowOverwrite off\n");
append_limit_cmds();
row_pos = 1; /* Append and create the rest of the directories */
gtk_tree_model_foreach(GTK_TREE_MODEL(widgets->directory_store), (GtkTreeModelForeachFunc) dirs_foreach, widgets);
strcat(user_profile, "</Anonymous>\n");
if( homedir!=NULL )
g_free(homedir);
/* Add the new user settings and AllowUser to the correct server */
found_server = 0;
/* Standard server selected, start adding users directly */
if( standard_server )
found_server = 1;
/* Add AllowUser UserName to the selected server */
if((fp=fopen(PROFTPD_CONF,"r"))==NULL)
{
free(address_buffer);
free(port_buffer);
free(user_profile);
return;
}
fseek(fp, 0, SEEK_END);
conf_size = ftell(fp);
rewind(fp);
old_buffer = allocate(conf_size);
new_buffer = allocate(conf_size+8192);
if( conf_size > 1 )
while(fgets(old_buffer, conf_size, fp)!=NULL)
{
strcat(new_buffer, old_buffer);
if( ! standard_server && ! found_server
&& ! strcmp(old_buffer, address_buffer) )
{
/* Lets see if this is the selected server */
while(fgets(old_buffer, conf_size, fp)!=NULL)
{
strcat(new_buffer, old_buffer);
/* This will expect the servers port on the second line !
* else itll miss some vaules .. */
if( strstr(old_buffer, "Port")
&& ! strcmp(old_buffer, port_buffer) )
{
found_server = 1;
break;
}
if( strstr(old_buffer, "</Virtualhost>") )
break;
}
}
/* Continue until we find the selected server */
if( ! found_server )
continue;
/* Add AllowUser Username .. to this server only */
if( strstr(old_buffer, "<Limit LOGIN")
&& found_server && ! limit_access )
{
strcat(new_buffer, " AllowUser ");
strcat(new_buffer, username);
strcat(new_buffer, "\n");
limit_access = 1; /* just incase so we just change the first occurance */
/* Add the user after </Limit> */
while(fgets(old_buffer, conf_size, fp)!=NULL)
{
strcat(new_buffer, old_buffer);
if( strstr(old_buffer, "</Limit") && limit_access == 1 )
{
/* Only add it once */
limit_access = 2;
strcat(new_buffer, user_profile);
}
}
}
/* Add the new user settings if we have another user (once) */
if( strstr(old_buffer, "</Anonymous") && limit_access == 1 )
{
/* Only add it once */
limit_access = 2;
strcat(new_buffer, user_profile);
}
}
fclose(fp);
free(old_buffer);
free(address_buffer);
free(port_buffer);
free(user_profile);
/* Password the user if it didnt exist before */
if( user_added )
{
#ifndef USE_DARWIN
password_user(username, password);
#elif USE_DARWIN
niutil_password_user(username, password);
#endif
}
else
{
info = g_strdup_printf(_("The system user \"%s\" already exists.\nThe user was added to this server but the password was not changed.\n"), username);
show_info(info);
g_free(info);
}
/* Write the new configuration if the user profile was added.
* Since the user could have already existed we use limit_access */
if( limit_access )
{
if((fp=fopen(PROFTPD_CONF,"w+"))==NULL)
{
info = g_strdup_printf(_("Could not write the new user configuration to:\n%s\nRun gproftpd as root\n"), PROFTPD_CONF);
show_info(info);
g_free(info);
free(new_buffer);
return;
}
else
{
fputs(new_buffer, fp);
fclose(fp);
}
}
free(new_buffer);
fix_newlines_in_conf();
/* Update the user list and the user settings */
populate_users(widgets);
select_first_user(widgets);
populate_user_settings(widgets);
populate_conf_tab(widgets);
/* Update the server */
reread_conf(widgets);
if( utf8!=NULL )
g_free(utf8);
}
syntax highlighted by Code2HTML, v. 0.9.1