/* * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * Copyright (C) 2000-2002 * Andrey Aristarkhov . * All rights reserved * Partial Copyright (C) 1996 * David L. Nugent. All rights reserved. * * $Id: user.c,v 1.8.2.6 2002/08/16 15:48:42 dron Exp $ * * Frontend functions for CVS' passwords manupulation * */ #include "user.h" #include "cvs.h" #ifndef safe_string #define safe_string(s) s ? s : "null" #endif static char pathpwd[MAXPATHLEN]; static char * pwpath = pathpwd; static int pw_fileupdate(char const * filename, mode_t fmode, char const * newline, char const * prefix, int pfxlen, int updmode) { int rc = 0; int updated = PWD_CREATE; int linesize = PWBUFSZ; char *line; FILE *outfp; FILE *infp; int outfd; char file[MAXPATHLEN]; int infd; if (pfxlen <= 1) rc = EINVAL; else { infd = open(filename, O_RDWR | O_CREAT, fmode); if (infd == -1) rc = errno; else { infp = fdopen(infd, "r+"); if (infp == NULL) { rc = errno; /* Assumes fopen(3) sets errno from open(2) */ close(infd); } else { strcpy(file, filename); strcat(file, ".new"); #ifdef __FreeBSD__ outfd = open(file, O_RDWR | O_CREAT | O_TRUNC | O_EXLOCK, fmode); #else outfd = open(file, O_RDWR | O_CREAT | O_TRUNC , fmode); #endif if (outfd == -1) rc = errno; else { outfp = fdopen(outfd, "w+"); if (outfp == NULL) { rc = errno; close(outfd); } else { updated = PWD_CREATE; linesize = PWBUFSZ; line = malloc(linesize); while (fgets(line, linesize, infp) != NULL) { char *p = strchr(line, '\n'); while ((p = strchr(line, '\n')) == NULL) { int l; expand_string(&line, &linesize, linesize + PWBUFSZ); l = strlen(line); if (fgets(line + l, linesize - l, infp) == NULL) break; } if (*line != '#' && *line != '\n') { if (!updated && strncmp(line, prefix, pfxlen) == 0) { updated = updmode == PWD_UPDATE ? PWD_UPDATE : PWD_DELETE; /* * Only actually write changes if updating */ if (updmode == PWD_UPDATE) strcpy(line, newline); else if (updmode == PWD_DELETE) continue; } } fputs(line, outfp); } /* * Now, we need to decide what to do: If we are in * update mode, and no record was updated, then error If * we are in insert mode, and record already exists, * then error */ if (updmode != updated) /* -1 return means: * update,delete=no user entry * create=entry exists */ rc = -1; else { /* * If adding a new record, append it to the end */ if (updmode == PWD_CREATE) fputs(newline, outfp); /* * Flush the file and check for the result */ if (fflush(outfp) == EOF) rc = errno; /* Failed to update */ else { /* * Copy data back into the * original file and truncate */ rewind(infp); rewind(outfp); while (fgets(line, linesize, outfp) != NULL) fputs(line, infp); /* * If there was a problem with copying * we will just rename 'file.new' * to 'file'. * This is a gross hack, but we may have * corrupted the original file * Unfortunately, it will lose the inode * and hence the lock. */ if (fflush(infp) == EOF || ferror(infp)) rename(file, filename); else ftruncate(infd, ftell(infp)); } } free(line); fclose(outfp); } remove(file); } fclose(infp); } } } return rc; } static void setpwpath(const char * file) { strcpy(pathpwd,file); } static char * getpwpath() { static char pathbuf[MAXPATHLEN]; strcpy(pathbuf,pathpwd); return pathbuf; } /* * Return values: * -1 -- generic error * 0 -- success * 1 -- user is empty or NULL * 2 -- passwd is empty or NULL */ static int fmtpwentry(char **buf, const char *user, const char * passwd, const char * alias) { int l; int has_alias; char *pw; if (user == NULL || strlen(user) == 0) { return 1; } else if (passwd == NULL || strlen(passwd) == 0) { return 2; } if (alias == NULL || strlen(alias) == 0) { has_alias = 0; } else { has_alias = strlen(alias); } l = strlen(passwd)+strlen(user) +has_alias+ 2; pw = (char *)malloc(l); if (has_alias) { sprintf(pw,"%s:%s:%s\n",user,passwd,alias); } else { sprintf(pw,"%s:%s\n",user,passwd); } *buf = pw; return 0; } static int pw_update(const char * user, const char * passwd, const char * alias, int mode) { int rc = 0; char pfx[32]; char * pwbuf; int l = sprintf(pfx, "%s:", user); /* * Update passwd file */ if (mode != PWD_DELETE) { rc = fmtpwentry(&pwbuf,user,passwd,alias); if (rc != 0) { return rc; } } else { pwbuf = malloc(strlen(user)+2); sprintf(pwbuf,"%s:",user); } rc = pw_fileupdate(getpwpath(), 0644, pwbuf, pfx, l, mode); if (pwbuf != NULL) { free(pwbuf); } return rc; } #ifndef CHARSET_EBCDIC #define LF 10 #define CR 13 #else /*CHARSET_EBCDIC*/ #define LF '\n' #define CR '\r' #endif /*CHARSET_EBCDIC*/ static int getline(char *s, int n, FILE *f) { register int i = 0; while (1) { s[i] = (char) fgetc(f); if (s[i] == CR) { s[i] = fgetc(f); } if ((s[i] == 0x4) || (s[i] == LF) || (i == (n - 1))) { s[i] = '\0'; return (feof(f) ? 1 : 0); } ++i; } } #define MAX_STRING_LEN 256 static char * get_password(const char * user) { static char pwd[MAX_STRING_LEN]; char line[MAX_STRING_LEN]; FILE * fpw=NULL; char * token; fpw = fopen (getpwpath(), "r"); if (fpw == NULL) { return NULL; } memset(pwd,0,MAX_STRING_LEN); while (!(getline (line, MAX_STRING_LEN, fpw))) { if (line[0] == '#') { continue; } token = strtok(line,":"); if (token == NULL) { continue; } if (strcmp (user, token) != 0) { continue; } /* User found */ token = strtok(NULL,":"); strcpy(pwd,token); fclose(fpw); return pwd; } fclose(fpw); return NULL; } static char * get_alias(const char * user) { static char alias[MAX_STRING_LEN]; char line[MAX_STRING_LEN]; FILE * fpw; char * token; fpw = fopen (getpwpath(), "r"); while (!(getline (line, sizeof (line), fpw))) { if (line[0] == '#') { continue; } token = strtok(line,":"); if (strcmp (user, token) != 0) { continue; } /* User found */ token = strtok(NULL,":"); token = strtok(NULL,":"); if (token == NULL) { return NULL; } strcpy(alias,token); fclose(fpw); return alias; } fclose(fpw); return NULL; } /*extern char * crypt(const char *, const char *);*/ static char const chars[] = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ."; static char * pw_crypt(const char *password) { int i; char salt[12]; static char buf[256]; /* * Calculate a salt value */ #ifdef __FreeBSD__ srandomdev(); #else srandom((unsigned long) (time(NULL) ^ getpid())); #endif for (i = 0; i < 8; i++) salt[i] = chars[random() % 63]; salt[i] = '\0'; return strcpy(buf, crypt(password, salt)); } /* * Return values: * -1 - user exists * 0 - user added, otherwise another code */ static int pw_add_user(const char * user, const char * passwd, const char * alias) { char * newpasswd; int res; if (trace) { printf("%s->pw_add_user: user=%s, passwd=%s, alias=%s\n",CLIENT_SERVER_STR,safe_string(user),safe_string(passwd),safe_string(alias)); fflush(stdout); } if (get_password(user) != NULL) { return -1; } if (passwd == NULL || strlen(passwd) == 0) { newpasswd=xstrdup("*"); } else { newpasswd = xstrdup(pw_crypt(passwd)); } res = pw_update(user,newpasswd,alias,PWD_CREATE); free(newpasswd); return res; } /* -1 - user does not exists, otherwise 0 */ static int pw_mod_user(const char * user, const char * passwd, const char * alias) { char * newpasswd; #ifdef NEVER if (trace) { printf("%s->pw_mod_user: user=%s, passwd=%s, alias=%s\n",CLIENT_SERVER_STR,safe_string(user),safe_string(passwd),safe_string(alias)); fflush(stdout); } #endif if ((newpasswd = get_password(user)) == NULL) { return -1; } if (passwd != NULL) { newpasswd = pw_crypt(passwd); } if (alias == NULL) alias = get_alias(user); return pw_update(user,newpasswd,alias,PWD_UPDATE); } /* -1 - user does not exists */ static int pw_del_user(const char * user) { if (get_password(user) == NULL) { return -1; } return pw_update(user,NULL,NULL,PWD_DELETE); } static const char *const password_usage[] = { "Usage: %s %s\n", /* "\tIf no \"username\" is given password will be set for the current user\n", "\t\"username\"\tUse it if you want to change password for the specified user\n"*/ "(Specify the --help global option for a list of other help options)\n", NULL }; static const char *const user_usage[] = { "Usage: %s %s <[-a | -m | -d] username> [-u alias] [-p | -P password]\n", "\t-a|-m|-d\t'add', 'modify' or 'delete' user respectively\n", "\t-u\tUse \"alias\" to specify system user for cvs-user.\n", "\t-P\tUse \"password\" to specify user password in a command line OR\n", "\t-p\tenter user password interactively\n", "(Specify the --help global option for a list of other help options)\n", NULL }; enum pwd_read_mode { PRM_NEW, PRM_ADMIN, PRM_CHECK }; static void verify_password_phrase(const char * password, unsigned char complete_check) { int plen, i; if (!password) { error(1,0,"Failed to read password"); } if (!complete_check) return; plen = strlen(password); for (i=0;i 126) { error(1,0,"Invalid character in password (0x%x). Password can contain only printable characters.",password[i]); } } if (plen < MIN_PASSWORD_LEN) { error(1,0,"Password length can't be less than %d characters",MIN_PASSWORD_LEN); } if (plen > MAX_PASSWORD_LEN) { error(1,0,"Password length can't be more than %d characters",MAX_PASSWORD_LEN); } } /* * Reads and verifies user's password from an input * Return password for the user */ static char * read_user_password(const char * user, int pwd_read_mode) { char prompt[128]; char * pwd, * pwd_verify; static char password[_PASSWORD_LEN]; memset(password,0,_PASSWORD_LEN); switch (pwd_read_mode) { case PRM_NEW: sprintf(prompt,"Enter New password for user '%s':",user); /* We need to strdup because GETPASS uses static object */ pwd_verify = GETPASS(prompt); verify_password_phrase(pwd_verify,TRUE); pwd = strdup(pwd_verify); memset(pwd_verify,0,strlen(pwd_verify)); sprintf(prompt,"Re-type New password for user '%s':",user); pwd_verify = GETPASS(prompt); verify_password_phrase(pwd_verify,FALSE); if (strcmp(pwd,pwd_verify)) { free(pwd); error(1,0,"Passwords are different"); } else { free(pwd); strcpy(password,pwd_verify); memset(pwd_verify,0,strlen(pwd_verify)); } break; case PRM_CHECK: pwd = get_password(user); sprintf(prompt,"Enter current password for user '%s': ",user); pwd_verify = GETPASS(prompt); verify_password_phrase(pwd_verify,FALSE); if (strcmp(pwd,crypt(pwd_verify,pwd))==0) { strcpy(password,pwd); } else { error(1,0,"Wrong password",user); } break; case PRM_ADMIN: pwd = get_password(CVS_ADMIN_USER); if (pwd == NULL) error(1,0,"Administrator's user accout not found. Please contact your CVS admin."); if (strcmp(pwd,"*")==0) error(1,0,"Password for Administrator's user accout is not set. Please contact your CVS admin."); pwd_verify = GETPASS("Enter CVS Administrator password: "); verify_password_phrase(pwd_verify,FALSE); if (strcmp(pwd,crypt(pwd_verify,pwd))==0) { strcpy(password,pwd); } else { error(1,0,"Administrator password is invalid"); } break; } return password; } static void set_password_path(cvsroot_t * root) { char passwdFile[MAXPATHLEN]; sprintf(passwdFile,"%s/%s/%s",root->directory,CVSROOTADM,CVSROOTADM_PASSWD); setpwpath(passwdFile); } static char * get_current_user(cvsroot_t * root) { char * user = root->username; if (user == NULL) { user=getcaller(); } if (user==NULL) { error(1,0,"Fatal error: Can't detect current user."); } /*# It's impossible in pserver mode */ return user; } int password(int argc, char ** argv) { char * user, * passwd, * passwd_to_send, * curr_password; char prompt[64]; user = passwd = passwd_to_send = NULL; /* * Hacking "init" command */ if (argc == -2) { /* called after mkmodules() in init() */ char passwdFile[MAXPATHLEN]; sprintf(passwdFile,"%s/%s",argv[0],CVSROOTADM_PASSWD); setpwpath(passwdFile); /* If password faile exists, remove it */ /*remove(passwdFile);*/ /* constructing password. Need more relyable algorithm. */ sprintf(passwdFile,"admin%d",getpid()); error(0,0,"Creating administrator account '%s': password is '%s'.",CVS_ADMIN_USER,passwdFile); if (pw_add_user(CVS_ADMIN_USER,passwdFile,NULL) > 0) { error(0,errno,"Failed to add administrator account"); } #ifdef NEVER if (trace) { printf("%s->user=%s added\n",CLIENT_SERVER_STR,safe_string(user)); fflush(stdout); } #endif return 0; } /* End of init section */ if (!server_active && current_parsed_root->method != pserver_method) { error (0, 0, "can only use `password' command with the 'pserver' method"); error (1, 0, "CVSROOT: %s", current_parsed_root->original); } if (argc == -1) usage(password_usage); #ifdef SERVER_SUPPORT if (server_active) { if (argc < 2) { printf("E protocol error: can't read password\n"); return 1; } if (argc > 2) { printf("E protocol error: too many parameters\n"); return 1; } passwd = descramble(argv[1]); } #else if (argc > 1 || argc == -1) { usage(password_usage); } #endif user = get_current_user(current_parsed_root); if (!server_active) { curr_password = get_cvs_password(); if (curr_password==NULL) { error(1,0,"You are not logged into CVSROOT=%s. Try to login first.",current_parsed_root->original); } sprintf(prompt,"Enter current password for user '%s': ",user); passwd = GETPASS(prompt); curr_password = descramble(curr_password); if (strcmp(curr_password,passwd)!=0) { error(1,0,"Wrong password. Command aborted."); } memset(curr_password,0,strlen(curr_password)); free(curr_password); memset(passwd,0,strlen(passwd)); passwd = read_user_password(user,PRM_NEW); passwd_to_send = scramble(passwd); #ifdef CLIENT_SUPPORT start_server (); ign_setup (); send_arg (passwd_to_send); send_to_server("password\012",0); memset(passwd_to_send,0,strlen(passwd_to_send)); free(passwd_to_send); return get_responses_and_close(); #endif /* CLIENT_SUPPORT */ } #ifdef SERVER_SUPPORT set_password_path(current_parsed_root); if (pw_mod_user(user,passwd,NULL)==-1) { free(passwd); error(0,0,"User '%s'not found",user); return 1; } return 0; #endif } #define checkOper(op) if (##op!= PWD_UNDEFINED) {\ error(0,0,"Choose one of of options -a | -m | -d");\ usage(user_usage);\ } int user(int argc, char ** argv) { enum updtype op; char * user, * curr_user, * passwd, * alias, * curr_password; char * cvsroot; int res; char c; unsigned char hasPassword, needPassword; user = alias = passwd = NULL; op = PWD_UNDEFINED; hasPassword = needPassword = 0; #ifdef NEVER if (trace) { printf("%s->cvs user: server_active: %d, argc=%d, argv[0]=%s, argv[1]=%s\n",CLIENT_SERVER_STR,server_active,argc,safe_string(argv[0]),safe_string(argv[1])); fflush(stdout); } #endif /* parse args */ if (argc == -1) usage(user_usage); optind = 0; while ((c = getopt (argc, argv, "+a:d:m:u:pP:")) != -1) { switch (c) { case 'a': checkOper(op); op = PWD_CREATE; user = optarg; break; case 'm': checkOper(op); op = PWD_UPDATE; user = optarg; break; case 'd': checkOper(op); op = PWD_DELETE; user = optarg; break; case 'u': alias = optarg; break; case 'P': hasPassword = 1; passwd = optarg; break; case 'p': needPassword = 1; break; case '?': default: usage (user_usage); break; } } argc -= optind; argv += optind; if (op == PWD_UNDEFINED) { usage(user_usage); } if (hasPassword && needPassword) { error(0,0,"-p and -P options are mutually exclusive"); usage(user_usage); } /* end of parse args */ curr_user = get_current_user(current_parsed_root); if ( strcmp(curr_user, CVS_ADMIN_USER) != 0) { error(1,0,"You are not an administrator"); } if (!server_active) { char * tmppass; curr_password = get_cvs_password(); if (curr_password==NULL) { error(1,0,"You are not logged into CVSROOT=%s. Try to login first.",current_parsed_root->original); } tmppass = GETPASS("Enter CVS Administrator password: "); if (!tmppass || strlen(tmppass)==0) { error(1,0,"Failed to read password. Command aborted."); } curr_password = descramble(curr_password); if (trace) { printf("OLD: %s, CUR: %s\n",curr_password,tmppass); fflush(stdout); } if (strcmp(curr_password,tmppass)!=0) { memset(curr_password,0,strlen(curr_password)); free(curr_password); error(1,0,"Wrong password. Command aborted."); } memset(curr_password,0,strlen(curr_password)); free(curr_password); memset(tmppass,0,strlen(tmppass)); if (needPassword) { passwd = read_user_password(user,PRM_NEW); } #ifdef CLIENT_SUPPORT if (trace) { printf("%s->cvs user: operation: %d, user=%s, passwd=%s(%d), alias=%s\n",CLIENT_SERVER_STR,op,safe_string(user),safe_string(passwd),needPassword | hasPassword,safe_string(alias)); fflush(stdout); } start_server (); ign_setup (); if (trace) { printf("%s->sending options\n",CLIENT_SERVER_STR); fflush(stdout); } if (op==PWD_CREATE) { send_arg ("-a"); } else if (op==PWD_UPDATE) { send_arg ("-m"); } else if (op==PWD_DELETE) { send_arg ("-d"); } send_arg(user); if (alias != NULL) { send_arg("-u"); send_arg(alias); } if (needPassword | hasPassword) { curr_password = scramble(passwd); send_arg("-P"); send_arg (curr_password); } else { curr_password = NULL; } send_to_server("admin-user\012",0); if (curr_password) { free(curr_password); memset(curr_password,0,strlen(curr_password)); } if (passwd) memset(passwd,0,strlen(passwd)); if (trace) { printf("%s->waiting response\n",CLIENT_SERVER_STR); fflush(stdout); } return get_responses_and_close(); #endif /* CLIENT_SUPPORT */ } else {/* (!server_active) */ #ifdef SERVER_SUPPORT if (hasPassword) passwd = descramble(passwd); /* Client scrmble it before sending */ else passwd = NULL; #endif } set_password_path(current_parsed_root); if (trace) { printf("%s->cvs user: operation: %d, user=%s, passwd=%s, alias=%s\n",CLIENT_SERVER_STR,op,safe_string(user),safe_string(passwd),safe_string(alias)); fflush(stdout); } switch (op) { case PWD_CREATE: res = pw_add_user(user,passwd,alias); if (res == -1) { error(1,0,"User '%s' already exists.",user); } if (res != 0) { error(1,errno,"Can't add user '%s' - Fatal internal error.",user); } error(0,0,"User '%s' successefully added.",user); break; case PWD_UPDATE: res = pw_mod_user(user,passwd,alias); if (res == -1) { error(1,0,"User '%s' not found.",user); } if (res != 0) { error(1,errno,"Can't add user '%s' - Fatal internal error.",user); } error(0,0,"User '%s' successefully updated.",user); break; case PWD_DELETE: if (pw_del_user(user) == -1) { error(1,0,"Can't delete user '%s' - user not found.",user); } else { error(0,0,"User '%s' successefully deleted.",user); } break; } if (server_active && passwd!=NULL) free(passwd); return 0; }