aboutsummaryrefslogblamecommitdiff
path: root/passgen.c
blob: b45e7b6eb33ac8b9e83c320a82833f46de5541bf (plain) (tree)
1
2
3
4
5
6
7
8
9
10
11






                    



                              


                                                 

      
 
                                            
                     
                                              
                          
     
                      
      



                              
     
                    
                         




                                                  


      














                                                           

 
                   
                          

                   
 










                                                            



                                                       

                                                                  


                                            
                

 
                          
 
                   
                     
                              


                                                         
     
               
      
             

 


                                                           
                                                                               
 
                                                      







                                             
     
                             
 




                                              




                                                            

                                
                                
                                
                                                   
                                  

                    





                                                      
                   


                                                                                                                        
                                                        
                                                                             



                                                                                   

                     
                                                


                                       
          
                                                        
                                                                                 
           
                                                                                            
                              
                                                                                          
                                    
                         
         

     



                                            
                                    

                         
     
                               
 
                      
                                                              
                                

                     
 

                                            

                                         
                           
 







                                             
                                                                           
                                    
                         


            


                                                                  
                                                           
     
 


                             


                                    

                                                                           
                      
                                 
                       
 
                    
 
#include "config.h"

#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <string.h>

#if    defined (__linux__) \
    || defined (__APPLE__) \
    || defined (__FreeBSD__) \
    || defined (__OpenBSD__)
#   define USE_GETENTROPY
#elif defined (_WIN32) && ! defined (__MINGW32__)
#   define USE_WINCRYPT
#endif


/* getentropy() vs rand()+time()+getpid() */
#ifdef USE_GETENTROPY
#if defined (__linux__) || defined (__APPLE__)
#   include <sys/random.h>
#else
#   include <unistd.h>
#endif
#elif defined (USE_WINCRYPT)
#   define WIN32_LEAN_AND_MEAN
#   include <Windows.h>
#   include <wincrypt.h>
#else
#   include <time.h>
/* getpid() on Windows */
#   if defined (_WIN32) && ! defined (__MINGW32__)
#       include <io.h>
#   else
#       include <unistd.h>
#   endif
#endif


#define CLASS(ch, chars) \
    { \
        .c = ch, \
        .letters = chars, \
        .size = sizeof(chars) - 1, \
    },
struct grammar_class {
    char c;
    char *letters;
    int size;
};
struct grammar_class classes[] = {
    CLASSES
};
int const classes_n = sizeof(classes) / sizeof(classes[0]);


#ifdef USE_WINCRYPT
HCRYPTPROV win_rng = NULL;
#endif
bool init_rng(void)
{
#ifdef USE_WINCRYPT
    CryptAcquireContext(
        &win_rng,
        NULL,
        NULL,
        PROV_RSA_FULL,
        CRYPT_VERIFYCONTEXT | CRYPT_SILENT
    );
    if (!win_rng)
        return false;
#elif ! defined (USE_GETENTROPY) && ! defined (USE_WINCRYPT)
    /*
     * TODO: seed better RNG
     * this isn't very good, but it's enough(?) for now
     *
     * anything else we could use on general posix systems?
     * perhaps /dev/urandom is a better fallback before doing this
     */
    srand(time(NULL) + getpid() % 420 - 69);
#endif
    return true;
}

unsigned int get_rng(void)
{
    unsigned int r;
#ifdef USE_GETENTROPY
    getentropy(&r, sizeof(r));
#elif defined (USE_WINCRYPT)
    /* TODO: This could fail. Figure out how to handle */
    CryptGenRandom(win_rng, sizeof(r), (BYTE *) &r);
#else
    r = rand();
#endif
    return r;
}

char grammar_buf[STATIC_BUFFER_SIZE + 1] = DEFAULT_GRAMMAR;
char password_buf[STATIC_BUFFER_SIZE + 1];

char *build_grammar(int triplets, int specials, int numbers, int *grammar_size)
{
    *grammar_size = triplets * 3 + specials + numbers;

    char *build = grammar_buf;
    if (*grammar_size > STATIC_BUFFER_SIZE) {
        build = malloc(*grammar_size + 1);
        if (build == NULL) {
            perror("malloc");
            return NULL;
        }
    }
    build[*grammar_size] = 0;

    if (triplets > 0) {
        memcpy(build, "Cvc", 3);
        for (int i = 1; i < triplets; ++i)
            memcpy(build + (i * 3), "cvc", 3);
    }
    memset(build + (triplets * 3), '!', specials);
    memset(build + (triplets * 3) + specials, '#', numbers);
    return build;
}

int main(int argc, char *argv[])
{
    int ret_code = EXIT_SUCCESS;
    char *grammar = grammar_buf;
    int grammar_size = sizeof(DEFAULT_GRAMMAR) - 1;
    char *password = password_buf;

    if (argc == 2) {
        if (strcmp(argv[1], "--help") == 0) {
            printf("\
passgen - a small, customisable password generator\n\
Usage: %s\n\
       %s <grammar>\n\
       %s <#triplets ('Cvc')> <#symbols> <#numbers>\n\
       %s --help\n\
Hints:\n\
  - No arguments generates with the default grammar.\n\
  - If three numbers are provided, a password will be generated in the 'standard form', <triplets><symbols><numbers>.\n\
Compile-time options (edit `config.h` to customise!):\n\
  - Default grammar: %s\n", argv[0], argv[0], argv[0], argv[0], grammar_buf);
            printf("  - Grammar mappings:\n");
            for (int i = 0; i < classes_n; ++i) {
                printf("    - '%c' => \"%s\"\n", classes[i].c, classes[i].letters);
            }
            return 0;
        }
        /* Take first argument as the grammar */
        grammar = argv[1];
        grammar_size = strlen(grammar);
    } else if (argc == 4) {
        /*
         * Take arguments as triplets, specials, numbers
         * atoi might be scuffed but so be it (it just goes = 0 if invalid input)
         */
        grammar = build_grammar(atoi(argv[1]), atoi(argv[2]), atoi(argv[3]), &grammar_size);
        if (grammar == NULL) {
            fprintf(stderr, "ERROR: Could not initialise memory for password grammar.\n");
            ret_code = EXIT_FAILURE;
            goto cleanup;
        }
    }

    if (grammar_size > STATIC_BUFFER_SIZE) {
        password = malloc(grammar_size + 1);
        if (password == NULL) {
            perror("malloc");
            ret_code = EXIT_FAILURE;
            goto cleanup;
        }
    }
    password[grammar_size] = 0;

    if (!init_rng()) {
        fprintf(stderr, "ERROR: Could not initialise RNG.\n");
        ret_code = EXIT_FAILURE;
        goto cleanup;
    }

    for (int i = 0; i < grammar_size; ++i) {
        char c = grammar[i];
        bool caps = c >= 'A' && c <= 'Z';
        if (caps)
            c += 'a' - 'A';

        struct grammar_class *class = NULL;
        for (int j = 0; j < classes_n; ++j) {
            if (c == classes[j].c) {
                class = &classes[j];
                break;
            }
        }
        if (class == NULL) {
            fprintf(stderr, "ERROR: Invalid grammar character '%c'.\n", c);
            ret_code = EXIT_FAILURE;
            goto cleanup;
        }

        do {
            password[i] = class->letters[get_rng() % class->size];
            if (caps && password[i] >= 'a' && password[i] <= 'z')
                password[i] -= 'a' - 'A';
        } while (i != 0 && password[i] == password[i - 1]);
    }

    printf("%s\n", password);

cleanup:
#ifdef USE_WINCRYPT
    CryptReleaseContext(win_rng, 0);
#endif
    /* TODO: this && kinda sucks, would like a better but still fast way */
    if (grammar != grammar_buf && grammar != argv[1])
        free(grammar);
    if (password != password_buf)
        free(password);

    return ret_code;
}